MBCBoard.mm   [plain text]


/*
	File:		MBCBoard.mm
	Contains:	Implementation of fundamental board and move classes
	Copyright:	© 2002-2005 Apple Computer, Inc. All rights reserved.

	IMPORTANT: This Apple software is supplied to you by Apple Computer,
	Inc.  ("Apple") in consideration of your agreement to the following
	terms, and your use, installation, modification or redistribution of
	this Apple software constitutes acceptance of these terms.  If you do
	not agree with these terms, please do not use, install, modify or
	redistribute this Apple software.
	
	In consideration of your agreement to abide by the following terms,
	and subject to these terms, Apple grants you a personal, non-exclusive
	license, under Apple's copyrights in this original Apple software (the
	"Apple Software"), to use, reproduce, modify and redistribute the
	Apple Software, with or without modifications, in source and/or binary
	forms; provided that if you redistribute the Apple Software in its
	entirety and without modifications, you must retain this notice and
	the following text and disclaimers in all such redistributions of the
	Apple Software.  Neither the name, trademarks, service marks or logos
	of Apple Computer, Inc. may be used to endorse or promote products
	derived from the Apple Software without specific prior written
	permission from Apple.  Except as expressly stated in this notice, no
	other rights or licenses, express or implied, are granted by Apple
	herein, including but not limited to any patent rights that may be
	infringed by your derivative works or by other works in which the
	Apple Software may be incorporated.
	
	The Apple Software is provided by Apple on an "AS IS" basis.  APPLE
	MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
	THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND
	FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS
	USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
	
	IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT,
	INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
	PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
	PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE,
	REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE,
	HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING
	NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN
	ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#import "MBCBoard.h"
#import "MBCEngineCommands.h"
#import "MBCMoveGenerator.h"

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

MBCPiece Captured(MBCPiece victim)
{
	victim = Opposite(victim & ~kPieceMoved);
	if (Promoted(victim))	// Captured promoted pieces revert to pawns
		return Matching(victim, PAWN);
	else
		return victim;
}

static const char * sPieceChar = " KQBNRP";

@implementation MBCMove

- (id) initWithCommand:(MBCMoveCode)command;
{
	fCommand	=	command;
	fFromSquare	=	kInvalidSquare;
	fToSquare	=	kInvalidSquare;
	fPiece		=	EMPTY;
	fPromotion	= 	EMPTY;
	fVictim		= 	EMPTY;
	fCastling	=	kUnknownCastle;
	fEnPassant	= 	NO;
	fAnimate	=	YES;
	
	return self;
}

+ (id) newWithCommand:(MBCMoveCode)command
{
	return [[MBCMove alloc] initWithCommand:command];
}

+ (id) moveWithCommand:(MBCMoveCode)command
{
	return [[MBCMove newWithCommand:command] autorelease];
}

- (id) initFromCompactMove:(MBCCompactMove)move
{
	[self initWithCommand:MBCMoveCode(move >> 24)];
	
	switch (fCommand) {
	case kCmdMove:	
	case kCmdPMove:
		fFromSquare	= (move >> 16) & 0xFF;
		fToSquare	= (move >> 8)  & 0xFF;
		fPromotion	= move & 0xFF;
		break;
	case kCmdDrop:
	case kCmdPDrop:
		fToSquare	= (move >> 8)  & 0xFF;
		fPiece		= move & 0xFF;
		break;
	default:
		break;
	}

	return self;
}

+ (id) newFromCompactMove:(MBCCompactMove)move
{
	return [[MBCMove alloc] initFromCompactMove:move];
}

+ (id) moveFromCompactMove:(MBCCompactMove)move
{
	return [[MBCMove newFromCompactMove:move] autorelease];
}

- (id) initFromEngineMove:(NSString *)engineMove
{
	const char * piece	= " KQBNRP  kqbnrp ";
	const char * move	= [engineMove cString];

	if (move[1] == '@') {
		[self initWithCommand:kCmdDrop];
		fPiece		= static_cast<MBCPiece>(strchr(piece, move[0])-piece);
		fToSquare	= Square(move+2);
	} else {
		[self initWithCommand:kCmdMove];
		fFromSquare	= Square(move);
		fToSquare	= Square(move+2);
		if (move[4])
			fPromotion	= static_cast<MBCPiece>(strchr(piece, move[0])-piece);
	}
	
	return self;
}

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

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

NSString * sPieceLetters[] = {
	@"",
	@"king_letter",
	@"queen_letter",
	@"bishop_letter",
	@"knight_letter",
	@"rook_letter",
	@"pawn_letter"
};

- (NSString *) pieceLetter:(MBCPiece)piece forDrop:(BOOL)drop
{
	piece = Piece(piece);
	if (!drop && piece==PAWN)
		return @" ";
	else
		return NSLocalizedString(sPieceLetters[piece], 
								 "Piece Letter");
}
 
- (NSString *) localizedText:(BOOL)aligned
{
	const char * tab = aligned ? "\t" : "";
	switch (fCommand) {
	case kCmdMove:
	case kCmdPMove:
		if (fPromotion)
			return [NSString stringWithFormat:@"%s%c%c%s%c%s%c%c=%@",
							 tab,
							 Col(fFromSquare), Row(fFromSquare)+'0',
							 tab, fVictim ? 'x' :'-', tab,
							 Col(fToSquare), Row(fToSquare)+'0',
							 [self pieceLetter:fPromotion forDrop:YES]];
		else if (fCastling != kNoCastle)
			return [NSString stringWithFormat:@"%s%s0-0%s",
							 tab, tab, 
							 fCastling == kCastleQueenside ? "-O" : ""];
		else
			return [NSString stringWithFormat:@"%s%@%c%c%s%c%s%c%c",
							 tab,
							 [self pieceLetter:fPiece forDrop:NO],
							 Col(fFromSquare), Row(fFromSquare)+'0',
							 tab, fVictim ? 'x' : '-', tab,
							 Col(fToSquare), Row(fToSquare)+'0'];
	case kCmdDrop:
	case kCmdPDrop:
		return [NSString stringWithFormat:@"%s%@%s@%s%c%c",
						 tab,
						 [self pieceLetter:fPiece forDrop:YES],
						 tab, tab,
						 Col(fToSquare), Row(fToSquare)+'0'];
	default:
		return @"";
	}
}

- (NSString *) engineMove
{
	const char * piece	= " KQBNRP  kqbnrp ";

#define SQUARETOCOORD(sq) 	Col(sq), Row(sq)+'0'

	switch (fCommand) {
	case kCmdMove:
		if (fPromotion) 
			return [NSString stringWithFormat:@"%c%c%c%c%c\n", 
							 SQUARETOCOORD(fFromSquare),
							 SQUARETOCOORD(fToSquare),
							 piece[fPromotion]];
		else
			return [NSString stringWithFormat:@"%c%c%c%c\n", 
							 SQUARETOCOORD(fFromSquare),
							 SQUARETOCOORD(fToSquare)];
	case kCmdDrop:
		return [NSString stringWithFormat:@"%c@%c%c\n", 
						 piece[fPiece],
						 SQUARETOCOORD(fToSquare)];
		break;
	default:
		return nil;
	}	
}

@end

@implementation MBCBoard

- (id) init
{
	fMoves	= nil;

	[self reset];
        
	return self;
}

- (void) reset
{
	memset(fCurPos.fBoard, EMPTY, 64);
	memset(fCurPos.fInHand, 0, 16);

	/* White pieces */
	fCurPos.fBoard[Square('a',1)] = White(ROOK);
	fCurPos.fBoard[Square('b',1)] = White(KNIGHT);
	fCurPos.fBoard[Square('c',1)] = White(BISHOP);
	fCurPos.fBoard[Square('d',1)] = White(QUEEN);
	fCurPos.fBoard[Square('e',1)] = White(KING);
	fCurPos.fBoard[Square('f',1)] = White(BISHOP);
	fCurPos.fBoard[Square('g',1)] = White(KNIGHT);
	fCurPos.fBoard[Square('h',1)] = White(ROOK);
	fCurPos.fBoard[Square('a',2)] = White(PAWN);
	fCurPos.fBoard[Square('b',2)] = White(PAWN);
	fCurPos.fBoard[Square('c',2)] = White(PAWN);
	fCurPos.fBoard[Square('d',2)] = White(PAWN);
	fCurPos.fBoard[Square('e',2)] = White(PAWN);
	fCurPos.fBoard[Square('f',2)] = White(PAWN);
	fCurPos.fBoard[Square('g',2)] = White(PAWN);
	fCurPos.fBoard[Square('h',2)] = White(PAWN);

	/* Black pieces */
	fCurPos.fBoard[Square('a',7)] = Black(PAWN);
	fCurPos.fBoard[Square('b',7)] = Black(PAWN);
	fCurPos.fBoard[Square('c',7)] = Black(PAWN);
	fCurPos.fBoard[Square('d',7)] = Black(PAWN);
	fCurPos.fBoard[Square('e',7)] = Black(PAWN);
	fCurPos.fBoard[Square('f',7)] = Black(PAWN);
	fCurPos.fBoard[Square('g',7)] = Black(PAWN);
	fCurPos.fBoard[Square('h',7)] = Black(PAWN);
	fCurPos.fBoard[Square('a',8)] = Black(ROOK);
	fCurPos.fBoard[Square('b',8)] = Black(KNIGHT);
	fCurPos.fBoard[Square('c',8)] = Black(BISHOP);
	fCurPos.fBoard[Square('d',8)] = Black(QUEEN);
	fCurPos.fBoard[Square('e',8)] = Black(KING);
	fCurPos.fBoard[Square('f',8)] = Black(BISHOP);
	fCurPos.fBoard[Square('g',8)] = Black(KNIGHT);
	fCurPos.fBoard[Square('h',8)] = Black(ROOK);

	fPrvPos		= fCurPos;
	fVariant	= kVarNormal;
	fMoveClock	= 0;

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

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

- (void) startGame:(MBCVariant)variant
{
	[self reset];
	fVariant	= variant;
}

- (MBCPiece) curContents:(MBCSquare)square
{
	return fCurPos.fBoard[square];
}

- (MBCPiece) oldContents:(MBCSquare)square
{
	return fPrvPos.fBoard[square];
}

- (int) curInHand:(MBCPiece)piece
{
	return fCurPos.fInHand[piece];
}

- (int) oldInHand:(MBCPiece)piece
{
	return fPrvPos.fInHand[piece];
}

//
// After every move, pieces on the board and in hand have to balance out
//
- (void) consistencyCheck
{
	char	inventory[8];

	memset(inventory, 0, 8);
	
	inventory[PAWN]		= 16;
	inventory[ROOK]		=  4;
	inventory[KNIGHT]	=  4;
	inventory[BISHOP]	=  4;
	inventory[QUEEN]	=  2;
	inventory[KING]		=  2;

	for (MBCPiece * p = fCurPos.fBoard; p < fCurPos.fBoard+64; ++p) {
		MBCPiece piece = *p;
		--inventory[Promoted(piece) ? PAWN : Piece(piece)];
	}
	for (int i = 1; i < 8; ++i) {
		inventory[i] -= fCurPos.fInHand[i]+fCurPos.fInHand[i|kBlackPiece];
		if (inventory[i]) 
			NSLog(@"Board consistency check: %d %d\n", i, inventory[i]);
	}
#if 0
	MBCMoveGenerator	logMoves([MBCDebugMoveBuilder debugMoveBuilder], 
								 fVariant, 0);
	logMoves.Generate(!([fMoves count] & 1), fCurPos);
	if (logMoves.InCheck(!([fMoves count] & 1), fCurPos)) 
		NSLog(@"Check!");
#endif
}

- (void) makeMove:(MBCMove *)move
{	
	//
	// Ignore everything except moves & drops
	//
	if (move->fCommand != kCmdMove && move->fCommand != kCmdDrop)
		return;

	//
	// Make the move on the board
	//
	MBCSquare	toSquare	= move->fToSquare;
	MBCSquare	fromSquare	= move->fFromSquare;
	MBCPiece * 	board		= fCurPos.fBoard;
	char *		inHand		= fCurPos.fInHand;
	MBCPiece 	piece 		= move->fPromotion;

	if (move->fCommand == kCmdMove) {
		move->fPiece = board[fromSquare];
		[self tryCastling:move];
		[self tryPromotion:move];
		if (!piece) { 
			//
			// Not a pawn promotion, piece stays the same
			//
			piece = move->fPiece;
		} else { 
			//
			// Pawn promotion
			//
			move->fPromotion = 
				Piece(piece) | Color(move->fPiece) | kPromoted;
			piece = move->fPromotion | kPieceMoved;
		}
		if (MBCPiece victim = board[toSquare]) {
			//
			// Record captured piece
			//
			move->fVictim = victim;
			++inHand[Captured(victim)];
		} else if (Piece(piece) == PAWN && Col(fromSquare) != Col(toSquare)) {
			//
			// En passant capture
			//
			MBCSquare victimSquare  = Square(Col(toSquare), Row(fromSquare));
			MBCPiece  victim		= board[victimSquare];
			move->fVictim 			= victim;
			++inHand[Captured(victim)];
			board[victimSquare] 	= EMPTY;
			move->fEnPassant		= YES;
		}
		if (move->fVictim || Piece(move->fPiece) == PAWN)
			fMoveClock	= 0;
		else 
			++fMoveClock;
		board[fromSquare] = EMPTY;
		unsigned row = Row(toSquare);
		switch (move->fCastling) {
		case kUnknownCastle:
		case kNoCastle:
			break;
		case kCastleQueenside:
			board[Square('d', row)] = board[Square('a', row)] | kPieceMoved;
			board[Square('a', row)] = EMPTY;
			break;
		case kCastleKingside:
			board[Square('f', row)] = board[Square('h', row)] | kPieceMoved;
			board[Square('h', row)] = EMPTY;
			break;
		}
		piece			   |= kPieceMoved;
		if (!move->fVictim && Piece(piece) == PAWN && 
			abs(Row(fromSquare)-Row(toSquare))==2
		)
			fCurPos.fEnPassant	= Square(Col(fromSquare), 
										 (Row(fromSquare)+Row(toSquare))/2);
		else
			fCurPos.fEnPassant	= kInvalidSquare;
	} else { 
		//
		// Drop, deplete in hand pieces. A dropped piece is not considered
		// to have moved yet.
		//
		piece	= move->fPiece;
		--inHand[piece];
	}
	board[toSquare] = piece;

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

	[self consistencyCheck];
}

