/* File: MBCBoard.mm Contains: Implementation of fundamental board and move classes Version: 1.0 Copyright: © 2002 by Apple Computer, Inc., all rights reserved. File Ownership: DRI: Matthias Neeracher x43683 Writers: (MN) Matthias Neeracher Change History (most recent first): $Log: MBCBoard.mm,v $ Revision 1.19 2007/03/03 01:13:16 neerache Fix warnings Revision 1.18 2007/03/02 21:10:58 neerache Move save/load fixes <rdar://problem/4366230> Revision 1.17 2007/03/02 20:31:44 neerache Don't corrupt promotion piece names <rdar://problem/4366230> Revision 1.16 2003/07/14 23:21:49 neerache Move promotion defaults into MBCBoard Revision 1.15 2003/06/30 05:00:11 neerache Add UnknownCastle, new... methods Revision 1.14 2003/06/16 05:28:32 neerache Added move generation facility Revision 1.13 2003/06/02 05:44:48 neerache Implement direct board manipulation Revision 1.12 2003/05/27 03:13:57 neerache Rework game loading/saving code Revision 1.11 2003/05/24 20:25:25 neerache Eliminate compact moves for most purposes Revision 1.10 2003/04/28 22:14:13 neerache Let board, not engine, handle last move Revision 1.9 2003/04/24 23:20:35 neeri Support pawn promotions Revision 1.8 2003/04/10 23:03:16 neeri Load positions Revision 1.7 2003/04/05 05:45:08 neeri Add PGN export Revision 1.6 2003/04/02 18:19:50 neeri Support saving board state Revision 1.5 2003/03/28 01:31:31 neeri Support hints, last move Revision 1.4 2002/09/13 23:57:05 neeri Support for Crazyhouse display and mouse Revision 1.3 2002/09/12 17:46:46 neeri Introduce dual board representation, in-hand pieces Revision 1.2 2002/08/26 23:09:44 neeri [MBCBoard makeMove:] needs to ignore everything except moves and drops Revision 1.1 2002/08/22 23:47:06 neeri Initial Checkin */ #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 UTF8String]; 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[4])-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&15]]; else return [NSString stringWithFormat:@"%c%c%c%c\n", SQUARETOCOORD(fFromSquare), SQUARETOCOORD(fToSquare)]; case kCmdDrop: return [NSString stringWithFormat:@"%c@%c%c\n", piece[fPiece&15], 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 stringWithUTF8String: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 stringWithUTF8String: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 UTF8String]; 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 '/': default: 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 UTF8String]; 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: