Archived
This commit is contained in:
@@ -2,4 +2,6 @@
|
||||
netcode messages and model functions"""
|
||||
|
||||
from controller.client import ClientController
|
||||
from controller.lobbyClient import LobbyClientController
|
||||
from controller.game import GameController
|
||||
from controller.lobby import LobbyController
|
||||
|
||||
+22
-57
@@ -14,6 +14,7 @@ from .auth import discord_process_code
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from controller import GameController
|
||||
from model import Game
|
||||
|
||||
|
||||
def vibe_check(t):
|
||||
@@ -50,24 +51,13 @@ class ClientController:
|
||||
websocket: WebSocketServerProtocol,
|
||||
):
|
||||
self.websocket = websocket
|
||||
self.on_message = self.login_context
|
||||
|
||||
self.game_controller = game_controller
|
||||
self.game_controller.clients.append(self)
|
||||
self.on_message = lambda _: None
|
||||
|
||||
async def listen(self) -> None:
|
||||
"""Hooks the client to the correct listen callbacks"""
|
||||
await self.send(
|
||||
Message(
|
||||
type=MessageType.CONTEXT,
|
||||
data_type="game_context_id",
|
||||
data="login",
|
||||
)
|
||||
)
|
||||
|
||||
await self.game_controller.announce_game(self.websocket)
|
||||
|
||||
async for message in self.websocket:
|
||||
await self.on_message(message)
|
||||
async def close(self):
|
||||
"""Remove self from Game broadcasting list"""
|
||||
self.game_controller.clients.remove(self)
|
||||
|
||||
async def send(self, message: Message) -> None:
|
||||
"""Send a Message object to the connected client"""
|
||||
@@ -95,45 +85,14 @@ class ClientController:
|
||||
Message(type=notification_type, data_type="object", data=data)
|
||||
)
|
||||
|
||||
@vibe_check(LoginMessage)
|
||||
async def login_context(self, message: LoginMessage) -> None:
|
||||
"""Handles all messages related to when the current client isn't yet logged in"""
|
||||
username = message.data
|
||||
if message.type == MessageType.REGISTER:
|
||||
if self.game_controller.game.started:
|
||||
return await self.send_error(
|
||||
"you cannot interact with a game that has already started"
|
||||
)
|
||||
if len(username) < 3:
|
||||
return await self.send_error(
|
||||
"username must be longer than 3 characters"
|
||||
)
|
||||
|
||||
if self.game_controller.game.get_player(username) is not None:
|
||||
return await self.send_error("username already exists")
|
||||
|
||||
player = await self.game_controller.game.create_player(username)
|
||||
elif message.type == MessageType.LOGIN:
|
||||
player2 = self.game_controller.game.get_player(username)
|
||||
|
||||
if player2 is None:
|
||||
return await self.send_error("player doesn't exists")
|
||||
|
||||
player = player2
|
||||
elif message.type == MessageType.DISCORD_LOGIN:
|
||||
user = discord_process_code(message.data)
|
||||
if user is None:
|
||||
await self.send_error("discord login not successful")
|
||||
return
|
||||
username = user["global_name"]
|
||||
async def join_game(
|
||||
self, game: "Game", username: str, discord_id: str, discord_avatar: str
|
||||
) -> None:
|
||||
player = next(
|
||||
(
|
||||
p
|
||||
for p in self.game_controller.game.players
|
||||
if p.discord_id == user["id"]
|
||||
),
|
||||
(p for p in game.players if p.discord_id == discord_id),
|
||||
None,
|
||||
)
|
||||
|
||||
if player is None:
|
||||
if self.game_controller.game.started:
|
||||
return await self.send_error(
|
||||
@@ -141,19 +100,18 @@ class ClientController:
|
||||
)
|
||||
player = await self.game_controller.game.create_player(username)
|
||||
|
||||
player.discord_id = user["id"]
|
||||
player.discord_id = discord_id
|
||||
player.image = (
|
||||
"https://cdn.discordapp.com/avatars/"
|
||||
+ user["id"]
|
||||
+ discord_id
|
||||
+ "/"
|
||||
+ user["avatar"]
|
||||
+ discord_avatar
|
||||
+ ".webp"
|
||||
)
|
||||
else:
|
||||
return await self.send_error("invalid message type for context")
|
||||
|
||||
self.player = player
|
||||
self.player.team.listeners.append(self.send_announce_dict)
|
||||
|
||||
if self.game_controller.game.started:
|
||||
self.on_message = self.game_context
|
||||
await self.send(
|
||||
@@ -188,6 +146,13 @@ class ClientController:
|
||||
data=self.player.uuid,
|
||||
)
|
||||
)
|
||||
await self.send(
|
||||
Message(
|
||||
type=MessageType.SET,
|
||||
data_type="current_game",
|
||||
data=self.player.game.uuid,
|
||||
)
|
||||
)
|
||||
|
||||
@vibe_check(Message)
|
||||
async def pregame_context(self, message: Message) -> None:
|
||||
|
||||
+12
-20
@@ -1,5 +1,5 @@
|
||||
import asyncio
|
||||
from typing import Callable, List, Any
|
||||
from typing import Callable, List, Any, TYPE_CHECKING
|
||||
from websockets.exceptions import ConnectionClosedOK
|
||||
from netcode import WSServer
|
||||
from netcode.ws_server import WebSocketServerProtocol
|
||||
@@ -9,27 +9,20 @@ from controller import (
|
||||
ClientController,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from controller import LobbyController
|
||||
|
||||
|
||||
class GameController:
|
||||
server: WSServer
|
||||
lobby_controller: "LobbyController"
|
||||
game: Game
|
||||
clients: list[ClientController]
|
||||
|
||||
clients: List[ClientController]
|
||||
|
||||
def __init__(self, game: Game, server: WSServer) -> None:
|
||||
self.server: WSServer = server
|
||||
def __init__(self, game: Game, lobby_controller: "LobbyController") -> None:
|
||||
self.lobby_controller: "LobbyController" = lobby_controller
|
||||
self.game: Game = game
|
||||
self.server.on_new_connection_callback = self.on_new_connection
|
||||
self.clients: list[ClientController] = []
|
||||
self.game.listeners.append(self.broadcast_announce_dict)
|
||||
self.clients: List[ClientController] = []
|
||||
|
||||
async def on_new_connection(
|
||||
self, websocket: WebSocketServerProtocol, callback: Callable
|
||||
):
|
||||
client = ClientController(self, websocket)
|
||||
self.clients.append(client)
|
||||
await asyncio.gather(client.listen(), callback())
|
||||
self.clients.remove(client)
|
||||
|
||||
async def send(self, message: Message, websocket: WebSocketServerProtocol):
|
||||
try:
|
||||
@@ -38,7 +31,8 @@ class GameController:
|
||||
print("Connection Closed")
|
||||
|
||||
async def broadcast(self, message: Message):
|
||||
await self.server.broadcast(message.model_dump_json())
|
||||
for c in self.clients:
|
||||
await c.send(message)
|
||||
|
||||
async def broadcast_announce_dict(
|
||||
self,
|
||||
@@ -98,6 +92,7 @@ class GameController:
|
||||
)
|
||||
|
||||
for player in self.game.players:
|
||||
# TODO : fix for coop
|
||||
await self.announce_dict(
|
||||
MessageType.CREATE,
|
||||
player.turn.serialize(),
|
||||
@@ -115,9 +110,6 @@ class GameController:
|
||||
websocket,
|
||||
)
|
||||
|
||||
async def guest_handler(self, websocket: WebSocketServerProtocol):
|
||||
await self.announce_game(websocket)
|
||||
|
||||
async def start_game(self):
|
||||
await self.game.start_game()
|
||||
for c in self.clients:
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import asyncio
|
||||
from typing import Callable, List, Any
|
||||
from websockets.exceptions import ConnectionClosedOK
|
||||
from netcode import WSServer
|
||||
from netcode.ws_server import WebSocketServerProtocol
|
||||
from netcode.models import Message, MessageType
|
||||
from model import Lobby
|
||||
from controller import GameController, LobbyClientController
|
||||
|
||||
|
||||
class LobbyController:
|
||||
server: WSServer
|
||||
lobby: Lobby
|
||||
clients: List[LobbyClientController]
|
||||
games: list[GameController]
|
||||
|
||||
def __init__(self, server: WSServer) -> None:
|
||||
self.server: WSServer = server
|
||||
self.server.on_new_connection_callback = self.on_new_connection
|
||||
self.clients: List[LobbyClientController] = []
|
||||
self.lobby = Lobby()
|
||||
self.games: List[GameController] = []
|
||||
|
||||
async def on_new_connection(
|
||||
self, websocket: WebSocketServerProtocol, callback: Callable
|
||||
):
|
||||
client = LobbyClientController(self, websocket)
|
||||
await asyncio.gather(client.listen(), callback())
|
||||
await client.close()
|
||||
|
||||
async def remove_client(self, client: LobbyClientController):
|
||||
self.clients.remove(client)
|
||||
|
||||
async def broadcast_to_unconnected(self, message: Message):
|
||||
for c in self.clients:
|
||||
if c.game_client is None:
|
||||
await c.send(message)
|
||||
|
||||
async def create_game(self):
|
||||
game = GameController(self.lobby.create_game(), self)
|
||||
self.games.append(game)
|
||||
await game.game.init_game()
|
||||
await self.broadcast_to_unconnected(
|
||||
Message(
|
||||
type=MessageType.UPDATE,
|
||||
data_type="object",
|
||||
data=self.lobby.serialize(),
|
||||
),
|
||||
)
|
||||
@@ -0,0 +1,149 @@
|
||||
"""Handles communication between one WebSocket tunnel and the game"""
|
||||
|
||||
from typing import Callable, Any, TYPE_CHECKING, Union
|
||||
from pydantic import ValidationError
|
||||
|
||||
from websockets.server import WebSocketServerProtocol
|
||||
from websockets.exceptions import ConnectionClosedOK
|
||||
|
||||
from netcode.models import Message, LoginMessage, MessageType
|
||||
|
||||
from .auth import discord_process_code
|
||||
|
||||
from controller import ClientController
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from controller import LobbyController
|
||||
|
||||
|
||||
def vibe_check(t):
|
||||
"""Enforces the selected type for the message"""
|
||||
|
||||
def decorator(func):
|
||||
async def new_f(self, message):
|
||||
try:
|
||||
message = t.model_validate_json(message)
|
||||
return await func(self, message)
|
||||
except ValidationError as e:
|
||||
print(e)
|
||||
await self.send_error(e.errors())
|
||||
|
||||
return new_f
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
class LobbyClientController:
|
||||
"""Handles communication between one WebSocket client and the lobby"""
|
||||
|
||||
websocket: WebSocketServerProtocol
|
||||
lobby_controller: "LobbyController"
|
||||
track_connection: Callable
|
||||
|
||||
discord_uid: str | None
|
||||
username: str | None
|
||||
discord_avatar: str | None
|
||||
|
||||
game_client: Union["ClientController", None]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
lobby: "LobbyController",
|
||||
websocket: WebSocketServerProtocol,
|
||||
):
|
||||
self.lobby_controller = lobby
|
||||
self.websocket = websocket
|
||||
self.on_message = self.login_context
|
||||
self.lobby_controller.clients.append(self)
|
||||
self.discord_uid = None
|
||||
self.username = None
|
||||
self.discord_avatar = None
|
||||
self.game_client = None
|
||||
|
||||
async def listen(self) -> None:
|
||||
"""Hooks the client to the correct listen callbacks"""
|
||||
await self.send(
|
||||
Message(
|
||||
type=MessageType.CONTEXT,
|
||||
data_type="game_context_id",
|
||||
data="login",
|
||||
)
|
||||
)
|
||||
|
||||
async for message in self.websocket:
|
||||
await self.on_message(message)
|
||||
|
||||
async def close(self):
|
||||
"""Remove self from Lobby broadcasting list"""
|
||||
if self.game_client is not None:
|
||||
await self.game_client.close()
|
||||
self.lobby_controller.clients.remove(self)
|
||||
|
||||
async def send(self, message: Message) -> None:
|
||||
"""Send a Message object to the connected client"""
|
||||
try:
|
||||
await self.websocket.send(message.model_dump_json())
|
||||
except ConnectionClosedOK:
|
||||
print("Connection Closed")
|
||||
|
||||
async def send_error(self, error: str) -> None:
|
||||
"""Send an error string to the connected client"""
|
||||
await self.send(Message(type=MessageType.ERROR, data_type="error", data=error))
|
||||
|
||||
@vibe_check(LoginMessage)
|
||||
async def login_context(self, message: LoginMessage) -> None:
|
||||
if message.type == MessageType.DISCORD_LOGIN:
|
||||
user = discord_process_code(message.data)
|
||||
if user is None:
|
||||
return
|
||||
if "id" in user:
|
||||
self.discord_uid = user["id"]
|
||||
self.username = user["username"]
|
||||
self.discord_avatar = user["avatar"]
|
||||
self.on_message = self.lobby_context
|
||||
await self.send(
|
||||
Message(
|
||||
type=MessageType.CREATE,
|
||||
data_type="object",
|
||||
data=self.lobby_controller.lobby.serialize(),
|
||||
)
|
||||
)
|
||||
await self.send(
|
||||
Message(
|
||||
type=MessageType.CONTEXT,
|
||||
data_type="game_context_id",
|
||||
data="lobby",
|
||||
)
|
||||
)
|
||||
else:
|
||||
print("user not connected")
|
||||
|
||||
@vibe_check(Message)
|
||||
async def lobby_context(self, message: Message) -> None:
|
||||
if message.type == MessageType.CREATE:
|
||||
if message.data_type == "game":
|
||||
await self.lobby_controller.create_game()
|
||||
if message.type == MessageType.JOIN:
|
||||
if message.data_type == "game":
|
||||
game_ctrler = next(
|
||||
(
|
||||
g
|
||||
for g in self.lobby_controller.games
|
||||
if str(g.game.uuid) == message.data
|
||||
),
|
||||
None,
|
||||
)
|
||||
if game_ctrler is None:
|
||||
return print("Game not found")
|
||||
self.game_client = ClientController(game_ctrler, self.websocket)
|
||||
self.on_message = self.game_context
|
||||
await self.game_client.game_controller.announce_game(self.websocket)
|
||||
await self.game_client.join_game(
|
||||
game_ctrler.game,
|
||||
self.username,
|
||||
self.discord_uid,
|
||||
self.discord_avatar,
|
||||
)
|
||||
|
||||
async def game_context(self, message: Message) -> None:
|
||||
await self.game_client.on_message(message)
|
||||
+8
-6
@@ -1,19 +1,21 @@
|
||||
#!/usr/bin/env python
|
||||
import asyncio
|
||||
|
||||
from netcode.ws_server import WebSocketServerProtocol
|
||||
from netcode import WSServer
|
||||
from controller import ClientController, GameController
|
||||
from model import Game
|
||||
|
||||
from controller import LobbyController
|
||||
|
||||
# from model import Game
|
||||
|
||||
gameServer = WSServer()
|
||||
|
||||
hero_game = Game()
|
||||
game_controller = GameController(hero_game, gameServer)
|
||||
# hero_game = Game()
|
||||
# game_controller = GameController(hero_game, gameServer)
|
||||
lobby = LobbyController(gameServer)
|
||||
|
||||
|
||||
async def main():
|
||||
await hero_game.init_game()
|
||||
# await hero_game.init_game()
|
||||
await gameServer.serve()
|
||||
|
||||
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
from model.serializable import Serializable
|
||||
from .serializable import Serializable
|
||||
|
||||
from model.card_validator import (
|
||||
from .card_validator import (
|
||||
CardRole,
|
||||
CardEffects,
|
||||
CardType,
|
||||
CardFaction,
|
||||
CardParser,
|
||||
)
|
||||
from model.card_loader import load_cards
|
||||
from model.card import Card, Effect, EffectType, EffectTimes
|
||||
from model.team import Team
|
||||
from model.turn import Turn
|
||||
from model.player import Player
|
||||
from model.game import Game
|
||||
from .card_loader import load_cards
|
||||
from .card import Card, Effect, EffectType, EffectTimes
|
||||
from .team import Team
|
||||
from .turn import Turn
|
||||
from .player import Player
|
||||
from .game import Game
|
||||
from .lobby import Lobby
|
||||
|
||||
+14
-3
@@ -1,7 +1,7 @@
|
||||
"""Instance of the game, with players and a market"""
|
||||
|
||||
import asyncio
|
||||
from typing import List
|
||||
from typing import List, TYPE_CHECKING
|
||||
import random
|
||||
from model import Card, CardRole, CardParser, Player, Team, Serializable, Effect
|
||||
|
||||
@@ -9,6 +9,9 @@ from model.card_loader import load_cards, create_card_effects, create_card
|
||||
|
||||
from netcode.models import MessageType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from model import Lobby
|
||||
|
||||
|
||||
class Game(Serializable):
|
||||
"""Instance of the game, with players and a market"""
|
||||
@@ -30,7 +33,9 @@ class Game(Serializable):
|
||||
|
||||
current_turn: Team | None
|
||||
|
||||
def __init__(self) -> None:
|
||||
lobby: "Lobby"
|
||||
|
||||
def __init__(self, lobby: "Lobby") -> None:
|
||||
super().__init__()
|
||||
self.market_stack: List[Card] = []
|
||||
self.market: List[Card | None] = [None for _ in range(5)]
|
||||
@@ -44,6 +49,9 @@ class Game(Serializable):
|
||||
|
||||
self.current_turn = None
|
||||
|
||||
self.lobby = lobby
|
||||
self.lobby.games.append(self)
|
||||
|
||||
async def init_game(self):
|
||||
await self.init_cards()
|
||||
await self.distribute_market_cards()
|
||||
@@ -137,7 +145,7 @@ class Game(Serializable):
|
||||
"effects": [effect.uuid for effect in self.effects],
|
||||
}
|
||||
|
||||
return {k: data[k] for k in data.keys() - ["loaded_cards"]}
|
||||
return {k: data[k] for k in data.keys() - ["loaded_cards", "lobby"]}
|
||||
|
||||
async def start_game(self):
|
||||
self.started = True
|
||||
@@ -176,3 +184,6 @@ class Game(Serializable):
|
||||
(m for m in [f for f in pile if f is not None] if str(m.uuid) == card_id),
|
||||
None,
|
||||
)
|
||||
|
||||
def end_game(self):
|
||||
self.lobby.remove(self)
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
"""Instance of the game, with players and a market"""
|
||||
|
||||
import asyncio
|
||||
from typing import List
|
||||
from model import Game, Serializable
|
||||
|
||||
from model.card_loader import load_cards, create_card_effects, create_card
|
||||
|
||||
from netcode.models import MessageType
|
||||
|
||||
|
||||
class Lobby(Serializable):
|
||||
"""Instance of the game, with players and a market"""
|
||||
|
||||
object_type = "lobby"
|
||||
|
||||
games: list[Game]
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self.games: List[Game] = []
|
||||
|
||||
def serialize(self) -> dict:
|
||||
data = super().serialize() | {
|
||||
"games": [game.uuid for game in self.games],
|
||||
}
|
||||
|
||||
return {k: data[k] for k in data.keys() - []}
|
||||
|
||||
def create_game(self) -> Game:
|
||||
game = Game(self)
|
||||
return game
|
||||
@@ -10,6 +10,7 @@ class MessageType(str, Enum):
|
||||
LOGIN = "login"
|
||||
READY = "ready"
|
||||
DISCORD_LOGIN = "discord_login"
|
||||
JOIN = "join"
|
||||
|
||||
CONTEXT = "context"
|
||||
CREATE = "create"
|
||||
|
||||
Reference in New Issue
Block a user