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..45e175c --- /dev/null +++ b/src/tic_tac_toe/main.py @@ -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() diff --git a/src/tic_tac_toe/utils.py b/src/tic_tac_toe/utils.py new file mode 100644 index 0000000..5dfc329 --- /dev/null +++ b/src/tic_tac_toe/utils.py @@ -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 + + 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()