- (MBCCastling) tryCastling:(MBCMove *)move
{
	move->fCastling			= kNoCastle;

	MBCSquare   fromSquare 	= move->fFromSquare;
	MBCPiece * 	board		= fCurPos.fBoard;
	MBCPiece	king 		= move->fPiece;

	if (Piece(king) != KING || king & kPieceMoved) 
		return kNoCastle;
	MBCPieceCode 	kingColor = Color(king);
	unsigned 		row = Row(move->fToSquare);
	if (Row(fromSquare) != row)
		return kNoCastle;
		
	//
	// These comparisons will fail if the rook has kPieceMoved set, which
	// they should.
	//
	switch (Col(move->fToSquare)) {
	case 'c':	// Queenside castle
		if (board[Square('a', row)] != Matching(kingColor, ROOK)
		 || board[Square('b', row)] != EMPTY
		 || board[Square('c', row)] != EMPTY
		 || board[Square('d', row)] != EMPTY
		)
			return kNoCastle;
		else
			return move->fCastling = kCastleQueenside;
	case 'g':	// Kingside castle
		if (board[Square('h', row)] != Matching(kingColor, ROOK)
		 || board[Square('g', row)] != EMPTY
		 || board[Square('f', row)] != EMPTY
		)
			return kNoCastle;
		else
			return move->fCastling = kCastleKingside;
	default:
		return kNoCastle;
	}
}

- (void)tryPromotion:(MBCMove *)move
{
	if (move->fCommand == kCmdMove 
	  && Piece(fCurPos.fBoard[move->fFromSquare]) == PAWN
    )
		//
		// Possibly a promotion where we need to fill in the piece
		//
		switch (Row(move->fToSquare)) {
		case 1:
			//
			// Black promotion
			//
			if (!move->fPromotion)
				move->fPromotion = fPromotion[0];
			break;
		case 8:
			//
			// White promotion
			//
			if (!move->fPromotion)
				move->fPromotion = fPromotion[1]; 
			break;
		default:
			move->fPromotion = EMPTY;
			break;
		}
}

- (BOOL) reachFromCol:(char)fromCol row:(unsigned)fromRow
			 deltaCol:(int)colDelta row:(int)rowDelta
				steps:(int)steps
{
	if (steps < 0)
		steps = -steps;
	while (--steps > 0) 
		if (fCurPos.fBoard[Square(fromCol += colDelta, fromRow += rowDelta)])
			return NO; // Occupied square in between

	return YES;
}

- (BOOL) reachDiagonalFromCol:(char)fromCol row:(unsigned)fromRow
						toCol:(char)toCol row:(unsigned)toRow
{
	int colDiff	= toCol - fromCol;
	int rowDiff	= (int)toRow - (int)fromRow;

	if (colDiff != rowDiff && colDiff != -rowDiff)
		return NO;	// Not on same diagonal

	return [self reachFromCol:fromCol row:fromRow 
				 deltaCol:(colDiff<0 ? -1 : 1) row:(rowDiff<0 ? -1 : 1)
				 steps:colDiff];
}

