def makeMove(self, board1, move, board2): log.debug("ICPlayer.makemove: id(self)=%d self=%s move=%s board1=%s board2=%s" % \ (id(self), self, move, board1, board2)) if board2 and not self.gamemodel.isObservationGame(): # TODO: Will this work if we just always use CASTLE_SAN? castle_notation = CASTLE_KK if board2.variant == FISCHERRANDOMCHESS: castle_notation = CASTLE_SAN self.connection.bm.sendMove(toAN(board2, move, castleNotation=castle_notation)) item = self.queue.get(block=True) try: if item == "del": raise PlayerIsDead if item == "int": raise TurnInterrupt ply, sanmove = item if ply < board1.ply: # This should only happen in an observed game board1 = self.gamemodel.getBoardAtPly(max(ply - 1, 0)) log.debug("ICPlayer.makemove: id(self)=%d self=%s from queue got: ply=%d sanmove=%s" % \ (id(self), self, ply, sanmove)) try: move = parseSAN(board1, sanmove) log.debug("ICPlayer.makemove: id(self)=%d self=%s parsed move=%s" % \ (id(self), self, move)) except ParsingError as err: raise return move finally: log.debug("ICPlayer.makemove: id(self)=%d self=%s returning move=%s" % \ (id(self), self, move)) self.okqueue.put("ok")
def __sendAnalyze (self, inverse=False): if inverse and self.board.board.opIsChecked(): # Many engines don't like positions able to take down enemy # king. Therefore we just return the "kill king" move # automaticaly self.emit("analyze", [([toAN(self.board, getMoveKillingKing(self.board))], MATE_VALUE-1, "")]) return def stop_analyze (): if self.engineIsAnalyzing: print("exit", file=self.engine) # Some engines (crafty, gnuchess) doesn't respond to exit command # we try to force them to stop with an empty board fen print("setboard 8/8/8/8/8/8/8/8 w - - 0 1", file=self.engine) self.engineIsAnalyzing = False print("post", file=self.engine) print("analyze", file=self.engine) self.engineIsAnalyzing = True if self.analysis_timer is not None: self.analysis_timer.cancel() self.analysis_timer.join() self.analysis_timer = Timer(conf.get("max_analysis_spin", 3), stop_analyze) self.analysis_timer.start()
def _searchNow(self, ponderhit=False): log.debug("_searchNow: self.needBestmove=%s ponderhit=%s self.board=%s" % ( self.needBestmove, ponderhit, self.board), extra={"task": self.defname}) commands = [] if ponderhit: commands.append("ponderhit") elif self.mode == NORMAL: commands.append("position %s" % self.uciPosition) if self.strength <= 3: commands.append("go depth %d" % self.strength) else: if self.moves > 0: commands.append("go wtime %d winc %d btime %d binc %d movestogo %s" % ( self.wtime, self.incr, self.btime, self.incr, self.moves)) else: commands.append("go wtime %d winc %d btime %d binc %d" % ( self.wtime, self.incr, self.btime, self.incr)) else: print("stop", file=self.engine) if self.mode == INVERSE_ANALYZING: if self.board.board.opIsChecked(): # Many engines don't like positions able to take down enemy # king. Therefore we just return the "kill king" move # automaticaly self.emit("analyze", [([toAN( self.board, getMoveKillingKing(self.board))], MATE_VALUE - 1, "")]) return commands.append("position fen %s" % self.board.asFen()) else: commands.append("position %s" % self.uciPosition) # commands.append("go infinite") move_time = int(conf.get("max_analysis_spin", 3)) * 1000 commands.append("go movetime %s" % move_time) if self.hasOption("MultiPV") and self.multipvSetting > 1: self.multipvExpected = min(self.multipvSetting, legalMoveCount(self.board)) else: self.multipvExpected = 1 self.analysis = [None] * self.multipvExpected if self.needBestmove: self.commands.append(commands) log.debug("_searchNow: self.needBestmove==True, appended to self.commands=%s" % self.commands, extra={"task": self.defname}) else: for command in commands: print(command, file=self.engine) if getStatus(self.board)[ 1] != WON_MATE: # XXX This looks fishy. self.needBestmove = True self.readyForStop = True
def emit_move_signal(self, cord0, cord1, promotion=None): # Game end can change cord0 to None while dragging a piece if cord0 is None: return color = self.view.model.boards[-1].color board = self.view.model.getBoardAtPly(self.view.shown, self.view.shown_variation_idx) # Ask player for which piece to promote into. If this move does not # include a promotion, QUEEN will be sent as a dummy value, but not used if promotion is None and board[ cord0].sign == PAWN and cord1.cord in board.PROMOTION_ZONE[ color]: if self.variant.variant == SITTUYINCHESS: # no promotion allowed if we have queen if board.board.boards[color][QUEEN]: promotion = None else: # promotion is always optional promotion = self.getPromotion() if promotion is None and cord0 == cord1: # if don't want in place promotion return elif len(self.variant.PROMOTIONS) == 1: promotion = lmove.PROMOTE_PIECE(self.variant.PROMOTIONS[0]) else: if conf.get("autoPromote", False): promotion = lmove.PROMOTE_PIECE(QUEEN_PROMOTION) else: promotion = self.getPromotion() if promotion is None: # Put back pawn moved be d'n'd self.view.runAnimation(redraw_misc=False) return if cord0.x < 0 or cord0.x > self.FILES - 1: move = Move(lmovegen.newMove(board[cord0].piece, cord1.cord, DROP)) else: move = Move(cord0, cord1, board, promotion) if (self.view.model.curplayer.__type__ == LOCAL or self.view.model.examined) and \ self.view.shownIsMainLine() and \ self.view.model.boards[-1] == board and \ self.view.model.status == RUNNING: if self.setup_position: self.emit("piece_moved", (cord0, cord1), board[cord0].color) else: self.emit("piece_moved", move, color) if self.view.model.examined: self.view.model.connection.bm.sendMove(toAN(board, move)) else: if board.board.next is None and not self.view.shownIsMainLine(): self.view.model.add_move2variation( board, move, self.view.shown_variation_idx) self.view.shown += 1 else: new_vari = self.view.model.add_variation(board, (move, )) self.view.setShownBoard(new_vari[-1])
def __usermove (self, board, move): if self.features["usermove"]: self.engine.write("usermove ") if self.features["san"]: print(toSAN(board, move), file=self.engine) else: cn = CASTLE_KK if board.variant == FISCHERRANDOMCHESS: cn = CASTLE_SAN print(toAN(board, move, short=True, castleNotation=cn), file=self.engine)
def __usermove(self, board, move): if self.features["usermove"]: self.engine.write("usermove ") if self.features["san"]: print(toSAN(board, move), file=self.engine) else: castle_notation = CASTLE_KK if board.variant == FISCHERRANDOMCHESS: castle_notation = CASTLE_SAN print( toAN(board, move, short=True, castleNotation=castle_notation), file=self.engine, )
def emit_move_signal(self, cord0, cord1, promotion=None): # Game end can change cord0 to None while dragging a piece if cord0 is None: return color = self.view.model.boards[-1].color board = self.view.model.getBoardAtPly(self.view.shown, self.view.shown_variation_idx) # Ask player for which piece to promote into. If this move does not # include a promotion, QUEEN will be sent as a dummy value, but not used if promotion is None and board[cord0].sign == PAWN and \ cord1.cord in board.PROMOTION_ZONE[color]: if self.variant.variant == SITTUYINCHESS: # no promotion allowed if we have queen if board.board.boards[color][QUEEN]: promotion = None else: # promotion is always optional promotion = self.getPromotion() if promotion is None and cord0 == cord1: # if don't want in place promotion return elif len(self.variant.PROMOTIONS) == 1: promotion = lmove.PROMOTE_PIECE(self.variant.PROMOTIONS[0]) else: if conf.get("autoPromote", False): promotion = lmove.PROMOTE_PIECE(QUEEN_PROMOTION) else: promotion = self.getPromotion() if promotion is None: # Put back pawn moved be d'n'd self.view.runAnimation(redraw_misc=False) return if cord0.x < 0 or cord0.x > self.FILES - 1: move = Move(lmovegen.newMove(board[cord0].piece, cord1.cord, DROP)) else: move = Move(cord0, cord1, board, promotion) if (self.view.model.curplayer.__type__ == LOCAL or self.view.model.examined) and \ self.view.shownIsMainLine() and \ self.view.model.boards[-1] == board and \ self.view.model.status == RUNNING: # emit move if self.setup_position: self.emit("piece_moved", (cord0, cord1), board[cord0].color) else: self.emit("piece_moved", move, color) if self.view.model.examined: self.view.model.connection.bm.sendMove(toAN(board, move)) else: play_or_add_move(self.view, board, move)
def __sendAnalyze(self, inverse=False): if inverse and self.board.board.opIsChecked(): # Many engines don't like positions able to take down enemy # king. Therefore we just return the "kill king" move # automaticaly self.emit("analyze", [(self.board.ply, [toAN( self.board, getMoveKillingKing(self.board))], MATE_VALUE - 1, "1", "")]) return print("post", file=self.engine) print("analyze", file=self.engine) self.engineIsAnalyzing = True if not conf.get("infinite_analysis"): loop = asyncio.get_event_loop() loop.call_later(conf.get("max_analysis_spin"), self.__stop_analyze)
def __sendAnalyze(self, inverse=False): if inverse and self.board.board.opIsChecked(): # Many engines don't like positions able to take down enemy # king. Therefore we just return the "kill king" move # automaticaly self.emit("analyze", [(self.board.ply, [toAN( self.board, getMoveKillingKing(self.board))], MATE_VALUE - 1, "", "")]) return print("post", file=self.engine) print("analyze", file=self.engine) self.engineIsAnalyzing = True if not conf.get("infinite_analysis", False): loop = asyncio.get_event_loop() loop.call_later(conf.get("max_analysis_spin", 3), self.__stop_analyze)
def makeMove(self, board1, move, board2): log.debug("ICPlayer.makemove: id(self)=%d self=%s move=%s board1=%s board2=%s" % ( id(self), self, move, board1, board2)) if board2 and not self.gamemodel.isObservationGame(): # TODO: Will this work if we just always use CASTLE_SAN? castle_notation = CASTLE_KK if board2.variant == FISCHERRANDOMCHESS: castle_notation = CASTLE_SAN self.connection.bm.sendMove(toAN(board2, move, castleNotation=castle_notation)) item = yield from self.gamemodel.ficsgame.queue.get() try: if item == "del": raise PlayerIsDead gameno, ply, curcol, lastmove, fen, wname, bname, wms, bms = item self.gamemodel.onBoardUpdate(gameno, ply, curcol, lastmove, fen, wname, bname, wms, bms) if curcol == self.color and ply == self.gamemodel.ply: item = yield from self.gamemodel.ficsgame.queue.get() if item == "del": raise PlayerIsDead gameno, ply, curcol, lastmove, fen, wname, bname, wms, bms = item self.gamemodel.onBoardUpdate(gameno, ply, curcol, lastmove, fen, wname, bname, wms, bms) if self.turn_interrupt: self.turn_interrupt = False raise TurnInterrupt if ply < board1.ply: # This should only happen in an observed game board1 = self.gamemodel.getBoardAtPly(max(ply - 1, 0)) log.debug("ICPlayer.makemove: id(self)=%d self=%s from queue got: ply=%d sanmove=%s" % ( id(self), self, ply, lastmove)) try: move = parseSAN(board1, lastmove) log.debug("ICPlayer.makemove: id(self)=%d self=%s parsed move=%s" % ( id(self), self, move)) except ParsingError: raise return move finally: log.debug("ICPlayer.makemove: id(self)=%d self=%s returning move=%s" % (id(self), self, move))
def makeMove(self, board1, move, board2): log.debug("ICPlayer.makemove: id(self)=%d self=%s move=%s board1=%s board2=%s" % ( id(self), self, move, board1, board2)) if board2 and not self.gamemodel.isObservationGame(): # TODO: Will this work if we just always use CASTLE_SAN? castle_notation = CASTLE_KK if board2.variant == FISCHERRANDOMCHESS: castle_notation = CASTLE_SAN self.connection.bm.sendMove(toAN(board2, move, castleNotation=castle_notation)) # wait for fics to send back our move we made item = yield from self.move_queue.get() item = yield from self.move_queue.get() try: if item == "end": raise GameEnded elif item == "del": raise PlayerIsDead gameno, ply, curcol, lastmove, fen, wname, bname, wms, bms = item self.gamemodel.onBoardUpdate(gameno, ply, curcol, lastmove, fen, wname, bname, wms, bms) if self.turn_interrupt: self.turn_interrupt = False raise TurnInterrupt if ply < board1.ply: # This should only happen in an observed game board1 = self.gamemodel.getBoardAtPly(max(ply - 1, 0)) log.debug("ICPlayer.makemove: id(self)=%d self=%s from queue got: ply=%d sanmove=%s" % ( id(self), self, ply, lastmove)) try: move = parseSAN(board1, lastmove) log.debug("ICPlayer.makemove: id(self)=%d self=%s parsed move=%s" % ( id(self), self, move)) except ParsingError: raise return move finally: log.debug("ICPlayer.makemove: id(self)=%d self=%s returning move=%s" % (id(self), self, move))
def makeMove(self, board1, move, board2): log.debug( "ICPlayer.makemove: id(self)=%d self=%s move=%s board1=%s board2=%s" % (id(self), self, move, board1, board2)) if board2 and not self.gamemodel.isObservationGame(): # TODO: Will this work if we just always use CASTLE_SAN? castle_notation = CASTLE_KK if board2.variant == FISCHERRANDOMCHESS: castle_notation = CASTLE_SAN self.connection.bm.sendMove( toAN(board2, move, castleNotation=castle_notation)) item = self.queue.get(block=True) try: if item == "del": raise PlayerIsDead if item == "int": raise TurnInterrupt ply, sanmove = item if ply < board1.ply: # This should only happen in an observed game board1 = self.gamemodel.getBoardAtPly(max(ply - 1, 0)) log.debug( "ICPlayer.makemove: id(self)=%d self=%s from queue got: ply=%d sanmove=%s" % (id(self), self, ply, sanmove)) try: move = parseSAN(board1, sanmove) log.debug( "ICPlayer.makemove: id(self)=%d self=%s parsed move=%s" % (id(self), self, move)) except ParsingError: raise return move finally: log.debug( "ICPlayer.makemove: id(self)=%d self=%s returning move=%s" % (id(self), self, move)) self.okqueue.put("ok")
def key_pressed(self, keyname): if keyname in "PNBRQKMFSOox12345678abcdefgh": self.keybuffer += keyname elif keyname == "minus": self.keybuffer += "-" elif keyname == "at": self.keybuffer += "@" elif keyname == "equal": self.keybuffer += "=" elif keyname == "Return": color = self.view.model.boards[-1].color board = self.view.model.getBoardAtPly( self.view.shown, self.view.shown_variation_idx) try: move = parseAny(board, self.keybuffer) except ParsingError: self.keybuffer = "" return if validate(board, move): if ((self.view.model.curplayer.__type__ == LOCAL or self.view.model.examined) and self.view.shownIsMainLine() and self.view.model.boards[-1] == board and self.view.model.status == RUNNING): # emit move self.emit("piece_moved", move, color) if self.view.model.examined: self.view.model.connection.bm.sendMove( toAN(board, move)) else: self.play_or_add_move(board, move) self.keybuffer = "" elif keyname == "BackSpace": self.keybuffer = self.keybuffer[:-1] if self.keybuffer else ""
def key_pressed(self, keyname): if keyname in "PNBRQKMFSOox12345678abcdefgh": self.keybuffer += keyname elif keyname == "minus": self.keybuffer += "-" elif keyname == "at": self.keybuffer += "@" elif keyname == "equal": self.keybuffer += "=" elif keyname == "Return": color = self.view.model.boards[-1].color board = self.view.model.getBoardAtPly( self.view.shown, self.view.shown_variation_idx) try: move = parseAny(board, self.keybuffer) except: self.keybuffer = "" return if validate(board, move): if (self.view.model.curplayer.__type__ == LOCAL or self.view.model.examined) and \ self.view.shownIsMainLine() and \ self.view.model.boards[-1] == board and \ self.view.model.status == RUNNING: # emit move self.emit("piece_moved", move, color) if self.view.model.examined: self.view.model.connection.bm.sendMove(toAN(board, move)) else: play_or_add_move(self.view, board, move) self.keybuffer = "" elif keyname == "BackSpace": self.keybuffer = self.keybuffer[:-1] if self.keybuffer else ""
def __sendAnalyze(self, inverse=False): if inverse and self.board.board.opIsChecked(): # Many engines don't like positions able to take down enemy # king. Therefore we just return the "kill king" move # automaticaly self.emit("analyze", [([toAN( self.board, getMoveKillingKing(self.board))], MATE_VALUE - 1, "")]) return def stop_analyze(): if self.engineIsAnalyzing: print("exit", file=self.engine) # Some engines (crafty, gnuchess) doesn't respond to exit command # we try to force them to stop with an empty board fen print("setboard 8/8/8/8/8/8/8/8 w - - 0 1", file=self.engine) self.engineIsAnalyzing = False print("post", file=self.engine) print("analyze", file=self.engine) self.engineIsAnalyzing = True loop = asyncio.get_event_loop() loop.call_later(conf.get("max_analysis_spin", 3), stop_analyze)
def _searchNow(self, ponderhit=False): log.debug( "_searchNow: self.needBestmove=%s ponderhit=%s self.board=%s" % (self.needBestmove, ponderhit, self.board), extra={"task": self.defname}) commands = [] if ponderhit: commands.append("ponderhit") elif self.mode == NORMAL: commands.append("position %s" % self.uciPosition) if self.strength <= 3: commands.append("go depth %d" % self.strength) else: if self.moves > 0: commands.append( "go wtime %d winc %d btime %d binc %d movestogo %s" % (self.wtime, self.incr, self.btime, self.incr, self.moves)) else: commands.append( "go wtime %d winc %d btime %d binc %d" % (self.wtime, self.incr, self.btime, self.incr)) else: print("stop", file=self.engine) if self.mode == INVERSE_ANALYZING: if self.board.board.opIsChecked(): # Many engines don't like positions able to take down enemy # king. Therefore we just return the "kill king" move # automaticaly self.emit("analyze", [(self.board.ply, [ toAN(self.board, getMoveKillingKing(self.board)) ], MATE_VALUE - 1, "1", "")]) return commands.append("position fen %s" % self.board.asFen()) else: commands.append("position %s" % self.uciPosition) if self.analysis_depth is not None: commands.append("go depth %s" % self.analysis_depth) elif conf.get("infinite_analysis"): commands.append("go infinite") else: move_time = int(conf.get("max_analysis_spin")) * 1000 commands.append("go movetime %s" % move_time) if self.hasOption("MultiPV") and self.multipvSetting > 1: self.multipvExpected = min(self.multipvSetting, legalMoveCount(self.board)) else: self.multipvExpected = 1 self.analysis = [None] * self.multipvExpected if self.needBestmove: self.commands.append(commands) log.debug( "_searchNow: self.needBestmove==True, appended to self.commands=%s" % self.commands, extra={"task": self.defname}) else: for command in commands: print(command, file=self.engine) if getStatus(self.board)[1] != WON_MATE: # XXX This looks fishy. self.needBestmove = True self.readyForStop = True
def _moveToUCI(self, board, move): castle_notation = CASTLE_KK if board.variant == FISCHERRANDOMCHESS: castle_notation = CASTLE_KR return toAN(board, move, short=True, castleNotation=castle_notation)
def makeMove(self, board1, move, board2): log.debug("ICPlayer.makemove: id(self)=%d self=%s move=%s board1=%s board2=%s" % ( id(self), self, move, board1, board2)) if board2 and not self.gamemodel.isObservationGame(): # TODO: Will this work if we just always use CASTLE_SAN? castle_notation = CASTLE_KK if board2.variant == FISCHERRANDOMCHESS: castle_notation = CASTLE_SAN self.connection.bm.sendMove(toAN(board2, move, castleNotation=castle_notation)) # wait for fics to send back our move we made item = yield from self.move_queue.get() log.debug("ICPlayer.makeMove: fics sent back the move we made") item = yield from self.move_queue.get() try: if item == "end": log.debug("ICPlayer.makeMove got: end") raise GameEnded elif item == "del": log.debug("ICPlayer.makeMove got: del") raise PlayerIsDead elif item == "stm": log.debug("ICPlayer.makeMove got: stm") self.turn_interrupt = False raise TurnInterrupt elif item == "fen": log.debug("ICPlayer.makeMove got: fen") self.turn_interrupt = False raise TurnInterrupt elif item == "pass": log.debug("ICPlayer.makeMove got: pass") self.pass_interrupt = False raise PassInterrupt gameno, ply, curcol, lastmove, fen, wname, bname, wms, bms = item log.debug("ICPlayer.makeMove got: %s %s %s %s" % (gameno, ply, curcol, lastmove)) self.gamemodel.onBoardUpdate(gameno, ply, curcol, lastmove, fen, wname, bname, wms, bms) if self.turn_interrupt: self.turn_interrupt = False raise TurnInterrupt if self.pass_interrupt: self.pass_interrupt = False raise PassInterrupt if ply < board1.ply: # This should only happen in an observed game board1 = self.gamemodel.getBoardAtPly(max(ply - 1, 0)) log.debug("ICPlayer.makemove: id(self)=%d self=%s from queue got: ply=%d sanmove=%s" % ( id(self), self, ply, lastmove)) try: move = parseSAN(board1, lastmove) log.debug("ICPlayer.makemove: id(self)=%d self=%s parsed move=%s" % ( id(self), self, move)) except ParsingError: raise return move finally: log.debug("ICPlayer.makemove: id(self)=%d self=%s returning move=%s" % (id(self), self, move))
def emit_move_signal(self, cord0, cord1, promotion=None): # Game end can change cord0 to None while dragging a piece if cord0 is None: return gating = None board = self.getBoard() color = board.color # Ask player for which piece to promote into. If this move does not # include a promotion, QUEEN will be sent as a dummy value, but not used if promotion is None and board[cord0].sign == PAWN and \ cord1.cord in board.PROMOTION_ZONE[color] and \ self.variant.variant != SITTUYINCHESS: if len(self.variant.PROMOTIONS) == 1: promotion = lmove.PROMOTE_PIECE(self.variant.PROMOTIONS[0]) elif self.variant.variant == LIGHTBRIGADECHESS: promotion = lmove.PROMOTE_PIECE(QUEEN_PROMOTION if color == WHITE else KNIGHT_PROMOTION) else: if conf.get("autoPromote"): promotion = lmove.PROMOTE_PIECE(QUEEN_PROMOTION) else: promotion = self.getPromotion() if promotion is None: # Put back pawn moved be d'n'd self.view.runAnimation(redraw_misc=False) return if promotion is None and board[cord0].sign == PAWN and \ cord0.cord in board.PROMOTION_ZONE[color] and \ self.variant.variant == SITTUYINCHESS: # no promotion allowed if we have queen if board.board.boards[color][QUEEN]: promotion = None # in place promotion elif cord1.cord in board.PROMOTION_ZONE[color]: promotion = lmove.PROMOTE_PIECE(QUEEN_PROMOTION) # queen move promotion (but not a pawn capture!) elif board[cord1] is None and (cord0.cord + cord1.cord) % 2 == 1: promotion = lmove.PROMOTE_PIECE(QUEEN_PROMOTION) holding = board.board.holding[color] if self.variant.variant == SCHESS: moved = board[cord0].sign hawk = holding[HAWK] > 0 elephant = holding[ELEPHANT] > 0 if (hawk or elephant) and cord0.cord in iterBits( board.board.virgin[color]): castling = moved == KING and abs(cord0.x - cord1.x) == 2 gating = self.getGating(castling, hawk, elephant) if gating is not None: if gating in (HAWK_GATE_AT_ROOK, ELEPHANT_GATE_AT_ROOK): side = 0 if cord0.x - cord1.x == 2 else 1 rcord = board.board.ini_rooks[color][side] move = Move(lmovegen.newMove(rcord, cord0.cord, gating)) else: move = Move(lmovegen.newMove(cord0.cord, cord1.cord, gating)) elif cord0.x < 0 or cord0.x > self.FILES - 1: move = Move(lmovegen.newMove(board[cord0].piece, cord1.cord, DROP)) else: move = Move(cord0, cord1, board, promotion) if (self.view.model.curplayer.__type__ == LOCAL or self.view.model.examined) and \ self.view.shownIsMainLine() and \ self.view.model.boards[-1] == board and \ self.view.model.status == RUNNING: # emit move if self.setup_position: self.emit("piece_moved", (cord0, cord1), board[cord0].color) else: self.emit("piece_moved", move, color) if self.view.model.examined: self.view.model.connection.bm.sendMove(toAN(board, move)) else: self.play_or_add_move(board, move)
def _moveToUCI (self, board, move): cn = CASTLE_KK if board.variant == FISCHERRANDOMCHESS: cn = CASTLE_KR return toAN(board, move, short=True, castleNotation=cn)