Пример #1
0
    def test_invalid_move_invalid_player(self):
        board = Board()
        game = Game(board=board, player_1='X', player_2='O')

        self.assertEqual(game.next_turn(), 'X')
        with self.assertRaises(InvalidMovementException):
            game.move('L', row=0, col=0)
Пример #2
0
    def test_invalid_move_game_over_game_is_tied(self):
        board = Board([
            ['O', 'X', 'X'],
            ['X', 'O', 'O'],
            ['X', 'O', 'X'],
        ])
        game = Game(board=board, player_1='X', player_2='O')

        self.assertTrue(game.is_finished())
        self.assertFalse(game.has_winner())
        self.assertTrue(game.is_tied())
        self.assertIsNone(game.get_winner())

        with self.assertRaises(InvalidMovementException):
            game.move('X', row=0, col=0)
Пример #3
0
    def test_invalid_move_game_has_winner(self):
        board = Board([
            ['X', None, None],
            ['O', 'X', None],
            [None, 'O', 'X'],
        ])

        game = Game(board=board, player_1='X', player_2='O')

        self.assertTrue(game.is_finished())
        self.assertTrue(game.has_winner())
        self.assertFalse(game.is_tied())
        self.assertEqual(game.get_winner(), 'X')

        self.assertEqual(game.next_turn(), 'X')
        with self.assertRaises(InvalidMovementException):
            game.move('X', row=0, col=2)
Пример #4
0
def test_draw(capsys):
    game = Game(4, "X", "MEOW", "KOTIK")
    game.move(0, 0, "X")
    game.move(0, 2, "O")
    game.move(1, 0, "O")
    game.move(1, 1, "X")
    game.move(2, 2, "O")
    assert (
        game.draw() ==
        "  | 1 | 2 | 3 | 4 \nA | X       O      \nB | O   X          \nC |         O      \nD |                \n"
    )
Пример #5
0
    def test_game_makes_valid_moves(self):
        board = Board()
        game = Game(board=board, player_1='X', player_2='O')

        # === First Move ===
        self.assertEqual(game.next_turn(), 'X')
        game.move('X', row=0, col=0)

        row_0 = game.board.get_row(0)
        self.assertEqual(row_0, ['X', None, None])

        row_1 = game.board.get_row(1)
        self.assertEqual(row_1, [None, None, None])

        row_2 = game.board.get_row(2)
        self.assertEqual(row_2, [None, None, None])

        # === Second Move ===
        self.assertEqual(game.next_turn(), 'O')
        game.move('O', row=1, col=1)

        row_0 = game.board.get_row(0)
        self.assertEqual(row_0, ['X', None, None])

        row_1 = game.board.get_row(1)
        self.assertEqual(row_1, [None, 'O', None])

        row_2 = game.board.get_row(2)
        self.assertEqual(row_2, [None, None, None])

        # === Third Move ===
        self.assertEqual(game.next_turn(), 'X')
        game.move('X', row=2, col=1)

        row_0 = game.board.get_row(0)
        self.assertEqual(row_0, ['X', None, None])

        row_1 = game.board.get_row(1)
        self.assertEqual(row_1, [None, 'O', None])

        row_2 = game.board.get_row(2)
        self.assertEqual(row_2, [None, 'X', None])
Пример #6
0
class ShellProgram:
    """This is the main class that manages the terminal around the game logic.
    To run this program, instantiate this class and call the run method.
    >>> ShellProgram().run()
    """
    title = 'Tic Tac Toe'

    def __init__(self, *args):
        """Initialize core instance variables.  Note that args are parsed
        in the parse_args() method.
        """
        self.args = args
        self.game = None
        """:type: Game"""
        self.state = None
        """:type: Game.State"""
        self._quit = False

    @staticmethod
    def parse_args(args: tuple):
        """Handle arguments passed to __init__().
        --colorized - Use ANSI terminal colors for player moves.
        """
        if '--colorized' in args:
            Game.colorized = True

    def run(self):
        """Begin running the shell and initialize the game."""
        self.display_title()
        player = self.choose_player()
        self.game = Game(player)
        self.parse_args(self.args)
        self.game_loop()

    def game_loop(self):
        """Handle the main game loop by iteratively checking the state.
        Terminate once self._quit == False."""
        while not self._quit:
            self.display_board()
            self.human_move()
            self.handle_state()
            if self.state is not Game.State.Incomplete:
                self.display_board()
                self.display_final_state_message()
                if not self.choose_play_again():
                    self.quit()
                else:
                    self.game.reset_board()

    def handle_state(self):
        """Call into the game and request a new state.  Updates self.state."""
        self.state = self.game.handle_state()

    def get_final_state_message(self) -> str:
        """Based on the end-game state, present different messages."""
        if self.state is Game.State.ComputerWins:
            return 'The computer wins.  Surprised?'
        elif self.state is Game.State.HumanWins:
            return 'Somehow you won, which is apparently impossible.'
        elif self.state is Game.State.Tie:
            return "It's a tie.  How exciting."
        else:
            return "Ummmm, I'm not quite sure what happened here."

    def quit(self):
        """Wrapper to set self._quit to True which will, in turn, terminate
        the game_loop().
        """
        self._quit = True

    # IO methods.

    @classmethod
    def display_title(cls):
        """Print the main title of the game defined at the top of the class."""
        print(cls.title)

    def display_final_state_message(self):
        """Retrieve the final state message and print it."""
        print(self.get_final_state_message())

    @classmethod
    def choose_play_again(cls):
        """Prompt the user to play again."""
        while True:
            choice = cls.input('Would you like to play again? (y/n) ').lower()
            if choice == 'y':
                return True
            elif choice == 'n':
                return False
            else:
                print("I'm sorry, I didn't understand.  Please type y or n.")

    @classmethod
    def choose_player(cls) -> Player:
        """Prompt the user to choose a player."""
        while True:
            choice = cls.input('Choose your player (X/O): ')
            try:
                return Player(choice.upper())
            except ValueError:
                print('Invalid choice, please try again.')

    def human_move(self):
        """Handle the move made by the user.  If invalid, display a message to
        the user and prompt for a new move.
        """
        while True:
            try:
                move = int(self.prompt_for_move())
                self.game.move(self.game.human, move)
                return
            except ValueError:
                self.display_error('Invalid move, please try again.')
            except (Game.InvalidMove, Game.AlreadyOccupied) as e:
                self.display_error(e)

    def prompt_for_move(self) -> str:
        """Prompt the user for a move."""
        return self.input('Where would you like to move? ')

    def display_board(self):
        """Print the current game board."""
        print(self.game.show())

    # IO wrappers.

    @classmethod
    def prompt_for_quit(cls):
        """Wrapper to handle when the user sends Ctrl+C to an input."""
        while True:
            try:
                # Put input message on next line.
                print()
                choice = input('Would you like to quit? (y/n) ').lower()
                if choice == 'y':
                    cls.exit()
                elif choice == 'n':
                    return
                else:
                    print('Invalid choice.  Please try again.')
            except KeyboardInterrupt:
                # Just bail if the user is persistent.
                cls.exit()

    @staticmethod
    def display_error(message: object):
        """Wrapper to print errors to the user."""
        print(message)

    @classmethod
    def input(cls, prompt: str) -> str:
        """Wrapper to get input from user and handle Ctrl+C and Ctrl+D."""
        while True:
            try:
                return input(prompt)
            except KeyboardInterrupt:
                # Ctrl+C - Confirm if we should exit the program.
                cls.prompt_for_quit()
            except EOFError:
                # Ctrl+D - Exit the program immediately.
                cls.exit()

    @staticmethod
    def exit(return_code: int=1):
        """Wrapper to force-exit the program."""
        # Ensure that user prompt ends up on next line.
        print()
        exit(return_code)
