Skip to content

Commit

Permalink
Fix(ui-client): TTTK-52 Moved UI Client to new file
Browse files Browse the repository at this point in the history
  • Loading branch information
Petzys committed Feb 28, 2024
1 parent 00a9073 commit 9c5158b
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 128 deletions.
128 changes: 1 addition & 127 deletions Client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,130 +275,4 @@ async def chat_message(self, message:str):
await self._websocket.send(json.dumps(msg))

async def close(self):
await self._websocket.close()

class GameClientUI(GameClient):
"""A class to represent a game client that connects to a server and sends and receives messages. This class is specifically designed to be used with a tkinter UI.
Attributes:
_tk_root (tk.Tk): The root of the tkinter application.
_in_queue (Queue): The queue to receive messages from the server.
_out_queue (Queue): The queue to send messages to the server.
For the rest of the attributes and methods see the `GameClient` class.
"""
def __init__(self, ip:str, port:int, player:Player, tk_root:tk.Tk, in_queue:Queue, out_queue:Queue) -> None:
self._tk_root = tk_root
self._in_queue = in_queue
self._out_queue = out_queue
super().__init__(ip, port, player)

@classmethod
async def join_game(cls, player: Player, ip: str, tk_root:tk.Tk, in_queue:Queue, out_queue:Queue, port: int = 8765) -> tuple[GameClientUI, asyncio.Task]:

client = cls(ip, port, player, tk_root, in_queue, out_queue)

await client.connect()
listening_task = asyncio.create_task(client.listen())
await asyncio.create_task(client.join_lobby())

return client, listening_task

async def _message_handler(self, message_type: str):
# Receive messages from the server
match message_type:
case "lobby/status":
self._in_queue.put({
"message_type": "lobby/status",
"player": self._lobby_status
})
self._tk_root.event_generate("<<lobby/status>>", when="tail")
case "game/start":
self._in_queue.put({
"message_type": "game/start",
"starting_player": self._current_player,
"starting_player_symbol": self._symbol == "X",
"opponent": self._opponent,
"opponent_symbol": self._symbol != "X"
})
self._tk_root.event_generate("<<game/start>>", when="tail")
case "game/end":
self._in_queue.put({
"message_type": "game/end",
"winner": self._winner,
"win": self._winner == self._player,
"final_playfield": self._game_status
})
self._tk_root.event_generate("<<game/end>>", when="tail")
self.client.close()
case "game/turn":
self._in_queue.put({
"message_type": "game/turn",
"next_player": int(self._current_player == self._opponent),
"playfield": self._game_status
})
self._tk_root.event_generate("<<game/turn>>", when="tail")
case "statistics/statistics":
pass
case "game/error":
self._in_queue.put({
"message_type": "game/error",
"error_message": self._error_history[-1]
})
self._tk_root.event_generate("<<game/error>>", when="tail")
case "chat/receive":
self._in_queue.put({
"message_type": "chat/receive",
"sender": self._chat_history[-1][0],
"message": self._chat_history[-1][1]
})
self._tk_root.event_generate("<<chat/receive>>", when="tail")

return

async def client_thread_function(tk_root:tk.Tk, in_queue:Queue, out_queue:Queue, player: Player, ip:str, port:int) -> None:
"""The function that is run in the client thread. It connects to the server. It sends and receives messages."""

client, listening_task = await GameClientUI.join_game(player=player, ip=ip, tk_root=tk_root, in_queue=in_queue, out_queue=out_queue, port=port)

while client._websocket.open:
# Send messages to the server
try:
message: dict = out_queue.get_nowait()
except Empty:
await asyncio.sleep(0.1)
logger.warning("Out queue is empty")
continue

logger.warning(f"Sending: {message}")
if message:
match message["message_type"]:
case "lobby/join":
pass
case "lobby/ready":
await client.lobby_ready(message["args"])
case "lobby/kick":
await client.lobby_kick(message["args"])
case "game/make_move":
await client.game_make_move(message["args"])
case "chat/message":
pass
case _:
logger.error(f"Unknown message type received from UI in out_queue: {message['message_type']}")
continue

out_queue.task_done()

await listening_task

return

def asyncio_thread_wrapper(tk_root:tk.Tk, in_queue:Queue, out_queue:Queue, player: Player, ip:str, port:int):
"""Wrapper function to run the client thread function in an asyncio event loop."""
asyncio.run(client_thread_function(tk_root, in_queue, out_queue, player, ip, port))

def client_thread(tk_root:tk.Tk, in_queue:Queue, out_queue:Queue, player: Player, ip:str, port:int = 8765) -> Thread:
"""Start a new client thread that connects to the server and sends and receives messages."""
thread = Thread(target=asyncio_thread_wrapper, args=(tk_root, in_queue, out_queue, player, ip , port), daemon=True)
thread.start()
return thread
await self._websocket.close()
139 changes: 139 additions & 0 deletions Client/ui_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
from __future__ import annotations
import asyncio
from websockets.client import connect
from Server.player import Player
from threading import Thread
from queue import Queue, Empty
import tkinter as tk
from Client.client import GameClient
import logging

# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class GameClientUI(GameClient):
"""A class to represent a game client that connects to a server and sends and receives messages. This class is specifically designed to be used with a tkinter UI.
Attributes:
_tk_root (tk.Tk): The root of the tkinter application.
_in_queue (Queue): The queue to receive messages from the server.
_out_queue (Queue): The queue to send messages to the server.
For the rest of the attributes and methods see the `GameClient` class.
"""
def __init__(self, ip:str, port:int, player:Player, tk_root:tk.Tk, in_queue:Queue, out_queue:Queue) -> None:
self._tk_root = tk_root
self._in_queue = in_queue
self._out_queue = out_queue
super().__init__(ip, port, player)

@classmethod
async def join_game(cls, player: Player, ip: str, tk_root:tk.Tk, in_queue:Queue, out_queue:Queue, port: int = 8765) -> tuple[GameClientUI, asyncio.Task]:

client = cls(ip, port, player, tk_root, in_queue, out_queue)

await client.connect()
listening_task = asyncio.create_task(client.listen())
await asyncio.create_task(client.join_lobby())

return client, listening_task

async def _message_handler(self, message_type: str):
# Receive messages from the server
match message_type:
case "lobby/status":
self._in_queue.put({
"message_type": "lobby/status",
"player": self._lobby_status
})
self._tk_root.event_generate("<<lobby/status>>", when="tail")
case "game/start":
self._in_queue.put({
"message_type": "game/start",
"starting_player": self._current_player,
"starting_player_symbol": self._symbol == "X",
"opponent": self._opponent,
"opponent_symbol": self._symbol != "X"
})
self._tk_root.event_generate("<<game/start>>", when="tail")
case "game/end":
self._in_queue.put({
"message_type": "game/end",
"winner": self._winner,
"win": self._winner == self._player,
"final_playfield": self._game_status
})
self._tk_root.event_generate("<<game/end>>", when="tail")
self.client.close()
case "game/turn":
self._in_queue.put({
"message_type": "game/turn",
"next_player": int(self._current_player == self._opponent),
"playfield": self._game_status
})
self._tk_root.event_generate("<<game/turn>>", when="tail")
case "statistics/statistics":
pass
case "game/error":
self._in_queue.put({
"message_type": "game/error",
"error_message": self._error_history[-1]
})
self._tk_root.event_generate("<<game/error>>", when="tail")
case "chat/receive":
self._in_queue.put({
"message_type": "chat/receive",
"sender": self._chat_history[-1][0],
"message": self._chat_history[-1][1]
})
self._tk_root.event_generate("<<chat/receive>>", when="tail")

return

async def client_thread_function(tk_root:tk.Tk, in_queue:Queue, out_queue:Queue, player: Player, ip:str, port:int) -> None:
"""The function that is run in the client thread. It connects to the server. It sends and receives messages."""

client, listening_task = await GameClientUI.join_game(player=player, ip=ip, tk_root=tk_root, in_queue=in_queue, out_queue=out_queue, port=port)

while client._websocket.open:
# Send messages to the server
try:
message: dict = out_queue.get_nowait()
except Empty:
await asyncio.sleep(0.1)
logger.warning("Out queue is empty")
continue

logger.warning(f"Sending: {message}")
if message:
match message["message_type"]:
case "lobby/join":
pass
case "lobby/ready":
await client.lobby_ready(message["args"])
case "lobby/kick":
await client.lobby_kick(message["args"])
case "game/make_move":
await client.game_make_move(message["args"])
case "chat/message":
pass
case _:
logger.error(f"Unknown message type received from UI in out_queue: {message['message_type']}")
continue

out_queue.task_done()

await listening_task

return

def asyncio_thread_wrapper(tk_root:tk.Tk, in_queue:Queue, out_queue:Queue, player: Player, ip:str, port:int):
"""Wrapper function to run the client thread function in an asyncio event loop."""
asyncio.run(client_thread_function(tk_root, in_queue, out_queue, player, ip, port))

def client_thread(tk_root:tk.Tk, in_queue:Queue, out_queue:Queue, player: Player, ip:str, port:int = 8765) -> Thread:
"""Start a new client thread that connects to the server and sends and receives messages."""
thread = Thread(target=asyncio_thread_wrapper, args=(tk_root, in_queue, out_queue, player, ip , port), daemon=True)
thread.start()
return thread
2 changes: 1 addition & 1 deletion UI/multi.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from .base_frame import base_frame
from .field_frame import Field
from .profile import Profile
from Client.client import client_thread
from Client.ui_client import client_thread
from .field_frame import player_type

class Join(base_frame):
Expand Down

0 comments on commit 9c5158b

Please sign in to comment.