Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
c2e156b
initialize project for a tic tac toe game
AdamBalski Oct 13, 2025
311f550
Merge pull request #1 from AGH-Programming-Classes/kb-init
KamilKr1355 Oct 13, 2025
71d6072
bootstrap logic
AdamBalski Oct 13, 2025
b2cc21e
Merge pull request #3 from AGH-Programming-Classes/kb-init
AdamBalski Oct 13, 2025
789fe66
add taking turns
AdamBalski Oct 13, 2025
25d8571
Merge pull request #14 from AGH-Programming-Classes/kb-turns
AdamBalski Oct 13, 2025
4f9953a
Added print_state feature
KamilKr1355 Oct 13, 2025
bd129cf
Merge branch 'krawiec-balski' into feature/print_state
KamilKr1355 Oct 13, 2025
1a9849e
Merge pull request #17 from AGH-Programming-Classes/feature/print_state
KamilKr1355 Oct 13, 2025
832d24b
minor fixes
AdamBalski Oct 13, 2025
7a1b009
Merge pull request #18 from AGH-Programming-Classes/kb-minor-fixes
AdamBalski Oct 13, 2025
ae10dbf
check if win feature
KamilKr1355 Oct 13, 2025
2a9e414
Added checking if player won
KamilKr1355 Oct 20, 2025
c20175b
Merge pull request #32 from AGH-Programming-Classes/feature/state
KamilKr1355 Oct 20, 2025
fe98445
final changes to state function
KamilKr1355 Oct 20, 2025
80f5dd5
Merge pull request #34 from AGH-Programming-Classes/feature/state
KamilKr1355 Oct 20, 2025
a02f9c5
fix
AdamBalski Oct 20, 2025
b7694c5
Merge pull request #35 from AGH-Programming-Classes/kb-fix
AdamBalski Oct 20, 2025
695ae33
add strategy and proxy for color display
AdamBalski Oct 20, 2025
33c9a50
Merge pull request #52 from AGH-Programming-Classes/kb-add-strategies…
AdamBalski Oct 20, 2025
638923f
Playing vs bot feature
KamilKr1355 Oct 20, 2025
dd0bf3b
Merge branch 'krawiec-balski' into feature/playing-random
KamilKr1355 Oct 20, 2025
27f0d8d
Merge pull request #58 from AGH-Programming-Classes/feature/playing-r…
KamilKr1355 Oct 20, 2025
02618f7
Naprawiono bledy
KamilKr1355 Oct 27, 2025
6ff44c0
Added Strategy Pattern desing for playing with bot/another player
KamilKr1355 Oct 27, 2025
6ee8801
Merge pull request #87 from AGH-Programming-Classes/strategy_pattern
KamilKr1355 Oct 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 16 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
# Hello World Project

A starting template for your project, following a modern Python project structure.
# Tic Tac Toe Project
Tic Tac Toe project for the AGH PitE classes. Made by Kamil Krawiec and Adam Balski.

## Project Layout

```
hello_world_project/
├── src/hello_world_project/
│ ├── main.py
│ ├── utils.py
│ ├── example_module.py
│ └── __init__.py
├── tests/
│ └── test_example_module.py
Tic Tac Toe project
├── LICENSE
├── pyproject.toml
├── README.md
├── requirements.txt
├── setup.cfg
└── README.md
├── src
│   └── tic_tac_toe
│   ├── __init__.py
│   ├── main.py
│   └── utils.py
└── tests
├── __init__.py
└── test_example_module.py
```

## Quick Start

### 1. Create and activate a virtual environment
```bash
python -m venv venv
python3 -m venv venv
source venv/bin/activate # macOS/Linux
```

Expand All @@ -35,12 +36,12 @@ Alternatively, you can temporarily point PYTHONPATH to src.

### 2. Run the application
```bash
python -m hello_world_project.main
python -m src.tic_tac_toe.main
```

## Running Tests

Run all tests:
```bash
python -m unittest discover -s tests
```
```
8 changes: 4 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@
requires = ["setuptools", "wheel"]

[project]
name = "hello_world_project"
version = "0.1.0"
description = "Hello World project."
authors = [{name = "Your Name"}]
name = "tic_tac_toe"
version = "0.0.1"
description = "Tic Tac Toe project"
authors = [{name = "Kamil Krawiec"}, {name = "Adam Balski"}]
readme = "README.md"
requires-python = ">=3.9"

Expand Down
1 change: 0 additions & 1 deletion src/hello_world_project/__init__.py

This file was deleted.

2 changes: 0 additions & 2 deletions src/hello_world_project/example_module.py

This file was deleted.

15 changes: 0 additions & 15 deletions src/hello_world_project/main.py

This file was deleted.

4 changes: 0 additions & 4 deletions src/hello_world_project/utils.py

This file was deleted.

1 change: 1 addition & 0 deletions src/tic_tac_toe/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Tic Tac Toe game package"""
50 changes: 50 additions & 0 deletions src/tic_tac_toe/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from src.tic_tac_toe.utils import PlayingStrategy, HumanStrategy, BotStrategy, Play,ColoringPrinter, GameState, NormalPrinter, State,\
UnderlinedReverseVideoDecoratingPrinter, ask_for_name, get_printer, print_state, set_printer, ask_if_pvp


def ask_boolean(question):
inpt = input(f"{question}" + " (true, t, yes or y for yes): ")
return inpt.lower() in {"true", "t", "yes", "y"}

def ask_for_reverse_video_and_underlining():
return ask_boolean("Do you want reverse video (swap fg and bg colors) and underline on all output?")
def ask_for_color():
color = input("Name color you want for output (0 - 7), if other input, default will be assumed: ")
if color not in "01234567":
return None
return int(color)

def main():
print("=== Tic Tac Toe ===")
if ask_if_pvp()=="P":
name1 = ask_for_name("player #1")
name2 = ask_for_name("player #2")
else:
name1 = ask_for_name("player #1")
name2 = "Bot"
print(f"Starting game for {name1} and {name2}")
color = ask_for_color()
if color != None:
set_printer(ColoringPrinter(color))
rev_video = ask_for_reverse_video_and_underlining()
if rev_video:
set_printer(UnderlinedReverseVideoDecoratingPrinter(get_printer()))

get_printer().print(f"Starting game for {name1} and {name2}")

state = State.new(name1, name2)
p1_turn = True
while state.state() == GameState.UNFINISHED:
if name2 =="Bot" and not p1_turn:
play = Play(BotStrategy())
else:
play = Play(HumanStrategy())
print_state(state)
play.play(state, p1_turn)
p1_turn = not p1_turn

print_state(state)


if __name__ == "__main__":
main()
159 changes: 159 additions & 0 deletions src/tic_tac_toe/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import enum
import typing
import random


def ask_for_name(player_tag) -> str:
while True:
name=input(f"Enter name of {player_tag}:")

if name!="Bot":
return name
import abc

class Printer(abc.ABC):
@abc.abstractmethod
def print(self, msg, **kwargs):
pass
class NormalPrinter(Printer):
def print(self, msg, **kwargs):
print(msg, **kwargs)
class ColoringPrinter(Printer):
def __init__(self, color_code):
self.color_code = color_code
def print(self, msg, **kwargs):
kwargs_copy = dict(kwargs)
kwargs_copy["end"] = ""
print(f"\033[{30 + self.color_code}m", **kwargs_copy)
print(msg, **kwargs_copy)
print("\033[0m", **kwargs)
class UnderlinedReverseVideoDecoratingPrinter(Printer):
def __init__(self, printer):
self.printer = printer
def print(self, msg, **kwargs):
kwargs_copy = dict(kwargs)
kwargs_copy["end"] = ""
print(f"\033[4;7m", **kwargs_copy)
self.printer.print(msg, **kwargs_copy)
print("\033[0m", **kwargs)

printer = NormalPrinter()
def get_printer():
return printer
def set_printer(new_printer):
global printer
printer = new_printer

class GameState(enum.Enum):
PLAYER_1 = 1
PLAYER_2 = 2
DRAW = 3
UNFINISHED = 4

class State:
def __init__(self, board, p1_name, p2_name):
self.board = board
self.p1_name = p1_name
self.p2_name = p2_name
@staticmethod
def new(p1_name, p2_name):
return State([[" ", " ", " "], [" ", " ", " "], [" ", " ", " "]], p1_name, p2_name)
def state(self) -> GameState:
diags = [
{self.board[0][0], self.board[0][1], self.board[0][2]},
{self.board[1][0], self.board[1][1], self.board[1][2]},
{self.board[2][0], self.board[2][1], self.board[2][2]},

{self.board[0][0], self.board[1][0], self.board[2][0]},
{self.board[0][1], self.board[1][1], self.board[2][1]},
{self.board[0][2], self.board[1][2], self.board[2][2]},

{self.board[0][0], self.board[1][1], self.board[2][2]},
{self.board[2][0], self.board[1][1], self.board[0][2]},
]
non_draw = False
for diag in diags:
if " " in diag:
non_draw = True
if diag == {"X"}:
return GameState.PLAYER_1
if diag == {"O"}:
return GameState.PLAYER_2
return GameState.UNFINISHED if non_draw else GameState.DRAW
def are_coordinates_valid(self, coordinates: typing.Tuple[int, int]):
return 0 <= coordinates[0] < 3 and 0 <= coordinates[1] < 3
def at_coordinates(self, coordinates: typing.Tuple[int, int]):
return self.board[coordinates[0]][coordinates[1]]

def print_state(state: State):
match state.state():
case GameState.PLAYER_1:
printer.print("X won")
case GameState.PLAYER_2:
printer.print("O won")
case GameState.DRAW:
printer.print("Draw")
case GameState.UNFINISHED:
printer.print("Unfinished")
msg = " A B C\n" + "\n".join(str(index+1) + " " + " ".join(row) for index,row in enumerate(state.board))
printer.print(msg)
def ask_for_row():
alphabet = ["1", "2", "3"]
result = None
while len(result := input("Specify row(1, 2 or 3): ")) != 1 or result not in alphabet:
printer.print("Need one character (1, 2 or 3)")
return ord(result) - 49
def ask_for_column():
alphabet = ["A", "B", "C"]
result = None
while len(result := input("Specify column(A, B or C): ")) != 1 or result not in alphabet:
printer.print("Need one character (A, B or C)")
return ord(result) - 65
def ask_for_coordinates():
row = ask_for_row()
col = ask_for_column()
return (row, col)

class PlayingStrategy(abc.ABC):
@abc.abstractmethod
def ask_for_move(self,State):
pass

class HumanStrategy(PlayingStrategy):
def ask_for_move(self,State):
row = ask_for_row()
col = ask_for_column()
return (row, col)

class BotStrategy(PlayingStrategy):
def ask_for_move(self, State):
return (random.randint(0,2),random.randint(0,2))

class Play:
def __init__(self,strategy: PlayingStrategy):
self.strategy = strategy
def play(self,state: State,p1_turn: bool):
while True:
coordinates = self.strategy.ask_for_move(state)
if state.board[coordinates[0]][coordinates[1]]==" ":
break

while (invalid := not state.are_coordinates_valid(coordinates)) or\
(tile := state.at_coordinates(coordinates)) != ' ':
if invalid:
printer.print("Invalid coordinates (out of board), asking again...")
else:
printer.print(f"Tile has been taken ('{tile}' sign has been placed)")
coordinates = ask_for_coordinates()
tile = "X" if p1_turn else "O"
state.board[coordinates[0]][coordinates[1]] = tile


def ask_if_pvp():
valid = ["p","b"]
while True:
result = input("Chcesz zagrać na bota(B), czy z innym graczem?(P) ")
if result.lower() in valid:
return result


14 changes: 3 additions & 11 deletions tests/test_example_module.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,8 @@
import unittest
from hello_world_project.example_module import add_numbers
from hello_world_project.utils import greet_user

class TestHelloWorldProject(unittest.TestCase):
def test_add_numbers(self):
self.assertEqual(add_numbers(2, 3), 5)
self.assertEqual(add_numbers(-5, 5), 0)
self.assertEqual(add_numbers(0, 0), 0)

def test_greet_user(self):
self.assertIn("Alice", greet_user("Alice"))
self.assertIn("User", greet_user(""))
class TestTicTacToe(unittest.TestCase):
def test_dummy(self):
self.assertTrue(True)

if __name__ == "__main__":
unittest.main()