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