/* File: MBCBoard.mm Contains: Implementation of fundamental board and move classes Copyright: © 2002-2005 Apple Computer, Inc. All rights reserved. IMPORTANT: This Apple software is supplied to you by Apple Computer, Inc. ("Apple") in consideration of your agreement to the following terms, and your use, installation, modification or redistribution of this Apple software constitutes acceptance of these terms. If you do not agree with these terms, please do not use, install, modify or redistribute this Apple software. In consideration of your agreement to abide by the following terms, and subject to these terms, Apple grants you a personal, non-exclusive license, under Apple's copyrights in this original Apple software (the "Apple Software"), to use, reproduce, modify and redistribute the Apple Software, with or without modifications, in source and/or binary forms; provided that if you redistribute the Apple Software in its entirety and without modifications, you must retain this notice and the following text and disclaimers in all such redistributions of the Apple Software. Neither the name, trademarks, service marks or logos of Apple Computer, Inc. may be used to endorse or promote products derived from the Apple Software without specific prior written permission from Apple. Except as expressly stated in this notice, no other rights or licenses, express or implied, are granted by Apple herein, including but not limited to any patent rights that may be infringed by your derivative works or by other works in which the Apple Software may be incorporated. The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #import "MBCBoard.h" #import "MBCEngineCommands.h" #import "MBCMoveGenerator.h" #import <string.h> #include <ctype.h> MBCPiece Captured(MBCPiece victim) { victim = Opposite(victim & ~kPieceMoved); if (Promoted(victim)) // Captured promoted pieces revert to pawns return Matching(victim, PAWN); else return victim; } static const char * sPieceChar = " KQBNRP"; @implementation MBCMove - (id) initWithCommand:(MBCMoveCode)command; { fCommand = command; fFromSquare = kInvalidSquare; fToSquare = kInvalidSquare; fPiece = EMPTY; fPromotion = EMPTY; fVictim = EMPTY; fCastling = kUnknownCastle; fEnPassant = NO; fAnimate = YES; return self; } + (id) newWithCommand:(MBCMoveCode)command { return [[MBCMove alloc] initWithCommand:command]; } + (id) moveWithCommand:(MBCMoveCode)command { return [[MBCMove newWithCommand:command] autorelease]; } - (id) initFromCompactMove:(MBCCompactMove)move { [self initWithCommand:MBCMoveCode(move >> 24)]; switch (fCommand) { case kCmdMove: case kCmdPMove: fFromSquare = (move >> 16) & 0xFF; fToSquare = (move >> 8) & 0xFF; fPromotion = move & 0xFF; break; case kCmdDrop: case kCmdPDrop: fToSquare = (move >> 8) & 0xFF; fPiece = move & 0xFF; break; default: break; } return self; } + (id) newFromCompactMove:(MBCCompactMove)move { return [[MBCMove alloc] initFromCompactMove:move]; } + (id) moveFromCompactMove:(MBCCompactMove)move { return [[MBCMove newFromCompactMove:move] autorelease]; } - (id) initFromEngineMove:(NSString *)engineMove { const char * piece = " KQBNRP kqbnrp "; const char * move = [engineMove cString]; if (move[1] == '@') { [self initWithCommand:kCmdDrop]; fPiece = static_cast<MBCPiece>(strchr(piece, move[0])-piece); fToSquare = Square(move+2); } else { [self initWithCommand:kCmdMove]; fFromSquare = Square(move); fToSquare = Square(move+2); if (move[4]) fPromotion = static_cast<MBCPiece>(strchr(piece, move[0])-piece); } return self; } + (id) newFromEngineMove:(NSString *)engineMove { return [[MBCMove alloc] initFromEngineMove:engineMove]; } + (id) moveFromEngineMove:(NSString *)engineMove { return [[MBCMove newFromEngineMove:engineMove] autorelease]; } NSString * sPieceLetters[] = { @"", @"king_letter", @"queen_letter", @"bishop_letter", @"knight_letter", @"rook_letter", @"pawn_letter" }; - (NSString *) pieceLetter:(MBCPiece)piece forDrop:(BOOL)drop { piece = Piece(piece); if (!drop && piece==PAWN) return @" "; else return NSLocalizedString(sPieceLetters[piece], "Piece Letter"); } - (NSString *) localizedText:(BOOL)aligned { const char * tab = aligned ? "\t" : ""; switch (fCommand) { case kCmdMove: case kCmdPMove: if (fPromotion) return [NSString stringWithFormat:@"%s%c%c%s%c%s%c%c=%@", tab, Col(fFromSquare), Row(fFromSquare)+'0', tab, fVictim ? 'x' :'-', tab, Col(fToSquare), Row(fToSquare)+'0', [self pieceLetter:fPromotion forDrop:YES]]; else if (fCastling != kNoCastle) return [NSString stringWithFormat:@"%s%s0-0%s", tab, tab, fCastling == kCastleQueenside ? "-O" : ""]; else return [NSString stringWithFormat:@"%s%@%c%c%s%c%s%c%c", tab, [self pieceLetter:fPiece forDrop:NO], Col(fFromSquare), Row(fFromSquare)+'0', tab, fVictim ? 'x' : '-', tab, Col(fToSquare), Row(fToSquare)+'0']; case kCmdDrop: case kCmdPDrop: return [NSString stringWithFormat:@"%s%@%s@%s%c%c", tab, [self pieceLetter:fPiece forDrop:YES], tab, tab, Col(fToSquare), Row(fToSquare)+'0']; default: return @""; } } - (NSString *) engineMove { const char * piece = " KQBNRP kqbnrp "; #define SQUARETOCOORD(sq) Col(sq), Row(sq)+'0' switch (fCommand) { case kCmdMove: if (fPromotion) return [NSString stringWithFormat:@"%c%c%c%c%c\n", SQUARETOCOORD(fFromSquare), SQUARETOCOORD(fToSquare), piece[fPromotion]]; else return [NSString stringWithFormat:@"%c%c%c%c\n", SQUARETOCOORD(fFromSquare), SQUARETOCOORD(fToSquare)]; case kCmdDrop: return [NSString stringWithFormat:@"%c@%c%c\n", piece[fPiece], SQUARETOCOORD(fToSquare)]; break; default: return nil; } } @end @implementation MBCBoard - (id) init { fMoves = nil; [self reset]; return self; } - (void) reset { memset(fCurPos.fBoard, EMPTY, 64); memset(fCurPos.fInHand, 0, 16); /* White pieces */ fCurPos.fBoard[Square('a',1)] = White(ROOK); fCurPos.fBoard[Square('b',1)] = White(KNIGHT); fCurPos.fBoard[Square('c',1)] = White(BISHOP); fCurPos.fBoard[Square('d',1)] = White(QUEEN); fCurPos.fBoard[Square('e',1)] = White(KING); fCurPos.fBoard[Square('f',1)] = White(BISHOP); fCurPos.fBoard[Square('g',1)] = White(KNIGHT); fCurPos.fBoard[Square('h',1)] = White(ROOK); fCurPos.fBoard[Square('a',2)] = White(PAWN); fCurPos.fBoard[Square('b',2)] = White(PAWN); fCurPos.fBoard[Square('c',2)] = White(PAWN); fCurPos.fBoard[Square('d',2)] = White(PAWN); fCurPos.fBoard[Square('e',2)] = White(PAWN); fCurPos.fBoard[Square('f',2)] = White(PAWN); fCurPos.fBoard[Square('g',2)] = White(PAWN); fCurPos.fBoard[Square('h',2)] = White(PAWN); /* Black pieces */ fCurPos.fBoard[Square('a',7)] = Black(PAWN); fCurPos.fBoard[Square('b',7)] = Black(PAWN); fCurPos.fBoard[Square('c',7)] = Black(PAWN); fCurPos.fBoard[Square('d',7)] = Black(PAWN); fCurPos.fBoard[Square('e',7)] = Black(PAWN); fCurPos.fBoard[Square('f',7)] = Black(PAWN); fCurPos.fBoard[Square('g',7)] = Black(PAWN); fCurPos.fBoard[Square('h',7)] = Black(PAWN); fCurPos.fBoard[Square('a',8)] = Black(ROOK); fCurPos.fBoard[Square('b',8)] = Black(KNIGHT); fCurPos.fBoard[Square('c',8)] = Black(BISHOP); fCurPos.fBoard[Square('d',8)] = Black(QUEEN); fCurPos.fBoard[Square('e',8)] = Black(KING); fCurPos.fBoard[Square('f',8)] = Black(BISHOP); fCurPos.fBoard[Square('g',8)] = Black(KNIGHT); fCurPos.fBoard[Square('h',8)] = Black(ROOK); fPrvPos = fCurPos; fVariant = kVarNormal; fMoveClock = 0; [fMoves release]; fMoves = [[NSMutableArray alloc] init]; fPromotion[0] = QUEEN; fPromotion[1] = QUEEN; } - (void) startGame:(MBCVariant)variant { [self reset]; fVariant = variant; } - (MBCPiece) curContents:(MBCSquare)square { return fCurPos.fBoard[square]; } - (MBCPiece) oldContents:(MBCSquare)square { return fPrvPos.fBoard[square]; } - (int) curInHand:(MBCPiece)piece { return fCurPos.fInHand[piece]; } - (int) oldInHand:(MBCPiece)piece { return fPrvPos.fInHand[piece]; } // // After every move, pieces on the board and in hand have to balance out // - (void) consistencyCheck { char inventory[8]; memset(inventory, 0, 8); inventory[PAWN] = 16; inventory[ROOK] = 4; inventory[KNIGHT] = 4; inventory[BISHOP] = 4; inventory[QUEEN] = 2; inventory[KING] = 2; for (MBCPiece * p = fCurPos.fBoard; p < fCurPos.fBoard+64; ++p) { MBCPiece piece = *p; --inventory[Promoted(piece) ? PAWN : Piece(piece)]; } for (int i = 1; i < 8; ++i) { inventory[i] -= fCurPos.fInHand[i]+fCurPos.fInHand[i|kBlackPiece]; if (inventory[i]) NSLog(@"Board consistency check: %d %d\n", i, inventory[i]); } #if 0 MBCMoveGenerator logMoves([MBCDebugMoveBuilder debugMoveBuilder], fVariant, 0); logMoves.Generate(!([fMoves count] & 1), fCurPos); if (logMoves.InCheck(!([fMoves count] & 1), fCurPos)) NSLog(@"Check!"); #endif } - (void) makeMove:(MBCMove *)move { // // Ignore everything except moves & drops // if (move->fCommand != kCmdMove && move->fCommand != kCmdDrop) return; // // Make the move on the board // MBCSquare toSquare = move->fToSquare; MBCSquare fromSquare = move->fFromSquare; MBCPiece * board = fCurPos.fBoard; char * inHand = fCurPos.fInHand; MBCPiece piece = move->fPromotion; if (move->fCommand == kCmdMove) { move->fPiece = board[fromSquare]; [self tryCastling:move]; [self tryPromotion:move]; if (!piece) { // // Not a pawn promotion, piece stays the same // piece = move->fPiece; } else { // // Pawn promotion // move->fPromotion = Piece(piece) | Color(move->fPiece) | kPromoted; piece = move->fPromotion | kPieceMoved; } if (MBCPiece victim = board[toSquare]) { // // Record captured piece // move->fVictim = victim; ++inHand[Captured(victim)]; } else if (Piece(piece) == PAWN && Col(fromSquare) != Col(toSquare)) { // // En passant capture // MBCSquare victimSquare = Square(Col(toSquare), Row(fromSquare)); MBCPiece victim = board[victimSquare]; move->fVictim = victim; ++inHand[Captured(victim)]; board[victimSquare] = EMPTY; move->fEnPassant = YES; } if (move->fVictim || Piece(move->fPiece) == PAWN) fMoveClock = 0; else ++fMoveClock; board[fromSquare] = EMPTY; unsigned row = Row(toSquare); switch (move->fCastling) { case kUnknownCastle: case kNoCastle: break; case kCastleQueenside: board[Square('d', row)] = board[Square('a', row)] | kPieceMoved; board[Square('a', row)] = EMPTY; break; case kCastleKingside: board[Square('f', row)] = board[Square('h', row)] | kPieceMoved; board[Square('h', row)] = EMPTY; break; } piece |= kPieceMoved; if (!move->fVictim && Piece(piece) == PAWN && abs(Row(fromSquare)-Row(toSquare))==2 ) fCurPos.fEnPassant = Square(Col(fromSquare), (Row(fromSquare)+Row(toSquare))/2); else fCurPos.fEnPassant = kInvalidSquare; } else { // // Drop, deplete in hand pieces. A dropped piece is not considered // to have moved yet. // piece = move->fPiece; --inHand[piece]; } board[toSquare] = piece; // // Record the move made in undo buffer // [fMoves addObject:move]; [self consistencyCheck]; } - (MBCCastling) tryCastling:(MBCMove *)move { move->fCastling = kNoCastle; MBCSquare fromSquare = move->fFromSquare; MBCPiece * board = fCurPos.fBoard; MBCPiece king = move->fPiece; if (Piece(king) != KING || king & kPieceMoved) return kNoCastle; MBCPieceCode kingColor = Color(king); unsigned row = Row(move->fToSquare); if (Row(fromSquare) != row) return kNoCastle; // // These comparisons will fail if the rook has kPieceMoved set, which // they should. // switch (Col(move->fToSquare)) { case 'c': // Queenside castle if (board[Square('a', row)] != Matching(kingColor, ROOK) || board[Square('b', row)] != EMPTY || board[Square('c', row)] != EMPTY || board[Square('d', row)] != EMPTY ) return kNoCastle; else return move->fCastling = kCastleQueenside; case 'g': // Kingside castle if (board[Square('h', row)] != Matching(kingColor, ROOK) || board[Square('g', row)] != EMPTY || board[Square('f', row)] != EMPTY ) return kNoCastle; else return move->fCastling = kCastleKingside; default: return kNoCastle; } } - (void)tryPromotion:(MBCMove *)move { if (move->fCommand == kCmdMove && Piece(fCurPos.fBoard[move->fFromSquare]) == PAWN ) // // Possibly a promotion where we need to fill in the piece // switch (Row(move->fToSquare)) { case 1: // // Black promotion // if (!move->fPromotion) move->fPromotion = fPromotion[0]; break; case 8: // // White promotion // if (!move->fPromotion) move->fPromotion = fPromotion[1]; break; default: move->fPromotion = EMPTY; break; } } - (BOOL) reachFromCol:(char)fromCol row:(unsigned)fromRow deltaCol:(int)colDelta row:(int)rowDelta steps:(int)steps { if (steps < 0) steps = -steps; while (--steps > 0) if (fCurPos.fBoard[Square(fromCol += colDelta, fromRow += rowDelta)]) return NO; // Occupied square in between return YES; } - (BOOL) reachDiagonalFromCol:(char)fromCol row:(unsigned)fromRow toCol:(char)toCol row:(unsigned)toRow { int colDiff = toCol - fromCol; int rowDiff = (int)toRow - (int)fromRow; if (colDiff != rowDiff && colDiff != -rowDiff) return NO; // Not on same diagonal return [self reachFromCol:fromCol row:fromRow deltaCol:(colDiff<0 ? -1 : 1) row:(rowDiff<0 ? -1 : 1) steps:colDiff]; } - (BOOL) reachStraightFromCol:(char)fromCol row:(unsigned)fromRow toCol:(char)toCol row:(unsigned)toRow { if (fromRow==toRow) return [self reachFromCol:fromCol row:fromRow deltaCol:(toCol<fromCol ? -1 : 1) row:0 steps:toCol-fromCol]; else if (fromCol==toCol) return [self reachFromCol:fromCol row:fromRow deltaCol:0 row:(toRow<fromRow ? -1 : 1) steps:toRow-fromRow]; else return NO; } - (MBCUnique) disambiguateMove:(MBCMove *)move { MBCSquare from = move->fFromSquare; unsigned fromRow = Row(from); char fromCol = Col(from); MBCSquare to = move->fToSquare; unsigned toRow = Row(to); char toCol = Col(to); MBCPiece piece = fCurPos.fBoard[from]; MBCUnique unique = 0; for (char col = 'a'; col < 'i'; ++col) for (unsigned row = 1; row<9; ++row) { if (col == fromCol && row == fromRow) continue; // Same as from square if (col == toCol && row == toRow) continue; // Same as to square if ((fCurPos.fBoard[Square(col, row)] ^ piece) & 15) continue; // Not a matching piece switch (Piece(piece)) { case PAWN: // // The only column ambiguities can exist with captures // if (fromRow == row && toCol-fromCol == col-toCol) break; continue; case KING: // Multiple kings? Only in suicide if (col>=toCol-1 && col<=toCol+1 && row>=toRow-1 && row<=toRow+1) break; continue; case KNIGHT: if (col == toCol-1 || col == toCol+1) { if (row == toRow-2 || row == toRow+2) break; } else if (col == toCol-2 || col == toCol+2) { if (row == toRow-1 || row == toRow+1) break; } continue; case BISHOP: if ([self reachDiagonalFromCol:col row:row toCol:toCol row:toRow]) break; continue; case QUEEN: if ([self reachDiagonalFromCol:col row:row toCol:toCol row:toRow]) break; // Fall through case ROOK: if ([self reachStraightFromCol:col row:row toCol:toCol row:toRow]) break; continue; default: continue; } unique |= kMatchingPieceExists; if (row == fromRow) unique |= kMatchingPieceOnSameRow; if (col == fromCol) unique |= kMatchingPieceOnSameCol; } return unique; } - (bool) undoMoves:(int)numMoves { if ((int)[fMoves count]<numMoves) return false; if (fMoveClock < numMoves) fMoveClock = 0; else fMoveClock -= numMoves; while (numMoves-- > 0) { MBCMove * move = [fMoves lastObject]; MBCPiece * board = fCurPos.fBoard; char * inHand = fCurPos.fInHand; if (move->fCommand == kCmdMove) { board[move->fFromSquare] = move->fPiece; unsigned row = Row(move->fToSquare); switch (move->fCastling) { case kUnknownCastle: case kNoCastle: break; case kCastleQueenside: board[Square('a', row)] = board[Square('d', row)] & ~kPieceMoved; board[Square('d', row)] = EMPTY; break; case kCastleKingside: board[Square('h', row)] = board[Square('f', row)] & ~kPieceMoved; board[Square('f', row)] = EMPTY; break; } } else ++inHand[move->fPiece]; board[move->fToSquare] = EMPTY; if (MBCPiece victim = move->fVictim) { MBCSquare victimSquare = move->fEnPassant ? Square(Col(move->fToSquare), Row(move->fFromSquare)) : move->fToSquare; board[victimSquare] = victim; --inHand[Captured(victim)]; } [fMoves removeLastObject]; [self consistencyCheck]; } fPrvPos = fCurPos; return true; } - (void) commitMove { fPrvPos = fCurPos; } - (NSString *) fen { char pos[128]; char * p = pos; *p++ = ' '; for (MBCSquare rank = 64; rank; rank -= 8) { for (MBCSquare square = rank-8; square < rank; ++square) if (MBCPiece piece = fCurPos.fBoard[square]) *p++ = " KQBNRP kqbnrp "[What(piece)]; else if (isdigit(p[-1])) ++p[-1]; else *p++ = '1'; if (rank > 8) *p++ = '/'; } *p++ = ' '; *p++ = ([fMoves count]&1) ? 'b' : 'w'; *p++ = ' '; if (fCurPos.fBoard[Square('e', 1)] == White(KING)) { if (fCurPos.fBoard[Square('h', 1)] == White(ROOK)) *p++ = 'K'; if (fCurPos.fBoard[Square('a', 1)] == White(ROOK)) *p++ = 'Q'; } if (fCurPos.fBoard[Square('e', 8)] == Black(KING)) { if (fCurPos.fBoard[Square('h', 8)] == Black(ROOK)) *p++ = 'k'; if (fCurPos.fBoard[Square('a', 8)] == Black(ROOK)) *p++ = 'q'; } if (p[-1] == ' ') *p++ = '-'; *p++ = ' '; *p++ = '-'; if ([fMoves count]) { MBCMove * move = [fMoves lastObject]; if ((move->fPiece & (7|kPieceMoved)) == PAWN && (Row(move->fToSquare) & 6) == 4 ) { p[-1] = Col(move->fToSquare); *p++ = Row(move->fToSquare) == 4 ? '3' : '6'; } } sprintf(p, " %d %d", fMoveClock, ([fMoves count]/2)+1); return [NSString stringWithCString:pos+1]; } - (NSString *) holding { char pos[128]; char * p = pos; *p++ = '['; for (MBCPiece piece = White(KING); piece <= Black(PAWN); ++piece) { for (int count = fCurPos.fInHand[piece]; count--; ) *p++ = " KQBNRP "[Piece(piece)]; if (piece == 8) { strcpy(p, "] ["); p += 3; } } strcpy(p, "]"); return [NSString stringWithCString:pos]; } - (NSString *)moves { NSMutableString * moves = [NSMutableString stringWithCapacity:200]; int numMoves = [fMoves count]; for (int m = 0; m<numMoves; ++m) { MBCMove * move = [fMoves objectAtIndex:m]; [moves appendString:[move engineMove]]; } return moves; } - (void) setFen:(NSString *)fen holding:(NSString *)holding moves:(NSString *)moves { if (moves) { // // We prefer to restore the game by replaying the moves // [self reset]; NSArray * m = [moves componentsSeparatedByString:@"\n"]; NSEnumerator * e = [m objectEnumerator]; while (NSString * move = [e nextObject]) if ([move length]) [self makeMove:[MBCMove moveFromEngineMove:move]]; if (![fen isEqual:[self fen]]) NSLog(@"FEN Mismatch, Expected: <%@> Got <%@>\n", fen, [self fen]); } else { const char * s = [fen cString]; MBCPiece * b = fCurPos.fBoard+56; MBCPiece p; memset(fCurPos.fBoard, 0, 64); while (isspace(*s)) ++s; do { switch (*s++) { case 'K': p = White(KING) | kPieceMoved; break; case 'Q': p = White(QUEEN); break; case 'B': p = White(BISHOP); break; case 'N': p = White(KNIGHT); break; case 'R': p = White(ROOK) | kPieceMoved; break; case 'P': p = White(PAWN) | kPieceMoved; break; case 'k': p = Black(KING) | kPieceMoved; break; case 'q': p = Black(QUEEN); break; case 'b': p = Black(BISHOP); break; case 'n': p = Black(KNIGHT); break; case 'r': p = Black(ROOK) | kPieceMoved; break; case 'p': p = Black(PAWN) | kPieceMoved; break; case '8': case '7': case '6': case '5': case '4': case '3': case '2': case '1': p = EMPTY; b += s[-1]-'0'; if (!((b-fCurPos.fBoard) & 7)) b -= 16; // Start previous rank break; case '/': p = EMPTY; break; } if (p) { *b++ = p; if (!((b-fCurPos.fBoard) & 7)) b -= 16; // Start previous rank } } while (b >= fCurPos.fBoard); while (isspace(*s)) ++s; if (*s++ == 'b') [fMoves addObject:[MBCMove moveWithCommand:kCmdNull]]; while (isspace(*s)) ++s; while (!isspace(*s)) switch (*s++) { case 'K': fCurPos.fBoard[4] &= ~kPieceMoved; fCurPos.fBoard[7] &= ~kPieceMoved; break; case 'Q': fCurPos.fBoard[4] &= ~kPieceMoved; fCurPos.fBoard[0] &= ~kPieceMoved; break; case 'k': fCurPos.fBoard[60] &= ~kPieceMoved; fCurPos.fBoard[63] &= ~kPieceMoved; break; case 'q': fCurPos.fBoard[60] &= ~kPieceMoved; fCurPos.fBoard[56] &= ~kPieceMoved; break; } while (isspace(*s)) ++s; if (*s == '-') fCurPos.fBoard[Square(*s, s[1]-'0')] &= ~kPieceMoved; s += 2; while (isspace(*s)) ++s; fMoveClock = 0; while (isdigit(*s)) fMoveClock = 10*fMoveClock + *s++ - '0'; memset(fCurPos.fInHand, 0, 16); s = [holding cString]; s = strchr(s, '['); if (!s) return; do { switch (*++s) { case 'Q': p = White(QUEEN); break; case 'B': p = White(BISHOP); break; case 'N': p = White(KNIGHT); break; case 'R': p = White(ROOK); break; case 'P': p = White(PAWN); break; default: p = 0; break; } if (p) ++fCurPos.fInHand[p]; } while (p); s = strchr(s, '['); if (!s) return; do { switch (*++s) { case 'Q': p = Black(QUEEN); break; case 'B': p = Black(BISHOP); break; case 'N': p = Black(KNIGHT); break; case 'R': p = Black(ROOK); break; case 'P': p = Black(PAWN); break; default: p = 0; break; } if (p) ++fCurPos.fInHand[p]; } while (p); } fPrvPos = fCurPos; } - (BOOL) saveMovesTo:(FILE *)f { NSArray * existingMoves = [fMoves copy]; int moves = [fMoves count]; // // Reset board so we can disambiguate moves // [self undoMoves:moves]; // // Now retrace the moves // for (int m = 0; m<moves; ++m) { if (!(m&1)) { if (!(m%10)) fputc('\n', f); fprintf(f, "%d. ", (m / 2)+1); } MBCMove * move = [existingMoves objectAtIndex:m]; if (move->fCommand == kCmdDrop) { // Drop, never ambiguous fprintf(f, "%c@%c%d ", sPieceChar[Piece(move->fPiece)], Col(move->fToSquare), Row(move->fToSquare)); } else { // Move, may be ambiguous MBCPiece p = Piece(fCurPos.fBoard[move->fFromSquare]); if (p==PAWN) { // Pawn moves look a bit different if (move->fVictim) // Capture fprintf(f, "%cx%c%d", Col(move->fFromSquare), Col(move->fToSquare), Row(move->fToSquare)); else // Move fprintf(f, "%c%d", Col(move->fToSquare), Row(move->fToSquare)); if (move->fPromotion) // Promotion? fprintf(f, "=%c ", sPieceChar[Piece(move->fPromotion)]); else fputc(' ', f); } else if (move->fCastling != kNoCastle) { if (move->fCastling == kCastleQueenside) fputs("O-O-O ", f); else fputs("O-O ", f); } else { MBCUnique u = [self disambiguateMove:move]; fputc(sPieceChar[p], f); if (u) { if (u != (kMatchingPieceExists|kMatchingPieceOnSameCol)) fputc(Col(move->fFromSquare), f); if (u & kMatchingPieceOnSameCol) fputc('0'+Row(move->fFromSquare), f); } if (move->fVictim) // Capture fputc('x', f); fprintf(f, "%c%d ", Col(move->fToSquare), Row(move->fToSquare)); } } [self makeMove: move]; } [existingMoves release]; return YES; } - (BOOL) canPromote:(MBCSide)side { MBCPiece piece; unsigned rank; if (side == kBlackSide != ([fMoves count]&1)) return NO; if (side == kBlackSide) { piece = Black(PAWN); rank = 2; } else { piece = White(PAWN); rank = 7; } for (char file = 'a'; file < 'i'; ++file) if (What(fCurPos.fBoard[Square(file, rank)]) == piece) return YES; return NO; } - (BOOL) canUndo { return [fMoves count] > 1; } - (MBCMove *) lastMove { return [fMoves lastObject]; } - (int) numMoves { return [fMoves count]; } - (MBCMove *) move:(int)index { return (index < (int)[fMoves count]) ? [fMoves objectAtIndex:index] : nil; } - (MBCPieces *) curPos { return &fCurPos; } - (MBCPiece) defaultPromotion:(BOOL)white { return fPromotion[white]; } - (void) setDefaultPromotion:(MBCPiece)piece for:(BOOL)white { fPromotion[white] = piece; } @end inline MBCCompactMove EncodeCompactMove( MBCMoveCode cmd, MBCSquare from, MBCSquare to, MBCPiece piece) { return (cmd << 24) | (from << 16) | (to << 8) | piece; } inline MBCCompactMove EncodeCompactCommand(MBCMoveCode cmd) { return cmd << 24; } MBCCompactMove MBCEncodeMove(const char * mv, int ponder) { const char * piece = " kqbnrp "; const char * p; MBCPiece promo = EMPTY; if (mv[4] && (p = strchr(piece, mv[4]))) promo = p-piece; return EncodeCompactMove(ponder ? kCmdPMove : kCmdMove, Square(mv+0), Square(mv+2), promo); } MBCCompactMove MBCEncodeDrop(const char * drop, int ponder) { const char * piece = " KQBNRP kqbnrp "; return EncodeCompactMove(ponder ? kCmdPDrop : kCmdDrop, 0, Square(drop+2), strchr(piece, drop[0])-piece); } MBCCompactMove MBCEncodeIllegal() { return EncodeCompactCommand(kCmdUndo); } MBCCompactMove MBCEncodeLegal() { return EncodeCompactCommand(kCmdMoveOK); } MBCCompactMove MBCEncodePong() { return EncodeCompactCommand(kCmdPong); } MBCCompactMove MBCEncodeStartGame() { return EncodeCompactCommand(kCmdStartGame); } MBCCompactMove MBCEncodeWhiteWins() { return EncodeCompactCommand(kCmdWhiteWins); } MBCCompactMove MBCEncodeBlackWins() { return EncodeCompactCommand(kCmdBlackWins); } MBCCompactMove MBCEncodeDraw() { return EncodeCompactCommand(kCmdDraw); } MBCCompactMove MBCEncodeTakeback() { return EncodeCompactCommand(kCmdUndo); } // Local Variables: // mode:ObjC // End: