generated from mwc/lab_tic_tac_toe
	Initial commit
This commit is contained in:
		
							
								
								
									
										23
									
								
								.commit_template
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								.commit_template
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# -----------------------------------------------------------------
 | 
			
		||||
# Write your entire commit message above this line.
 | 
			
		||||
# 
 | 
			
		||||
# The first line should be a quick description of what you changed.
 | 
			
		||||
# Then leave a blank line. 
 | 
			
		||||
# Then, taking as many lines as you want, answer the questions 
 | 
			
		||||
# corresponding to your checkpoint. 
 | 
			
		||||
#
 | 
			
		||||
# Checkpoint 1: 
 | 
			
		||||
# - This is the first time you have been asked to read a substantial amount
 | 
			
		||||
#   of code written by someone else. What was this experience like for you? 
 | 
			
		||||
#   Did you find any strategies which made it easier to make sense of the code?
 | 
			
		||||
#
 | 
			
		||||
# Checkpoint 2: 
 | 
			
		||||
# - Describe the strategy you used to check for a winner. 
 | 
			
		||||
#
 | 
			
		||||
# Checkpoint 3: 
 | 
			
		||||
# - Playing tic-tac-toe is pretty easy, even for children, but it takes a lot
 | 
			
		||||
#   of work to get a computer to play well. How did your awareness of your 
 | 
			
		||||
#   own cognition change as you worked on this lab?
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
*.swp
 | 
			
		||||
*.swo
 | 
			
		||||
**/__pycache__/*
 | 
			
		||||
							
								
								
									
										34
									
								
								nim/game_stub.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								nim/game_stub.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
class NimGameStub:
 | 
			
		||||
    """A stub is a minimal version of a class which stands in for the 
 | 
			
		||||
    real class, which hasn't yet been written. The stub has all the correct 
 | 
			
		||||
    methods, and their inputs and outputs are the right kind of thing, 
 | 
			
		||||
    but it doesn't really do anything. 
 | 
			
		||||
    """
 | 
			
		||||
    def get_initial_state(self):
 | 
			
		||||
        return {
 | 
			
		||||
            "board": [1, 3, 5, 7],
 | 
			
		||||
            "first_player": True
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    def get_next_state(self, state, action):
 | 
			
		||||
        next_state = {
 | 
			
		||||
            "board": state["board"].copy(),
 | 
			
		||||
            "first_player": not state["first_player"],
 | 
			
		||||
        }
 | 
			
		||||
        return next_state
 | 
			
		||||
 | 
			
		||||
    def get_actions(self, state):
 | 
			
		||||
        return [
 | 
			
		||||
            (0, 0), 
 | 
			
		||||
            (1, 0), (1, 1),
 | 
			
		||||
            (2, 0), (2, 1), (2, 2), (3, 0), (3, 1), (3, 2), (3, 3),
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
    def get_reward(self, state):
 | 
			
		||||
        return 0
 | 
			
		||||
 | 
			
		||||
    def is_over(self, state):
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
    def get_objective(self, state):
 | 
			
		||||
        return max if state["first_player"] else min
 | 
			
		||||
							
								
								
									
										35
									
								
								nim/player.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								nim/player.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
from nim.game_stub import NimGameStub
 | 
			
		||||
from strategy.lookahead_strategy import LookaheadStrategy
 | 
			
		||||
 | 
			
		||||
class HumanNimPlayer:
 | 
			
		||||
    def __init__(self, name):
 | 
			
		||||
        self.name = name
 | 
			
		||||
        self.game = NimGameStub()
 | 
			
		||||
 | 
			
		||||
    def choose_action(self, state):
 | 
			
		||||
        actions = self.game.get_actions(state)
 | 
			
		||||
        for i, action in enumerate(actions):
 | 
			
		||||
            row, lines_to_remove = action
 | 
			
		||||
            print(f"{i}. Remove {lines_to_remove} from row {row}.")
 | 
			
		||||
        choice = self.get_int(len(actions))
 | 
			
		||||
        return actions[choice]
 | 
			
		||||
 | 
			
		||||
    def get_int(self, maximum):
 | 
			
		||||
        while True:
 | 
			
		||||
            response = input("> ")
 | 
			
		||||
            if response.isdigit():
 | 
			
		||||
                value = int(response)
 | 
			
		||||
                if value < maximum:
 | 
			
		||||
                    return value
 | 
			
		||||
            print("Invalid input.")
 | 
			
		||||
 | 
			
		||||
class ComputerNimPlayer:
 | 
			
		||||
    def __init__(self, name):
 | 
			
		||||
        self.name = name
 | 
			
		||||
        self.strategy = LookaheadStrategy(NimGameStub(), max_depth=3, deterministic=False)
 | 
			
		||||
 | 
			
		||||
    def choose_action(self, state):
 | 
			
		||||
        action = self.strategy.choose_action(state)
 | 
			
		||||
        row, lines_to_remove = action
 | 
			
		||||
        print(f"{self.name} removes {lines_to_remove} from row {row}")
 | 
			
		||||
        return action
 | 
			
		||||
							
								
								
									
										32
									
								
								nim/view.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								nim/view.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
from nim.game_stub import NimGameStub
 | 
			
		||||
 | 
			
		||||
class NimView:
 | 
			
		||||
    def __init__(self, player0, player1):
 | 
			
		||||
        self.players = [player0, player1]
 | 
			
		||||
        self.game = NimGameStub()
 | 
			
		||||
 | 
			
		||||
    def greet(self):
 | 
			
		||||
        print(f"{self.players[0].name} and {self.players[1].name}, welcome to Nim.")
 | 
			
		||||
    
 | 
			
		||||
    def show_board(self, state):
 | 
			
		||||
        for lines_in_row in state["board"]:
 | 
			
		||||
            print("| " * lines_in_row)
 | 
			
		||||
 | 
			
		||||
    def get_action(self, state):
 | 
			
		||||
        self.show_board(state)
 | 
			
		||||
        player = self.get_current_player(state)
 | 
			
		||||
        return player.choose_action(state)
 | 
			
		||||
 | 
			
		||||
    def get_current_player(self, state):
 | 
			
		||||
        if state["first_player"]:
 | 
			
		||||
            return self.players[0]
 | 
			
		||||
        else:
 | 
			
		||||
            return self.players[1]
 | 
			
		||||
 | 
			
		||||
    def conclude(self, state):
 | 
			
		||||
        self.show_board(state)
 | 
			
		||||
        if self.game.get_reward(state) > 0:
 | 
			
		||||
            winner = self.players[0]
 | 
			
		||||
        else:
 | 
			
		||||
            winner = self.players[1]
 | 
			
		||||
        print(f"Congratulations, {winner.name}!")
 | 
			
		||||
							
								
								
									
										35
									
								
								notes.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								notes.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
# Tic Tac Toe notes
 | 
			
		||||
 | 
			
		||||
## Checkpoint 1 Notes
 | 
			
		||||
 | 
			
		||||
Which class is responsible for each of the following behaviors?
 | 
			
		||||
For each, explain how the behavior is accomplished.
 | 
			
		||||
 | 
			
		||||
### Checking to see whether the game is over
 | 
			
		||||
 | 
			
		||||
### Determining which actions are available at a particular state
 | 
			
		||||
 | 
			
		||||
### Showing the board
 | 
			
		||||
 | 
			
		||||
### Choosing which action to play on a turn
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## Checkpoint 2 Notes
 | 
			
		||||
 | 
			
		||||
### TTT Strategy
 | 
			
		||||
 | 
			
		||||
For each of the following board states, if you are playing as X
 | 
			
		||||
and it's your turn, which action would you take? Why?
 | 
			
		||||
 | 
			
		||||
   | O | O       |   | O       | X |       X | O |   
 | 
			
		||||
---+---+---   ---+---+---   ---+---+---   ---+---+---
 | 
			
		||||
 X | X |         | X |       X | O | O       |   |   
 | 
			
		||||
---+---+---   ---+---+---   ---+---+---   ---+---+---
 | 
			
		||||
   |   |         |   | O       |   |         |   |   
 | 
			
		||||
 | 
			
		||||
### Initial game state
 | 
			
		||||
 | 
			
		||||
You can get the inital game state using game.get_initial_state(). 
 | 
			
		||||
What is the current and future reward for this state? What does this mean? 
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										15
									
								
								play_nim.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								play_nim.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
from nim.game_stub import NimGameStub
 | 
			
		||||
from nim.view import NimView
 | 
			
		||||
from nim.player import HumanNimPlayer, ComputerNimPlayer
 | 
			
		||||
 | 
			
		||||
player0 = HumanNimPlayer(input("What's your name? "))
 | 
			
		||||
player1 = ComputerNimPlayer("Robot")
 | 
			
		||||
view = NimView(player0, player1)
 | 
			
		||||
game = NimGameStub()
 | 
			
		||||
 | 
			
		||||
view.greet()
 | 
			
		||||
state = game.get_initial_state()
 | 
			
		||||
while not game.is_over(state):
 | 
			
		||||
    action = view.get_action(state)
 | 
			
		||||
    state = game.get_next_state(state, action)
 | 
			
		||||
view.conclude(state)
 | 
			
		||||
							
								
								
									
										15
									
								
								play_ttt.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								play_ttt.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
from ttt.game import TTTGame
 | 
			
		||||
from ttt.view import TTTView
 | 
			
		||||
from ttt.player import TTTHumanPlayer, TTTComputerPlayer
 | 
			
		||||
 | 
			
		||||
player0 = TTTHumanPlayer("Player 1")
 | 
			
		||||
player1 = TTTHumanPlayer("Player 2")
 | 
			
		||||
game = TTTGame()
 | 
			
		||||
view = TTTView(player0, player1)
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
							
								
								
									
										34
									
								
								poetry.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								poetry.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
# This file is automatically @generated by Poetry 2.0.0 and should not be changed by hand.
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "click"
 | 
			
		||||
version = "8.1.8"
 | 
			
		||||
description = "Composable command line interface toolkit"
 | 
			
		||||
optional = false
 | 
			
		||||
python-versions = ">=3.7"
 | 
			
		||||
groups = ["main"]
 | 
			
		||||
files = [
 | 
			
		||||
    {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"},
 | 
			
		||||
    {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"},
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[package.dependencies]
 | 
			
		||||
colorama = {version = "*", markers = "platform_system == \"Windows\""}
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "colorama"
 | 
			
		||||
version = "0.4.6"
 | 
			
		||||
description = "Cross-platform colored terminal text."
 | 
			
		||||
optional = false
 | 
			
		||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
 | 
			
		||||
groups = ["main"]
 | 
			
		||||
markers = "platform_system == \"Windows\""
 | 
			
		||||
files = [
 | 
			
		||||
    {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
 | 
			
		||||
    {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[metadata]
 | 
			
		||||
lock-version = "2.1"
 | 
			
		||||
python-versions = ">=3.10,<4.0"
 | 
			
		||||
content-hash = "0272075b8c7e01c3558d126d3efff1c07b71bcde638baf2353e2f48fa2bf5db5"
 | 
			
		||||
							
								
								
									
										21
									
								
								pyproject.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								pyproject.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
			
		||||
[project]
 | 
			
		||||
name = "lab-tic-tac-toe"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
description = ""
 | 
			
		||||
authors = [
 | 
			
		||||
    {name = "Chris Proctor",email = "chris@chrisproctor.net"}
 | 
			
		||||
]
 | 
			
		||||
license = {text = "MIT"}
 | 
			
		||||
readme = "README.md"
 | 
			
		||||
requires-python = ">=3.10,<4.0"
 | 
			
		||||
dependencies = [
 | 
			
		||||
    "click (>=8.1.8,<9.0.0)"
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[build-system]
 | 
			
		||||
requires = ["poetry-core>=2.0.0,<3.0.0"]
 | 
			
		||||
build-backend = "poetry.core.masonry.api"
 | 
			
		||||
 | 
			
		||||
[tool.poetry]
 | 
			
		||||
package-mode = false
 | 
			
		||||
							
								
								
									
										96
									
								
								strategy/lookahead_strategy.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								strategy/lookahead_strategy.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,96 @@
 | 
			
		||||
from types import MethodType
 | 
			
		||||
from random import choice
 | 
			
		||||
 | 
			
		||||
class LookaheadStrategy:
 | 
			
		||||
    """A Strategy which considers the future consequences of an action.
 | 
			
		||||
 | 
			
		||||
    To initialize a LookaheadStrategy, pass in an instance of a game containing
 | 
			
		||||
    the following methods. These methods encode the rules of the game, 
 | 
			
		||||
    which a LookaheadStrategy needs to know in order to determine which move is best.
 | 
			
		||||
 | 
			
		||||
        - get_next_state: state, action -> state
 | 
			
		||||
        - get_actions: state -> [actions]
 | 
			
		||||
        - get_reward: state -> int
 | 
			
		||||
        - is_over: state -> bool
 | 
			
		||||
        - get_objective: str -> function
 | 
			
		||||
 | 
			
		||||
    Optionally, pass the following arguments to control the behavior of the LookaheadStrategy:
 | 
			
		||||
 | 
			
		||||
        - max_depth: int. A game may be too complex to search the full state tree.
 | 
			
		||||
          Setting max_depth will set a cutoff on how far ahead the LookaheadStrategy will look.
 | 
			
		||||
        - deterministic: bool. It's possible that there are multiple equally-good actions. 
 | 
			
		||||
          When deterministic is True, LookaheadStrategy will always choose the first of the 
 | 
			
		||||
          equally-good actions, so that LookaheadStrategy will always play out the same game. 
 | 
			
		||||
          When deterministic is False, LookaheadStrategy will choose randomly from all actions
 | 
			
		||||
          which are equally-good. 
 | 
			
		||||
        - Explain: When set to True, LookaheadStrategy will print out its reasoning. 
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, game, max_depth=None, deterministic=True, explain=False):
 | 
			
		||||
        self.validate_game(game)
 | 
			
		||||
        self.game = game
 | 
			
		||||
        self.max_depth = max_depth
 | 
			
		||||
        self.deterministic = deterministic
 | 
			
		||||
        self.explain = explain
 | 
			
		||||
 | 
			
		||||
    def choose_action(self, state, depth=0):
 | 
			
		||||
        """Given a state, chooses an action.
 | 
			
		||||
        This is the most important method of a Strategy, corresponding to the situation where
 | 
			
		||||
        it's a player's turn to play a game and she needs to decide what to do. 
 | 
			
		||||
 | 
			
		||||
        Strategy chooses an action by considering all possible actions, and finding the 
 | 
			
		||||
        total current and future reward which would come from playing that action. 
 | 
			
		||||
        Then we use the game's objective to choose the "best" reward. Usually bigger is better, 
 | 
			
		||||
        but in zero-sum games like tic tac toe, the players want opposite outcomes. One player
 | 
			
		||||
        wants the reward to be high, while the other wants the reward to be low.
 | 
			
		||||
 | 
			
		||||
        Once we know which reward is best, we choose an action which will lead to that reward.
 | 
			
		||||
        """
 | 
			
		||||
        possible_actions = self.game.get_actions(state)
 | 
			
		||||
        rewards = {}
 | 
			
		||||
        for action in possible_actions:
 | 
			
		||||
            future_state = self.game.get_next_state(state, action)
 | 
			
		||||
            rewards[action] = self.get_current_and_future_reward(future_state, depth=depth)
 | 
			
		||||
        objective = self.game.get_objective(state)
 | 
			
		||||
        best_reward = objective(rewards.values())
 | 
			
		||||
        best_actions = [action for action in possible_actions if rewards[action] == best_reward]
 | 
			
		||||
        if self.deterministic:
 | 
			
		||||
            action = best_actions[0]
 | 
			
		||||
        else:
 | 
			
		||||
            action = choice(best_actions)
 | 
			
		||||
        if self.explain:
 | 
			
		||||
            self.print_explanation(state, action, rewards[action], depth)
 | 
			
		||||
        return action
 | 
			
		||||
 | 
			
		||||
    def get_current_and_future_reward(self, state, depth=0):
 | 
			
		||||
        """Calculates the reward from this state, and from all future states which would be 
 | 
			
		||||
        reached, assuming all players are using this Strategy.
 | 
			
		||||
        """
 | 
			
		||||
        reward = self.game.get_reward(state)
 | 
			
		||||
        if (self.max_depth is None or depth <= self.max_depth) and not self.game.is_over(state):
 | 
			
		||||
            action = self.choose_action(state, depth=depth+1)
 | 
			
		||||
            future_state = self.game.get_next_state(state, action)
 | 
			
		||||
            reward += self.get_current_and_future_reward(future_state, depth=depth+1)
 | 
			
		||||
        return reward
 | 
			
		||||
 | 
			
		||||
    def validate_game(self, game):
 | 
			
		||||
        "Checks that the game has all the required methods."
 | 
			
		||||
        required_methods = [
 | 
			
		||||
            "get_next_state",
 | 
			
		||||
            "get_actions",
 | 
			
		||||
            "get_reward",
 | 
			
		||||
            "is_over",
 | 
			
		||||
            "get_objective",
 | 
			
		||||
        ]
 | 
			
		||||
        for method in required_methods:
 | 
			
		||||
            if not (hasattr(game, method) and isinstance(getattr(game, method), MethodType)):
 | 
			
		||||
                message = f"Game {game} does not have method {method}."
 | 
			
		||||
                raise ValueError(message)
 | 
			
		||||
 | 
			
		||||
    def print_explanation(self, state, action, reward, depth):
 | 
			
		||||
        """Prints out the current state of exploration of the state tree"""
 | 
			
		||||
        indent = '│ ' * (max(0, depth-1)) + ('├ ' if depth > 0 else '')
 | 
			
		||||
        print(f"{indent}[{reward}] Best action: {action} {state}")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										11
									
								
								strategy/random_strategy.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								strategy/random_strategy.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
from random import choice
 | 
			
		||||
 | 
			
		||||
class RandomStrategy:
 | 
			
		||||
    """A Strategy which randomly chooses a move. Not a great choice.
 | 
			
		||||
    """
 | 
			
		||||
    def __init__(self, game):
 | 
			
		||||
        self.game = game
 | 
			
		||||
 | 
			
		||||
    def choose_action(self, state):
 | 
			
		||||
        possible_actions = self.game.get_actions(state)
 | 
			
		||||
        return choice(possible_actions)
 | 
			
		||||
							
								
								
									
										61
									
								
								ttt/game.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								ttt/game.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,61 @@
 | 
			
		||||
class TTTGame:
 | 
			
		||||
    "Models a tic-tac-toe game."
 | 
			
		||||
 | 
			
		||||
    def get_initial_state(self):
 | 
			
		||||
        "Returns the game's initial state."
 | 
			
		||||
        return {
 | 
			
		||||
            "board": ['-', '-', '-', '-', '-', '-', '-', '-', '-'],
 | 
			
		||||
            "player_x": True,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    def get_next_state(self, state, action):
 | 
			
		||||
        """Given a state and an action, returns the resulting state.
 | 
			
		||||
        In the resulting state, the current player's symbol has been placed 
 | 
			
		||||
        in an empty board space, and it is the opposite player's turn.
 | 
			
		||||
        """
 | 
			
		||||
        new_board = state["board"].copy()
 | 
			
		||||
        new_board[action] = 'X' if state["player_x"] else 'O'
 | 
			
		||||
        return {
 | 
			
		||||
            "board": new_board,
 | 
			
		||||
            "player_x": not state["player_x"],
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    def get_actions(self, state):
 | 
			
		||||
        "Returns a list of the indices of empty spaces"
 | 
			
		||||
        return [index for index in range(9) if state["board"][index] == '-']
 | 
			
		||||
 | 
			
		||||
    def is_over(self, state):
 | 
			
		||||
        "Checks whether the game is over."
 | 
			
		||||
        return self.board_is_full(state) or self.check_winner(state, 'X') or self.check_winner(state, 'O')
 | 
			
		||||
 | 
			
		||||
    def get_reward(self, state):
 | 
			
		||||
        """Determines the reward associated with reaching this state.
 | 
			
		||||
        For tic-tac-toe, the two opponents each want a different game outcome. So 
 | 
			
		||||
        we set the reward for X winning to 1 and the reward for O winning to -1.
 | 
			
		||||
        All other states (unfinished games and games which ended in a draw) are worth 0.
 | 
			
		||||
        """
 | 
			
		||||
        if self.check_winner(state, 'X'):
 | 
			
		||||
            return 1
 | 
			
		||||
        elif self.check_winner(state, 'O'):
 | 
			
		||||
            return -1
 | 
			
		||||
        else:
 | 
			
		||||
            return 0
 | 
			
		||||
 | 
			
		||||
    def get_objective(self, state):
 | 
			
		||||
        """Returns a player's objective, or a function describing what a player wants. 
 | 
			
		||||
        This function should choose the best value from a list. In tic tac toe, the players
 | 
			
		||||
        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`.
 | 
			
		||||
        """
 | 
			
		||||
        return max if state["player_x"] else min
 | 
			
		||||
 | 
			
		||||
    def board_is_full(self, state):
 | 
			
		||||
        "Checks whether all the spaces in the board are occupied."
 | 
			
		||||
        for space in state["board"]:
 | 
			
		||||
            if space == '-':
 | 
			
		||||
                return False
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def check_winner(self, state, symbol):
 | 
			
		||||
        "Checks whether the player with `symbol` has won the game."
 | 
			
		||||
        return False
 | 
			
		||||
							
								
								
									
										33
									
								
								ttt/player.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								ttt/player.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
from click import Choice, prompt
 | 
			
		||||
from strategy.random_strategy import RandomStrategy
 | 
			
		||||
from ttt.game import TTTGame
 | 
			
		||||
import random
 | 
			
		||||
 | 
			
		||||
class TTTHumanPlayer:
 | 
			
		||||
    "A human tic tac toe player."
 | 
			
		||||
 | 
			
		||||
    def __init__(self, name):
 | 
			
		||||
        "Sets up the player."
 | 
			
		||||
        self.name = name
 | 
			
		||||
        self.game = TTTGame()
 | 
			
		||||
 | 
			
		||||
    def choose_action(self, state):
 | 
			
		||||
        "Chooses an action by prompting the player for a choice."
 | 
			
		||||
        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"
 | 
			
		||||
 | 
			
		||||
    def __init__(self, name):
 | 
			
		||||
        "Sets up the player."
 | 
			
		||||
        self.name = name
 | 
			
		||||
        self.strategy = RandomStrategy(TTTGame())
 | 
			
		||||
 | 
			
		||||
    def choose_action(self, state):
 | 
			
		||||
        "Chooses a random move from the moves available."
 | 
			
		||||
        action = self.strategy.choose_action(state)
 | 
			
		||||
        print(f"{self.name} chooses {action}.")
 | 
			
		||||
        return action
 | 
			
		||||
							
								
								
									
										75
									
								
								ttt/view.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								ttt/view.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,75 @@
 | 
			
		||||
from ttt.game import TTTGame
 | 
			
		||||
import click
 | 
			
		||||
 | 
			
		||||
class TTTView:
 | 
			
		||||
    "Handles user interaction with a tic-tac-toe game."
 | 
			
		||||
    greeting = "Welcome to tic-tac-toe"
 | 
			
		||||
    goodbye = "Well, that's a wrap."
 | 
			
		||||
    divider = "---+---+---"
 | 
			
		||||
    x_color = "red"
 | 
			
		||||
    o_color = "blue"
 | 
			
		||||
    option_color = "bright_black"
 | 
			
		||||
 | 
			
		||||
    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 = 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, state):
 | 
			
		||||
        "Shows the board and asks the current player for their choice of action."
 | 
			
		||||
        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(state)
 | 
			
		||||
 | 
			
		||||
    def print_board(self, state):
 | 
			
		||||
        "Prints the current board, showing indices of available spaces"
 | 
			
		||||
        print(self.format_row(state, [0, 1, 2]))
 | 
			
		||||
        print(self.divider)
 | 
			
		||||
        print(self.format_row(state, [3, 4, 5]))
 | 
			
		||||
        print(self.divider)
 | 
			
		||||
        print(self.format_row(state, [6, 7, 8]))
 | 
			
		||||
 | 
			
		||||
    def format_row(self, state, indices):
 | 
			
		||||
        "Returns a string for one row in the board, like ' X | O | X '"
 | 
			
		||||
        spaces = [self.format_value(state, i) for i in indices]
 | 
			
		||||
        return f" {spaces[0]} | {spaces[1]} | {spaces[2]} "
 | 
			
		||||
 | 
			
		||||
    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 state["board"][index] == 'X':
 | 
			
		||||
            return click.style('X', fg=self.x_color)
 | 
			
		||||
        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, state):
 | 
			
		||||
        """Says goodbye.
 | 
			
		||||
        """
 | 
			
		||||
        self.print_board(state)
 | 
			
		||||
        if self.game.check_winner(state, 'X'):
 | 
			
		||||
            winner = self.players['X']
 | 
			
		||||
        elif self.game.check_winner(state, 'O'):
 | 
			
		||||
            winner = self.players['O']
 | 
			
		||||
        else:
 | 
			
		||||
            winner = None
 | 
			
		||||
        print(self.goodbye)
 | 
			
		||||
        if winner:        
 | 
			
		||||
            print(f"Congratulations to {winner.name}.")
 | 
			
		||||
        else:
 | 
			
		||||
            print("Nobody won this game.")
 | 
			
		||||
		Reference in New Issue
	
	Block a user