187 lines
6.4 KiB
Python
Executable File
187 lines
6.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
from random import choice
|
|
from enum import Enum
|
|
from sys import exit
|
|
from textwrap import dedent
|
|
|
|
|
|
class CellState(Enum):
|
|
"""
|
|
Enum to track the state for a cell on the board
|
|
X is a cell marked by the computer,
|
|
O is a cell marked by the player,
|
|
U is a cell that is unmarked
|
|
"""
|
|
|
|
X = False
|
|
O = True
|
|
U = None
|
|
|
|
|
|
class TicTacToe:
|
|
def __init__(self) -> None:
|
|
self.board: list[list[CellState]] = [
|
|
[CellState.U, CellState.U, CellState.U],
|
|
[CellState.U, CellState.U, CellState.U],
|
|
[CellState.U, CellState.U, CellState.U],
|
|
]
|
|
self.turn = 0
|
|
|
|
# Duplicate code in this function because I don't think I'm getting
|
|
# a mutable reference in Python, and I don't know a better way :pensive:
|
|
def change_state(self, cell: int, state: CellState) -> None:
|
|
# Data integreity check cause this is Python rip
|
|
if cell not in range(1, 10):
|
|
raise ValueError(f"Cell {cell} is not a number 1-9.")
|
|
if state.value is None:
|
|
raise ValueError("Cell cannot be reset to initial state")
|
|
# Okay cool now actually changing the state
|
|
# Decrements cell by one to turn into a valid index
|
|
index: int = cell - 1
|
|
# Decides the row using floor divison on the index
|
|
# (the amount of times index evenly divides into 3 is what list it will be in)
|
|
row: int = index // 3
|
|
# Gets the remanider of the previous operation to decide what row it's in
|
|
col: int = index % 3
|
|
self.board[row][col] = state
|
|
|
|
def get_cell(self, cell: int):
|
|
if cell not in range(1, 10):
|
|
raise ValueError(f"Cell {cell} is not a number 1-9.")
|
|
index: int = cell - 1
|
|
# Decides the row using floor divison on the index
|
|
# (the amount of times index evenly divides into 3 is what list it will be in)
|
|
row: int = index // 3
|
|
# Gets the remanider of the previous operation to decide what row it's in
|
|
col: int = index % 3
|
|
return self.board[row][col]
|
|
|
|
def display_cell(self, cell: int) -> str:
|
|
sign = self.get_cell(cell).name
|
|
return sign if not sign == "U" else str(cell)
|
|
|
|
def unmarked_cells(self) -> tuple[int, ...]:
|
|
unmarked: list[int] = []
|
|
for i in range(1, 10):
|
|
if self.get_cell(i).value is None:
|
|
unmarked.append(i)
|
|
else:
|
|
continue
|
|
return tuple(unmarked)
|
|
|
|
|
|
def main():
|
|
tic_tac_toe = TicTacToe()
|
|
# Game loop
|
|
while True:
|
|
# Check to see if the game tied
|
|
# (I'm too lazy to think about using turn count to do this quicker)
|
|
if len(tic_tac_toe.unmarked_cells()) == 0:
|
|
print("What a tie!")
|
|
break
|
|
# Incriment turn count
|
|
tic_tac_toe.turn += 1
|
|
# Emulate computer turn
|
|
draw_move(tic_tac_toe)
|
|
display_board(tic_tac_toe)
|
|
# Check if computer won
|
|
if victory_for(tic_tac_toe, CellState.X):
|
|
print("Computer won :(")
|
|
break
|
|
# Start player turn
|
|
enter_move(tic_tac_toe)
|
|
display_board(tic_tac_toe)
|
|
# Check if player won
|
|
if victory_for(tic_tac_toe, CellState.O):
|
|
print("You won!")
|
|
break
|
|
|
|
|
|
def display_board(board: TicTacToe):
|
|
# The function accepts one parameter containing the board's current status
|
|
# and prints it out to the console.
|
|
board_display = f"""\
|
|
+-------+-------+-------+
|
|
| | | |
|
|
| {board.display_cell(1)} | {board.display_cell(2)} | {board.display_cell(3)} |
|
|
| | | |
|
|
+-------+-------+-------+
|
|
| | | |
|
|
| {board.display_cell(4)} | {board.display_cell(5)} | {board.display_cell(6)} |
|
|
| | | |
|
|
+-------+-------+-------+
|
|
| | | |
|
|
| {board.display_cell(7)} | {board.display_cell(8)} | {board.display_cell(9)} |
|
|
| | | |
|
|
+-------+-------+-------+
|
|
"""
|
|
print(dedent(board_display), end="")
|
|
|
|
|
|
def enter_move(board: TicTacToe) -> None:
|
|
# The function accepts the board's current status, asks the user about their move,
|
|
# checks the input, and updates the board according to the user's decision.
|
|
while True:
|
|
user_input: str = input("Enter your move: ")
|
|
cell: int
|
|
try:
|
|
cell = int(user_input)
|
|
except ValueError:
|
|
print("Please enter a valid value")
|
|
continue
|
|
if cell not in board.unmarked_cells():
|
|
print("Please choose an unoccupied sqare")
|
|
continue
|
|
break
|
|
board.change_state(cell, CellState.O)
|
|
|
|
|
|
def make_list_of_free_fields(board: TicTacToe) -> list[tuple[int, int]]:
|
|
# The function browses the board and builds a list of all the free squares;
|
|
# the list consists of tuples, while each tuple is a pair of row and column numbers.
|
|
free_fields: list[tuple[int, int]] = []
|
|
unmarked_cells = board.unmarked_cells()
|
|
for i in unmarked_cells:
|
|
cell = i
|
|
free_fields.append((cell // 3 + 1, cell % 3 + 1))
|
|
return free_fields
|
|
|
|
|
|
def victory_for(board: TicTacToe, sign: CellState) -> bool:
|
|
# The function analyzes the board's status in order to check if
|
|
# the player using 'O's or 'X's has won the game
|
|
|
|
# The is certainly a better way to approach this, but oh well
|
|
# For example, if any of the spaces in a condition are unmarked, then it should be ignored
|
|
row1 = board.board[0]
|
|
row2 = board.board[1]
|
|
row3 = board.board[2]
|
|
col1 = [row1[0], row2[0], row3[0]]
|
|
col2 = [row1[1], row2[1], row3[1]]
|
|
col3 = [row1[2], row2[2], row3[2]]
|
|
diag1 = [row1[0], row2[1], row3[2]]
|
|
diag2 = [row1[2], row2[1], row3[0]]
|
|
win_conditions = [row1, row2, row3, col1, col2, col3, diag1, diag2]
|
|
for condition in win_conditions:
|
|
if all(state == sign for state in condition):
|
|
return True
|
|
return False
|
|
|
|
|
|
def draw_move(board: TicTacToe):
|
|
# The function draws the computer's move and updates the board.
|
|
# Puts an X in the middle of the board on the first turn, otherwise
|
|
computer_cell: int = 5 if board.turn == 1 else choice(board.unmarked_cells())
|
|
board.change_state(computer_cell, CellState.X)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
main()
|
|
exit(0)
|
|
except KeyboardInterrupt:
|
|
print("\nQuitting game...")
|
|
exit(0)
|
|
except:
|
|
exit(1)
|