- (BOOL) reachStraightFromCol:(char)fromCol row:(unsigned)fromRow
						toCol:(char)toCol row:(unsigned)toRow
{
	if (fromRow==toRow)
		return [self reachFromCol:fromCol row:fromRow 
					 deltaCol:(toCol<fromCol ? -1 : 1) row:0 
					 steps:toCol-fromCol];
	else if (fromCol==toCol)
		return [self reachFromCol:fromCol row:fromRow 
					 deltaCol:0 row:(toRow<fromRow ? -1 : 1)
					 steps:toRow-fromRow];
	else
		return NO;
}

- (MBCUnique) disambiguateMove:(MBCMove *)move
{
	MBCSquare 	from	= move->fFromSquare;
	unsigned	fromRow	= Row(from);
	char		fromCol	= Col(from);
	MBCSquare 	to		= move->fToSquare;
	unsigned	toRow	= Row(to);
	char		toCol	= Col(to);
	MBCPiece 	piece	= fCurPos.fBoard[from];
	MBCUnique	unique	= 0;

	for (char col = 'a'; col < 'i'; ++col)
		for (unsigned row = 1; row<9; ++row) {
			if (col == fromCol && row == fromRow)
				continue;	// Same as from square
			if (col == toCol && row == toRow)
				continue;	// Same as to square
			if ((fCurPos.fBoard[Square(col, row)] ^ piece) & 15) 
				continue;	// Not a matching piece
			switch (Piece(piece)) {
			case PAWN: 
				//
				// The only column ambiguities can exist with captures
				//
				if (fromRow == row && toCol-fromCol == col-toCol)
					break;	
				continue;
			case KING: // Multiple kings? Only in suicide
				if (col>=toCol-1 && col<=toCol+1 && row>=toRow-1 && row<=toRow+1)
					break;
				continue;
			case KNIGHT: 
				if (col == toCol-1 || col == toCol+1) {
					if (row == toRow-2 || row == toRow+2)
						break;
				} else if (col == toCol-2 || col == toCol+2) {
					if (row == toRow-1 || row == toRow+1)
						break;
				}
				continue;
			case BISHOP:
				if ([self reachDiagonalFromCol:col row:row toCol:toCol row:toRow])
					break;
				continue;
			case QUEEN:
				if ([self reachDiagonalFromCol:col row:row toCol:toCol row:toRow])
					break;
				// Fall through
			case ROOK:
				if ([self reachStraightFromCol:col row:row toCol:toCol row:toRow])
					break;
				continue;
			default:
				continue;
			}
			unique	|= kMatchingPieceExists;
			if (row == fromRow)
				unique	|= kMatchingPieceOnSameRow;
			if (col == fromCol)
				unique	|= kMatchingPieceOnSameCol;
		}	
	return unique;
}

- (bool) undoMoves:(int)numMoves
{
	if ((int)[fMoves count]<numMoves)
		return false;

	if (fMoveClock < numMoves)
		fMoveClock = 0;
	else
		fMoveClock -= numMoves;

	while (numMoves-- > 0) {
		MBCMove *  	move 		= [fMoves lastObject];
		MBCPiece * 	board		= fCurPos.fBoard;
		char *		inHand		= fCurPos.fInHand;
	
		if (move->fCommand == kCmdMove) {
			board[move->fFromSquare] = move->fPiece;
			unsigned row = Row(move->fToSquare);
			switch (move->fCastling) {
			case kUnknownCastle:	
			case kNoCastle:	
				break;
			case kCastleQueenside:
				board[Square('a', row)] = 
					board[Square('d', row)] & ~kPieceMoved;
				board[Square('d', row)] = EMPTY;
				break;
			case kCastleKingside:
				board[Square('h', row)] = 
					board[Square('f', row)] & ~kPieceMoved;
				board[Square('f', row)] = EMPTY;
				break;
			}
		} else 
			++inHand[move->fPiece];
		board[move->fToSquare] = EMPTY;
		if (MBCPiece victim = move->fVictim) {
			MBCSquare victimSquare =
				move->fEnPassant 
				? Square(Col(move->fToSquare), Row(move->fFromSquare))
				: move->fToSquare;
			board[victimSquare] = victim;
			--inHand[Captured(victim)];
		}

		[fMoves removeLastObject];

		[self consistencyCheck];
	}
	fPrvPos = fCurPos;

	return true;
}

