From 6c449852c28a7431a838a7023cbe6694ecfa6271 Mon Sep 17 00:00:00 2001 From: Chris Proctor Date: Wed, 11 May 2022 15:25:04 -0400 Subject: [PATCH] Refactor to extract state from game --- play.py | 15 ++++++++------- ttt_game.py | 35 ++++------------------------------ ttt_player.py | 28 +++++++++++---------------- ttt_view.py | 53 ++++++++++++++++++++++++++++----------------------- 4 files changed, 52 insertions(+), 79 deletions(-) diff --git a/play.py b/play.py index 6935515..20c3c6b 100644 --- a/play.py +++ b/play.py @@ -4,11 +4,12 @@ from ttt_player import TTTHumanPlayer player0 = TTTHumanPlayer("Player 1") player1 = TTTHumanPlayer("Player 2") -game = TTTGame(player0, player1) -view = TTTView() +game = TTTGame() +view = TTTView(player0, player1) -view.greet(game) -while not game.is_over(game.state): - action = view.get_action(game) - game.play_action(action) -view.conclude(game) +state = game.get_initial_state() +view.greet() +while not game.is_over(state): + action = view.get_action(state) + state = game.get_next_state(state, action) +view.conclude(state) diff --git a/ttt_game.py b/ttt_game.py index a8a9b71..2f0e302 100644 --- a/ttt_game.py +++ b/ttt_game.py @@ -1,18 +1,11 @@ class TTTGame: "Models a tic-tac-toe game." - def __init__(self, playerX, playerO): - self.state = self.get_initial_state() - self.players = { - 'X': playerX, - 'O': playerO, - } - def get_initial_state(self): "Returns the game's initial state." return { "board": ['-', '-', '-', '-', '-', '-', '-', '-', '-'], - "player": "X", + "player_x": True, } def get_next_state(self, state, action): @@ -21,14 +14,10 @@ class TTTGame: in an empty board space, and it is the opposite player's turn. """ new_board = state["board"].copy() - new_board[action] = state["player"] - if state["player"] == "O": - new_player = "X" - else: - new_player = "O" + new_board[action] = 'X' if state["player_x"] else 'O' return { "board": new_board, - "player": new_player, + "player_x": not state["player_x"], } def get_actions(self, state): @@ -58,20 +47,7 @@ class TTTGame: want opposite things, so we set X's objective to the built-in function `max` (which chooses the largest number), and we set O's objective to the built-in function `min`. """ - if state["player"] == 'X': - return max - elif state["player"] == 'O': - return min - else: - raise ValueError(f"Unrecognized player {state['player']}") - - def play_action(self, action): - "Plays a move, updating the game's state." - self.state = self.get_next_state(self.state, action) - - def is_valid_move(self, move): - "Checks whether a move is valid" - return move in self.get_valid_moves() + return max if state["player_x"] else min def board_is_full(self, state): "Checks whether all the spaces in the board are occupied." @@ -83,6 +59,3 @@ class TTTGame: def check_winner(self, state, symbol): "Checks whether the player with `symbol` has won the game." return False - - - diff --git a/ttt_player.py b/ttt_player.py index ed651bc..7aaca65 100644 --- a/ttt_player.py +++ b/ttt_player.py @@ -1,5 +1,6 @@ from click import Choice, prompt from strategy import RandomStrategy +from ttt_game import TTTGame import random class TTTHumanPlayer: @@ -8,12 +9,14 @@ class TTTHumanPlayer: def __init__(self, name): "Sets up the player." self.name = name + self.game = TTTGame() - def choose_action(self, game): + def choose_action(self, state): "Chooses an action by prompting the player for a choice." - choices = Choice([str(i) for i in game.get_actions(game.state)]) - move = prompt("> ", type=choices, show_choices=False) - return int(move) + actions = self.game.get_actions(state) + choices = Choice([str(action) for action in actions]) + action = int(prompt("> ", type=choices, show_choices=False)) + return action class TTTComputerPlayer: "A computer tic tac toe player" @@ -21,19 +24,10 @@ class TTTComputerPlayer: def __init__(self, name): "Sets up the player." self.name = name + self.strategy = RandomStrategy(TTTGame()) - def choose_action(self, game): + def choose_action(self, state): "Chooses a random move from the moves available." - strategy = RandomStrategy(game) - action = strategy.choose_action(game.state) + action = self.strategy.choose_action(state) print(f"{self.name} chooses {action}.") - return move - - def get_symbol(self, game): - "Returns this player's symbol in the game." - if game.players['X'] == self: - return 'X' - elif game.players['O'] == self: - return 'O' - else: - raise ValueError(f"Player {self.name} isn't in this game!") + return action diff --git a/ttt_view.py b/ttt_view.py index 344129f..0be4b33 100644 --- a/ttt_view.py +++ b/ttt_view.py @@ -1,3 +1,4 @@ +from ttt_game import TTTGame import click class TTTView: @@ -9,54 +10,61 @@ class TTTView: o_color = "blue" option_color = "bright_black" - def greet(self, game): + def __init__(self, playerX, playerO): + self.game = TTTGame() + self.players = { + "X": playerX, + "O": playerO, + } + + def greet(self): "Starts a new game by greeting the players." - x_name = game.players['X'].name - o_name = game.players['O'].name + x_name = self.players['X'].name + o_name = self.players['O'].name print(self.greeting) print(f"{x_name} will play as X.") print(f"{o_name} will play as O.") - def get_action(self, game): + def get_action(self, state): "Shows the board and asks the current player for their choice of action." - self.print_board_with_options(game) - current_player_symbol = game.state["player"] - player = game.players[current_player_symbol] + self.print_board(state) + current_player_symbol = 'X' if state["player_x"] else 'O' + player = self.players[current_player_symbol] print(f"{player.name}, it's your move.") - return player.choose_action(game) + return player.choose_action(state) - def print_board_with_options(self, game): + def print_board(self, state): "Prints the current board, showing indices of available spaces" - print(self.format_row(game, [0, 1, 2])) + print(self.format_row(state, [0, 1, 2])) print(self.divider) - print(self.format_row(game, [3, 4, 5])) + print(self.format_row(state, [3, 4, 5])) print(self.divider) - print(self.format_row(game, [6, 7, 8])) + print(self.format_row(state, [6, 7, 8])) - def format_row(self, game, indices): + def format_row(self, state, indices): "Returns a string for one row in the board, like ' X | O | X '" - spaces = [self.format_value(game, i) for i in indices] + spaces = [self.format_value(state, i) for i in indices] return f" {spaces[0]} | {spaces[1]} | {spaces[2]} " - def format_value(self, game, index): + def format_value(self, state, index): """Formats the value for a single space on the board. If the game board already has a symbol in that space, formats that value for the Terminal. If the space is empty, instead formats the index of the space. """ - if game.state["board"][index] == 'X': + if state["board"][index] == 'X': return click.style('X', fg=self.x_color) - elif game.state["board"][index] == 'O': + elif state["board"][index] == 'O': return click.style('O', fg=self.o_color) else: return click.style(index, fg=self.option_color) - def conclude(self, game): + def conclude(self, state): """Says goodbye. """ - self.print_board_with_options(game) - if game.check_winner(game.state, 'X'): + self.print_board(state) + if self.game.check_winner(state, 'X'): winner = game.players['X'] - elif game.check_winner(game.state, 'O'): + elif self.game.check_winner(game.state, 'O'): winner = game.players['O'] else: winner = None @@ -65,6 +73,3 @@ class TTTView: print(f"Congratulations to {winner.name}.") else: print("Nobody won this game.") - - -