Spaces:
Build error
Build error
| from enum import IntEnum | |
| PIECE_TYPES = 14 | |
| _COLUMN_TO_INT = { | |
| c:num for c, num in zip("abcdefgh", range(0, 8)) | |
| } | |
| class Player(IntEnum): | |
| WHITE = 1 | |
| BLACK = 2 | |
| class Piece(IntEnum): | |
| PAWN_W = 1 | |
| PAWN_B = 2 | |
| ROOK_W = 3 | |
| ROOK_B = 4 | |
| KNIGHT_W = 5 | |
| KNIGHT_B = 6 | |
| BISHOP_W = 7 | |
| BISHOP_B = 8 | |
| QUEEN_W = 9 | |
| QUEEN_B = 10 | |
| KING_W = 11 | |
| KING_B = 12 | |
| EN_PASSANT_SPACE = 13 | |
| def from_fen(cls, letter: chr, default = None): | |
| return { | |
| 'p': Piece.PAWN_B, | |
| 'P': Piece.PAWN_W, | |
| 'r': Piece.ROOK_B, | |
| 'R': Piece.ROOK_W, | |
| 'n': Piece.KNIGHT_B, | |
| 'N': Piece.KNIGHT_W, | |
| 'b': Piece.BISHOP_B, | |
| 'B': Piece.BISHOP_W, | |
| 'q': Piece.QUEEN_B, | |
| 'Q': Piece.QUEEN_W, | |
| 'k': Piece.KING_B, | |
| 'K': Piece.KING_W, | |
| }.get(letter, default) | |
| class BitBoard: | |
| def __init__(self): | |
| self.boards = [0]*PIECE_TYPES | |
| self.next_to_move = Player.WHITE | |
| self.castling_status = set() # Just add pieces for QUEEN_B, etc. | |
| self.halfstep_count = 0 | |
| self.fullstep_count = 0 | |
| def make_move(self, mv: str): | |
| # NOTE: This does not handle castling yet and probably screws up clearing en-passant. | |
| # Convert the pair into from/to. | |
| from_x = _COLUMN_TO_INT[mv[0].lower()] | |
| from_y = int(mv[1])-1 | |
| to_x = _COLUMN_TO_INT[mv[2].lower()] | |
| to_y = int(mv[3])-1 | |
| promote_to = None | |
| if len(mv) > 3: | |
| promote_to = Piece.from_fen(mv[4]) | |
| # Clear piece at moving spot. | |
| moving_piece = self.get_piece(from_x, from_y) | |
| self.clear_piece(from_x, from_y) | |
| if promote_to is None: | |
| self.set_piece(to_x, to_y, moving_piece) | |
| else: | |
| self.set_piece(to_x, to_y, promote_to) | |
| def from_fen(cls, fen_string: str): | |
| # rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1 | |
| # 4r3/1k6/pp3r2/1b2P2p/3R1p2/P1R2P2/1P4PP/6K1 w - - 0 35 | |
| new_board = cls() | |
| board_layout, current_player, castle_status, en_passant, halfmove_clock, fullmove_clock = fen_string.split(" ") | |
| board_rows = board_layout.split("/") | |
| for y, row in enumerate(reversed(board_rows)): | |
| x = 0 | |
| while row: | |
| if row[0].isdigit(): | |
| x += int(row[0]) | |
| else: | |
| piece = Piece.from_fen(row[0]) | |
| new_board.set_piece(x, y, piece) | |
| x += 1 | |
| row = row[1:] | |
| if current_player == 'w': | |
| new_board.next_to_move = Player.WHITE | |
| elif current_player == 'b': | |
| new_board.next_to_move = Player.BLACK | |
| else: | |
| raise Exception(f"Bad parse. Starting player unrecognized: {current_player}") | |
| # Parse castling here. | |
| for character in castle_status: | |
| new_board.castling_status.add(Piece.from_fen(character)) | |
| # Parse en_passant here | |
| new_board.halfstep_count = int(halfmove_clock) | |
| new_board.fullmove_count = int(fullmove_clock) | |
| return new_board | |
| def get_piece(self, x, y): | |
| assert(0 <= x < 8 and 0 <= y < 8) | |
| # X and Y should be in the range 0-7 inclusive. | |
| # a1 is bottom-left, bit zero. | |
| # h8 is the top-right, bit 63. | |
| idx = (1 << x+y*8) | |
| for i in range(1, PIECE_TYPES): | |
| if self.boards[i] & idx: | |
| return Piece(i) | |
| return None | |
| def set_piece(self, x, y, piece: Piece, clear_previous=True): | |
| assert(x >= 0 and x < 8 and y >= 0 and y < 8) | |
| idx = (1 << x+y*8) | |
| if clear_previous: | |
| self.clear_piece(x, y) | |
| self.boards[int(piece)] |= idx | |
| def clear_piece(self, x, y): | |
| assert (x >= 0 and x < 8 and y >= 0 and y < 8) | |
| idx = (1 << x + y * 8) | |
| clear_mask = 0xFFFF_FFFF_FFFF_FFFF & ~idx | |
| for i in range(0, PIECE_TYPES): | |
| self.boards[i] &= clear_mask | |