refactor(Lobby/model): metadata, pydantic models

parent f3c9c952
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)
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
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
......
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
......
......@@ -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
......
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)
......
......@@ -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)
......@@ -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}")
......@@ -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")
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment