MBCBoard.mm   [plain text]

/*
File: MBCBoard.mm
Contains: Implementation of fundamental board and move classes
Copyright: © 2002-2011 by Apple 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 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 "MBCPlayer.h"
#import "MBCDocument.h"

#import <string.h>
#include <ctype.h>

NSString * gVariantName[] = {
@"normal", @"crazyhouse", @"suicide", @"losers", nil
};

const char gVariantChar[] = "nzsl";

const MBCSide gHumanSide[] = {
kBothSides, kWhiteSide, kBlackSide, kNeitherSide, kBothSides
};

const MBCSide gEngineSide[] = {
kNeitherSide, kBlackSide, kWhiteSide, kBothSides, kNeitherSide
};

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);
}

//sanitize - fPiece needs to be a valid index in the character sequence pointed to by piece
//fFromSquare and fToSquare should stay within the board's confines (64 squares indexed 0-63)

if (fPiece > (strlen(piece)-1) || fFromSquare >= kBoardSquares || fToSquare >= kBoardSquares) {
fCommand = kCmdNull;
}

return self;
}

+ (id) newFromEngineMove:(NSString *)engineMove
{
return [[MBCMove alloc] initFromEngineMove:engineMove];
}

+ (id) moveFromEngineMove:(NSString *)engineMove
{
return [[MBCMove newFromEngineMove:engineMove] autorelease];
}

+ (BOOL)compactMoveIsWin:(MBCCompactMove)move
{
switch (move >> 24) {
case kCmdWhiteWins:
case kCmdBlackWins:
return YES;
default:
return NO;
}
}

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
{
NSString * origin = [self origin];
NSString * operation = [self operation];
NSString * destination = [self destinationForTitle:YES];
NSString * check = [self check];
NSString * text;
if ([origin length] || [destination length])
text = [NSString localizedStringWithFormat:NSLocalizedString(@"title_move_fmt", "%@%@%@"),
origin, operation, destination];
else
text = operation;
if ([check length])
text = [NSString localizedStringWithFormat:NSLocalizedString(@"title_check_fmt", @"%@%@"),
text, check];

return text;
}

- (NSString *) origin
{
switch (fCommand) {
case kCmdMove:
case kCmdPMove:
if (fCastling != kNoCastle)
return @"";
else
return [NSString localizedStringWithFormat:NSLocalizedString(@"move_origin_fmt", @"%@%c%c"),
[self pieceLetter:fPiece forDrop:NO],
Col(fFromSquare), Row(fFromSquare)+'0'];
case kCmdDrop:
case kCmdPDrop:
return [NSString localizedStringWithFormat:NSLocalizedString(@"drop_origin_fmt", @"%@"),
[self pieceLetter:fPiece forDrop:YES]];
default:
return @"";
}
}

- (NSString *) operation
{
UniChar op = fVictim ? 0x00D7 : '-';
switch (fCommand) {
case kCmdMove:
case kCmdPMove:
switch (fCastling) {
case kCastleQueenside:
return @"0 - 0 - 0";
case kCastleKingside:
return @"0 - 0";
default:
break;
}
break;
case kCmdDrop:
case kCmdPDrop:
op = '@';
break;
default:
op = ' ';
break;
}
return [NSString localizedStringWithFormat:NSLocalizedString(@"operation_fmt", @"%C"), op];
}

- (NSString *) destinationForTitle:(BOOL)forTitle
{
NSString * check = [self check];
NSString * text;
if (fCastling != kNoCastle && fCastling != kUnknownCastle)
return check;
else if (fPromotion)
text = [NSString localizedStringWithFormat:NSLocalizedString(@"promo_dest_fmt", @"%c%c=@%"),
Col(fToSquare), Row(fToSquare)+'0', [self pieceLetter:fPromotion forDrop:NO]];
else
text = [NSString localizedStringWithFormat:NSLocalizedString(@"move_dest_fmt", @"%c%c"),
Col(fToSquare), Row(fToSquare)+'0'];
if ([check length])
return [NSString localizedStringWithFormat:NSLocalizedString(@"dest_check_fmt", @"%@ %@"),
text, check];
else
return text;
}

- (NSString *) destination
{
return [self destinationForTitle:NO];
}

- (NSString *) check
{
if (fCheckMate)
return NSLocalizedString(@"move_is_checkmate", @"");
else if (fCheck)
return NSLocalizedString(@"move_is_check", @"+");
else
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

bool MBCPieces::NoPieces(MBCPieceCode color)
{
color = (MBCPieceCode)Opposite(Color(color));

return fInHand[color+QUEEN] == 1
&& fInHand[color+BISHOP] == 2
&& fInHand[color+KNIGHT] == 2
&& fInHand[color+ROOK] == 2
&& fInHand[color+PAWN] == 8;
}

@implementation MBCBoard

- (id)init
{
if (self = [super init])
fObservers = [[NSMutableArray alloc] init];

return self;
}

- (void)removeChessObservers
{
NSNotificationCenter * notificationCenter = [NSNotificationCenter defaultCenter];
[fObservers enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
[notificationCenter removeObserver:obj];
}];
[fObservers removeAllObjects];
}

- (void)dealloc
{
[self removeChessObservers];
[fObservers release];
[fMoves release];
[super dealloc];
}

- (void)setDocument:(id)doc
{
fDocument = doc;
fMoves = nil;

[self resetWithVariant:[doc variant]];

[self removeChessObservers];
[fObservers addObject:
[[NSNotificationCenter defaultCenter]
addObserverForName:MBCGameLoadNotification object:doc
queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
NSDictionary * dict = [note userInfo];
NSString * fen = [dict objectForKey:@"Position"];
NSString * holding = [dict objectForKey:@"Holding"];
NSString * moves = [dict objectForKey:@"Moves"];
fVariant = [doc variant];

if (fen.length > 0 || moves.length > 0)
[self setFen:fen holding:holding moves:moves];
}]];
}

- (void) resetWithVariant:(MBCVariant)variant
{
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 = variant;
fMoveClock = 0;

[fMoves release];
fMoves = [[NSMutableArray alloc] init];

fPromotion[0] = QUEEN;
fPromotion[1] = QUEEN;
}

- (void) startGame:(MBCVariant)variant
{
[self resetWithVariant: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])
MBCAbort([NSString localizedStringWithFormat:@"Board consistency check: %d %d\n", i, inventory[i]],
fDocument);
}
#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 &&
labs(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;
if (--inHand[piece] < 0)
MBCAbort([NSString localizedStringWithFormat:@"Dropping non-existent %c",
sPieceChar[Piece(move->fPiece)]],
fDocument);

}
board[toSquare] = piece;

//
// Record the move made in undo buffer
//
[fMoves addObject:move];

//
// Is the move a check?
//
if (fVariant != kVarSuicide) {
MBCMoveGenerator checkChecker(nil, fVariant, 0);
if ((move->fCheck = checkChecker.InCheck(!([fMoves count] & 1), fCurPos)))
move->fCheckMate = checkChecker.InCheckMate(!([fMoves count] & 1), fCurPos);
}

[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;
}
}

- (MBCSide)sideOfMove:(MBCMove *)move
{
switch (move->fCommand) {
case kCmdWhiteWins:
return kWhiteSide;
case kCmdBlackWins:
return kBlackSide;
case kCmdDraw:
return ([fMoves count] & 1) ? kWhiteSide : kBlackSide;
default:
return Color(move->fPiece)==kWhitePiece ? kWhiteSide : kBlackSide;
}
}

- (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';
}
}
snprintf(p, 32, " %d %lu", 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-- > 0; )
*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.length > 0) {
//
// We prefer to restore the game by replaying the moves
//
[self resetWithVariant:fVariant];
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 * firstSquareInRank = fCurPos.fBoard+kSquareA8; //beginning with the last rank
MBCPiece * b = firstSquareInRank;
MBCPiece p;

bool isCorrupt = false;

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-firstSquareInRank > 8)
isCorrupt = true;
else if (b-firstSquareInRank == 8)
b = (firstSquareInRank -= 8); //beginning of previous rank
break;
case '\0':
firstSquareInRank = fCurPos.fBoard-1;
break;
case '/':
default:
p = EMPTY;
break;
}
if (p) {
*b++ = p;
if (b-firstSquareInRank > 8)
isCorrupt = true;
else if (b-firstSquareInRank == 8)
b = (firstSquareInRank -= 8); //beginning of previous rank
}
} while (!isCorrupt && b >= fCurPos.fBoard);

do {
fMoveClock = 0;

if (!*s || isCorrupt) break;

while (isspace(*s))
++s;

if (*s == 'b') {
++s;
[fMoves addObject:[MBCMove moveWithCommand:kCmdNull]];
}

while (isspace(*s))
++s;

while (*s && !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;

//check if "en passant" move
if (*s >= 'a' && *s <= 'h' && *(s+1) >= '1' && *(s+1) <= '8')
fCurPos.fBoard[Square(*s, s[1]-'0')] &= ~kPieceMoved;

if (!*s || !*(s+1)) break;

s += 2;

while (isspace(*s))
++s;

while (isdigit(*s))
fMoveClock = 10*fMoveClock + *s++ - '0';

//parse "Holding" key
memset(fCurPos.fInHand, 0, 16);

s = [holding UTF8String];

s = strchr(s, '[');
if (!s)
break; //expecting [...][...]

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)
break;

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);
} while (1);

if (isCorrupt) {
//use a clean starting configuration to initialize the board
[self setFen:@"rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" holding:holding moves:@""];

/*****
//RADAR 34171287
//display an alert message to let the user know the game could not be loaded
NSAlert *alert = [[NSAlert alloc] init];
[alert addButtonWithTitle:@"OK"];
[alert setMessageText:NSLocalizedString(@"game_load_fail", @"Failed to load game")];
[alert setInformativeText:NSLocalizedString(@"game_load_fail_msg", @"The specified game file could not be loaded.")];
[alert setAlertStyle:NSAlertStyleWarning];
[alert runModal];
[alert release];
*****/
}
}
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];
fPrvPos = fCurPos;

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 count] ? [fMoves lastObject] : nil;
}

- (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;
}

- (MBCMoveCode) outcome
{
if (![fMoves count])
return kCmdNull;

MBCMove * lastMove = (MBCMove *)[fMoves lastObject];
if (lastMove->fCheckMate) {
if (fVariant == kVarLosers)
return ([fMoves count] & 1) ? kCmdBlackWins : kCmdWhiteWins;
else
return ([fMoves count] & 1) ? kCmdWhiteWins : kCmdBlackWins;
}
if (fVariant == kVarSuicide || fVariant == kVarLosers) {
if (!lastMove->fVictim)
return kCmdNull;
MBCPiece color = Opposite(Color(lastMove->fVictim));
if (fVariant == kVarSuicide && !fCurPos.fInHand[color+KING])
return kCmdNull;
if (fCurPos.NoPieces(Color(lastMove->fVictim)))
return ([fMoves count] & 1) ? kCmdBlackWins : kCmdWhiteWins;
}
if (fVariant != kVarSuicide) {
MBCMoveGenerator checkChecker(nil, fVariant, 0);
if (checkChecker.InStaleMate(!([fMoves count] & 1), fCurPos))
return fVariant != kVarLosers ? kCmdDraw
: ([fMoves count] & 1) ? kCmdBlackWins : kCmdWhiteWins;
if (fCurPos.NoPieces(kWhitePiece) && fCurPos.NoPieces(kBlackPiece))
return kCmdDraw;
}

return kCmdNull;
}