- (void) commitMove
{
	fPrvPos = fCurPos;
}

- (NSString *) fen
{
	char 	pos[128];
	char * 	p	= pos;

	*p++ = ' ';
	for (MBCSquare rank = 64; rank; rank -= 8) {
		for (MBCSquare square = rank-8; square < rank; ++square) 
			if (MBCPiece piece = fCurPos.fBoard[square])
				*p++ = " KQBNRP  kqbnrp "[What(piece)];
			else if (isdigit(p[-1]))
				++p[-1];
			else
				*p++ = '1';
		if (rank > 8)
			*p++ = '/';
	}
	*p++ = ' ';
	*p++ = ([fMoves count]&1) ? 'b' : 'w';
	*p++ = ' ';
	if (fCurPos.fBoard[Square('e', 1)] == White(KING)) {
		if (fCurPos.fBoard[Square('h', 1)] == White(ROOK))
			*p++ = 'K';
		if (fCurPos.fBoard[Square('a', 1)] == White(ROOK))
			*p++ = 'Q';
	}
	if (fCurPos.fBoard[Square('e', 8)] == Black(KING)) {
		if (fCurPos.fBoard[Square('h', 8)] == Black(ROOK))
			*p++ = 'k';
		if (fCurPos.fBoard[Square('a', 8)] == Black(ROOK))
			*p++ = 'q';
	}
	if (p[-1] == ' ')
		*p++ = '-';
	*p++ = ' ';
	*p++ = '-';
	if ([fMoves count]) {
		MBCMove *  move = [fMoves lastObject];
		if ((move->fPiece & (7|kPieceMoved)) == PAWN
			&& (Row(move->fToSquare) & 6) == 4
		) {
		    p[-1] = Col(move->fToSquare);
			*p++  = Row(move->fToSquare) == 4 ? '3' : '6';
		}
	}
	sprintf(p, " %d %d", fMoveClock, ([fMoves count]/2)+1);

	return [NSString stringWithCString:pos+1];
}

- (NSString *) holding
{
	char 	pos[128];
	char * 	p	= pos;

	*p++ = '[';
	for (MBCPiece piece = White(KING); piece <= Black(PAWN); ++piece) {
		for (int count = fCurPos.fInHand[piece]; count--; )
			*p++ = " KQBNRP "[Piece(piece)];
		if (piece == 8) {
			strcpy(p, "] [");
			p += 3;
		}
	}
	strcpy(p, "]");

	return [NSString stringWithCString:pos];
}

- (NSString *)moves
{	
	NSMutableString * 	moves 		= [NSMutableString stringWithCapacity:200];
	int 				numMoves 	= [fMoves count];

	for (int m = 0; m<numMoves; ++m) {
		MBCMove *  move = [fMoves objectAtIndex:m];
		[moves appendString:[move engineMove]];
	}
	return moves;
}

- (void) setFen:(NSString *)fen holding:(NSString *)holding 
	moves:(NSString *)moves
{
	if (moves) {
		//
		// We prefer to restore the game by replaying the moves
		//
		[self reset];
		NSArray * 		m = [moves componentsSeparatedByString:@"\n"];
		NSEnumerator *	e = [m objectEnumerator];
		while (NSString * move = [e nextObject]) 
			if ([move length])
				[self makeMove:[MBCMove moveFromEngineMove:move]];
		if (![fen isEqual:[self fen]]) 
			NSLog(@"FEN Mismatch, Expected: <%@> Got <%@>\n",
				  fen, [self fen]);
	} else {
		const char * s = [fen cString];
		MBCPiece *   b = fCurPos.fBoard+56;
		MBCPiece	 p;

		memset(fCurPos.fBoard, 0, 64);
		while (isspace(*s))
			++s;
		do {
			switch (*s++) {
			case 'K':
				p = White(KING) | kPieceMoved;
				break;
			case 'Q':
				p = White(QUEEN);
				break;
			case 'B':
				p = White(BISHOP);
				break;
			case 'N':
				p = White(KNIGHT);
				break;
			case 'R':
				p = White(ROOK) | kPieceMoved;
				break;
			case 'P':
				p = White(PAWN) | kPieceMoved;
				break;
			case 'k':
				p = Black(KING) | kPieceMoved;
				break;
			case 'q':
				p = Black(QUEEN);
				break;
			case 'b':
				p = Black(BISHOP);
				break;
			case 'n':
				p = Black(KNIGHT);
				break;
			case 'r':
				p = Black(ROOK) | kPieceMoved;
				break;
			case 'p':
				p = Black(PAWN) | kPieceMoved;
				break;
			case '8':
			case '7':
			case '6':
			case '5':
			case '4':
			case '3':
			case '2':
			case '1':
				p =  EMPTY;
				b += s[-1]-'0';
				if (!((b-fCurPos.fBoard) & 7))
					b -= 16; // Start previous rank
				break;
			case '/':
				p = EMPTY;
				break;
			}
			if (p) {
				*b++ = p;
				if (!((b-fCurPos.fBoard) & 7))
					b -= 16; // Start previous rank
			}
		} while (b >= fCurPos.fBoard);

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

		if (*s++ == 'b')	
			[fMoves addObject:[MBCMove moveWithCommand:kCmdNull]];
		
		while (isspace(*s))
			++s;
		
		while (!isspace(*s))
			switch (*s++) {
			case 'K':
				fCurPos.fBoard[4] &= ~kPieceMoved;
				fCurPos.fBoard[7] &= ~kPieceMoved;
				break;
			case 'Q':
				fCurPos.fBoard[4] &= ~kPieceMoved;
				fCurPos.fBoard[0] &= ~kPieceMoved;
				break;
			case 'k':
				fCurPos.fBoard[60] &= ~kPieceMoved;
				fCurPos.fBoard[63] &= ~kPieceMoved;
				break;
			case 'q':
				fCurPos.fBoard[60] &= ~kPieceMoved;
				fCurPos.fBoard[56] &= ~kPieceMoved;
				break;
			}

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

		if (*s == '-')
			fCurPos.fBoard[Square(*s, s[1]-'0')] &= ~kPieceMoved;
		s += 2;

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

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

		memset(fCurPos.fInHand, 0, 16);

		s = [holding cString];

		s = strchr(s, '[');
		if (!s)
			return;

		do {
			switch (*++s) {
			case 'Q':
				p	= White(QUEEN);
				break;
			case 'B':
				p	= White(BISHOP);
				break;
			case 'N':
				p	= White(KNIGHT);
				break;
			case 'R':
				p 	= White(ROOK);
				break;
			case 'P':
				p	= White(PAWN);
				break;
			default:
				p	= 0;
				break;
			}
			if (p)
				++fCurPos.fInHand[p];
		} while (p);

		s = strchr(s, '[');
		if (!s)
			return;
		
		do {
			switch (*++s) {
			case 'Q':
				p	= Black(QUEEN);
				break;
			case 'B':
				p	= Black(BISHOP);
				break;
			case 'N':
				p	= Black(KNIGHT);
				break;
			case 'R':
				p	= Black(ROOK);
				break;
			case 'P':
				p	= Black(PAWN);
				break;
			default:
				p 	= 0;
				break;
			}
			if (p)
				++fCurPos.fInHand[p];
		} while (p);
	}
	fPrvPos = fCurPos;
}

- (BOOL) saveMovesTo:(FILE *)f
{
	NSArray * existingMoves = [fMoves copy];
	int moves = [fMoves count];
	
	//
	// Reset board so we can disambiguate moves
	//
	[self undoMoves:moves];

	//
	// Now retrace the moves
	//
	for (int m = 0; m<moves; ++m) {
		if (!(m&1)) {
			if (!(m%10))
				fputc('\n', f);
			fprintf(f, "%d. ", (m / 2)+1);
		}
		MBCMove *  move = [existingMoves objectAtIndex:m];

		if (move->fCommand == kCmdDrop) { // Drop, never ambiguous
			fprintf(f, "%c@%c%d ", sPieceChar[Piece(move->fPiece)], 
					Col(move->fToSquare), Row(move->fToSquare));
		} else { // Move, may be ambiguous
			MBCPiece  p = Piece(fCurPos.fBoard[move->fFromSquare]);

			if (p==PAWN) { // Pawn moves look a bit different
				if (move->fVictim) // Capture
					fprintf(f, "%cx%c%d", Col(move->fFromSquare),
							Col(move->fToSquare), Row(move->fToSquare));
				else // Move
					fprintf(f, "%c%d", Col(move->fToSquare), 
							Row(move->fToSquare));
				if (move->fPromotion) // Promotion?
					fprintf(f, "=%c ", sPieceChar[Piece(move->fPromotion)]);
				else
					fputc(' ', f);
			} else if (move->fCastling != kNoCastle) {
				if (move->fCastling == kCastleQueenside)
					fputs("O-O-O ", f);
				else
					fputs("O-O ", f);
			} else {
				MBCUnique u = [self disambiguateMove:move];
				fputc(sPieceChar[p], f);
				if (u) {
					if (u != (kMatchingPieceExists|kMatchingPieceOnSameCol))
						fputc(Col(move->fFromSquare), f);
					if (u & kMatchingPieceOnSameCol)
						fputc('0'+Row(move->fFromSquare), f);
				}
				if (move->fVictim) // Capture
					fputc('x', f);
				fprintf(f, "%c%d ", Col(move->fToSquare), Row(move->fToSquare));
			}
		}
		
		[self makeMove: move];
	}
	[existingMoves release];

	return YES;
}

