From 2bea25337333090e2a3086011941e4117eaae82f Mon Sep 17 00:00:00 2001 From: Timm Weber <> Date: Mon, 4 Mar 2024 07:50:16 -0500 Subject: [PATCH] [TTTK-15], [TTTK-54]: Fixed a lot of bugs in AI, Client and Server --- AI/ai_context.py | 2 ++ AI/ai_strategy.py | 62 +++++++++++++++++++++++--------------- Client/client.py | 2 +- Server/gamestate.py | 1 + Server/websocket_server.py | 21 +++++++------ 5 files changed, 53 insertions(+), 35 deletions(-) diff --git a/AI/ai_context.py b/AI/ai_context.py index fdee57b..e24153a 100644 --- a/AI/ai_context.py +++ b/AI/ai_context.py @@ -1,5 +1,6 @@ from . import ai_strategy from threading import Thread +import time class AIContext(): @@ -27,6 +28,7 @@ def run_strategy(self): ai_context = AIContext(weak_ai) # run the strategy ai_context.run_strategy() + time.sleep(3) # create strong ai strategy: set arg to true to indicate that it is the second AI-player in the game(this is needed if AIs play against each other) strong_ai = ai_strategy.AdvancedAIStrategy(True) # set the strategy diff --git a/AI/ai_strategy.py b/AI/ai_strategy.py index 35d6110..e46f9e5 100644 --- a/AI/ai_strategy.py +++ b/AI/ai_strategy.py @@ -5,20 +5,25 @@ from Server.gamestate import GameState import asyncio import random +import logging +import copy + +logging.basicConfig(level=logging.DEBUG) +logger = logging.getLogger(__name__) class AIStrategy(ABC, GameClient): def __init__(self, second_player: bool): + self._strength = "Placeholder" self._good_luck_message = "Good luck!" self._good_game_message_lost = "Good game! You played well." self._good_game_message_won = "Good game! I'll have better luck next time." self._good_game_message_draw = "Good game! We are evenly matched." - self._ai_uuid = "108eaa05-2b0e-4e00-a190-8856edcd56a5" - self._ai_uuid2 = "108eaa05-2b0e-4e00-a190-8856edcd56a6" self._rulebase = RuleBase() self._ip = "127.0.0.1" self._port = 8765 + super().__init__(self._ip, self._port, self._player) def thread_entry(self): asyncio.run(self.run()) @@ -27,15 +32,18 @@ async def run(self): # The AI-UUID is hardcoded so that it can be excluded from statistics await self.join_game() + asyncio.timeout(1) await self.lobby_ready() - print("test") + logger.info("test") + + await self._listening_task async def join_game(self): await self.connect() self._listening_task = asyncio.create_task(self.listen()) - await asyncio.create_task(self.join_lobby()) + await self.join_lobby() @@ -46,17 +54,18 @@ async def _message_handler(self, message_type: str): # AI does not need this pass case "game/start": - print("start") - self.wish_good_luck() + logger.info("start") + if self._current_player == self._player: + await self.do_turn() + #await self.wish_good_luck() case "game/end": - self.say_good_game() + await self.say_good_game() await self.close() - await self._listening_task case "game/turn": - if self._current_player.uuid == self._ai_uuid: - self.do_turn() + if self._current_player == self._player: + await self.do_turn() case "statistics/statistics": # AI does not need this @@ -70,16 +79,16 @@ async def _message_handler(self, message_type: str): return - def wish_good_luck(self): - self.chat_message(self._good_luck_message) + async def wish_good_luck(self): + await self.chat_message(self._good_luck_message) - def say_good_game(self): + async def say_good_game(self): if self._winner.uuid == self._ai_uuid: - self.chat_message(self._good_game_message_won) + await self.chat_message(self._good_game_message_won) elif self._winner.uuid == None: - self.chat_message(self._good_game_message_draw) + await self.chat_message(self._good_game_message_draw) else: - self.chat_message(self._good_game_message_lost) + await self.chat_message(self._good_game_message_lost) def get_empty_cells(self, game_status: list): """ @@ -101,30 +110,34 @@ async def do_turn(self): class WeakAIStrategy(AIStrategy): def __init__(self, second_player: bool = False): - super().__init__(second_player) + self._ai_uuid = "108eaa05-2b0e-4e00-a190-8856edcd56a5" + self._ai_uuid2 = "108eaa05-2b0e-4e00-a190-8856edcd56a6" self._strength = "Weak" self._good_luck_message = "Good luck! I'm still learning so please have mercy on me." self._good_game_message_lost = "Good game! I will try to do better next time." self._good_game_message_won = "Good game! I can't believe I won!" self._good_game_message_draw = "Good game! I' happy I didn't lose." self._player = Player(f"{self._strength} AI", random.randint(0, 0xFFFFFF), uuid=(self._ai_uuid if not second_player else self._ai_uuid2)) + super().__init__(second_player) async def do_turn(self): - empty_cells = self.get_empty_cells(self._game_status) + empty_cells = self.get_empty_cells(self._playfield) move = random.randint(0, len(empty_cells) - 1) await self.game_make_move(empty_cells[move][0], empty_cells[move][1]) class AdvancedAIStrategy(AIStrategy): def __init__(self, second_player: bool = False): - super().__init__(second_player) + self._ai_uuid = "108eaa05-2b0e-4e00-a190-8856edcd56a5" + self._ai_uuid2 = "108eaa05-2b0e-4e00-a190-8856edcd56a6" self._strength = "Advanced" self._good_luck_message = "Good luck! I hope you are ready for a challenge." self._good_game_message_lost = "Good game! I admire your skills." self._good_game_message_won = "Good game! I hope you learned something from me." self._good_game_message_draw = "Good game! I hope you are ready for a rematch." self._player = Player(f"{self._strength} AI", random.randint(0, 0xFFFFFF), uuid=(self._ai_uuid if not second_player else self._ai_uuid2)) + super().__init__(second_player) async def do_turn(self): """ @@ -134,17 +147,18 @@ async def do_turn(self): 3. Make a random move(or maybe more complex logic) """ - empty_cells = self.get_empty_cells(self._game_status) + empty_cells = self.get_empty_cells(self._playfield) # Check for own winning move for possible_move in empty_cells: temp_gamestate = GameState() # Make deep copy of the game state - temp_gamestate._playfield = [self._game_status[row].copy() for row in self._game_status] + temp_gamestate._playfield = copy.deepcopy(self._playfield) temp_gamestate.set_player_position(self._player_number, possible_move) - RuleBase.check_win(temp_gamestate) + logger.info(temp_gamestate._playfield) + self._rulebase.check_win(temp_gamestate) if temp_gamestate.winner == self._player_number: await self.game_make_move(possible_move[0], possible_move[1]) return @@ -154,10 +168,10 @@ async def do_turn(self): temp_gamestate = GameState() # Make deep copy of the game state - temp_gamestate._playfield = [self._game_status[row].copy() for row in self._game_status] + temp_gamestate._playfield = copy.deepcopy(self._playfield) temp_gamestate.set_player_position(self._opponent_number, possible_move) - RuleBase.check_win(temp_gamestate) + self._rulebase.check_win(temp_gamestate) if temp_gamestate.winner == self._opponent_number: await self.game_make_move(possible_move[0], possible_move[1]) return diff --git a/Client/client.py b/Client/client.py index 983c241..d414833 100644 --- a/Client/client.py +++ b/Client/client.py @@ -28,7 +28,7 @@ class GameClient: _opponent_number (int): The number of the opponent in the game. 1 for the first player, 2 for the second player. _current_player (Player): The player that is currently allowed to make a move. _lobby_status (list[str]): The status of the lobby. Contains all players in the lobby. - _game_status (list[list[int]]): The status of the game. Contains the current playfield. + _playfield (list[list[int]]): The status of the game. Contains the current playfield. _statistics: The statistics of the game. TODO _chat_history (list[tuple[Player, str]]): The chat history of the game. Contains all messages sent in the game. _winner (Player): The winner of the game. None if the game is not finished yet or it is a draw. diff --git a/Server/gamestate.py b/Server/gamestate.py index ba57b2f..6b95487 100644 --- a/Server/gamestate.py +++ b/Server/gamestate.py @@ -45,6 +45,7 @@ def set_player_position(self, player: int, new_position: tuple[int, int]): new_position (tuple[int, int]): The new position set by the player on the playfield. """ self._playfield[new_position[0]][new_position[1]] = player + self._current_player = 1 if player == 2 else 2 def set_winner(self, winner: int): """ diff --git a/Server/websocket_server.py b/Server/websocket_server.py index 237891a..824157b 100644 --- a/Server/websocket_server.py +++ b/Server/websocket_server.py @@ -13,7 +13,8 @@ class Lobby: def __init__(self, admin:Player, port: int = 8765) -> None: - self._players = {admin.uuid : admin} + self._players = {} + #self._players = {admin.uuid : admin} self._game = None self._inprogress = False self._port = port @@ -67,7 +68,7 @@ async def handler(self, websocket): "players": [player.as_dict() for player in self._players.values()], })) - if all([player.ready for player in self._players.values()]) and len(self._players) == 2: + if all([player.ready for player in self._players.values()]) and len(self._connections) == 2: # TODO add error messages for why game cant start with not enough or too many ready players # all players are ready, start the game rulebase = RuleBase() @@ -84,24 +85,24 @@ async def handler(self, websocket): case "game/make_move": # check if move can be made if not self._inprogress: - await websocket.send(json.dumps({"message_type": "game/error", "error": "Game not in progress"})) + await websocket.send(json.dumps({"message_type": "game/error", "error_message": "Game not in progress"})) break if message_json["player_uuid"] != self._game.current_player_uuid: - await websocket.send(json.dumps({"message_type": "game/error", "error": "Not your turn"})) + await websocket.send(json.dumps({"message_type": "game/error", "error_message": "Not your turn"})) break # make move, catch illegal move try: - self._game.move(self._players(message_json["player_uuid"]), message_json["move"]) + self._game.move(self._game.players.index(self._players[message_json["player_uuid"]]), (message_json["move"]["x"], message_json["move"]["y"])) except ValueError as e: - await websocket.send(json.dumps({"message_type": "game/error", "error": str(e)})) + await websocket.send(json.dumps({"message_type": "game/error", "error_message": str(e)})) # check for winning state - if self._game.finished: + if self._game.state.finished: websockets.broadcast(self._connections, json.dumps({ "message_type": "game/end", - "winner_uuid": self._game.winner.uuid, - "final_playfiled": self._game.playfield, + "winner_uuid": self._game.state.winner.uuid, + "final_playfiled": self._game.state.playfield, })) self._inprogress = False @@ -109,7 +110,7 @@ async def handler(self, websocket): else: websockets.broadcast(self._connections, json.dumps({ "message_type": "game/turn", - "updated_playfield": self._game.playfield, + "updated_playfield": self._game.state.playfield, "next_player_uuid": self._game.current_player_uuid, }))