NSString * LocalizedString(NSDictionary * localization, NSString * key, NSString * fallback)
{
NSString * value = [[localization valueForKey:@"strings"] valueForKey:key];

return value ? value : fallback;
}

NSString * LocalizedStringWithFormat(NSDictionary * localization, NSString * format, ...)
{
va_list args;
va_start(args, format);
NSString * s = [[NSString alloc] initWithFormat:format locale:[localization valueForKey:@"locale"]
arguments:args];
va_end(args);

return [s autorelease];
}

BOOL OldSquares(NSString * fmtString)
{
/* We used to specify squares as "%c %d", now we use "%@ %@". To avoid
breakage during the transition, we allow both
*/
NSRange r = [fmtString rangeOfString:@"%c"];
if (r.length)
return YES;
r = [fmtString rangeOfString:@"$c"];
if (r.length)
return YES;

return NO;
}

static NSString * sPieceName[] = {
@"", @"king", @"queen", @"bishop", @"knight", @"rook", @"pawn"
};

static NSString * sFileKey[] = {
@"file_a", @"file_b", @"file_c", @"file_d", @"file_e", @"file_f", @"file_g", @"file_h"
};

static NSString * sFileDefault[] = {
@"A", @"B", @"C", @"D", @"E", @"F", @"G", @"H"
};

#define LOC_FILE(f) LOC(sFileKey[(f)-'a'], sFileDefault[(f)-'a'])

static NSString * sRankKey[] = {
@"rank_1", @"rank_2", @"rank_3", @"rank_4", @"rank_5", @"rank_6", @"rank_7", @"rank_8"
};

static NSString * sRankDefault[] = {
@"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8"
};

#define LOC_RANK(r) LOC(sRankKey[(r)-1], sRankDefault[(r)-1])

- (NSString *)stringFromMove:(MBCMove *)move withLocalization:(NSDictionary *)localization
{
switch (move->fCommand) {
case kCmdDrop: {
NSString * format = LOC(@"drop_fmt", @"%@ %c %d.");
NSString * pkey = [NSString stringWithFormat:@"%@_d", sPieceName[Piece(move->fPiece)]];
NSString * pdef = [NSString stringWithFormat:@"drop @%@", sPieceName[Piece(move->fPiece)]];
NSString * ploc = LOC(pkey, pdef);
char col = Col(move->fToSquare);
int row = Row(move->fToSquare);
if (OldSquares(format))
return LocalizedStringWithFormat(localization, format, ploc, toupper(col), row);
else
return LocalizedStringWithFormat(localization, format, ploc, LOC_FILE(col), LOC_RANK(row));
}
case kCmdPMove:
case kCmdMove: {
MBCPiece piece;
MBCPiece victim;
MBCPiece promo;

if (move->fCastling == kUnknownCastle)
move->fCastling = [self tryCastling:move];
switch (move->fCastling) {
case kCastleQueenside:
return LOC(@"qcastle_fmt", @"Castle [[emph +]]queen side.");
case kCastleKingside:
return LOC(@"kcastle_fmt", @"Castle [[emph +]]king side.");
default:
if (move->fPiece) { // Move already executed
piece = move->fPiece;
victim = move->fVictim;
} else {
piece = fCurPos.fBoard[move->fFromSquare];
victim= fCurPos.fBoard[move->fToSquare];
}
promo = move->fPromotion;
NSString * pname = LOC(sPieceName[Piece(piece)], sPieceName[Piece(piece)]);
char fcol = Col(move->fFromSquare);
int frow = Row(move->fFromSquare);
char tcol = Col(move->fToSquare);
int trow = Row(move->fToSquare);
if (promo) {
NSString * format = victim
? LOC(@"cpromo_fmt", @"%@ %c %d takes %c %d %@.")
: LOC(@"promo_fmt", @"%@ %c %d to %c %d %@.");
NSString * pkey = [NSString stringWithFormat:@"%@_p", sPieceName[Piece(promo)]];
NSString * pdef = [NSString stringWithFormat:@"promoting to %@", sPieceName[Piece(promo)]];
NSString * ploc = LOC(pkey, pdef);

if (OldSquares(format))
return LocalizedStringWithFormat(localization, format, pname,
toupper(fcol), frow, toupper(tcol), trow,
ploc);
else
return LocalizedStringWithFormat(localization, format, pname,
LOC_FILE(fcol), LOC_RANK(frow), LOC_FILE(tcol), LOC_RANK(trow),
ploc);
} else {
NSString * format = victim
? LOC(@"cmove_fmt", @"%@ %c %d takes %c %d.")
: LOC(@"move_fmt", @"%@ %c %d to %c %d.");

if (OldSquares(format))
return LocalizedStringWithFormat(localization, format, pname,
toupper(fcol), frow, toupper(tcol), trow);
else
return LocalizedStringWithFormat(localization, format, pname,
LOC_FILE(fcol), LOC_RANK(frow), LOC_FILE(tcol), LOC_RANK(trow));
}
}}
case kCmdWhiteWins:
switch (fVariant) {
default:
if ([fMoves count] && ((MBCMove *)[fMoves lastObject])->fCheckMate)
return LOC(@"check_mate", @"[[emph +]]Check mate!");
//
// Fall through
//
case kVarSuicide:
case kVarLosers:
return LOC(@"white_win", @"White wins!");
}
case kCmdBlackWins:
switch (fVariant) {
default:
if ([fMoves count] && ((MBCMove *)[fMoves lastObject])->fCheckMate)
return LOC(@"check_mate", @"[[emph +]]Check mate!");
//
// Fall through
//
case kVarSuicide:
case kVarLosers:
return LOC(@"black_win", @"Black wins!");
}
case kCmdDraw:
return LOC(@"draw", @"The game is a draw!");
default:
return @"";
}
}

- (NSString *)extStringFromMove:(MBCMove *)move withLocalization:(NSDictionary *)localization
{
NSString * basic = [self stringFromMove:move withLocalization:localization];
NSString * ext;

if (move->fCheck || move->fCheckMate) {
NSString * fmt = LOC(@"has_check_fmt", @"%@, %@");
NSString * check= move->fCheckMate ? LOC(@"check_mate", @"check mate!") : LOC(@"check", @"check!");

ext = LocalizedStringWithFormat(localization, fmt, basic, check);
} else {
NSString * fmt = LOC(@"no_check_fmt", @"%@.");

ext = LocalizedStringWithFormat(localization, fmt, basic);
}

static NSRegularExpression * sFilter;
if (!sFilter)
sFilter = [[NSRegularExpression alloc] initWithPattern:@"\\[\\[.*?\\]\\]" options:0 error:nil];
return [sFilter stringByReplacingMatchesInString:ext options:0
range:NSMakeRange(0, [ext length]) withTemplate:@""];
}

@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: