コード例 #1
0
    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
コード例 #2
0
    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
コード例 #3
0
    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}'
            )
コード例 #4
0
    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()
コード例 #5
0
 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
コード例 #6
0
    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
コード例 #7
0
    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
コード例 #8
0
 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
コード例 #9
0
 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
コード例 #10
0
    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)
コード例 #11
0
    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
コード例 #12
0
    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
コード例 #13
0
    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
コード例 #14
0
    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
コード例 #15
0
    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))
コード例 #16
0
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))
コード例 #17
0
 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)
コード例 #18
0
    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