Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Docs #20

Merged
merged 17 commits into from
Mar 20, 2024
Merged

Docs #20

Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,4 @@ l10n/
launch.json
venv
*.db
Client/Data/profiles.json
Client/Data/profiles.json
48 changes: 0 additions & 48 deletions Client/client.plantuml

This file was deleted.

27 changes: 26 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,26 @@
# TicTacToeKojote
# TicTacToeKojote

This is a simple TicTacToe game written in Python. IT can be played by two players in a GUI. However, it only works on Windows.

## Installation
- Install Python 3.11 or higher from [python.org](https://www.python.org/downloads/)
- Install the required packages by running `pip install -r requirements.txt` from the root directory of the project
- Run the game by executing `python main.py` from the root directory of the project

## Features
- Play TicTacToe with a friend online
- Play TicTacToe with a friend offline
- Play TicTacToe against an AI (weak or strong)
- Chat with your opponent during the game
- Create Profiles
- View your game statistics whenever you are connected to a server

## How to play
- **Manage your player profiles:** View Use Case 1: Manage Player Profiles
- **Play against AI:** View the corresponding [sequence diagram](docs/sequence_diagrams/play_vs_ai.png)
- **Play against another player locally:** View the corresponding [sequence diagram](docs/sequence_diagrams/play_locally.png)
- **Host an online game:** View Use Case 3.1: Host Game
- **Join an online game:** View Use Case 3.2: Join Game
- **Leave an online game:** View Use Case 3.3: Leave Game
- **Send Chat messages:** View Use Case 4: Chat
- **Display the statistics:** View Use Case 5: Display Statistics
Binary file added docs/class_diagrams/AI.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
75 changes: 75 additions & 0 deletions docs/class_diagrams/AI.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
@startuml AI
!pragma useIntermediatePackages false

class AI.ai_context.AIContext {
- _strategy: AIStrategy

+ AIContext(strategy: ai_strategy.AIStrategy)
+ None set_strategy(strategy: ai_strategy.AIStrategy)
+ None run_strategy()
}
class AI.ai_rulebase.AIRulebase {
+ check_win(state: Server.game_state.GameState)
}
abstract class AI.ai_strategy.AIStrategy {
- _strength: str
- _good_luck_message: str
- _good_game_message_lost: str
- _good_game_message_won: str
- _good_game_message_draw: str
- _current_uuid: str
- _rulebase: AI.ai_rulebase.AIRulebase
- _ip: str
- _port: int

+ AIStrategy()
- post_init()
+ None thread_entry()
+ None run()
+ None join_game()
- _message_handler(message_type: str)
- wish_good_luck()
- say_good_game()
- list[list[int]] get_empty_cells(game_status: list[list[int]])
+ None do_turn()
}

class AI.ai_strategy.AdvancedAIStrategy {
- _current_uuid: str
- _strength: str
- _good_luck_message: str
- _good_game_message_lost: str
- _good_game_message_won: str
- _good_game_message_draw: str
- _player: Server.player.Player

+ AdvancedAIStrategy(uuid: str = 'd90397a5-a255-4101-9864-694b43ce8a6c')
+ None do_turn()
}

class AI.ai_strategy.WeakAIStrategy {
- _current_uuid: str
- _strength: str
- _good_luck_message: str
- _good_game_message_lost: str
- _good_game_message_won: str
- _good_game_message_draw: str
- _player: Server.player.Player

+ WeakAIStrategy(uuid: str = '108eaa05-2b0e-4e00-a190-8856edcd56a5')
- check_winning_move(empty_cells: list[list[int]], player:int)
+ None do_turn()
}

AI.ai_context.AIContext *-- AI.ai_strategy.AIStrategy
AI.ai_strategy.AIStrategy <|-- AI.ai_strategy.AdvancedAIStrategy
AI.ai_strategy.AIStrategy <|-- AI.ai_strategy.WeakAIStrategy
AI.ai_strategy.AIStrategy *-- AI.ai_rulebase.AIRulebase
AI.ai_strategy.AIStrategy --|> Client.client.GameClient
AI.ai_rulebase.AIRulebase --|> Server.rulebase.Rulebase

AI.ai_strategy.AdvancedAIStrategy *-- Server.player.Player
AI.ai_strategy.WeakAIStrategy *-- Server.player.Player

@enduml

Binary file added docs/class_diagrams/Client.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/class_diagrams/Server.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
64 changes: 64 additions & 0 deletions docs/class_diagrams/client.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
@startuml Client
!pragma useIntermediatePackages false

class Client.client.GameClient {
- _ip: str
- _port: int
- _player: Player
- _player_number: int
- _symbol: str
- _opponent: Player
- _opponent_number: int
- _starting_player: Player
- _current_player: Player
- _lobby_status: list[str]
- _playfield: list[list[int]]
- _statistics: Server.statistics.Statistics
- _chat_history: list[tuple[Player, str]]
- _winner: Player
- _error_history: list[str]
- _json_schema: Any

+ None GameClient(ip:str, port:int, player:Player)
- None connect()
+ tuple[GameClient, asyncio.Task, Thread] create_game(player: Player, port:int = 8765)
+ tuple[GameClient, asyncio.Task] join_game(player: Player, ip:str, port:int = 8765)
+ None listen()
+ Player get_player_by_uuid(uuid:str)
- None _preprocess_message(message:str)
- None _message_handler(message_type: str)
+ None join_lobby()
+ None lobby_ready(ready:bool = True)
+ None lobby_kick(player_to_kick_index:int)
+ None game_make_move(x:int, y:int)
+ None chat_message(message:str)
+ None close()
+ None terminate()
}

class Client.ui_client.GameClientUI {
- _tk_root: tk.Tk
- _in_queue: queue.Queue
- _out_queue: queue.Queue

+ None GameClientUI(ip:str, port:int, player:Player, tk_root:tk.Tk, out_queue:Queue, in_queue:Queue)
+ tuple[GameClientUI, asyncio.Task, asyncio.Task] join_game(player: Player, ip: str, tk_root:tk.Tk, out_queue:Queue, in_queue:Queue, port: int = 8765)
- None listen()
- None _message_handler(message_type: str)
- None await_commands()
- None send_gamestate_to_ui()
}

class Client.profile_save.Profile {
+ list[Player] get_profiles()
+ None set_profiles(players:list[Player], selected:int)
+ None delete_all_profiles()
}

class Server.statistics.Statistics {
}

Client.client.GameClient <|-- Client.ui_client.GameClientUI
Client.client.GameClient *-- Server.statistics.Statistics
@enduml

90 changes: 90 additions & 0 deletions docs/class_diagrams/server.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
@startuml Server
!pragma useIntermediatePackages false

class Server.game.Game {
- _uuid: UUID
- _id: int
+ state: Server.gamestate.GameState
+ players: list[Server.player.Player]
+ rule_base: RuleBase

+ Game(player1: Player, player2: Player, rule_base: RuleBase = RuleBase())
+ None move(player: int, new_position: tuple[int, int])
+ str current_player_uuid()
+ Server.player.Player winner()
}
class Server.gamestate.GameState {
- _playfield: list[list[int]]
- _finished: bool
- _winner: int
- _current_player: int

+ GameState(playfield_dimensions: tuple[int, int] = (3,3))
+ None set_player_position(player: int, new_position: tuple[int, int])
+ None set_winner(winner: int)
+ int winner()
+ bool finished()
+ list[list[int]] playfield()
+ int playfield_value(position: tuple[int, int])
+ tuple[int, int] playfield_dimensions()
+ int current_player()
}
class Server.player.Player {
+ uuid: UUID
+ display_name: str
+ color: int
+ ready: bool

+ Player(display_name: str, color: int, uuid: UUID = uuid4(), ready:bool = False)
+ dict[str, Any] as_dict()
+ Player from_dict(data: dict[str, Any])
}
class Server.rulebase.RuleBase {
- _playfield_dimensions: tuple[int, int]

+ RuleBase(playfield_dimensions: tuple[int, int] = (3,3))
+ bool is_move_valid(state: GameState, new_position: tuple[int, int])
+ None check_win(state: GameState)
+ bool is_game_state_valid(state: GameState)
+ list[list[int]] transpose(matrix: list[list[int]])
}
class Server.websocket_server.Lobby {
- _players: dict[str, Server.player.Player]
- _game: Server.game.Game
- _inprogress: bool
- _port: int
- _connections: list[websockets.server.WebSocketServerProtocol]
- _stats: Server.statistics.Statistics
- _json_schema: dict[str, Any]

+ Lobby(admin:Player, port: int = 8765)
+ None handler(websocket: websockets.server.WebSocketServerProtocol)
- None _end_game()
+ None start_server()
+ None run()
}
class Server.statistics.Statistics {
- path: str
- conn: sqlite3.Connection
- cursor: sqlite3.Cursor

+ Statistics(path: str = os.path.abspath('Server/Data/statistics.db'))
+ list[tuple[str, int]] get_statistics()
+ None increment_emojis(player: Player, message: str)
+ None increment_moves(player: Player)
+ None increment_games(player_list: list[Player], winner: int)
- None _increment_win(player: Player)
- None _increment_loss(player: Player)
- None _increment_draws(player: Player)
- None _check_add_profile(player: Player)
- bool _check_profile(uuid_str: str)
- None _add_profile(player: Player)
}
Server.game.Game *-- Server.rulebase.RuleBase
Server.game.Game *-- Server.gamestate.GameState
Server.game.Game *-- Server.player.Player
Server.websocket_server.Lobby *-- Server.game.Game
Server.websocket_server.Lobby *-- Server.statistics.Statistics
Server.websocket_server.Lobby *-- Server.player.Player
@enduml

Binary file added docs/patterns/observer.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 31 additions & 0 deletions docs/patterns/observer.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
@startuml Server
!pragma useIntermediatePackages false

class Server.websocket_server.Lobby {
- _connections: set[WebSocketServerProtocol]
+ handler(websocket: WebSocketServerProtocol)
}

class websockets.WebSocketServerProtocol {
+ send(message: str)
+ str recv()
}

note left of Server.websocket_server.Lobby::handler
Whenever a new connection is made, the handler is called.
The handler adds the connection to the set of connections
(_connections) and removes it when the connection is closed.
Whenever a message is received, the handler uses
websockets.broadcast() to send the updated game state to all
connections in _connections.
end note

note bottom of websockets.WebSocketServerProtocol
The WebSocketServerProtocol is a protocol that is used to
handle the WebSocket connection. It has methods to send and
receive messages, and it also has methods to handle the
opening and closing of the connection.
end note

Server.websocket_server.Lobby o-- websockets.WebSocketServerProtocol
@enduml
Binary file added docs/sequence_diagrams/create_profile.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
15 changes: 15 additions & 0 deletions docs/sequence_diagrams/create_profile.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
@startuml
title Create Player Profile

actor Player
database Client

Player->Client: Click Profile Button
Client->Player: Show Profile Creation Prompt
Player->Client: Set Name
Player->Client: Set Color
Player->Client: Click Create Profile Button
Client->Client: Save Profile on Disk
Client->Player: Show Profile

@enduml
Binary file added docs/sequence_diagrams/delete_profile.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions docs/sequence_diagrams/delete_profile.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@startuml
title Delete Player Profile

actor Player
database Client

Player->Client: Click Profile Button
Client->Player: Show Profile
Player->Client: Click Delete Profile Button
Client->Client: Delete Profile from Disk
Client->Player: Show Profile Creation Prompt

@enduml
Binary file added docs/sequence_diagrams/edit_profile.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading