From d0c717fafe2870e284ae9fe36ca6ee6faa4d37a0 Mon Sep 17 00:00:00 2001 From: foreverpyrite Date: Sun, 5 Oct 2025 21:23:50 +0000 Subject: [PATCH] This is all so pointless lol, but it's funny yeah --- .gitignore | 10 ++ .python-version | 1 + README.md | 299 ++++++++++++++++++++++++++++++++++++++++++++++++ main.py | 186 ++++++++++++++++++++++++++++++ pyproject.toml | 7 ++ 5 files changed, 503 insertions(+) create mode 100644 .gitignore create mode 100644 .python-version create mode 100644 README.md create mode 100755 main.py create mode 100644 pyproject.toml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..505a3b1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +# Python-generated files +__pycache__/ +*.py[oc] +build/ +dist/ +wheels/ +*.egg-info + +# Virtual environments +.venv diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..24ee5b1 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/README.md b/README.md new file mode 100644 index 0000000..be8120e --- /dev/null +++ b/README.md @@ -0,0 +1,299 @@ +# tic-tac-toe +This silly little repo showcases the stupid amount of effort I put into a python course. + +You can see in the full spec it just wanted me to write out four silly little functions, but that's not what I did. +Because I was trying to be thorough and not hardcode anything. + +I ended up hardcoding the display and logic for who wins, along with poor detection for ties. +Way way way more effort than it was worth and I don't even think it'll get accepted lol. +~~Plot twist: You didn't have to do it at all!!!~~ + +## Running the program +uhh...okay I guess? +Just download `main.py` and run `py main.py` on Windows or `./main.py` on Linux + +## The specific instructions +
+It did NOT tell me to make classes or anything, it just wanted simple programming + +### Scenario + +Your task is to write **a simple program which pretends to play *tic-tac-toe* with the user**. To make it all easier for you, we've decided to simplify the game. Here are our assumptions: + +- the computer (i.e., your program) should play the game using `'X's`; +- the user (e.g., you) should play the game using `'O'`s; +- the first move belongs to the computer − it always puts its first `'X'` in the middle of the board; +- all the squares are numbered row by row starting with `1` (see the example session below for reference) +- the user inputs their move by entering the number of the square they choose − the number must be valid, i.e., it must be an integer, it must be greater than `0` and less than `10`, and it cannot point to a field which is already occupied; +- the program checks if the game is over − there are four possible verdicts: the game should continue, the game ends with a tie, you win, or the computer wins; +- the computer responds with its move and the check is repeated; +- don't implement any form of artificial intelligence − a random field choice made by the computer is good enough for the game. + +The example session with the program may look as follows: +``` ++-------+-------+-------+ +| | | | +| 1 | 2 | 3 | +| | | | ++-------+-------+-------+ +| | | | +| 4 | X | 6 | +| | | | ++-------+-------+-------+ +| | | | +| 7 | 8 | 9 | +| | | | ++-------+-------+-------+ +Enter your move: 1 ++-------+-------+-------+ +| | | | +| O | 2 | 3 | +| | | | ++-------+-------+-------+ +| | | | +| 4 | X | 6 | +| | | | ++-------+-------+-------+ +| | | | +| 7 | 8 | 9 | +| | | | ++-------+-------+-------+ ++-------+-------+-------+ +| | | | +| O | X | 3 | +| | | | ++-------+-------+-------+ +| | | | +| 4 | X | 6 | +| | | | ++-------+-------+-------+ +| | | | +| 7 | 8 | 9 | +| | | | ++-------+-------+-------+ +Enter your move: 8 ++-------+-------+-------+ +| | | | +| O | X | 3 | +| | | | ++-------+-------+-------+ +| | | | +| 4 | X | 6 | +| | | | ++-------+-------+-------+ +| | | | +| 7 | O | 9 | +| | | | ++-------+-------+-------+ ++-------+-------+-------+ +| | | | +| O | X | 3 | +| | | | ++-------+-------+-------+ +| | | | +| 4 | X | X | +| | | | ++-------+-------+-------+ +| | | | +| 7 | O | 9 | +| | | | ++-------+-------+-------+ +Enter your move: 4 ++-------+-------+-------+ +| | | | +| O | X | 3 | +| | | | ++-------+-------+-------+ +| | | | +| O | X | X | +| | | | ++-------+-------+-------+ +| | | | +| 7 | O | 9 | +| | | | ++-------+-------+-------+ ++-------+-------+-------+ +| | | | +| O | X | X | +| | | | ++-------+-------+-------+ +| | | | +| O | X | X | +| | | | ++-------+-------+-------+ +| | | | +| 7 | O | 9 | +| | | | ++-------+-------+-------+ +Enter your move: 7 ++-------+-------+-------+ +| | | | +| O | X | X | +| | | | ++-------+-------+-------+ +| | | | +| O | X | X | +| | | | ++-------+-------+-------+ +| | | | +| O | O | 9 | +| | | | ++-------+-------+-------+ +You won! +``` + +### Requirements + +Implement the following features: + +- the board should be stored as a three-element list, while each element is another three-element list (the inner lists represent rows) so that all of the squares may be accessed using the following syntax: + +``` +board[row][column] +``` + +each of the inner list's elements can contain 'O', 'X', or a digit representing the square's number (such a square is considered free) +the board's appearance should be exactly the same as the one presented in the example. +implement the functions defined for you in the editor. + +Drawing a random integer number can be done by utilizing a Python function called `randrange()`. The example program below shows how to use it (the program prints ten random numbers from 0 to 8). + +[!NOTE] the `from-import` instruction provides access to the `randrange` function defined within an external Python module callled `random`. +```python +from random import randrange + +for i in range(10): +print(randrange(8)) + ``` + + + ```python +def display_board(board): + # The function accepts one parameter containing the board's current status + # and prints it out to the console. + + +def enter_move(board): + # 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. + + +def make_list_of_free_fields(board): + # 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. + + +def victory_for(board, sign): + # The function analyzes the board's status in order to check if + # the player using 'O's or 'X's has won the game + + +def draw_move(board): + # The function draws the computer's move and updates the board. +``` + + +
+Their solution + +```python +from random import randrange + + +def display_board(board): + print("+-------" * 3,"+", sep="") + for row in range(3): + print("| " * 3,"|", sep="") + for col in range(3): + print("| " + str(board[row][col]) + " ", end="") + print("|") + print("| " * 3,"|",sep="") + print("+-------" * 3,"+",sep="") + + +def enter_move(board): + ok = False # fake assumption - we need it to enter the loop + while not ok: + move = input("Enter your move: ") + ok = len(move) == 1 and move >= '1' and move <= '9' # is user's input valid? + if not ok: + print("Bad move - repeat your input!") # no, it isn't - do the input again + continue + move = int(move) - 1 # cell's number from 0 to 8 + row = move // 3 # cell's row + col = move % 3 # cell's column + sign = board[row][col] # check the selected square + ok = sign not in ['O','X'] + if not ok: # it's occupied - to the input again + print("Field already occupied - repeat your input!") + continue + board[row][col] = 'O' # set '0' at the selected square + + +def make_list_of_free_fields(board): + free = [] + for row in range(3): # iterate through rows + for col in range(3): # iterate through columns + if board[row][col] not in ['O','X']: # is the cell free? + free.append((row,col)) # yes, it is - append new tuple to the list + return free + + +def victory_for(board,sgn): + if sgn == "X": # are we looking for X? + who = 'me' # yes - it's computer's side + elif sgn == "O": # ... or for O? + who = 'you' # yes - it's our side + else: + who = None # we should not fall here! + cross1 = cross2 = True # for diagonals + for rc in range(3): + if board[rc][0] == sgn and board[rc][1] == sgn and board[rc][2] == sgn: # check row rc + return who + if board[0][rc] == sgn and board[1][rc] == sgn and board[2][rc] == sgn: # check column rc + return who + if board[rc][rc] != sgn: # check 1st diagonal + cross1 = False + if board[2 - rc][2 - rc] != sgn: # check 2nd diagonal + cross2 = False + if cross1 or cross2: + return who + return None + + +def draw_move(board): + free = make_list_of_free_fields(board) # make a list of free fields + cnt = len(free) + if cnt > 0: + this = randrange(cnt) + row, col = free[this] + board[row][col] = 'X' + + +board = [ [3 * j + i + 1 for i in range(3)] for j in range(3) ] +board[1][1] = 'X' # set first 'X' in the middle +free = make_list_of_free_fields(board) +human_turn = True # which turn is it now? +while len(free): + display_board(board) + if human_turn: + enter_move(board) + victor = victory_for(board,'O') + else: + draw_move(board) + victor = victory_for(board,'X') + if victor != None: + break + human_turn = not human_turn + free = make_list_of_free_fields(board) + +display_board(board) +if victor == 'you': + print("You won!") +elif victor == 'me': + print("I won") +else: + print("Tie!") +``` +
+
diff --git a/main.py b/main.py new file mode 100755 index 0000000..62eebdd --- /dev/null +++ b/main.py @@ -0,0 +1,186 @@ +#!/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) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..11ba6d4 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,7 @@ +[project] +name = "tic-tac-toe" +version = "0.1.0" +description = "All of this is unnecassary, it's supposed to be a simple tic-tac-toe implementation lol" +readme = "README.md" +requires-python = ">=3.13" +dependencies = []