This is all so pointless lol, but it's funny
yeah
This commit is contained in:
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# Python-generated files
|
||||||
|
__pycache__/
|
||||||
|
*.py[oc]
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
wheels/
|
||||||
|
*.egg-info
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
|
.venv
|
||||||
1
.python-version
Normal file
1
.python-version
Normal file
@@ -0,0 +1 @@
|
|||||||
|
3.13
|
||||||
299
README.md
Normal file
299
README.md
Normal file
@@ -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
|
||||||
|
<details>
|
||||||
|
<summary>It did NOT tell me to make classes or anything, it just wanted simple programming</summary>
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Their solution</summary>
|
||||||
|
|
||||||
|
```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!")
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
</details>
|
||||||
186
main.py
Executable file
186
main.py
Executable file
@@ -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)
|
||||||
7
pyproject.toml
Normal file
7
pyproject.toml
Normal file
@@ -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 = []
|
||||||
Reference in New Issue
Block a user