/* * piece.c * * Rules for all pieces. */ /* 3Dc, a game of 3-Dimensional Chess Copyright (C) 1995 Paul Hicks This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. E-Mail: paulh@euristix.ie */ #include #include "machine.h" #include "3Dc.h" #define PARAMS (Piece *, File, Rank, Level) Local INLINE Boolean KingMayMove PARAMS, QueenMayMove PARAMS, BishopMayMove PARAMS, KnightMayMove PARAMS, RookMayMove PARAMS, PrinceMayMove PARAMS, PrincessMayMove PARAMS, AbbeyMayMove PARAMS, CannonMayMove PARAMS, GalleyMayMove PARAMS, PawnMayMove PARAMS; #undef PARAMS /* This function interprets the result of TraverseDir(piece...) */ Global Boolean IsMoveLegal(const Piece *piece, const Piece *dest) { if (dest == SQUARE_EMPTY) return TRUE; if (dest == SQUARE_INVALID) { n3DcErr = E3DcSIMPLE; return FALSE; } else if ( piece->bwSide == dest->bwSide ) { n3DcErr = E3DcBLOCK; return FALSE; } return TRUE; } Global Piece * PieceNew(const Title nType, const File x, const Rank y, const Level z, const Colour col) { Piece *piece; piece = (Piece *)malloc(sizeof(Piece)); if (!piece) return NULL; piece->xyzPos.xFile = x; piece->xyzPos.yRank = y; piece->xyzPos.zLevel = z; piece->bwSide = col; piece->nName = nType; piece->bVisible = TRUE; piece->bHasMoved = FALSE; return piece; } Global void PieceDelete(Piece *piece) { if (Board[piece->xyzPos.zLevel][piece->xyzPos.yRank][piece->xyzPos.xFile] == piece) Board[piece->xyzPos.zLevel][piece->xyzPos.yRank][piece->xyzPos.xFile] = NULL; /* We don't need to remove the piece from the muster, as pieceDelete * is only called when restarting, at which time init3Dc is also called, * thereby overwriting Muster's reference to the piece. The only reason * for removing the piece from Board above is so that the board may be * redrawn cleanly. */ free(piece); piece = NULL; } Global Boolean PieceMayMove(Piece *piece, const File xNew, const Rank yNew, const Level zNew) { Boolean retval; if (!piece || !piece->bVisible) { n3DcErr = E3DcINVIS; return FALSE; } /* Do bits which are the same for all pieces first */ if (xNew == piece->xyzPos.xFile && yNew == piece->xyzPos.yRank && zNew == piece->xyzPos.zLevel) { n3DcErr = E3DcSIMPLE; return FALSE; } if ((Board[zNew][yNew][xNew] != NULL) && (Board[zNew][yNew][xNew]->bVisible == TRUE) && (Board[zNew][yNew][xNew]->bwSide == piece->bwSide)) { n3DcErr = E3DcBLOCK; return FALSE; /* Can't take a piece on your team */ } switch (piece->nName) { case king: retval = KingMayMove(piece, xNew, yNew, zNew); break; case queen: retval = QueenMayMove(piece, xNew, yNew, zNew); break; case bishop: retval = BishopMayMove(piece, xNew, yNew, zNew); break; case knight: retval = KnightMayMove(piece, xNew, yNew, zNew); break; case rook: retval = RookMayMove(piece, xNew, yNew, zNew); break; case prince: retval = PrinceMayMove(piece, xNew, yNew, zNew); break; case princess: retval = PrincessMayMove(piece, xNew, yNew, zNew); break; case abbey: retval = AbbeyMayMove(piece, xNew, yNew, zNew); break; case cannon: retval = CannonMayMove(piece, xNew, yNew, zNew); break; case galley: retval = GalleyMayMove(piece, xNew, yNew, zNew); break; case pawn: retval = PawnMayMove(piece, xNew, yNew, zNew); break; default: retval = FALSE; n3DcErr = E3DcSIMPLE; } if ( retval != FALSE ) { if ( FakeMoveAndIsKingChecked(piece, xNew, yNew, zNew) == TRUE ) { n3DcErr = E3DcCHECK; return FALSE; } } return retval; } /* * Execute the move */ Global Boolean PieceMove(Piece *piece, const File xNew, const Rank yNew, const Level zNew) { Move thisMove; Boolean moveType; /* Not quite Boolean... */ if (!(moveType = PieceMayMove(piece, xNew, yNew, zNew))) return FALSE; /* * Keep record of move */ thisMove.xyzBefore.xFile = piece->xyzPos.xFile; thisMove.xyzBefore.yRank = piece->xyzPos.yRank; thisMove.xyzBefore.zLevel = piece->xyzPos.zLevel; thisMove.xyzAfter.xFile = xNew; thisMove.xyzAfter.yRank = yNew; thisMove.xyzAfter.zLevel = zNew; if (moveType == EnPASSANT) { thisMove.nHadMoved = EnPASSANT; thisMove.pVictim = Board[zNew][yNew + (piece->bwSide == WHITE ? -1 : 1)][xNew]; } else if (moveType == CASTLE) { thisMove.nHadMoved = CASTLE; thisMove.pVictim = Board[1][yNew][xNew < ((FILES-1)/2) ? 0 : FILES-1]; } else if (moveType == PROMOTE) { thisMove.nHadMoved = PROMOTE; thisMove.pVictim = Board[zNew][yNew][xNew]; } else { thisMove.nHadMoved = piece->bHasMoved; thisMove.pVictim = Board[zNew][yNew][xNew]; } StackPush(MoveStack, &thisMove); piece->bHasMoved = TRUE; PieceDisplay(piece, FALSE); Board[piece->xyzPos.zLevel][piece->xyzPos.yRank][piece->xyzPos.xFile] = NULL; if (Board[zNew][yNew][xNew]) /* Kill victim */ { PieceDisplay(Board[zNew][yNew][xNew], FALSE); Board[zNew][yNew][xNew]->bVisible = FALSE; Board[zNew][yNew][xNew] = NULL; } Board[zNew][yNew][xNew] = piece; piece->xyzPos.xFile = xNew; piece->xyzPos.yRank = yNew; piece->xyzPos.zLevel = zNew; PieceDisplay(piece, TRUE); /* Now move any special pieces */ if (moveType == CASTLE) { int xRookSrc, xRookDest; /* If xNew on right of board then move to left * else move to right */ if (xNew > (FILES/2)) { xRookSrc = FILES -1; xRookDest = xNew -1; } else { xRookSrc = 0; xRookDest = xNew +1; } PieceDisplay(Board[1][yNew][xRookSrc], FALSE); Board[1][yNew][xRookDest] = Board[1][yNew][xRookSrc]; Board[1][yNew][xRookSrc] = NULL; (Board[1][yNew][xRookDest])->xyzPos.xFile = xRookDest; (Board[1][yNew][xRookDest])->bHasMoved = TRUE; PieceDisplay(Board[1][yNew][xRookDest], TRUE); } else if (moveType == EnPASSANT) { int yPawnSrc; /* If yNew is forward of half-way then victim is back one * else it is forward one */ yPawnSrc = (yNew > (RANKS/2) ? yNew-1 : yNew+1); PieceDisplay(Board[zNew][yPawnSrc][xNew], FALSE); Board[zNew][yPawnSrc][xNew]->bVisible = FALSE; Board[zNew][yPawnSrc][xNew] = NULL; } #if 0 /* I think that this code is obsolete.. */ /* Check that the king isn't in check */ if (IsKingChecked( piece->bwSide )) { /* Oops, this move puts the king in check; * it's illegal, so undo it */ PieceUndo(); n3DcErr = E3DcCHECK; return FALSE; } #endif /* If this bit is up with EnPASSANT and CASTLE, then the * promotion dialog pops up even though the promotion is * illegal. A promotion doesn't affect whether or not * the opponent checks your king (even if you promote to * cannon or something, you're still in the same place as * the dissappearing pawn..) so it works out better all * around if we just do it here. */ if (moveType == PROMOTE) { PieceDisplay(piece, FALSE); PiecePromote(piece); /* This function asks for promotion type, etc. */ } return TRUE; } /* * Undo the move */ Global Boolean PieceUndo(void) { Move *move; Colour bwMoved, bwTaken; Coord src, dest; move = StackPop(MoveStack); if (move == NULL) return FALSE; src = move->xyzAfter; dest = move->xyzBefore; bwMoved = Board[src.zLevel][src.yRank][src.xFile]->bwSide; bwTaken = (bwMoved == WHITE ? BLACK : WHITE); /* Clear the "moved-to" square */ PieceDisplay(Board[src.zLevel][src.yRank][src.xFile], FALSE); /* Move the "moved" piece back */ Board[dest.zLevel][dest.yRank][dest.xFile] = Board[src.zLevel][src.yRank][src.xFile]; (Board[dest.zLevel][dest.yRank][dest.xFile])->xyzPos = dest; Board[src.zLevel][src.yRank][src.xFile] = NULL; switch (move->nHadMoved) { case PROMOTE: /* This piece was promoted from a pawn: demote it */ Board[dest.zLevel][dest.yRank][dest.xFile]->nName = pawn; (Board[dest.zLevel][dest.yRank][dest.xFile])->bHasMoved = TRUE; break; case CASTLE: { int xRookSrc, xRookDest; /* xRookDest is beside edge */ /* The move undone was a castle */ /* The king is back in the right place; now * fix the rook */ if (src.xFile < dest.xFile) { /* Castled to a smaller-id square (Queen's side for white, * King's side for black) */ xRookSrc = dest.xFile -1; xRookDest = 0; } else { /* Castled to a larger-id square (Queen's side for black, * King's side for white) */ xRookSrc = dest.xFile +1; xRookDest = FILES -1; } PieceDisplay(Board[1][dest.yRank][xRookSrc], FALSE); Board[1][dest.yRank][xRookDest] = Board[1][dest.yRank][xRookSrc]; Board[1][dest.yRank][xRookSrc] = NULL; (Board[1][dest.yRank][xRookDest])->xyzPos.xFile = xRookDest; (Board[1][dest.yRank][xRookDest])->xyzPos.yRank = dest.yRank; (Board[1][dest.yRank][xRookDest])->xyzPos.zLevel = 1; PieceDisplay(Board[1][dest.yRank][xRookDest], TRUE); /* And finally---reset the bHasMoved flags */ (Board[1][dest.yRank][dest.xFile])->bHasMoved = FALSE; (Board[1][dest.yRank][xRookDest])->bHasMoved = FALSE; } break; case EnPASSANT: FallThrough(); default: (Board[dest.zLevel][dest.yRank][dest.xFile])->bHasMoved = move->nHadMoved; } /* Draw the piece in its original space */ PieceDisplay(Board[dest.zLevel][dest.yRank][dest.xFile], TRUE); /* Put any taken piece back */ if (move->pVictim) { Coord srcPos; /* Don't use src as the victim's square, as it could have * been en passant */ srcPos = move->pVictim->xyzPos; Board[srcPos.zLevel][srcPos.yRank][srcPos.xFile] = move->pVictim; Board[srcPos.zLevel][srcPos.yRank][srcPos.xFile]->bVisible = TRUE; PieceDisplay(Board[srcPos.zLevel][srcPos.yRank][srcPos.xFile], TRUE); } free(move); return TRUE; } /* * Here down are the specific piece-movement functions * * These all assume that piece is of the correct type. * No check is made and things get very odd if this assumption * is contradicted, so be careful. */ Local INLINE Boolean KingMayMove(Piece *piece, const File xNew, const Rank yNew, const Level zNew) { File xDiff, xCur, xInc; Rank yDiff; Level zDiff; xDiff = xNew - piece->xyzPos.xFile; yDiff = yNew - piece->xyzPos.yRank; zDiff = zNew - piece->xyzPos.zLevel; xDiff = ABS(xDiff); yDiff = ABS(yDiff); zDiff = ABS(zDiff); /* Not allowed move more than 1 except when castling */ if ( (piece->bHasMoved && (xDiff > 2)) || (yDiff > 1) || (zDiff > 1) ) { n3DcErr = E3DcDIST; return FALSE; } /* * At this stage, we have determined that, given an empty board, * the move is legal. Now take other pieces into account. */ if (FakeMoveAndIsKingChecked( piece, xNew, yNew, zNew) || ( (xDiff == 2) && FakeMoveAndIsKingChecked( piece, (xNew + piece->xyzPos.xFile)/2, yNew, zNew ) )) { n3DcErr = E3DcCHECK; return FALSE; } if (xDiff == 2) { /* Castling */ File xRook; if (yDiff || zDiff) { n3DcErr = E3DcSIMPLE; return FALSE; } /* * Determine x-pos of castling rook */ if (xNew > piece->xyzPos.xFile) xRook = FILES-1; else xRook = 0; if (piece->bHasMoved || Board[1][yNew][xRook]->bHasMoved) { n3DcErr = E3DcMOVED; return FALSE; } else if (!Board[1][yNew][xRook]) { n3DcErr = E3DcSIMPLE; return FALSE; } xInc = ( xRook == 0 ) ? -1 : 1 ; for (xCur = piece->xyzPos.xFile + xInc; xCur != xRook; xCur += xInc) { /* Is the castle blocked? */ if (Board[1][yNew][xCur]) { n3DcErr = E3DcBLOCK; return FALSE; } } return CASTLE; } return TRUE; } Local INLINE Boolean QueenMayMove(Piece *piece, const File xNew, const Rank yNew, const Level zNew) { File xDiff; Rank yDiff; Level zDiff; Piece *pDestSquare; xDiff = xNew - piece->xyzPos.xFile; yDiff = yNew - piece->xyzPos.yRank; zDiff = zNew - piece->xyzPos.zLevel; if ((xDiff && yDiff && (ABS(xDiff) != ABS(yDiff))) || (xDiff && zDiff && (ABS(xDiff) != ABS(zDiff))) || (yDiff && zDiff && (ABS(yDiff) != ABS(zDiff)))) { n3DcErr = E3DcSIMPLE; return False; } /* * At this stage, we have determined that, given an empty board, * the move is legal. Now take other pieces into account. */ pDestSquare = TraverseDir(piece, xDiff, yDiff, zDiff, MAX(ABS(xDiff), MAX(ABS(yDiff), ABS(zDiff)))); return IsMoveLegal(piece, pDestSquare); } Local INLINE Boolean BishopMayMove(Piece *piece, const File xNew, const Rank yNew, const Level zNew) { File xDiff; Rank yDiff; Level zDiff; Piece *pDestSquare; xDiff = xNew - piece->xyzPos.xFile; yDiff = yNew - piece->xyzPos.yRank; zDiff = zNew - piece->xyzPos.zLevel; if (!DIAG3D(xDiff, yDiff, zDiff)) { n3DcErr = E3DcSIMPLE; return FALSE; } /* * At this stage, we have determined that, given an empty board, * the move is legal. Now take other pieces into account. */ pDestSquare = TraverseDir(piece, xDiff, yDiff, zDiff, MAX(ABS(xDiff), ABS(yDiff))); return IsMoveLegal(piece, pDestSquare); } Local INLINE Boolean KnightMayMove(Piece *piece, const File xNew, const Rank yNew, const Level zNew) { File xDiff; Rank yDiff; if (zNew != piece->xyzPos.zLevel) { n3DcErr = E3DcLEVEL; return FALSE; /* Knights may not change level */ } xDiff = xNew - piece->xyzPos.xFile; yDiff = yNew - piece->xyzPos.yRank; xDiff = ABS(xDiff); yDiff = ABS(yDiff); if ((xDiff == 0) || (yDiff == 0) || ((xDiff + yDiff) != 3)) return FALSE; return TRUE; } Local INLINE Boolean RookMayMove(Piece *piece, const File xNew, const Rank yNew, const Level zNew) { File xDiff; Rank yDiff; Level zDiff; Piece *pDestSquare; xDiff = xNew - piece->xyzPos.xFile; yDiff = yNew - piece->xyzPos.yRank; zDiff = zNew - piece->xyzPos.zLevel; if (!HORZ3D(xDiff, yDiff, zDiff)) { n3DcErr = E3DcSIMPLE; return FALSE; } /* * At this stage, we have determined that, given an empty board, * the move is legal. Now take other pieces into account. */ pDestSquare = TraverseDir(piece, xDiff, yDiff, zDiff, MAX(ABS(xDiff), MAX(ABS(yDiff), ABS(zDiff)))); return IsMoveLegal(piece, pDestSquare); } Local INLINE Boolean PrinceMayMove(Piece *piece, const File xNew, const Rank yNew, const Level zNew) { File xDiff; Rank yDiff; if (zNew != piece->xyzPos.zLevel) { n3DcErr = E3DcLEVEL; return FALSE; /* Princes may not change level */ } xDiff = xNew - piece->xyzPos.xFile; yDiff = yNew - piece->xyzPos.yRank; xDiff = ABS(xDiff); yDiff = ABS(yDiff); if (xDiff > 1 || yDiff > 1) /* Not allowed move more than 1 */ { n3DcErr = E3DcDIST; return FALSE; } return TRUE; } Local INLINE Boolean PrincessMayMove(Piece *piece, const File xNew, const Rank yNew, const Level zNew) { File xDiff; Rank yDiff; Piece * pDestSquare; if (zNew != piece->xyzPos.zLevel) { n3DcErr = E3DcLEVEL; return FALSE; /* Princesses may not change level */ } xDiff = xNew - piece->xyzPos.xFile; yDiff = yNew - piece->xyzPos.yRank; if (xDiff && yDiff && (ABS(xDiff) != ABS(yDiff))) { n3DcErr = E3DcSIMPLE; return FALSE; } /* * At this stage, we have determined that, given an empty board, * the move is legal. Now take other pieces into account. */ pDestSquare = TraverseDir(piece, xDiff, yDiff, 0, MAX(ABS(xDiff), ABS(yDiff))); return IsMoveLegal(piece, pDestSquare); } Local INLINE Boolean AbbeyMayMove(Piece *piece, const File xNew, const Rank yNew, const Level zNew) { File xDiff; Rank yDiff; Piece *pDestSquare; if (zNew != piece->xyzPos.zLevel) { n3DcErr = E3DcLEVEL; return FALSE; /* Abbies may not change level */ } xDiff = xNew - piece->xyzPos.xFile; yDiff = yNew - piece->xyzPos.yRank; if (!DIAG(xDiff, yDiff)) { n3DcErr = E3DcSIMPLE; return FALSE; } /* * At this stage, we have determined that, given an empty board, * the move is legal. Now take other pieces into account. */ pDestSquare = TraverseDir(piece, xDiff, yDiff, 0, MAX(ABS(xDiff), ABS(yDiff))); return IsMoveLegal(piece, pDestSquare); } Local INLINE Boolean CannonMayMove(Piece *piece, const File xNew, const Rank yNew, const Level zNew) { File xDiff; Rank yDiff; Level zDiff; xDiff = xNew - piece->xyzPos.xFile; yDiff = yNew - piece->xyzPos.yRank; zDiff = zNew - piece->xyzPos.zLevel; xDiff = ABS(xDiff); yDiff = ABS(yDiff); zDiff = ABS(zDiff); if (((xDiff + yDiff + zDiff) != 6) || ((xDiff != 3) && (yDiff != 3)) || ((xDiff != 2) && (yDiff != 2) && (zDiff != 2))) { n3DcErr = E3DcSIMPLE; return FALSE; } return TRUE; } Local INLINE Boolean GalleyMayMove(Piece *piece, const File xNew, const Rank yNew, const Level zNew) { File xDiff; Rank yDiff; Piece *pDestSquare; if (zNew != piece->xyzPos.zLevel) { n3DcErr = E3DcLEVEL; return FALSE; /* Gallies may not change level */ } xDiff = xNew - piece->xyzPos.xFile; yDiff = yNew - piece->xyzPos.yRank; if (!HORZ(xDiff, yDiff)) { n3DcErr = E3DcSIMPLE; return FALSE; } /* * At this stage, we have determined that, given an empty board, * the move is legal. Now take other pieces into account. */ pDestSquare = TraverseDir(piece, xDiff, yDiff, 0, MAX(ABS(xDiff), ABS(yDiff))); return IsMoveLegal(piece, pDestSquare); } Local INLINE Boolean PawnMayMove(Piece *piece, const File xNew, const Rank yNew, const Level zNew) { File xDiff; Rank yDiff, yInc; if (zNew != piece->xyzPos.zLevel) { n3DcErr = E3DcLEVEL; return FALSE; /* Pawns may not change level */ } xDiff = xNew - piece->xyzPos.xFile; yInc = yDiff = yNew - piece->xyzPos.yRank; xDiff = ABS(xDiff); yDiff = ABS(yDiff); /* * Pawns must move at least 1 forward */ if ((yDiff == 0) || ((yInc < 0) && (piece->bwSide == WHITE)) || ((yInc > 0) && (piece->bwSide == BLACK))) /* Moving backwards */ { n3DcErr = E3DcSIMPLE; return FALSE; } /* Check the definitely-illegal moves first.. */ if (xDiff > 1 || (xDiff == 1 && yDiff != 1)) { n3DcErr = E3DcSIMPLE; return FALSE; } /* * It is difficult to cater for 'en passant' in the middle of a * conditional. So, against all convention laid out in other * rules functions, I am checking a move and returning true if it * is valid, rather than returning FALSE if it is invalid. */ #if 0 /* * TODO: * Only allow en passant taking of pawns that moved two spaces * forward in one go (in the previous move only?) * Each piece must have an identifier; either its memory location * or its offset into the Muster. That way this can be used as the * 4th line of this conditional. */ ( StackPeek(MoveStack, 1)->nId == Board[zNew][yNew - yInc][xNew]->nId && !(StackPeek(MoveStack, 1)->nHadMoved) && Board[zNew][yNew - yInc][xNew]->bHasMoved /* Moved only once */ ) #endif /* 0 */ if (xDiff == 1 && yDiff == 1 && !Board[zNew][yNew][xNew]) { /* En passant? */ if (Board[zNew][yNew - yInc][xNew] && /* 'Takable' piece */ Board[zNew][yNew - yInc][xNew]->nName == pawn && /* Is pawn */ Board[zNew][yNew - yInc][xNew]->bwSide != piece->bwSide && /* Is enemy */ 1) /* Dummy line to reduce no. of changes */ { return EnPASSANT; } else { n3DcErr = E3DcSIMPLE; return FALSE; } } /* * Pawns can not move forward under these conditions: * They move more than 2 * They move more than 1 and they have already moved * They attempt to take any piece (catered for in next conditional) */ if (yDiff > 2 || /* Move too far */ (piece->bHasMoved && yDiff == 2)) /* Move too far */ { n3DcErr = E3DcDIST; return FALSE; } /* * Pawns may not take anything under these conditions: * They do not move diagonally forward one space * The victim is an ally */ if (Board[zNew][yNew][xNew] && /* Taking something */ (!(xDiff == 1 && yDiff == 1) || /* Not moving diagonally */ Board[zNew][yNew][xNew]->bwSide == piece->bwSide)) { n3DcErr = E3DcSIMPLE; return FALSE; } /* Check for possible promotion */ if ((yNew == FILES-1 && piece->bwSide == WHITE) || (yNew == 0 && piece->bwSide == BLACK)) return PROMOTE; return TRUE; }