Пример #1
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)
Пример #2
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))