From c2e156b15e9991d9448094725fee92f7347bbf9f Mon Sep 17 00:00:00 2001 From: AdamBalski Date: Mon, 13 Oct 2025 13:44:57 +0200 Subject: [PATCH 01/13] initialize project for a tic tac toe game --- README.md | 31 ++++++++++++----------- pyproject.toml | 8 +++--- src/hello_world_project/__init__.py | 1 - src/hello_world_project/example_module.py | 2 -- src/hello_world_project/main.py | 15 ----------- src/hello_world_project/utils.py | 4 --- src/tic_tac_toe/__init__.py | 1 + src/tic_tac_toe/main.py | 11 ++++++++ src/tic_tac_toe/utils.py | 2 ++ tests/test_example_module.py | 14 +++------- 10 files changed, 37 insertions(+), 52 deletions(-) delete mode 100644 src/hello_world_project/__init__.py delete mode 100644 src/hello_world_project/example_module.py delete mode 100644 src/hello_world_project/main.py delete mode 100644 src/hello_world_project/utils.py create mode 100644 src/tic_tac_toe/__init__.py create mode 100644 src/tic_tac_toe/main.py create mode 100644 src/tic_tac_toe/utils.py diff --git a/README.md b/README.md index eb29125..d1b0ee0 100644 --- a/README.md +++ b/README.md @@ -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 ``` @@ -35,7 +36,7 @@ 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 @@ -43,4 +44,4 @@ python -m hello_world_project.main Run all tests: ```bash python -m unittest discover -s tests -``` \ No newline at end of file +``` diff --git a/pyproject.toml b/pyproject.toml index a6c4f20..6b69720 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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" diff --git a/src/hello_world_project/__init__.py b/src/hello_world_project/__init__.py deleted file mode 100644 index 29b5856..0000000 --- a/src/hello_world_project/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""hello_world_project package.""" diff --git a/src/hello_world_project/example_module.py b/src/hello_world_project/example_module.py deleted file mode 100644 index b389af8..0000000 --- a/src/hello_world_project/example_module.py +++ /dev/null @@ -1,2 +0,0 @@ -def add_numbers(a: int, b: int) -> int: - return a + b diff --git a/src/hello_world_project/main.py b/src/hello_world_project/main.py deleted file mode 100644 index c0f4cd6..0000000 --- a/src/hello_world_project/main.py +++ /dev/null @@ -1,15 +0,0 @@ -from hello_world_project.utils import greet_user -from hello_world_project.example_module import add_numbers - -def main(): - print("=== Hello World Project ===") - - name = input("Enter your name: ") - greeting = greet_user(name) - print(greeting) - - result = add_numbers(10, 5) - print(f"The result of adding 10 and 5 is {result}.") - -if __name__ == "__main__": - main() diff --git a/src/hello_world_project/utils.py b/src/hello_world_project/utils.py deleted file mode 100644 index 308cb26..0000000 --- a/src/hello_world_project/utils.py +++ /dev/null @@ -1,4 +0,0 @@ -def greet_user(name: str) -> str: - if not name: - name = "User" - return f"Hello, {name}! Welcome to the Hello World project." diff --git a/src/tic_tac_toe/__init__.py b/src/tic_tac_toe/__init__.py new file mode 100644 index 0000000..41d7f61 --- /dev/null +++ b/src/tic_tac_toe/__init__.py @@ -0,0 +1 @@ +"""Tic Tac Toe game package""" diff --git a/src/tic_tac_toe/main.py b/src/tic_tac_toe/main.py new file mode 100644 index 0000000..379f5a8 --- /dev/null +++ b/src/tic_tac_toe/main.py @@ -0,0 +1,11 @@ +from src.tic_tac_toe.utils import ask_for_name + +def main(): + print("=== Tic Tac Toe ===") + + name1 = ask_for_name("player #1") + name2 = ask_for_name("player #2") + print(f"Starting game for {name1} and {name2}") + +if __name__ == "__main__": + main() diff --git a/src/tic_tac_toe/utils.py b/src/tic_tac_toe/utils.py new file mode 100644 index 0000000..615f508 --- /dev/null +++ b/src/tic_tac_toe/utils.py @@ -0,0 +1,2 @@ +def ask_for_name(player_tag) -> str: + return input(f"Enter name of {player_tag}:") diff --git a/tests/test_example_module.py b/tests/test_example_module.py index fefdcf3..3f90ea6 100644 --- a/tests/test_example_module.py +++ b/tests/test_example_module.py @@ -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() From 71d6072bdc9b3de7ac756544ca2b82910abbb439 Mon Sep 17 00:00:00 2001 From: AdamBalski Date: Mon, 13 Oct 2025 14:01:17 +0200 Subject: [PATCH 02/13] bootstrap logic --- src/tic_tac_toe/main.py | 8 +++++++- src/tic_tac_toe/utils.py | 26 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/tic_tac_toe/main.py b/src/tic_tac_toe/main.py index 379f5a8..d2b09d1 100644 --- a/src/tic_tac_toe/main.py +++ b/src/tic_tac_toe/main.py @@ -1,4 +1,4 @@ -from src.tic_tac_toe.utils import ask_for_name +from src.tic_tac_toe.utils import GameState, ask_for_name, turn def main(): print("=== Tic Tac Toe ===") @@ -7,5 +7,11 @@ def main(): name2 = ask_for_name("player #2") print(f"Starting game for {name1} and {name2}") + state = None #todo: initialize state + p1_turn = True + while state.state() == GameState.UNFINISHED: + turn(state, p1_turn) + p1_turn = not p1_turn + if __name__ == "__main__": main() diff --git a/src/tic_tac_toe/utils.py b/src/tic_tac_toe/utils.py index 615f508..5a3e8e0 100644 --- a/src/tic_tac_toe/utils.py +++ b/src/tic_tac_toe/utils.py @@ -1,2 +1,28 @@ +import enum +import typing + + def ask_for_name(player_tag) -> str: return input(f"Enter name of {player_tag}:") + +class GameState(enum.EnumType): + 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 + def state(self) -> GameState: + # todo + return GameState.UNFINISHED + +def print_state(state: State): + # todo + pass +def turn(state: State, p1_turn: bool): + # todo + pass From 789fe669e2bf88eeb37015fb30309155a6701866 Mon Sep 17 00:00:00 2001 From: AdamBalski Date: Mon, 13 Oct 2025 14:25:58 +0200 Subject: [PATCH 03/13] add taking turns --- src/tic_tac_toe/main.py | 7 +++++-- src/tic_tac_toe/utils.py | 39 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/tic_tac_toe/main.py b/src/tic_tac_toe/main.py index d2b09d1..c62e2b3 100644 --- a/src/tic_tac_toe/main.py +++ b/src/tic_tac_toe/main.py @@ -1,4 +1,4 @@ -from src.tic_tac_toe.utils import GameState, ask_for_name, turn +from src.tic_tac_toe.utils import GameState, State, ask_for_name, print_state, turn def main(): print("=== Tic Tac Toe ===") @@ -7,11 +7,14 @@ def main(): name2 = ask_for_name("player #2") print(f"Starting game for {name1} and {name2}") - state = None #todo: initialize state + state = State.new(name1, name2) p1_turn = True while state.state() == GameState.UNFINISHED: + print_state(state) turn(state, p1_turn) p1_turn = not p1_turn + print(f"Game has finished with state: {state.state()}") + if __name__ == "__main__": main() diff --git a/src/tic_tac_toe/utils.py b/src/tic_tac_toe/utils.py index 5a3e8e0..8d376a8 100644 --- a/src/tic_tac_toe/utils.py +++ b/src/tic_tac_toe/utils.py @@ -5,7 +5,7 @@ def ask_for_name(player_tag) -> str: return input(f"Enter name of {player_tag}:") -class GameState(enum.EnumType): +class GameState(enum.Enum): PLAYER_1 = 1 PLAYER_2 = 2 DRAW = 3 @@ -16,13 +16,46 @@ 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: # todo return GameState.UNFINISHED + 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): # todo pass +def ask_for_row(): + alphabet = ["1", "2", "3"] + result = None + while len(result := input("Specify column")) != 1 or result not in alphabet: + print("Need one character (1, 2 or 3)") + return ord(result) - 65 +def ask_for_column(): + alphabet = ["A", "B", "C"] + result = None + while len(result := input("Specify column")) != 1 or result not in alphabet: + 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) def turn(state: State, p1_turn: bool): - # todo - pass + coordinates = ask_for_coordinates() + while (invalid := not state.are_coordinates_valid(coordinates)) or\ + (tile := state.at_coordinates(coordinates)) != ' ': + if invalid: + print("Invalid coordinates (out of board), asking again...") + else: + 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 + + From 4f9953abd79aaa437a0ffc7f22e9fa7e449526a7 Mon Sep 17 00:00:00 2001 From: Kamil Krawiec Date: Mon, 13 Oct 2025 14:30:17 +0200 Subject: [PATCH 04/13] Added print_state feature --- src/tic_tac_toe/utils.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/tic_tac_toe/utils.py b/src/tic_tac_toe/utils.py index 5a3e8e0..9207059 100644 --- a/src/tic_tac_toe/utils.py +++ b/src/tic_tac_toe/utils.py @@ -5,7 +5,7 @@ def ask_for_name(player_tag) -> str: return input(f"Enter name of {player_tag}:") -class GameState(enum.EnumType): +class GameState(enum.Enum): PLAYER_1 = 1 PLAYER_2 = 2 DRAW = 3 @@ -21,8 +21,18 @@ def state(self) -> GameState: return GameState.UNFINISHED def print_state(state: State): - # todo - pass + match state.state(): + case 1: + print("X won") + case 2: + print("O won") + case 3: + print("Draw") + case 4: + print("Unfinished") + msg = " a b c" + "\n".join(str(index+1) + " " + " ".join(row) for index,row in enumerate(state.board)) + print(msg + ) def turn(state: State, p1_turn: bool): # todo pass From 832d24b1618fb8deb37084f12975ba77d55fb679 Mon Sep 17 00:00:00 2001 From: AdamBalski Date: Mon, 13 Oct 2025 14:36:50 +0200 Subject: [PATCH 05/13] minor fixes --- src/tic_tac_toe/utils.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/tic_tac_toe/utils.py b/src/tic_tac_toe/utils.py index 3047626..3a1f833 100644 --- a/src/tic_tac_toe/utils.py +++ b/src/tic_tac_toe/utils.py @@ -23,7 +23,7 @@ def state(self) -> GameState: # todo return GameState.UNFINISHED def are_coordinates_valid(self, coordinates: typing.Tuple[int, int]): - return 0 < coordinates[0] < 3 and 0 < coordinates[1] < 3 + 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]] @@ -37,19 +37,18 @@ def print_state(state: State): print("Draw") case 4: print("Unfinished") - msg = " a b c" + "\n".join(str(index+1) + " " + " ".join(row) for index,row in enumerate(state.board)) - print(msg - ) + msg = " a b c\n" + "\n".join(str(index+1) + " " + " ".join(row) for index,row in enumerate(state.board)) + print(msg) def ask_for_row(): alphabet = ["1", "2", "3"] result = None - while len(result := input("Specify column")) != 1 or result not in alphabet: + while len(result := input("Specify row(1, 2 or 3): ")) != 1 or result not in alphabet: print("Need one character (1, 2 or 3)") - return ord(result) - 65 + return ord(result) - 49 def ask_for_column(): alphabet = ["A", "B", "C"] result = None - while len(result := input("Specify column")) != 1 or result not in alphabet: + while len(result := input("Specify column(A, B or C): ")) != 1 or result not in alphabet: print("Need one character (A, B or C)") return ord(result) - 65 def ask_for_coordinates(): From ae10dbf8ba6151da3bd3630ede78e43e4cde947c Mon Sep 17 00:00:00 2001 From: Kamil Krawiec Date: Mon, 13 Oct 2025 14:51:34 +0200 Subject: [PATCH 06/13] check if win feature --- src/tic_tac_toe/utils.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/tic_tac_toe/utils.py b/src/tic_tac_toe/utils.py index 3a1f833..2ffe352 100644 --- a/src/tic_tac_toe/utils.py +++ b/src/tic_tac_toe/utils.py @@ -26,6 +26,22 @@ 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 check_if_win(self): + if any(len(set(row)) == 1 for row in self.board): + return True + + if len(set([self.board[i][i]] for i in range(3))) == 1 and self.board[0][0]!=" ": + return True + + if len(set([self.board[i][2 - i]] for i in range(3))) == 1 and self.board[0][2]!=" ": + return True + + for col in range(3): + if len(set([self.board[i][col]] for i in range(3))) == 1 and self.board[0][col]!=" ": + return True + + + return False def print_state(state: State): match state.state(): From 2a9e41495e281d9daffaf12ea211ebe5099d2808 Mon Sep 17 00:00:00 2001 From: Kamil Krawiec Date: Mon, 20 Oct 2025 13:20:28 +0200 Subject: [PATCH 07/13] Added checking if player won --- src/tic_tac_toe/utils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tic_tac_toe/utils.py b/src/tic_tac_toe/utils.py index 2ffe352..e936580 100644 --- a/src/tic_tac_toe/utils.py +++ b/src/tic_tac_toe/utils.py @@ -37,10 +37,9 @@ def check_if_win(self): return True for col in range(3): - if len(set([self.board[i][col]] for i in range(3))) == 1 and self.board[0][col]!=" ": + if self.board[0][col]==self.board[1][col]==self.board[2][col]: return True - return False def print_state(state: State): From fe984451218d59fca78adb37e73f134745eb9c38 Mon Sep 17 00:00:00 2001 From: Kamil Krawiec Date: Mon, 20 Oct 2025 13:41:32 +0200 Subject: [PATCH 08/13] final changes to state function --- src/tic_tac_toe/utils.py | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/src/tic_tac_toe/utils.py b/src/tic_tac_toe/utils.py index e936580..4b6da6b 100644 --- a/src/tic_tac_toe/utils.py +++ b/src/tic_tac_toe/utils.py @@ -20,25 +20,47 @@ def __init__(self, board, p1_name, p2_name): def new(p1_name, p2_name): return State([[" ", " ", " "], [" ", " ", " "], [" ", " ", " "]], p1_name, p2_name) def state(self) -> GameState: - # todo + if self.check_if_win()=="X": + return GameState.PLAYER_1 + if self.check_if_win()=="O": + return GameState.PLAYER_2 + if self.check_if_draw: + return GameState.DRAW return GameState.UNFINISHED 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 check_if_draw(self): + for row in self.board: + for p in row: + if p==" ": + return False + return True def check_if_win(self): - if any(len(set(row)) == 1 for row in self.board): - return True + if any(len(set(row)) == 1 and row[0]=="X" for row in self.board): + return "X" + if any(len(set(row)) == 1 and row[0]=="O" for row in self.board): + return "O" - if len(set([self.board[i][i]] for i in range(3))) == 1 and self.board[0][0]!=" ": - return True + if len(set([self.board[i][i]] for i in range(3))) == 1 and self.board[0][0]=="X": + return "X" + + if len(set([self.board[i][i]] for i in range(3))) == 1 and self.board[0][0]=="O": + return "O" - if len(set([self.board[i][2 - i]] for i in range(3))) == 1 and self.board[0][2]!=" ": - return True + if len(set([self.board[i][2 - i]] for i in range(3))) == 1 and self.board[0][2]=="X": + return "X" + + if len(set([self.board[i][2 - i]] for i in range(3))) == 1 and self.board[0][2]=="O": + return "O" for col in range(3): if self.board[0][col]==self.board[1][col]==self.board[2][col]: - return True + if self.board[0][col]=="X": + return "X" + else: + return "O" return False From a02f9c5a9ce51c1678b72dbe2fd8cc1fb792e7c8 Mon Sep 17 00:00:00 2001 From: AdamBalski Date: Mon, 20 Oct 2025 13:52:33 +0200 Subject: [PATCH 09/13] fix --- src/tic_tac_toe/main.py | 2 +- src/tic_tac_toe/utils.py | 68 +++++++++++++++------------------------- 2 files changed, 26 insertions(+), 44 deletions(-) diff --git a/src/tic_tac_toe/main.py b/src/tic_tac_toe/main.py index c62e2b3..a4c95fc 100644 --- a/src/tic_tac_toe/main.py +++ b/src/tic_tac_toe/main.py @@ -14,7 +14,7 @@ def main(): turn(state, p1_turn) p1_turn = not p1_turn - print(f"Game has finished with state: {state.state()}") + print_state(state) if __name__ == "__main__": main() diff --git a/src/tic_tac_toe/utils.py b/src/tic_tac_toe/utils.py index 4b6da6b..5c87964 100644 --- a/src/tic_tac_toe/utils.py +++ b/src/tic_tac_toe/utils.py @@ -20,59 +20,41 @@ def __init__(self, board, p1_name, p2_name): def new(p1_name, p2_name): return State([[" ", " ", " "], [" ", " ", " "], [" ", " ", " "]], p1_name, p2_name) def state(self) -> GameState: - if self.check_if_win()=="X": - return GameState.PLAYER_1 - if self.check_if_win()=="O": - return GameState.PLAYER_2 - if self.check_if_draw: - return GameState.DRAW - return GameState.UNFINISHED + diags = [ + {self.board[0][0], self.board[0][1], self.board[0][1]}, + {self.board[1][0], self.board[1][1], self.board[1][1]}, + {self.board[2][0], self.board[2][1], self.board[2][1]}, + + {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 check_if_draw(self): - for row in self.board: - for p in row: - if p==" ": - return False - return True - def check_if_win(self): - if any(len(set(row)) == 1 and row[0]=="X" for row in self.board): - return "X" - if any(len(set(row)) == 1 and row[0]=="O" for row in self.board): - return "O" - - if len(set([self.board[i][i]] for i in range(3))) == 1 and self.board[0][0]=="X": - return "X" - - if len(set([self.board[i][i]] for i in range(3))) == 1 and self.board[0][0]=="O": - return "O" - - if len(set([self.board[i][2 - i]] for i in range(3))) == 1 and self.board[0][2]=="X": - return "X" - - if len(set([self.board[i][2 - i]] for i in range(3))) == 1 and self.board[0][2]=="O": - return "O" - - for col in range(3): - if self.board[0][col]==self.board[1][col]==self.board[2][col]: - if self.board[0][col]=="X": - return "X" - else: - return "O" - - return False def print_state(state: State): match state.state(): - case 1: + case GameState.PLAYER_1: print("X won") - case 2: + case GameState.PLAYER_2: print("O won") - case 3: + case GameState.DRAW: print("Draw") - case 4: + case GameState.UNFINISHED: print("Unfinished") msg = " a b c\n" + "\n".join(str(index+1) + " " + " ".join(row) for index,row in enumerate(state.board)) print(msg) From 695ae33c3ba3cb4d6ac7433cf2f6636f951a2b09 Mon Sep 17 00:00:00 2001 From: AdamBalski Date: Mon, 20 Oct 2025 14:26:13 +0200 Subject: [PATCH 10/13] add strategy and proxy for color display --- src/tic_tac_toe/main.py | 24 +++++++++++++++-- src/tic_tac_toe/utils.py | 56 ++++++++++++++++++++++++++++++++-------- 2 files changed, 67 insertions(+), 13 deletions(-) diff --git a/src/tic_tac_toe/main.py b/src/tic_tac_toe/main.py index a4c95fc..fb4a106 100644 --- a/src/tic_tac_toe/main.py +++ b/src/tic_tac_toe/main.py @@ -1,11 +1,31 @@ -from src.tic_tac_toe.utils import GameState, State, ask_for_name, print_state, turn +from src.tic_tac_toe.utils import ColoringPrinter, GameState, NormalPrinter, State, UnderlinedReverseVideoDecoratingPrinter, ask_for_name, get_printer, print_state, set_printer, turn + + +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 ===") + 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())) + name1 = ask_for_name("player #1") name2 = ask_for_name("player #2") - print(f"Starting game for {name1} and {name2}") + get_printer().print(f"Starting game for {name1} and {name2}") state = State.new(name1, name2) p1_turn = True diff --git a/src/tic_tac_toe/utils.py b/src/tic_tac_toe/utils.py index 5c87964..91ab2b5 100644 --- a/src/tic_tac_toe/utils.py +++ b/src/tic_tac_toe/utils.py @@ -1,9 +1,43 @@ import enum import typing +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 def ask_for_name(player_tag) -> str: - return input(f"Enter name of {player_tag}:") + return input(f"Enter name of {player_tag}: ") class GameState(enum.Enum): PLAYER_1 = 1 @@ -49,26 +83,26 @@ def at_coordinates(self, coordinates: typing.Tuple[int, int]): def print_state(state: State): match state.state(): case GameState.PLAYER_1: - print("X won") + printer.print("X won") case GameState.PLAYER_2: - print("O won") + printer.print("O won") case GameState.DRAW: - print("Draw") + printer.print("Draw") case GameState.UNFINISHED: - print("Unfinished") - msg = " a b c\n" + "\n".join(str(index+1) + " " + " ".join(row) for index,row in enumerate(state.board)) - print(msg) + 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: - print("Need one character (1, 2 or 3)") + 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: - print("Need one character (A, B or C)") + printer.print("Need one character (A, B or C)") return ord(result) - 65 def ask_for_coordinates(): row = ask_for_row() @@ -79,9 +113,9 @@ def turn(state: State, p1_turn: bool): while (invalid := not state.are_coordinates_valid(coordinates)) or\ (tile := state.at_coordinates(coordinates)) != ' ': if invalid: - print("Invalid coordinates (out of board), asking again...") + printer.print("Invalid coordinates (out of board), asking again...") else: - print(f"Tile has been taken ('{tile}' sign has been placed)") + 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 From 638923f3cee0eb002b415f0f824fa56fb37daec6 Mon Sep 17 00:00:00 2001 From: Kamil Krawiec Date: Mon, 20 Oct 2025 14:36:18 +0200 Subject: [PATCH 11/13] Playing vs bot feature --- src/tic_tac_toe/main.py | 12 +++++++++--- src/tic_tac_toe/utils.py | 27 +++++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/tic_tac_toe/main.py b/src/tic_tac_toe/main.py index a4c95fc..f378cf1 100644 --- a/src/tic_tac_toe/main.py +++ b/src/tic_tac_toe/main.py @@ -1,10 +1,15 @@ -from src.tic_tac_toe.utils import GameState, State, ask_for_name, print_state, turn +from src.tic_tac_toe.utils import GameState, State, ask_for_name, print_state, turn,ask_if_pvp def main(): print("=== Tic Tac Toe ===") - name1 = ask_for_name("player #1") - name2 = ask_for_name("player #2") + + 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}") state = State.new(name1, name2) @@ -16,5 +21,6 @@ def main(): print_state(state) + if __name__ == "__main__": main() diff --git a/src/tic_tac_toe/utils.py b/src/tic_tac_toe/utils.py index 5c87964..1d57950 100644 --- a/src/tic_tac_toe/utils.py +++ b/src/tic_tac_toe/utils.py @@ -1,9 +1,14 @@ import enum import typing +import random def ask_for_name(player_tag) -> str: - return input(f"Enter name of {player_tag}:") + while True: + name=input(f"Enter name of {player_tag}:") + + if name!="Bot": + return name class GameState(enum.Enum): PLAYER_1 = 1 @@ -75,7 +80,16 @@ def ask_for_coordinates(): col = ask_for_column() return (row, col) def turn(state: State, p1_turn: bool): - coordinates = ask_for_coordinates() + if state.p2_name=="Bot" and p1_turn: + coordinates = ask_for_coordinates() + elif state.p2_name=="Bot" and not p1_turn: + while True: + coordinates=generate_random_position() + if state.board[coordinates[0]][coordinates[1]]==" ": + break + else: + coordinates = ask_for_coordinates() + while (invalid := not state.are_coordinates_valid(coordinates)) or\ (tile := state.at_coordinates(coordinates)) != ' ': if invalid: @@ -85,5 +99,14 @@ def turn(state: State, p1_turn: bool): 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 + +def generate_random_position(): + return (random.randint(0,2),random.randint(0,2)) From 02618f7e669337f15e58a1453f2b30bb5d15f8b8 Mon Sep 17 00:00:00 2001 From: Kamil Krawiec Date: Mon, 27 Oct 2025 13:26:50 +0100 Subject: [PATCH 12/13] Naprawiono bledy --- src/tic_tac_toe/main.py | 2 -- src/tic_tac_toe/utils.py | 4 ---- 2 files changed, 6 deletions(-) diff --git a/src/tic_tac_toe/main.py b/src/tic_tac_toe/main.py index 4920906..0e5e19a 100644 --- a/src/tic_tac_toe/main.py +++ b/src/tic_tac_toe/main.py @@ -30,8 +30,6 @@ def main(): if rev_video: set_printer(UnderlinedReverseVideoDecoratingPrinter(get_printer())) - name1 = ask_for_name("player #1") - name2 = ask_for_name("player #2") get_printer().print(f"Starting game for {name1} and {name2}") state = State.new(name1, name2) diff --git a/src/tic_tac_toe/utils.py b/src/tic_tac_toe/utils.py index a317803..c178bda 100644 --- a/src/tic_tac_toe/utils.py +++ b/src/tic_tac_toe/utils.py @@ -44,10 +44,6 @@ def set_printer(new_printer): global printer printer = new_printer - -def ask_for_name(player_tag) -> str: - return input(f"Enter name of {player_tag}: ") - class GameState(enum.Enum): PLAYER_1 = 1 PLAYER_2 = 2 From 6ff44c0c25d036028137a73258b25e89bdd5c2a8 Mon Sep 17 00:00:00 2001 From: Kamil Krawiec Date: Mon, 27 Oct 2025 14:19:55 +0100 Subject: [PATCH 13/13] Added Strategy Pattern desing for playing with bot/another player --- src/tic_tac_toe/main.py | 10 +++++-- src/tic_tac_toe/utils.py | 59 ++++++++++++++++++++++++---------------- 2 files changed, 43 insertions(+), 26 deletions(-) diff --git a/src/tic_tac_toe/main.py b/src/tic_tac_toe/main.py index 0e5e19a..45e175c 100644 --- a/src/tic_tac_toe/main.py +++ b/src/tic_tac_toe/main.py @@ -1,5 +1,5 @@ -from src.tic_tac_toe.utils import ColoringPrinter, GameState, NormalPrinter, State,\ - UnderlinedReverseVideoDecoratingPrinter, ask_for_name, get_printer, print_state, set_printer, turn, ask_if_pvp +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): @@ -35,8 +35,12 @@ def main(): 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) - turn(state, p1_turn) + play.play(state, p1_turn) p1_turn = not p1_turn print_state(state) diff --git a/src/tic_tac_toe/utils.py b/src/tic_tac_toe/utils.py index c178bda..5dfc329 100644 --- a/src/tic_tac_toe/utils.py +++ b/src/tic_tac_toe/utils.py @@ -60,9 +60,9 @@ 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][1]}, - {self.board[1][0], self.board[1][1], self.board[1][1]}, - {self.board[2][0], self.board[2][1], self.board[2][1]}, + {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]}, @@ -113,26 +113,42 @@ def ask_for_coordinates(): row = ask_for_row() col = ask_for_column() return (row, col) -def turn(state: State, p1_turn: bool): - if state.p2_name=="Bot" and p1_turn: - coordinates = ask_for_coordinates() - elif state.p2_name=="Bot" and not p1_turn: + +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=generate_random_position() + coordinates = self.strategy.ask_for_move(state) if state.board[coordinates[0]][coordinates[1]]==" ": break - else: - coordinates = ask_for_coordinates() - - 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 + + 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: @@ -140,7 +156,4 @@ def ask_if_pvp(): if result.lower() in valid: return result -def generate_random_position(): - return (random.randint(0,2),random.randint(0,2)) -