Пример #7
0
def make_step(sock: socket.socket, game: Game) -> bool:
    def make_move():
        cell = input("Ваш ход: ")
        while not re.match(r"[A-Z] \d", cell) or ord(
                cell.split(" ")[0]) - 65 >= game.field_size:
            print(
                "Введите клетку в формате: Буква Цифра. Клетка не должна выходить за пределы игрового поля."
            )
            cell = input("Ваш ход: ")
        own_x, own_y = cell.split(" ")
        is_move_success = False
        while not is_move_success:
            try:
                game.move(ord(own_x) - 65, int(own_y) - 1)
                sock.sendall(f"MOVE {cell}".encode("utf-8"))
            except Exception as e:
                print(e)
                make_move()

            is_move_success = True

    game_over = False
    print(f"Ожидание хода игрока {game.enemy_name}")

    data = sock.recv(BUFFER_SIZE)
    command, *arguments = data.decode("utf-8").split(" ")

    if command == "MOVE":
        enemy_x, enemy_y, *rest = arguments
        try:
            game.move(ord(enemy_x) - 65, int(enemy_y) - 1, ENEMY_SIDE)
        except Exception as e:
            print(e)

        if rest:
            game_over = True
            winner = rest[1]
            print(game.draw())

            if winner == "DRAW":
                print("Результат игры - ничья")
            else:
                print(
                    f"Выиграл игрок {game.gamer_name if winner == OWN_SIDE else game.enemy_name}"
                )

            return game_over
    elif command == "STOP":
        game_over = True
        winner = arguments[0]

        if winner == "DRAW":
            print("Результат игры - ничья")
        else:
            print(
                f"Выиграл игрок {game.gamer_name if winner == OWN_SIDE else game.enemy_name}"
            )

        return game_over
    else:
        raise WrongCommand

    print(game.draw())

    make_move()
    print(game.draw())

    return game_over
Пример #8
0
def test_check_winner_draw_or_no_yet_winner(moves, winner_expected):
    game = Game(3, "X", "MEOW", "KOTIK")
    for i in moves:
        x, y, z = i
        game.move(x, y, z)
    assert game.check_winner() == winner_expected
Пример #9
0
def test_move_raises_wrong_move_exception_cell_already_taken():
    game = Game(2, "X", "MEOW", "KOTIK")
    game.move(1, 1)
    with pytest.raises(WrongMove) as excinfo:
        game.move(1, 1)
    assert "Эта клетка уже занята" in str(excinfo.value)
Пример #10
0
def test_move_raises_wrong_move_exception_cell_not_found(moves, error_message):
    game = Game(2, "X", "MEOW", "KOTIK")
    with pytest.raises(WrongMove) as excinfo:
        x, y = moves
        game.move(x, y)
    assert "Такой клетки не существует" in str(excinfo.value)
Пример #11
0
def test_move_success():
    game = Game(2, "X", "MEOW", "KOTIK")
    game.move(1, 1)
    assert game.field[1][1] == game.side
Пример #12
0
class GUIProgram:
    """This is the main class that manages tkinter around the game logic.
    To run the program, instantiate this class and call the run method.
    >>> GUIProgram().run()
    """

    standard_button_dimensions = {
        'width': 2,
        'height': 1,
    }

    def __init__(self, *args):
        """Initialize core instance variables.  Note that args is not currently
        used.  It is simply available to remain consistent with the
        shell.ShellProgram class.
        """
        self.args = args
        self.app = Tk()
        self.game = None
        """:type: Game"""
        self.state = None
        """:type: Game.State"""

    # noinspection PyAttributeOutsideInit
    def run(self):
        """Begin running the GUI and initialize the game."""
        self.choose_player()
        self.window = Window(self, master=self.app)
        self.app.title('Tic Tac Toe')
        self.app.resizable(width=False, height=False)
        self.bring_to_front()
        self.app.mainloop()
        self.app.quit()

    def choose_player(self):
        """Hides the main app temporarily so the user can pick a player."""
        self.app.withdraw()
        ChoosePlayerDialog(self)

    def handle_player_choice(self, player: Player):
        """This is a callback used by the ChoosePlayerDialog class once the
        user has chosen a player.  Once executed, initializes the game and
        shows the main app to the user.
        """
        self.game = Game(player)
        self.app.deiconify()

    def handle_state(self):
        """Handle the game logic after the user has placed a move."""
        self.state = self.game.handle_state()
        if self.state in (Game.State.ComputerWins, Game.State.HumanWins):
            self.colorize_winner()
        else:
            self.window.update()

    def colorize_winner(self):
        """Highlight the buttons used to win the game."""
        player, play = self.game.get_winner_and_play()
        if player and play:
            for position in play:
                button = self.window.move_buttons[position]
                button.configure(highlightbackground='yellow')
                button.configure(background='yellow')
            self.window.update()

    def human_move(self, position):
        """Callback used by the Window class to handle moves."""
        self.game.move(self.game.human, position)

    @staticmethod
    def bring_to_front():
        """Unfortunately, OS X seems to place tkinter behind the terminal.
        Of all the methods out there, it seems like the best way to handle
        this is to make an OS call.
        """
        if sys.platform == 'darwin':
            apple_script = ('tell app "Finder" to set frontmost of process '
                            '"Python" to true')
            os.system("/usr/bin/osascript -e '{}'".format(apple_script))
Пример #13
0
class TestGame(unittest.TestCase):
    def setUp(self):
        self.game = Game(Player.X)

    def test_initial_game(self):
        game = Game(Player.X)
        self.assertIs(game.human, Player.X)
        self.assertIs(game.computer, Player.O)
        self.assertEqual(game.spaces, [None] * 9)
        game = Game(Player.O)
        self.assertIs(game.human, Player.O)
        self.assertIs(game.computer, Player.X)
        self.assertEqual(game.spaces, [None] * 9)

    def test_move(self):
        self.game.move(Player.X, 0)
        self.assertIs(self.game.spaces[0], Player.X)

    def test_invalid_move(self):
        self.assertRaises(Game.InvalidMove, self.game.move, Player.O, 9)

    def test_already_occupied(self):
        self.game.move(Player.X, 0)
        self.assertRaises(Game.AlreadyOccupied, self.game.move, Player.O, 0)

    def test_get_player_spaces(self):
        self.game.move(Player.X, 0)
        self.assertEqual(self.game.get_player_spaces(Player.X),
                         frozenset([0]))
        self.game.move(Player.O, 1)
        self.assertEqual(self.game.get_player_spaces(Player.O),
                         frozenset([1]))
        self.game.move(Player.X, 2)
        self.assertEqual(self.game.get_player_spaces(Player.X),
                         frozenset([0, 2]))
        self.game.move(Player.O, 3)
        self.assertEqual(self.game.get_player_spaces(Player.O),
                         frozenset([1, 3]))

    def test_get_empty_spaces(self):
        self.game.move(Player.X, 0)
        self.assertEqual(self.game.get_empty_spaces(),
                         frozenset(i for i in range(9) if i not in range(1)))
        self.game.move(Player.O, 1)
        self.assertEqual(self.game.get_empty_spaces(),
                         frozenset(i for i in range(9) if i not in range(2)))
        self.game.move(Player.X, 2)
        self.assertEqual(self.game.get_empty_spaces(),
                         frozenset(i for i in range(9) if i not in range(3)))
        self.game.move(Player.O, 3)
        self.assertEqual(self.game.get_empty_spaces(),
                         frozenset(i for i in range(9) if i not in range(4)))

    def test_get_winner(self):
        self.game.move(Player.X, 0)
        self.assertIsNone(self.game.get_winner())
        self.game.move(Player.O, 1)
        self.assertIsNone(self.game.get_winner())
        self.game.move(Player.X, 3)
        self.assertIsNone(self.game.get_winner())
        self.game.move(Player.O, 4)
        self.assertIsNone(self.game.get_winner())
        self.game.move(Player.X, 6)
        self.assertIs(self.game.get_winner(), Player.X)
        self.game = Game(Player.X)
        self.game.move(Player.X, 0)
        self.game.move(Player.O, 2)
        self.game.move(Player.X, 3)
        self.game.move(Player.O, 4)
        self.game.move(Player.X, 5)
        self.game.move(Player.O, 6)
        self.assertIs(self.game.get_winner(), Player.O)

    def test_get_state(self):
        # Ensure state is incomplete until human wins.
        self.assertIs(self.game.get_state(), Game.State.Incomplete)
        self.game.move(Player.X, 0)
        self.assertIs(self.game.get_state(), Game.State.Incomplete)
        self.game.move(Player.O, 1)
        self.assertIs(self.game.get_state(), Game.State.Incomplete)
        self.game.move(Player.X, 3)
        self.assertIs(self.game.get_state(), Game.State.Incomplete)
        self.game.move(Player.O, 4)
        self.assertIs(self.game.get_state(), Game.State.Incomplete)
        self.game.move(Player.X, 6)
        self.assertIs(self.game.get_state(), Game.State.HumanWins)
        # Reset the game and check for computer wins.
        self.game = Game(Player.X)
        self.game.move(Player.X, 0)
        self.game.move(Player.O, 2)
        self.game.move(Player.X, 3)
        self.game.move(Player.O, 4)
        self.game.move(Player.X, 5)
        self.game.move(Player.O, 6)
        self.assertIs(self.game.get_state(), Game.State.ComputerWins)
        # Reset the game and check for tie.
        self.game = Game(Player.X)
        self.game.move(Player.X, 0)
        self.game.move(Player.O, 1)
        self.game.move(Player.X, 2)
        self.game.move(Player.X, 3)
        self.game.move(Player.O, 4)
        self.game.move(Player.X, 5)
        self.game.move(Player.O, 6)
        self.game.move(Player.X, 7)
        self.game.move(Player.O, 8)
        self.assertIs(self.game.get_state(), Game.State.Tie)

    def test_state_value(self):
        self.assertLess(Game.State.HumanWins.value, Game.State.Tie.value)
        self.assertLess(Game.State.Tie.value, Game.State.ComputerWins.value)

    def test_minimax(self):
        x = Player.X
        o = Player.O
        _ = None
        # Test out various game states to ensure the computer moves correctly.
        self.game.spaces = [
            x, _, o,
            x, _, _,
            _, o, _,
        ]
        self.game.computer_move()
        self.assert_outcome(self.game, [
            x, _, o,
            x, _, _,
            o, o, _,
        ])
        self.game.spaces = [
            x, x, _,
            _, _, _,
            _, o, _,
        ]
        self.game.computer_move()
        self.assert_outcome(self.game, [
            x, x, o,
            _, _, _,
            _, o, _,
        ])

    def test_show(self):
        self.assertEqual(self.game.show(), "\n".join((
            " 0 | 1 | 2 ",
            "---+---+---",
            " 3 | 4 | 5 ",
            "---+---+---",
            " 6 | 7 | 8 ",
        )))
        self.game.move(Player.X, 0)
        self.assertEqual(self.game.show(), "\n".join((
            " X | 1 | 2 ",
            "---+---+---",
            " 3 | 4 | 5 ",
            "---+---+---",
            " 6 | 7 | 8 ",
        )))
        self.game.move(Player.O, 4)
        self.assertEqual(self.game.show(), "\n".join((
            " X | 1 | 2 ",
            "---+---+---",
            " 3 | O | 5 ",
            "---+---+---",
            " 6 | 7 | 8 ",
        )))

    def assert_outcome(self, game, outcome: [Player]):
        if game.spaces != outcome:
            actual = Game.format_board(*(x or ' ' for x in game.spaces))
            expected = Game.format_board(*(x or ' ' for x in outcome))
            raise AssertionError('\nExpected:\n{}\nActual:\n{}'
                                 .format(expected, actual))