- (BOOL) canPromote:(MBCSide)side
{
	MBCPiece 	piece;
	unsigned	rank;

	if (side == kBlackSide != ([fMoves count]&1))
		return NO;

	if (side == kBlackSide) {
		piece	= Black(PAWN);
		rank	= 2;
	} else {
		piece	= White(PAWN);
		rank 	= 7;
	}

	for (char file = 'a'; file < 'i'; ++file)
		if (What(fCurPos.fBoard[Square(file, rank)]) == piece)
			return YES;

	return NO;
}

- (BOOL) canUndo
{
	return [fMoves count] > 1;
}

- (MBCMove *) lastMove
{
	return [fMoves lastObject];
}

- (int) numMoves
{
	return [fMoves count];
}

- (MBCMove *) move:(int)index
{
	return (index < (int)[fMoves count]) ? [fMoves objectAtIndex:index] : nil;
}

- (MBCPieces *) curPos
{
	return &fCurPos;
}

- (MBCPiece) defaultPromotion:(BOOL)white
{
	return fPromotion[white];
}

- (void) setDefaultPromotion:(MBCPiece)piece for:(BOOL)white
{
	fPromotion[white] = piece;
}

@end

inline MBCCompactMove EncodeCompactMove(
			 MBCMoveCode cmd, MBCSquare from, MBCSquare to, MBCPiece piece)
{
	return (cmd << 24) | (from << 16) | (to << 8) | piece;
}

inline MBCCompactMove EncodeCompactCommand(MBCMoveCode cmd)
{
	return cmd << 24;
}

MBCCompactMove MBCEncodeMove(const char * mv, int ponder)
{
	const char * 	piece	= " kqbnrp ";
	const char * 	p;
	MBCPiece		promo	= EMPTY;

	if (mv[4] && (p = strchr(piece, mv[4])))
		promo	= p-piece;

	return EncodeCompactMove(ponder ? kCmdPMove : kCmdMove,
							 Square(mv+0), Square(mv+2), promo);
}

MBCCompactMove MBCEncodeDrop(const char * drop, int ponder)
{
	const char * 	piece	= " KQBNRP  kqbnrp ";
	
	return EncodeCompactMove(ponder ? kCmdPDrop : kCmdDrop,
							 0, Square(drop+2),
							 strchr(piece, drop[0])-piece);
}

MBCCompactMove MBCEncodeIllegal()
{
	return EncodeCompactCommand(kCmdUndo);
}

MBCCompactMove MBCEncodeLegal()
{
	return EncodeCompactCommand(kCmdMoveOK);
}

MBCCompactMove MBCEncodePong()
{
	return EncodeCompactCommand(kCmdPong);
}

MBCCompactMove MBCEncodeStartGame()
{
	return EncodeCompactCommand(kCmdStartGame);
}

MBCCompactMove MBCEncodeWhiteWins()
{
	return EncodeCompactCommand(kCmdWhiteWins);
}

MBCCompactMove MBCEncodeBlackWins()
{
	return EncodeCompactCommand(kCmdBlackWins);
}

MBCCompactMove MBCEncodeDraw()
{
	return EncodeCompactCommand(kCmdDraw);
}

MBCCompactMove MBCEncodeTakeback()
{
	return EncodeCompactCommand(kCmdUndo);
}

// Local Variables:
// mode:ObjC
// End: