Check out Atomic Chess, our featured variant for November, 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

Play-test applet for chess variants. Applet you can play your own variant against.[All Comments] [Add Comment or Rating]
💡📝H. G. Muller wrote on Thu, Jul 23, 2020 07:56 AM UTC:

Just to be sure: lines in subroutines are only 'pre-processed' when they are executed, but functions when they are defined? So in subroutines var and # would be equivalent?

I slowly start to converge on a design. The idea is that the Post-Move code would always only contain only the line gosub HandleMove false; (for white) or gosub HandleMove true; (for black). Similarly, the Post-Game code would only contain gosub GameEnd true/false; . The routines would be universally applicable code, which could go into the Pre-Game section, or into a file that would be included from there.

The rules would then be described in the Pre-Game section by a large initialized array 'legdefs' of integer numbers, encoding all the moves, functions for each piece type returning where the move data for that piece type starts in legdefs. Plus statements to set a number of parameters, like what the royal type for each player is, whether stalemate is a win or draw, what King destinations imply castling, where the castling partners are located, how large the promotion zone is, what you can promote to on various ranks, which pieces van promote and stuff like that. Defaults for those parameters could be set in the included file, but the CV-specific code could overrule those settings after the include. It is this Pre-Game code that would be generated by the Play-Test Applet.

A rough draft of the code for such a design is this:

// the following arrays and functions are variant-specific, and would be defined in Pre-Game:

set legdefs (....); // a (very large) array of numbers, describing the moves of all pieces
                    // each move is a sequence of 'legs' (leaps or slides in one direction)
                    // each leg is described by 4 numbers in legdef:
                    // range, sideway step, forward step and 'mode'.
                    // the mode packs bit flags describing what must be at the end of the leg
                    // a move with 0 legs is used to indicates the list for that piece ends

def P cond #0 223 45;  // functions map piece types on their move definition in legdefs
def p cond #0 235 70;  // each piece has two move definitions: e.p.&castling-only, or all their moves
def K ...

set many wroyal K broyal k;
...

// the following code is generic, and can be handled as a Pre-Game include

sub GotMove orisqr destsqr:
  // process the move specified by the params and the globals like locustsqr
  if == 0 #task:      // apply side effects of a matching move
    verify == #orisqr origin and == #destsqr dest:
    if #locustsqr:
      empty locustsqr;
    endif;
    if #dropsqr:
      add #unload #dropsqr;
    set hit true;    // abort further generation

  elseif == 1 #task: // test input move for pseudo-legality
    verify == #orisqr origin and == #destsqr dest:
    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;
    endif;
    set hit true;    // we found a matching pseudo-legal move

  elseif == 2 #task: // accumulate all pseudo-legal moves
    setlegal #orisqr #destsqr;

  elseif == 3 #task: // test whether move captures king (for check test)
    if == #royal space #destsqr:
      set hit true;
    elseif #locustsqr:
      set hit == #royal space #locustsqr;
    endif;

  elseif == 4 #task: // find one legal move (for mate test)
    my victim locustvictim;
    set victim space #destsqr; // save old values
    // make the move
    if #locustsqr:
      set locustvictim space locustsqr;
    endif;
    if #dropsqr:
      add #unload #dropsqr;
    endif;
    move #orisqr #destsqr;
    // test whether in check
    gosub InCheck #side;
    // unmake the move
    move #destsqr #orisqr;
    if #dropsqr:
      empty dropsqr;
    endif;
    if #locustsqr:
      add #locustvictim #locustsqr;
    endif;
    set hit not #hit; // continue when in check, stop when not

  endif;
endsub;

sub NextLeg togo legindex startsqr cursqr iso:
  // this routine is the heart of the move generator
  // it generates the destinations of one leg,
  // and then calls itsef recursively for the remaining legs
  // 'togo' tells how much legs still have to be done; when 0 we have a complete move
  // 'legindex' tells where the leg descriptions start in the 'legdefs' array
  // 'startsqr' is where the piece that must make the move starts
  // 'cursqr' is where any previous legs brought us
  // 'iso' is the number of leaps taken in an earlier riding leg
  if #togo: // move not complete yet; attempt next leg
    my range dx dy mode to;
    // retrieve leg description (next 4 elements in 'legdefs')
    set range elem #legindex #legdefs;
    set dx elem + 1 #legindex #legdefs;
    set dy elem + 2 #legindex #legdefs;
    set mode elem + 3 #legindex #legdefs;
    if & #mode i_FLAG:                    // initial move only
      verify not ?startsqr;               // suppres it for non-virgin piece
    endif;
    // simple example, handling only (divergent) leaps
    if == 1 #range:                       // leg is leap
      set to where #cursqr #dx #dy;       // calculate destination
      set victim space #to;               // look what is there
      verify != void #victim:             // abort if off board
      if == @ #victim:                    // empty target
        if match #to #epsqrs and
           & mode e_FLAG:                 // move is valid e.p. capture
          set locustsqr #ep;              // flag that it will capture last mover
          set implied true;               // this is an implied side effect
        else:
          verify & #mode m_FLAG;          // m_FLAG etc. are in fact hard-coded constants like 1, 2, 4, 8...
        endif;
      elseif != #side islower #victim:    // foe
        verify & #mode c_FLAG;
      else:                               // must be friend
        verify & #mode d_FLAG;
        set unload #victim;               // remember what we 'captured'.
      endif;
      gosub NextLeg dec #togo + 4 #legindex #startsqr #to #iso; // do nex leg
    else:                                 // leg is slide
      ...
    endif;
  else: // move finished successfully; process it
    GotMove #startsqr #cursqr;
  endif;
endsub;

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
  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
    set many locustsqr 0 dropsqr 0;   // default is no locust capture or unload
    set implied 0;                    // generated e.p. or castlings set this
    gosub NextLeg #legcount #index #sqr #sqr 0;
    set index + #index * 4 #legcount; // skip to the next set of legs (= move)
  loop until hit;
endsub;

sub GenAll black:
  if #black:
    def friends onlylower;
  else:
    def friends onlyupper;
  endif;
  for (from piece) fn friends;
    gosub GenMoves #piece #from 1;
  next;
endsub;
 
sub HandleMove player:
  gosub ParseMove thismove; // this extracts origin, dest, moved, suicide, freedrop, dropped
  set hit false;
  if == mln $maxmln:
    set task 1; // request legality test
    gosub GenMoves moved origin 1; // match all moves
    if not #hit:
      die "a " moved " cannot move that way!";
    endif;
  endif;
  set task 0; // request execution of implied side effects
  gosub GenMoves moved origin 0;   // only tries e.p. and castling
  eval join "MOVE: " thismove;     // make the move
  setflag dest;
endsub;

sub InCheck player
  set task 3; // request king-capture test
  set hit false;
  set royal cond #player #broyal #wroyal;
  gosub GenAll not #player;
endsub;

sub GameEnd player:
  gosub InCheck player;
  if #hit:
    die "that move would put you in check!"
  endif;
  set task 4;  // request one legal move
  gosub GenAll not player;
  if not #hit: // could not find one
    gosub InCheck not player;
    if #hit:   // checkmate!
      won;
    elseif #staledraw: // stalemate
      draw;
    else:
      won;
    endif;
  endif;
  ... // draw stuff
endsub;