Mike Sherwin wrote: ↑Mon Feb 17, 2025 2:07 am
Should (victim & sideToMove) be (victim & opponent) instead?
Should for(piece=0; piece<16; piece++) { } also select from pieces 16 to 31 somehow?
for (piece = 0 + 16 * sideToMove; piece < 16 + 16 * sideToMove; piece++) { }?
You are right about the piece numbers; I wanted the counter (say p) to run from 0 to 16, but then do
piece = sideToMove + p;
but I forgot the latter. Note that sideToMove would be 0x40 or 0x80 in this design. The test (victim &sideToMove) is correct: you test for your own pieces here, where an edge guard has both color bits set (0xC0), and is thus always considered your own piece. And will then block the move.
I am starting to like your idea with the plethora of piece types better, though. I now also tried some code for that. It would use the very simple loop I posted in the beginning for all piece types. But because many pieces can promote now, instead of AddMove or AddCapture it would always call AddWithPromotion(from, to, piece, victim). It would then be up to that function to decide if there is a promotion.
Code: Select all
char promoChoice[] = { T_QUEEN, T_KNIGHT, T_ROOK, T_BISHOP, 0 };
void AddPromotion(int fromSquare, int toSquare, int piece, int victim)
{
int effect = promoTable[piece][toSquare];
if(effect < 0) { // invalid effect, flags promotion with choice
int promoPtr = 0;
while((effect = promoChoice[promoPtr++])) // loop over possible choices
AddMove(fromSquare, toSquare, effect, victim);
} else AddMove(fromSquare, toSquare, effect, victim); // promotion without choice ('morphing')
}
Effects 1-N would indicate promotion to the type with that number, effect 0 would mean no promotion. This seems codewise simpler than having the separate loop for Pawns. The desired Pawn behavior can be achieved by starting with a 'charged Pawn' type on 2nd rank, which has range = 2 for its non-capture move. It would have a board-size promoTable[T_CHARGED_PAWN] that specifies effect T_PAWN everywhere, so that all its moves would make it morph into a normal Pawn. The normal Pawn would have a promoTable[T_PAWN] that specifies -1 on the final rank to indicate it promotes with choice there.
This initially present 'charged King' and 'charged Rook' pieces would have tables that makes them morph into normal Kings and Rooks everywhere. Only the charged types could participate in castling, and the castling code will test for that. The other uncharged piece types would use a (shared) promoTable that is completely filled with zeros
One of the reasons I like this (besides simplicity) is its extreme flexibility. This would make the engine very easy to adapt to all kind of variants with complex promotion/morphing rules. It could be extended to
Code: Select all
int effect = promoTable[piece][toSquare];
if(effect < 0) { // invalid effect, flags promotion with choice
if(effect > -64) { // codes -63 to -1 indicate promotion sets
int promoPtr = firstPromotion[~effect]; // use the specified promotion set
while((effect = promoChoice[promoPtr++]))
AddMove(fromSquare, toSquare, effect, victim);
} else if(effect == -64) { // capture-triggered effects
effect = captureMatrix[piece][victim];
} else if(effect == -65) {
...
} else return; // reserved code; reject move
} else AddMove(fromSquare, toSquare, effect, victim); // promotion without choice ('morphing')
}
This would allow different piece types to have different promotion choices. And it could defer the decision about morphing to a table that makes it dependent on what you captured, instead of where you landed. And it could be used to trigger other effects, such as confining pieces to part of the board, as in Chinese Chess, or instant wins by reaching a square as in King of the Hill.
[Edit] This did not solve the issue of how to set the e.p. flag on a double push yet. But this can be done by defining a special effect on the 4th rank of promoTable[T_CHARGED_PAWN], that causes morphing into T_PAWN, in combination with setting the e.p. flag. On the 3rd rank it would merely morph into T_PAWN. We would of course need separate white and black versions of both the charged and the normal Pawns.
If we have
Code: Select all
enum Type { T_NONE, T_CHARGED_WPAWN, T_CHARGED_BPAWN, T_CHARGED_ROOK, T_CHARGED_KING, T_WPAWN, T_BPAWN, T_KNIGHT, T_BISHOP, T_ROOK, T_QUEEN, T_KING };
Then effects 5 - 11 (T_WPAWN - T_KING) would cause the moving piece to change to those types. Effects 1 and 2 could then be used to indicate promotion to T_WPAWN and T_BPAWN combined with setting the e.p. flag.
Code: Select all
int MakeMove(Move move, Undo *u)
{
int epFlag = 0, newType;
u->piece = board[move.fromSquare]; board[move.fromSquare] = EMPTY;
u->victim = board[move.toSquare]; location[u->victim] = INVALID;
board[move.toSquare] = u->piece; location[u->piece] = move.toSquare;
u->type = type[u->piece];
switch(move.specialEffect) {
default: // e.p. capture
u->epSquare = move.specialEffect - 20; // codes >= 20 indicate e.p. target
u->epVictim = board[u->epSquare]; // remove the e.p.-captured piece
board[u->epSquare] = EMPTY;
location[u->epVictim] = INVALID;
case 0: // this is actually the common case
newType = u->type; // non-promotion
break;
case 1:
case 2:
newType = move.specialEffect + 4; // double push
epFlag = 1;
break;
case 4:
case 5:
case 6:
case 7:
case 8:
case 9:
case 10:
case 11:
newType = move.specialEffect; // plain pomotion/morphing
break;
case 12: // castling
newType = T_KING;
... // move and morph Rook
}
type[u->piece] = newType;
return epFlag;
}