From 900c4622ad693bccd9b8420e8be0c2d4631ef4fa Mon Sep 17 00:00:00 2001 From: Chris Proctor Date: Thu, 28 Apr 2022 15:03:52 -0400 Subject: [PATCH] Initial version of lab --- .commit_template | 9 +++++++ .gitignore | 1 + play.py | 14 +++++++++++ poetry.lock | 33 ++++++++++++++++++++++++++ pyproject.toml | 16 +++++++++++++ ttt_game.py | 45 +++++++++++++++++++++++++++++++++++ ttt_player.py | 35 +++++++++++++++++++++++++++ ttt_view.py | 61 ++++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 214 insertions(+) create mode 100644 .commit_template create mode 100644 .gitignore create mode 100644 play.py create mode 100644 poetry.lock create mode 100644 pyproject.toml create mode 100644 ttt_game.py create mode 100644 ttt_player.py create mode 100644 ttt_view.py diff --git a/.commit_template b/.commit_template new file mode 100644 index 0000000..f634563 --- /dev/null +++ b/.commit_template @@ -0,0 +1,9 @@ +(Commit summary. Replace this with a one-line description of this commit.) + +What I changed +(Replace this with a description of what you changed in this commit. This should be 1-2 sentences.) + +Why I changed it +(Describe why you made these changes. Were you working toward a goal? Did you reorganize your code? This should be 1-2 sentences.) + +Estimate for remaining time to finish assignment: [REPLACE WITH TIME ESTIMATE] diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96403d3 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__/* diff --git a/play.py b/play.py new file mode 100644 index 0000000..339ab73 --- /dev/null +++ b/play.py @@ -0,0 +1,14 @@ +from ttt_game import TTTGame +from ttt_view import TTTView +from ttt_player import TTTHumanPlayer + +player0 = TTTHumanPlayer("Player 1") +player1 = TTTHumanPlayer("Player 2") +game = TTTGame(player0, player1) +view = TTTView() + +view.greet(game) +while not game.is_over(): + move = view.get_move(game) + game.play_move(move) +view.conclude(game) diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..bdfa763 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,33 @@ +[[package]] +name = "click" +version = "8.1.3" +description = "Composable command line interface toolkit" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[metadata] +lock-version = "1.1" +python-versions = "^3.9" +content-hash = "b1fe87cf4a78305c8810dfd14d6b6354185272e19826bd925ee2d6c7d1a8c763" + +[metadata.files] +click = [ + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, +] +colorama = [ + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..fc28a9c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,16 @@ +[tool.poetry] +name = "mwc-pedprog-unit02-lab02" +version = "0.1.0" +description = "" +authors = ["Chris Proctor "] +license = "MIT" + +[tool.poetry.dependencies] +python = "^3.9" +click = "^8.1.3" + +[tool.poetry.dev-dependencies] + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/ttt_game.py b/ttt_game.py new file mode 100644 index 0000000..0043ab6 --- /dev/null +++ b/ttt_game.py @@ -0,0 +1,45 @@ + +class TTTGame: + """Models a tic-tac-toe game. + """ + + def __init__(self, playerX, playerO): + self.board = [None] * 9 + self.turn_index = 0 + self.players = { + 'X': playerX, + 'O': playerO, + } + + def play_move(self, move): + if not self.is_valid_move(move): + raise ValueError(f"Illegal move {move} with board {self.board}.") + self.board[move] = self.get_current_player_symbol() + self.turn_index += 1 + + def get_valid_moves(self): + "Returns a list of the indices of empty spaces" + return [index for index in range(9) if self.board[index] is None] + + def is_valid_move(self, move): + "Checks whether a move is valid." + return move in self.get_valid_moves() + + def get_current_player_symbol(self): + "Returns the symbol of the current player" + if self.turn_index % 2 == 0: + return 'X' + else: + return 'O' + + def get_current_player(self): + "Returns the symbol of the current player and the current player" + return self.players[self.get_current_player_symbol()] + + def is_over(self): + "Checks whether the game is over." + return self.check_winner('X') or self.check_winner('O') + + def check_winner(self, symbol): + "Checks whether the player with `symbol` has won the game." + return False diff --git a/ttt_player.py b/ttt_player.py new file mode 100644 index 0000000..88dc823 --- /dev/null +++ b/ttt_player.py @@ -0,0 +1,35 @@ +from click import Choice, prompt +import random + +class TTTPlayer: + "A tic tac toe player." + + def __init__(self, name): + "Sets up the player." + self.name = name + + 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!") + +class TTTHumanPlayer(TTTPlayer): + "A human tic tac toe player." + + def choose_move(self, game): + "Chooses a move by prompting the player for a choice." + choices = Choice([str(i) for i in game.get_valid_moves()]) + move = prompt("> ", type=choices, show_choices=False) + return int(move) + +class TTTComputerPlayer(TTTPlayer): + "A computer tic tac toe player" + + def choose_move(self, game): + "Chooses a random move from the moves available." + return random.choice(game.get_valid_moves()) + diff --git a/ttt_view.py b/ttt_view.py new file mode 100644 index 0000000..992c09f --- /dev/null +++ b/ttt_view.py @@ -0,0 +1,61 @@ +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 greet(self, game): + "Starts a new game by greeting the players." + print(self.greeting) + print(f"{game.players['X']} will play as X.") + print(f"{game.players['O']} will play as O.") + + def get_move(self, game): + "Shows the board and asks the current player for their choice of move." + self.print_board_with_options(game) + player = game.get_current_player() + print(f"{player.name}, it's your move.") + return player.choose_move(game) + + def print_board_with_options(self, game): + "Prints the current board, showing indices of available spaces" + print(self.format_row(game, [0, 1, 2])) + print(self.divider) + print(self.format_row(game, [3, 4, 6])) + print(self.divider) + print(self.format_row(game, [6, 7, 8])) + + def format_row(self, game, indices): + "Returns a string for one row in the board, like ' X | O | X '" + spaces = [self.format_value(game, i) for i in indices] + return f" {spaces[0]} | {spaces[1]} | {spaces[2]} " + + def format_value(self, game, 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.board[index] == 'X': + return click.style('X', fg=self.x_color) + elif game.board[index] == 'O': + return click.style('O', fg=self.o_color) + else: + return click.style(index, fg=self.option_color) + + def conclude(self, game): + """Says goodbye. + """ + if game.check_winner('X'): + winner = game.players['X'] + elif game.check_winner('X'): + winner = game.players['X'] + else: + raise Exception("Tried to conclude a game which wasn't over!") + print(self.goodbye) + print(f"Congratulations to {winner.name}.")