|
#!/usr/bin/env python3 |
|
|
|
import random |
|
|
|
# CONSTANTS # |
|
|
|
X = "❎" |
|
O = "⭕" |
|
PIECES = [X, O] |
|
|
|
HORIZONTAL_LINE = "〰〰〰〰〰〰〰〰〰" |
|
|
|
YES = "yes" |
|
NO = "no" |
|
YES_NO_RESPONSES = (YES, NO) |
|
|
|
WINNING_MOVES = [ |
|
["1", "4", "7"], # left, vertival |
|
["2", "5", "8"], # middle, vertical |
|
["3", "6", "9"], # right, vertical |
|
["1", "2", "3"], # top, horizontal |
|
["4", "5", "6"], # middle, horizontal |
|
["7", "8", "9"], # bottom, horizontal |
|
["1", "5", "9"], # top left to bottom right, diagonal |
|
["3", "5", "7"], # top right to bottom left, diagonal |
|
] |
|
|
|
# the board comprises of numbered emoji |
|
# we use 1-indexing for ease of use. |
|
EMOJI = ["1️⃣ ", "2️⃣ ", "3️⃣ ", "4️⃣ ", "5️⃣ ", "6️⃣ ", "7️⃣ ", "8️⃣ ", "9️⃣ "] |
|
EMOJI_TO_CELL = {emoji: str(cell) for cell, emoji in enumerate(EMOJI, start=1)} |
|
|
|
|
|
class Board(object): |
|
def __init__(self, players): |
|
|
|
self.players = players |
|
|
|
# set an empty board |
|
self.labels = list(EMOJI_TO_CELL.keys()) |
|
self.cells = list(EMOJI_TO_CELL.values()) |
|
self.moves = {p: [] for p in self.players} |
|
|
|
def set_cell(self, cell, player): |
|
idx = int(cell) - 1 # handle 1-indexing of cell pieces |
|
self.cells[idx] = player |
|
self.labels[idx] = player.piece |
|
self.moves[player].append(cell) |
|
|
|
@property |
|
def open_cells(self): |
|
return list(filter(lambda cell: not isinstance(cell, Player), self.cells)) |
|
|
|
@property |
|
def is_full(self): |
|
return len(self.open_cells) == 0 |
|
|
|
def is_player_winning(self, player): |
|
return len(self.active_winning_moves_for_player(player)) > 0 |
|
|
|
def active_winning_moves_for_player(self, player): |
|
# find the list of winning moves that are currently "in-play" for a given player. |
|
return list( |
|
filter( |
|
lambda move: all([cell in self.moves[player] for cell in move]), |
|
WINNING_MOVES, |
|
) |
|
) |
|
|
|
def __repr__(self): |
|
return f""" |
|
{ self.labels[0] } { self.labels[1] } { self.labels[2] } |
|
|
|
{ self.labels[3] } { self.labels[4] } { self.labels[5] } |
|
|
|
{ self.labels[6] } { self.labels[7] } { self.labels[8] } |
|
""" |
|
|
|
|
|
class Player(object): |
|
def __init__(self, piece): |
|
self.piece = piece |
|
|
|
def __str__(self): |
|
return self.piece |
|
|
|
def __repr__(self): |
|
return self.piece |
|
|
|
|
|
class TicTacToe(object): |
|
def __init__(self): |
|
self.players = [Player(X), Player(O)] |
|
self.board = Board(self.players) |
|
self.last_player = None |
|
|
|
@property |
|
def current_player(self): |
|
return self.player2 if self.last_player == self.player1 else self.player1 |
|
|
|
def move_for_player(self, player): |
|
while True: |
|
print(HORIZONTAL_LINE) |
|
print(f"{self.current_player}, choose a cell:") |
|
cell_name = str(input()) |
|
if cell_name not in self.board.open_cells: |
|
open_cell_list = ", ".join(self.board.open_cells) |
|
print(f"Try again! Cell must be one of: {open_cell_list}") |
|
else: |
|
break |
|
|
|
self.board.set_cell(cell_name, player) |
|
self.last_player = player |
|
|
|
def turn(self): |
|
self.move_for_player(self.current_player) |
|
print(HORIZONTAL_LINE) |
|
print(self.board) |
|
|
|
def play(self): |
|
|
|
# shuffle and set the players |
|
random.shuffle(self.players) |
|
self.player1, self.player2 = self.players |
|
|
|
# print the empty board |
|
print(self.board) |
|
|
|
while True: |
|
self.turn() |
|
if self.board.is_player_winning(self.last_player): |
|
print(HORIZONTAL_LINE) |
|
print(f"{self.last_player} wins! ") |
|
break |
|
|
|
elif self.board.is_full: |
|
print(HORIZONTAL_LINE) |
|
print("It's a tie!") |
|
break |
|
|
|
|
|
class Game(object): |
|
def ask_for_retry(self): |
|
response = "" |
|
print(f"Would you like to play again, {YES}/{NO}?") |
|
while not (response in YES_NO_RESPONSES): |
|
response = input().lower() |
|
return response == YES |
|
|
|
def play(self): |
|
print("tic tac toe ") |
|
print(HORIZONTAL_LINE) |
|
while True: |
|
TicTacToe().play() |
|
print(HORIZONTAL_LINE) |
|
if self.ask_for_retry(): |
|
continue |
|
else: |
|
print("Goodbye!") |
|
break |
|
|
|
|
|
if __name__ == "__main__": |
|
Game().play() |