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)
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)
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)
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" )
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])
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)
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
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
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)
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)
def test_move_success(): game = Game(2, "X", "MEOW", "KOTIK") game.move(1, 1) assert game.field[1][1] == game.side
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))
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))