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

- (void)removeChessObservers
{
    if (!fHasObservers)
        return;
    
    NSNotificationCenter * notificationCenter = [NSNotificationCenter defaultCenter];
    [notificationCenter removeObserver:self name:MBCGameLoadNotification object:self];
    fHasObservers   = NO;
}

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

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

	[self resetWithVariant:[doc variant]];
    
    [self removeChessObservers];
    [[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 || moves)
             [self setFen:fen holding:holding moves:moves];
     }];
    fHasObservers = YES;
}

- (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 && 
			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;
		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) {
		//
		// 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 *   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 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 @% at", 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: