Check out Janggi (Korean Chess), our featured variant for December, 2024.


[ Help | Earliest Comments | Latest Comments ]
[ List All Subjects of Discussion | Create New Subject of Discussion ]
[ List Earliest Comments Only For Pages | Games | Rated Pages | Rated Games | Subjects of Discussion ]

Comments/Ratings for a Single Item

EarliestEarlier Reverse Order LaterLatest
GAME code table-driven move generator[Subject Thread] [Add Response]
H. G. Muller wrote on Sun, Aug 2, 2020 11:04 AM UTC:

I ran again into a problem. I am trying to generate an array of squares reachable by a ride in a given direction, through

set tosqrs ride #cursqr #dx #dy

where cursqr is a formal parameter to the NextLeg subroutine, and dx and dy are my variables. When I follow this by

print join "from=" #cursqr;
print join "dx=" #dx;
print join "dy=" #dy;
print join "leg=" #legindex;
printr tosqrs;

it prints something like this, though:

from=f8
dx=0
dy=-2
leg=48
Array
(
    [0] => Array
        (
            [0] => f6
        )
    [1] => -2
)

Instead of the expected array of squares I get an array that always has two elements, where the second element always has the value of dy and the first element is always an array containing a single square, namely the first square on the path. It looks a lot like the ride operator fails to absorb its third argument, leaving it for the set command to group it with the returned result into an array. Yet it must have seen the operand, because the first square in the array it returns took it into account. And it fails to return the remaining squares on the path; f4 would also have been empty.

[Edit] It seems the array that is the first element does not always contain just one square. Apparently it continues the ride for as long as the squares are occupied, rather than empty...


🕸Fergus Duniho wrote on Sun, Aug 2, 2020 04:23 PM UTC in reply to H. G. Muller from 11:04 AM:

Okay, I corrected two problems with it, and it works now. I changed the while condition at the end from $space[$c] != "@" to $space[$c] == "@", and I added a break at the end to keep it from executing the code after the next case statement.


H. G. Muller wrote on Sun, Aug 2, 2020 06:32 PM UTC in reply to Fergus Duniho from 04:23 PM:

Indeed it works now. And I changed the plans a little. As e.p. capture cannot be done without creating e.p. rights, and this creation is a side effect of regular leaps and rides, implementing generation of the latter is the logical next step. To get some idea of whether the code is working, it will be used for highlighting pseudo-legal moves. Because such highlighting has to be done from the Post-Game sections, we first have to fill those, with gosub GameEnd false; and gosub GameEnd true;  for white and black, respectively. As Game Courier wants to have the complete list of moves for all pieces in advance, we also need a routine GenAll that calls GenMove for all pieces of a given player (which is the opponent of the player passed to GameEnd). This is pretty much standard:

sub GenAll player:
  my from piece;
  set hit false;
  if #player:
    def friends onlylower;
  else:
    def friends onlyupper;
  endif;
  for (from piece) fn friends:
    gosub GenMoves #piece #from 1;
  next;
endsub;

For now the only task of the GameEnd routine is to use this  'all-moves' generator for filling the array of legal moves; a new task (2) is defined in GotMove to do a setlegal #orisqr #destsqr there. In a later stage this would only have to be done for moves that need explicit specification of side effects; we don't want to encourage the user to click the destination of such moves without typing the side effects! The routine GameEnd just specifies the task, and inverts the player.

sub GameEnd player:
  set task 2;
  set side not #player;
  gosub GenAll #side;
  drawn;
endsub;

That is all pretty straightforward. The real work is done by NextLeg, which is now expanded to:

sub NextLeg togo legindex startsqr cursqr locustsqr dropsqr iso:
  my range dx dy mode to tosqrs k len newindex hx hy;
  // retrieve leg description (next 4 elements in 'legdefs')
  set rng elem #legindex #legdefs;
  set dx elem + 1 #legindex #legdefs;
  set dy elem + 2 #legindex #legdefs;
  set mode elem + 3 #legindex #legdefs;
  verify not flag #startsqr or not & 64 #mode; // 64 = initial
  set tosqrs ray #cursqr #dx #dy;         // squares along ray in this direction
  set r count #tosqrs;                    // view distance
  verify #r;                              // stop if nowhere to go
  // here we can shorten the range for non-jumping moves
  dec togo;                               // count this leg done
  // here we can set dropsqr for legs that unload
  set r min #rng #r;                      // farthest we can get (at least 1!)
  // here we can treat the 'iso' length request, and else
  if & 1 #mode:                           // leg includes free ride
    set k 0;
    do while < #k dec #r:                 // for non-final (and thus empty) squares
      set to elem #k #tosqrs;
      if not #togo:                       // no more legs follow
        gosub GotMove #startsqr #to #locustsqr #dropsqr 0 0;
      endif;
      inc k;
    loop;
  endif;
  // at this point only the move with exactly r steps can be left
  set len cond == 1 #rng #iso #r;           // if leg is slide, remember its length
  set to elem dec #r #tosqrs;               // target square, r steps away along ride
  set stopper space #to;                    // what is on it
  if == @ stopper:                          // target is empty
    verify & 1 #mode;                       // 1 = non-capture
  else:                                     // target is occupied
    // here we can handle hopping
    set side islower space #startsqr;       // whether we are black
    set fratricide cond #side islower #stopper isupper #stopper;
    if #fratricide:                         // target is friend
      if & 8 #mode:                         // 8 = first leg of castling
        verify match #to #partners;         // ends at castling partner
        verify not flag #to;                // partner is virgin
        set locustsqr #to;                  // order its removal
        set to where #startsqr elem + 5 #legindex #legdefs 0;
        set dropsqr where #to - 0 #dx #dy;  // make it appear on other side of King
        gosub GotMove #startsqr #to #locustsqr #dropsqr #stopper 1;
        return;
      endif;
      // here we can handle move induction
      verify & 4096 #mode;                  // 4096 = destroy (friendly capture)
    else:                                   // target is foe
      verify & 2 #mode:                     // 2 = capture
      // TODO: check-only moves
    endif;
  endif;
  if not #togo:
    gosub GotMove #startsqr #to #locustsqr #dropsqr #stopper 0;
  endif;
endsub;

The task is to generate all possible realizations of one leg of the move, where the leg is a ride that is specified in the legdefs table (at legindex) as its range, x-y step and 'mode'. The latter is a set of bit flags packed into an integer, which specifies what the leg can do (e.g. capture). For most legs the destination will be unique, e.g. because they are leaps, or because the mode requires a certain kind of occupant on the destination. And by definition a ride can only have one occupied square in it, at which it must terminate. The only case where a leg as several alternative destinations is thus when a true rider makes a non-capture move. In this case NextLeg has to loop over the destinations, and generate a move for each of them.

The algorithm of NextLeg roughly has three parts. First it determines the farthest square of the ride, taking into account the board size and occupation, the range of the leg, and whether a specific length was requested (to match that of an earlier leg). Then, if the mode allows non-captures, it loops over all squares up to that final square, which are guaranteed to be empty. Finally it determines if a move to the final square is possible, given the occupant and the mode. This can (in a non-final leg) still lead to two different realizations, one that captures the occupant, and one that uses it as a screen to hop over.

In phase 3 of the project we only handle simple moves, consisting of a single leg. For every realization of the leg NextLeg then calls GotMove to process the move. In multi-leg moves it might have to call itself recursively at those points, to attempt the remaining legs, but for now we can leave out these recursive calls. The only modes we handle is capture, non-capture and friendly capture. (The castling code of phase 2 goes into that latter section.) It is ignored whether a leap is lame, and the more exotic features of XBetza, such as move induction and unloading of victims are not implemented either. Hopping, locust capture, and bent riders are intrinsically two-leg phenomena, and won't work either.


H. G. Muller wrote on Sun, Aug 2, 2020 09:37 PM UTC:

I have advanced one more phase already. For one, I added some multi-leg capability to the move generator, namely the case where this is 'transparent' (i.e. without side effects). So the recursive calls to NextLeg are in place. This makes hoppers and bent riders work. Like their single-leg counterparts, such moves are also fully defined by the origin and destination square, and can be further processed in the same way. Therefore this was a quite trivial change. E.g. for handling the hoppers the code

    if & 16 #mode:                          // 16 = hop-on leg
      set newindex + 4 #legindex;           // another leg always follows
      gosub NextLeg #togo #newindex #startsqr #to #locustsqr #dropsqr #len;
    endif;

was added at the top of the section for occupied end-points. Another leg is guaranteed to follow here, so we don't even have to test for the value of togo. We do have to do that at the end of NextLeg, where we call NextLeg only if togo > 0 (in a similar way as above), and otherwise call GotMove. In this phase this recursive call serves only to handle the bent riders. The conditional section for occupied squares, which would use that recursive call for finishing locust captures, is temporarily disabled by putting a return statement where it will eventually set the locust square.

The more tricky part is enforcing the 'non-jumping' feature of XBetza. This is only well-defined on purely orthogonal or diagonal moves, and should never be used for obliques. The code works by first deriving the basic step in the same direction as the lame leap, and then uses that to generate the slide along the ray of the ride. This way it will also encounter any intermediate pieces over which the ride jumped. The number of steps is then converted to a number of leaps in the ride by multiplying with the ratio of the lengths. This can lead to shortening of the ride. As a length measure we use |dx| + |dy|, to make sure it is never zero.

  if & 128 #mode:                         // 128 = non-jumping
    set hx >> + 8 * 5 #dx 4;              // derive unit step
    set hy >> + 8 * 5 #dy 4;              //   (works for |dx|, |dy| = 0, 2, 3, 4).
    set len count ride #starsqr #hx #hy;  // distance to first obstacle along ray
    set len * #len + abs #hx abs #hy;     // |hx| + |hy| is measure of step length
    set len / #len + abs #dx abs #dy;     // distance in leg's leaps, rounded down
    verify #len;
    set r min #r #len;                    // clip blocked part of path
    // TODO: e.p. rights creation
  endif;

In this phase 4 the initial Pawn pushes are thus correctly generated. (In phase 3 they were still able to jump.) The Vao now has its capture move, and the Giraffe the intended Griffon move (instead of the Ferz that it was in phase 3). We are now in a position to implement the creation of e.p. rights due to such lame leaps that are used as initial Pawn moves.


H. G. Muller wrote on Mon, Aug 3, 2020 11:45 AM UTC:

The target for phase 5 is sort-of reached: e.p. capture finally works automatically. The implementation is a bit flaky, though, in the sense that XBetza would allow some very strange forms of e.p. capture, which would not be handled correctly at all. Such as e.p.-capturing Checkers, or e.p. capture with a sliding move, or riders that create e.p. rights for their capture on the squares they leap over... It is hard to imagine any CV would ever use those.

So for now e.p. rights are only created by lame leaps, which is good enough for the double (or triple) push of Pawns.; if rights-creation is specified on a lame rider, only the first leap would create the rights. The e.p. squares are accumulated in a loop over all the squares that the lame leap passes through, in the array eps. By default this variable would hold a scalar 0, though.

    if & << 1 14 #mode:                   // must create e.p. rights
      set eps ( );                        // array to accumulate e.p. squares
      set to where #cursqr #dx #dy;       // only for the first leap!
      set k #cursqr;
      do while != #to #k and onboard #k:
        set k where #k #hx #hy;
        push eps #k;
      loop;
    endif;

If the generated move matches the input move, the e.p. rights it creates are copied from eps to the global variable epsqrs, and the global ep is set to the destination square. So that the immediately following move can consult those to decide if it can perform. Thus at the end of case 1 of the GotMove routine (where the matching is confirmed) we put:

      set epsqrs #eps;   // remember e.p. squares
      set ep #destsqr;   // remember e.p. victim location

Another simplifying assumption is made on the e.p. capture itself. Namely that this would only be specified on the final leg of a move, and that this leg will always be a leap. The test for an e.p. capture then only has to be done in the section of NextLeg that handles the end-point of the leg:

  if match #to #epsqrs and & 4 #mode:       // 4 = e.p.-capable, and hits e.p square
    gosub GotMove #startsqr #to #ep 0 0 1;  // ASSUMES ALWAYS LAST LEG! (1 = implied)
  endif;

Note that this doesn't test whether the e.p. square is empty. The fact that rights for now can only be created by a lame leap already guarantees this. But even if we would start to support other ways of e.p.-rights creation, e.g. by a hop, the burden would be on the creating code to decide if the hopped piece makes a suitable e.p. square (which then would open the possibility for a double capture).

Anyway, e.p. capture on Pawns (or in fact any piece) with Betza D, A, G or H initial moves by a simple leap now works. Note the e.p. capture is generated with the implied argument to GotMove set, so that it will be treated as an implied side effect; the user will only have to enter the move of its own Pawn.


H. G. Muller wrote on Mon, Aug 3, 2020 12:30 PM UTC:

Some questions about Game Courier:

  • Can 'include' files for GAME code be put anywhere on the server, by giving thier path name in the include statement, or must they be in a specific directory? It would be convenient to have all the CV-independent code in an included file, but this file will no doubt need some maintenance, so it would be a problem if it was in a place where I could not update it. So I was hoping it could be put in one of the /membergraphics/MS* directories where I can upload it, similar to the betza.js script.
  • Is there a more user-friendly way to enter composit moves than typing, in GAME courier? It appears that the move is sent to the server as soon as you click the destination square, so if you want there to be stuff after it, you cannot even use the mouse for indicating the destination. Is there a way to suppress immediate sending of the move on the second click (e.g. by requesting this through the $legalmoves array), so there is opportunity to append something by typing first? (And if there isn't, shouldn't we try to make one?)

