Skip to content

Instantly share code, notes, and snippets.

@Diapolo10
Last active September 14, 2023 01:07
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Diapolo10/0af07b0f4c3036ad8353fbc41e1c31b1 to your computer and use it in GitHub Desktop.
Save Diapolo10/0af07b0f4c3036ad8353fbc41e1c31b1 to your computer and use it in GitHub Desktop.
[Python] Flexible Tic Tac Toe
"""Tic-Tac-Toe implementation"""
from typing import Literal, get_args
# Custom type annotations
PlayerSymbol = Literal['X', 'O']
BoardSymbol = Literal[' ', PlayerSymbol]
Board = list[list[BoardSymbol]]
BOARD_WIDTH = 3
BOARD_HEIGHT = 3
VALID_PLAYER_SYMBOLS: tuple[PlayerSymbol] = get_args(PlayerSymbol)
def choose_side() -> tuple[PlayerSymbol, ...]:
player_symbol = None
while player_symbol not in VALID_PLAYER_SYMBOLS:
player_symbol = input(f"Player, please choose your side (one of: {', '.join(VALID_PLAYER_SYMBOLS)}): ").strip().upper()[:1]
if player_symbol == VALID_PLAYER_SYMBOLS[0]:
return VALID_PLAYER_SYMBOLS
return tuple(reversed(VALID_PLAYER_SYMBOLS))
class TicTacToe:
def __init__(self, players: tuple[PlayerSymbol]) -> None:
self.players = players
self.board = [
[' ' for _ in range(BOARD_WIDTH)]
for _ in range(BOARD_HEIGHT)
]
self.winner = None
self.game_loop()
def game_loop(self):
while self.winner is None and self.board_has_space():
for player_num, player in enumerate(self.players, 1):
self.render_board()
player_choice = self.ask_input(player_num)
self.update_board(player_choice, player)
winner = self.check_winner()
if winner or not self.board_has_space():
break
self.render_board()
if winner is None:
print("The game is finished! It is a tie.")
for player_num, player in enumerate(self.players, 1):
if player == self.winner:
print(f"Player {player_num} has won the game!")
def check_winner(self) -> PlayerSymbol | None:
"""Check which player has met the winning conditions."""
for player in self.players:
# Check rows
for row in self.board:
if all(cell == player for cell in row):
return player
# Check columns
for col in zip(*self.board):
if all(cell == player for cell in col):
return player
# Check crosses (assumes 3x3 board)
if all(cell == player
for cell in self.board[::4]):
return player
if all(cell == player
for cell in self.board[2:-1:2]):
return player
def board_has_space(self) -> bool:
"""Return True if there is space left on the board."""
return any(
cell == ' '
for row in self.board
for cell in row
)
def render_board(self):
"""Print out the current state of the board."""
board_text = '-+-+-'.join(
'|'.join(cols)
for row in self.board
for cols in row
)
print(board_text)
def update_board(self, coordinates: tuple[int, int], symbol: PlayerSymbol):
"""Place a new symbol on the board."""
x, y = coordinates
current_symbol = self.board[x][y]
if current_symbol not in VALID_PLAYER_SYMBOLS:
self.board[x][y] = symbol
else:
print(f"{current_symbol} is already in the location {coordinates}. Skipping this turn.")
def ask_input(self, player_number: int, is_ai: bool = False) -> tuple[int, int]:
"""Ask a player to enter their desired coordinates to place a new symbol."""
text_coordinates = input(
f"Player {player_number}, please make your choice: "
).strip().split()
coordinates = (
int(text_coordinates[0]),
int(text_coordinates[1]),
)
return coordinates
def main():
print("Welcome to the Tic-Tac-Toe Game!")
players = choose_side()
game = TicTacToe(players)
game.game_loop()
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment