def move_to_tensor(move): return [ chess.square_file(move.from_square) / 8, chess.square_rank(move.from_square) / 8, chess.square_file(move.to_square) / 8, chess.square_rank(move.to_square) / 8 ]
def get_model_num_from_move(self, move): directions = [-1, 1, -7, 7, -8, 8, -9, 9] knight_moves = [17, 15, 10, 6, -17, -15, -10, -6] diff = move.to_square - move.from_square # Underpromotion if move.promotion != 5 and move.promotion != None: directions = [7, 8, 9] for d in directions: if abs(diff) == d: direction = d _type = 3 * directions.index(direction) + move.promotion - 2 + 64 # Knight move elif diff in knight_moves and square_distance(move.to_square, move.from_square) == 2: _type = 56 + knight_moves.index(diff) else: direction = 0 for d in reversed(directions): if np.sign(diff) == np.sign(d) and diff % d == 0: direction = d break if square_rank(move.to_square) == square_rank(move.from_square): direction = np.sign(diff) * 1 elif square_file(move.to_square) == square_file(move.from_square): direction = np.sign(diff) * 8 distance = abs(diff / direction) _type = 7 * directions.index(direction) + (distance - 1) move_num = move.from_square * (7 * 8 + 8 + 9) + _type return int(move_num)
def paint_pieces(self, painter, board, flip, piece_size): last_move = self.game.get_last_move() for s in chess.SQUARES: x = piece_size * ((7 - chess.square_file(s)) if flip else chess.square_file(s)) y = piece_size * (chess.square_rank(s) if flip else (7 - chess.square_rank(s))) p = board.piece_at(s) if p: if s == self.from_square and self.mouseMovePos: x = self.mouseMovePos.x() - self.offset_x y = self.mouseMovePos.y() - self.offset_y # center images if show_ascii: sym = p.unicode_symbol() painter.drawText(x, y, piece_size, piece_size, Qt.AlignCenter, sym) else: piece_index = PIECE_IMAGE_INDEX[p.piece_type] + (0 if p.color else 6) img = QImage.scaled(self.piece_map[piece_index], piece_size, piece_size, Qt.KeepAspectRatio) offset_x = (piece_size-img.width())/2 offset_y = (piece_size-img.height())/2 painter.drawImage(x+offset_x, y+offset_y, img) if last_move and (last_move.from_square == s or last_move.to_square == s): painter.drawRect(x, y, piece_size, piece_size)
def create_move_encoding(self, move): assert isinstance(move, chess.Move) index = 0 move_key = (chess.square_rank(move.to_square) - chess.square_rank(move.from_square), chess.square_file(move.to_square) - chess.square_file(move.from_square), 0 if move.promotion is None else move.promotion) init_vector = [0 for x in range(64 * 73)] if move is None or move == chess.Move.null(): return init_vector if self.board.piece_at(move.from_square).piece_type == chess.KNIGHT \ or move.promotion is not None and move.promotion != chess.QUEEN: index = 56 + self.special_move_indices[move_key] else: king_steps = chess.square_distance(move.from_square, move.to_square) unit_vector = (move_key[0], move_key[1]) if king_steps != 0: unit_vector = (move_key[0] / king_steps, move_key[1] / king_steps) if unit_vector != (0, 0): index = 7 * self.unit_moves[unit_vector] + king_steps - 1 print(index) init_vector[move.from_square * 73 + index] = 1 return init_vector
def is_forward(self): """From the perspective of the current player, i.e. current player's pawns move up the board.""" from_rank = chess.square_rank(self.move.from_square) to_rank = chess.square_rank(self.move.to_square) if self.aug.current_color == chess.WHITE: return from_rank < to_rank return from_rank > to_rank
def get_random_board(num_pcs=None): board = chess.Board(None) lst = list(range(64)) random.shuffle(lst) kingw = lst.pop() kingb = lst.pop() piecew = chess.Piece(chess.KING, True) board_map = {kingw: piecew} pieceb = chess.Piece(chess.KING, False) board_map[kingb] = pieceb if num_pcs is None: num_pcs = random.randint(0, len(lst)) for pos in lst[0:num_pcs]: min_pcs = 1 if chess.square_rank(pos) == 0 or chess.square_rank(pos) == 7: min_pcs = 2 p = random.randint(min_pcs, 5) col = random.randint(0, 1) piece = chess.Piece(p, col) board_map[pos] = piece board.set_piece_map(board_map) return board
def dovetail_mate(puzzle: Puzzle) -> bool: node = puzzle.game.end() board = node.board() king = board.king(not puzzle.pov) assert king is not None assert isinstance(node, ChildNode) if square_file(king) in [0, 7] or square_rank(king) in [0, 7]: return False queen_square = node.move.to_square if (util.moved_piece_type(node) != QUEEN or square_file(queen_square) == square_file(king) or square_rank(queen_square) == square_rank(king) or square_distance(queen_square, king) > 1): return False for square in [ s for s in SquareSet(chess.BB_ALL) if square_distance(s, king) == 1 ]: if square == queen_square: continue attackers = list(board.attackers(puzzle.pov, square)) if attackers == [queen_square]: if board.piece_at(square): return False elif attackers: return False return True
def box_corner(self, available_squares, square): if chess.square_file(square) == 0 or chess.square_file(square) == 7: if (available_squares[1] and available_squares[2]) in self.board.attacks(self.board.king(self.winner())): print('Box mate') elif chess.square_rank(square) == 0 or chess.square_rank(square) == 7: if (available_squares[0] and available_squares[1]) in self.board.attacks(self.board.king(self.winner())): print('Box mate')
def utility1(board, print_board=False): global node_count node_count += 1 factor = (1 if (board.turn == chess.WHITE) else -1) if board.is_variant_win(): return 1000 * factor elif board.is_variant_loss(): return -1000 * factor fen = Counter(board.board_fen()) wk_rank = chess.square_rank(board.pieces(chess.KING, chess.WHITE).pop()) bk_rank = chess.square_rank(board.pieces(chess.KING, chess.BLACK).pop()) rankdiff = (wk_rank - bk_rank) material = (9 * (fen['Q'] - fen['q']) + 5 * (fen['R'] - fen['r']) + 3 * (fen['B'] - fen['b'] + fen['N'] - fen['n'])) w_mobility = len(list(board.legal_moves)) board.push(chess.Move.null()) b_mobility = len(list(board.legal_moves)) board.pop() mobility = (w_mobility - b_mobility) * (-1 if board.turn == chess.BLACK else 1) #mobility = 0 # utility from white's perspective utility = material + 5 * rankdiff + 0.2 * mobility return utility * factor
def click(self, x, y): global focus, turn x = int(x / 50) y = int(y / 50) # moving the piece that is in my team if focus != None and focus.p.color == turn: # wheter the move was legal or not if focus.Move(x * 50, y * 50, s) == True: # moving a turn turn = not turn focus = None return # unselecting when move is illigal but staying in function to see if player selected something else focus = None # on which soldier did the player click and is it on his team for square in chess.SQUARES: f = board.piece_at(square) if f != None and chess.square_file( square ) == x and 7 - chess.square_rank(square) == y and f.color == turn: focus = soldier(chess.square_file(square), 7 - chess.square_rank(square), f) break
def is_short_diagonal(from_sq, to_sq): ''' return True if diagonal is short ''' if (((chess.square_rank(to_sq) <= 3) and (chess.square_file(to_sq) <= 3)) or ((chess.square_rank(to_sq) > 3) and (chess.square_file(to_sq) > 3))): # This means that King is in lower-left quadrant or # in upper-right quadrant # In this quadrants NW_SE_diagonals are shortest if nw_se_diagonal(from_sq, to_sq): return True elif sw_ne_diagonal(from_sq, to_sq): return False else: # pragma: no cover raise KeyError else: # Other two quadrants. And diagonals are vise-versa. if nw_se_diagonal(from_sq, to_sq): return False elif sw_ne_diagonal(from_sq, to_sq): return True else: # pragma: no cover raise KeyError
def moveMatch(candidateMove): if move[1] == 0: match = board.is_castling(candidateMove) elif move[1] == 1: match = board.is_capture(candidateMove) and chess.square_rank( candidateMove.from_square) < chess.square_rank( candidateMove.to_square) # forward capture (y-axis) elif move[1] == 2: match = board.is_capture(candidateMove) and chess.square_rank( candidateMove.from_square) > chess.square_rank( candidateMove.to_square) # backwards capture elif move[1] == 3: match = board.is_capture(candidateMove) and chess.square_rank( candidateMove.from_square) == chess.square_rank( candidateMove.to_square) # horizontal capture elif move[1] == 4: match = board.is_into_check(candidateMove) elif move[1] == 5: match = chess.square_rank( candidateMove.from_square) < chess.square_rank( candidateMove.to_square) # forward candidateMove elif move[1] == 6: match = chess.square_rank( candidateMove.from_square) > chess.square_rank( candidateMove.to_square) elif move[1] == 7: match = chess.square_file( candidateMove.from_square) > chess.square_file( candidateMove.to_square) elif move[1] == 8: match = chess.square_file( candidateMove.from_square) < chess.square_file( candidateMove.to_square) return match
def utility(self, board, player): #info = self.engine.analyse(board, chess.engine.Limit(time=0.001)) # nodes = 100 #score = info['score'].pov(player).score() t = time.time() fen = Counter(board.board_fen()) wk_rank = chess.square_rank( board.pieces(chess.KING, chess.WHITE).pop()) bk_rank = chess.square_rank( board.pieces(chess.KING, chess.BLACK).pop()) rankdiff = (wk_rank - bk_rank) material = 9 * (fen['Q'] - fen['q']) + 5 * ( fen['R'] - fen['r']) + 3 * (fen['B'] - fen['b'] + fen['N'] - fen['n']) + (fen['P'] - fen['p']) w_mobility = len(list(board.legal_moves)) board.push(chess.Move.null()) b_mobility = len(list(board.legal_moves)) board.pop() mobility = (w_mobility - b_mobility) * (-1 if board.turn == chess.BLACK else 1) # utility from white's perspective utility = material + 5 * rankdiff + 0.2 * mobility utility *= (1 if (player == chess.WHITE) else -1) #print(board) #print(f"WHITE? {player == chess.WHITE} Util: {utility} Depth: {len(board.move_stack)} Elapsed: {time.time() - t}") #import pdb; pdb.set_trace() return utility
def BoardEncode(self): """Converts a board to numpy array representation (8,8,21) same as Alphazero with history_length = 1 (only one board)""" array = np.zeros((8, 8, 26), dtype=int) for square, piece in self.board.piece_map().items(): rank, file = chess.square_rank(square), chess.square_file(square) piece_type, color = piece.piece_type, piece.color # The first six planes encode the pieces of the active player, # the following six those of the active player's opponent. Since # this class always stores boards oriented towards the white player, # White is considered to be the active player here. offset = 0 if color == chess.WHITE else 6 offset1 = 6 if color == chess.WHITE else 18 # Chess enumerates piece types beginning with one, which we have # to account for idx = piece_type - 1 # We use now a for loop to save the squares attacked by the piece we just found for i in list(self.board.attacks(square)): array[chess.square_rank(i),chess.square_file(i),idx+offset1] = 1 array[rank, file, idx + offset] = 1 # Repetition counters array[:, :, 24] = self.board.is_repetition(2) array[:, :, 25] = self.board.is_repetition(3) #return array #def observation(self, board: chess.Board) -> np.array: #Converts chess.Board observations instance to numpy arrays. #self._history.push(board) #history = self._history.view(orientation=board.turn) history = array meta = np.zeros( shape=(8 ,8, 7), dtype=int ) # Active player color meta[:, :, 0] = int(self.board.turn) # Total move count meta[:, :, 1] = self.board.fullmove_number # Active player castling rights meta[:, :, 2] = self.board.has_kingside_castling_rights(self.board.turn) meta[:, :, 3] = self.board.has_queenside_castling_rights(self.board.turn) # Opponent player castling rights meta[:, :, 4] = self.board.has_kingside_castling_rights(not self.board.turn) meta[:, :, 5] = self.board.has_queenside_castling_rights(not self.board.turn) # No-progress counter meta[:, :, 6] = self.board.halfmove_clock observation = np.concatenate([history, meta], axis=-1) return np.transpose(observation, (2, 0, 1))
def back_rank_mate(puzzle: Puzzle) -> bool: node = puzzle.game.end() board = node.board() king = board.king(not puzzle.pov) assert king is not None assert isinstance(node, ChildNode) back_rank = 7 if puzzle.pov else 0 if board.is_checkmate() and square_rank(king) == back_rank: squares = SquareSet.from_square(king + (-8 if puzzle.pov else 8)) if puzzle.pov: if chess.square_file(king) < 7: squares.add(king - 7) if chess.square_file(king) > 0: squares.add(king - 9) else: if chess.square_file(king) < 7: squares.add(king + 9) if chess.square_file(king) > 0: squares.add(king + 7) for square in squares: piece = board.piece_at(square) if piece is None or piece.color == puzzle.pov or board.attackers( puzzle.pov, square): return False return any( square_rank(checker) == back_rank for checker in board.checkers()) return False
def fill_piece(iplanes, ix, bb, b, flip_rank, flip_file): """ Compute piece placement and attack plane for a given piece type """ if AUX_INP: abb = 0 squares = chess.SquareSet(bb) for sq in squares: abb = abb | b.attacks_mask(sq) f = chess.square_file(sq) r = chess.square_rank(sq) if flip_rank: r = RANK_U - r if flip_file: f = FILE_U - f iplanes[r, f, ix + 12] = 1.0 squares = chess.SquareSet(abb) for sq in squares: f = chess.square_file(sq) r = chess.square_rank(sq) if flip_rank: r = RANK_U - r if flip_file: f = FILE_U - f iplanes[r, f, ix] = 1.0 else: squares = chess.SquareSet(bb) for sq in squares: f = chess.square_file(sq) r = chess.square_rank(sq) if flip_rank: r = RANK_U - r if flip_file: f = FILE_U - f iplanes[r, f, ix] = 1.0
def ladder(self, available_squares, square): # If all the remaining squares are attacked by a queen or a rook ladder = False checkmater_file = 0 checkmater_rank = 0 attacker_file = 0 attacker_rank = 0 for i in available_squares[1:4]: for attacker in self.board.attackers(self.winner(), i): if square != i and (str(self.board.piece_at(attacker)).upper() == 'Q' or str(self.board.piece_at(attacker)).upper() == 'R'): # For some reason, it would print 'Ladder mate' 3 times. This just bypasses it. attacker_file = chess.square_file(attacker) attacker_rank = chess.square_rank(attacker) ladder = True if (available_squares[0] and available_squares[4]) in self.board.attacks(square): checkmater_file = chess.square_file(square) checkmater_rank = chess.square_rank(square) ladder = True else: ladder = False if (((checkmater_file == 0 and attacker_file == 1) or (checkmater_file == 7 and attacker_file == 6)) or ((checkmater_rank == 0 and attacker_rank == 1) or (checkmater_rank == 7 and attacker_rank == 6))): if ladder: print('Ladder mate')
def adjust_move_tensor(self, move_tensor): def move_tensor_diff(tensor_1, tensor_2): """ Compute the difference between two move tensors. """ source_diff = scipy.spatial.distance.euclidean( tensor_1[:2], tensor_2[:2]) target_diff = scipy.spatial.distance.euclidean( tensor_1[2:], tensor_2[2:]) return source_diff**2 + target_diff tensor_1 = [x * 8 for x in move_tensor[0][:-1]] legal_moves = list(self.legal_moves) min_diff = 139 # Maximum diff between two moves. for i, legal_move in enumerate(legal_moves): tensor_2 = [ chess.square_file(legal_move.from_square), chess.square_rank(legal_move.from_square), chess.square_file(legal_move.to_square), chess.square_rank(legal_move.to_square) ] diff = move_tensor_diff(tensor_1, tensor_2) if diff < min_diff: min_diff = diff move_i = i return (legal_moves[move_i], min_diff / 139)
def ladder_corner(self, available_squares, square): # 8 plausible ways to get ladder mated on a corner ladder = False if chess.square_file(square) == 0 or chess.square_file(square) == 7: for i in available_squares[:2]: for attacker in self.board.attackers(self.winner(), i): if (square != i and (str(self.board.piece_at(attacker)).upper() == 'Q' or str(self.board.piece_at(attacker)).upper() == 'R') and (chess.square_file(attacker) == 1 or chess.square_file(attacker) == 6)): ladder = True elif chess.square_rank(square) == 0 or chess.square_rank(square) == 7: for i in available_squares[1:]: for attacker in self.board.attackers(self.winner(), i): if (square != i and (str(self.board.piece_at(attacker)).upper() == 'Q' or str(self.board.piece_at(attacker)).upper() == 'R') and (chess.square_rank(attacker) == 1 or chess.square_rank(attacker) == 6)): ladder = True # Prevents from printing it out multiple times if ladder: print('Ladder mate')
def encode_knight_move(move): def encode_delta(delta): return 0 if delta >= 0 else 1 file_delta = chess.square_file(move.to_square) - chess.square_file(move.from_square) rank_delta = chess.square_rank(move.to_square) - chess.square_rank(move.from_square) move_type = 0 if abs(rank_delta) > abs(file_delta) else 4 # Provides 8 unique encodings without 8 ifs return move_type + 2 * encode_delta(file_delta) + encode_delta(rank_delta)
def draw_pieces(screen, state): if state.PLAYING_SIDE is chess.WHITE: for i in range(len(chess.SQUARES)): square = chess.SQUARES[len(chess.SQUARES) - 1 - i] x = chess.square_file(square) y = chess.square_rank(square) piece = state.board.piece_at(i) if piece is not None: bpiece = pieces[piece.symbol()] else: bpiece = None if bpiece != None: screen.blit( IMAGES[bpiece], pg.Rect(WIDTH - SQUARE_SIZE - (x * SQUARE_SIZE), y * SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE)) else: for i in range(len(chess.SQUARES)): square = chess.SQUARES[i] x = chess.square_file(square) y = chess.square_rank(square) piece = state.board.piece_at(i) if piece is not None: bpiece = pieces[piece.symbol()] else: bpiece = None if bpiece != None: screen.blit( IMAGES[bpiece], pg.Rect(WIDTH - SQUARE_SIZE - (x * SQUARE_SIZE), y * SQUARE_SIZE, SQUARE_SIZE, SQUARE_SIZE))
def dir_and_dist(square1, square2): rank1 = chess.square_rank(square1) rank2 = chess.square_rank(square2) file1 = chess.square_file(square1) file2 = chess.square_file(square2) horiz = 'W' if file1 > file2 else 'E' vert = 'S' if rank1 > rank2 else 'N' filedist = abs(file1 - file2) rankdist = abs(rank1 - rank2) diag = filedist == rankdist straight = filedist == 0 or rankdist == 0 knight = (filedist == 1 and rankdist == 2) or (filedist == 2 and rankdist == 1) far = 'F' if rankdist == 2 else 'S' if diag: dist = filedist return {'dir': '{}{}'.format(vert, horiz), 'dist': dist} elif straight: queen_dir = horiz if filedist != 0 else vert dist = filedist if filedist != 0 else rankdist return {'dir': '{}'.format(queen_dir), 'dist': dist} elif knight: # The distance should not be needed when handling a knight move, -1 is an indicator return {'dir': '{}{}{}'.format(vert, horiz, far), 'dist': -1}
def detect_backward_pawns(board, color): backward_pawn_count = 0 pawns = list(board.pieces(chess.PAWN, color)) for i in pawns: is_backward = True for j in pawns: if i == j: continue else: # If any other pawn is found at the behind of this pawn, then is_backward value is setted as False # For white and black players, procedure is reversed if color == chess.WHITE and chess.square_rank( i) > chess.square_rank(j): is_backward = False break elif color == chess.BLACK and chess.square_rank( i) < chess.square_rank(j): is_backward = False break if is_backward == True: backward_pawn_count += 1 return backward_pawn_count
def get_mobility(board, pieces, num): x = [] pieces = list(pieces) for i in range(num): if len(pieces) - 1 < i: x.append([0, 0, 0, 0]) continue mir = 8 mar = 0 mif = 8 maf = 0 index = pieces[i] ora = chess.square_rank(index) ofa = chess.square_file(index) for attack in board.attacks(index): sr = chess.square_rank(attack) sf = chess.square_file(attack) if sr < mir: mir = sr if sr > mar: mar = sr if sf < mif: mif = sf if sf > maf: maf = sf x.append([ (mir - ora) / 7., (mar - ora) / 7., (mif - ofa) / 7., (maf - ofa) / 7. ]) return x
def __init__(self, index): self.tile_index = index self.tile_name = chess.SQUARE_NAMES[index] self.tile_ofRowIndex = chess.square_rank(index) self.tile_ofRowName = chess.square_rank(index) self.tile_ofColumnIndex = chess.square_file(index)
def crowning(self, to): if self.map.piece_at(to).piece_type == chess.PAWN: if self.map.turn: if chess.square_rank(to) == 8: return True else: if chess.square_rank(to) == 1: return True return False
def move_to_cat(board, move): pieceFile = chess.square_file(move.from_square) pieceType = board.piece_type_at(move.from_square) if pieceType == 3: piece = 0 elif pieceType == 2: piece = 1 elif pieceType == 4: piece = 2 if pieceType == 1 and pieceFile < 2: piece = 3 if pieceType == 1 and 1 < pieceFile < 4: piece = 4 if pieceType == 1 and 3 < pieceFile < 6: piece = 5 if pieceType == 1 and 5 < pieceFile < 8: piece = 6 elif pieceType == 6: piece = 7 elif pieceType == 5: piece = 8 pieces = [0, 0, 0, 0, 0, 0, 0, 0, 0] pieces[piece] = 1 moves = [0, 0, 0, 0, 0, 0, 0, 0, 0] def checking(board, move): board.push(move) check = board.is_check() board.pop() return check if board.is_castling(move): moves[0] = 1 elif board.is_capture(move) and chess.square_rank( move.from_square) < chess.square_rank(move.to_square): moves[1] = 1 elif board.is_capture(move) and chess.square_rank( move.from_square) > chess.square_rank(move.to_square): moves[2] = 1 elif board.is_capture(move) and chess.square_rank( move.from_square) == chess.square_rank(move.to_square): moves[3] = 1 if checking(board, move): moves[4] = 1 if chess.square_rank(move.from_square) < chess.square_rank( move.to_square): # forward move: moves[5] = 1 if chess.square_rank(move.from_square) > chess.square_rank(move.to_square): moves[6] = 1 if chess.square_file(move.from_square) > chess.square_file(move.to_square): moves[7] = 1 if chess.square_file(move.from_square) < chess.square_file(move.to_square): moves[8] = 1 return pieces + moves
def _hw_detect_move_player(self): """ Detect move from player We compare the expected occupancy with the actual occupancy. If two squares are different then we set the square where the clients piece used to be as the from square and the other square as the too square. Using this we create a candidate move (from_square, to_square). If this is a legal move then we will place this move in the output field. Note that the chess library will detect any other side effects from the move like captures, promotions and en passant. The internal board will be updated correctly and the diff will be marked on the board. """ draw_offer = False while not self.game_is_over(): occupancy = self._hwi.get_occupancy() diff = self._diff_occupancy_board(occupancy) self._hwi.mark_squares(diff) offers = self._hwi.game_end_offers() if offers is HardwareInterface.Offer.RESIGN: self._resigned = True return engine.PlayResult(None, None, resigned=True) if offers is HardwareInterface.Offer.DRAW: draw_offer = True # Detect all changed squares squares = [] for file in range(8): for rank in range(8): if diff[file][rank]: squares.append(chess.square(file, rank)) if len(squares) == 2: # Try to convert changed squares to legal move with self._board_lock: # Promotion not implemented if self._board.piece_at(squares[0]) is not None and \ self._board.piece_at(squares[0]).color == self.color: move = chess.Move(squares[0], squares[1]) elif self._board.piece_at(squares[1]) is not None and \ self._board.piece_at(squares[1]).color == self.color: move = chess.Move(squares[1], squares[0]) else: continue if self._board.piece_at(move.from_square).piece_type == chess.PAWN and \ ((self.color == chess.WHITE and chess.square_rank(move.to_square) == 7) or (self.color == chess.BLACK and chess.square_rank(move.to_square) == 0)): move.promotion = chess.QUEEN move_is_legal = move in self._board.legal_moves # save result to not use two locks if move_is_legal and move.promotion is not None: move.promotion = self._hwi.promotion_piece() if move_is_legal: with self._board_lock: self._board.push(move) with self._playResult_lock: self._output_playResult = engine.PlayResult(move, None, draw_offered=draw_offer) return self._update_display() sleep(SLEEP_TIME)
def get_kings_distance_euclidean(board): king_white_square = board.king(True) king_black_square = board.king(False) king_white_file = chess.square_file(king_white_square) king_white_rank = chess.square_rank(king_white_square) king_black_file = chess.square_file(king_black_square) king_black_rank = chess.square_rank(king_black_square) x = abs(king_white_file - king_black_file) y = abs(king_white_rank - king_black_rank) return math.sqrt(x**2 + y**2)
def unpack(move: chess.Move) -> Tuple[int, int, int, int]: """Converts chess.Move instances into move coordinates.""" from_rank = chess.square_rank(move.from_square) from_file = chess.square_file(move.from_square) to_rank = chess.square_rank(move.to_square) to_file = chess.square_file(move.to_square) return from_rank, from_file, to_rank, to_file
def board(board=None, squares=None, flipped=False, coordinates=True, lastmove=None, check=None, arrows=(), size=None, style=None): """ Renders a board with pieces and/or selected squares as an SVG image. :param board: A :class:`chess.BaseBoard` for a chessboard with pieces or ``None`` (the default) for a chessboard without pieces. :param squares: A :class:`chess.SquareSet` with selected squares. :param flipped: Pass ``True`` to flip the board. :param coordinates: Pass ``False`` to disable coordinates in the margin. :param lastmove: A :class:`chess.Move` to be highlighted. :param check: A square to be marked as check. :param arrows: A list of :class:`~chess.svg.Arrow` objects like ``[chess.svg.Arrow(chess.E2, chess.E4)]`` or a list of tuples like ``[(chess.E2, chess.E4)]``. An arrow from a square pointing to the same square is drawn as a circle, like ``[(chess.E2, chess.E2)]``. :param size: The size of the image in pixels (e.g., ``400`` for a 400 by 400 board) or ``None`` (the default) for no size limit. :param style: A CSS stylesheet to include in the SVG image. >>> import chess >>> import chess.svg >>> >>> from IPython.display import SVG >>> >>> board = chess.Board("8/8/8/8/4N3/8/8/8 w - - 0 1") >>> squares = board.attacks(chess.E4) >>> SVG(chess.svg.board(board=board, squares=squares)) # doctest: +SKIP .. image:: ../docs/Ne4.svg """ margin = 20 if coordinates else 0 svg = _svg(8 * SQUARE_SIZE + 2 * margin, size) if style: ET.SubElement(svg, "style").text = style defs = ET.SubElement(svg, "defs") if board: for color in chess.COLORS: for piece_type in chess.PIECE_TYPES: if board.pieces_mask(piece_type, color): defs.append(ET.fromstring(PIECES[chess.Piece(piece_type, color).symbol()])) if squares: defs.append(ET.fromstring(XX)) if check is not None: defs.append(ET.fromstring(CHECK_GRADIENT)) for square, bb in enumerate(chess.BB_SQUARES): file_index = chess.square_file(square) rank_index = chess.square_rank(square) x = (file_index if not flipped else 7 - file_index) * SQUARE_SIZE + margin y = (7 - rank_index if not flipped else rank_index) * SQUARE_SIZE + margin cls = ["square", "light" if chess.BB_LIGHT_SQUARES & bb else "dark"] if lastmove and square in [lastmove.from_square, lastmove.to_square]: cls.append("lastmove") fill_color = DEFAULT_COLORS[" ".join(cls)] cls.append(chess.SQUARE_NAMES[square]) ET.SubElement(svg, "rect", { "x": str(x), "y": str(y), "width": str(SQUARE_SIZE), "height": str(SQUARE_SIZE), "class": " ".join(cls), "stroke": "none", "fill": fill_color, }) if square == check: ET.SubElement(svg, "rect", { "x": str(x), "y": str(y), "width": str(SQUARE_SIZE), "height": str(SQUARE_SIZE), "class": "check", "fill": "url(#check_gradient)", }) # Render pieces. if board is not None: piece = board.piece_at(square) if piece: ET.SubElement(svg, "use", { "xlink:href": "#%s-%s" % (chess.COLOR_NAMES[piece.color], chess.PIECE_NAMES[piece.piece_type]), "transform": "translate(%d, %d)" % (x, y), }) # Render selected squares. if squares is not None and squares & bb: ET.SubElement(svg, "use", { "xlink:href": "#xx", "x": str(x), "y": str(y), }) if coordinates: for file_index, file_name in enumerate(chess.FILE_NAMES): x = (file_index if not flipped else 7 - file_index) * SQUARE_SIZE + margin svg.append(_text(file_name, x, 0, SQUARE_SIZE, margin)) svg.append(_text(file_name, x, margin + 8 * SQUARE_SIZE, SQUARE_SIZE, margin)) for rank_index, rank_name in enumerate(chess.RANK_NAMES): y = (7 - rank_index if not flipped else rank_index) * SQUARE_SIZE + margin svg.append(_text(rank_name, 0, y, margin, SQUARE_SIZE)) svg.append(_text(rank_name, margin + 8 * SQUARE_SIZE, y, margin, SQUARE_SIZE)) for tail, head in arrows: tail_file = chess.square_file(tail) tail_rank = chess.square_rank(tail) head_file = chess.square_file(head) head_rank = chess.square_rank(head) xtail = margin + (tail_file + 0.5 if not flipped else 7.5 - tail_file) * SQUARE_SIZE ytail = margin + (7.5 - tail_rank if not flipped else tail_rank + 0.5) * SQUARE_SIZE xhead = margin + (head_file + 0.5 if not flipped else 7.5 - head_file) * SQUARE_SIZE yhead = margin + (7.5 - head_rank if not flipped else head_rank + 0.5) * SQUARE_SIZE if (head_file, head_rank) == (tail_file, tail_rank): ET.SubElement(svg, "circle", { "cx": str(xhead), "cy": str(yhead), "r": str(SQUARE_SIZE * 0.9 / 2), "stroke-width": str(SQUARE_SIZE * 0.1), "stroke": "#888", "fill": "none", "opacity": "0.5", }) else: marker_size = 0.75 * SQUARE_SIZE marker_margin = 0.1 * SQUARE_SIZE dx, dy = xhead - xtail, yhead - ytail hypot = math.hypot(dx, dy) shaft_x = xhead - dx * (marker_size + marker_margin) / hypot shaft_y = yhead - dy * (marker_size + marker_margin) / hypot xtip = xhead - dx * marker_margin / hypot ytip = yhead - dy * marker_margin / hypot ET.SubElement(svg, "line", { "x1": str(xtail), "y1": str(ytail), "x2": str(shaft_x), "y2": str(shaft_y), "stroke": "#888", "stroke-width": str(SQUARE_SIZE * 0.2), "opacity": "0.5", "stroke-linecap": "butt", "class": "arrow", }) marker = [] marker.append((xtip, ytip)) marker.append((shaft_x + dy * 0.5 * marker_size / hypot, shaft_y - dx * 0.5 * marker_size / hypot)) marker.append((shaft_x - dy * 0.5 * marker_size / hypot, shaft_y + dx * 0.5 * marker_size / hypot)) ET.SubElement(svg, "polygon", { "points": " ".join(str(x) + "," + str(y) for x, y in marker), "fill": "#888", "opacity": "0.5", "class": "arrow", }) return ET.tostring(svg).decode("utf-8")