Refactor to extract state from game

This commit is contained in:
Chris Proctor 2022-05-11 15:25:04 -04:00
parent ee230f4be9
commit 6c449852c2
4 changed files with 52 additions and 79 deletions

15
play.py
View File

@ -4,11 +4,12 @@ from ttt_player import TTTHumanPlayer
player0 = TTTHumanPlayer("Player 1") player0 = TTTHumanPlayer("Player 1")
player1 = TTTHumanPlayer("Player 2") player1 = TTTHumanPlayer("Player 2")
game = TTTGame(player0, player1) game = TTTGame()
view = TTTView() view = TTTView(player0, player1)
view.greet(game) state = game.get_initial_state()
while not game.is_over(game.state): view.greet()
action = view.get_action(game) while not game.is_over(state):
game.play_action(action) action = view.get_action(state)
view.conclude(game) state = game.get_next_state(state, action)
view.conclude(state)

View File

@ -1,18 +1,11 @@
class TTTGame: class TTTGame:
"Models a tic-tac-toe game." "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): def get_initial_state(self):
"Returns the game's initial state." "Returns the game's initial state."
return { return {
"board": ['-', '-', '-', '-', '-', '-', '-', '-', '-'], "board": ['-', '-', '-', '-', '-', '-', '-', '-', '-'],
"player": "X", "player_x": True,
} }
def get_next_state(self, state, action): 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. in an empty board space, and it is the opposite player's turn.
""" """
new_board = state["board"].copy() new_board = state["board"].copy()
new_board[action] = state["player"] new_board[action] = 'X' if state["player_x"] else 'O'
if state["player"] == "O":
new_player = "X"
else:
new_player = "O"
return { return {
"board": new_board, "board": new_board,
"player": new_player, "player_x": not state["player_x"],
} }
def get_actions(self, state): 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` 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`. (which chooses the largest number), and we set O's objective to the built-in function `min`.
""" """
if state["player"] == 'X': return max if state["player_x"] else min
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()
def board_is_full(self, state): def board_is_full(self, state):
"Checks whether all the spaces in the board are occupied." "Checks whether all the spaces in the board are occupied."
@ -83,6 +59,3 @@ class TTTGame:
def check_winner(self, state, symbol): def check_winner(self, state, symbol):
"Checks whether the player with `symbol` has won the game." "Checks whether the player with `symbol` has won the game."
return False return False

View File

@ -1,5 +1,6 @@
from click import Choice, prompt from click import Choice, prompt
from strategy import RandomStrategy from strategy import RandomStrategy
from ttt_game import TTTGame
import random import random
class TTTHumanPlayer: class TTTHumanPlayer:
@ -8,12 +9,14 @@ class TTTHumanPlayer:
def __init__(self, name): def __init__(self, name):
"Sets up the player." "Sets up the player."
self.name = name 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." "Chooses an action by prompting the player for a choice."
choices = Choice([str(i) for i in game.get_actions(game.state)]) actions = self.game.get_actions(state)
move = prompt("> ", type=choices, show_choices=False) choices = Choice([str(action) for action in actions])
return int(move) action = int(prompt("> ", type=choices, show_choices=False))
return action
class TTTComputerPlayer: class TTTComputerPlayer:
"A computer tic tac toe player" "A computer tic tac toe player"
@ -21,19 +24,10 @@ class TTTComputerPlayer:
def __init__(self, name): def __init__(self, name):
"Sets up the player." "Sets up the player."
self.name = name 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." "Chooses a random move from the moves available."
strategy = RandomStrategy(game) action = self.strategy.choose_action(state)
action = strategy.choose_action(game.state)
print(f"{self.name} chooses {action}.") print(f"{self.name} chooses {action}.")
return move return action
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!")

View File

@ -1,3 +1,4 @@
from ttt_game import TTTGame
import click import click
class TTTView: class TTTView:
@ -9,54 +10,61 @@ class TTTView:
o_color = "blue" o_color = "blue"
option_color = "bright_black" 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." "Starts a new game by greeting the players."
x_name = game.players['X'].name x_name = self.players['X'].name
o_name = game.players['O'].name o_name = self.players['O'].name
print(self.greeting) print(self.greeting)
print(f"{x_name} will play as X.") print(f"{x_name} will play as X.")
print(f"{o_name} will play as O.") 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." "Shows the board and asks the current player for their choice of action."
self.print_board_with_options(game) self.print_board(state)
current_player_symbol = game.state["player"] current_player_symbol = 'X' if state["player_x"] else 'O'
player = game.players[current_player_symbol] player = self.players[current_player_symbol]
print(f"{player.name}, it's your move.") 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" "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.divider)
print(self.format_row(game, [3, 4, 5])) print(self.format_row(state, [3, 4, 5]))
print(self.divider) 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 '" "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]} " 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. """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 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 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) 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) return click.style('O', fg=self.o_color)
else: else:
return click.style(index, fg=self.option_color) return click.style(index, fg=self.option_color)
def conclude(self, game): def conclude(self, state):
"""Says goodbye. """Says goodbye.
""" """
self.print_board_with_options(game) self.print_board(state)
if game.check_winner(game.state, 'X'): if self.game.check_winner(state, 'X'):
winner = game.players['X'] winner = game.players['X']
elif game.check_winner(game.state, 'O'): elif self.game.check_winner(game.state, 'O'):
winner = game.players['O'] winner = game.players['O']
else: else:
winner = None winner = None
@ -65,6 +73,3 @@ class TTTView:
print(f"Congratulations to {winner.name}.") print(f"Congratulations to {winner.name}.")
else: else:
print("Nobody won this game.") print("Nobody won this game.")