1/*
2	File:		MBCBoard.mm
3	Contains:	Implementation of fundamental board and move classes
4	Copyright:	� 2002-2011 by Apple Inc., all rights reserved.
5
6	IMPORTANT: This Apple software is supplied to you by Apple Computer,
7	Inc.  ("Apple") in consideration of your agreement to the following
8	terms, and your use, installation, modification or redistribution of
9	this Apple software constitutes acceptance of these terms.  If you do
10	not agree with these terms, please do not use, install, modify or
11	redistribute this Apple software.
12
13	In consideration of your agreement to abide by the following terms,
14	and subject to these terms, Apple grants you a personal, non-exclusive
15	license, under Apple's copyrights in this original Apple software (the
16	"Apple Software"), to use, reproduce, modify and redistribute the
17	Apple Software, with or without modifications, in source and/or binary
18	forms; provided that if you redistribute the Apple Software in its
19	entirety and without modifications, you must retain this notice and
20	the following text and disclaimers in all such redistributions of the
21	Apple Software.  Neither the name, trademarks, service marks or logos
22	of Apple Inc. may be used to endorse or promote products
23	derived from the Apple Software without specific prior written
24	permission from Apple.  Except as expressly stated in this notice, no
25	other rights or licenses, express or implied, are granted by Apple
26	herein, including but not limited to any patent rights that may be
27	infringed by your derivative works or by other works in which the
28	Apple Software may be incorporated.
29
30	The Apple Software is provided by Apple on an "AS IS" basis.  APPLE
31	MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
32	THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND
33	FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS
34	USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
35
36	IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT,
37	INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
38	PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
39	PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE,
40	REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE,
41	HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING
42	NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN
43	ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
44*/
45#import "MBCBoard.h"
46#import "MBCEngineCommands.h"
47#import "MBCMoveGenerator.h"
48#import "MBCPlayer.h"
49#import "MBCDocument.h"
50
51#import <string.h>
52#include <ctype.h>
53
54NSString *  gVariantName[] = {
55	@"normal", @"crazyhouse", @"suicide", @"losers", nil
56};
57
58const char  gVariantChar[]	= "nzsl";
59
60const MBCSide gHumanSide[]  = {
61    kBothSides, kWhiteSide, kBlackSide, kNeitherSide, kBothSides
62};
63
64const MBCSide gEngineSide[] = {
65    kNeitherSide, kBlackSide, kWhiteSide, kBothSides, kNeitherSide
66};
67
68MBCPiece Captured(MBCPiece victim)
69{
70	victim = Opposite(victim & ~kPieceMoved);
71	if (Promoted(victim))	// Captured promoted pieces revert to pawns
72		return Matching(victim, PAWN);
73	else
74		return victim;
75}
76
77static const char * sPieceChar = " KQBNRP";
78
79@implementation MBCMove
80
81- (id) initWithCommand:(MBCMoveCode)command;
82{
83	fCommand	=	command;
84	fFromSquare	=	kInvalidSquare;
85	fToSquare	=	kInvalidSquare;
86	fPiece		=	EMPTY;
87	fPromotion	= 	EMPTY;
88	fVictim		= 	EMPTY;
89	fCastling	=	kUnknownCastle;
90	fEnPassant	= 	NO;
91	fAnimate	=	YES;
92
93	return self;
94}
95
96+ (id) newWithCommand:(MBCMoveCode)command
97{
98	return [[MBCMove alloc] initWithCommand:command];
99}
100
101+ (id) moveWithCommand:(MBCMoveCode)command
102{
103	return [[MBCMove newWithCommand:command] autorelease];
104}
105
106- (id) initFromCompactMove:(MBCCompactMove)move
107{
108	[self initWithCommand:MBCMoveCode(move >> 24)];
109
110	switch (fCommand) {
111	case kCmdMove:
112	case kCmdPMove:
113		fFromSquare	= (move >> 16) & 0xFF;
114		fToSquare	= (move >> 8)  & 0xFF;
115		fPromotion	= move & 0xFF;
116		break;
117	case kCmdDrop:
118	case kCmdPDrop:
119		fToSquare	= (move >> 8)  & 0xFF;
120		fPiece		= move & 0xFF;
121		break;
122	default:
123		break;
124	}
125
126	return self;
127}
128
129+ (id) newFromCompactMove:(MBCCompactMove)move
130{
131	return [[MBCMove alloc] initFromCompactMove:move];
132}
133
134+ (id) moveFromCompactMove:(MBCCompactMove)move
135{
136	return [[MBCMove newFromCompactMove:move] autorelease];
137}
138
139- (id) initFromEngineMove:(NSString *)engineMove
140{
141	const char * piece	= " KQBNRP  kqbnrp ";
142	const char * move	= [engineMove UTF8String];
143
144	if (move[1] == '@') {
145		[self initWithCommand:kCmdDrop];
146		fPiece		= static_cast<MBCPiece>(strchr(piece, move[0])-piece);
147		fToSquare	= Square(move+2);
148	} else {
149		[self initWithCommand:kCmdMove];
150		fFromSquare	= Square(move);
151		fToSquare	= Square(move+2);
152		if (move[4])
153			fPromotion	= static_cast<MBCPiece>(strchr(piece, move[4])-piece);
154	}
155
156	return self;
157}
158
159+ (id) newFromEngineMove:(NSString *)engineMove
160{
161	return [[MBCMove alloc] initFromEngineMove:engineMove];
162}
163
164+ (id) moveFromEngineMove:(NSString *)engineMove
165{
166	return [[MBCMove newFromEngineMove:engineMove] autorelease];
167}
168
169+ (BOOL)compactMoveIsWin:(MBCCompactMove)move
170{
171    switch (move >> 24) {
172    case kCmdWhiteWins:
173    case kCmdBlackWins:
174        return YES;
175    default:
176        return NO;
177    }
178}
179
180NSString * sPieceLetters[] = {
181	@"",
182	@"king_letter",
183	@"queen_letter",
184	@"bishop_letter",
185	@"knight_letter",
186	@"rook_letter",
187	@"pawn_letter"
188};
189
190- (NSString *) pieceLetter:(MBCPiece)piece forDrop:(BOOL)drop
191{
192	piece = Piece(piece);
193	if (!drop && piece==PAWN)
194		return @" ";
195	else
196		return NSLocalizedString(sPieceLetters[piece],
197								 "Piece Letter");
198}
199
200- (NSString *) localizedText
201{
202    NSString * origin       = [self origin];
203    NSString * operation    = [self operation];
204    NSString * destination  = [self destinationForTitle:YES];
205    NSString * check        = [self check];
206    NSString * text;
207    if ([origin length] || [destination length])
208        text = [NSString localizedStringWithFormat:NSLocalizedString(@"title_move_fmt", "%@%@%@"),
209                origin, operation, destination];
210    else
211        text = operation;
212    if ([check length])
213        text = [NSString localizedStringWithFormat:NSLocalizedString(@"title_check_fmt", @"%@%@"),
214                text, check];
215
216    return text;
217}
218
219- (NSString *) origin
220{
221	switch (fCommand) {
222	case kCmdMove:
223	case kCmdPMove:
224		if (fCastling != kNoCastle)
225			return @"";
226		else
227			return [NSString localizedStringWithFormat:NSLocalizedString(@"move_origin_fmt", @"%@%c%c"),
228					[self pieceLetter:fPiece forDrop:NO],
229					Col(fFromSquare), Row(fFromSquare)+'0'];
230	case kCmdDrop:
231	case kCmdPDrop:
232		return [NSString localizedStringWithFormat:NSLocalizedString(@"drop_origin_fmt", @"%@"),
233				[self pieceLetter:fPiece forDrop:YES]];
234	default:
235		return @"";
236	}
237}
238
239- (NSString *) operation
240{
241	UniChar op = fVictim ? 0x00D7 : '-';
242	switch (fCommand) {
243	case kCmdMove:
244	case kCmdPMove:
245		switch (fCastling) {
246		case kCastleQueenside:
247			return @"0 - 0 - 0";
248		case kCastleKingside:
249			return @"0 - 0";
250		default:
251			break;
252		}
253		break;
254	case kCmdDrop:
255	case kCmdPDrop:
256		op = '@';
257		break;
258	default:
259		op = ' ';
260		break;
261	}
262	return [NSString localizedStringWithFormat:NSLocalizedString(@"operation_fmt", @"%C"), op];
263}
264
265- (NSString *) destinationForTitle:(BOOL)forTitle
266{
267    NSString * check = [self check];
268    NSString * text;
269	if (fCastling != kNoCastle && fCastling != kUnknownCastle)
270		return check;
271	else if (fPromotion)
272		text = [NSString localizedStringWithFormat:NSLocalizedString(@"promo_dest_fmt", @"%c%c=@%"),
273                    Col(fToSquare), Row(fToSquare)+'0', [self pieceLetter:fPromotion forDrop:NO]];
274	else
275		text = [NSString localizedStringWithFormat:NSLocalizedString(@"move_dest_fmt", @"%c%c"),
276                    Col(fToSquare), Row(fToSquare)+'0'];
277    if ([check length])
278        return [NSString localizedStringWithFormat:NSLocalizedString(@"dest_check_fmt", @"%@ %@"),
279                text, check];
280    else
281        return text;
282}
283
284- (NSString *) destination
285{
286    return [self destinationForTitle:NO];
287}
288
289- (NSString *) check
290{
291    if (fCheckMate)
292        return NSLocalizedString(@"move_is_checkmate", @"�");
293    else if (fCheck)
294        return NSLocalizedString(@"move_is_check", @"+");
295    else
296        return @"";
297}
298
299- (NSString *) engineMove
300{
301	const char * piece	= " KQBNRP  kqbnrp ";
302
303#define SQUARETOCOORD(sq) 	Col(sq), Row(sq)+'0'
304
305	switch (fCommand) {
306	case kCmdMove:
307		if (fPromotion)
308			return [NSString stringWithFormat:@"%c%c%c%c%c\n",
309							 SQUARETOCOORD(fFromSquare),
310							 SQUARETOCOORD(fToSquare),
311							 piece[fPromotion&15]];
312		else
313			return [NSString stringWithFormat:@"%c%c%c%c\n",
314							 SQUARETOCOORD(fFromSquare),
315							 SQUARETOCOORD(fToSquare)];
316	case kCmdDrop:
317		return [NSString stringWithFormat:@"%c@%c%c\n",
318						 piece[fPiece&15],
319						 SQUARETOCOORD(fToSquare)];
320		break;
321	default:
322		return nil;
323	}
324}
325
326@end
327
328bool MBCPieces::NoPieces(MBCPieceCode color)
329{
330    color = (MBCPieceCode)Opposite(Color(color));
331
332    return fInHand[color+QUEEN]  == 1
333        && fInHand[color+BISHOP] == 2
334        && fInHand[color+KNIGHT] == 2
335        && fInHand[color+ROOK]   == 2
336        && fInHand[color+PAWN]   == 8;
337}
338
339@implementation MBCBoard
340
341- (id)init
342{
343    if (self = [super init])
344        fObservers = [[NSMutableArray alloc] init];
345
346    return self;
347}
348
349- (void)removeChessObservers
350{
351    NSNotificationCenter * notificationCenter = [NSNotificationCenter defaultCenter];
352    [fObservers enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
353        [notificationCenter removeObserver:obj];
354    }];
355    [fObservers removeAllObjects];
356}
357
358- (void)dealloc
359{
360    [self removeChessObservers];
361    [fObservers release];
362    [fMoves release];
363    [super dealloc];
364}
365
366- (void)setDocument:(id)doc
367{
368    fDocument   = doc;
369	fMoves      = nil;
370
371	[self resetWithVariant:[doc variant]];
372
373    [self removeChessObservers];
374    [fObservers addObject:
375     [[NSNotificationCenter defaultCenter]
376        addObserverForName:MBCGameLoadNotification object:doc
377        queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
378            NSDictionary * dict    = [note userInfo];
379            NSString *     fen     = [dict objectForKey:@"Position"];
380            NSString *     holding = [dict objectForKey:@"Holding"];
381            NSString *     moves   = [dict objectForKey:@"Moves"];
382            fVariant               = [doc variant];
383
384            if (fen || moves)
385                [self setFen:fen holding:holding moves:moves];
386        }]];
387}
388
389- (void) resetWithVariant:(MBCVariant)variant
390{
391	memset(fCurPos.fBoard, EMPTY, 64);
392	memset(fCurPos.fInHand, 0, 16);
393
394	/* White pieces */
395	fCurPos.fBoard[Square('a',1)] = White(ROOK);
396	fCurPos.fBoard[Square('b',1)] = White(KNIGHT);
397	fCurPos.fBoard[Square('c',1)] = White(BISHOP);
398	fCurPos.fBoard[Square('d',1)] = White(QUEEN);
399	fCurPos.fBoard[Square('e',1)] = White(KING);
400	fCurPos.fBoard[Square('f',1)] = White(BISHOP);
401	fCurPos.fBoard[Square('g',1)] = White(KNIGHT);
402	fCurPos.fBoard[Square('h',1)] = White(ROOK);
403	fCurPos.fBoard[Square('a',2)] = White(PAWN);
404	fCurPos.fBoard[Square('b',2)] = White(PAWN);
405	fCurPos.fBoard[Square('c',2)] = White(PAWN);
406	fCurPos.fBoard[Square('d',2)] = White(PAWN);
407	fCurPos.fBoard[Square('e',2)] = White(PAWN);
408	fCurPos.fBoard[Square('f',2)] = White(PAWN);
409	fCurPos.fBoard[Square('g',2)] = White(PAWN);
410	fCurPos.fBoard[Square('h',2)] = White(PAWN);
411
412	/* Black pieces */
413	fCurPos.fBoard[Square('a',7)] = Black(PAWN);
414	fCurPos.fBoard[Square('b',7)] = Black(PAWN);
415	fCurPos.fBoard[Square('c',7)] = Black(PAWN);
416	fCurPos.fBoard[Square('d',7)] = Black(PAWN);
417	fCurPos.fBoard[Square('e',7)] = Black(PAWN);
418	fCurPos.fBoard[Square('f',7)] = Black(PAWN);
419	fCurPos.fBoard[Square('g',7)] = Black(PAWN);
420	fCurPos.fBoard[Square('h',7)] = Black(PAWN);
421	fCurPos.fBoard[Square('a',8)] = Black(ROOK);
422	fCurPos.fBoard[Square('b',8)] = Black(KNIGHT);
423	fCurPos.fBoard[Square('c',8)] = Black(BISHOP);
424	fCurPos.fBoard[Square('d',8)] = Black(QUEEN);
425	fCurPos.fBoard[Square('e',8)] = Black(KING);
426	fCurPos.fBoard[Square('f',8)] = Black(BISHOP);
427	fCurPos.fBoard[Square('g',8)] = Black(KNIGHT);
428	fCurPos.fBoard[Square('h',8)] = Black(ROOK);
429
430	fPrvPos		= fCurPos;
431	fVariant	= variant;
432	fMoveClock	= 0;
433
434	[fMoves release];
435	fMoves	= [[NSMutableArray alloc] init];
436
437	fPromotion[0] = QUEEN;
438	fPromotion[1] = QUEEN;
439}
440
441- (void) startGame:(MBCVariant)variant
442{
443	[self resetWithVariant:variant];
444}
445
446- (MBCPiece) curContents:(MBCSquare)square
447{
448	return fCurPos.fBoard[square];
449}
450
451- (MBCPiece) oldContents:(MBCSquare)square
452{
453	return fPrvPos.fBoard[square];
454}
455
456- (int) curInHand:(MBCPiece)piece
457{
458	return fCurPos.fInHand[piece];
459}
460
461- (int) oldInHand:(MBCPiece)piece
462{
463	return fPrvPos.fInHand[piece];
464}
465
466//
467// After every move, pieces on the board and in hand have to balance out
468//
469- (void) consistencyCheck
470{
471	char	inventory[8];
472
473	memset(inventory, 0, 8);
474
475	inventory[PAWN]		= 16;
476	inventory[ROOK]		=  4;
477	inventory[KNIGHT]	=  4;
478	inventory[BISHOP]	=  4;
479	inventory[QUEEN]	=  2;
480	inventory[KING]		=  2;
481
482	for (MBCPiece * p = fCurPos.fBoard; p < fCurPos.fBoard+64; ++p) {
483		MBCPiece piece = *p;
484		--inventory[Promoted(piece) ? PAWN : Piece(piece)];
485	}
486	for (int i = 1; i < 8; ++i) {
487		inventory[i] -= fCurPos.fInHand[i]+fCurPos.fInHand[i|kBlackPiece];
488		if (inventory[i])
489            MBCAbort([NSString localizedStringWithFormat:@"Board consistency check: %d %d\n", i, inventory[i]],
490                     fDocument);
491	}
492#if 0
493	MBCMoveGenerator	logMoves([MBCDebugMoveBuilder debugMoveBuilder],
494								 fVariant, 0);
495	logMoves.Generate(!([fMoves count] & 1), fCurPos);
496	if (logMoves.InCheck(!([fMoves count] & 1), fCurPos))
497		NSLog(@"Check!");
498#endif
499}
500
501- (void) makeMove:(MBCMove *)move
502{
503	//
504	// Ignore everything except moves & drops
505	//
506	if (move->fCommand != kCmdMove && move->fCommand != kCmdDrop)
507		return;
508
509	//
510	// Make the move on the board
511	//
512	MBCSquare	toSquare	= move->fToSquare;
513	MBCSquare	fromSquare	= move->fFromSquare;
514	MBCPiece * 	board		= fCurPos.fBoard;
515	char *		inHand		= fCurPos.fInHand;
516	MBCPiece 	piece 		= move->fPromotion;
517
518	if (move->fCommand == kCmdMove) {
519		move->fPiece = board[fromSquare];
520		[self tryCastling:move];
521		[self tryPromotion:move];
522		if (!piece) {
523			//
524			// Not a pawn promotion, piece stays the same
525			//
526			piece = move->fPiece;
527		} else {
528			//
529			// Pawn promotion
530			//
531			move->fPromotion =
532				Piece(piece) | Color(move->fPiece) | kPromoted;
533			piece = move->fPromotion | kPieceMoved;
534		}
535		if (MBCPiece victim = board[toSquare]) {
536			//
537			// Record captured piece
538			//
539			move->fVictim = victim;
540			++inHand[Captured(victim)];
541		} else if (Piece(piece) == PAWN && Col(fromSquare) != Col(toSquare)) {
542			//
543			// En passant capture
544			//
545			MBCSquare victimSquare  = Square(Col(toSquare), Row(fromSquare));
546			MBCPiece  victim		= board[victimSquare];
547			move->fVictim 			= victim;
548			++inHand[Captured(victim)];
549			board[victimSquare] 	= EMPTY;
550			move->fEnPassant		= YES;
551		}
552		if (move->fVictim || Piece(move->fPiece) == PAWN)
553			fMoveClock	= 0;
554		else
555			++fMoveClock;
556		board[fromSquare] = EMPTY;
557		unsigned row = Row(toSquare);
558		switch (move->fCastling) {
559		case kUnknownCastle:
560		case kNoCastle:
561			break;
562		case kCastleQueenside:
563			board[Square('d', row)] = board[Square('a', row)] | kPieceMoved;
564			board[Square('a', row)] = EMPTY;
565			break;
566		case kCastleKingside:
567			board[Square('f', row)] = board[Square('h', row)] | kPieceMoved;
568			board[Square('h', row)] = EMPTY;
569			break;
570		}
571		piece			   |= kPieceMoved;
572		if (!move->fVictim && Piece(piece) == PAWN &&
573			abs(Row(fromSquare)-Row(toSquare))==2
574		)
575			fCurPos.fEnPassant	= Square(Col(fromSquare),
576										 (Row(fromSquare)+Row(toSquare))/2);
577		else
578			fCurPos.fEnPassant	= kInvalidSquare;
579	} else {
580		//
581		// Drop, deplete in hand pieces. A dropped piece is not considered
582		// to have moved yet.
583		//
584		piece	= move->fPiece;
585		if (--inHand[piece] < 0)
586            MBCAbort([NSString localizedStringWithFormat:@"Dropping non-existent %c",
587                      sPieceChar[Piece(move->fPiece)]],
588                     fDocument);
589
590	}
591	board[toSquare] = piece;
592
593	//
594	// Record the move made in undo buffer
595	//
596	[fMoves addObject:move];
597
598    //
599    // Is the move a check?
600    //
601    if (fVariant != kVarSuicide) {
602        MBCMoveGenerator	checkChecker(nil, fVariant, 0);
603        if ((move->fCheck = checkChecker.InCheck(!([fMoves count] & 1), fCurPos)))
604            move->fCheckMate = checkChecker.InCheckMate(!([fMoves count] & 1), fCurPos);
605    }
606
607	[self consistencyCheck];
608}
609
610- (MBCCastling) tryCastling:(MBCMove *)move
611{
612	move->fCastling			= kNoCastle;
613
614	MBCSquare   fromSquare 	= move->fFromSquare;
615	MBCPiece * 	board		= fCurPos.fBoard;
616	MBCPiece	king 		= move->fPiece;
617
618	if (Piece(king) != KING || king & kPieceMoved)
619		return kNoCastle;
620	MBCPieceCode 	kingColor = Color(king);
621	unsigned 		row = Row(move->fToSquare);
622	if (Row(fromSquare) != row)
623		return kNoCastle;
624
625	//
626	// These comparisons will fail if the rook has kPieceMoved set, which
627	// they should.
628	//
629	switch (Col(move->fToSquare)) {
630	case 'c':	// Queenside castle
631		if (board[Square('a', row)] != Matching(kingColor, ROOK)
632		 || board[Square('b', row)] != EMPTY
633		 || board[Square('c', row)] != EMPTY
634		 || board[Square('d', row)] != EMPTY
635		)
636			return kNoCastle;
637		else
638			return move->fCastling = kCastleQueenside;
639	case 'g':	// Kingside castle
640		if (board[Square('h', row)] != Matching(kingColor, ROOK)
641		 || board[Square('g', row)] != EMPTY
642		 || board[Square('f', row)] != EMPTY
643		)
644			return kNoCastle;
645		else
646			return move->fCastling = kCastleKingside;
647	default:
648		return kNoCastle;
649	}
650}
651
652- (void)tryPromotion:(MBCMove *)move
653{
654	if (move->fCommand == kCmdMove
655	  && Piece(fCurPos.fBoard[move->fFromSquare]) == PAWN
656    )
657		//
658		// Possibly a promotion where we need to fill in the piece
659		//
660		switch (Row(move->fToSquare)) {
661		case 1:
662			//
663			// Black promotion
664			//
665			if (!move->fPromotion)
666				move->fPromotion = fPromotion[0];
667			break;
668		case 8:
669			//
670			// White promotion
671			//
672			if (!move->fPromotion)
673				move->fPromotion = fPromotion[1];
674			break;
675		default:
676			move->fPromotion = EMPTY;
677			break;
678		}
679}
680
681- (MBCSide)sideOfMove:(MBCMove *)move
682{
683    switch (move->fCommand) {
684    case kCmdWhiteWins:
685        return kWhiteSide;
686    case kCmdBlackWins:
687        return kBlackSide;
688    case kCmdDraw:
689        return ([fMoves count] & 1) ? kWhiteSide : kBlackSide;
690    default:
691        return Color(move->fPiece)==kWhitePiece ? kWhiteSide : kBlackSide;
692    }
693}
694
695- (BOOL) reachFromCol:(char)fromCol row:(unsigned)fromRow
696			 deltaCol:(int)colDelta row:(int)rowDelta
697				steps:(int)steps
698{
699	if (steps < 0)
700		steps = -steps;
701	while (--steps > 0)
702		if (fCurPos.fBoard[Square(fromCol += colDelta, fromRow += rowDelta)])
703			return NO; // Occupied square in between
704
705	return YES;
706}
707
708- (BOOL) reachDiagonalFromCol:(char)fromCol row:(unsigned)fromRow
709						toCol:(char)toCol row:(unsigned)toRow
710{
711	int colDiff	= toCol - fromCol;
712	int rowDiff	= (int)toRow - (int)fromRow;
713
714	if (colDiff != rowDiff && colDiff != -rowDiff)
715		return NO;	// Not on same diagonal
716
717	return [self reachFromCol:fromCol row:fromRow
718				 deltaCol:(colDiff<0 ? -1 : 1) row:(rowDiff<0 ? -1 : 1)
719				 steps:colDiff];
720}
721
722- (BOOL) reachStraightFromCol:(char)fromCol row:(unsigned)fromRow
723						toCol:(char)toCol row:(unsigned)toRow
724{
725	if (fromRow==toRow)
726		return [self reachFromCol:fromCol row:fromRow
727					 deltaCol:(toCol<fromCol ? -1 : 1) row:0
728					 steps:toCol-fromCol];
729	else if (fromCol==toCol)
730		return [self reachFromCol:fromCol row:fromRow
731					 deltaCol:0 row:(toRow<fromRow ? -1 : 1)
732					 steps:toRow-fromRow];
733	else
734		return NO;
735}
736
737- (MBCUnique) disambiguateMove:(MBCMove *)move
738{
739	MBCSquare 	from	= move->fFromSquare;
740	unsigned	fromRow	= Row(from);
741	char		fromCol	= Col(from);
742	MBCSquare 	to		= move->fToSquare;
743	unsigned	toRow	= Row(to);
744	char		toCol	= Col(to);
745	MBCPiece 	piece	= fCurPos.fBoard[from];
746	MBCUnique	unique	= 0;
747
748	for (char col = 'a'; col < 'i'; ++col)
749		for (unsigned row = 1; row<9; ++row) {
750			if (col == fromCol && row == fromRow)
751				continue;	// Same as from square
752			if (col == toCol && row == toRow)
753				continue;	// Same as to square
754			if ((fCurPos.fBoard[Square(col, row)] ^ piece) & 15)
755				continue;	// Not a matching piece
756			switch (Piece(piece)) {
757			case PAWN:
758				//
759				// The only column ambiguities can exist with captures
760				//
761				if (fromRow == row && toCol-fromCol == col-toCol)
762					break;
763				continue;
764			case KING: // Multiple kings? Only in suicide
765				if (col>=toCol-1 && col<=toCol+1 && row>=toRow-1 && row<=toRow+1)
766					break;
767				continue;
768			case KNIGHT:
769				if (col == toCol-1 || col == toCol+1) {
770					if (row == toRow-2 || row == toRow+2)
771						break;
772				} else if (col == toCol-2 || col == toCol+2) {
773					if (row == toRow-1 || row == toRow+1)
774						break;
775				}
776				continue;
777			case BISHOP:
778				if ([self reachDiagonalFromCol:col row:row toCol:toCol row:toRow])
779					break;
780				continue;
781			case QUEEN:
782				if ([self reachDiagonalFromCol:col row:row toCol:toCol row:toRow])
783					break;
784				// Fall through
785			case ROOK:
786				if ([self reachStraightFromCol:col row:row toCol:toCol row:toRow])
787					break;
788				continue;
789			default:
790				continue;
791			}
792			unique	|= kMatchingPieceExists;
793			if (row == fromRow)
794				unique	|= kMatchingPieceOnSameRow;
795			if (col == fromCol)
796				unique	|= kMatchingPieceOnSameCol;
797		}
798	return unique;
799}
800
801- (bool) undoMoves:(int)numMoves
802{
803	if ((int)[fMoves count]<numMoves)
804		return false;
805
806	if (fMoveClock < numMoves)
807		fMoveClock = 0;
808	else
809		fMoveClock -= numMoves;
810
811	while (numMoves-- > 0) {
812		MBCMove *  	move 		= [fMoves lastObject];
813		MBCPiece * 	board		= fCurPos.fBoard;
814		char *		inHand		= fCurPos.fInHand;
815
816		if (move->fCommand == kCmdMove) {
817			board[move->fFromSquare] = move->fPiece;
818			unsigned row = Row(move->fToSquare);
819			switch (move->fCastling) {
820			case kUnknownCastle:
821			case kNoCastle:
822				break;
823			case kCastleQueenside:
824				board[Square('a', row)] =
825					board[Square('d', row)] & ~kPieceMoved;
826				board[Square('d', row)] = EMPTY;
827				break;
828			case kCastleKingside:
829				board[Square('h', row)] =
830					board[Square('f', row)] & ~kPieceMoved;
831				board[Square('f', row)] = EMPTY;
832				break;
833			}
834		} else
835			++inHand[move->fPiece];
836		board[move->fToSquare] = EMPTY;
837		if (MBCPiece victim = move->fVictim) {
838			MBCSquare victimSquare =
839				move->fEnPassant
840				? Square(Col(move->fToSquare), Row(move->fFromSquare))
841				: move->fToSquare;
842			board[victimSquare] = victim;
843			--inHand[Captured(victim)];
844		}
845
846		[fMoves removeLastObject];
847
848		[self consistencyCheck];
849	}
850	fPrvPos = fCurPos;
851
852	return true;
853}
854
855- (void) commitMove
856{
857	fPrvPos = fCurPos;
858}
859
860- (NSString *) fen
861{
862	char 	pos[128];
863	char * 	p	= pos;
864
865	*p++ = ' ';
866	for (MBCSquare rank = 64; rank; rank -= 8) {
867		for (MBCSquare square = rank-8; square < rank; ++square)
868			if (MBCPiece piece = fCurPos.fBoard[square])
869				*p++ = " KQBNRP  kqbnrp "[What(piece)];
870			else if (isdigit(p[-1]))
871				++p[-1];
872			else
873				*p++ = '1';
874		if (rank > 8)
875			*p++ = '/';
876	}
877	*p++ = ' ';
878	*p++ = ([fMoves count]&1) ? 'b' : 'w';
879	*p++ = ' ';
880	if (fCurPos.fBoard[Square('e', 1)] == White(KING)) {
881		if (fCurPos.fBoard[Square('h', 1)] == White(ROOK))
882			*p++ = 'K';
883		if (fCurPos.fBoard[Square('a', 1)] == White(ROOK))
884			*p++ = 'Q';
885	}
886	if (fCurPos.fBoard[Square('e', 8)] == Black(KING)) {
887		if (fCurPos.fBoard[Square('h', 8)] == Black(ROOK))
888			*p++ = 'k';
889		if (fCurPos.fBoard[Square('a', 8)] == Black(ROOK))
890			*p++ = 'q';
891	}
892	if (p[-1] == ' ')
893		*p++ = '-';
894	*p++ = ' ';
895	*p++ = '-';
896	if ([fMoves count]) {
897		MBCMove *  move = [fMoves lastObject];
898		if ((move->fPiece & (7|kPieceMoved)) == PAWN
899			&& (Row(move->fToSquare) & 6) == 4
900		) {
901		    p[-1] = Col(move->fToSquare);
902			*p++  = Row(move->fToSquare) == 4 ? '3' : '6';
903		}
904	}
905	snprintf(p, 32, " %d %lu", fMoveClock, ([fMoves count]/2)+1);
906
907	return [NSString stringWithUTF8String:pos+1];
908}
909
910- (NSString *) holding
911{
912	char 	pos[128];
913	char * 	p	= pos;
914
915	*p++ = '[';
916	for (MBCPiece piece = White(KING); piece <= Black(PAWN); ++piece) {
917		for (int count = fCurPos.fInHand[piece]; count-- > 0; )
918			*p++ = " KQBNRP "[Piece(piece)];
919		if (piece == 8) {
920			strcpy(p, "] [");
921			p += 3;
922		}
923	}
924	strcpy(p, "]");
925
926	return [NSString stringWithUTF8String:pos];
927}
928
929- (NSString *)moves
930{
931	NSMutableString * 	moves 		= [NSMutableString stringWithCapacity:200];
932	int 				numMoves 	= [fMoves count];
933
934	for (int m = 0; m<numMoves; ++m) {
935		MBCMove *  move = [fMoves objectAtIndex:m];
936		[moves appendString:[move engineMove]];
937	}
938	return moves;
939}
940
941- (void) setFen:(NSString *)fen holding:(NSString *)holding
942	moves:(NSString *)moves
943{
944	if (moves) {
945		//
946		// We prefer to restore the game by replaying the moves
947		//
948		[self resetWithVariant:fVariant];
949		NSArray * 		m = [moves componentsSeparatedByString:@"\n"];
950		NSEnumerator *	e = [m objectEnumerator];
951		while (NSString * move = [e nextObject])
952			if ([move length])
953				[self makeMove:[MBCMove moveFromEngineMove:move]];
954		if (![fen isEqual:[self fen]])
955			NSLog(@"FEN Mismatch, Expected: <%@> Got <%@>\n",
956				  fen, [self fen]);
957	} else {
958		const char * s = [fen UTF8String];
959		MBCPiece *   b = fCurPos.fBoard+56;
960		MBCPiece	 p;
961
962		memset(fCurPos.fBoard, 0, 64);
963		while (isspace(*s))
964			++s;
965		do {
966			switch (*s++) {
967			case 'K':
968				p = White(KING) | kPieceMoved;
969				break;
970			case 'Q':
971				p = White(QUEEN);
972				break;
973			case 'B':
974				p = White(BISHOP);
975				break;
976			case 'N':
977				p = White(KNIGHT);
978				break;
979			case 'R':
980				p = White(ROOK) | kPieceMoved;
981				break;
982			case 'P':
983				p = White(PAWN) | kPieceMoved;
984				break;
985			case 'k':
986				p = Black(KING) | kPieceMoved;
987				break;
988			case 'q':
989				p = Black(QUEEN);
990				break;
991			case 'b':
992				p = Black(BISHOP);
993				break;
994			case 'n':
995				p = Black(KNIGHT);
996				break;
997			case 'r':
998				p = Black(ROOK) | kPieceMoved;
999				break;
1000			case 'p':
1001				p = Black(PAWN) | kPieceMoved;
1002				break;
1003			case '8':
1004			case '7':
1005			case '6':
1006			case '5':
1007			case '4':
1008			case '3':
1009			case '2':
1010			case '1':
1011				p =  EMPTY;
1012				b += s[-1]-'0';
1013				if (!((b-fCurPos.fBoard) & 7))
1014					b -= 16; // Start previous rank
1015				break;
1016			case '/':
1017			default:
1018				p = EMPTY;
1019				break;
1020			}
1021			if (p) {
1022				*b++ = p;
1023				if (!((b-fCurPos.fBoard) & 7))
1024					b -= 16; // Start previous rank
1025			}
1026		} while (b >= fCurPos.fBoard);
1027
1028		while (isspace(*s))
1029			++s;
1030
1031		if (*s++ == 'b')
1032			[fMoves addObject:[MBCMove moveWithCommand:kCmdNull]];
1033
1034		while (isspace(*s))
1035			++s;
1036
1037		while (!isspace(*s))
1038			switch (*s++) {
1039			case 'K':
1040				fCurPos.fBoard[4] &= ~kPieceMoved;
1041				fCurPos.fBoard[7] &= ~kPieceMoved;
1042				break;
1043			case 'Q':
1044				fCurPos.fBoard[4] &= ~kPieceMoved;
1045				fCurPos.fBoard[0] &= ~kPieceMoved;
1046				break;
1047			case 'k':
1048				fCurPos.fBoard[60] &= ~kPieceMoved;
1049				fCurPos.fBoard[63] &= ~kPieceMoved;
1050				break;
1051			case 'q':
1052				fCurPos.fBoard[60] &= ~kPieceMoved;
1053				fCurPos.fBoard[56] &= ~kPieceMoved;
1054				break;
1055			}
1056
1057		while (isspace(*s))
1058			++s;
1059
1060		if (*s == '-')
1061			fCurPos.fBoard[Square(*s, s[1]-'0')] &= ~kPieceMoved;
1062		s += 2;
1063
1064		while (isspace(*s))
1065			++s;
1066
1067		fMoveClock = 0;
1068		while (isdigit(*s))
1069			fMoveClock = 10*fMoveClock + *s++ - '0';
1070
1071		memset(fCurPos.fInHand, 0, 16);
1072
1073		s = [holding UTF8String];
1074
1075		s = strchr(s, '[');
1076		if (!s)
1077			return;
1078
1079		do {
1080			switch (*++s) {
1081			case 'Q':
1082				p	= White(QUEEN);
1083				break;
1084			case 'B':
1085				p	= White(BISHOP);
1086				break;
1087			case 'N':
1088				p	= White(KNIGHT);
1089				break;
1090			case 'R':
1091				p 	= White(ROOK);
1092				break;
1093			case 'P':
1094				p	= White(PAWN);
1095				break;
1096			default:
1097				p	= 0;
1098				break;
1099			}
1100			if (p)
1101				++fCurPos.fInHand[p];
1102		} while (p);
1103
1104		s = strchr(s, '[');
1105		if (!s)
1106			return;
1107
1108		do {
1109			switch (*++s) {
1110			case 'Q':
1111				p	= Black(QUEEN);
1112				break;
1113			case 'B':
1114				p	= Black(BISHOP);
1115				break;
1116			case 'N':
1117				p	= Black(KNIGHT);
1118				break;
1119			case 'R':
1120				p	= Black(ROOK);
1121				break;
1122			case 'P':
1123				p	= Black(PAWN);
1124				break;
1125			default:
1126				p 	= 0;
1127				break;
1128			}
1129			if (p)
1130				++fCurPos.fInHand[p];
1131		} while (p);
1132	}
1133	fPrvPos = fCurPos;
1134}
1135
1136- (BOOL) saveMovesTo:(FILE *)f
1137{
1138	NSArray * existingMoves = [fMoves copy];
1139	int moves = [fMoves count];
1140
1141	//
1142	// Reset board so we can disambiguate moves
1143	//
1144	[self undoMoves:moves];
1145
1146	//
1147	// Now retrace the moves
1148	//
1149	for (int m = 0; m<moves; ++m) {
1150		if (!(m&1)) {
1151			if (!(m%10))
1152				fputc('\n', f);
1153			fprintf(f, "%d. ", (m / 2)+1);
1154		}
1155		MBCMove *  move = [existingMoves objectAtIndex:m];
1156
1157		if (move->fCommand == kCmdDrop) { // Drop, never ambiguous
1158			fprintf(f, "%c@%c%d ", sPieceChar[Piece(move->fPiece)],
1159					Col(move->fToSquare), Row(move->fToSquare));
1160		} else { // Move, may be ambiguous
1161			MBCPiece  p = Piece(fCurPos.fBoard[move->fFromSquare]);
1162
1163			if (p==PAWN) { // Pawn moves look a bit different
1164				if (move->fVictim) // Capture
1165					fprintf(f, "%cx%c%d", Col(move->fFromSquare),
1166							Col(move->fToSquare), Row(move->fToSquare));
1167				else // Move
1168					fprintf(f, "%c%d", Col(move->fToSquare),
1169							Row(move->fToSquare));
1170				if (move->fPromotion) // Promotion?
1171					fprintf(f, "=%c ", sPieceChar[Piece(move->fPromotion)]);
1172				else
1173					fputc(' ', f);
1174			} else if (move->fCastling != kNoCastle) {
1175				if (move->fCastling == kCastleQueenside)
1176					fputs("O-O-O ", f);
1177				else
1178					fputs("O-O ", f);
1179			} else {
1180				MBCUnique u = [self disambiguateMove:move];
1181				fputc(sPieceChar[p], f);
1182				if (u) {
1183					if (u != (kMatchingPieceExists|kMatchingPieceOnSameCol))
1184						fputc(Col(move->fFromSquare), f);
1185					if (u & kMatchingPieceOnSameCol)
1186						fputc('0'+Row(move->fFromSquare), f);
1187				}
1188				if (move->fVictim) // Capture
1189					fputc('x', f);
1190				fprintf(f, "%c%d ", Col(move->fToSquare), Row(move->fToSquare));
1191			}
1192		}
1193
1194		[self makeMove: move];
1195	}
1196	[existingMoves release];
1197	fPrvPos = fCurPos;
1198
1199	return YES;
1200}
1201
1202- (BOOL) canPromote:(MBCSide)side
1203{
1204	MBCPiece 	piece;
1205	unsigned	rank;
1206
1207	if (side == kBlackSide != ([fMoves count]&1))
1208		return NO;
1209
1210	if (side == kBlackSide) {
1211		piece	= Black(PAWN);
1212		rank	= 2;
1213	} else {
1214		piece	= White(PAWN);
1215		rank 	= 7;
1216	}
1217
1218	for (char file = 'a'; file < 'i'; ++file)
1219		if (What(fCurPos.fBoard[Square(file, rank)]) == piece)
1220			return YES;
1221
1222	return NO;
1223}
1224
1225- (BOOL) canUndo
1226{
1227	return [fMoves count] > 1;
1228}
1229
1230- (MBCMove *) lastMove
1231{
1232	return [fMoves count] ? [fMoves lastObject] : nil;
1233}
1234
1235- (int) numMoves
1236{
1237	return [fMoves count];
1238}
1239
1240- (MBCMove *) move:(int)index
1241{
1242	return (index < (int)[fMoves count]) ? [fMoves objectAtIndex:index] : nil;
1243}
1244
1245- (MBCPieces *) curPos
1246{
1247	return &fCurPos;
1248}
1249
1250- (MBCPiece) defaultPromotion:(BOOL)white
1251{
1252	return fPromotion[white];
1253}
1254
1255- (void) setDefaultPromotion:(MBCPiece)piece for:(BOOL)white
1256{
1257	fPromotion[white] = piece;
1258}
1259
1260- (MBCMoveCode) outcome
1261{
1262    if (![fMoves count])
1263        return kCmdNull;
1264
1265    MBCMove * lastMove = (MBCMove *)[fMoves lastObject];
1266    if (lastMove->fCheckMate) {
1267        if (fVariant == kVarLosers)
1268            return ([fMoves count] & 1) ? kCmdBlackWins : kCmdWhiteWins;
1269        else
1270            return ([fMoves count] & 1) ? kCmdWhiteWins : kCmdBlackWins;
1271    }
1272    if (fVariant == kVarSuicide || fVariant == kVarLosers) {
1273        if (!lastMove->fVictim)
1274            return kCmdNull;
1275        MBCPiece color = Opposite(Color(lastMove->fVictim));
1276        if (fVariant == kVarSuicide && !fCurPos.fInHand[color+KING])
1277            return kCmdNull;
1278        if (fCurPos.NoPieces(Color(lastMove->fVictim)))
1279            return ([fMoves count] & 1) ? kCmdBlackWins : kCmdWhiteWins;
1280    }
1281    if (fVariant != kVarSuicide) {
1282        MBCMoveGenerator	checkChecker(nil, fVariant, 0);
1283        if (checkChecker.InStaleMate(!([fMoves count] & 1), fCurPos))
1284            return fVariant != kVarLosers ? kCmdDraw
1285            : ([fMoves count] & 1) ? kCmdBlackWins : kCmdWhiteWins;
1286        if (fCurPos.NoPieces(kWhitePiece) && fCurPos.NoPieces(kBlackPiece))
1287            return kCmdDraw;
1288    }
1289
1290    return kCmdNull;
1291}
1292
1293NSString * LocalizedString(NSDictionary * localization, NSString * key, NSString * fallback)
1294{
1295	NSString * value = [[localization valueForKey:@"strings"] valueForKey:key];
1296
1297	return value ? value : fallback;
1298}
1299
1300NSString * LocalizedStringWithFormat(NSDictionary * localization, NSString * format, ...)
1301{
1302    va_list args;
1303    va_start(args, format);
1304    NSString * s = [[NSString alloc] initWithFormat:format locale:[localization valueForKey:@"locale"]
1305                                          arguments:args];
1306    va_end(args);
1307
1308    return [s autorelease];
1309}
1310
1311BOOL OldSquares(NSString * fmtString)
1312{
1313	/* We used to specify squares as "%c %d", now we use "%@ %@". To avoid
1314     breakage during the transition, we allow both
1315     */
1316	NSRange r = [fmtString rangeOfString:@"%c"];
1317	if (r.length)
1318		return YES;
1319	r = [fmtString rangeOfString:@"$c"];
1320	if (r.length)
1321		return YES;
1322
1323	return NO;
1324}
1325
1326static NSString *	sPieceName[] = {
1327	@"", @"king", @"queen", @"bishop", @"knight", @"rook", @"pawn"
1328};
1329
1330static NSString * 	sFileKey[] = {
1331	@"file_a", @"file_b", @"file_c", @"file_d", @"file_e", @"file_f", @"file_g", @"file_h"
1332};
1333
1334static NSString * 	sFileDefault[] = {
1335	@"A", @"B", @"C", @"D", @"E", @"F", @"G", @"H"
1336};
1337
1338#define LOC_FILE(f) LOC(sFileKey[(f)-'a'], sFileDefault[(f)-'a'])
1339
1340static NSString * 	sRankKey[] = {
1341	@"rank_1", @"rank_2", @"rank_3", @"rank_4", @"rank_5", @"rank_6", @"rank_7", @"rank_8"
1342};
1343
1344static NSString * 	sRankDefault[] = {
1345	@"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8"
1346};
1347
1348#define LOC_RANK(r) LOC(sRankKey[(r)-1], sRankDefault[(r)-1])
1349
1350- (NSString *)stringFromMove:(MBCMove *)move withLocalization:(NSDictionary *)localization
1351{
1352    switch (move->fCommand) {
1353    case kCmdDrop: {
1354        NSString * format  	= LOC(@"drop_fmt", @"%@ %c %d.");
1355        NSString * pkey 	= [NSString stringWithFormat:@"%@_d", sPieceName[Piece(move->fPiece)]];
1356        NSString * pdef 	= [NSString stringWithFormat:@"drop @% at", sPieceName[Piece(move->fPiece)]];
1357        NSString * ploc 	= LOC(pkey, pdef);
1358        char	   col  	= Col(move->fToSquare);
1359        int		   row  	= Row(move->fToSquare);
1360        if (OldSquares(format))
1361            return LocalizedStringWithFormat(localization, format, ploc, toupper(col), row);
1362        else
1363            return LocalizedStringWithFormat(localization, format, ploc, LOC_FILE(col), LOC_RANK(row));
1364    }
1365    case kCmdPMove:
1366    case kCmdMove: {
1367        MBCPiece	piece;
1368        MBCPiece	victim;
1369        MBCPiece	promo;
1370
1371        if (move->fCastling == kUnknownCastle)
1372            move->fCastling = [self tryCastling:move];
1373        switch (move->fCastling) {
1374        case kCastleQueenside:
1375            return LOC(@"qcastle_fmt", @"Castle [[emph +]]queen side.");
1376        case kCastleKingside:
1377            return LOC(@"kcastle_fmt", @"Castle [[emph +]]king side.");
1378        default:
1379            if (move->fPiece) { // Move already executed
1380                piece 	= move->fPiece;
1381                victim	= move->fVictim;
1382            } else {
1383                piece = fCurPos.fBoard[move->fFromSquare];
1384                victim= fCurPos.fBoard[move->fToSquare];
1385            }
1386            promo	= move->fPromotion;
1387            NSString * pname = LOC(sPieceName[Piece(piece)], sPieceName[Piece(piece)]);
1388            char	   fcol  = Col(move->fFromSquare);
1389            int	   frow  = Row(move->fFromSquare);
1390            char	   tcol  = Col(move->fToSquare);
1391            int	   trow  = Row(move->fToSquare);
1392            if (promo) {
1393                NSString * format = victim
1394                ? LOC(@"cpromo_fmt", @"%@ %c %d takes %c %d %@.")
1395                : LOC(@"promo_fmt", @"%@ %c %d to %c %d %@.");
1396                NSString * pkey  = [NSString stringWithFormat:@"%@_p", sPieceName[Piece(promo)]];
1397                NSString * pdef  = [NSString stringWithFormat:@"promoting to %@", sPieceName[Piece(promo)]];
1398                NSString * ploc  = LOC(pkey, pdef);
1399
1400                if (OldSquares(format))
1401                    return LocalizedStringWithFormat(localization, format, pname,
1402                            toupper(fcol), frow, toupper(tcol), trow,
1403                            ploc);
1404                else
1405                    return LocalizedStringWithFormat(localization, format, pname,
1406                            LOC_FILE(fcol), LOC_RANK(frow), LOC_FILE(tcol), LOC_RANK(trow),
1407                            ploc);
1408            } else {
1409                NSString * format = victim
1410                ? LOC(@"cmove_fmt", @"%@ %c %d takes %c %d.")
1411                : LOC(@"move_fmt", @"%@ %c %d to %c %d.");
1412
1413               if (OldSquares(format))
1414                    return LocalizedStringWithFormat(localization, format, pname,
1415                            toupper(fcol), frow, toupper(tcol), trow);
1416                else
1417                    return LocalizedStringWithFormat(localization, format, pname,
1418                            LOC_FILE(fcol), LOC_RANK(frow), LOC_FILE(tcol), LOC_RANK(trow));
1419            }
1420        }}
1421    case kCmdWhiteWins:
1422        switch (fVariant) {
1423        default:
1424            if ([fMoves count] && ((MBCMove *)[fMoves lastObject])->fCheckMate)
1425                return LOC(@"check_mate", @"[[emph +]]Check mate!");
1426            //
1427            // Fall through
1428            //
1429        case kVarSuicide:
1430        case kVarLosers:
1431            return LOC(@"white_win", @"White wins!");
1432        }
1433    case kCmdBlackWins:
1434        switch (fVariant) {
1435        default:
1436            if ([fMoves count] && ((MBCMove *)[fMoves lastObject])->fCheckMate)
1437                return LOC(@"check_mate", @"[[emph +]]Check mate!");
1438            //
1439            // Fall through
1440            //
1441        case kVarSuicide:
1442        case kVarLosers:
1443            return LOC(@"black_win", @"Black wins!");
1444        }
1445    case kCmdDraw:
1446        return LOC(@"draw", @"The game is a draw!");
1447    default:
1448        return @"";
1449	}
1450}
1451
1452- (NSString *)extStringFromMove:(MBCMove *)move withLocalization:(NSDictionary *)localization
1453{
1454    NSString * basic = [self stringFromMove:move withLocalization:localization];
1455    NSString * ext;
1456
1457    if (move->fCheck || move->fCheckMate) {
1458        NSString * fmt  = LOC(@"has_check_fmt", @"%@, %@");
1459        NSString * check= move->fCheckMate ? LOC(@"check_mate", @"check mate!") : LOC(@"check", @"check!");
1460
1461        ext = LocalizedStringWithFormat(localization, fmt, basic, check);
1462    } else {
1463        NSString * fmt  = LOC(@"no_check_fmt", @"%@.");
1464
1465        ext = LocalizedStringWithFormat(localization, fmt, basic);
1466    }
1467
1468    static NSRegularExpression * sFilter;
1469    if (!sFilter)
1470        sFilter = [[NSRegularExpression alloc] initWithPattern:@"\\[\\[.*?\\]\\]" options:0 error:nil];
1471    return [sFilter stringByReplacingMatchesInString:ext options:0
1472                                               range:NSMakeRange(0, [ext length]) withTemplate:@""];
1473}
1474
1475@end
1476
1477inline MBCCompactMove EncodeCompactMove(
1478			 MBCMoveCode cmd, MBCSquare from, MBCSquare to, MBCPiece piece)
1479{
1480	return (cmd << 24) | (from << 16) | (to << 8) | piece;
1481}
1482
1483inline MBCCompactMove EncodeCompactCommand(MBCMoveCode cmd)
1484{
1485	return cmd << 24;
1486}
1487
1488MBCCompactMove MBCEncodeMove(const char * mv, int ponder)
1489{
1490	const char * 	piece	= " kqbnrp ";
1491	const char * 	p;
1492	MBCPiece		promo	= EMPTY;
1493
1494	if (mv[4] && (p = strchr(piece, mv[4])))
1495		promo	= p-piece;
1496
1497	return EncodeCompactMove(ponder ? kCmdPMove : kCmdMove,
1498							 Square(mv+0), Square(mv+2), promo);
1499}
1500
1501MBCCompactMove MBCEncodeDrop(const char * drop, int ponder)
1502{
1503	const char * 	piece	= " KQBNRP  kqbnrp ";
1504
1505	return EncodeCompactMove(ponder ? kCmdPDrop : kCmdDrop,
1506							 0, Square(drop+2),
1507							 strchr(piece, drop[0])-piece);
1508}
1509
1510MBCCompactMove MBCEncodeIllegal()
1511{
1512	return EncodeCompactCommand(kCmdUndo);
1513}
1514
1515MBCCompactMove MBCEncodeLegal()
1516{
1517	return EncodeCompactCommand(kCmdMoveOK);
1518}
1519
1520MBCCompactMove MBCEncodePong()
1521{
1522	return EncodeCompactCommand(kCmdPong);
1523}
1524
1525MBCCompactMove MBCEncodeStartGame()
1526{
1527	return EncodeCompactCommand(kCmdStartGame);
1528}
1529
1530MBCCompactMove MBCEncodeWhiteWins()
1531{
1532	return EncodeCompactCommand(kCmdWhiteWins);
1533}
1534
1535MBCCompactMove MBCEncodeBlackWins()
1536{
1537	return EncodeCompactCommand(kCmdBlackWins);
1538}
1539
1540MBCCompactMove MBCEncodeDraw()
1541{
1542	return EncodeCompactCommand(kCmdDraw);
1543}
1544
1545MBCCompactMove MBCEncodeTakeback()
1546{
1547	return EncodeCompactCommand(kCmdUndo);
1548}
1549
1550// Local Variables:
1551// mode:ObjC
1552// End:
1553