Check out Glinski's Hexagonal Chess, our featured variant for May, 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 ]

Single Comment

GAME code table-driven move generator[Subject Thread] [Add Response]
H. G. Muller wrote on Tue, Jul 28, 2020 06:52 AM UTC:

Move legality testing will be based on a move generator. This generates the pseuo-legal moves that are possible in the given position, always being fully aware of any side effects these moves might have. These moves are then compared to the input move, and if there is a match we know the input move was pseuo-legal. Normally we consider a move a match only if all the parameters ori, desti, promo, suicide, freedrop and dropped of the input move are equal to their counterparts orisqr, destsqr, locustsqr, dropsqr and unload of the generated move. But for moves with implied side effects we only match ori, desti and promo (the latter should be 0), and accept what the move generator specifies as side effects. E.g. for castling the locustsqr would be at the Rook to make it disappear, and the dropsqr would indicate where we must drop a Rook to make it reappear.

To make this process efficient, we generate only moves of the piece that according to the input is going to move. For moves that were already tested for legality before (now played for setting up the current position) we even take a short-cut on that: we only generate the moves with implied side effects, to calculate and perform those. The subroutine GenMoves will thus have three parameters: the piece type and start location from which its moves should be generated, and a Boolean that indicates whether we want all moves of the piece, or just those with implied side effects. In the latter case most pieces would of course have no moves at all; in orthodox Chess only the KIng (castling) and Pawns (e.p.) have moves with side efects.

sub GenMoves piece sqr all:            // generates moves for 'piece' on 'sqr' in current position
                                       // 'all' specifies whether we get only moves with side effects
  my index legcount;
  set side islower #piece;             // remember which side we are generating for
  set index fn #piece #all;            // function returns start of piece description in legdefs table
  set hit false;
  do:
    set legcount elem #index #legdefs; // the first element specifies the number of legs
    verify #legcount;                  // a 0 sentinel ends the list of moves
    inc index;                         // 'reads away' the leg count
    gosub NextLeg #legcount #index #sqr #sqr 0 0 0;
    set index + #index * 4 #legcount;  // skip to the next set of legs (= move)
  loop until #hit;
endsub;

The move descriptions come from the table legdefs, and where in this table the description of the given piece type starts is given by a function with the name of the piece type. This function has an additional argument, indicating whether we want all moves or just those with implied side effects. The latter will be found behind the former in the legdefs table, so that we can just start later in the table to get only those. Each move description will start with a number indicating how many legs the move has (where each leg is a leap or a ride). Each leg is then described by 4 numbers: range, sideway and vertical step, and mode. We call the subroutine NextLeg to interpret these. To get to the next move of a piece the table index then has to be incremented by 1 plus 4 times the number of legs. A dummy move with 0 legs indicates we are done with this piece, and ends the loop by terminating the subroutine though verify. A global variable hit can cause the loop to terminate early; there is usually no need to go on generating moves after we found the one we were looking for.

The routine NextLeg does the real work. It calls itself recursively in case there is more than one leg, to do the follow-up legs. Its first parameter controls the depth of this recursion, the next specifies where in legdefs to find the four numbers that describe the leg. Then follow a number of squares: the start square of the move, and the start square of the leg (which for the first leg are of course the same), then the squares for the side effects (starting at the default 0) locustsqr and dropsqr. Finally there is a parameter with which we can request an exact length of a ride when it is non-zero.

We will not go into the details of NextLeg now. When it succeeds in constructing the move according to the description in legdefs, it will eventually call a routine GotMove, passing it the parameters that describe the move and all its side effects. Because we will want to use the move generator for various purposes (e.g. testing whether a given move is pseuo-legal, regeneration of implied side effects, testing whether we can capture a royal piece, making a table of legal moves...), a global variable task will specify what this routine must do. For now we will only have 'task 1', regeneration of the implied side effects.

sub GotMove orisqr destsqr locustsqr dropsqr unload implied:
  switch #task:
    case 1:
      verify == #orisqr #ori and == #destsqr #desti;
      if not #implied: // explicitly specified side effects must also match
        verify == #locustsqr #suicide;
        verify == #dropsqr #freedrop;
        verify == #unload #dropped or not #dropsqr;
     else:            // no side effects must be specified when they are implied
        verify not #suicide and not #freedrop;
        if #locustsqr:
          capture #locustsqr;
        endif;
        set implieddrop #dropsqr;
      endif;
      set hit true;    // we found a matching pseudo-legal move
  endswitch;
endsub;

The move generator always specifies the side effects, and whether these are implied. If they are explicit, they will be automatically applied by Game Courier's execution of the input move. If they were implied, we perform any locust capture, and remember whether we should drop a piece somewhere, so we can do that later.

Why not immediately do all implied side efects? Well, we cannot always make the freedrop of a castling Rook before Game Courier has made the input move, as in some castlings the Rook then would clobber the King. So we must apply the freedrop after feeding the move to Game Courier.

sub HandleMove player:
  set mvs explode chr 59 thismove; // split at semicolons
  gosub ParseMove #player;         // syntax check and interpretation
  set task 1;
  set implieddrop 0;
  gosub GenMoves #mover #ori 0;    // only moves with implied side effects
  set k 0;
  do while < var k count var mvs:  // for all legs
    eval join "MOVE: " trim elem var k var mvs; // apply the move
    inc k;
  loop;
  if #implieddrop:                 // implied freedrop
    add #dropped #implieddrop;
  endif;
endsub;

Note that this code doesn't worry about promotions yet.