def test_zobrist_hashing(self):
        game = CCGame(width=5)

        hasher = CCZobristHash(game)

        game_2 = CCGame(width=5)

        self.assertEqual(hasher.get_hash(game), hasher.get_hash(game_2))

        game.move(2, 0, CCMovement.LS)

        self.assertFalse(hasher.get_hash(game) == hasher.get_hash(game_2))
    def test_alpha_beta_prunning(self):
        """assert that using alpha-beta prunning doesn't alter
        the choice of movements"""
        game = CCGame(width=5, player_row_span=3)
        strat_ab = MinMaxStrategy(steps=1, alpha_beta_pruning=True)
        strat_no_ab = MinMaxStrategy(steps=1, alpha_beta_pruning=False)

        N_STEPS = 10

        for _ in range(0, N_STEPS):
            m_ab = strat_ab.select_move(game, game.player_turn)
            m_no_ab = strat_no_ab.select_move(game, game.player_turn)

            self.assertEqual(m_ab, m_no_ab)
            game.apply_move_sequence(m_ab)
    def test_player_1_wins(self):
        game = CCGame(width=5, player_row_span=3)
        game.board = TEST_BOARD_STRATEGY_PLAYER_1_WINS_IN_TWO
        strategy = MinMaxStrategy(alpha_beta_pruning=False)

        move, score = strategy._select_move(game, 1, 0, -100000, 100000)
        self.assertTrue(score > 1000)
        game.apply_move_sequence(move)

        game.rotate_turn()

        move, score = strategy._select_move(game, 1, 0, -100000, 100000)
        self.assertEqual(100000, score)

        game.apply_move_sequence(move)
        self.assertEqual(1, game.state())
示例#4
0
    def test_player_1_wins(self):
        game = CCGame(width=5, player_row_span=3)
        game.board = TEST_BOARD_STRATEGY_PLAYER_1_WINS_IN_TWO
        strategy = OnlyMaxStrategy(steps=1,
                                   player=1,
                                   heuristic=CombinedVerticalAdvance())

        move, score = strategy._select_move(game, 0)
        self.assertEqual(50000, score)
        game.apply_move_sequence(move)
        game.rotate_turn()

        move, score = strategy._select_move(game, 0)
        self.assertEqual(100000, score)

        game.apply_move_sequence(move)

        self.assertEqual(1, game.state())
示例#5
0
    def _available_moves(game: CCGame,
                         row: int,
                         column: int,
                         player: int,
                         previous_moves: list = [],
                         previous_positions: list = []) -> List[CCMove]:
        """
        Returns a dict a list of movements that the player
        can make, considering the piece found at 'row' and 'column'
        """
        moves: List[CCMove] = []
        jumping = game.player_can_only_jump

        def undo_movement():
            if game.player_turn != player:
                game.rotate_turn()
            game.undo_last_move()
            if not jumping and game.player_can_only_jump:
                # reset jumping state
                game.player_can_only_jump = False

        for movement in CCMovement:
            if game.can_move(row, column, movement):
                turn = game.move(row, column, movement)
                if (game.moved_to_row[-1],
                        game.moved_to_column[-1]) in previous_positions:
                    # we already passed through this state, avoid
                    # infinite recursion
                    undo_movement()
                    continue
                previous_positions.append(
                    (game.moved_to_row[-1], game.moved_to_column[-1]))
                previous_moves.append(movement)
                moves.append(CCMove([*previous_positions], [*previous_moves]))
                if turn == player:
                    # turn hasn't rotated -> current piece can still jump more
                    moves += (CCReasoner._available_moves(
                        game, game.moved_to_row[-1], game.moved_to_column[-1],
                        player, previous_moves, previous_positions))
                previous_positions.pop()
                previous_moves.pop()
                undo_movement()

        return moves
    def test_player_2_wins_in_one(self):
        game = CCGame(width=5, player_row_span=3)
        game.board = TEST_BOARD_STRATEGY_PLAYER_2_WINS_IN_ONE
        strategy = MinMaxStrategy(steps=0)

        game.rotate_turn()

        move, score = strategy._select_move(game, 2, 0, -100000, 100000)
        game.apply_move_sequence(move)
        self.assertEqual(100000, score)
        self.assertEqual(2, game.state())
 def move(self, game: CCGame, player: int):
     valid_move = False
     while not valid_move:
         while not (self.gui.first_click and self.gui.second_click):
             self.gui.update()
         if not self.movement_is_valid(
                 self.gui.first_click[0], self.gui.first_click[1],
                 self.gui.second_click[0], self.gui.second_click[1], game,
                 player):
             print('Invalid move, try again')
             self.gui.reset_user_input()
             continue
         # check if movement is valid!
         else:
             game._do_move(self.gui.first_click[0], self.gui.first_click[1],
                           self.gui.second_click[0],
                           self.gui.second_click[1])
             valid_move = True
     self.gui.reset_user_input()
    def test_transposition_table(self):
        """assert that using a transposition table doesn't alter
        the choice of movements"""
        game = CCGame(width=5, player_row_span=3)
        strat_tt = MinMaxStrategy(steps=1,
                                  alpha_beta_pruning=False,
                                  transposition_table=True)
        strat_no_tt = MinMaxStrategy(steps=1,
                                     alpha_beta_pruning=False,
                                     transposition_table=False)

        N_STEPS = 10

        for _ in range(0, N_STEPS):
            m_tt = strat_tt.select_move(game, game.player_turn)

            h_tt = strat_tt.heuristic.value(game, game.player_turn)
            h_no_tt = strat_no_tt.heuristic.value(game, game.player_turn)

            self.assertAlmostEqual(h_tt, h_no_tt, 2)

            game.apply_move_sequence(m_tt)
示例#9
0
 def test_combined_heuristic(self):
     game_1 = CCGame(width=5)
     game_2 = CCGame(width=5)
     game_1.board = TEST_BOARD_VA_2_1
     game_2.board = TEST_BOARD_VA_2_2
     heuristic = CombinedVerticalAdvance()
     self.assertTrue(heuristic.value(game_1, 2) <
                     heuristic.value(game_2, 2))
示例#10
0
 def test_combined_vertical_advance_player_1(self):
     game_1 = CCGame(width=5)
     game_2 = CCGame(width=5)
     game_1.board = TEST_BOARD_VA_1_1
     game_2.board = TEST_BOARD_VA_1_2
     heuristic = CombinedVerticalAdvance()
     self.assertTrue(heuristic.value(game_1, 1) <
                     heuristic.value(game_2, 1))
 def test_available_moves(self):
     game = CCGame(width=5)
     moves = CCReasoner.available_moves(game, 1)
     self.assertEqual(moves, [
         CCMove([(1, 0), (3, 0)], [CCMovement.LS]),
         CCMove([(1, 0), (3, 2)], [CCMovement.RS]),
         CCMove([(1, 1), (3, 1)], [CCMovement.LS]),
         CCMove([(1, 1), (3, 3)], [CCMovement.RS]),
         CCMove([(2, 0), (3, 0)], [CCMovement.LS]),
         CCMove([(2, 0), (3, 1)], [CCMovement.RS]),
         CCMove([(2, 1), (3, 1)], [CCMovement.LS]),
         CCMove([(2, 1), (3, 2)], [CCMovement.RS]),
         CCMove([(2, 2), (3, 2)], [CCMovement.LS]),
         CCMove([(2, 2), (3, 3)], [CCMovement.RS])
     ])
    def compete(weights_1: list, weights_2: list):
        """
        Plays a game between two AIs with different weights and return
        the result of the game.
        The second AI is in disadvantage (has a pure greedy lookahead).

        Return the number of turns it took for player 1 to beat player 2,
        or MAX_TURNS if player 1 didn't manage to beat player 2.
        """
        print('{} versus greedy {}'.format(weights_1, weights_2))

        heuristic_1 = OptimizedCombinedHeuristic(weights_1)
        heuristic_2 = OptimizedCombinedHeuristic(weights_2)

        game = CCGame(width=GAME_WIDTH,
                      player_row_span=PLAYER_ROW_SPAN,
                      visitors=[heuristic_1, heuristic_2])

        strategy_1 = MinMaxStrategy(steps=LOOK_AHEAD,
                                    pre_sort_moves=True,
                                    transposition_table=True,
                                    heuristic=heuristic_1)
        strategy_2 = OnlyMaxStrategy(player=2,
                                     steps=0,
                                     transposition_table=True,
                                     heuristic=heuristic_2)

        turns = 0

        last_boards: Set[CCGame] = set()
        board_repeats = 0

        while (game.state() == 0 and turns < MAX_TURNS):
            strategy = (strategy_1 if game.player_turn == 1 else strategy_2)
            move = strategy.select_move(game, game.player_turn)
            game.apply_move_sequence(move)
            if game in last_boards:
                board_repeats += 1

            if board_repeats == 3:
                # infinite loop (tie)
                print('Board repeats - tie')
                return MAX_TURNS

            last_boards.add(deepcopy(game))
            turns += 1

        state = game.state()
        if state == 1:
            print(f'Won in {turns} turns.')
            return min(MAX_TURNS, turns)
        else:
            print(f'Failed to beat player 2, game state: {state}')
            return MAX_TURNS
 def test_available_moves_depth_2(self):
     game = CCGame(width=5)
     game.move(2, 0, CCMovement.RS)
     game.rotate_turn()
     moves = CCReasoner.available_moves(game, 1)
     self.assertTrue(
         CCMove([(0,
                  0), (2,
                       0), (4,
                            2)], [CCMovement.LS, CCMovement.RS]) in moves)
     self.assertTrue(
         CCMove([(1, 0), (3,
                          2), (3,
                               0)], [CCMovement.RS, CCMovement.L]) in moves)
     self.assertTrue(
         CCMove([(2, 2), (2,
                          0), (4,
                               2)], [CCMovement.L, CCMovement.RS]) in moves)
示例#14
0
 def test_inv_squared_sum_center_line(self):
     game = CCGame(width=5)
     game.board = TEST_BOARD_CENTER_LINE
     heuristic = InvSquaredSumCenterLine()
     self.assertTrue(heuristic.value(game, 2) <
                     heuristic.value(game, 1))
 def test_use_only_max_end_game(self):
     game = CCGame(width=5, player_row_span=2)
     game.board = TEST_BOARD_END_GAME
     strat = MinMaxStrategy(steps=0, alpha_beta_pruning=False)
     self.assertTrue(strat._use_only_max(game))
 def test_use_only_max_false(self):
     game = CCGame(width=5, player_row_span=3)
     game.board = TEST_BOARD_VA_1_1
     strat = MinMaxStrategy(steps=0, alpha_beta_pruning=False)
     self.assertFalse(strat._use_only_max(game))
 def test_available_moves_player_2(self):
     game = CCGame(width=5)
     moves_1 = CCReasoner.available_moves(game, 1)
     game.rotate_turn()
     moves_2 = CCReasoner.available_moves(game, 2)
     self.assertEqual(len(moves_1), len(moves_2))
示例#18
0
    def _select_move(self, game: CCGame, depth: int) -> Tuple[CCMove, float]:
        """
        Returns: tuple
            - position 0: best movet that can be done by the player
                at this level.
            - position 1: best heuristic value that can be achieved at this
                level if following best move.
        """
        best_move, best_score = (None, -100000.0)
        if self.use_transposition_table and self.hasher:
            # transposition table business logic
            position_hash = self.hasher.get_hash(game)
            best_move, best_score, cached_depth = self.transposition_table.get(
                position_hash, (None, -100000.0, -1))

            if best_move and cached_depth == depth:
                return (best_move, best_score)

        moves = self.available_moves(game, self.player)
        if game.player_turn != self.player:
            raise AssertionError("""
                Player turn hasn't been rotated properly - this is likely
                a software bug
            """)

        for move in moves:
            if not best_move:
                best_move = move

            game.apply_move_sequence(move)
            # doesn't matter what the other does
            game.rotate_turn()

            # check if game has already ended
            if game.state() == 1:
                # player 1 wins
                # prefer winning in as few steps as possible
                curr_score = (100000 /
                              (depth + 1) if self.player == 1 else -100000)
            elif game.state() == 2:
                # player 2 wins
                # prefer winning in as few steps as possible
                curr_score = (-100000 if self.player == 1 else 100000 /
                              (depth + 1))
            else:
                if depth == self.steps:
                    curr_score = self.heuristic.value(game, self.player)
                else:
                    curr_score = self._select_move(game, depth + 1)[1]

            # keep the best move that can be done at this level
            if (curr_score > best_score):
                best_score = curr_score
                best_move = move

            # undo movement
            for _ in range(0, len(move.directions)):
                game.undo_last_move()

        if best_move:
            if self.hasher:
                # save into transposition table
                self.transposition_table[position_hash] = (best_move,
                                                           best_score, depth)
            return (best_move, best_score)
        else:
            raise AssertionError("""
                No possible movements available, this must be a software bug
            """)
示例#19
0
def play(board_size: int, player_row_span: int):
    random.seed(1)

    # (these weights were found running different experiments with the
    #  weight_search.py script)
    oc_heuristic = OptimizedCombinedHeuristic(weights=[0.01, 0.44, 0.55])

    game = CCGame(width=board_size,
                  player_row_span=player_row_span,
                  visitors=[oc_heuristic])

    manual_players = set([2])

    ai_players = {
        1:
        MinMaxStrategy(steps=1,
                       pre_sort_moves=True,
                       transposition_table=True,
                       heuristic=oc_heuristic)
    }

    start = time.time()

    player_turn = 1

    turns = 0

    gui = PygameGUI(game)
    manual_player = ManualPlayer(gui)

    ai_players_perf: Dict[int, List[float]] = {1: [], 2: []}

    while (game.state() == 0):
        print(f'Turn: {player_turn}')
        assert game.player_turn == player_turn
        gui.update()

        if player_turn not in manual_players:
            strategy = ai_players[player_turn]
            start = time.time()
            move = strategy.select_move(game, player_turn)
            end = time.time()
            ai_players_perf[player_turn].append(end - start)
            print(f'Move sequence: {move}')
            print(f'Turn {turns}')
            print(('Performance: '
                   f'{stats.describe(ai_players_perf[player_turn])}'))
            print(f'Heuristic values: {oc_heuristic.value(game, 1)} - '
                  f'{oc_heuristic.value(game, 2)}')
            game.apply_move_sequence(move)
        else:
            print("It's manual's player turn!")
            manual_player.move(game, player_turn)
            if game.player_turn == player_turn:
                game.rotate_turn()

        player_turn = game.player_turn
        turns += 1

        print('..........................')

    print(f'PLAYER {game.state()} WINS after {turns} turns')
    end = time.time()
    print(end - start)
示例#20
0
    def _select_move(self, game: CCGame, player: int, depth: int, alpha: float,
                     beta: float) -> Tuple[CCMove, float]:
        """
        Returns: tuple
            - position 0: best movem that can be done by the player
                at this level.
            - position 1: best heuristic value that can be achieved at this
                level if following best move.
                Heuristic is negative for player 2 and position for player 1.
        """
        if self.hasher:
            # transposition table business logic
            position_hash = self.hasher.get_hash(game)
            tt = (self.transposition_table_1
                  if player == 1 else self.transposition_table_2)
            best_move, best_score, cached_depth = (tt.get(
                position_hash, (None, -100000.0, -1)))
            if best_move and cached_depth == depth:
                return (best_move, best_score)

        moves = self.available_moves(game, player)
        if game.player_turn != player:
            raise AssertionError("""
                Player turn hasn't been rotated properly - this is likely
                a software bug
            """)

        moves_queue = PriorityQueue()  # type: ignore

        for move in moves:
            priority = 1
            positions = move.board_positions
            if self.pre_sort_moves:
                advance = (positions[-1][0] - positions[0][0] if player == 1
                           else positions[0][0] - positions[-1][0])

                if (self.extra_prunning and advance <= 0 and depth >= 3):
                    # prune movements down the tree which don't bring any
                    # extra advance
                    continue

                # otherwise sort movements by vertical advance to maximize
                # alpha-beta pruning
                priority = -advance
            moves_queue.put(PrioritizedCCMove(priority, move))

        best_move = None
        maximizing = depth % 2 == 0
        best_score = -100000.0 if maximizing else 100000.0

        while not moves_queue.empty():
            move = moves_queue.get().move
            if not best_move:
                best_move = move

            game.apply_move_sequence(move)

            # check if game has already ended
            if game.state() == 1:
                # player 1 wins
                # prefer winning in as few steps as possible
                curr_score = (100000 / (depth + 1) if player == 1 else -100000)
                if not maximizing:
                    curr_score = -curr_score
            elif game.state() == 2:
                # player 2 wins
                # prefer winning in as few steps as possible
                curr_score = (-100000 if player == 1 else 100000 / (depth + 1))
                if not maximizing:
                    curr_score = -curr_score
            else:
                if depth == self.steps * 2:  # maximizing
                    # approximate the score of the game by
                    # subtracting heuristics
                    curr_score = (
                        self.heuristic.value(game, player) -
                        self.heuristic.value(game, 2 if player == 1 else 1))
                else:
                    curr_score = self._select_move(game,
                                                   2 if player == 1 else 1,
                                                   depth + 1, alpha, beta)[1]

            # keep the best move that can be done at this level
            if ((maximizing and curr_score > best_score)
                    or (not maximizing and curr_score < best_score)):
                best_score = curr_score
                best_move = move

            # undo movement
            if game.player_turn != player:
                game.rotate_turn()
            for _ in range(0, len(move.directions)):
                game.undo_last_move()

            # perform alpha-beta pruning
            if self.alpha_beta_pruning:
                if maximizing:
                    alpha = max(alpha, best_score)
                else:
                    beta = min(beta, best_score)
                if beta <= alpha:
                    # alpha/beta pruning
                    break

        if best_move:
            if self.hasher:
                # save into transposition table
                tt[position_hash] = (best_move, best_score, depth)
            return (best_move, best_score)
        else:
            raise AssertionError("""
                No possible movements available, this must be a software bug
            """)
示例#21
0
 def test_combined_vertical_advance_symmetry(self):
     game = CCGame(width=5)
     heuristic = CombinedVerticalAdvance()
     self.assertEqual(heuristic.value(game, 1),
                      heuristic.value(game, 2))
示例#22
0
    def test_optimized_combined_heuristic(self):
        """test that the optimized, stateful heuristic provides the same
        values as the non-optimized one"""
        heuristic = CombinedHeuristic()
        optimized_heuristic = OptimizedCombinedHeuristic()

        game_1 = CCGame(width=5, visitors=[optimized_heuristic])

        def heuristics_agree():
            for player in [1, 2]:
                self.assertAlmostEqual(heuristic.value(game_1, player),
                                       optimized_heuristic.value(game_1,
                                                                 player),
                                       2)

        game_1.move(2, 1, CCMovement.LS)
        heuristics_agree()

        game_1.move(7, 1, CCMovement.RN)
        heuristics_agree()

        game_1.undo_last_move()
        game_1.rotate_turn()
        heuristics_agree()

        game_1.undo_last_move()
        heuristics_agree()
示例#23
0
 def test_inv_squared_sum_dest_corner(self):
     game = CCGame(width=5)
     game.board = TEST_BOARD_SQUARED_SUM
     heuristic = InvSquaredSumDestCorner()
     self.assertTrue(heuristic.value(game, 1) <
                     heuristic.value(game, 2))
示例#24
0
 def inv_squared_sum_dest_corner_zero(self):
     game = CCGame(width=5)
     game.board = TEST_BOARD_SQUARED_SUM_ZERO
     heuristic = InvSquaredSumDestCorner()
     self.assertEqual(heuristic.value(game, 1), 0)
     self.assertEqual(heuristic.value(game, 2), 0)