🕸Fergus Duniho wrote on Mon, Aug 3, 2020 03:27 PM UTC in reply to H. G. Muller from 12:30 PM:

Can 'include' files for GAME code be put anywhere on the server, by giving thier path name in the include statement, or must they be in a specific directory?

You should now be able to use an include file in another directory by specifying its path. If you begin the name with a forward slash, it should prepend the root path to the name instead of the includes directory. I have not tested this. So, let me know if it works.

Is there a more user-friendly way to enter composit moves than typing, in GAME courier?

You can program your game to use the continuemove command when a move has a second part. Extra Move Chess uses this in the Post-Game code to allow each player to enter the second part of his move.


H. G. Muller wrote on Tue, Aug 4, 2020 05:43 AM UTC in reply to Fergus Duniho from Mon Aug 3 03:27 PM:

This continuemove can be very useful, as my move parser already recognizes composit moves of the type x-y; y-z as a locust capture of the piece at y as a side effect of the over-all move x-z (i.e. x-z; @-y). I have one problem that interferes with this, however:

In the Shogi variants (e.g. with the Chu-Shogi Lion) it usually happes that if x-y; y-z is legal, then x-y by itself is also legal. So if the user enters x-y you don't know whether you have to invoke continuemove for a second leg, or whether he is already done. I see that Adam DeWitt solved this problem by always using continuemove in this case, and having the user type pass in response to it if he did not want a second leg. But this kind of defeats the purpose of not having to type.

Would it be possible to have the move-entry form appear with the move field already containing the text 'pass'? (Or perhaps with an arbitrary text passed as argument to continuemove?) Then the user would only have to click the 'Play' button if he did not want to enter more legs. And if he does want to play another leg, he can click the board, to make piece ID plus origin square replace the text.

(This would also require a small change in the way the JavaScript click handler works in that case, as currently it seems to prepend the piece+square info of the board click to the text contained in the move field. I suppose this is done for 'reverse entry', where you click the destination first. But it could make an exception for the text 'pass', and always erase that from the move field when the board is clicked. Or only do the prepend thing when the current text starts with a hyphen.)

Another thing:

When I try to print something (for debugging purposes, using print or printr) in the Post-Game sections, it does not seem to show up anywhere. Unless I ask the browser for the page source. Then the output of the print statements appears in the header of the HTML page; FireFox furthermore complains about the </HEAD> and <BODY ...> tags (by displaying them in red), which it apparently does not expect in the place they are now.

Yet another issue:

I am trying to implement promotion now, and I managed to get askpromote present a menu of possible piece choices. But when I then click one (say a Rook), my move parser flags the resulting move as illegal, because the promotion is added as R-dest with the literal string 'dest' rather than a square name. Do I misunderstand the format of promotion 'moves'? When I have GAME Courier perform the move it seems to understand this format, and I can of course have my move parser expect it. Would it also be what the user types if he enters the move by hand?

Oh, and when a Pawn is in the askpromote menu, and I select it, nothing happens when I press 'Play'. (This could be because I am in an infinite loop.) I can imagine that it dislikes adding a no-op promotion move 'P-dest', but how else could it be indicated that I want to defer promotion? Not in all CVs Pawn promotion is mandatory everywhere in the zone.

[Edit] I can confirm that the include with a path name works; I have made a new preset that tries it out, after uploading a betza.txt file to the membergraphics directory of the Play-Test Applet. This preset was made by straight copy-pasting of the GAME code produced by the Applet, after specifying the promotion choice (Lion is forbidden, so the default would not work), setting up the initial position, and redefining the Lion move (which in the Applet's table does not have the locust capture of the Chu Shogi Lion; perhaps I should add such a Lion in the table). It doesn't enforce the Lion-trading rules (yet), and the current betza.txt file doesn't test for game end yet.


🕸Fergus Duniho wrote on Wed, Aug 5, 2020 02:10 AM UTC in reply to H. G. Muller from Tue Aug 4 05:43 AM:

Would it be possible to have the move-entry form appear with the move field already containing the text 'pass'?

I am working on buttons for entering pass and skip. This is similar in concept to the button that enters the resign command. There are still bugs to work out, and hopefully, I'll get them working tomorrow.

When I try to print something (for debugging purposes, using print or printr) in the Post-Game sections, it does not seem to show up anywhere. Unless I ask the browser for the page source. Then the output of the print statements appears in the header of the HTML page; FireFox furthermore complains about the and <BODY ...> tags (by displaying them in red), which it apparently does not expect in the place they are now.

The GAME Code program is run before the HEAD section is displayed. It fills some META fields with images of the board. This is mainly for SEO and might not be especially useful.

Text that is displayed by the GAME Code program is normally hidden if the program completes successfully. You can test code more immediately by entering it in the Pre-Game section or in the appropriate Post-Game section and clicking the Run button.

I am trying to implement promotion now, and I managed to get askpromote present a menu of possible piece choices.

I have not had time yet to look into this, because I'm still working on the pass/skip buttons.

Oh, and when a Pawn is in the askpromote menu, and I select it, nothing happens when I press 'Play'. (This could be because I am in an infinite loop.) I can imagine that it dislikes adding a no-op promotion move 'P-dest', but how else could it be indicated that I want to defer promotion?

I have not looked into this either, but I would advise you to closely examine the code for the Pawn subroutines in the fairychess include file.


H. G. Muller wrote on Wed, Aug 5, 2020 07:44 AM UTC in reply to Fergus Duniho from 02:10 AM:

I have not looked into this either, but I would advise you to closely examine the code for the Pawn subroutines in the fairychess include file.

OK, it seems that $answered is the thing I need to break out of the infinite loop, when the move is not augmented as a result of askpromote. So this issue is solved now. Just for my understanding: the GAME code manual lists this just as answered. Is the $ prefix something similar to # or ?, to distinguish variables that live in a different name space?

Now that you are working on the continuemove form: for the purpose of entering a multi-leg move, it would be convenient if the form started with the piece that was just moved already selected, as if the user had already clicked it, so that he now would have to click only the (highlighted) destination. But of course for true multi-move variants like Marseillais Chess you would not want that.

The JavaScript in the page should be able to see the difference, though: if there is an array legalmoves passed to the JavaScript, and all moves in it start with the same square, it could just simulate a click on that square by calling movePiece() for that square:

if(legalMoves != null) {
  var origen = legalmoves[0][0];
  for(var i=legalmoves.length-1; i>0; i--) if(origen != legalmoves[i][0]) break;
  if(i > 0) movePiece(origen); // fake a click on the only piece that can legally move
}

If there is a 'Pass' button, the user would not be hindered by the fact that there already is some text in the move field because of this.

[Edit] I encountered some unexpected behavior:

When I do set a #b; where b is an array, it will in general copy the entire array. Except when it is an array of only a single element. Then a will not be an array, but just that element. And expressions like match #something a then do not work. This is pretty annoying when you cannot know in advance how many elements the array will have.


🕸Fergus Duniho wrote on Wed, Aug 5, 2020 01:42 PM UTC in reply to H. G. Muller from 07:44 AM:

The pass/skip buttons are now done. When commands are banned in general, the appropriate button will show up with one of these commands is allowed. When commands are not banned, pass will show up on the first move, and skip will show up on any subsequent move for the same turn. My original intention was that pass would be for passing a turn, and skip would be for skipping an optional move within a turn, but even I forgot about that when I programmed Extra Move Chess. Since it would break past games to change it now, what's done is done.

it seems that $answered is the thing I need to break out of the infinite loop

That is half of the answer, though if you're evaluating moves only in the Post-Game sections, then the other part of the answer may be irrelevant.

the GAME code manual lists this just as answered. Is the $ prefix something similar to # or ?, to distinguish variables that live in a different name space?

It is a system variable, meaning it exists in the PHP program at large rather than in the variable stack used for user variables. In GAME Code, the variable prefixes are used to retrieve values, but they are not an inherent part of the variable name. You could retrieve its value with either system answered or with $answered. To set a variable, you would normally enter its name without a prefix. The $ sign is used for system variables, because that is what is used in PHP for variables, and they are really PHP variables.

if there is an array legalmoves passed to the JavaScript, and all moves in it start with the same square, it could just simulate a click on that square by calling movePiece() for that square:

I'll look into that. One application of this would be to make forced moves more visible.

When I do set a #b; where b is an array, it will in general copy the entire array. Except when it is an array of only a single element. Then a will not be an array, but just that element.

I just tested for that and didn't get the behavior you described. I had b as an array of one element, and it copied an array to a, not a single element. I used this code:

push b abbot;
set a #b;
dump;

H. G. Muller wrote on Wed, Aug 5, 2020 02:01 PM UTC in reply to Fergus Duniho from 01:42 PM:

It seems the cond operation is the culprit. I got in in this context:

set a (K);
set b (k);
set c cond 1 #a #b;
printr c;

This prints a scalar K. And to top that:

set a cond 1 (b c) (d e);

gives me the array (c b)! That seems taking right-to-left evaluation of expressions a bit too far.


🕸Fergus Duniho wrote on Wed, Aug 5, 2020 02:48 PM UTC in reply to H. G. Muller from 02:01 PM:

The cond operator will evaluate an array and return its result. This is useful for preventing evaluation of an expression until cond determines which one to evaluate. set c var cond 1 a b; or set e cond 1 (#a) (#b); will work as you want. For the other one that includes arrays directly, set c cond 1 ((b c)) ((d e)); will do what you want.


H. G. Muller wrote on Wed, Aug 5, 2020 03:24 PM UTC in reply to Fergus Duniho from 02:48 PM:

Hmm, I cannot say that I understand this. What does "evaluating an array" mean? Reversing the order of its elements? According to the manual cond is a ternary operator that returns one of its last two operands. The parentheses should only have an effect for the operand that it does not return, which is only relevant if it has a side effect. It says it always evaluates the operand that it does return, parentheses or not.

Is it that the parentheses in this context have special meaning, so that when you pass a bare array it strips the parentheses, and parses the operands differently? That still does not seem to explain how it could swap the order of the elements.

I was just wrestling with some other very mysterious behavior. I have an expression:

  set traded cond and match #victim #protected
                      match #mover #protected #mover 0;

This appears to make the variable traded (which was 0 before) disappear! A print #traded; directly behind it prints 'traded'. The problem is related to the fact that I tried to set protected to an empty array, though set protected ( ); . Apparently the parentheses are no good for this; when I replace it by set protected array; I really get an empty array, and the the problem disappears. When I print the individual sub-expressions match #victim #protected and match #mover #protected they both print as empty strings, in both cases, which seems to be the representation of false. So when those are the same no matter how I set protected, how can it still matter for the final result? How can a variable disappear because of an assignment anyway?


🕸Fergus Duniho wrote on Wed, Aug 5, 2020 03:43 PM UTC in reply to H. G. Muller from 03:24 PM:

What does "evaluating an array" mean?

Evaluating the expression in the array.

How can a variable disappear because of an assignment anyway?

If it receives a value of null, it will register as unset. But if you use dump to view all the variables, it should still show up.


H. G. Muller wrote on Wed, Aug 5, 2020 03:54 PM UTC in reply to Fergus Duniho from 03:43 PM:

But what is the 'expression' in an array (a b)? I just see two constants, a and b.

So null is a different value from false, but both print the same, namely as an empty string? How can I know whether a boolean operator like and returns null or false?


🕸Fergus Duniho wrote on Wed, Aug 5, 2020 04:32 PM UTC in reply to H. G. Muller from 03:54 PM:

But what is the 'expression' in an array (a b)?

The expression is "a b". It would evaluate to an array containing the two elements.

So null is a different value from false, but both print the same, namely as an empty string?

They print the same if you use var. They do not print the same if you use the # prefix to access a variable's value.

How can I know whether a boolean operator like and returns null or false?

A Boolean operator should return false, not null.

It looks like you were misusing cond. Here is the code you gave me:

set traded cond and match #victim #protected
                      match #mover #protected #mover 0;

The cond operator should have three arguments, but by the time it reaches it, it has none or only one. The match operator is multiple-arity. This means it uses all the arguments to its right. So, whatever value match #mover #protected #mover 0 returns gets included as one of the arguments passed to the other match. Since #mover will match #mover, this value will be true. So, this reduces your expression to:

set traded cond and match #victim #protected true;

Since and receives a single argument, it goes into control flow mode. If #victim matches #protected, we get and true, which allows flow to continue to cond, but cond has no arguments, because and has discarded its argument without returning anything. If it is false, it halts execution of the expression, returning a value of false for the expression. So, you must have gotten a null value for #traded when #victim matched #protected.

I think what you want is this:

set traded cond and == #victim #protected == #mover #protected #mover 0;

H. G. Muller wrote on Wed, Aug 5, 2020 05:53 PM UTC in reply to Fergus Duniho from 04:32 PM:

The expression is "a b". It would evaluate to an array containing the two elements.

But why would it evaluate to (b a) rather than (a b)?

The match operator is multiple-arity.

Ah, now I am starting to understand it. Since #protected was supposed to be an (empty) array, match should have been ternary, and the expression would be OK. (And in fact it is). But because the initialization set protected ( ); did not make it an array at all, the problem you now point out occurred, and things became a mess. So fixing the initialization of protected (or actually having piece types in there) solved the problem.

I think what you want is this:

set traded cond and == #victim #protected == #mover #protected #mover 0;

That would have been what I wanted if #protected would have been a single piece type rather than an array of possible piece types. This code is for enforcing Chu-Shogi Lion-trading type rules, and even in the simple case it would contain both the white and the black type, to make the code color-independent. But there could potentially be multiple types that you cannot trade for each other (e.g. in Tengu Dai Shogi). Of course in almost every CV protected would be an empty set, but I figured that match on an empty set would not be very expensive. Perhaps this, and similar weirdness (such as the counter-strike rule, and iron pieces) should all be put in a conditional code section the execution of which is controlled by a single boolean. Which would then be set to false to skip the code by default, and only variants that have one of these exotic rules could then initialize it to true. I have no idea whether the code I have now would cause any significant CPU load; perhaps I should use the stopwatch trick to time the execution from Pre-Game to Post-Game.

For now I highlight all pseudo-legal moves without testing any for full legality, and it is having to do the in-check test on all possible moves that is by far the most expensive piece of code. The mate test only becomes expensive when you are in check; during most of the game the first move you try would already be legal, proving there is no mate.

 


🕸Fergus Duniho wrote on Wed, Aug 5, 2020 08:50 PM UTC in reply to H. G. Muller from 07:44 AM:
if(legalMoves != null) {
 var origen = legalmoves[0][0];
 for(var i=legalmoves.length-1; i>0; i--) if(origen != legalmoves[i][0]) break;
 if(i > 0) movePiece(origen); // fake a click on the only piece that can legally move
}

I got the following code to call the function, though the function didn't work right.

if (typeof legalMoves != 'undefined') {
    var ori = legalMoves[0][0];
    for (var i=1; i < legalMoves.length; i++) {
        if (ori != legalMoves[i][0]) {
            ori = "";
            break;
        }
    }
    if (ori != "") {
        movePiece(ori); // fake a click on the only piece that can legally move
    }
}

But it occurred to me that if it did work right, it would remove the display of the last move. So, I'm not sure I should bother trying to get it to work.


🕸Fergus Duniho wrote on Wed, Aug 5, 2020 09:08 PM UTC in reply to H. G. Muller from 05:53 PM:

But why would it evaluate to (b a) rather than (a b)?

To evaluate a Polish notation expression, it reads each word from the end of the line and pushes it onto a stack. Each operator pops off as many arguments as it needs from the stack and pushes its result back onto the stack. Without any operators, it just pushes each word in the expression onto the stack in reverse order and does nothing more with it. In this code, the variables v1 and v2 both get set to "b a" by the same process:

set v1 a b;
push v2 b;
push v2 a;
dump;

The mate test only becomes expensive when you are in check; during most of the game the first move you try would already be legal, proving there is no mate.

But if you want to display legal moves, you will still need to calculate all of them. That's why my stalemated and checkmated subroutines no longer stop when one legal move has been found.


H. G. Muller wrote on Wed, Aug 5, 2020 10:17 PM UTC in reply to Fergus Duniho from 08:50 PM:

But it occurred to me that if it did work right, it would remove the display of the last move. So, I'm not sure I should bother trying to get it to work.

I don't see off-hand why that wouldn't work. The code I suggested had a case mismatch for the M. Perhaps it is a timing problem, that it tries to run before the page is fully loaded. It should be possible to solve that by putting the code in a function (say F), and call that with an onload="F();" in the HTML <body> tag.

I am not sure what exactly would get erased. The highlighting of the previous move? I proposed to do this only in the page after continuemove (which is different anyway, as it displays 'previous moves'), not for the regular move entry, for the rare case there only is a single piece that can move. For entry of a second leg there is no interest in what happened before; you just did that yourself.

As to the Polish expressions:

What puzzles me is that if I do

set v1 (a b);
set v2 cond 1 (a b) (c d);

that v2 reverses a and b, but v1 not. The syntax of the set command is also

set variable expression;

so in both cases we are dealing with expressions that are evaluated. But the arguments of cond are apparently treated differently from the expression in set.


🕸Fergus Duniho wrote on Wed, Aug 5, 2020 11:52 PM UTC in reply to H. G. Muller from 10:17 PM:

I don't see off-hand why that wouldn't work. The code I suggested had a case mismatch for the M. Perhaps it is a timing problem, that it tries to run before the page is fully loaded.

It's possible I made more changes than necessary, since it never worked. I eventually placed an alert in the function, which showed it was being called. I would have to analyze the function more closely to determine what didn't work.

I proposed to do this only in the page after continuemove

In that case, you should make use of ask to spell out the optional moves and ask which one the player wants to make.

so in both cases we are dealing with expressions that are evaluated.

So that expressions may be passed to cond in an unevaluated state, this particular operator is able to trigger the evaluation of an array as an expression. This is not the usual default behavior, and in set v1 (a b);, the array inside (a b) is not evaluated as an expression. The default behavior is to not evaluate an array as an expression, and it is by means of this that two expressions may be passed to cond without being evaluated first. This allows cond to be used in recursive functions.


H. G. Muller wrote on Thu, Aug 6, 2020 06:09 AM UTC in reply to Fergus Duniho from Wed Aug 5 11:52 PM:

What exactly did you mean by "didn't work right"? What happens? Does it do anything at all?

In that case, you should make use of ask to spell out the optional moves and ask which one the player wants to make.

That is ugly. For a Chu-Shogi Lion there would typically be 8 moves for the second leg, next to the pass option, and the user would have to interpret all the square coordinates to see which of these he wants. We don't prefer that system of move entry for the main move, and it is also far inferior for the second move, unless perhaps when there is only choice between two options. (Shogi-promotion Yes/No comes to mind, although even that is probably better handled by the askpromote function showing the images.) We chose (with good reason) for a system that picks moves by clicking squares on the board, and we should stick to that for as long as it is a matter of selecting a square. Even the system as it works now, where the user has to select the piece that just made the first part of the move again for making the second part is much better. It would just be cool if he wouldn't have to re-select it. That would also make it much more obvious he still isn't done with the move. He could see that from the 'Previous Moves' in the entry block, of course, but his attention at this point would be at the board, so that is easy to miss.

This allows cond to be used in recursive functions.

OK, I see. To create the possibility for conditional evaluation, the evaluation that would normally take place is fooled into thinking it is just passing an array (which by default is not evaluated further). The cond operator then has to evaluate the operand it picks after the fact, and this is triggered by the operand being an array. But when the operand happened to be an array by itself, rather than due to explicit parenthesizing in the statement, it aso triggers this extra evaluation, which was neither wanted nor expected. At that point cond can no longer see where the parentheses came from.

This is really extremely confusing, and should be stressed in the manual entry about cond. There should be a strong warning that one should always pass the expressions for op2 and op3 as arrays (i.e. parenthesized)  when one of those could evaluate to an array. Is there in fact any reason to not use parentheses around the alternatives? Why would you ever want to evaluate the expression that is to be discarded? I don't think I will ever use cond without parentheses again. That makes the code much easier to read as well.

 


H. G. Muller wrote on Thu, Aug 6, 2020 08:25 AM UTC:

OK, I experimented a bit myself: I played a game until the page for entering the second leg came up, and then used "Save page as..." to make a local copy of it. If I load that local copy in the browser it is a bit mutilated, because it apparently misses som CSS file, but it is recognizable enough, and the embedded JavaScript actually functions: when I click on the piece that must continue the move the highlights come on. So then I edited the page, to add the JavaScript

function F() {
  var ori = legalMoves[0][0];
  for(var i=legalMoves.length-1; i>0; i--) if(legalMoves[i][0] != ori) break;
  if(i) movePiece(ori);
}
F();

directly after the definition of clearBorders(). This indeed did not work. When I hit F12 to see the JavaScript console it complains that img is not defined when movePiece tries to use it; this happens in a conditional section if(!submit || !movesField). Since the page actually does define a movesfield and a submitmove in its HTML this must be caused by premature execution of the script, before the entire page has loaded.

So I moved the calling of F(); to the very end of the page, in its own <script> tags. And then it worked! An alternative is to change the BODY tag to

<BODY CLASS="print" LANG="en" onload = "F();">

This also postpones execution of F() until after we can be sure movesField is present on the page. Yet another method (which I did not test), to do it from the existing JavaScript block, would be to delay the execution by, say, half a second through setTimeout("F();", 500); . This is a bit more risky, as it would fail when for some reason loading of the page takes more than 500ms, while we wouln't want to make the delay much larger so that the user has to noticeably wait for it. Inserting the existing <script> block at the bottom of the page, or in any case after all essential HTML elements, and just keeping a straight call F(); in it, should of course also work.


🕸Fergus Duniho wrote on Thu, Aug 6, 2020 04:26 PM UTC in reply to H. G. Muller from 06:09 AM:

What exactly did you mean by "didn't work right"? What happens? Does it do anything at all?

I created a position in Extra Move Chess where the King was in check and had only one legal move. In this position, it never simulated a click on the King, it didn't automatically show the move it had available, and it continued to show the last move played. It was as though the code wasn't there. I was able to test that the code itself worked by placing an alert into the function it called, and this alert accurately reported the King's position, but once it called the function, nothing happened.

This is really extremely confusing, and should be stressed in the manual entry about cond.

It's covered in the documentation, but it could be covered better. I'm working on improving the documentation when I have spare time.

Is there in fact any reason to not use parentheses around the alternatives?

When they are strings or numbers, there should be no need to use parentheses.


25 comments displayed

EarliestEarlier Reverse Order LaterLatest

Permalink to the exact comments currently displayed.