Refactor to extract state from game
This commit is contained in:
parent
ee230f4be9
commit
6c449852c2
15
play.py
15
play.py
|
@ -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)
|
||||||
|
|
35
ttt_game.py
35
ttt_game.py
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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!")
|
|
||||||
|
|
53
ttt_view.py
53
ttt_view.py
|
@ -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.")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue