refactor: Async, announces waiting

parent 357a4209
......@@ -5,7 +5,7 @@ from starlette.exceptions import HTTPException
from starlette.responses import PlainTextResponse
# Game state
from server.ws import sio
from server.ws import sio, lobby
# Server
app = FastAPI()
......@@ -33,6 +33,13 @@ async def hello_world():
return "Hello, gentle[wo]man"
@router.get("/reset")
async def reset(
):
lobby.reset()
return "Done."
app.include_router(router)
......
......@@ -15,6 +15,7 @@ class Metadata(BaseModel):
ready: bool = False
sid: str = ""
last_announce: Optional[Announce] = None
fresh_announce: bool = True
class LobbyManager(ClientManager):
......@@ -46,9 +47,11 @@ class LobbyManager(ClientManager):
self.metadata[player.name].ready = True
print(f"{player} ready to play! ({self.nb_ready} people ready)")
def announces(self, player: Player, announce: Announce):
async def announces(self, player: Player, announce: Announce):
# FIXME: Call this message on incoming "ANNOUNCE"
self.metadata[player.name].last_announce = announce
meta = self.metadata[player.name]
meta.last_announce = announce
meta.fresh_announce = True
print(f"{player} ready to play! ({self.nb_ready} people ready)")
def start_game(self, max_players=2):
......@@ -59,37 +62,63 @@ class LobbyManager(ClientManager):
print(f"Game started : {' vs '.join([p.name for p in players])}")
def reset(self):
players = len(self.players)
games = len(self.games)
msg = f"Resetting! sorry for the {players} players / {games} games..."
print(msg)
for p in self.players:
m = self.metadata[p.name]
self.send(p, MessageToPlayer.Reset)
self.sio.disconnect(m.sid)
self.lobby.clear()
self.metadata.clear()
self.games.clear()
print(f"Reset done, {players} players, {games} games.")
return msg
def send(self, to: Player, message: MessageToPlayer, extra=None):
data = {message: message.name}
if extra:
data["extra"] = extra
self.sio.send(message)
pass
def handle_message(self, sid, data):
sender: Optional[Player] = None
message = None
extras = None
extras = {"players": [p.name for p in self.players]}
print(f"Lobby| Received message from {sid}: {data}.")
for name, metadata in self.metadata.items():
if metadata.sid == sid:
sender = self.lobby[name]
sender = self.which_player(sid)
if sender:
print(f"Lobby| Found sender: {sender.name}")
for option in MessageFromPlayer:
if option.value in data:
if option == MessageFromPlayer.Waiting:
self.metadata[sender.name].ready = False
extras = [p.name for p in self.players]
print(f"MSG|Player ready {extras}")
print(f"MSG|Player {sender.name} waiting, while {extras} ready.")
if len(self.players_ready) > 1:
message = MessageToPlayer.ReadyToStart
elif option == MessageFromPlayer.Ready:
self.wants_to_play(player=sender)
message = MessageToPlayer.Ready
extras = [p.name for p in self.players_ready]
print(f"MSG|Players ready: {extras}.")
message = MessageToPlayer.ReadyToStart \
if len(self.players_ready) > 1 else MessageToPlayer.Waiting
extras["playersReady"] = [p.name for p in self.players_ready]
elif option == MessageFromPlayer.Bet:
# FIXME vraie annonce, pas juste carre d'as lol
self.announces(player=sender,
announce=Announce(bet=CARRE_ACE))
game: Game = self.game_with(sender)
# TODO: connect with current game, return appropriate message
elif option == MessageFromPlayer.Menteur:
self.announces(player=sender, announce=Announce())
......@@ -99,3 +128,19 @@ class LobbyManager(ClientManager):
if extras:
body["extras"] = extras
return json.dumps(body)
def which_player(self, sid) -> Player:
sender: Optional[Player] = None
for name, metadata in self.metadata.items():
if metadata.sid == sid:
sender = self.lobby[name]
return sender
def game_with(self, player: Player) -> Game:
return [g for g in self.games if player in g.players][0]
def send_waiting_for(self, player: Player):
game = self.game_with(player)
self.send(player, MessageToPlayer.YourTurn, extra={"bet": game.current_bet})
for p in [p for p in game.players if p != player]:
self.send(p, MessageToPlayer.Waiting, extra={"waitingFor": p.name})
......@@ -3,7 +3,7 @@ from enum import Enum
class MessageToPlayer(Enum):
Waiting = "WAITING_ROOM"
Ready = "READY_ROOM"
ReadyToStart = "READY_ROOM"
NewGame = "NEW_GAME"
GiveHand = "GIVE_HAND"
WaitTurn = "WAITING_TURN"
......@@ -13,6 +13,7 @@ class MessageToPlayer(Enum):
Win = "WINNER"
WinnerIs = "WINNER_IS"
Lose = "LOSER"
Reset = "RESET"
class MessageFromPlayer(Enum):
......
......@@ -48,6 +48,9 @@ class Card(BaseModel):
def __str__(self) -> str:
return f"{self.value.name} of {self.color.value if self.color else '?'}"
def __hash__(self) -> int:
return hash(self.value) + 100 * hash(self.color)
def score(self) -> int:
return int(self.value.value)
......
......@@ -37,13 +37,14 @@ class Game:
@property
def global_hand(self) -> Hand:
return Hand([c for p in self.players for c in p.hand])
all_cards = [c for p in self.players for c in p.hand.cards]
return Hand(cards=all_cards)
def new_game(self):
async def new_game(self):
self.deck.reset()
while len(self.players) > 1:
loser = self.new_turn()
loser = await self.new_turn()
if self.defeats[loser] == 5:
print(f"{loser} is eliminated!")
......@@ -57,7 +58,7 @@ class Game:
self.message(MessageToPlayer.WinnerIs, extra=winner)
print(f"Game over - {winner.name} wins with {len(winner.hand)} cards!")
def new_turn(self) -> Player:
async def new_turn(self) -> Player:
"""
Runs a turn of the game.
......@@ -83,7 +84,7 @@ class Game:
self.current_bet = None
last_player = None
for current_player in itertools.cycle(self.players):
loser = self.play_turn(current_player, last_player)
loser = await self.play_turn(current_player, last_player)
if loser is not None:
self.players.remove(loser)
self.players.insert(0, loser)
......@@ -100,8 +101,8 @@ class Game:
:return:
"""
to_scan = [Card(c.value) for c in self.global_hand.cards.copy()] # Keep only values
to_find = [Card(c.value) for c in bet.cards]
to_scan = [Card(value=c.value) for c in self.global_hand.cards.copy()] # Keep only values
to_find = [Card(value=c.value) for c in bet.cards]
menteur = False
for card in to_find:
......@@ -130,7 +131,7 @@ class Game:
self.players.append(player)
return True
def play_turn(self, current_player: Player, last_player: Player) -> Optional[Player]:
async def play_turn(self, current_player: Player, last_player: Player) -> Optional[Player]:
"""
Runs a turn, eventually returning a loser.
......@@ -144,7 +145,7 @@ class Game:
self.message(MessageToPlayer.YourTurn, current_player, extra=self.current_bet)
while not self.current_bet: # Ask a valid bet
# FIXME: Wait for player announce? Maybe just sleep 10?
announce = current_player.announce(self.current_bet)
announce = await current_player.announce(self.current_bet)
if announce.bet:
self.current_bet = announce.bet
print(f"{current_player} starts the round: {self.current_bet}")
......
from abc import abstractmethod, ABC
from dataclasses import dataclass
from typing import Optional
from pydantic.main import BaseModel
......@@ -38,7 +37,7 @@ class Player(ABC):
self.hand.cards.clear()
@abstractmethod
def announce(self, current_bet: Optional[Hand]) -> Announce:
async def announce(self, current_bet: Optional[Hand]) -> Announce:
"""
Announces a bet or Menteur, based on the current bet.
:param current_bet:
......@@ -54,7 +53,7 @@ class NaivePlayer(Player, ABC):
def __str__(self):
return "Naive " + super().__str__()
def announce(self, current_bet: Optional[Hand]) -> Announce:
async def announce(self, current_bet: Optional[Hand]) -> Announce:
if not current_bet or self.hand.value() > current_bet.value():
return Announce(bet=self.hand)
else:
......@@ -67,7 +66,7 @@ class RandomPlayer(Player, ABC):
def __str__(self):
return "Random " + super().__str__()
def announce(self, current_bet: Optional[Hand]) -> Announce:
async def announce(self, current_bet: Optional[Hand]) -> Announce:
if current_bet and not current_bet.is_menteur and current_bet.is_carre_as:
return Announce()
else:
......@@ -80,6 +79,6 @@ class MenteurPlayer(Player, ABC):
def __str__(self):
return "Menteur " + super().__str__()
def announce(self, current_bet: Optional[Hand]) -> Announce:
async def announce(self, current_bet: Optional[Hand]) -> Announce:
hand = random_option()
return Announce(bet=hand)
......@@ -185,14 +185,14 @@ class TestHand(TestCase):
# Full[Three of ♥|Three of ♣|Three of ♠|Four of ♥|Four of ♣]
# > Full[Two of ♥|Two of ♣|Two of ♠|Four of ♥|Four of ♣]]
full1 = Hand([Card(v) for v in [
full1 = Hand(cards=[Card(value=v) for v in [
Value.Three,
Value.Three,
Value.Three,
Value.Four,
Value.Four,
]])
full2 = Hand([Card(v) for v in [
full2 = Hand(cards=[Card(value=v) for v in [
Value.Two,
Value.Two,
Value.Two,
......
......@@ -30,11 +30,11 @@ class TestGame(TestCase):
self.game.add_player(RandomPlayer("Foo"))
self.assertEqual(3, len(self.game.players), "Should not add duplicate")
def test_turn(self):
async def test_turn(self):
menteur = MenteurPlayer()
naive = NaivePlayer()
self.game = Game([menteur, naive])
self.game.new_turn()
await self.game.new_turn()
self.assertEqual(1, len([v for v in self.game.defeats.values() if v > 0]), "There should have been one defeat.")
self.assertTrue(1 in self.game.defeats.values(), "A player should have one defeat.")
......@@ -42,8 +42,8 @@ class TestGame(TestCase):
loser = [p for p in self.game.players if self.game.defeats[p]][0]
self.assertEqual(self.game.players[0], loser, "The loser should be first to play.")
def test_full_game(self):
self.game.new_game()
async def test_full_game(self):
await self.game.new_game()
self.assertGreater(len(self.game.defeats.values()), 0, "There should be at least one player with defeats.")
self.assertTrue(5 in self.game.defeats.values(), "A player should have lost five times.")
......@@ -28,7 +28,7 @@ class MockPlayer(Player):
def count(self, msg: MessageToPlayer) -> int:
return len([m for (m, e) in self.messages if m is msg])
def announce(self, current_bet: Optional[Hand]) -> Announce:
async def announce(self, current_bet: Optional[Hand]) -> Announce:
return Announce(bet=self.bets.pop() if self.bets else CARRE_ACE)
def receive(self, message: MessageToPlayer, extra: Optional[Any] = None):
......@@ -62,15 +62,15 @@ class TestManager(TestCase):
deck=Deck(cards=cards),
manager=self.manager)
def test_turn_messages(self):
self.game.new_turn()
async def test_turn_messages(self):
await self.game.new_turn()
self.assertGot(self.j1, MessageToPlayer.LoseRound, extra=self.j1.name, msg=f"J1 should hear round lost.")
self.assertGot(self.j2, MessageToPlayer.LoseRound, extra=self.j1.name, msg=f"J2 should hear round lost by j1.")
for player in [self.j1, self.j2]:
self.assertGot(player, MessageToPlayer.LoseRound, msg="End of round not announced")
def test_game_messages(self):
self.game.new_game()
async def test_game_messages(self):
await self.game.new_game()
self.assertEqual(len([m for m in self.j1.messages if m is MessageToPlayer.LoseRound]), 5,
f"{self.j1} should lose 5 rounds: {'|'.join([str(m) for m in self.j1.messages])}")
......
from time import sleep
from typing import Optional
import socketio
from server.game.lobby import LobbyManager
from server.game.message import MessageToPlayer
from server.model.hand import Hand
from server.model.players import Player, Announce
......@@ -21,6 +23,10 @@ class ClientPlayer(Player):
self.ready = False
async def announce(self, current_bet: Optional[Hand]) -> Announce:
announce = None
while not self.lobby.metadata[self.name].fresh_announce:
lobby.send_waiting_for(self)
sleep(2)
return lobby.last_announces[self]
......@@ -47,3 +53,4 @@ async def ping_server(sid, data):
@sio.event
def disconnect(sid):
print('[WS] Disconnect ', sid)
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