1# -*- coding: UTF-8 -*- 2 3from .ldata import bitPosArray, fileBits, rankBits, moveArray 4from .bitboard import firstBit, iterBits 5from .validator import validateMove 6 7from pychess.Utils.const import SAN, AN, LAN, ENPASSANT, EMPTY, PAWN, KING_CASTLE, QUEEN_CASTLE,\ 8 reprFile, reprRank, chr2Sign, cordDic, reprSign, reprCord, reprSignSittuyin, reprSignMakruk,\ 9 QUEEN, KNIGHT, BISHOP, ROOK, KING, NORMALCHESS, NORMAL_MOVE, PROMOTIONS, WHITE, BLACK, DROP,\ 10 FAN_PIECES, SITTUYINCHESS, FISCHERRANDOMCHESS, SUICIDECHESS, MAKRUKCHESS, CAMBODIANCHESS,\ 11 GIVEAWAYCHESS, ATOMICCHESS, WILDCASTLECHESS, WILDCASTLESHUFFLECHESS, HORDECHESS,\ 12 chrU2Sign, CASTLE_KR, CASTLE_SAN, QUEEN_PROMOTION, NULL_MOVE, FAN, ASEAN_QUEEN 13from pychess.Utils.repr import reprPiece, localReprSign 14from pychess.Utils.lutils.lmovegen import genAllMoves, genPieceMoves, newMove 15 16 17def RANK(cord): 18 return cord >> 3 19 20 21def FILE(cord): 22 return cord & 7 23 24 25def TCORD(move): 26 return move & 63 27 28 29def FCORD(move): 30 return move >> 6 & 63 31 32 33def FLAG(move): 34 return move >> 12 35 36 37def PROMOTE_PIECE(flag): 38 return flag - 2 39 40 41def FLAG_PIECE(piece): 42 return piece + 2 43 44 45class ParsingError(Exception): 46 """ Please raise this with a 3-tupple: (move, reason, board.asFen()) 47 The reason should be usable in the context: 'Move was not parseable 48 because %s' % reason """ 49 pass 50 51 52def sittuyin_promotion_fcord(board, tcord): 53 queenMoves = moveArray[ASEAN_QUEEN] 54 for fcord in iterBits(queenMoves[tcord]): 55 if board.arBoard[fcord]: 56 return fcord 57 58################################################################################ 59# parseAny # 60################################################################################ 61 62 63def parseAny(board, algnot): 64 type = determineAlgebraicNotation(algnot) 65 if type == SAN: 66 return parseSAN(board, algnot) 67 if type == AN: 68 return parseAN(board, algnot) 69 if type == LAN: 70 return parseLAN(board, algnot) 71 return parseFAN(board, algnot) 72 73 74def determineAlgebraicNotation(algnot): 75 upnot = algnot.upper() 76 if upnot in ("O-O", "O-O-O", "0-0", "0-0-0", "OO", "OOO", "00", "000"): 77 return SAN 78 79 # Test for e2-e4 80 if "-" in algnot: 81 return LAN 82 83 # Test for b4xc5 84 if "x" in algnot and algnot.split('x')[0] in cordDic: 85 return LAN 86 87 # Test for e2e4 or a7a8q or a7a8=q 88 if algnot[:2] in cordDic and algnot[2:4] in cordDic: 89 return AN 90 91 if algnot[0] in FAN_PIECES[WHITE] or algnot[0] in FAN_PIECES[BLACK]: 92 return FAN 93 94 return SAN 95 96################################################################################ 97# listToSan # 98################################################################################ 99 100 101def listToSan(board, moves): 102 # Work on a copy to ensure we don't break things 103 board = board.clone() 104 sanmoves = [] 105 for move in moves: 106 san = toSAN(board, move) 107 sanmoves.append(san) 108 board.applyMove(move) 109 return sanmoves 110 111################################################################################ 112# listToMoves # 113################################################################################ 114 115 116def listToMoves(board, 117 movstrs, 118 type=None, 119 testvalidate=False, 120 ignoreErrors=False): 121 # Work on a copy to ensure we don't break things 122 board = board.clone() 123 moves = [] 124 125 for mstr in movstrs: 126 try: 127 if type is None: 128 move = parseAny(board, mstr) 129 elif type == SAN: 130 move = parseSAN(board, mstr) 131 elif type == AN: 132 move = parseAN(board, mstr) 133 elif type == LAN: 134 move = parseLAN(board, mstr) 135 except ParsingError: 136 if ignoreErrors: 137 break 138 raise 139 140 if testvalidate and mstr != "--": 141 if not validateMove(board, move): 142 if not ignoreErrors: 143 raise ParsingError(mstr, 'Validation', board.asFen()) 144 break 145 146 moves.append(move) 147 board.applyMove(move) 148 149 return moves 150 151################################################################################ 152# toSan # 153################################################################################ 154 155 156def toSAN(board, move, localRepr=False): 157 """ Returns a Short/Abbreviated Algebraic Notation string of a move 158 The board should be prior to the move """ 159 160 def check_or_mate(): 161 board_clone = board.clone() 162 board_clone.applyMove(move) 163 sign = "" 164 if board_clone.isChecked(): 165 for altmove in genAllMoves(board_clone): 166 if board.variant == ATOMICCHESS: 167 from pychess.Variants.atomic import kingExplode 168 if kingExplode(board_clone, altmove, 1 - board_clone.color) and \ 169 not kingExplode(board_clone, altmove, board_clone.color): 170 sign = "+" 171 break 172 elif kingExplode(board_clone, altmove, board_clone.color): 173 continue 174 board_clone.applyMove(altmove) 175 if board_clone.opIsChecked(): 176 board_clone.popMove() 177 continue 178 sign = "+" 179 break 180 else: 181 sign = "#" 182 return sign 183 184 flag = move >> 12 185 186 if flag == NULL_MOVE: 187 return "--" 188 189 fcord = (move >> 6) & 63 190 if flag == KING_CASTLE: 191 return "O-O%s" % check_or_mate() 192 elif flag == QUEEN_CASTLE: 193 return "O-O-O%s" % check_or_mate() 194 195 tcord = move & 63 196 197 fpiece = fcord if flag == DROP else board.arBoard[fcord] 198 tpiece = board.arBoard[tcord] 199 200 part0 = "" 201 part1 = "" 202 203 if fpiece != PAWN or flag == DROP: 204 if board.variant in (CAMBODIANCHESS, MAKRUKCHESS): 205 part0 += reprSignMakruk[fpiece] 206 elif board.variant == SITTUYINCHESS: 207 part0 += reprSignSittuyin[fpiece] 208 elif localRepr: 209 part0 += localReprSign[fpiece] 210 else: 211 part0 += reprSign[fpiece] 212 213 part1 = reprCord[tcord] 214 215 if flag == DROP: 216 return "%s@%s%s" % (part0, part1, check_or_mate()) 217 218 if fpiece != PAWN: 219 xs = [] 220 ys = [] 221 222 board_clone = board.clone() 223 for altmove in genAllMoves(board_clone, drops=False): 224 mfcord = FCORD(altmove) 225 if board_clone.arBoard[mfcord] == fpiece and \ 226 mfcord != fcord and \ 227 TCORD(altmove) == tcord: 228 board_clone.applyMove(altmove) 229 if not board_clone.opIsChecked(): 230 xs.append(FILE(mfcord)) 231 ys.append(RANK(mfcord)) 232 board_clone.popMove() 233 234 x = FILE(fcord) 235 y = RANK(fcord) 236 237 if ys or xs: 238 if y in ys and x not in xs: 239 # If we share rank with another piece, but not file 240 part0 += reprFile[x] 241 elif x in xs and y not in ys: 242 # If we share file with another piece, but not rank 243 part0 += reprRank[y] 244 elif x in xs and y in ys: 245 # If we share both file and rank with other pieces 246 part0 += reprFile[x] + reprRank[y] 247 else: 248 # If we doesn't share anything, it is standard to put file 249 part0 += reprFile[x] 250 251 if tpiece != EMPTY or flag == ENPASSANT: 252 if not (board.variant == SITTUYINCHESS and fcord == tcord): 253 part1 = "x" + part1 254 if fpiece == PAWN: 255 part0 += reprFile[FILE(fcord)] 256 257 if board.variant == SITTUYINCHESS and flag in PROMOTIONS: 258 part0 = reprCord[fcord] 259 260 notat = part0 + part1 261 if flag in PROMOTIONS: 262 if board.variant in (CAMBODIANCHESS, MAKRUKCHESS): 263 notat += "=" + reprSignMakruk[PROMOTE_PIECE(flag)] 264 elif board.variant == SITTUYINCHESS: 265 notat += "=" + reprSignSittuyin[PROMOTE_PIECE(flag)] 266 elif localRepr: 267 notat += "=" + localReprSign[PROMOTE_PIECE(flag)] 268 else: 269 notat += "=" + reprSign[PROMOTE_PIECE(flag)] 270 271 return "%s%s" % (notat, check_or_mate()) 272 273################################################################################ 274# parseSan # 275################################################################################ 276 277 278def parseSAN(board, san): 279 """ Parse a Short/Abbreviated Algebraic Notation string """ 280 notat = san 281 282 color = board.color 283 284 if notat == "--": 285 return newMove(board.kings[color], board.kings[color], NULL_MOVE) 286 287 if notat[-1] in "+#": 288 notat = notat[:-1] 289 # If '++' was used in place of # 290 if notat[-1] == "+": 291 notat = notat[:-1] 292 293 flag = NORMAL_MOVE 294 295 # If last char is a piece char, we assue it the promote char 296 c = notat[-1] 297 if c in "KQRBNSMFkqrbnsmf.": 298 c = c.lower() 299 if c == "k" and board.variant != SUICIDECHESS and board.variant != GIVEAWAYCHESS: 300 raise ParsingError(san, _("invalid promoted piece"), board.asFen()) 301 elif c == ".": 302 if board.variant in (CAMBODIANCHESS, MAKRUKCHESS, SITTUYINCHESS): 303 # temporary hack for xboard bug 304 flag = QUEEN_PROMOTION 305 else: 306 raise ParsingError(san, "invalid san", board.asFen()) 307 else: 308 flag = chr2Sign[c] + 2 309 310 if notat[-2] == "=": 311 notat = notat[:-2] 312 else: 313 notat = notat[:-1] 314 315 if len(notat) < 2: 316 raise ParsingError(san, _("the move needs a piece and a cord"), 317 board.asFen()) 318 319 if notat[0] in "O0o": 320 fcord = board.ini_kings[color] 321 flag = KING_CASTLE if notat in ("O-O", "0-0", "o-o", "OO", "00", "oo") else QUEEN_CASTLE 322 side = flag - QUEEN_CASTLE 323 if FILE(fcord) == 3 and board.variant in (WILDCASTLECHESS, 324 WILDCASTLESHUFFLECHESS): 325 side = 0 if side == 1 else 1 326 if board.variant == FISCHERRANDOMCHESS: 327 tcord = board.ini_rooks[color][side] 328 else: 329 tcord = board.fin_kings[color][side] 330 return newMove(fcord, tcord, flag) 331 332 # LAN is not allowed in pgn spec, but sometimes it occures 333 if "-" in notat: 334 notat = notat.replace("-", "") 335 336 if "@" in notat: 337 tcord = cordDic[notat[-2:]] 338 if notat[0].islower(): 339 # Sjeng-ism 340 piece = chr2Sign[notat[0]] 341 else: 342 piece = chrU2Sign[notat[0]] 343 return newMove(piece, tcord, DROP) 344 345 # standard piece letters 346 if notat[0] in "QRBKNSMF": 347 piece = chrU2Sign[notat[0]] 348 notat = notat[1:] 349 # unambigious lowercase piece letters 350 elif notat[0] in "qrknsm": 351 piece = chr2Sign[notat[0]] 352 notat = notat[1:] 353 # a lowercase bishop letter or a pawn capture 354 elif notat[0] == "b" and len(notat) > 2 and board.variant == NORMALCHESS: 355 tcord = cordDic[notat[-2:]] 356 trank = int(notat[-1]) 357 # if from and to lines are not neighbours -> Bishop 358 if abs(ord(notat[0]) - ord(notat[-2])) > 1: 359 piece = chr2Sign[notat[0]] 360 notat = notat[1:] 361 # if from and to lines are neighbours (or the same) but to is an empty square 362 # which can't be en-passant square target -> Bishop 363 elif board.arBoard[tcord] == EMPTY and ((color == BLACK and trank != 3) or (color == WHITE and trank != 6)): 364 piece = chr2Sign[notat[0]] 365 notat = notat[1:] 366 # elif "ba3", "bc3" ,"ba6", "bc6" 367 # these can be Bishop or Pawn moves, but we don't try to introspect them (sorry) 368 else: 369 piece = PAWN 370 else: 371 piece = PAWN 372 if notat[-1] in "18" and flag == NORMAL_MOVE and board.variant != SITTUYINCHESS: 373 flag = QUEEN_PROMOTION 374 375 if "x" in notat: 376 notat, tcord = notat.split("x") 377 if tcord not in cordDic: 378 raise ParsingError(san, _("the captured cord (%s) is incorrect") % 379 tcord, board.asFen()) 380 381 tcord = cordDic[tcord] 382 383 if piece == PAWN: 384 # If a pawn is attacking an empty cord, we assue it an enpassant 385 if board.arBoard[tcord] == EMPTY: 386 if (color == BLACK and 2 * 8 <= tcord < 3 * 8) or (color == WHITE and 5 * 8 <= tcord < 6 * 8): 387 flag = ENPASSANT 388 else: 389 raise ParsingError( 390 san, _("pawn capture without target piece is invalid"), 391 board.asFen()) 392 else: 393 if not notat[-2:] in cordDic: 394 raise ParsingError(san, _("the end cord (%s) is incorrect") % 395 notat[-2:], board.asFen()) 396 397 tcord = cordDic[notat[-2:]] 398 notat = notat[:-2] 399 400 # In suicide promoting to king is valid, so 401 # more than 1 king per side can exist ! 402 if board.variant != SUICIDECHESS and board.variant != GIVEAWAYCHESS and piece == KING: 403 return newMove(board.kings[color], tcord, flag) 404 405 # If there is any extra location info, like in the move Bexd1 or Nh3f4 we 406 # want to know 407 frank = None 408 ffile = None 409 if notat and notat[0] in reprRank: 410 frank = int(notat[0]) - 1 411 notat = notat[1:] 412 if notat and notat[0] in reprFile: 413 ffile = ord(notat[0]) - ord("a") 414 notat = notat[1:] 415 if notat and notat[0] in reprRank: 416 frank = int(notat[0]) - 1 417 notat = notat[1:] 418 # we know all we want 419 return newMove(frank * 8 + ffile, tcord, flag) 420 421 if piece == PAWN: 422 if (ffile is not None) and ffile != FILE(tcord): 423 # capture 424 if color == WHITE: 425 fcord = tcord - 7 if ffile > FILE(tcord) else tcord - 9 426 else: 427 fcord = tcord + 7 if ffile < FILE(tcord) else tcord + 9 428 else: 429 if color == WHITE: 430 pawns = board.boards[WHITE][PAWN] 431 # In horde white pawns on first rank may move two squares also 432 if board.variant == HORDECHESS and RANK(tcord) == 2 and not ( 433 pawns & fileBits[FILE(tcord)] & rankBits[1]): 434 fcord = tcord - 16 435 else: 436 fcord = tcord - 16 if RANK(tcord) == 3 and not ( 437 pawns & fileBits[FILE(tcord)] & rankBits[2]) else tcord - 8 438 else: 439 pawns = board.boards[BLACK][PAWN] 440 fcord = tcord + 16 if RANK(tcord) == 4 and not ( 441 pawns & fileBits[FILE(tcord)] & rankBits[5]) else tcord + 8 442 443 if board.variant == SITTUYINCHESS and flag == QUEEN_PROMOTION: 444 if pawns & fileBits[FILE(tcord)] & rankBits[RANK(tcord)]: 445 # in place promotion 446 return newMove(tcord, tcord, flag) 447 else: 448 # queen move promotion (fcord have to be the closest cord of promotion zone) 449 fcord = sittuyin_promotion_fcord(board, tcord) 450 return newMove(fcord, tcord, flag) 451 return newMove(fcord, tcord, flag) 452 else: 453 if board.pieceCount[color][piece] == 1: 454 # we have only one from this kind if piece, so: 455 fcord = firstBit(board.boards[color][piece]) 456 return newMove(fcord, tcord, flag) 457 else: 458 # We find all pieces who could have done it. (If san was legal, there should 459 # never be more than one) 460 moves = genPieceMoves(board, piece, tcord) 461 if len(moves) == 1: 462 return moves.pop() 463 else: 464 for move in moves: 465 f = FCORD(move) 466 if frank is not None and frank != RANK(f): 467 continue 468 if ffile is not None and ffile != FILE(f): 469 continue 470 board_clone = board.clone() 471 board_clone.applyMove(move) 472 if board_clone.opIsChecked(): 473 continue 474 return move 475 476 errstring = _("no %(piece)s is able to move to %(cord)s") % {"piece": reprPiece[piece], "cord": reprCord[tcord]} 477 raise ParsingError(san, errstring, board.asFen()) 478 479################################################################################ 480# toLan # 481################################################################################ 482 483 484def toLAN(board, move, localRepr=False): 485 """ Returns a Long/Expanded Algebraic Notation string of a move 486 board should be prior to the move """ 487 488 fcord = FCORD(move) 489 tcord = TCORD(move) 490 flag = FLAG(move) 491 fpiece = fcord if flag == DROP else board.arBoard[fcord] 492 493 s = "" 494 if fpiece != PAWN or flag == DROP: 495 if board.variant in (CAMBODIANCHESS, MAKRUKCHESS): 496 s = reprSignMakruk[fpiece] 497 elif board.variant == SITTUYINCHESS: 498 s = reprSignSittuyin[fpiece] 499 elif localRepr: 500 s = localReprSign[fpiece] 501 else: 502 s = reprSign[fpiece] 503 504 if flag == DROP: 505 s += "@" 506 else: 507 s += reprCord[FCORD(move)] 508 if board.arBoard[tcord] == EMPTY: 509 s += "-" 510 else: 511 s += "x" 512 513 s += reprCord[tcord] 514 515 if flag in PROMOTIONS: 516 s += "=" + reprSign[PROMOTE_PIECE(flag)] 517 518 return s 519 520################################################################################ 521# parseLan # 522################################################################################ 523 524 525def parseLAN(board, lan): 526 """ Parse a Long/Expanded Algebraic Notation string """ 527 528 # To parse LAN pawn moves like "e2-e4" as SAN moves, we have to remove a few 529 # fields 530 if len(lan) == 5: 531 if "x" in lan: 532 # e4xd5 -> exd5 533 return parseSAN(board, lan[0] + lan[3:]) 534 else: 535 # e2-e4 -> e4 536 return parseSAN(board, lan[3:]) 537 538 # We want to use the SAN parser for LAN moves like "Nb1-c3" or "Rd3xd7" 539 # The san parser should be able to handle most stuff, as long as we remove 540 # the slash 541 if not lan.upper().startswith("O-O") and not lan.startswith("--"): 542 lan = lan.replace("-", "") 543 return parseSAN(board, lan) 544 545################################################################################ 546# toAN # 547################################################################################ 548 549 550def toAN(board, move, short=False, castleNotation=CASTLE_SAN): 551 """ Returns a Algebraic Notation string of a move 552 board should be prior to the move 553 short -- returns the short variant, e.g. f7f8q rather than f7f8=Q 554 """ 555 556 fcord = (move >> 6) & 63 557 tcord = move & 63 558 flag = move >> 12 559 560 if flag in (KING_CASTLE, QUEEN_CASTLE): 561 if castleNotation == CASTLE_SAN: 562 return flag == KING_CASTLE and "O-O" or "O-O-O" 563 elif castleNotation == CASTLE_KR: 564 rooks = board.ini_rooks[board.color] 565 tcord = rooks[flag == KING_CASTLE and 1 or 0] 566 # No treatment needed for CASTLE_KK 567 568 if flag == DROP: 569 if board.variant == SITTUYINCHESS: 570 s = "%s@%s" % (reprSignSittuyin[fcord], reprCord[tcord]) 571 else: 572 s = "%s@%s" % (reprSign[fcord], reprCord[tcord]) 573 else: 574 s = reprCord[fcord] + reprCord[tcord] 575 576 if flag in PROMOTIONS: 577 if short: 578 if board.variant in (CAMBODIANCHESS, MAKRUKCHESS): 579 s += reprSignMakruk[PROMOTE_PIECE(flag)].lower() 580 elif board.variant == SITTUYINCHESS: 581 s += reprSignSittuyin[PROMOTE_PIECE(flag)].lower() 582 else: 583 s += reprSign[PROMOTE_PIECE(flag)].lower() 584 else: 585 if board.variant in (CAMBODIANCHESS, MAKRUKCHESS): 586 s += "=" + reprSignMakruk[PROMOTE_PIECE(flag)] 587 elif board.variant == SITTUYINCHESS: 588 s += "=" + reprSignSittuyin[PROMOTE_PIECE(flag)] 589 else: 590 s += "=" + reprSign[PROMOTE_PIECE(flag)] 591 return s 592 593################################################################################ 594# parseAN # 595################################################################################ 596 597 598def parseAN(board, an): 599 """ Parse an Algebraic Notation string """ 600 601 if not 4 <= len(an) <= 6: 602 raise ParsingError(an, "the move must be 4 or 6 chars long", 603 board.asFen()) 604 605 if "@" in an: 606 tcord = cordDic[an[-2:]] 607 if an[0].islower(): 608 # Sjeng-ism 609 piece = chr2Sign[an[0]] 610 else: 611 piece = chrU2Sign[an[0]] 612 return newMove(piece, tcord, DROP) 613 614 try: 615 fcord = cordDic[an[:2]] 616 tcord = cordDic[an[2:4]] 617 except KeyError as e: 618 raise ParsingError(an, "the cord (%s) is incorrect" % e.args[0], 619 board.asFen()) 620 621 flag = NORMAL_MOVE 622 623 if len(an) > 4 and not an[-1] in "QRBNMSFqrbnmsf": 624 if (board.variant != SUICIDECHESS and board.variant != GIVEAWAYCHESS) or \ 625 (board.variant == SUICIDECHESS or board.variant == GIVEAWAYCHESS) and not an[ 626 -1] in "Kk": 627 raise ParsingError(an, "invalid promoted piece", board.asFen()) 628 629 if len(an) == 5: 630 # The a7a8q variant 631 flag = chr2Sign[an[4].lower()] + 2 632 elif len(an) == 6: 633 # The a7a8=q variant 634 flag = chr2Sign[an[5].lower()] + 2 635 elif board.arBoard[fcord] == KING: 636 if fcord - tcord == 2: 637 flag = QUEEN_CASTLE 638 if board.variant == FISCHERRANDOMCHESS: 639 tcord = board.ini_rooks[board.color][0] 640 elif fcord - tcord == -2: 641 flag = KING_CASTLE 642 if board.variant == FISCHERRANDOMCHESS: 643 tcord = board.ini_rooks[board.color][1] 644 elif board.arBoard[ 645 tcord] == ROOK: 646 color = board.color 647 friends = board.friends[color] 648 if bitPosArray[tcord] & friends: 649 if board.ini_rooks[color][0] == tcord: 650 flag = QUEEN_CASTLE 651 else: 652 flag = KING_CASTLE 653 else: 654 flag = NORMAL_MOVE 655 elif board.arBoard[fcord] == PAWN and board.arBoard[tcord] == EMPTY and \ 656 FILE(fcord) != FILE(tcord) and RANK(fcord) != RANK(tcord): 657 flag = ENPASSANT 658 elif board.arBoard[fcord] == PAWN: 659 if an[3] in "18" and board.variant != SITTUYINCHESS: 660 flag = QUEEN_PROMOTION 661 662 return newMove(fcord, tcord, flag) 663 664 665################################################################################ 666# toFAN # 667################################################################################ 668 669san2WhiteFanDic = { 670 ord("K"): FAN_PIECES[WHITE][KING], 671 ord("Q"): FAN_PIECES[WHITE][QUEEN], 672 ord("M"): FAN_PIECES[WHITE][QUEEN], 673 ord("F"): FAN_PIECES[WHITE][QUEEN], 674 ord("R"): FAN_PIECES[WHITE][ROOK], 675 ord("B"): FAN_PIECES[WHITE][BISHOP], 676 ord("S"): FAN_PIECES[WHITE][BISHOP], 677 ord("N"): FAN_PIECES[WHITE][KNIGHT], 678 ord("P"): FAN_PIECES[WHITE][PAWN], 679 ord("+"): "†", 680 ord("#"): "‡" 681} 682 683san2BlackFanDic = { 684 ord("K"): FAN_PIECES[BLACK][KING], 685 ord("Q"): FAN_PIECES[BLACK][QUEEN], 686 ord("M"): FAN_PIECES[BLACK][QUEEN], 687 ord("F"): FAN_PIECES[BLACK][QUEEN], 688 ord("R"): FAN_PIECES[BLACK][ROOK], 689 ord("B"): FAN_PIECES[BLACK][BISHOP], 690 ord("S"): FAN_PIECES[BLACK][BISHOP], 691 ord("N"): FAN_PIECES[BLACK][KNIGHT], 692 ord("P"): FAN_PIECES[BLACK][PAWN], 693 ord("+"): "†", 694 ord("#"): "‡" 695} 696 697 698def toFAN(board, move): 699 """ Returns a Figurine Algebraic Notation string of a move """ 700 701 san = toSAN(board, move) 702 return san.translate(san2WhiteFanDic) 703 704 705################################################################################ 706# parseFAN # 707################################################################################ 708 709fan2SanDic = {} 710for k, v in san2WhiteFanDic.items(): 711 fan2SanDic[ord(v)] = chr(k) 712for k, v in san2BlackFanDic.items(): 713 fan2SanDic[ord(v)] = chr(k) 714 715 716def parseFAN(board, fan): 717 """ Parse a Figurine Algebraic Notation string """ 718 719 san = fan.translate(fan2SanDic) 720 return parseSAN(board, san) 721 722################################################################################ 723# toPolyglot # 724################################################################################ 725 726 727def toPolyglot(board, move): 728 """ Returns a 16-bit Polyglot-format move 729 board should be prior to the move 730 """ 731 pg = move & 4095 732 if FLAG(move) in PROMOTIONS: 733 pg |= (PROMOTE_PIECE(FLAG(move)) - 1) << 12 734 elif FLAG(move) == QUEEN_CASTLE: 735 pg = (pg & 4032) | board.ini_rooks[board.color][0] 736 elif FLAG(move) == KING_CASTLE: 737 pg = (pg & 4032) | board.ini_rooks[board.color][1] 738 739 return pg 740 741################################################################################ 742# parsePolyglot # 743################################################################################ 744 745 746def parsePolyglot(board, pg): 747 """ Parse a 16-bit Polyglot-format move """ 748 749 tcord = TCORD(pg) 750 fcord = FCORD(pg) 751 flag = NORMAL_MOVE 752 if pg >> 12: 753 flag = FLAG_PIECE((pg >> 12) + 1) 754 elif board.arBoard[fcord] == KING: 755 if board.arBoard[tcord] == ROOK: 756 color = board.color 757 friends = board.friends[color] 758 if bitPosArray[tcord] & friends: 759 if board.ini_rooks[color][0] == tcord: 760 flag = QUEEN_CASTLE 761 if board.variant == NORMALCHESS: # Want e1c1/e8c8 762 tcord += 2 763 else: 764 flag = KING_CASTLE 765 if board.variant == NORMALCHESS: # Want e1g1/e8g8 766 tcord -= 1 767 elif board.arBoard[fcord] == PAWN and board.arBoard[tcord] == EMPTY and \ 768 FILE(fcord) != FILE(tcord) and RANK(fcord) != RANK(tcord): 769 flag = ENPASSANT 770 771 return newMove(fcord, tcord, flag) 772