💡📝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;
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: