From 4790b994b1722192f757d678f4e449a6e78e8084 Mon Sep 17 00:00:00 2001 From: Giovani Date: Tue, 6 Jul 2021 00:19:59 -0400 Subject: [PATCH] feat: expand network connection implementation --- tetris/online.py | 115 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 98 insertions(+), 17 deletions(-) diff --git a/tetris/online.py b/tetris/online.py index ef5d537..7f71931 100644 --- a/tetris/online.py +++ b/tetris/online.py @@ -1,49 +1,130 @@ import asyncio +from os import stat import websockets +import json +import queue # refer to https://docs.python.org/3/library/queue.html for multi-threading +from typing import Dict from threading import Thread class MultiplayerService(): _thread = None + _receive_piece_queue = queue.Queue() + _send_piece_queue = queue.Queue() + _current_game_id = None @classmethod - def init(cls): + def init(cls) -> None: thread = Thread(target=asyncio.get_event_loop().run_until_complete,\ args=(_NetworkConnectionService.init(),)) thread.start() @classmethod - def quit(cls): - _NetworkConnectionService.close_connection() - -class _NetworkConnectionService(): - _websocket = None - _URI = "ws://webapi.tetri5.com" - _is_closed = False + def enter_game(cls, game_id: str) -> None: + cls._current_game_id = game_id + _NetworkConnectionService._join_game = True + pass @classmethod - async def init(cls): + def send_piece(cls, piece: "PieceDto") -> None: + cls._send_piece_queue.put(piece) + + @classmethod + def try_receive_piece(cls) -> "PieceDto": + return cls._receive_piece_queue.get() if not cls._receive_piece_queue.empty() else None + + @classmethod + def quit(cls) -> None: + _NetworkConnectionService.close_connection() + +class _NetworkConnectionService(): + _URI = "ws://localhost:5001" # TODO get from config file + _websocket = None + _is_closed = False + _join_game = False + + @classmethod + async def init(cls) -> None: await cls._connect_to_server() await cls._run_network_loop() @classmethod - def close_connection(cls): + def close_connection(cls) -> None: cls._is_closed = True @classmethod - async def _run_network_loop(cls): + async def _run_network_loop(cls) -> None: while True: - await asyncio.sleep(16e-3) - await cls._websocket.send("ping") - print(await cls._websocket.recv()) + await asyncio.sleep(16e-3) # TODO add clock tick instead + await cls._try_enter_game() + await cls._try_send_piece() + # await cls._try_receive_message() + + # if conection is closed, exit loop if cls._is_closed: await cls._websocket.close() break @classmethod - async def _connect_to_server(cls): - print("Connecting to server...") + async def _try_enter_game(cls) -> None: + if not cls._join_game: + return + + json_message = json.dumps({"action": "enter_game", "gameId": MultiplayerService._current_game_id}) + await cls._websocket.send(json_message) + + json_response = await cls._websocket.recv() + print(json_response) + + cls._join_game = False + + @classmethod + async def _try_send_piece(cls) -> None: + # if no messages to proccess, return + if MultiplayerService._send_piece_queue.empty(): + return + + # get next piece to send and send to server + piece = MultiplayerService._send_piece_queue.get() + + # construct json message + message = {} + message["action"] = "send_piece" + message["gameId"] = MultiplayerService._current_game_id + message["piece"] = piece.__dict__() + + await cls._websocket.send(json.dumps(message)) + + @classmethod + async def _try_receive_message(cls) -> None: + try: + async for message in cls._websocket: + data = json.loads(message) + print(data) + # if message type is receive_piece, put it in the receive queue + if data["type"] == "receive_piece": + # convert Dict to PieceDto + MultiplayerService._receive_piece_queue.put(PieceDto.create(data["piece"])) + finally: + pass # TODO handle connection closed exception + + @classmethod + async def _connect_to_server(cls) -> None: + print("Connecting to server...") # TODO replace with logging cls._websocket = await websockets.connect(cls._URI, ping_interval=None) # ping_interval=None is important, otherwise the server will disconnect us # https://stackoverflow.com/a/58993145/11512104 - print("Connected to server...") \ No newline at end of file + print("Connected to server...") # TODO replace with logging + +# DTOs + +class PieceDto(): + def __init__(self, x: int, y: int, type_: str) -> None: + self.x = x + self.y = y + self.type = type_ + pass + + @staticmethod + def create(data: Dict) -> "PieceDto": + return PieceDto(data["x"], data["y"], data["type"]) \ No newline at end of file