From 7daced84586c958af02b28182a52e10167fbb341 Mon Sep 17 00:00:00 2001 From: Carina Straub Date: Mon, 27 Nov 2023 18:02:01 +0100 Subject: [PATCH 1/6] Resturctured Game and Server class Co-authored-by: Paco --- teamprojekt_competition_server/server/game.py | 108 +++++++++++++++--- teamprojekt_competition_server/server/main.py | 34 ++++-- .../server/server.py | 19 +-- .../server/server_protocol.py | 22 ++-- 4 files changed, 138 insertions(+), 45 deletions(-) diff --git a/teamprojekt_competition_server/server/game.py b/teamprojekt_competition_server/server/game.py index a80da4f2..cca70809 100644 --- a/teamprojekt_competition_server/server/game.py +++ b/teamprojekt_competition_server/server/game.py @@ -1,21 +1,57 @@ """base class for games""" - class Game: """game interface""" def __init__(self, player1, player2) -> None: self.player1 = player1 self.player2 = player2 - self.current_player = 1 - self.env = 1 - self.num_ready = 0 + self.env = None + + async def start_game(self): + """starts the game""" + player1_ready = self.player1.start_game(self) + player2_ready = self.player2.start_game(self) + print(player1_ready, player2_ready) + if player1_ready and player2_ready: + await self.one_game_cycle() + elif(player2_ready): + self.end_game("Player 1 is not ready") + else: + self.end_game("Player 2 is not ready") + + + async def one_game_cycle(self): + """sends a step request to both players and changes the enviroment accordingly.""" + # has to be implemented in a subclass + pass + + def get_action(self, player): + """request an valid action from one player""" + for _ in range(3): # each plyer gets three tries for a valid move + action = player.step(env=self.env) + if self.valid_action(action): return action + self.end_game("Invalid action") + return None + + def valid_action(self, action): + """check weather an action is valid""" + return True + + def check_game_finished(self) -> bool: + """detirmens if the game has ended - def send_step(self): - """sends a step request to player instance.""" - player = self.player1 if (self.current_player == 1) else self.player2 - player.step(env=self.env) - print(f"Game: Send Player {self.current_player} a step request.") + Returns: + bool: returns true if game has ended + """ + return True + + def end_game(self, reason = "unknown"): + """ends the game""" + print("Game has endet. Reason: " + reason) #log the reason the game has endet + self.player1.end_game() + self.player2.end_game() + def receive_step(self, action): """handles step received from player @@ -26,14 +62,52 @@ def receive_step(self, action): print(f"Game: Player {self.current_player} made the step {action}.") self.current_player = 1 if self.current_player == 2 else 2 if input("End game? (y/n)") == "y": - self.player1.end_game() - self.player2.end_game() + self.end_game() else: self.send_step() - def ready(self): - """ "manages the counter for ready players""" - print("Player is ready.") - self.num_ready += 1 - if self.num_ready == 2: - self.send_step() +class AlternatingGame(Game): + """Game class where the environment is updated BETWEEN each players step""" + async def one_game_cycle(self): + """sends a step request to both players and changes the enviroment accordingly.""" + #step for player1 + action1 = self.get_action(player=self.player1) + if action1==None: return # exit if move wasn't valid + self.update_environment(action1) + if (self.check_game_finished): # check if game has endet + return + + #step for player2 + action2 = self.get_action(player=self.player1) + if action1==None: return # exit if move wasn't valid + self.update_environment(action2) + if (self.check_game_finished): # check if game has endet + return + + await self.one_game_cycle + + def update_environment(self, action): + """update the game enviroment after one action""" + pass + +class SimultaneousGame(Game): + """Game class where the environment is AFTER both players made a step""" + async def one_game_cycle(self): + """sends a step request to both players and changes the enviroment accordingly.""" + #step for player1 + action1 = self.get_action(player=self.player1) + if action1==None: return # exit if move wasn't valid + #step for player2 + action2 = self.get_action(player=self.player1) + if action2==None: return # exit if move wasn't valid + #update enviroment + self.update_environment(action1=action1, action2=action2) + + if (not self.check_game_finished): # check if game has endet + await self.one_game_cycle + else: + return + + def update_environment(self, action1, action2): + """update the game enviroment after both actions""" + pass \ No newline at end of file diff --git a/teamprojekt_competition_server/server/main.py b/teamprojekt_competition_server/server/main.py index 358eb1e3..0dbf5c31 100644 --- a/teamprojekt_competition_server/server/main.py +++ b/teamprojekt_competition_server/server/main.py @@ -1,16 +1,26 @@ -"""run a client dummy agent""" - -from twisted.internet import reactor - -from twisted.internet.endpoints import TCP4ServerEndpoint -from .server_protocol import COMPServerFactory +"""run the server""" +from teamprojekt_competition_server.server.server_protocol import COMPServerProtocol +from .server import COMPServer +from .game import AlternatingGame # run with "python -m teamprojekt_competition_server.server.main" if __name__ == "__main__": - factory = COMPServerFactory() - - endpoint = TCP4ServerEndpoint(reactor, 1234) - endpoint.listen(factory) - print("Server Started") - reactor.run() # type: ignore[attr-defined] + + class MyGame(AlternatingGame): + def __init__(self, player1: COMPServerProtocol, player2) -> None: + super().__init__(player1, player2) + self.env =1 + + def valid_action(self, action): + return isinstance(action, int) + + def check_game_finished(self) -> bool: + return (input("Continue game? (y/n)") == "n") + + def update_environment(self, action): + self.env += action["action"] + print(action, self.env) + + server = COMPServer(GameClass=MyGame) + server.start() diff --git a/teamprojekt_competition_server/server/server.py b/teamprojekt_competition_server/server/server.py index befbfd73..d187a0d6 100644 --- a/teamprojekt_competition_server/server/server.py +++ b/teamprojekt_competition_server/server/server.py @@ -1,20 +1,25 @@ """class for server""" +from typing import Type + from twisted.internet import reactor -from .server_protocol import COMPServerFactory +from twisted.internet.endpoints import TCP4ServerEndpoint +from .server_protocol import COMPServerFactory +from .game import Game class COMPServer: """class for server instance""" - def __init__(self) -> None: - self.factory = COMPServerFactory() + def __init__(self, GameClass: Type[Game]) -> None: + self.factory = COMPServerFactory(game_class=GameClass) def start(self): """set up server at localhost:1234.""" - - reactor.listenTCP(1234, self.factory) - reactor.run() + endpoint = TCP4ServerEndpoint(reactor, 1234) + endpoint.listen(self.factory) + print("Server Started") + reactor.run() # type: ignore[attr-defined] def stop(self): """terminates server.""" - reactor.stop() + reactor.stop() \ No newline at end of file diff --git a/teamprojekt_competition_server/server/server_protocol.py b/teamprojekt_competition_server/server/server_protocol.py index e6776448..0b75d9d6 100644 --- a/teamprojekt_competition_server/server/server_protocol.py +++ b/teamprojekt_competition_server/server/server_protocol.py @@ -1,6 +1,7 @@ """class for server protocol""" +import asyncio -from typing import cast, List +from typing import cast, List, Type from twisted.internet.interfaces import IAddress from twisted.protocols import amp from twisted.internet.protocol import Protocol, ServerFactory @@ -33,7 +34,7 @@ def connectionLost(self, reason): ) # TODO: this is really really, like ultra hacky !!!! return super().connectionLost(reason) - def auth_client(self, token: str, version): + async def auth_client(self, token: str, version): """is called when a client wants to authenticate itself Args: @@ -54,7 +55,7 @@ def auth_client(self, token: str, version): self ) # TODO: this is really really, like ultra hacky !!!! - cast(COMPServerFactory, self.factory).find_opponent( + await cast(COMPServerFactory, self.factory).find_opponent( self ) # TODO: this is really really, like ultra hacky !!!! @@ -69,7 +70,7 @@ def start_game(self, game: Game): game (Game): game that starts """ self.game = game - self.callRemote(StartGame, game_id=222).addCallback(lambda x: game.ready()) + return self.callRemote(StartGame, game_id=222).asFuture(loop=asyncio.get_event_loop()) def step(self, env): """perfroms step requested by player""" @@ -78,7 +79,7 @@ def answer(x): action = x.get("action") self.game.receive_step(action=action) - self.callRemote(Step, env=int(env)).addCallback(answer) + return self.callRemote(Step, env=int(env)).asFuture(loop=asyncio.get_event_loop()) def end_game(self): """ends the game""" @@ -93,12 +94,16 @@ class COMPServerFactory(ServerFactory): # queue for storing agents waiting for a game player_queue: List[COMPServerProtocol] = [] + def __init__(self, game_class: Type[Game]) -> None: + super().__init__() + self.GameClass = game_class + def buildProtocol(self, addr: IAddress) -> Protocol | None: """builds the protocoll""" protocol = COMPServerProtocol(factory=self) return protocol - def find_opponent(self, player: COMPServerProtocol): + async def find_opponent(self, player: COMPServerProtocol): """addes the player to a queue in order to match two players Args: player (COMPServerProtocol): player that wants to find an opponent @@ -108,9 +113,8 @@ def find_opponent(self, player: COMPServerProtocol): else: player1 = player player2 = self.player_queue.pop() - game = Game(player1=player1, player2=player2) - player1.start_game(game) - player2.start_game(game) + game = self.GameClass(player1=player1, player2=player2) + await game.start_game() def client_connected(self, client: COMPServerProtocol): """add a newly connected client to the list of logged in clients From 16d40dff54ce60a9815298fa5e5c1f80a1377c04 Mon Sep 17 00:00:00 2001 From: Paul Masan Date: Thu, 7 Dec 2023 15:25:43 +0100 Subject: [PATCH 2/6] Reordered the core structure and started with asyncio compability --- .idea/.gitignore | 8 + .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 4 + .idea/modules.xml | 8 + .idea/teamproject-competition-server.iml | 12 ++ .idea/vcs.xml | 6 + .../client/client.py | 12 +- .../client/client_protocol.py | 17 ++- teamprojekt_competition_server/client/main.py | 4 +- .../server/factory.py | 24 +++ teamprojekt_competition_server/server/game.py | 113 -------------- .../server/game_manager.py | 19 +++ .../server/interfaces.py | 60 ++++++++ teamprojekt_competition_server/server/main.py | 37 ++--- .../server/player.py | 25 +++ .../server/protocol.py | 51 +++++++ .../server/server.py | 15 +- .../server/server_protocol.py | 142 ------------------ .../shared/commands.py | 56 +------ .../shared/twisted_asyncio.py | 10 ++ 20 files changed, 285 insertions(+), 344 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/teamproject-competition-server.iml create mode 100644 .idea/vcs.xml create mode 100644 teamprojekt_competition_server/server/factory.py delete mode 100644 teamprojekt_competition_server/server/game.py create mode 100644 teamprojekt_competition_server/server/game_manager.py create mode 100644 teamprojekt_competition_server/server/interfaces.py create mode 100644 teamprojekt_competition_server/server/player.py create mode 100644 teamprojekt_competition_server/server/protocol.py delete mode 100644 teamprojekt_competition_server/server/server_protocol.py create mode 100644 teamprojekt_competition_server/shared/twisted_asyncio.py diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..1c2fda56 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 00000000..105ce2da --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000..f5a93a6f --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..3de58b75 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/teamproject-competition-server.iml b/.idea/teamproject-competition-server.iml new file mode 100644 index 00000000..2946dc0d --- /dev/null +++ b/.idea/teamproject-competition-server.iml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..c8397c94 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/teamprojekt_competition_server/client/client.py b/teamprojekt_competition_server/client/client.py index 5b74f87c..266fde8f 100644 --- a/teamprojekt_competition_server/client/client.py +++ b/teamprojekt_competition_server/client/client.py @@ -1,12 +1,11 @@ """class for client""" -from twisted.internet import defer, reactor +from twisted.internet import reactor from twisted.internet.endpoints import TCP4ClientEndpoint, connectProtocol from twisted.protocols.amp import CommandLocator from .client_protocol import COMPClientProtocol -from ..shared.commands import AuthClient class COMPClient(CommandLocator): @@ -23,13 +22,6 @@ def connect_client(self, token: str): Args: token (str): token to verify the client """ - version = int(1) destination = TCP4ClientEndpoint(reactor, "127.0.0.1", 1234) - auth = connectProtocol(destination, COMPClientProtocol(agent=self.agent)) - auth.addCallback( - lambda ampProto: ampProto.callRemote( - AuthClient, token=str.encode(token), version=version - ) - ) - defer.DeferredList([auth]) + auth = connectProtocol(destination, COMPClientProtocol(agent=self.agent, token=token)) reactor.run() # type: ignore[attr-defined] diff --git a/teamprojekt_competition_server/client/client_protocol.py b/teamprojekt_competition_server/client/client_protocol.py index 01b63f9a..16b4eb9d 100644 --- a/teamprojekt_competition_server/client/client_protocol.py +++ b/teamprojekt_competition_server/client/client_protocol.py @@ -4,15 +4,18 @@ from twisted.protocols import amp from twisted.internet.protocol import ClientFactory, Protocol -from ..shared.commands import StartGame, EndGame, Step +from ..shared.commands import StartGame, EndGame, Step, Auth class COMPClientProtocol(amp.AMP): """protocol for the client""" - def __init__(self, agent, boxReceiver=None, locator=None): + token: str = "Unknown" + + def __init__(self, agent, token, boxReceiver=None, locator=None): super().__init__(boxReceiver, locator) self.agent = agent + self.token = token def start_game(self, game_id: int): """is called when the server starts the game @@ -58,6 +61,16 @@ def step(self, env): Step.responder(step) + def auth(self): + """called for auth the client + + Returns: + {"token": String}: the clients auth token + """ + return {"token": self.token, "version": 1} + + Auth.responder(auth) + class COMPClientFactory(ClientFactory): """factory for COMP clients""" diff --git a/teamprojekt_competition_server/client/main.py b/teamprojekt_competition_server/client/main.py index 8b4db434..50fc0a67 100644 --- a/teamprojekt_competition_server/client/main.py +++ b/teamprojekt_competition_server/client/main.py @@ -20,6 +20,4 @@ def step(self, env): return int(input(f"Enviroment: {env} \nEnter a move: ")) agent = MyAgent() - token = "ABC" # dummy token - agent.run(token) - print("Hello") + agent.run("HelloWorldToken") diff --git a/teamprojekt_competition_server/server/factory.py b/teamprojekt_competition_server/server/factory.py new file mode 100644 index 00000000..c1a54f27 --- /dev/null +++ b/teamprojekt_competition_server/server/factory.py @@ -0,0 +1,24 @@ +""" Hello World """ + +import asyncio +import logging as log + +from twisted.internet.interfaces import IAddress +from twisted.internet.protocol import Protocol, ServerFactory + +from .protocol import COMPServerProtocol +from .player import COMPPlayer +from .game_manager import GameManager + +class COMPServerFactory(ServerFactory): + """factory for COMP servers""" + + manager : GameManager = GameManager() #TODO: this is hacked, move it into the COMPServer class... + + def buildProtocol(self, addr: IAddress) -> Protocol | None: + """builds the protocoll""" + protocol : COMPServerProtocol = COMPServerProtocol() + + self.manager.add_player(COMPPlayer(protocol)) + + return protocol \ No newline at end of file diff --git a/teamprojekt_competition_server/server/game.py b/teamprojekt_competition_server/server/game.py deleted file mode 100644 index cca70809..00000000 --- a/teamprojekt_competition_server/server/game.py +++ /dev/null @@ -1,113 +0,0 @@ -"""base class for games""" - -class Game: - """game interface""" - - def __init__(self, player1, player2) -> None: - self.player1 = player1 - self.player2 = player2 - self.env = None - - async def start_game(self): - """starts the game""" - player1_ready = self.player1.start_game(self) - player2_ready = self.player2.start_game(self) - print(player1_ready, player2_ready) - if player1_ready and player2_ready: - await self.one_game_cycle() - elif(player2_ready): - self.end_game("Player 1 is not ready") - else: - self.end_game("Player 2 is not ready") - - - async def one_game_cycle(self): - """sends a step request to both players and changes the enviroment accordingly.""" - # has to be implemented in a subclass - pass - - def get_action(self, player): - """request an valid action from one player""" - for _ in range(3): # each plyer gets three tries for a valid move - action = player.step(env=self.env) - if self.valid_action(action): return action - self.end_game("Invalid action") - return None - - def valid_action(self, action): - """check weather an action is valid""" - return True - - def check_game_finished(self) -> bool: - """detirmens if the game has ended - - Returns: - bool: returns true if game has ended - """ - return True - - def end_game(self, reason = "unknown"): - """ends the game""" - print("Game has endet. Reason: " + reason) #log the reason the game has endet - self.player1.end_game() - self.player2.end_game() - - - def receive_step(self, action): - """handles step received from player - - Args: - action (int): players requested action.""" - - print(f"Game: Player {self.current_player} made the step {action}.") - self.current_player = 1 if self.current_player == 2 else 2 - if input("End game? (y/n)") == "y": - self.end_game() - else: - self.send_step() - -class AlternatingGame(Game): - """Game class where the environment is updated BETWEEN each players step""" - async def one_game_cycle(self): - """sends a step request to both players and changes the enviroment accordingly.""" - #step for player1 - action1 = self.get_action(player=self.player1) - if action1==None: return # exit if move wasn't valid - self.update_environment(action1) - if (self.check_game_finished): # check if game has endet - return - - #step for player2 - action2 = self.get_action(player=self.player1) - if action1==None: return # exit if move wasn't valid - self.update_environment(action2) - if (self.check_game_finished): # check if game has endet - return - - await self.one_game_cycle - - def update_environment(self, action): - """update the game enviroment after one action""" - pass - -class SimultaneousGame(Game): - """Game class where the environment is AFTER both players made a step""" - async def one_game_cycle(self): - """sends a step request to both players and changes the enviroment accordingly.""" - #step for player1 - action1 = self.get_action(player=self.player1) - if action1==None: return # exit if move wasn't valid - #step for player2 - action2 = self.get_action(player=self.player1) - if action2==None: return # exit if move wasn't valid - #update enviroment - self.update_environment(action1=action1, action2=action2) - - if (not self.check_game_finished): # check if game has endet - await self.one_game_cycle - else: - return - - def update_environment(self, action1, action2): - """update the game enviroment after both actions""" - pass \ No newline at end of file diff --git a/teamprojekt_competition_server/server/game_manager.py b/teamprojekt_competition_server/server/game_manager.py new file mode 100644 index 00000000..cfa2a164 --- /dev/null +++ b/teamprojekt_competition_server/server/game_manager.py @@ -0,0 +1,19 @@ +"""Structure which handles multiple games""" + +import logging as log + +from .interfaces import IPlayer, IGame + +class GameManager: + + players : list[IPlayer] = [] + games : list[IGame] = [] + + def __init__(self) -> None: + pass + + def add_player(self, player: IPlayer) -> None: + log.debug("Connected Player") + + self.players.append(player) + \ No newline at end of file diff --git a/teamprojekt_competition_server/server/interfaces.py b/teamprojekt_competition_server/server/interfaces.py new file mode 100644 index 00000000..2a9bfab1 --- /dev/null +++ b/teamprojekt_competition_server/server/interfaces.py @@ -0,0 +1,60 @@ +"""defines interfaces for the server logic""" + +import logging as log + +import abc + +class IPlayer(abc.ABC): + + @abc.abstractmethod + async def authenticate(self): + ... + + @abc.abstractmethod + async def notify_start(self): + ... + + @abc.abstractmethod + async def get_action(self, obv): + ... + + @abc.abstractmethod + async def notify_end(self): + ... + + +class IGame(abc.ABC): + """game interface""" + + players : list[IPlayer] = [] + + def __init__(self, players : list[IPlayer]) -> None: + self.players = players + + async def start(self): + for p in self.players: + if not await p.notify_start(): + log.error("player not ready...") + + async def end(self, reason="unknown"): + for p in self.players: + await p.notify_end(reason) + + @abc.abstractmethod + async def _game_cycle(self): + """sends a step request to both players and changes the enviroment accordingly.""" + ... + + @abc.abstractmethod + async def _validate_action(self, action) -> bool: + """check weather an action is valid""" + ... + + @abc.abstractmethod + async def _is_finished(self) -> bool: + """detirmens if the game has ended + + Returns: + bool: returns true if game has ended + """ + ... \ No newline at end of file diff --git a/teamprojekt_competition_server/server/main.py b/teamprojekt_competition_server/server/main.py index 0dbf5c31..b9f033b8 100644 --- a/teamprojekt_competition_server/server/main.py +++ b/teamprojekt_competition_server/server/main.py @@ -1,26 +1,29 @@ """run the server""" -from teamprojekt_competition_server.server.server_protocol import COMPServerProtocol + from .server import COMPServer -from .game import AlternatingGame +from .interfaces import IGame, IPlayer + +import logging +logging.basicConfig(level = logging.DEBUG) # run with "python -m teamprojekt_competition_server.server.main" if __name__ == "__main__": - - class MyGame(AlternatingGame): - def __init__(self, player1: COMPServerProtocol, player2) -> None: - super().__init__(player1, player2) - self.env =1 - - def valid_action(self, action): - return isinstance(action, int) + + class ExampleGame(IGame): + def __init__(self, players: list[IPlayer]) -> None: + super().__init__(players=players) + self.env = 0 + + async def _game_cycle(self): + for p in self.players: + self.env += await p.get_action() - def check_game_finished(self) -> bool: - return (input("Continue game? (y/n)") == "n") + async def _validate_action(self, action): + return isinstance(action, int) - def update_environment(self, action): - self.env += action["action"] - print(action, self.env) - - server = COMPServer(GameClass=MyGame) + async def _is_finished(self) -> bool: + return self.env > 10 + + server = COMPServer() server.start() diff --git a/teamprojekt_competition_server/server/player.py b/teamprojekt_competition_server/server/player.py new file mode 100644 index 00000000..e5a46e4a --- /dev/null +++ b/teamprojekt_competition_server/server/player.py @@ -0,0 +1,25 @@ +"""Player""" + +from .protocol import COMPServerProtocol +from .interfaces import IPlayer + +from ..shared.twisted_asyncio import twisted_async + +class COMPPlayer(IPlayer): + connection : COMPServerProtocol + + def __init__(self, connection : COMPServerProtocol) -> None: + self.connection = connection + + async def authenticate(self): + return await self.connection.get_token() + + async def notify_start(self): + self.connection.notify_start() + + async def get_action(self, obv): + return await self.connection.get_step(obv) + + async def notify_end(self): + return self.connection.notify_end() + \ No newline at end of file diff --git a/teamprojekt_competition_server/server/protocol.py b/teamprojekt_competition_server/server/protocol.py new file mode 100644 index 00000000..08f3d1d1 --- /dev/null +++ b/teamprojekt_competition_server/server/protocol.py @@ -0,0 +1,51 @@ +"""class for server protocol""" +import asyncio +import logging as log + +from twisted.protocols import amp + +from ..shared.commands import Auth, StartGame, EndGame, Step +from ..shared.twisted_asyncio import twisted_async + + +class COMPServerProtocol(amp.AMP): + """amp protocol for a COMP server""" + + def __init__(self, boxReceiver=None, locator=None): + super().__init__(boxReceiver, locator) + + def connectionLost(self, reason) -> None: + """is called when a client disconnects""" + + # TODO handle this! + + return super().connectionLost(reason) + + def connectionMade(self): + log.debug("Client connected") + test = self.get_token() + print(test) + return super().connectionMade() + + async def get_token(self) -> str: + token = await self.callRemote(Auth) + log.debug(token) + return token + + async def notify_start(self) -> bool: + """starts the game + + Args: + game (Game): game that starts + """ + return await self.callRemote(StartGame, game_id=222).asFuture(loop=asyncio.get_event_loop()) + + async def get_step(self, obv): + """perfroms step requested by player""" + + return await self.callRemote(Step, obv=int(obv)).asFuture(loop=asyncio.get_event_loop()) + + async def notify_end(self, result, stats) -> bool: + """ends the game""" + + return await self.callRemote(EndGame, result=True, stats=4).asFuture(loop=asyncio.get_event_loop()) diff --git a/teamprojekt_competition_server/server/server.py b/teamprojekt_competition_server/server/server.py index d187a0d6..d2f101c0 100644 --- a/teamprojekt_competition_server/server/server.py +++ b/teamprojekt_competition_server/server/server.py @@ -1,25 +1,26 @@ """class for server""" from typing import Type +import logging as log from twisted.internet import reactor from twisted.internet.endpoints import TCP4ServerEndpoint -from .server_protocol import COMPServerFactory -from .game import Game +from .factory import COMPServerFactory class COMPServer: """class for server instance""" - def __init__(self, GameClass: Type[Game]) -> None: - self.factory = COMPServerFactory(game_class=GameClass) + def __init__(self) -> None: + self.factory = COMPServerFactory() def start(self): """set up server at localhost:1234.""" - endpoint = TCP4ServerEndpoint(reactor, 1234) - endpoint.listen(self.factory) - print("Server Started") + self.endpoint = TCP4ServerEndpoint(reactor, 1234) #TODO the port should be in some .env file or so + self.endpoint.listen(self.factory) + log.debug("Server Started") #TODO some more info here reactor.run() # type: ignore[attr-defined] def stop(self): """terminates server.""" + log.debug("Server Stopped") reactor.stop() \ No newline at end of file diff --git a/teamprojekt_competition_server/server/server_protocol.py b/teamprojekt_competition_server/server/server_protocol.py deleted file mode 100644 index 0b75d9d6..00000000 --- a/teamprojekt_competition_server/server/server_protocol.py +++ /dev/null @@ -1,142 +0,0 @@ -"""class for server protocol""" -import asyncio - -from typing import cast, List, Type -from twisted.internet.interfaces import IAddress -from twisted.protocols import amp -from twisted.internet.protocol import Protocol, ServerFactory - -from ..shared.commands import ( - AuthClient, - StartGame, - EndGame, - Step, -) - -from .game import Game - - -class COMPServerProtocol(amp.AMP): - """amp protocol for a COMP server""" - - game = None - - def __init__(self, factory, boxReceiver=None, locator=None): - self.factory = factory - super().__init__(boxReceiver, locator) - - def connectionLost(self, reason): - """is called when a client disconnects""" - cast( - COMPServerFactory, self.factory - ).client_disconnected( # keep track of the logged in clients - self - ) # TODO: this is really really, like ultra hacky !!!! - return super().connectionLost(reason) - - async def auth_client(self, token: str, version): - """is called when a client wants to authenticate itself - - Args: - token (str): token to authenticate the client - version (int): current version of the client - - Returns: - {"uuid": int}: unique client ID - """ - - # TODO: Auth. the client - - print(f"--- Authentification --- \nToken: {token} | Version: {version}") - - cast( - COMPServerFactory, self.factory - ).client_connected( # keep track of the (auth.) connected clients - self - ) # TODO: this is really really, like ultra hacky !!!! - - await cast(COMPServerFactory, self.factory).find_opponent( - self - ) # TODO: this is really really, like ultra hacky !!!! - - return {"uuid": 1111} # dummy uuid - - AuthClient.responder(auth_client) - - def start_game(self, game: Game): - """starts the game - - Args: - game (Game): game that starts - """ - self.game = game - return self.callRemote(StartGame, game_id=222).asFuture(loop=asyncio.get_event_loop()) - - def step(self, env): - """perfroms step requested by player""" - - def answer(x): - action = x.get("action") - self.game.receive_step(action=action) - - return self.callRemote(Step, env=int(env)).asFuture(loop=asyncio.get_event_loop()) - - def end_game(self): - """ends the game""" - self.callRemote(EndGame, result=True, stats=4).addCallback(lambda x: print(x)) - - -class COMPServerFactory(ServerFactory): - """factory for COMP servers""" - - # a basic list for storing logged in clients - active_clients: List[COMPServerProtocol] = [] - # queue for storing agents waiting for a game - player_queue: List[COMPServerProtocol] = [] - - def __init__(self, game_class: Type[Game]) -> None: - super().__init__() - self.GameClass = game_class - - def buildProtocol(self, addr: IAddress) -> Protocol | None: - """builds the protocoll""" - protocol = COMPServerProtocol(factory=self) - return protocol - - async def find_opponent(self, player: COMPServerProtocol): - """addes the player to a queue in order to match two players - Args: - player (COMPServerProtocol): player that wants to find an opponent - """ - if len(self.player_queue) < 1: # no players waiting - self.player_queue.append(player) - else: - player1 = player - player2 = self.player_queue.pop() - game = self.GameClass(player1=player1, player2=player2) - await game.start_game() - - def client_connected(self, client: COMPServerProtocol): - """add a newly connected client to the list of logged in clients - - Args: - client (COMPServerProtocol): the client that connected - """ - self.active_clients.append(client) # add new client - # print("a client connected to the server.") - # print("currently there are " + str(len(self.active_clients)) - # + " active clients:") - - def client_disconnected(self, client: COMPServerProtocol): - """remove a disconnected client from the list of logged in clients - - Args: - client (COMPServerProtocol): the client that disconnected - """ - try: - self.active_clients.remove(client) # try to remove disconnected client - except ValueError: - pass - # print("a client disconnected.") - # print("currently there are " + str(len(self.active_clients)) - # + " active clients:") diff --git a/teamprojekt_competition_server/shared/commands.py b/teamprojekt_competition_server/shared/commands.py index de9b481a..d338efea 100644 --- a/teamprojekt_competition_server/shared/commands.py +++ b/teamprojekt_competition_server/shared/commands.py @@ -5,45 +5,7 @@ from twisted.protocols.amp import Integer, String, Boolean, Command -class ServerCommand(Command): - """Interface for commands send from the server""" - - pass - - -class ClientCommand(Command): - """Interface for commands send from the client""" - - pass - - -class AuthFailed(Exception): - """Exception raised when authentication fails. - - This exception is raised in cases where the authentication process encounters - an issue, such as an invalid token. - - Attributes: - None - """ - - pass - - -class InvalidVersion(Exception): - """Exception raised for an invalid version during authentication. - - This exception is raised when the version provided during the authentication - process is not compatible with the current version of the server. - - Attributes: - None - """ - - pass - - -class AuthClient(ClientCommand): +class Auth(Command): """Command for authenticating the client with the server. Arguments: @@ -53,23 +15,17 @@ class AuthClient(ClientCommand): Response: uuid (Integer): UUID assigned by the server to the client """ - - arguments = [(b"token", String()), (b"version", Integer())] - response = [(b"uuid", Integer())] - fatalErrors = { - AuthFailed: b"INVALID_TOKEN", - InvalidVersion: b"INCOMPATIBLE_VERSION", - } + response = [(b"token", String()),(b"version", Integer())] -class StartGame(ServerCommand): +class StartGame(Command): """Command to notify the client that the game starts""" arguments = [(b"game_id", Integer())] response = [(b"ready", Boolean())] -class EndGame(ServerCommand): +class EndGame(Command): """Command to notify the client that the game has ended""" arguments = [ @@ -79,11 +35,11 @@ class EndGame(ServerCommand): response = [(b"ready", Boolean())] -class Step(ServerCommand): +class Step(Command): """Command for requesting the next step from the agent""" arguments = [ - (b"env", Integer()) + (b"obv", Integer()) ] # Integer acts as a dummy type, we might want to create a custom data-type here! response = [ (b"action", Integer()) diff --git a/teamprojekt_competition_server/shared/twisted_asyncio.py b/teamprojekt_competition_server/shared/twisted_asyncio.py new file mode 100644 index 00000000..431e125e --- /dev/null +++ b/teamprojekt_competition_server/shared/twisted_asyncio.py @@ -0,0 +1,10 @@ + +import functools +from twisted.internet import defer + +def twisted_async(f): + @functools.wraps(f) + def wrapper(*args, **kwargs): + result = f(*args, **kwargs) + return defer.ensureDeferred(result) + return wrapper \ No newline at end of file From 907d466ac5cf0975a8f074f2cc1e878095840f30 Mon Sep 17 00:00:00 2001 From: Paul Masan Date: Sun, 10 Dec 2023 22:18:45 +0100 Subject: [PATCH 3/6] switched from the async idea to a callback version... again Co-authored-by: Carina Straub --- .../client/client.py | 5 +- .../client/client_protocol.py | 4 +- .../server/factory.py | 17 +++-- .../server/game_manager.py | 34 ++++++--- .../server/interfaces.py | 65 +++++++++++------ teamprojekt_competition_server/server/main.py | 41 ++++++----- .../server/player.py | 36 +++++----- .../server/protocol.py | 71 +++++++++++++------ .../server/server.py | 13 ++-- .../shared/commands.py | 3 +- .../shared/twisted_asyncio.py | 10 --- 11 files changed, 189 insertions(+), 110 deletions(-) delete mode 100644 teamprojekt_competition_server/shared/twisted_asyncio.py diff --git a/teamprojekt_competition_server/client/client.py b/teamprojekt_competition_server/client/client.py index 266fde8f..ebe7e24b 100644 --- a/teamprojekt_competition_server/client/client.py +++ b/teamprojekt_competition_server/client/client.py @@ -7,7 +7,6 @@ from .client_protocol import COMPClientProtocol - class COMPClient(CommandLocator): """client that manages the connection over the protocoll with the server""" @@ -23,5 +22,7 @@ def connect_client(self, token: str): token (str): token to verify the client """ destination = TCP4ClientEndpoint(reactor, "127.0.0.1", 1234) - auth = connectProtocol(destination, COMPClientProtocol(agent=self.agent, token=token)) + auth = connectProtocol( + destination, COMPClientProtocol(agent=self.agent, token=token) + ) reactor.run() # type: ignore[attr-defined] diff --git a/teamprojekt_competition_server/client/client_protocol.py b/teamprojekt_competition_server/client/client_protocol.py index 16b4eb9d..3f68f765 100644 --- a/teamprojekt_competition_server/client/client_protocol.py +++ b/teamprojekt_competition_server/client/client_protocol.py @@ -67,7 +67,7 @@ def auth(self): Returns: {"token": String}: the clients auth token """ - return {"token": self.token, "version": 1} + return {"token": str.encode(self.token), "version": 1} Auth.responder(auth) @@ -78,5 +78,5 @@ class COMPClientFactory(ClientFactory): def buildProtocol(self, addr: IAddress) -> Protocol | None: """builds the COMP protocol""" return COMPClientProtocol( - agent=None + agent=None, token="ThisIsSomeCoolDummyToken" ) # TODO: there is something fucked up with the agent... diff --git a/teamprojekt_competition_server/server/factory.py b/teamprojekt_competition_server/server/factory.py index c1a54f27..bfc7403e 100644 --- a/teamprojekt_competition_server/server/factory.py +++ b/teamprojekt_competition_server/server/factory.py @@ -1,6 +1,5 @@ """ Hello World """ -import asyncio import logging as log from twisted.internet.interfaces import IAddress @@ -8,17 +7,17 @@ from .protocol import COMPServerProtocol from .player import COMPPlayer -from .game_manager import GameManager +from .game_manager import game_manager + class COMPServerFactory(ServerFactory): """factory for COMP servers""" - manager : GameManager = GameManager() #TODO: this is hacked, move it into the COMPServer class... - def buildProtocol(self, addr: IAddress) -> Protocol | None: """builds the protocoll""" - protocol : COMPServerProtocol = COMPServerProtocol() - - self.manager.add_player(COMPPlayer(protocol)) - - return protocol \ No newline at end of file + protocol: COMPServerProtocol = COMPServerProtocol() + + new_player = COMPPlayer(protocol) + game_manager.add_player(new_player) + + return protocol diff --git a/teamprojekt_competition_server/server/game_manager.py b/teamprojekt_competition_server/server/game_manager.py index cfa2a164..d6ff93a0 100644 --- a/teamprojekt_competition_server/server/game_manager.py +++ b/teamprojekt_competition_server/server/game_manager.py @@ -1,19 +1,37 @@ """Structure which handles multiple games""" import logging as log +from typing import Type from .interfaces import IPlayer, IGame + class GameManager: - - players : list[IPlayer] = [] - games : list[IGame] = [] - + players: list[IPlayer] = [] + queue: list[int] = [] + games: list[IGame] = [] + GameClass: Type[IGame] + def __init__(self) -> None: pass - + def add_player(self, player: IPlayer) -> None: - log.debug("Connected Player") - self.players.append(player) - \ No newline at end of file + log.debug("Player added") + + player.id = len(self.players)-1 + + def add_player_to_queue(self, player_id: int): + if len(self.queue) > 0: + log.debug("matched two players") + player1 = self.players[self.queue.pop()] + player2 = self.players[player_id] + new_game = self.GameClass(players=[player1, player2]) + self.games.append(new_game) + new_game.start() + else: + self.queue.append(player_id) + log.debug("added player to queue") + + +game_manager = GameManager() \ No newline at end of file diff --git a/teamprojekt_competition_server/server/interfaces.py b/teamprojekt_competition_server/server/interfaces.py index 2a9bfab1..56c6a537 100644 --- a/teamprojekt_competition_server/server/interfaces.py +++ b/teamprojekt_competition_server/server/interfaces.py @@ -4,57 +4,82 @@ import abc + +class IAction: + pass + + class IPlayer(abc.ABC): + id : int = -1 + @abc.abstractmethod - async def authenticate(self): + def authenticate(self, result_callback): ... - + @abc.abstractmethod - async def notify_start(self): + def notify_start(self): ... @abc.abstractmethod - async def get_action(self, obv): + def get_action(self, obv, result_callback) -> IAction: ... @abc.abstractmethod - async def notify_end(self): + def notify_end(self): ... class IGame(abc.ABC): """game interface""" - - players : list[IPlayer] = [] - - def __init__(self, players : list[IPlayer]) -> None: + + players: list[IPlayer] = [] + current_actions: list[IAction] = [] + result_received: int = 0 + + def __init__(self, players: list[IPlayer]) -> None: self.players = players - async def start(self): + def start(self): for p in self.players: - if not await p.notify_start(): - log.error("player not ready...") + p.notify_start() - async def end(self, reason="unknown"): + def end(self, reason="unknown"): for p in self.players: - await p.notify_end(reason) + p.notify_end(reason) @abc.abstractmethod - async def _game_cycle(self): + def _game_cycle(self): """sends a step request to both players and changes the enviroment accordingly.""" - ... - + + def _request_actions(self): + self.result_received = 0 + + for i, p in enumerate(self.players): + + def __res(v: IAction): + self.current_actions[i] = v + self.result_received += 1 + if self.result_received == len(self.players): + self._game_cycle() + + if self._is_finished(): + self.end() + else: + self._request_actions() + + p.get_action(callback=__res) + @abc.abstractmethod - async def _validate_action(self, action) -> bool: + def _validate_action(self, action) -> bool: """check weather an action is valid""" ... @abc.abstractmethod - async def _is_finished(self) -> bool: + def _is_finished(self) -> bool: """detirmens if the game has ended Returns: bool: returns true if game has ended """ - ... \ No newline at end of file + ... diff --git a/teamprojekt_competition_server/server/main.py b/teamprojekt_competition_server/server/main.py index b9f033b8..8c8203dc 100644 --- a/teamprojekt_competition_server/server/main.py +++ b/teamprojekt_competition_server/server/main.py @@ -3,27 +3,36 @@ from .server import COMPServer from .interfaces import IGame, IPlayer +from .game_manager import game_manager + import logging -logging.basicConfig(level = logging.DEBUG) + +logging.basicConfig(level=logging.DEBUG) # run with "python -m teamprojekt_competition_server.server.main" -if __name__ == "__main__": - class ExampleGame(IGame): - def __init__(self, players: list[IPlayer]) -> None: - super().__init__(players=players) - self.env = 0 - - async def _game_cycle(self): - for p in self.players: - self.env += await p.get_action() - - async def _validate_action(self, action): - return isinstance(action, int) - - async def _is_finished(self) -> bool: - return self.env > 10 +class ExampleGame(IGame): + def __init__(self, players: list[IPlayer]) -> None: + super().__init__(players=players) + self.env = 0 + + def _game_cycle(self): + for p in self.players: + self.env += sum(self.current_actions) + + def _validate_action(self, action): + return isinstance(action, int) + def _is_finished(self) -> bool: + return self.env > 10 + + +def main(): + game_manager.GameClass = ExampleGame server = COMPServer() server.start() + + +if __name__ == "__main__": + main() diff --git a/teamprojekt_competition_server/server/player.py b/teamprojekt_competition_server/server/player.py index e5a46e4a..d0b43202 100644 --- a/teamprojekt_competition_server/server/player.py +++ b/teamprojekt_competition_server/server/player.py @@ -3,23 +3,27 @@ from .protocol import COMPServerProtocol from .interfaces import IPlayer -from ..shared.twisted_asyncio import twisted_async +from .game_manager import game_manager + class COMPPlayer(IPlayer): - connection : COMPServerProtocol - - def __init__(self, connection : COMPServerProtocol) -> None: - self.connection = connection - - async def authenticate(self): - return await self.connection.get_token() - - async def notify_start(self): + connection: COMPServerProtocol + + def __init__(self, connection: COMPServerProtocol) -> None: + self.connection = connection + + def connection_made(): + self.authenticate(result_callback=lambda x: game_manager.add_player_to_queue(self.id)) + self.connection.addConnectionMadeCallback(connection_made) + + def authenticate(self, result_callback): + return self.connection.get_token(result_callback) + + def notify_start(self): self.connection.notify_start() - - async def get_action(self, obv): - return await self.connection.get_step(obv) - - async def notify_end(self): + + def get_action(self, obv, result_callback): + return self.connection.get_step(obv, result_callback) + + def notify_end(self): return self.connection.notify_end() - \ No newline at end of file diff --git a/teamprojekt_competition_server/server/protocol.py b/teamprojekt_competition_server/server/protocol.py index 08f3d1d1..b093cbdf 100644 --- a/teamprojekt_competition_server/server/protocol.py +++ b/teamprojekt_competition_server/server/protocol.py @@ -1,51 +1,80 @@ """class for server protocol""" -import asyncio + import logging as log +from typing import Callable from twisted.protocols import amp +from twisted.internet.interfaces import IAddress + from ..shared.commands import Auth, StartGame, EndGame, Step -from ..shared.twisted_asyncio import twisted_async class COMPServerProtocol(amp.AMP): """amp protocol for a COMP server""" + connection_made_callbacks : list[Callable[[None], None]] = [] + connection_lost_callbacks : list[Callable[[None], None]] = [] + def __init__(self, boxReceiver=None, locator=None): super().__init__(boxReceiver, locator) - def connectionLost(self, reason) -> None: - """is called when a client disconnects""" + def addConnectionMadeCallback(self, callback): + self.connection_made_callbacks.append(callback) + + def addConnectionLostCallback(self, callback): + self.connection_lost_callbacks.append(callback) - # TODO handle this! + def connectionMade(self) -> None: + addr: IAddress = self.transport.getPeer() # type: ignore + log.debug( + f"Connected to client with IP address: {addr.host}, Port: {addr.port} via {addr.type}" + ) + + #broadcast to callbacks + for c in self.connection_made_callbacks: + c() + + return super().connectionMade() + + def connectionLost(self, reason): + + #broadcast to callbacks + for c in self.connection_lost_callbacks: + c() return super().connectionLost(reason) - def connectionMade(self): - log.debug("Client connected") - test = self.get_token() - print(test) - return super().connectionMade() + def get_token(self, return_callback: Callable[[str], None]) -> None: + """get token from client to authenticate - async def get_token(self) -> str: - token = await self.callRemote(Auth) - log.debug(token) - return token + Args: + game (Game): game that starts + """ + return self.callRemote(Auth).addCallback( + callback=lambda res: return_callback(res["token"]) + ) - async def notify_start(self) -> bool: + def notify_start(self) -> None: """starts the game Args: game (Game): game that starts """ - return await self.callRemote(StartGame, game_id=222).asFuture(loop=asyncio.get_event_loop()) + return self.callRemote(StartGame, game_id=222) - async def get_step(self, obv): + def get_step(self, obv, return_callback: Callable[[list], None]) -> None: """perfroms step requested by player""" - return await self.callRemote(Step, obv=int(obv)).asFuture(loop=asyncio.get_event_loop()) + return self.callRemote(Step, obv=int(obv)).addCallback( + callback=lambda res: return_callback(res["action"]) + ) - async def notify_end(self, result, stats) -> bool: + def notify_end( + self, result, stats, return_callback: Callable[[bool], None] + ) -> None: """ends the game""" - - return await self.callRemote(EndGame, result=True, stats=4).asFuture(loop=asyncio.get_event_loop()) + + return self.callRemote(EndGame, result=True, stats=4).addCallback( + callback=lambda res: return_callback(res["ready"]) + ) diff --git a/teamprojekt_competition_server/server/server.py b/teamprojekt_competition_server/server/server.py index d2f101c0..72a71ec7 100644 --- a/teamprojekt_competition_server/server/server.py +++ b/teamprojekt_competition_server/server/server.py @@ -1,5 +1,5 @@ """class for server""" -from typing import Type + import logging as log from twisted.internet import reactor @@ -7,6 +7,7 @@ from .factory import COMPServerFactory + class COMPServer: """class for server instance""" @@ -15,12 +16,14 @@ def __init__(self) -> None: def start(self): """set up server at localhost:1234.""" - self.endpoint = TCP4ServerEndpoint(reactor, 1234) #TODO the port should be in some .env file or so + self.endpoint = TCP4ServerEndpoint( + reactor, 1234 + ) # TODO the port should be in some .env file or so self.endpoint.listen(self.factory) - log.debug("Server Started") #TODO some more info here - reactor.run() # type: ignore[attr-defined] + log.debug("Server Started") # TODO some more info here + reactor.run() # type: ignore[attr-defined] def stop(self): """terminates server.""" log.debug("Server Stopped") - reactor.stop() \ No newline at end of file + reactor.stop() diff --git a/teamprojekt_competition_server/shared/commands.py b/teamprojekt_competition_server/shared/commands.py index d338efea..affb1867 100644 --- a/teamprojekt_competition_server/shared/commands.py +++ b/teamprojekt_competition_server/shared/commands.py @@ -15,7 +15,8 @@ class Auth(Command): Response: uuid (Integer): UUID assigned by the server to the client """ - response = [(b"token", String()),(b"version", Integer())] + + response = [(b"token", String()), (b"version", Integer())] class StartGame(Command): diff --git a/teamprojekt_competition_server/shared/twisted_asyncio.py b/teamprojekt_competition_server/shared/twisted_asyncio.py deleted file mode 100644 index 431e125e..00000000 --- a/teamprojekt_competition_server/shared/twisted_asyncio.py +++ /dev/null @@ -1,10 +0,0 @@ - -import functools -from twisted.internet import defer - -def twisted_async(f): - @functools.wraps(f) - def wrapper(*args, **kwargs): - result = f(*args, **kwargs) - return defer.ensureDeferred(result) - return wrapper \ No newline at end of file From c952e883eced40c9fbbc15dede41262b6611f4dd Mon Sep 17 00:00:00 2001 From: Carina Straub Date: Mon, 11 Dec 2023 18:56:21 +0100 Subject: [PATCH 4/6] fixed errors and implemented example game functionallity Co-authored-by: Paco Co-authored-by: Paul Masan --- .../client/agent.py | 4 +- .../client/client_protocol.py | 10 ++-- teamprojekt_competition_server/client/main.py | 6 +- .../server/factory.py | 1 + .../server/game_manager.py | 19 +++---- .../server/interfaces.py | 55 +++++++++++++------ teamprojekt_competition_server/server/main.py | 15 ++++- .../server/player.py | 14 +++-- .../server/protocol.py | 8 +-- 9 files changed, 79 insertions(+), 53 deletions(-) diff --git a/teamprojekt_competition_server/client/agent.py b/teamprojekt_competition_server/client/agent.py index fd6c1567..320c9b23 100644 --- a/teamprojekt_competition_server/client/agent.py +++ b/teamprojekt_competition_server/client/agent.py @@ -10,12 +10,12 @@ def __init__(self) -> None: self.client = COMPClient(agent=self) pass - def step(self, env): + def step(self, obv): """this is an abstract method for one step that the agent makes and should be overwritten Args: - env (_type_): current enviroment + obv (_type_): current enviroment Raises: NotImplementedError: this method should be overwritten diff --git a/teamprojekt_competition_server/client/client_protocol.py b/teamprojekt_competition_server/client/client_protocol.py index 3f68f765..d2a81be2 100644 --- a/teamprojekt_competition_server/client/client_protocol.py +++ b/teamprojekt_competition_server/client/client_protocol.py @@ -26,7 +26,7 @@ def start_game(self, game_id: int): Returns: {"ready": boolean}: true if the client is ready to start the game """ - print(f"--- Started Game --- \nGame ID: {game_id}") + print(f"------ Started Game [Game ID: {game_id}] ------") return {"ready": True} # dummy ready StartGame.responder(start_game) @@ -41,12 +41,12 @@ def end_game(self, result, stats): Returns: {"ready": boolean}: true if the client is ready to start a new game """ - print(f"--- Ended Game --- \nGame ID: {result} | Stats: {stats}") + print(f"------ Ended Game [Game ID: {result} | Stats: {stats}] ------") return {"ready": True} # dummy ready EndGame.responder(end_game) - def step(self, env): + def step(self, obv): """is called when the server wants the client to make a step Args: @@ -55,8 +55,8 @@ def step(self, env): Returns: {"action": int}: action that should be executed """ - action = self.agent.step(env=int(env)) # dummy action - print(f"--- Next Step --- \nEnviroment: {env} | Action: {action}") + action = self.agent.step(obv=int(obv)) # dummy action + print(f"Send action: {action}") return {"action": action} Step.responder(step) diff --git a/teamprojekt_competition_server/client/main.py b/teamprojekt_competition_server/client/main.py index 50fc0a67..f7e08d49 100644 --- a/teamprojekt_competition_server/client/main.py +++ b/teamprojekt_competition_server/client/main.py @@ -8,16 +8,16 @@ class MyAgent(COMPAgent): """Dummy Agent for testing""" - def step(self, env): + def step(self, obv): """dummy step function Args: - env (int): enviroment + obv (int): enviroment Returns: int: action """ - return int(input(f"Enviroment: {env} \nEnter a move: ")) + return int(input(f"Observation: {obv} | Enter a move: ")) agent = MyAgent() agent.run("HelloWorldToken") diff --git a/teamprojekt_competition_server/server/factory.py b/teamprojekt_competition_server/server/factory.py index bfc7403e..22c348a8 100644 --- a/teamprojekt_competition_server/server/factory.py +++ b/teamprojekt_competition_server/server/factory.py @@ -21,3 +21,4 @@ def buildProtocol(self, addr: IAddress) -> Protocol | None: game_manager.add_player(new_player) return protocol + \ No newline at end of file diff --git a/teamprojekt_competition_server/server/game_manager.py b/teamprojekt_competition_server/server/game_manager.py index d6ff93a0..592e83bb 100644 --- a/teamprojekt_competition_server/server/game_manager.py +++ b/teamprojekt_competition_server/server/game_manager.py @@ -7,31 +7,28 @@ class GameManager: - players: list[IPlayer] = [] - queue: list[int] = [] - games: list[IGame] = [] - GameClass: Type[IGame] - + def __init__(self) -> None: - pass + self.players: list[IPlayer] = [] + self.queue: list[int] = [] + self.games: list[IGame] = [] + self.GameClass: Type[IGame] def add_player(self, player: IPlayer) -> None: self.players.append(player) - log.debug("Player added") player.id = len(self.players)-1 def add_player_to_queue(self, player_id: int): if len(self.queue) > 0: - log.debug("matched two players") - player1 = self.players[self.queue.pop()] + player1 = self.players[self.queue.pop(0)] player2 = self.players[player_id] + log.debug(f"matched two players: player1 {player1.id}, player2 {player2.id}") new_game = self.GameClass(players=[player1, player2]) self.games.append(new_game) new_game.start() else: self.queue.append(player_id) - log.debug("added player to queue") - + log.debug(f"added player to queue. ID: {player_id}") game_manager = GameManager() \ No newline at end of file diff --git a/teamprojekt_competition_server/server/interfaces.py b/teamprojekt_competition_server/server/interfaces.py index 56c6a537..1818aedc 100644 --- a/teamprojekt_competition_server/server/interfaces.py +++ b/teamprojekt_competition_server/server/interfaces.py @@ -11,7 +11,8 @@ class IAction: class IPlayer(abc.ABC): - id : int = -1 + def __init__(self) -> None: + self.id : int = -1 @abc.abstractmethod def authenticate(self, result_callback): @@ -26,49 +27,49 @@ def get_action(self, obv, result_callback) -> IAction: ... @abc.abstractmethod - def notify_end(self): + def notify_end(self, result, stats): ... class IGame(abc.ABC): """game interface""" - players: list[IPlayer] = [] - current_actions: list[IAction] = [] - result_received: int = 0 - def __init__(self, players: list[IPlayer]) -> None: - self.players = players + self.players: list[IPlayer] = players + self.current_actions: list[IAction] = [None for _ in players] + self.result_received: int = 0 def start(self): for p in self.players: p.notify_start() + self._game_cycle() def end(self, reason="unknown"): - for p in self.players: - p.notify_end(reason) + for i, p in enumerate(self.players): + p.notify_end(result=self._player_won(i), stats=self._player_stats(i)) @abc.abstractmethod - def _game_cycle(self): - """sends a step request to both players and changes the enviroment accordingly.""" + def _update_enviroment(self): + """works with the current_actions list to change the enviroment accordingly.""" - def _request_actions(self): + def _game_cycle(self): self.result_received = 0 for i, p in enumerate(self.players): - def __res(v: IAction): - self.current_actions[i] = v + def __res(v: IAction, index=i): + log.debug(f"got action {v} from player {index}") + self.current_actions[index] = v self.result_received += 1 if self.result_received == len(self.players): - self._game_cycle() + self._update_enviroment() if self._is_finished(): self.end() else: - self._request_actions() - - p.get_action(callback=__res) + self._game_cycle() + + p.get_action(obv=self._observation(), result_callback=__res) @abc.abstractmethod def _validate_action(self, action) -> bool: @@ -83,3 +84,21 @@ def _is_finished(self) -> bool: bool: returns true if game has ended """ ... + @abc.abstractmethod + def _observation(self): + """retutns the observation for the players""" + ... + + @abc.abstractmethod + def _player_won(self, index) -> bool: + """check wether the player has won + + Returns: + bool: returns true if player has won + """ + ... + + @abc.abstractmethod + def _player_stats(self, index) -> int: + """retutns the player stats""" + ... diff --git a/teamprojekt_competition_server/server/main.py b/teamprojekt_competition_server/server/main.py index 8c8203dc..d065fcc0 100644 --- a/teamprojekt_competition_server/server/main.py +++ b/teamprojekt_competition_server/server/main.py @@ -17,15 +17,24 @@ def __init__(self, players: list[IPlayer]) -> None: super().__init__(players=players) self.env = 0 - def _game_cycle(self): - for p in self.players: - self.env += sum(self.current_actions) + def _update_enviroment(self): + self.env += sum(self.current_actions) def _validate_action(self, action): return isinstance(action, int) def _is_finished(self) -> bool: return self.env > 10 + + def _observation(self): + return self.env + + def _player_stats(self, index) -> int: + return 0 + + def _player_won(self, index) -> bool: + if index==0: return True + return False def main(): diff --git a/teamprojekt_competition_server/server/player.py b/teamprojekt_competition_server/server/player.py index d0b43202..83ba775c 100644 --- a/teamprojekt_competition_server/server/player.py +++ b/teamprojekt_competition_server/server/player.py @@ -1,4 +1,5 @@ """Player""" +import logging as log from .protocol import COMPServerProtocol from .interfaces import IPlayer @@ -7,14 +8,13 @@ class COMPPlayer(IPlayer): - connection: COMPServerProtocol def __init__(self, connection: COMPServerProtocol) -> None: - self.connection = connection + self.connection: COMPServerProtocol = connection - def connection_made(): + def connected(): self.authenticate(result_callback=lambda x: game_manager.add_player_to_queue(self.id)) - self.connection.addConnectionMadeCallback(connection_made) + self.connection.addConnectionMadeCallback(connected) def authenticate(self, result_callback): return self.connection.get_token(result_callback) @@ -25,5 +25,7 @@ def notify_start(self): def get_action(self, obv, result_callback): return self.connection.get_step(obv, result_callback) - def notify_end(self): - return self.connection.notify_end() + def notify_end(self, result, stats): + def callback(ready: bool): + if ready: game_manager.add_player_to_queue(self.id) + return self.connection.notify_end(result=result, stats=stats, return_callback=callback) diff --git a/teamprojekt_competition_server/server/protocol.py b/teamprojekt_competition_server/server/protocol.py index b093cbdf..4faad3f1 100644 --- a/teamprojekt_competition_server/server/protocol.py +++ b/teamprojekt_competition_server/server/protocol.py @@ -13,11 +13,10 @@ class COMPServerProtocol(amp.AMP): """amp protocol for a COMP server""" - connection_made_callbacks : list[Callable[[None], None]] = [] - connection_lost_callbacks : list[Callable[[None], None]] = [] - def __init__(self, boxReceiver=None, locator=None): super().__init__(boxReceiver, locator) + self.connection_made_callbacks : list[Callable[[None], None]] = [] + self.connection_lost_callbacks : list[Callable[[None], None]] = [] def addConnectionMadeCallback(self, callback): self.connection_made_callbacks.append(callback) @@ -30,7 +29,6 @@ def connectionMade(self) -> None: log.debug( f"Connected to client with IP address: {addr.host}, Port: {addr.port} via {addr.type}" ) - #broadcast to callbacks for c in self.connection_made_callbacks: c() @@ -75,6 +73,6 @@ def notify_end( ) -> None: """ends the game""" - return self.callRemote(EndGame, result=True, stats=4).addCallback( + return self.callRemote(EndGame, result=result, stats=stats).addCallback( callback=lambda res: return_callback(res["ready"]) ) From 7c01c48eee039e86feed591c84b6f173bf0485d2 Mon Sep 17 00:00:00 2001 From: Carina Straub Date: Mon, 11 Dec 2023 20:35:44 +0100 Subject: [PATCH 5/6] added docstrings --- .../server/factory.py | 2 -- .../server/game_manager.py | 15 ++++++-- .../server/interfaces.py | 35 ++++++++++++++++++- teamprojekt_competition_server/server/main.py | 3 ++ .../server/player.py | 5 +-- .../server/protocol.py | 14 ++++++-- 6 files changed, 65 insertions(+), 9 deletions(-) diff --git a/teamprojekt_competition_server/server/factory.py b/teamprojekt_competition_server/server/factory.py index 22c348a8..bbed2096 100644 --- a/teamprojekt_competition_server/server/factory.py +++ b/teamprojekt_competition_server/server/factory.py @@ -1,7 +1,5 @@ """ Hello World """ -import logging as log - from twisted.internet.interfaces import IAddress from twisted.internet.protocol import Protocol, ServerFactory diff --git a/teamprojekt_competition_server/server/game_manager.py b/teamprojekt_competition_server/server/game_manager.py index 592e83bb..34827c90 100644 --- a/teamprojekt_competition_server/server/game_manager.py +++ b/teamprojekt_competition_server/server/game_manager.py @@ -7,7 +7,8 @@ class GameManager: - + """manager for the games""" + def __init__(self) -> None: self.players: list[IPlayer] = [] self.queue: list[int] = [] @@ -15,15 +16,25 @@ def __init__(self) -> None: self.GameClass: Type[IGame] def add_player(self, player: IPlayer) -> None: + """adds a player to the player list and gives it its index as id + + Args: + player (IPlayer): player to add + """ self.players.append(player) player.id = len(self.players)-1 def add_player_to_queue(self, player_id: int): + """adds a player to the queue + + Args: + player_id (int): id of the player + """ if len(self.queue) > 0: player1 = self.players[self.queue.pop(0)] player2 = self.players[player_id] - log.debug(f"matched two players: player1 {player1.id}, player2 {player2.id}") + log.debug(f"matched two players: player {player1.id}, player {player2.id}") new_game = self.GameClass(players=[player1, player2]) self.games.append(new_game) new_game.start() diff --git a/teamprojekt_competition_server/server/interfaces.py b/teamprojekt_competition_server/server/interfaces.py index 1818aedc..aaf859a6 100644 --- a/teamprojekt_competition_server/server/interfaces.py +++ b/teamprojekt_competition_server/server/interfaces.py @@ -6,28 +6,49 @@ class IAction: + """Interface for an action + """ pass class IPlayer(abc.ABC): + """Interface for a player""" def __init__(self) -> None: self.id : int = -1 @abc.abstractmethod def authenticate(self, result_callback): + """authenticates player + + Args: + result_callback (Callable): callback + """ ... @abc.abstractmethod def notify_start(self): + """notifies player that the game has started + """ ... @abc.abstractmethod def get_action(self, obv, result_callback) -> IAction: + """gets an action from the player + + Args: + obv (Any): obserervation + result_callback (Callable): callback + + Returns: + IAction: action + """ ... @abc.abstractmethod def notify_end(self, result, stats): + """notifies player that the game has ended + """ ... @@ -36,23 +57,35 @@ class IGame(abc.ABC): def __init__(self, players: list[IPlayer]) -> None: self.players: list[IPlayer] = players - self.current_actions: list[IAction] = [None for _ in players] + self.current_actions: list = [None for _ in players] self.result_received: int = 0 def start(self): + """ + notifies all players that the game has started + and starts the game cycle + """ for p in self.players: p.notify_start() self._game_cycle() def end(self, reason="unknown"): + """notifies all players that the game has ended + + Args: + reason (str, optional): reason why the game has ended. Defaults to "unknown". + """ for i, p in enumerate(self.players): p.notify_end(result=self._player_won(i), stats=self._player_stats(i)) @abc.abstractmethod def _update_enviroment(self): """works with the current_actions list to change the enviroment accordingly.""" + ... def _game_cycle(self): + """collectes all actions and puts them in current_actions list + """ self.result_received = 0 for i, p in enumerate(self.players): diff --git a/teamprojekt_competition_server/server/main.py b/teamprojekt_competition_server/server/main.py index d065fcc0..4a65007a 100644 --- a/teamprojekt_competition_server/server/main.py +++ b/teamprojekt_competition_server/server/main.py @@ -13,6 +13,8 @@ class ExampleGame(IGame): + """example for a game""" + def __init__(self, players: list[IPlayer]) -> None: super().__init__(players=players) self.env = 0 @@ -38,6 +40,7 @@ def _player_won(self, index) -> bool: def main(): + """main function for testing""" game_manager.GameClass = ExampleGame server = COMPServer() server.start() diff --git a/teamprojekt_competition_server/server/player.py b/teamprojekt_competition_server/server/player.py index 83ba775c..f9e4a091 100644 --- a/teamprojekt_competition_server/server/player.py +++ b/teamprojekt_competition_server/server/player.py @@ -1,5 +1,4 @@ """Player""" -import logging as log from .protocol import COMPServerProtocol from .interfaces import IPlayer @@ -8,6 +7,7 @@ class COMPPlayer(IPlayer): + """player of the game""" def __init__(self, connection: COMPServerProtocol) -> None: self.connection: COMPServerProtocol = connection @@ -27,5 +27,6 @@ def get_action(self, obv, result_callback): def notify_end(self, result, stats): def callback(ready: bool): - if ready: game_manager.add_player_to_queue(self.id) + if ready: + game_manager.add_player_to_queue(self.id) return self.connection.notify_end(result=result, stats=stats, return_callback=callback) diff --git a/teamprojekt_competition_server/server/protocol.py b/teamprojekt_competition_server/server/protocol.py index 4faad3f1..95198530 100644 --- a/teamprojekt_competition_server/server/protocol.py +++ b/teamprojekt_competition_server/server/protocol.py @@ -15,13 +15,23 @@ class COMPServerProtocol(amp.AMP): def __init__(self, boxReceiver=None, locator=None): super().__init__(boxReceiver, locator) - self.connection_made_callbacks : list[Callable[[None], None]] = [] - self.connection_lost_callbacks : list[Callable[[None], None]] = [] + self.connection_made_callbacks : list[Callable[[], None]] = [] + self.connection_lost_callbacks : list[Callable[[], None]] = [] def addConnectionMadeCallback(self, callback): + """adds callback that is executed, when the connection is made + + Args: + callback (function): callback to execute, when the connection is made + """ self.connection_made_callbacks.append(callback) def addConnectionLostCallback(self, callback): + """adds callback that is executed, when the connection is lost + + Args: + callback (function): callback to execute, when the connection is lost + """ self.connection_lost_callbacks.append(callback) def connectionMade(self) -> None: From 5299612d47c9bb548be10e0dbe1d59dc182f353a Mon Sep 17 00:00:00 2001 From: yunus Date: Mon, 11 Dec 2023 22:09:50 +0100 Subject: [PATCH 6/6] Added DocString, formatted with black --- .../client/client.py | 4 +-- .../log/client/client.log | 12 +++++++ .../log/client/protocol.log | 4 +++ .../log/server/gameManager.log | 12 +++++++ .../log/server/protocol.log | 4 +++ .../log/server/server.log | 6 ++++ .../server/factory.py | 1 - .../server/game_manager.py | 9 ++--- .../server/interfaces.py | 34 +++++++++---------- teamprojekt_competition_server/server/main.py | 11 +++--- .../server/player.py | 31 ++++++++++++++--- .../server/protocol.py | 22 ++++++------ 12 files changed, 104 insertions(+), 46 deletions(-) create mode 100644 teamprojekt_competition_server/log/client/client.log create mode 100644 teamprojekt_competition_server/log/client/protocol.log create mode 100644 teamprojekt_competition_server/log/server/gameManager.log create mode 100644 teamprojekt_competition_server/log/server/protocol.log create mode 100644 teamprojekt_competition_server/log/server/server.log diff --git a/teamprojekt_competition_server/client/client.py b/teamprojekt_competition_server/client/client.py index ebe7e24b..c360ff38 100644 --- a/teamprojekt_competition_server/client/client.py +++ b/teamprojekt_competition_server/client/client.py @@ -22,7 +22,5 @@ def connect_client(self, token: str): token (str): token to verify the client """ destination = TCP4ClientEndpoint(reactor, "127.0.0.1", 1234) - auth = connectProtocol( - destination, COMPClientProtocol(agent=self.agent, token=token) - ) + connectProtocol(destination, COMPClientProtocol(agent=self.agent, token=token)) reactor.run() # type: ignore[attr-defined] diff --git a/teamprojekt_competition_server/log/client/client.log b/teamprojekt_competition_server/log/client/client.log new file mode 100644 index 00000000..94e79174 --- /dev/null +++ b/teamprojekt_competition_server/log/client/client.log @@ -0,0 +1,12 @@ +2023-12-11T20:25:22+0100 [teamprojekt_competition_server.client.client.COMPClient#info] Inizialized agent: 140432192338960 +2023-12-11T20:25:22+0100 [teamprojekt_competition_server.client.client.COMPClient#info] version: 1 +2023-12-11T20:25:22+0100 [teamprojekt_competition_server.client.client.COMPClient#info] Connected with token HelloWorldToken +2023-12-11T20:25:23+0100 [teamprojekt_competition_server.client.client.COMPClient#info] Inizialized agent: 140183568677904 +2023-12-11T20:25:23+0100 [teamprojekt_competition_server.client.client.COMPClient#info] version: 1 +2023-12-11T20:25:23+0100 [teamprojekt_competition_server.client.client.COMPClient#info] Connected with token HelloWorldToken +2023-12-11T20:33:44+0100 [teamprojekt_competition_server.client.client.COMPClient#info] Inizialized agent: 140671271353360 +2023-12-11T20:33:44+0100 [teamprojekt_competition_server.client.client.COMPClient#info] version: 1 +2023-12-11T20:33:44+0100 [teamprojekt_competition_server.client.client.COMPClient#info] Connected with token HelloWorldToken +2023-12-11T20:33:46+0100 [teamprojekt_competition_server.client.client.COMPClient#info] Inizialized agent: 140397980761104 +2023-12-11T20:33:46+0100 [teamprojekt_competition_server.client.client.COMPClient#info] version: 1 +2023-12-11T20:33:46+0100 [teamprojekt_competition_server.client.client.COMPClient#info] Connected with token HelloWorldToken diff --git a/teamprojekt_competition_server/log/client/protocol.log b/teamprojekt_competition_server/log/client/protocol.log new file mode 100644 index 00000000..a093a715 --- /dev/null +++ b/teamprojekt_competition_server/log/client/protocol.log @@ -0,0 +1,4 @@ +2023-12-11T20:33:44+0100 [teamprojekt_competition_server.client.client_protocol.COMPClientProtocol#info] Initialized protocol: 140671236286992 with agent: 140671271353360, token: 140671262383408 +2023-12-11T20:33:46+0100 [teamprojekt_competition_server.client.client_protocol.COMPClientProtocol#info] Initialized protocol: 140397948106592 with agent: 140397980761104, token: 140397969693744 +2023-12-11T20:33:46+0100 [teamprojekt_competition_server.client.client_protocol.COMPClientProtocol#info] Receive StartGame command, game_id: 222 +2023-12-11T20:33:46+0100 [teamprojekt_competition_server.client.client_protocol.COMPClientProtocol#info] Receive StartGame command, game_id: 222 diff --git a/teamprojekt_competition_server/log/server/gameManager.log b/teamprojekt_competition_server/log/server/gameManager.log new file mode 100644 index 00000000..12894dee --- /dev/null +++ b/teamprojekt_competition_server/log/server/gameManager.log @@ -0,0 +1,12 @@ +2023-12-11T20:25:19+0100 [teamprojekt_competition_server.server.game_manager.GameManager#info] Initialized GameManager -> 140477668476768 +2023-12-11T20:25:22+0100 [teamprojekt_competition_server.server.game_manager.GameManager#info] Added player with id: 0 +2023-12-11T20:25:22+0100 [teamprojekt_competition_server.server.game_manager.GameManager#info] added player with id: 0 to queue +2023-12-11T20:25:23+0100 [teamprojekt_competition_server.server.game_manager.GameManager#info] Added player with id: 1 +2023-12-11T20:25:23+0100 [teamprojekt_competition_server.server.game_manager.GameManager#info] Matched two players: player1 0, player2 1 +2023-12-11T20:25:23+0100 [teamprojekt_competition_server.server.game_manager.GameManager#info] Started game: 140477668480752 +2023-12-11T20:33:24+0100 [teamprojekt_competition_server.server.game_manager.GameManager#info] Initialized GameManager -> 140512021235168 +2023-12-11T20:33:44+0100 [teamprojekt_competition_server.server.game_manager.GameManager#info] Added player with id: 0 +2023-12-11T20:33:44+0100 [teamprojekt_competition_server.server.game_manager.GameManager#info] added player with id: 0 to queue +2023-12-11T20:33:46+0100 [teamprojekt_competition_server.server.game_manager.GameManager#info] Added player with id: 1 +2023-12-11T20:33:46+0100 [teamprojekt_competition_server.server.game_manager.GameManager#info] Matched two players: player1 0, player2 1 +2023-12-11T20:33:46+0100 [teamprojekt_competition_server.server.game_manager.GameManager#info] Started game: 140512021239152 diff --git a/teamprojekt_competition_server/log/server/protocol.log b/teamprojekt_competition_server/log/server/protocol.log new file mode 100644 index 00000000..3ef38eda --- /dev/null +++ b/teamprojekt_competition_server/log/server/protocol.log @@ -0,0 +1,4 @@ +2023-12-11T20:25:22+0100 [teamprojekt_competition_server.server.protocol.COMPServerProtocol#info] Initialized Protocol: 140477668478352 +2023-12-11T20:25:23+0100 [teamprojekt_competition_server.server.protocol.COMPServerProtocol#info] Initialized Protocol: 140477668480416 +2023-12-11T20:33:44+0100 [teamprojekt_competition_server.server.protocol.COMPServerProtocol#info] Initialized Protocol: 140512021236752 +2023-12-11T20:33:46+0100 [teamprojekt_competition_server.server.protocol.COMPServerProtocol#info] Initialized Protocol: 140512021238816 diff --git a/teamprojekt_competition_server/log/server/server.log b/teamprojekt_competition_server/log/server/server.log new file mode 100644 index 00000000..c79709de --- /dev/null +++ b/teamprojekt_competition_server/log/server/server.log @@ -0,0 +1,6 @@ +2023-12-11T20:25:19+0100 [teamprojekt_competition_server.server.server.COMPServer#info] COMPServer 140477692281472 initialized with factory: 140477692281520 +2023-12-11T20:25:19+0100 [teamprojekt_competition_server.server.server.COMPServer#info] Server: 140477692281472 now listening to localhost:1234 +2023-12-11T20:25:27+0100 [teamprojekt_competition_server.server.server.COMPServer#info] Reactor running +2023-12-11T20:33:24+0100 [teamprojekt_competition_server.server.server.COMPServer#info] COMPServer 140512046928736 initialized with factory: 140512046928496 +2023-12-11T20:33:24+0100 [teamprojekt_competition_server.server.server.COMPServer#info] Server: 140512046928736 now listening to localhost:1234 +2023-12-11T20:33:51+0100 [teamprojekt_competition_server.server.server.COMPServer#info] Reactor running diff --git a/teamprojekt_competition_server/server/factory.py b/teamprojekt_competition_server/server/factory.py index bbed2096..3731d077 100644 --- a/teamprojekt_competition_server/server/factory.py +++ b/teamprojekt_competition_server/server/factory.py @@ -19,4 +19,3 @@ def buildProtocol(self, addr: IAddress) -> Protocol | None: game_manager.add_player(new_player) return protocol - \ No newline at end of file diff --git a/teamprojekt_competition_server/server/game_manager.py b/teamprojekt_competition_server/server/game_manager.py index 34827c90..aef793e7 100644 --- a/teamprojekt_competition_server/server/game_manager.py +++ b/teamprojekt_competition_server/server/game_manager.py @@ -22,9 +22,9 @@ def add_player(self, player: IPlayer) -> None: player (IPlayer): player to add """ self.players.append(player) - - player.id = len(self.players)-1 - + + player.id = len(self.players) - 1 + def add_player_to_queue(self, player_id: int): """adds a player to the queue @@ -42,4 +42,5 @@ def add_player_to_queue(self, player_id: int): self.queue.append(player_id) log.debug(f"added player to queue. ID: {player_id}") -game_manager = GameManager() \ No newline at end of file + +game_manager = GameManager() diff --git a/teamprojekt_competition_server/server/interfaces.py b/teamprojekt_competition_server/server/interfaces.py index aaf859a6..16a1f16d 100644 --- a/teamprojekt_competition_server/server/interfaces.py +++ b/teamprojekt_competition_server/server/interfaces.py @@ -6,30 +6,29 @@ class IAction: - """Interface for an action - """ + """Interface for an action""" + pass class IPlayer(abc.ABC): """Interface for a player""" - + def __init__(self) -> None: - self.id : int = -1 - + self.id: int = -1 + @abc.abstractmethod def authenticate(self, result_callback): """authenticates player Args: - result_callback (Callable): callback + result_callback (Callable): callback """ ... @abc.abstractmethod def notify_start(self): - """notifies player that the game has started - """ + """notifies player that the game has started""" ... @abc.abstractmethod @@ -47,8 +46,7 @@ def get_action(self, obv, result_callback) -> IAction: @abc.abstractmethod def notify_end(self, result, stats): - """notifies player that the game has ended - """ + """notifies player that the game has ended""" ... @@ -62,7 +60,7 @@ def __init__(self, players: list[IPlayer]) -> None: def start(self): """ - notifies all players that the game has started + notifies all players that the game has started and starts the game cycle """ for p in self.players: @@ -70,10 +68,10 @@ def start(self): self._game_cycle() def end(self, reason="unknown"): - """notifies all players that the game has ended + """notifies all players that the game has ended Args: - reason (str, optional): reason why the game has ended. Defaults to "unknown". + reason (str, optional): reason why the game has ended. Defaults to "unknown" """ for i, p in enumerate(self.players): p.notify_end(result=self._player_won(i), stats=self._player_stats(i)) @@ -84,8 +82,7 @@ def _update_enviroment(self): ... def _game_cycle(self): - """collectes all actions and puts them in current_actions list - """ + """collectes all actions and puts them in current_actions list""" self.result_received = 0 for i, p in enumerate(self.players): @@ -101,7 +98,7 @@ def __res(v: IAction, index=i): self.end() else: self._game_cycle() - + p.get_action(obv=self._observation(), result_callback=__res) @abc.abstractmethod @@ -117,11 +114,12 @@ def _is_finished(self) -> bool: bool: returns true if game has ended """ ... + @abc.abstractmethod def _observation(self): """retutns the observation for the players""" ... - + @abc.abstractmethod def _player_won(self, index) -> bool: """check wether the player has won @@ -130,7 +128,7 @@ def _player_won(self, index) -> bool: bool: returns true if player has won """ ... - + @abc.abstractmethod def _player_stats(self, index) -> int: """retutns the player stats""" diff --git a/teamprojekt_competition_server/server/main.py b/teamprojekt_competition_server/server/main.py index 4a65007a..4c5e63f9 100644 --- a/teamprojekt_competition_server/server/main.py +++ b/teamprojekt_competition_server/server/main.py @@ -14,7 +14,7 @@ class ExampleGame(IGame): """example for a game""" - + def __init__(self, players: list[IPlayer]) -> None: super().__init__(players=players) self.env = 0 @@ -27,15 +27,16 @@ def _validate_action(self, action): def _is_finished(self) -> bool: return self.env > 10 - + def _observation(self): return self.env - + def _player_stats(self, index) -> int: return 0 - + def _player_won(self, index) -> bool: - if index==0: return True + if index == 0: + return True return False diff --git a/teamprojekt_competition_server/server/player.py b/teamprojekt_competition_server/server/player.py index f9e4a091..62e988a3 100644 --- a/teamprojekt_competition_server/server/player.py +++ b/teamprojekt_competition_server/server/player.py @@ -11,22 +11,45 @@ class COMPPlayer(IPlayer): def __init__(self, connection: COMPServerProtocol) -> None: self.connection: COMPServerProtocol = connection - + def connected(): - self.authenticate(result_callback=lambda x: game_manager.add_player_to_queue(self.id)) + """Connects player to server""" + self.authenticate( + result_callback=lambda x: game_manager.add_player_to_queue(self.id) + ) + self.connection.addConnectionMadeCallback(connected) def authenticate(self, result_callback): + """authenticates player + + Args: resul_callback (callback function) + + Returns: token (string)""" return self.connection.get_token(result_callback) def notify_start(self): + """notifies start of game""" self.connection.notify_start() def get_action(self, obv, result_callback): + """receive action from server + + Args: + obv(any): observation""" return self.connection.get_step(obv, result_callback) def notify_end(self, result, stats): + """called when game ends + + Args: + result (any): result of the game + stats: (any): stats of the game""" + def callback(ready: bool): - if ready: + if ready: game_manager.add_player_to_queue(self.id) - return self.connection.notify_end(result=result, stats=stats, return_callback=callback) + + return self.connection.notify_end( + result=result, stats=stats, return_callback=callback + ) diff --git a/teamprojekt_competition_server/server/protocol.py b/teamprojekt_competition_server/server/protocol.py index 95198530..376d3ddb 100644 --- a/teamprojekt_competition_server/server/protocol.py +++ b/teamprojekt_competition_server/server/protocol.py @@ -15,8 +15,8 @@ class COMPServerProtocol(amp.AMP): def __init__(self, boxReceiver=None, locator=None): super().__init__(boxReceiver, locator) - self.connection_made_callbacks : list[Callable[[], None]] = [] - self.connection_lost_callbacks : list[Callable[[], None]] = [] + self.connection_made_callbacks: list[Callable[[], None]] = [] + self.connection_lost_callbacks: list[Callable[[], None]] = [] def addConnectionMadeCallback(self, callback): """adds callback that is executed, when the connection is made @@ -25,7 +25,7 @@ def addConnectionMadeCallback(self, callback): callback (function): callback to execute, when the connection is made """ self.connection_made_callbacks.append(callback) - + def addConnectionLostCallback(self, callback): """adds callback that is executed, when the connection is lost @@ -35,19 +35,19 @@ def addConnectionLostCallback(self, callback): self.connection_lost_callbacks.append(callback) def connectionMade(self) -> None: + """called upon connectionMade event""" addr: IAddress = self.transport.getPeer() # type: ignore - log.debug( - f"Connected to client with IP address: {addr.host}, Port: {addr.port} via {addr.type}" - ) - #broadcast to callbacks + debug_msg = f"connected to client with IP: {addr.host}" + debug_msg_rest = f", Port: {addr.port} viae {addr.type}" + log.debug(debug_msg + debug_msg_rest) + # broadcast to callbacks for c in self.connection_made_callbacks: c() return super().connectionMade() - + def connectionLost(self, reason): - - #broadcast to callbacks + """called upon connectionLost event""" for c in self.connection_lost_callbacks: c() @@ -59,7 +59,7 @@ def get_token(self, return_callback: Callable[[str], None]) -> None: Args: game (Game): game that starts """ - return self.callRemote(Auth).addCallback( + self.callRemote(Auth).addCallback( callback=lambda res: return_callback(res["token"]) )