def parseSAN(board, san, full=True): """ Parse a Short/Abbreviated Algebraic Notation string """ notat = san color = board.color if notat == "--": return newMove(board.kings[color], board.kings[color], NULL_MOVE) if notat[-1] in "+#": notat = notat[:-1] # If '++' was used in place of # if notat[-1] == "+": notat = notat[:-1] flag = NORMAL_MOVE # If last char is a piece char, we assue it the promote char c = notat[-1] if c in "KQRBNSMFkqrbnsmf.": c = c.lower() if c == "k" and board.variant != SUICIDECHESS: raise ParsingError(san, _("invalid promoted piece"), board.asFen()) elif c == "." and board.variant in (CAMBODIANCHESS, MAKRUKCHESS, SITTUYINCHESS): # temporary hack for xboard bug flag = QUEEN_PROMOTION else: flag = chr2Sign[c] + 2 if notat[-2] == "=": notat = notat[:-2] else: notat = notat[:-1] if len(notat) < 2: raise ParsingError(san, _("the move needs a piece and a cord"), board.asFen()) if notat[0] in "O0o": fcord = board.ini_kings[color] flag = KING_CASTLE if notat == "O-O" or notat == "0-0" or notat == "o-o" else QUEEN_CASTLE side = flag - QUEEN_CASTLE if FILE(fcord) == 3 and board.variant in (WILDCASTLECHESS, WILDCASTLESHUFFLECHESS): side = 0 if side == 1 else 1 if board.variant == FISCHERRANDOMCHESS: tcord = board.ini_rooks[color][side] else: tcord = board.fin_kings[color][side] return newMove(fcord, tcord, flag) # LAN is not allowed in pgn spec, but sometimes it occures if "-" in notat: notat = notat.replace("-", "") if "@" in notat: tcord = cordDic[notat[-2:]] if notat[0].islower(): # Sjeng-ism piece = chr2Sign[notat[0]] else: piece = chrU2Sign[notat[0]] return newMove(piece, tcord, DROP) if notat[0] in "QRBKNSMF": piece = chrU2Sign[notat[0]] notat = notat[1:] else: piece = PAWN if notat[ -1] in "18" and flag == NORMAL_MOVE and board.variant != SITTUYINCHESS: raise ParsingError( san, _("promotion move without promoted piece is incorrect"), board.asFen()) if "x" in notat: notat, tcord = notat.split("x") if tcord not in cordDic: raise ParsingError( san, _("the captured cord (%s) is incorrect") % tcord, board.asFen()) tcord = cordDic[tcord] if piece == PAWN: # If a pawn is attacking an empty cord, we assue it an enpassant if board.arBoard[tcord] == EMPTY: if (color == BLACK and 2 * 8 <= tcord < 3 * 8) or ( color == WHITE and 5 * 8 <= tcord < 6 * 8): flag = ENPASSANT else: raise ParsingError( san, _("pawn capture without target piece is invalid"), board.asFen()) else: if not notat[-2:] in cordDic: raise ParsingError( san, _("the end cord (%s) is incorrect") % notat[-2:], board.asFen()) tcord = cordDic[notat[-2:]] notat = notat[:-2] # In suicide promoting to king is valid, so # more than 1 king per side can exist ! if board.variant != SUICIDECHESS and piece == KING: return newMove(board.kings[color], tcord, flag) # If there is any extra location info, like in the move Bexd1 or Nh3f4 we # want to know frank = None ffile = None if notat and notat[0] in reprRank: frank = int(notat[0]) - 1 notat = notat[1:] if notat and notat[0] in reprFile: ffile = ord(notat[0]) - ord("a") notat = notat[1:] if notat and notat[0] in reprRank: frank = int(notat[0]) - 1 notat = notat[1:] # we know all we want return newMove(frank * 8 + ffile, tcord, flag) if piece == PAWN: if (ffile is not None) and ffile != FILE(tcord): # capture if color == WHITE: fcord = tcord - 7 if ffile > FILE(tcord) else tcord - 9 else: fcord = tcord + 7 if ffile < FILE(tcord) else tcord + 9 else: if color == WHITE: pawns = board.boards[WHITE][PAWN] fcord = tcord - 16 if RANK(tcord) == 3 and not ( pawns & fileBits[FILE(tcord)] & rankBits[2]) else tcord - 8 else: pawns = board.boards[BLACK][PAWN] fcord = tcord + 16 if RANK(tcord) == 4 and not ( pawns & fileBits[FILE(tcord)] & rankBits[5]) else tcord + 8 if board.variant == SITTUYINCHESS and flag == QUEEN_PROMOTION and \ (pawns & fileBits[FILE(tcord)] & rankBits[RANK(tcord)]): return newMove(tcord, tcord, flag) return newMove(fcord, tcord, flag) else: if board.pieceCount[color][piece] == 1: # we have only one from this kind if piece, so: fcord = firstBit(board.boards[color][piece]) return newMove(fcord, tcord, flag) else: # We find all pieces who could have done it. (If san was legal, there should # never be more than one) moves = genPieceMoves(board, piece, tcord) if len(moves) == 1: return moves.pop() else: for move in moves: f = FCORD(move) if frank is not None and frank != RANK(f): continue if ffile is not None and ffile != FILE(f): continue board_clone = board.clone(full) board_clone.applyMove(move, full) if board_clone.opIsChecked(): continue return move errstring = "no %s is able to move to %s" % (reprPiece[piece], reprCord[tcord]) raise ParsingError(san, errstring, board.asFen())
def parseSAN(board, san): """ Parse a Short/Abbreviated Algebraic Notation string """ notat = san color = board.color if notat == "--": return newMove(board.kings[color], board.kings[color], NULL_MOVE) if notat[-1] in "+#": notat = notat[:-1] # If '++' was used in place of # if notat[-1] == "+": notat = notat[:-1] flag = NORMAL_MOVE # If last char is a piece char, we assue it the promote char c = notat[-1] if c in "KQRBNSMFkqrbnsmf.": c = c.lower() if c == "k" and board.variant != SUICIDECHESS and board.variant != GIVEAWAYCHESS: raise ParsingError(san, _("invalid promoted piece"), board.asFen()) elif c == ".": if board.variant in (CAMBODIANCHESS, MAKRUKCHESS, SITTUYINCHESS): # temporary hack for xboard bug flag = QUEEN_PROMOTION else: raise ParsingError(san, "invalid san", board.asFen()) else: flag = chr2Sign[c] + 2 if notat[-2] == "=": notat = notat[:-2] else: notat = notat[:-1] if len(notat) < 2: raise ParsingError(san, _("the move needs a piece and a cord"), board.asFen()) if notat[0] in "O0o": fcord = board.ini_kings[color] flag = KING_CASTLE if notat in ("O-O", "0-0", "o-o", "OO", "00", "oo") else QUEEN_CASTLE side = flag - QUEEN_CASTLE if FILE(fcord) == 3 and board.variant in (WILDCASTLECHESS, WILDCASTLESHUFFLECHESS): side = 0 if side == 1 else 1 if board.variant == FISCHERRANDOMCHESS: tcord = board.ini_rooks[color][side] else: tcord = board.fin_kings[color][side] return newMove(fcord, tcord, flag) # LAN is not allowed in pgn spec, but sometimes it occures if "-" in notat: notat = notat.replace("-", "") if "@" in notat: tcord = cordDic[notat[-2:]] if notat[0].islower(): # Sjeng-ism piece = chr2Sign[notat[0]] else: piece = chrU2Sign[notat[0]] return newMove(piece, tcord, DROP) # standard piece letters if notat[0] in "QRBKNSMF": piece = chrU2Sign[notat[0]] notat = notat[1:] # unambigious lowercase piece letters elif notat[0] in "qrknsm": piece = chr2Sign[notat[0]] notat = notat[1:] # a lowercase bishop letter or a pawn capture elif notat[0] == "b" and len(notat) > 2 and board.variant == NORMALCHESS: tcord = cordDic[notat[-2:]] trank = int(notat[-1]) # if from and to lines are not neighbours -> Bishop if abs(ord(notat[0]) - ord(notat[-2])) > 1: piece = chr2Sign[notat[0]] notat = notat[1:] # if from and to lines are neighbours (or the same) but to is an empty square # which can't be en-passant square target -> Bishop elif board.arBoard[tcord] == EMPTY and ( (color == BLACK and trank != 3) or (color == WHITE and trank != 6)): piece = chr2Sign[notat[0]] notat = notat[1:] # elif "ba3", "bc3" ,"ba6", "bc6" # these can be Bishop or Pawn moves, but we don't try to introspect them (sorry) else: piece = PAWN else: piece = PAWN if notat[ -1] in "18" and flag == NORMAL_MOVE and board.variant != SITTUYINCHESS: flag = QUEEN_PROMOTION if "x" in notat: notat, tcord = notat.split("x") if tcord not in cordDic: raise ParsingError( san, _("the captured cord (%s) is incorrect") % tcord, board.asFen()) tcord = cordDic[tcord] if piece == PAWN: # If a pawn is attacking an empty cord, we assue it an enpassant if board.arBoard[tcord] == EMPTY: if (color == BLACK and 2 * 8 <= tcord < 3 * 8) or ( color == WHITE and 5 * 8 <= tcord < 6 * 8): flag = ENPASSANT else: raise ParsingError( san, _("pawn capture without target piece is invalid"), board.asFen()) else: if not notat[-2:] in cordDic: raise ParsingError( san, _("the end cord (%s) is incorrect") % notat[-2:], board.asFen()) tcord = cordDic[notat[-2:]] notat = notat[:-2] # In suicide promoting to king is valid, so # more than 1 king per side can exist ! if board.variant != SUICIDECHESS and board.variant != GIVEAWAYCHESS and piece == KING: return newMove(board.kings[color], tcord, flag) # If there is any extra location info, like in the move Bexd1 or Nh3f4 we # want to know frank = None ffile = None if notat and notat[0] in reprRank: frank = int(notat[0]) - 1 notat = notat[1:] if notat and notat[0] in reprFile: ffile = ord(notat[0]) - ord("a") notat = notat[1:] if notat and notat[0] in reprRank: frank = int(notat[0]) - 1 notat = notat[1:] # we know all we want return newMove(frank * 8 + ffile, tcord, flag) if piece == PAWN: if (ffile is not None) and ffile != FILE(tcord): # capture if color == WHITE: fcord = tcord - 7 if ffile > FILE(tcord) else tcord - 9 else: fcord = tcord + 7 if ffile < FILE(tcord) else tcord + 9 else: if color == WHITE: pawns = board.boards[WHITE][PAWN] # In horde white pawns on first rank may move two squares also if board.variant == HORDECHESS and RANK(tcord) == 2 and not ( pawns & fileBits[FILE(tcord)] & rankBits[1]): fcord = tcord - 16 else: fcord = tcord - 16 if RANK(tcord) == 3 and not ( pawns & fileBits[FILE(tcord)] & rankBits[2]) else tcord - 8 else: pawns = board.boards[BLACK][PAWN] fcord = tcord + 16 if RANK(tcord) == 4 and not ( pawns & fileBits[FILE(tcord)] & rankBits[5]) else tcord + 8 if board.variant == SITTUYINCHESS and flag == QUEEN_PROMOTION: if pawns & fileBits[FILE(tcord)] & rankBits[RANK(tcord)]: # in place promotion return newMove(tcord, tcord, flag) else: # queen move promotion (fcord have to be the closest cord of promotion zone) fcord = sittuyin_promotion_fcord(board, tcord) return newMove(fcord, tcord, flag) return newMove(fcord, tcord, flag) else: if board.pieceCount[color][piece] == 1: # we have only one from this kind if piece, so: fcord = firstBit(board.boards[color][piece]) return newMove(fcord, tcord, flag) else: # We find all pieces who could have done it. (If san was legal, there should # never be more than one) moves = genPieceMoves(board, piece, tcord) if len(moves) == 1: return moves.pop() else: for move in moves: f = FCORD(move) if frank is not None and frank != RANK(f): continue if ffile is not None and ffile != FILE(f): continue board_clone = board.clone() board_clone.applyMove(move) if board_clone.opIsChecked(): continue return move errstring = _("no %(piece)s is able to move to %(cord)s") % { "piece": reprPiece[piece], "cord": reprCord[tcord] } raise ParsingError(san, errstring, board.asFen())
def parseSAN (board, san): """ Parse a Short/Abbreviated Algebraic Notation string """ notat = san color = board.color if notat == "--": return newMove(board.kings[color], board.kings[color], NULL_MOVE) if notat[-1] in ("+", "#"): notat = notat[:-1] # If '++' was used in place of # if notat[-1] == "+": notat = notat[:-1] flag = NORMAL_MOVE # If last char is a piece char, we assue it the promote char c = notat[-1] if c in ("K", "Q", "R", "B", "N", "k", "q", "r", "b", "n"): c = c.lower() if c == "k" and board.variant != SUICIDECHESS: raise ParsingError, (san, "invalid promoted piece", board.asFen()) flag = chr2Sign[c] + 2 if notat[-2] == "=": notat = notat[:-2] else: notat = notat[:-1] if len(notat) < 2: raise ParsingError, (san, _("the move needs a piece and a cord"), board.asFen()) if notat[0] in "O0o": fcord = board.ini_kings[color] flag = KING_CASTLE if notat == "O-O" or notat == "0-0" or notat == "o-o" else QUEEN_CASTLE side = flag -QUEEN_CASTLE if FILE(fcord) == 3 and board.variant in (WILDCASTLECHESS, WILDCASTLESHUFFLECHESS): side = 0 if side == 1 else 1 if board.variant == FISCHERRANDOMCHESS: tcord = board.ini_rooks[color][side] else: tcord = board.fin_kings[color][side] return newMove (fcord, tcord, flag) # LAN is not allowed in pgn spec, but sometimes it occures if "-" in notat: notat = notat.replace("-", "") if "@" in notat: tcord = cordDic[notat[-2:]] if notat[0].islower(): # Sjeng-ism piece = chr2Sign[notat[0]] else: piece = chrU2Sign[notat[0]] return newMove(piece, tcord, DROP) if notat[0] in ("Q", "R", "B", "K", "N"): piece = chrU2Sign[notat[0]] notat = notat[1:] else: piece = PAWN if notat[-1] in ("1", "8") and flag == NORMAL_MOVE: raise ParsingError, ( san, _("promotion move without promoted piece is incorrect"), board.asFen()) if "x" in notat: notat, tcord = notat.split("x") if not tcord in cordDic: raise ParsingError, ( san, _("the captured cord (%s) is incorrect") % tcord, board.asFen()) tcord = cordDic[tcord] if piece == PAWN: # If a pawn is attacking an empty cord, we assue it an enpassant if board.arBoard[tcord] == EMPTY: flag = ENPASSANT else: if not notat[-2:] in cordDic: raise ParsingError, ( san, "the end cord (%s) is incorrect" % notat[-2:], board.asFen()) tcord = cordDic[notat[-2:]] notat = notat[:-2] # In suicide promoting to king is valid, so # more than 1 king per side can exist ! if board.variant != SUICIDECHESS and piece == KING: return newMove(board.kings[color], tcord, flag) # If there is any extra location info, like in the move Bexd1 or Nh3f4 we # want to know frank = None ffile = None if notat and notat[0] in reprRank: frank = int(notat[0])-1 notat = notat[1:] if notat and notat[0] in reprFile: ffile = ord(notat[0]) - ord("a") notat = notat[1:] if notat and notat[0] in reprRank: frank = int(notat[0])-1 notat = notat[1:] # we know all we want return newMove(frank*8+ffile, tcord, flag) if piece == PAWN: if (ffile is not None) and ffile != FILE(tcord): # capture if color == WHITE: fcord = tcord-7 if ffile > FILE(tcord) else tcord-9 else: fcord = tcord+7 if ffile < FILE(tcord) else tcord+9 else: if color == WHITE: pawns = board.boards[WHITE][PAWN] fcord = tcord-16 if RANK(tcord)==3 and not (pawns & fileBits[FILE(tcord)] & rankBits[2]) else tcord-8 else: pawns = board.boards[BLACK][PAWN] fcord = tcord+16 if RANK(tcord)==4 and not (pawns & fileBits[FILE(tcord)] & rankBits[5]) else tcord+8 return newMove(fcord, tcord, flag) else: if board.pieceCount[color][piece] == 1: # we have only one from this kind if piece, so: fcord = firstBit(board.boards[color][piece]) return newMove(fcord, tcord, flag) else: # We find all pieces who could have done it. (If san was legal, there should # never be more than one) moves = genPieceMoves(board, piece, tcord) if len(moves) == 1: return moves.pop() else: for move in moves: f = FCORD(move) if frank != None and frank != RANK(f): continue if ffile != None and ffile != FILE(f): continue board_clone = board.clone() board_clone.applyMove(move) if board_clone.opIsChecked(): continue return move errstring = "no %s is able to move to %s" % (reprPiece[piece], reprCord[tcord]) raise ParsingError, (san, errstring, board.asFen())
def parseSAN(board, san): """ Parse a Short/Abbreviated Algebraic Notation string """ notat = san color = board.color if notat == "--": return newMove(board.kings[color], board.kings[color], NULL_MOVE) if notat[-1] in "+#": notat = notat[:-1] # If '++' was used in place of # if notat[-1] == "+": notat = notat[:-1] flag = NORMAL_MOVE # If last char is a piece char, we assue it the promote char c = notat[-1] if c in "KQRBNSMFkqrbnsmf.": c = c.lower() if c == "k" and board.variant != SUICIDECHESS and board.variant != GIVEAWAYCHESS: raise ParsingError(san, _("invalid promoted piece"), board.asFen()) elif c == "." and board.variant in (CAMBODIANCHESS, MAKRUKCHESS, SITTUYINCHESS): # temporary hack for xboard bug flag = QUEEN_PROMOTION else: flag = chr2Sign[c] + 2 if notat[-2] == "=": notat = notat[:-2] else: notat = notat[:-1] if len(notat) < 2: raise ParsingError(san, _("the move needs a piece and a cord"), board.asFen()) if notat[0] in "O0o": fcord = board.ini_kings[color] flag = KING_CASTLE if notat in ("O-O", "0-0", "o-o", "OO", "00", "oo") else QUEEN_CASTLE side = flag - QUEEN_CASTLE if FILE(fcord) == 3 and board.variant in (WILDCASTLECHESS, WILDCASTLESHUFFLECHESS): side = 0 if side == 1 else 1 if board.variant == FISCHERRANDOMCHESS: tcord = board.ini_rooks[color][side] else: tcord = board.fin_kings[color][side] return newMove(fcord, tcord, flag) # LAN is not allowed in pgn spec, but sometimes it occures if "-" in notat: notat = notat.replace("-", "") if "@" in notat: tcord = cordDic[notat[-2:]] if notat[0].islower(): # Sjeng-ism piece = chr2Sign[notat[0]] else: piece = chrU2Sign[notat[0]] return newMove(piece, tcord, DROP) # standard piece letters if notat[0] in "QRBKNSMF": piece = chrU2Sign[notat[0]] notat = notat[1:] # unambigious lowercase piece letters elif notat[0] in "qrknsm": piece = chr2Sign[notat[0]] notat = notat[1:] # a lowercase bishop letter or a pawn capture elif notat[0] == "b" and len(notat) > 2 and board.variant == NORMALCHESS: tcord = cordDic[notat[-2:]] trank = int(notat[-1]) # if from and to lines are not neighbours -> Bishop if abs(ord(notat[0]) - ord(notat[-2])) > 1: piece = chr2Sign[notat[0]] notat = notat[1:] # if from and to lines are neighbours (or the same) but to is an empty square # which can't be en-passant square target -> Bishop elif board.arBoard[tcord] == EMPTY and ((color == BLACK and trank != 3) or (color == WHITE and trank != 6)): piece = chr2Sign[notat[0]] notat = notat[1:] # elif "ba3", "bc3" ,"ba6", "bc6" # these can be Bishop or Pawn moves, but we don't try to introspect them (sorry) else: piece = PAWN else: piece = PAWN if notat[-1] in "18" and flag == NORMAL_MOVE and board.variant != SITTUYINCHESS: raise ParsingError( san, _("promotion move without promoted piece is incorrect"), board.asFen()) if "x" in notat: notat, tcord = notat.split("x") if tcord not in cordDic: raise ParsingError(san, _("the captured cord (%s) is incorrect") % tcord, board.asFen()) tcord = cordDic[tcord] if piece == PAWN: # If a pawn is attacking an empty cord, we assue it an enpassant if board.arBoard[tcord] == EMPTY: if (color == BLACK and 2 * 8 <= tcord < 3 * 8) or (color == WHITE and 5 * 8 <= tcord < 6 * 8): flag = ENPASSANT else: raise ParsingError( san, _("pawn capture without target piece is invalid"), board.asFen()) else: if not notat[-2:] in cordDic: raise ParsingError(san, _("the end cord (%s) is incorrect") % notat[-2:], board.asFen()) tcord = cordDic[notat[-2:]] notat = notat[:-2] # In suicide promoting to king is valid, so # more than 1 king per side can exist ! if board.variant != SUICIDECHESS and board.variant != GIVEAWAYCHESS and piece == KING: return newMove(board.kings[color], tcord, flag) # If there is any extra location info, like in the move Bexd1 or Nh3f4 we # want to know frank = None ffile = None if notat and notat[0] in reprRank: frank = int(notat[0]) - 1 notat = notat[1:] if notat and notat[0] in reprFile: ffile = ord(notat[0]) - ord("a") notat = notat[1:] if notat and notat[0] in reprRank: frank = int(notat[0]) - 1 notat = notat[1:] # we know all we want return newMove(frank * 8 + ffile, tcord, flag) if piece == PAWN: if (ffile is not None) and ffile != FILE(tcord): # capture if color == WHITE: fcord = tcord - 7 if ffile > FILE(tcord) else tcord - 9 else: fcord = tcord + 7 if ffile < FILE(tcord) else tcord + 9 else: if color == WHITE: pawns = board.boards[WHITE][PAWN] # In horde white pawns on first rank may move two squares also if board.variant == HORDECHESS and RANK(tcord) == 2 and not ( pawns & fileBits[FILE(tcord)] & rankBits[1]): fcord = tcord - 16 else: fcord = tcord - 16 if RANK(tcord) == 3 and not ( pawns & fileBits[FILE(tcord)] & rankBits[2]) else tcord - 8 else: pawns = board.boards[BLACK][PAWN] fcord = tcord + 16 if RANK(tcord) == 4 and not ( pawns & fileBits[FILE(tcord)] & rankBits[5]) else tcord + 8 if board.variant == SITTUYINCHESS and flag == QUEEN_PROMOTION and \ (pawns & fileBits[FILE(tcord)] & rankBits[RANK(tcord)]): return newMove(tcord, tcord, flag) return newMove(fcord, tcord, flag) else: if board.pieceCount[color][piece] == 1: # we have only one from this kind if piece, so: fcord = firstBit(board.boards[color][piece]) return newMove(fcord, tcord, flag) else: # We find all pieces who could have done it. (If san was legal, there should # never be more than one) moves = genPieceMoves(board, piece, tcord) if len(moves) == 1: return moves.pop() else: for move in moves: f = FCORD(move) if frank is not None and frank != RANK(f): continue if ffile is not None and ffile != FILE(f): continue board_clone = board.clone() board_clone.applyMove(move) if board_clone.opIsChecked(): continue return move errstring = "no %s is able to move to %s" % (reprPiece[piece], reprCord[tcord]) raise ParsingError(san, errstring, board.asFen())