From 527608981fa17a106f3bb1e9db520e61525c1a67 Mon Sep 17 00:00:00 2001 From: Paul-Louis NECH Date: Tue, 14 Apr 2020 20:25:10 +0200 Subject: [PATCH] refactor(Lobby/model): metadata, pydantic models --- server/game/lobby.py | 48 ++++++++++++++++++++++++++++++++++++++---------- server/model/card.py | 8 ++++---- server/model/deck.py | 3 +-- server/model/hand.py | 9 ++++----- server/model/hands.py | 60 ++++++++++++++++++++++++++++++++++-------------------------- server/model/known.py | 12 ++++++------ server/model/players.py | 11 ++++++----- server/test/test_lobby.py | 13 +++++-------- server/ws.py | 6 ++++-- 9 files changed, 102 insertions(+), 68 deletions(-) diff --git a/server/game/lobby.py b/server/game/lobby.py index 1e7de37..7a3ae36 100644 --- a/server/game/lobby.py +++ b/server/game/lobby.py @@ -1,6 +1,7 @@ -from collections import defaultdict -from typing import List, Dict +import json +from typing import List, Dict, Optional +from pydantic.main import BaseModel from socketio import AsyncServer from server.game.manager import ClientManager @@ -9,14 +10,20 @@ from server.model.game import Game from server.model.players import Player, Announce +class Metadata(BaseModel): + ready: bool = False + sid: str = "" + last_announce: Optional[Announce] = None + + class LobbyManager(ClientManager): """ A ClientManager that handles a lobby, then orchestrates games. """ def __init__(self, sio: AsyncServer): super().__init__() - self.last_announces: Dict[Player, Announce] = {} - self.lobby: Dict[Player, bool] = {} + self.lobby: Dict[str, Player] = {} + self.metadata: Dict[str, Metadata] = {} self.games: List[Game] = [] self.sio = sio @@ -26,28 +33,49 @@ class LobbyManager(ClientManager): @property def players_ready(self): - return [k for k, v in self.lobby.items() if v] + return [self.lobby[k] for k, m in self.metadata.items() if m.ready] - async def add_player(self, player: Player, is_ready: bool = False) -> None: - self.lobby[player] = is_ready + async def add_player(self, player: Player, is_ready: bool = False) -> str: + self.lobby[player.name] = player + self.metadata[player.name] = Metadata(ready=is_ready) print(f"Added {player} to a lobby with {len(self.lobby)} players.") - await self.sio.emit('messageChannel', "PONG") + return f"Bienvenu, {player}! Il y a {len(self.lobby)} joueurs en ligne." def wants_to_play(self, player: Player): - self.lobby[player] = True + self.metadata[player.name].ready = True print(f"{player} ready to play! ({self.nb_ready} people ready)") def announces(self, player: Player, announce: Announce): # FIXME: Call this message on incoming "ANNOUNCE" - self.last_announces[player] = announce + self.metadata[player.name].last_announce = announce print(f"{player} ready to play! ({self.nb_ready} people ready)") def start_game(self, max_players=2): players = self.players_ready[:max_players] self.games.append(Game(players, manager=self)) + for p in players: + self.metadata[p.name].ready = False print(f"Game started : {' vs '.join([p.name for p in players])}") def send(self, to: Player, message: MessageToPlayer, extra=None): self.sio.send(message) pass + + def handle_message(self, sid, data): + sender: Optional[Player] = None + sanitized = str(data) + print(f"Lobby| Received message from {sid}: {data}.") + + for player in self.players: + if player.name in sanitized: + sender = player + if sender: + print(f"Lobby| Found sender: {sender.name}") + + message = MessageToPlayer.Waiting + extras = [p.name for p in self.players] + body = {"message": message.name} + if extras: + body["extras"] = extras + return json.dumps(body) diff --git a/server/model/card.py b/server/model/card.py index 39ae536..f40309d 100644 --- a/server/model/card.py +++ b/server/model/card.py @@ -1,7 +1,8 @@ -from dataclasses import dataclass from enum import Enum from typing import Optional +from pydantic.main import BaseModel + class Value(Enum): Two = 1 @@ -26,8 +27,7 @@ class Color(Enum): Diamonds = "♦" -@dataclass(frozen=True) -class Card: +class Card(BaseModel): value: Value color: Optional[Color] = None @@ -57,4 +57,4 @@ def lowest_value_and_rest(): otherValues = list(Value) otherValues.remove(lowValue) - return lowValue, otherValues \ No newline at end of file + return lowValue, otherValues diff --git a/server/model/deck.py b/server/model/deck.py index bc84067..fd4af26 100644 --- a/server/model/deck.py +++ b/server/model/deck.py @@ -1,5 +1,4 @@ from random import randrange, shuffle -from typing import List from server.model.card import Card, Value, Color @@ -7,7 +6,7 @@ from server.model.card import Card, Value, Color class Deck: def __init__(self, cards=None): if cards is None: - cards = [Card(v, c) for v in Value for c in Color] + cards = [Card(value=v, color=c) for v in Value for c in Color] else: print("Deck init with cards:", cards, len(cards)) self.cards = cards diff --git a/server/model/hand.py b/server/model/hand.py index 047bb3b..e4a4c7e 100644 --- a/server/model/hand.py +++ b/server/model/hand.py @@ -1,14 +1,13 @@ from collections import Counter from typing import List +from pydantic.main import BaseModel + from server.model.card import Card, Value -class Hand: - def __init__(self, cards: List[Card] = None): - if cards is None: - cards = [] - self.cards: List[Card] = cards +class Hand(BaseModel): + cards: List[Card] = [] def __contains__(self, item: Card): return item in self.cards diff --git a/server/model/hands.py b/server/model/hands.py index b916eba..38a2b85 100644 --- a/server/model/hands.py +++ b/server/model/hands.py @@ -5,44 +5,52 @@ from server.model.card import Value, Color, Card from server.model.hand import Hand +def hand(cards: List[Card]) -> Hand: + return Hand(cards=cards) + + +def card(value: Value, color: Color) -> Card: + return Card(value=value, color=color) + + def carre(value) -> Hand: - return Hand([ - Card(value, Color.Hearts), - Card(value, Color.Clubs), - Card(value, Color.Diamonds), - Card(value, Color.Spades) + return hand([ + card(value, Color.Hearts), + card(value, Color.Clubs), + card(value, Color.Diamonds), + card(value, Color.Spades) ]) def full(value_aux: Value, value_par: Value) -> Hand: - return Hand([ - Card(value_aux, Color.Hearts), - Card(value_aux, Color.Clubs), - Card(value_aux, Color.Diamonds), - Card(value_par, Color.Hearts), - Card(value_par, Color.Clubs) + return hand([ + card(value_aux, Color.Hearts), + card(value_aux, Color.Clubs), + card(value_aux, Color.Diamonds), + card(value_par, Color.Hearts), + card(value_par, Color.Clubs) ]) def brelan(value) -> Hand: - return Hand([ - Card(value, Color.Hearts), - Card(value, Color.Clubs), - Card(value, Color.Diamonds) + return hand([ + card(value, Color.Hearts), + card(value, Color.Clubs), + card(value, Color.Diamonds) ]) def pair(value) -> Hand: - return Hand([ - Card(value, Color.Hearts), - Card(value, Color.Clubs) + return hand([ + card(value, Color.Hearts), + card(value, Color.Clubs) ]) def single(low_value) -> Hand: - return Hand([ - Card(low_value, Color.Hearts) + return hand([ + card(low_value, Color.Hearts) ]) @@ -52,13 +60,13 @@ def double_pair(value: Value, other: Value = None): other = Value.Two if value == Value.Three else Value.Three assert other != value - return Hand([ + return hand([ # Pair of value - Card(value, Color.Hearts), - Card(value, Color.Clubs), + card(value, Color.Hearts), + card(value, Color.Clubs), # And pair of twos or threes - Card(other, Color.Hearts), - Card(other, Color.Clubs) + card(other, Color.Hearts), + card(other, Color.Clubs) ]) @@ -77,7 +85,7 @@ def all_options() -> List[Hand]: for b in brelans: for p in pairs: if not any([c in b.cards for c in p.cards]): # Valid full - hands.append(Hand([*b.cards, *p.cards])) + hands.append(hand([*b.cards, *p.cards])) return hands diff --git a/server/model/known.py b/server/model/known.py index d0acae6..e340263 100644 --- a/server/model/known.py +++ b/server/model/known.py @@ -1,10 +1,10 @@ -from server.model.card import Value, Color, Card -from server.model.hands import pair, single, double_pair, brelan, full, carre +from server.model.card import Value, Color +from server.model.hands import pair, single, double_pair, brelan, full, carre, card -ACE_OF_HEARTS = Card(Value.Ace, Color.Hearts) -ACE_OF_SPADES = Card(Value.Ace, Color.Spades) -ACE_OF_DIAMONDS = Card(Value.Ace, Color.Diamonds) -ACE_OF_CLUBS = Card(Value.Ace, Color.Clubs) +ACE_OF_HEARTS = card(Value.Ace, Color.Hearts) +ACE_OF_SPADES = card(Value.Ace, Color.Spades) +ACE_OF_DIAMONDS = card(Value.Ace, Color.Diamonds) +ACE_OF_CLUBS = card(Value.Ace, Color.Clubs) PAIR_ACE = pair(Value.Ace) SINGLE_ACE = single(Value.Ace) DOUBLE_PAIR_ACE = double_pair(Value.Ace) diff --git a/server/model/players.py b/server/model/players.py index 49d3517..3a0f729 100644 --- a/server/model/players.py +++ b/server/model/players.py @@ -2,14 +2,15 @@ from abc import abstractmethod, ABC from dataclasses import dataclass from typing import Optional +from pydantic.main import BaseModel + from server.model.animals import random_animal_name from server.model.card import Card from server.model.hand import Hand from server.model.hands import random_option -@dataclass -class Announce: +class Announce(BaseModel): bet: Optional[Hand] = None @property @@ -55,7 +56,7 @@ class NaivePlayer(Player, ABC): def announce(self, current_bet: Optional[Hand]) -> Announce: if not current_bet or self.hand.value() > current_bet.value(): - return Announce(self.hand) + return Announce(bet=self.hand) else: return Announce() @@ -70,7 +71,7 @@ class RandomPlayer(Player, ABC): if current_bet and not current_bet.is_menteur and current_bet.is_carre_as: return Announce() else: - return Announce(random_option()) + return Announce(bet=random_option()) class MenteurPlayer(Player, ABC): @@ -81,4 +82,4 @@ class MenteurPlayer(Player, ABC): def announce(self, current_bet: Optional[Hand]) -> Announce: hand = random_option() - return Announce(hand) + return Announce(bet=hand) diff --git a/server/test/test_lobby.py b/server/test/test_lobby.py index 9292a35..9475cd2 100644 --- a/server/test/test_lobby.py +++ b/server/test/test_lobby.py @@ -29,10 +29,7 @@ class MockPlayer(Player): return len([m for (m, e) in self.messages if m is msg]) def announce(self, current_bet: Optional[Hand]) -> Announce: - if self.bets: - return Announce(self.bets.pop()) - else: - return Announce(CARRE_ACE) + return Announce(bet=self.bets.pop() if self.bets else CARRE_ACE) def receive(self, message: MessageToPlayer, extra: Optional[Any] = None): self.messages.append((message, extra)) @@ -92,8 +89,8 @@ class TestManager(TestCase): self.assertIn(MessageToPlayer.Win, self.j1.messages, "Win not told") self.assertIn(MessageToPlayer.Lose, self.j2.messages, "Lose not told") - def assertGot(self, player: MockPlayer, type: MessageToPlayer, extra=None, msg: str = "message not received"): - self.assertIn(type, [t for (t, _) in player.messages], msg) + def assertGot(self, player: MockPlayer, message: MessageToPlayer, extra=None, msg: str = "message not received"): + self.assertIn(message, [t for (t, _) in player.messages], msg) if extra: - matching = [m for (m, e) in player.messages if e == extra and m == type] - self.assertTrue(matching, f"No message {type} with extra {extra}") + matching = [m for (m, e) in player.messages if e == extra and m == message] + self.assertTrue(matching, f"No message {message} with extra {extra}") diff --git a/server/ws.py b/server/ws.py index 54643f1..037f852 100644 --- a/server/ws.py +++ b/server/ws.py @@ -18,6 +18,7 @@ class ClientPlayer(Player): def __init__(self, lobby: LobbyManager): super().__init__() self.lobby = lobby + self.ready = False async def announce(self, current_bet: Optional[Hand]) -> Announce: return lobby.last_announces[self] @@ -27,13 +28,14 @@ class ClientPlayer(Player): async def connect(sid, environ): print("[WS] Connect ", sid, environ) player = ClientPlayer(lobby) - await lobby.add_player(player) + reply: str = await lobby.add_player(player) + await sio.emit('messageChannel', reply, room=sid) @sio.event async def message(sid, data): print("[WS] Message ", data) - await sio.emit('messageChannel', "OK", room=sid) + await sio.emit('messageChannel', lobby.handle_message(sid, data), room=sid) @sio.on("pingServer") -- libgit2 0.27.0