def get_legal_moves(cls, state): legal_moves = np.full(cls.BOARD_SHAPE, False) friendly_index = 0 if cls.is_player_1_turn(state) else 1 enemy_index = 1 - friendly_index for i, j in iter_product(cls.BOARD_SHAPE): if np.any(state[i, j, :2] == 1): continue for di, dj, in DIRECTIONS_8: p_x, p_y = i + di, j + dj if not (cls.is_valid(p_x, p_y) and state[p_x, p_y, enemy_index] == 1): continue p_x += di p_y += dj while cls.is_valid(p_x, p_y) and state[p_x, p_y, enemy_index] == 1: p_x += di p_y += dj if cls.is_valid(p_x, p_y) and state[p_x, p_y, friendly_index] == 1: legal_moves[i, j] = True break return legal_moves
def generator(): # This function will double count: # i.e. each valid winning line will be returned twice: once forwards and once backwards increasing = np.arange(MultiTicTacToe.W) decreasing = increasing[::-1] for coord_states in iter_product(MultiTicTacToe.D * [MultiTicTacToe.W + 2]): # coords_states is a tuple with length D, each element indicates whether # that dimension is a specific constant value, increasing, or decreasing indices = np.zeros((MultiTicTacToe.W, MultiTicTacToe.D), dtype=int) stationary = True for i, coord_state in enumerate(coord_states): if coord_state < MultiTicTacToe.W: vals = np.full((MultiTicTacToe.D, ), coord_state) elif coord_state == MultiTicTacToe.W: vals = increasing stationary = False else: vals = decreasing stationary = False indices[:, i] = vals # if none of the indices are increasing or decreasing, then all spots correspond to the exact same square if not stationary: yield indices
def get_from_to_move(cls, state, move, friendly_slice=None): if friendly_slice is None: friendly_slice, *_ = cls.get_stats(state) from_squares = [] # squares that went from friendly to empty to_squares = [] # squares that went from not friendly to friendly for i, j, in iter_product(cls.BOARD_SHAPE): if np.any(state[i, j, friendly_slice] == 1) and np.all( move[i, j, :12] == 0): # insert to the start of the list if its a king if state[i, j, friendly_slice][0] == 1: from_squares.insert(0, (i, j)) else: from_squares.append((i, j)) elif np.all(state[i, j, friendly_slice] == 0) and np.any( move[i, j, friendly_slice] == 1): # insert to the start of the list if its a king if move[i, j, friendly_slice][0] == 1: to_squares.insert(0, (i, j)) else: to_squares.append((i, j)) if (len(from_squares) == 1 and len(to_squares) == 1) or (len(from_squares) == 2 and len(to_squares) == 2): return from_squares[0][0], from_squares[0][1], to_squares[0][ 0], to_squares[0][1] else: raise Exception( f'Invalid number of piece moves: from_squares = {from_squares}, to_squares = {to_squares}' )
def draw(self, position=None): """ Draws the given position to the screen. If no position is given, then the current position will be drawn. """ if position is not None: self.board.set_state(position) else: position = self.board.get_state() indices = self.GameClass.get_img_index_representation(position) changed_indices = indices != self.last_indices self.last_indices = indices for i, j in iter_product(self.GameClass.BOARD_SHAPE): img = self.imgs[indices[i, j]] if PygameUI.FLIP_LR: x = 64 * (self.GameClass.COLUMNS - 1 - i) else: x = 64 * i y = 64 * j if self.checkerboard: self.canvas.blit( self.light_square if (i + j) % 2 == 0 else self.dark_square, (y, x)) if img is not None: self.canvas.blit(img, (y, x)) # Note pygame inverts x and y # noinspection PyUnresolvedReferences if changed_indices[i, j]: self.canvas.blit(self.highlight_img, (y, x)) pygame.display.flip() # Not sure why this is necessary to get the screen to update self.flush()
def get_possible_moves(cls, state): moves = [] for i, j in iter_product(TicTacToe.BOARD_SHAPE): if np.all(state[i, j, :2] == 0): move = cls.null_move(state) move[i, j, :2] = [1, 0] if cls.is_player_1_turn(state) else [0, 1] moves.append(move) return moves
def check_win(pieces): # TODO: update this for Gomoku, the rest of the class is properly implemented for i, j in iter_product(Gomoku.BOARD_SHAPE): if i < Gomoku.W - 5 and np.all(pieces[i:i + 5, j]): return True if j < Gomoku.W - 5 and np.all(pieces[i, j:j + 5]): return True return False
def check_win(cls, pieces): # Check vertical for i, j in iter_product((cls.ROWS, cls.COLUMNS - 3)): if np.all(pieces[i, j:j + 4]): return True # Check horizontal for i, j in iter_product((cls.ROWS - 3, cls.COLUMNS)): if np.all(pieces[i:i + 4, j]): return True # Check diagonals flipped_pieces = np.fliplr(pieces) for i, j in iter_product((cls.ROWS - 3, cls.COLUMNS - 3)): if np.all(np.diag(pieces[i:i + 4, j:j + 4])) or np.all( np.diag(flipped_pieces[i:i + 4, j:j + 4])): return True return False
def get_king_pos(cls, state, player_slice): king_pos = None for i, j in iter_product(cls.BOARD_SHAPE): if state[i, j, player_slice][0] == 1: if king_pos is None: king_pos = i, j else: raise ValueError('Multiple kings found!') if king_pos is None: raise ValueError('No king found!') return king_pos
def get_possible_moves(cls, state): moves = [] for coords in iter_product(MultiTicTacToe.BOARD_SHAPE): if state[coords + (0, )] == 0 and state[coords + (1, )] == 0: move = cls.null_move(state) if cls.is_player_1_turn(state): move[coords + (0, )] = 1 else: move[coords + (1, )] = 1 moves.append(move) return moves
def encode_board_bytes(cls, state): # https://codegolf.stackexchange.com/a/19446 bitboard = np.sum(state[:, :, :12], axis=-1) == 1 is_white_turn = cls.is_player_1_turn(state) pieces = [] for i, j in iter_product(Chess.BOARD_SHAPE): if not bitboard[i, j]: continue # KQRBNP, King to move, Rook that can castle or Pawn that moved 2 squares, same 8 for black where = np.argwhere(state[i, j, :12])[0, 0] piece = where % 6 is_white = where < 6 if piece == 0 and is_white == is_white_turn: # if piece is a king whose turn it is pieces.append(6 if is_white_turn else 14) continue if piece == 2 and i == (7 if is_white else 0): # if piece is a rook on home rank if i == 0 and j == 0 and state[0, 2, -2] == 1: pieces.append(15) continue if i == 0 and j == 7 and state[0, 6, -2] == 1: pieces.append(15) continue if i == 7 and j == 0 and state[7, 2, -2] == 1: pieces.append(7) continue if i == 7 and j == 7 and state[7, 6, -2] == 1: pieces.append(7) continue if piece == 5 and is_white != is_white_turn and i == (4 if is_white else 3) \ and state[(5 if is_white else 2), j, -2] == 1: pieces.append(7 if is_white else 15) continue pieces.append(piece + (0 if is_white else 8)) board_bytes = [] for row in bitboard: value = 0 for i, square in enumerate(row): if square: value += (1 << i) board_bytes.append(value) if np.sum(bitboard) % 2 == 1: board_bytes.append(pieces[0]) pieces = pieces[1:] for i in range(0, len(pieces), 2): board_bytes.append(16 * pieces[i] + pieces[i + 1]) return bytes(board_bytes)
def get_legal_moves(cls, state): legal_moves = np.full(cls.MOVE_SHAPE, False) player_index = 0 if cls.is_player_1_turn(state) else 1 for i, j in iter_product(cls.BOARD_SHAPE): if state[i, j, player_index] == 1: for p_x, p_y in cls.shoot(state, i, j): partial_move = cls.null_move(state) partial_move[i, j, player_index] = 0 partial_move[p_x, p_y, player_index] = 1 p_direction, p_distance = cls.parse(p_x - i, p_y - j) for t_x, t_y in cls.shoot(partial_move, p_x, p_y): t_direction, t_distance = cls.parse( t_x - p_x, t_y - p_y) legal_moves[i, j, p_direction, p_distance, t_direction, t_distance] = True return legal_moves
def get_possible_moves(cls, state): moves = [] player_index = 0 if cls.is_player_1_turn(state) else 1 for i, j in iter_product(cls.BOARD_SHAPE): if state[i, j, player_index] == 1: for p_x, p_y in cls.shoot(state, i, j): partial_move = cls.null_move(state) partial_move[i, j, player_index] = 0 partial_move[p_x, p_y, player_index] = 1 for t_x, t_y in cls.shoot(partial_move, p_x, p_y): full_move = np.copy( partial_move ) # Don't use null_move because turn was already switched above full_move[t_x, t_y, 2] = 1 moves.append(full_move) return moves
def get_possible_moves(cls, state): moves = [] if cls.is_player_1_turn(state): hit_miss_slice = slice(1, 3) opponent_pieces_slice = 3 else: hit_miss_slice = slice(4, 6) opponent_pieces_slice = 0 for i, j in iter_product(Battleship.BOARD_SHAPE): if np.all(state[i, j, hit_miss_slice] == 0): move = cls.null_move(state) move[i, j, hit_miss_slice] = [ 1, 0 ] if move[i, j, opponent_pieces_slice] else [0, 1] moves.append(move) return moves
def get_possible_moves(cls, state): moves = [] friendly_index = 0 if cls.is_player_1_turn(state) else 1 enemy_index = 1 - friendly_index player_piece = [enemy_index, friendly_index] for i, j in iter_product(cls.BOARD_SHAPE): if np.any(state[i, j, :2] == 1): continue flip_squares = np.full(cls.BOARD_SHAPE, False) for di, dj, in DIRECTIONS_8: p_x, p_y = i + di, j + dj if not (cls.is_valid(p_x, p_y) and state[p_x, p_y, enemy_index] == 1): continue p_x += di p_y += dj while cls.is_valid(p_x, p_y) and state[p_x, p_y, enemy_index] == 1: p_x += di p_y += dj if cls.is_valid(p_x, p_y) and state[p_x, p_y, friendly_index] == 1: # success, mark all squares between i, j and p_x, p_y (not including endpoints) p_x -= di p_y -= dj while not (p_x == i and p_y == j): flip_squares[p_x, p_y] = True p_x -= di p_y -= dj if np.any(flip_squares): move = cls.null_move(state) move[i, j, :2] = player_piece move[flip_squares, :2] = player_piece moves.append(move) if len(moves) == 0: pass_move = cls.null_move(state) moves.append(pass_move) return moves
def get_possible_moves(cls, state): if cls.is_draw_by_insufficient_material(state): return [] friendly_slice, enemy_slice, pawn_direction, queening_row, pawn_starting_row, castling_row, en_passant_row = \ cls.get_stats(state) moves = [] for i, j in iter_product(cls.BOARD_SHAPE): square_piece = state[i, j, friendly_slice] if np.any(square_piece == 1): if square_piece[0] == 1: # king moves king_moves = cls.get_finite_distance_moves( state, i, j, DIRECTIONS_8, friendly_slice) # castling moves king_column = 4 for castling_column, pass_through_column, rook_column, empty_column in [ (2, 3, 0, 1), (6, 5, 7, None) ]: # don't need to check that castling_column is safe because that will be done later anyways if state[castling_row, castling_column, -2] and \ np.all(state[castling_row, pass_through_column, :12] == 0) and \ np.all(state[castling_row, castling_column, :12] == 0) and \ cls.square_safe(state, castling_row, king_column, enemy_slice, -pawn_direction) and \ cls.square_safe(state, castling_row, pass_through_column, enemy_slice, -pawn_direction) and \ state[castling_row, rook_column, friendly_slice][2] == 1 and \ (empty_column is None or np.all(state[castling_row, empty_column, :12] == 0)): move = cls.create_move(state, i, j, castling_row, castling_column) move[castling_row, pass_through_column, :12] = move[ castling_row, rook_column, :12] move[castling_row, rook_column, :12] = 0 king_moves.append(move) # remove the castling flag from all moves for move in king_moves: move[castling_row, :, -2] = 0 moves.extend(king_moves) if square_piece[1] == 1: # queen moves moves.extend( cls.get_infinite_distance_moves( state, i, j, DIRECTIONS_8, friendly_slice)) if square_piece[2] == 1: # rook moves rook_moves = cls.get_infinite_distance_moves( state, i, j, STRAIGHT_DIRECTIONS, friendly_slice) # if castling was possible before the rook moved, remove the castling flag from all moves for castling_column, rook_column in [(2, 0), (6, 7)]: if state[ castling_row, castling_column, -2] == 1 and i == castling_row and j == rook_column: for move in rook_moves: move[castling_row, castling_column, -2] = 0 moves.extend(rook_moves) if square_piece[3] == 1: # bishop moves moves.extend( cls.get_infinite_distance_moves( state, i, j, DIAGONAL_DIRECTIONS, friendly_slice)) if square_piece[4] == 1: # knight moves moves.extend( cls.get_finite_distance_moves(state, i, j, cls.KNIGHT_MOVES, friendly_slice)) if square_piece[5] == 1: # pawn moves is_promoting = i + pawn_direction == queening_row if np.all(state[i + pawn_direction, j, :12] == 0): move = cls.create_move(state, i, j, i + pawn_direction, j) if is_promoting: moves.extend( cls.promote_on_move(move, i + pawn_direction, j, friendly_slice)) else: moves.append(move) if i == pawn_starting_row and \ np.all(state[i + pawn_direction, j, :12] == 0) and \ np.all(state[i + 2 * pawn_direction, j, :12] == 0): move = cls.create_move(state, i, j, i + 2 * pawn_direction, j) # set en passant flag if there is an adjacent enemy pawn for dj in [1, -1]: if cls.is_valid(i + 2 * pawn_direction, j + dj) and \ state[i + 2 * pawn_direction, j + dj, enemy_slice][5] == 1: # play out the en passant capture and verify that it is a valid move test_board = cls.null_move(move) test_move = cls.create_move( test_board, i + 2 * pawn_direction, j + dj, i + pawn_direction, j) test_move[i + 2 * pawn_direction, j, :12] = 0 if cls.king_safe(test_move, enemy_slice, friendly_slice, -pawn_direction): # verified, set the en passant flag now move[i + pawn_direction, j, -2] = 1 break moves.append(move) for dj in [1, -1]: target_i, target_j = i + pawn_direction, j + dj if cls.is_valid(target_i, target_j): if np.any(state[target_i, target_j, enemy_slice] == 1): move = cls.create_move(state, i, j, target_i, target_j) if is_promoting: moves.extend( cls.promote_on_move( move, target_i, target_j, friendly_slice)) else: moves.append(move) elif i == en_passant_row and state[target_i, target_j, -2] == 1 \ and state[i, target_j, enemy_slice][5] == 1 \ and np.all(state[target_i, target_j, :12] == 0): move = cls.create_move(state, i, j, target_i, target_j) move[i, target_j, :12] = 0 moves.append(move) king_safe_func = partial(cls.king_safe, friendly_slice=friendly_slice, enemy_slice=enemy_slice, pawn_direction=pawn_direction) return list(filter(king_safe_func, moves))
class SymmetryTransform: # noinspection PyChainedComparisons PAWNLESS_UNIQUE_SQUARE_INDICES = [ (i, j) for i, j in iter_product(Chess.BOARD_SHAPE) if i < 4 and j < 4 and i <= j ] UNIQUE_SQUARE_INDICES = [(i, j) for i, j in iter_product(Chess.BOARD_SHAPE) if j < 4] def __init__(self, GameClass, state): self.GameClass = get_verified_chess_subclass(GameClass) self.transform_funcs = [] if GameClass.heuristic(state) < 0: # black is attacking, so switch white and black self.transform_funcs.append(self.flip_state_colors) i, j = GameClass.get_king_pos(state, GameClass.BLACK_SLICE) i = GameClass.ROWS - 1 - i else: i, j = GameClass.get_king_pos(state, GameClass.WHITE_SLICE) pawnless = np.all(state[:, :, 5] == 0) and np.all(state[:, :, 11] == 0) if pawnless and not (i < 4): self.transform_funcs.append(SymmetryTransform.flip_state_i) i = GameClass.ROWS - 1 - i if not (j < 4): # horizontal flipping can be done, even with pawns self.transform_funcs.append(SymmetryTransform.flip_state_j) j = GameClass.COLUMNS - 1 - j if pawnless and not (i <= j): self.transform_funcs.append(SymmetryTransform.flip_state_diagonal) @staticmethod def identity(GameClass): identity = SymmetryTransform(GameClass, GameClass.STARTING_STATE) identity.transform_funcs = [] return identity def is_identity(self): return len(self.transform_funcs) == 0 def transform_state(self, state): for transform_func in self.transform_funcs: state = transform_func(state) return state def untransform_state(self, state): # since all transform_funcs are their own inverses, we can just run through them in reverse for transform_func in self.transform_funcs[::-1]: state = transform_func(state) return state def transform_outcome(self, outcome): return -outcome if self.flip_state_colors in self.transform_funcs else outcome def flip_state_colors(self, state): special_layers = np.copy(state[..., -2:]) special_layers[..., -1] = 1 - special_layers[..., -1] new_state = np.concatenate( (state[..., self.GameClass.BLACK_SLICE], state[..., self.GameClass.WHITE_SLICE], special_layers), axis=-1) # need to flip board vertically after flipping colours # this ensures that the pawns move in the correct directions return SymmetryTransform.flip_state_i(new_state) @staticmethod def flip_state_i(state): return np.flip(state, axis=0) @staticmethod def flip_state_j(state): return np.flip(state, axis=1) @staticmethod def flip_state_diagonal(state): return np.rot90(np.flip(state, axis=1), axes=(0, 1))
def get_legal_moves(cls, state): return np.array([state[coords + (0,)] == 0 and state[coords + (1,)] == 0 for coords in iter_product(MultiTicTacToe.BOARD_SHAPE)]) \ .reshape(MultiTicTacToe.BOARD_SHAPE)
def parse_board_bytes(cls, board_bytes): board_bytes = list(board_bytes) # convert back to list of integers bitboard = np.array([[row & (1 << square) for square in range(8)] for row in board_bytes[:8]]) != 0 board_bytes = board_bytes[8:] pieces = [] piece_count = np.sum(bitboard) if piece_count % 2 == 1: pieces.append(board_bytes[0]) board_bytes = board_bytes[1:] for piece_bytes in board_bytes: pieces.append(piece_bytes // 16) pieces.append(piece_bytes % 16) if len(pieces) != piece_count: raise ValueError( f'Inconsistent number of pieces! Expected {piece_count} but got {len(pieces)}' ) state = np.zeros(Chess.STATE_SHAPE) en_passant_processed = False for i, j in iter_product(Chess.BOARD_SHAPE): if not bitboard[i, j]: continue piece_value = pieces.pop(0) is_white = piece_value < 8 piece = piece_value % 8 if piece == 6: # if the piece is a king whose turn it is # process turn information if is_white: state[:, :, -1] = 1 piece = 0 # convert to regular king if piece == 7: if i == 0 or i == 7: # if this is castling information if i == 0 and j == 0 and not is_white: state[0, 2, -2] = 1 elif i == 0 and j == 7 and not is_white: state[0, 6, -2] = 1 elif i == 7 and j == 0 and is_white: state[7, 2, -2] = 1 elif i == 7 and j == 7 and is_white: state[7, 6, -2] = 1 else: raise ValueError( f'Castling information on invalid square! ' f'i = {i}, j = {j}, is_white = {is_white}') piece = 2 # convert to regular rook elif i == 3 or i == 4: # if this is en passant information if en_passant_processed: raise ValueError( f'Second en passant square found! i = {i}, j = {j}, is_white = {is_white}' ) if i == 4 and is_white: state[i + 1, j, -2] = 1 elif i == 3 and not is_white: state[i - 1, j, -2] = 1 else: raise ValueError( f'En passant rank does not match the correct colour! ' f'i = {i}, j = {j}, is_white = {is_white}') en_passant_processed = True piece = 5 # convert to regular pawn else: raise ValueError( f'Special piece on invalid square! i = {i}, j = {j}, is_white = {is_white}' ) state[i, j, piece + (0 if is_white else 6)] = 1 if en_passant_processed: # check if en passant information is consistent with whose turn it is is_white_pawn_en_passant = np.any(state[5, :, -2] == 1) if is_white_pawn_en_passant == cls.is_player_1_turn(state): raise ValueError( 'The player whose turn it is also has an en passant pawn!') return state