Last active
December 9, 2021 21:49
-
-
Save cwshevlin/327ba45e575c9c7b04f318aee89eb52e to your computer and use it in GitHub Desktop.
A command line tic tac toe game
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from typing import Optional | |
import argparse | |
def _validate_number_of_players(number_of_players: int): | |
if number_of_players != 2: | |
raise ValueError('Tic Tac Toe is a 2 player game for now.') | |
def _validate_player_mark(player_mark: str): | |
if len(player_mark) != 1: | |
raise ValueError('Please keep your tic tac toe marks to one character.') | |
def _validate_row_and_column(row: str, column: str): | |
try: | |
int(row) | |
int(column) | |
except ValueError: | |
raise ValueError('Please provide numbers for the rows and columns.') | |
class Game: | |
def __init__(self): | |
self.current_player_index: int = 0 | |
self.board: Board = Board(3, 3) | |
self.players: list[Player] = [] | |
def initialize_players(self): | |
""" | |
Initialize the players and get names and marks as input from the player. | |
""" | |
print('Welcome to Tic Tac Toe! How many players are playing?') | |
number_of_players = int(input()) | |
_validate_number_of_players(int(number_of_players)) | |
for player_number in range(number_of_players): | |
print(f'Player {player_number + 1}, what is your name?') | |
player_name = input() | |
print(f'Hi {player_name}! What would you like to use to mark your moves?') | |
player_mark = input() | |
_validate_player_mark(player_mark) | |
self.players.append(Player(player_name, player_mark)) | |
def get_current_player(self): | |
return self.players[self.current_player_index] | |
def complete_turn(self): | |
""" | |
Marks the board with the player's input, checks if the board is winning, and increments the player index. | |
""" | |
print(f'{self.get_current_player().name}, your turn! Where would you like to play?') | |
row = input('Row:') | |
column = input('Column:') | |
try: | |
self.board.mark_turn(self.get_current_player(), int(row), int(column)) | |
except ValueError as e: | |
print(f'\n{e}\n') | |
self.complete_turn() | |
return | |
if self.board.is_board_winning(): | |
print(f'\n\n{self.get_current_player().name} wins!\n\n') | |
print(self.board.print_current_board()) | |
elif self.board.is_board_full(): | |
print(f'\n\nCat\'s game!\n\n') | |
print(self.board.print_current_board()) | |
self._increment_player_index() | |
def _increment_player_index(self): | |
self.current_player_index += 1 | |
if self.current_player_index >= len(self.players): | |
self.current_player_index = 0 | |
class Player: | |
def __init__(self, name: Optional[str] = None, mark: Optional[str] = None): | |
self.mark = mark | |
self.name = name | |
class Board: | |
def __init__(self, number_of_rows: int = 3, number_of_columns: int = 3): | |
self.board: list[list[str]] = [[''] * number_of_columns for i in range(number_of_rows)] | |
self.number_of_rows = number_of_rows | |
self.number_of_columns = number_of_columns | |
self.past_moves: list[Move] = [] | |
def mark_turn(self, player: Player, row: int, column: int): | |
""" | |
Receives the row and column from the player, records a move, and updates the board. | |
""" | |
row_index = row - 1 | |
column_index = column - 1 | |
self._validate_row_and_column(row_index, column_index) | |
move = Move(player, player.mark, row, column) | |
self.past_moves.append(move) | |
self.board[row_index][column_index] = player.mark | |
self.print_current_board() | |
def print_current_board(self): | |
board_string = '-------------\n\n' | |
for i in range(self.number_of_rows): | |
board_string += '|' | |
for j in range(self.number_of_columns): | |
mark = self.board[i][j] | |
if not mark: | |
mark = ' ' | |
board_string += f' {mark} |' | |
board_string += '\n\n-------------\n\n' | |
print(f'\n\nLet\'s take a look at the board: \n\n{board_string}') | |
def is_board_winning(self) -> bool: | |
""" | |
Check rows, columns, and diagonals for the same character | |
""" | |
return self._are_diagonals_winning() or self._are_rows_winning() or self._are_columns_winning() | |
def is_board_full(self) -> bool: | |
""" | |
Returns True if the board is full of marks, False if there is at least one blank position | |
""" | |
full = True | |
for i in range(self.number_of_rows): | |
for j in range(self.number_of_columns): | |
if not self.board[i][j]: | |
full = False | |
return full | |
def _are_diagonals_winning(self) -> bool: | |
""" | |
Check diagonals for winning characters | |
""" | |
first_diagonal_wins = self.board[0][0] and (self.board[0][0] == self.board[1][1] == self.board[2][2]) | |
second_diagonal_wins = self.board[0][2] and (self.board[0][2] == self.board[1][1] == self.board[2][0]) | |
return first_diagonal_wins or second_diagonal_wins | |
def _are_rows_winning(self) -> bool: | |
""" | |
Check rows for winning characters | |
""" | |
first_row_wins = self.board[0][0] and (self.board[0][0] == self.board[0][1] == self.board[0][2]) | |
second_row_wins = self.board[1][0] and (self.board[1][0] == self.board[1][1] == self.board[1][2]) | |
third_row_wins = self.board[2][0] and (self.board[2][0] == self.board[2][1] == self.board[2][2]) | |
return first_row_wins or second_row_wins or third_row_wins | |
def _are_columns_winning(self) -> bool: | |
""" | |
Check columns for winning characters | |
""" | |
first_column_wins = self.board[0][0] and (self.board[0][0] == self.board[1][0] == self.board[2][0]) | |
second_column_wins = self.board[0][1] and (self.board[0][1] == self.board[1][1] == self.board[2][1]) | |
third_column_wins = self.board[0][2] and (self.board[0][2] == self.board[1][2] == self.board[2][2]) | |
return first_column_wins or second_column_wins or third_column_wins | |
def _validate_row_and_column(self, row, column): | |
if not (row < self.number_of_rows and column < self.number_of_columns): | |
raise ValueError('Moves must be within the bounds of the board') | |
if not self.board[row][column] == '': | |
raise ValueError('There is already a mark at that position, choose another one') | |
class Move: | |
def __init__(self, player: Player, mark: str, row: int, column: int): | |
self.player = player | |
self.mark = mark | |
self.row = row | |
self.column = column | |
def game_loop(): | |
game = Game() | |
game.initialize_players() | |
while not game.board.is_board_winning() and not game.board.is_board_full(): | |
game.complete_turn() | |
print('\n\nThanks for playing!') | |
parser = argparse.ArgumentParser(description='Play a tic tac toe game') | |
parser.add_argument('command', metavar='command', type=str, | |
help='"new_game" to start a new game or "rules" to learn the rules.') | |
args = parser.parse_args() | |
if args.command == 'new_game': | |
print('Starting a new game.\n') | |
game_loop() | |
elif args.command == 'rules': | |
print(f'Via Wikipedia: \n\n ' | |
f'"Tic-tac-toe is played on a three-by-three grid by two players, ' | |
f'who alternately place the marks X and O in one of the nine ' | |
f'spaces in the grid." \n\n' | |
f'To win the game, get three of your marks in a row before your opponent.') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment