feat(messages): Manager messages to player

parent b8cdc814
from http.client import HTTPException
import socketio import socketio
from fastapi import FastAPI, APIRouter from fastapi import FastAPI, APIRouter
from fastapi.exceptions import RequestValidationError from fastapi.exceptions import RequestValidationError
from starlette.exceptions import HTTPException from starlette.exceptions import HTTPException
from starlette.responses import PlainTextResponse from starlette.responses import PlainTextResponse
from server.game.lobby import LobbyManager
from server.model.data import Game
# Game state # Game state
from server.model.players import RandomPlayer
from server.ws import sio from server.ws import sio
# Server # Server
app = FastAPI() app = FastAPI()
router = APIRouter() router = APIRouter()
...@@ -39,15 +33,6 @@ async def hello_world(): ...@@ -39,15 +33,6 @@ async def hello_world():
return "Hello, gentle[wo]man" return "Hello, gentle[wo]man"
@router.post("/join")
async def join(player_name: str):
if game.add_player(RandomPlayer(player_name) # TODO: Let user play
):
return "Welcome %s, %i players currently waiting!" % (player_name, len(game.players))
else:
raise HTTPException(status_code=403, detail=f"{player_name} already connected, choose another name.")
app.include_router(router) app.include_router(router)
......
...@@ -5,7 +5,7 @@ from socketio import AsyncServer ...@@ -5,7 +5,7 @@ from socketio import AsyncServer
from server.game.manager import ClientManager from server.game.manager import ClientManager
from server.game.message import MessageToPlayer from server.game.message import MessageToPlayer
from server.model.data import Game from server.model.game import Game
from server.model.players import Player, Announce from server.model.players import Player, Announce
...@@ -38,6 +38,7 @@ class LobbyManager(ClientManager): ...@@ -38,6 +38,7 @@ class LobbyManager(ClientManager):
print(f"{player} ready to play! ({self.nb_ready} people ready)") print(f"{player} ready to play! ({self.nb_ready} people ready)")
def announces(self, player: Player, announce: Announce): def announces(self, player: Player, announce: Announce):
# FIXME: Call this message on incoming "ANNOUNCE"
self.last_announces[player] = announce self.last_announces[player] = announce
print(f"{player} ready to play! ({self.nb_ready} people ready)") print(f"{player} ready to play! ({self.nb_ready} people ready)")
......
...@@ -9,6 +9,7 @@ class ClientManager(ABC): ...@@ -9,6 +9,7 @@ class ClientManager(ABC):
""" """
A listener of game state that notifies clients. A listener of game state that notifies clients.
""" """
def __init__(self): def __init__(self):
self.players: List[Player] = [] self.players: List[Player] = []
...@@ -18,5 +19,8 @@ class ClientManager(ABC): ...@@ -18,5 +19,8 @@ class ClientManager(ABC):
self.send(p, message) self.send(p, message)
@abstractmethod @abstractmethod
def send(self, to: Player, message: MessageToPlayer, extra=None): def send(self,
to: Player,
message: MessageToPlayer,
extra=None):
raise NotImplementedError("Send a message to clients ") raise NotImplementedError("Send a message to clients ")
...@@ -4,9 +4,13 @@ from enum import Enum ...@@ -4,9 +4,13 @@ from enum import Enum
class MessageToPlayer(Enum): class MessageToPlayer(Enum):
Waiting = "WAITING_ROOM" Waiting = "WAITING_ROOM"
Ready = "READY_ROOM" Ready = "READY_ROOM"
GiveHand = "GIVE_HAND"
WaitTurn = "WAITING_TURN" WaitTurn = "WAITING_TURN"
YourTurn = "YOUR_TURN" YourTurn = "YOUR_TURN"
Announce = "ANNOUNCE"
LoseRound = "LOSE_ROUND"
Win = "WINNER" Win = "WINNER"
WinnerIs = "WINNER_IS"
Lose = "LOSER" Lose = "LOSER"
......
from random import randrange, shuffle from random import randrange, shuffle
from typing import List
from server.model.card import Card, Value, Color from server.model.card import Card, Value, Color
class Deck: class Deck:
def __init__(self): def __init__(self, cards=None):
self.cards = [Card(v, c) for v in Value for c in Color] if cards is None:
cards = [Card(v, c) for v in Value for c in Color]
else:
print("Deck init with cards:", cards, len(cards))
self.cards = cards
self.defausse = [] self.defausse = []
def __len__(self): def __len__(self):
......
...@@ -3,10 +3,11 @@ from collections import defaultdict ...@@ -3,10 +3,11 @@ from collections import defaultdict
from typing import List, Dict, Optional from typing import List, Dict, Optional
from server.game.manager import ClientManager from server.game.manager import ClientManager
from server.model.card import Card from server.game.message import MessageToPlayer
from server.model.card import Card, Value
from server.model.deck import Deck from server.model.deck import Deck
from server.model.hand import Hand from server.model.hand import Hand
from server.model.players import Player from server.model.players import Player, Announce
class Game: class Game:
...@@ -22,6 +23,17 @@ class Game: ...@@ -22,6 +23,17 @@ class Game:
self.players: List[Player] = players self.players: List[Player] = players
self.defeats: Dict[Player, int] = defaultdict(lambda: 0) self.defeats: Dict[Player, int] = defaultdict(lambda: 0)
self.current_bet: Optional[Hand] = None self.current_bet: Optional[Hand] = None
self.manager = manager
def message(self, message: MessageToPlayer,
*to: Player,
extra=None
) -> None:
if self.manager:
if not to:
to = self.players
for player in to:
self.manager.send(player, message, extra)
@property @property
def global_hand(self) -> Hand: def global_hand(self) -> Hand:
...@@ -35,13 +47,14 @@ class Game: ...@@ -35,13 +47,14 @@ class Game:
if self.defeats[loser] == 5: if self.defeats[loser] == 5:
print(f"{loser} is eliminated!") print(f"{loser} is eliminated!")
self.message(MessageToPlayer.Lose, loser)
self.players.remove(loser) self.players.remove(loser)
else: else:
print(f"{loser} lost the round, now playing with {self.defeats[loser] + 1} cards!") print(f"{loser} lost the round, now playing with {self.defeats[loser] + 1} cards!")
self.players.remove(loser)
self.players.insert(0, loser)
winner = self.players[0] winner = self.players[0]
self.message(MessageToPlayer.Win, winner)
self.message(MessageToPlayer.WinnerIs, extra=winner)
print(f"Game over - {winner.name} wins with {len(winner.hand)} cards!") print(f"Game over - {winner.name} wins with {len(winner.hand)} cards!")
def new_turn(self) -> Player: def new_turn(self) -> Player:
...@@ -52,11 +65,19 @@ class Game: ...@@ -52,11 +65,19 @@ class Game:
""" """
# Distribution # Distribution
self.deck.reset() self.deck.reset()
self.message(MessageToPlayer.WaitTurn)
for current_player in self.players: for current_player in self.players:
current_player.clear() current_player.clear()
for i in range(self.defeats[current_player] + 1): count_player_cards = self.defeats[current_player] + 1
current_player.give(self.deck.random_card()) print(f"Drawing {count_player_cards} card(s) for {current_player}: ", end="")
print(f"Drew {current_player.hand} for {current_player}.") for i in range(count_player_cards):
card = self.deck.random_card()
current_player.give(card)
print(f"{card}")
self.message(MessageToPlayer.GiveHand, current_player, extra=current_player.hand)
print(f"Cards sent.")
# Tour # Tour
self.current_bet = None self.current_bet = None
...@@ -64,6 +85,9 @@ class Game: ...@@ -64,6 +85,9 @@ class Game:
for current_player in itertools.cycle(self.players): for current_player in itertools.cycle(self.players):
loser = self.play_turn(current_player, last_player) loser = self.play_turn(current_player, last_player)
if loser is not None: if loser is not None:
self.players.remove(loser)
self.players.insert(0, loser)
self.message(MessageToPlayer.LoseRound, loser)
return loser return loser
last_player = current_player last_player = current_player
...@@ -117,14 +141,26 @@ class Game: ...@@ -117,14 +141,26 @@ class Game:
print(f"| {current_player}'s turn >") print(f"| {current_player}'s turn >")
if not self.current_bet: # First player, has to bet something if not self.current_bet: # First player, has to bet something
self.message(MessageToPlayer.YourTurn, current_player, extra=self.current_bet)
while not self.current_bet: # Ask a valid 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 = current_player.announce(self.current_bet)
if announce.bet: if announce.bet:
self.current_bet = announce.bet self.current_bet = announce.bet
print(f"{current_player} starts the round: {self.current_bet}") print(f"{current_player} starts the round: {self.current_bet}")
self.message(MessageToPlayer.Announce, extra={"player": current_player, "announce": announce})
else:
print(f"You cannot say Menteur on first round, {current_player}!")
else: # Next player, announce or menteur else: # Next player, announce or menteur
announce = current_player.announce(self.current_bet)
# Wait, is the announce the last possible one?
if len(self.current_bet.cards) == 4 and all([c.value is Value.Ace for c in self.current_bet.cards]):
print("CARRE D'AS!")
announce = Announce() # MENTEUR obligatoire
else:
self.message(MessageToPlayer.YourTurn, current_player, extra=self.current_bet)
announce = current_player.announce(self.current_bet)
if announce.bet: if announce.bet:
while announce.bet.value() < self.current_bet.value(): # Bad announce! while announce.bet.value() < self.current_bet.value(): # Bad announce!
...@@ -134,6 +170,7 @@ class Game: ...@@ -134,6 +170,7 @@ class Game:
# Valid bet: # Valid bet:
print(f" {current_player} bets {self.current_bet}.") print(f" {current_player} bets {self.current_bet}.")
self.message(MessageToPlayer.Announce, extra={"player": current_player, "announce": announce})
else: # Menteur! Who lost the round? else: # Menteur! Who lost the round?
menteur = self.is_menteur(self.current_bet) menteur = self.is_menteur(self.current_bet)
......
...@@ -19,7 +19,7 @@ def full(value_aux: Value, ...@@ -19,7 +19,7 @@ def full(value_aux: Value,
return Hand([ return Hand([
Card(value_aux, Color.Hearts), Card(value_aux, Color.Hearts),
Card(value_aux, Color.Clubs), Card(value_aux, Color.Clubs),
Card(value_aux, Color.Spades), Card(value_aux, Color.Diamonds),
Card(value_par, Color.Hearts), Card(value_par, Color.Hearts),
Card(value_par, Color.Clubs) Card(value_par, Color.Clubs)
]) ])
......
fastapi==0.53.2 fastapi==0.53.2
uvicorn==0.11.3 uvicorn==0.11.3
python-socketio==4.5.1
from unittest import TestCase from unittest import TestCase
from server.model.data import Game from server.model.game import Game
from server.model.players import MenteurPlayer, NaivePlayer, RandomPlayer from server.model.players import MenteurPlayer, NaivePlayer, RandomPlayer
...@@ -38,8 +38,8 @@ class TestGame(TestCase): ...@@ -38,8 +38,8 @@ class TestGame(TestCase):
self.assertEqual(1, len([v for v in self.game.defeats.values() if v > 0]), "There should have been one defeat.") 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.") self.assertTrue(1 in self.game.defeats.values(), "A player should have one defeat.")
loser = [p for p in self.game.players if self.game.defeats[p]][0]
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.") self.assertEqual(self.game.players[0], loser, "The loser should be first to play.")
def test_full_game(self): def test_full_game(self):
......
from typing import Optional, List
from unittest import TestCase
from server.game.manager import ClientManager
from server.game.message import MessageToPlayer
from server.model.card import Value
from server.model.deck import Deck
from server.model.game import Game
from server.model.hand import Hand
from server.model.hands import pair, brelan
from server.model.known import CARRE_ACE
from server.model.players import Announce, Player
class MockPlayer(Player):
def __init__(self,
name: str = None,
bets: List[Optional[Hand]] = None
):
super().__init__(name)
if bets is None:
bets = []
self.bets: List[Optional[Hand]] = bets
self.messages: List[MessageToPlayer] = []
def count(self, msg: MessageToPlayer) -> int:
return len([m for m 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)
def receive(self, message: MessageToPlayer):
self.messages.append(message)
def print_msgs(self) -> str:
'|'.join([str(m) for m in self.messages])
class MockManager(ClientManager):
def __init__(self, players: List[MockPlayer]):
super().__init__()
self.players = players
def send(self, to: Player, message: MessageToPlayer, extra=None):
if isinstance(to, MockPlayer):
to.receive(message)
print(f"Sent {message} to {to}.")
class TestManager(TestCase):
def setUp(self) -> None:
self.j1 = MockPlayer("j1", [pair(Value.Ace)])
self.j2 = MockPlayer("j2", [brelan(Value.Two)])
self.manager = MockManager([self.j1, self.j2])
cards = []
cards.extend(self.j1.bets[0].cards)
cards.extend(self.j2.bets[0].cards)
self.game = Game(players=[self.j1, self.j2],
deck=Deck(cards=cards),
manager=self.manager)
def test_turn_messages(self):
self.game.new_turn()
self.assertEqual(self.j1.count(MessageToPlayer.LoseRound), 6, f"j1 should lose 6 rounds.")
self.assertEqual(self.j2.count(MessageToPlayer.LoseRound), 6, f"j2 should lose 6 rounds.")
for player in [self.j1, self.j2]:
self.assertIn(MessageToPlayer.Lose, player.messages, "Loser not announced")
def test_game_messages(self):
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])}")
self.assertEqual(self.j1.count(MessageToPlayer.YourTurn), 6,
f"{self.j1} should play 6 rounds: {self.j1.print_msgs()}")
self.assertEqual(self.j2.count(MessageToPlayer.YourTurn), 1,
f"{self.j2} should play 1 rounds: {self.j2.print_msgs()}")
self.assertEqual(len([m for m in self.j2.messages if m is MessageToPlayer.LoseRound]), 0,
f"{self.j2} should lose 0 rounds: {'|'.join([str(m) for m in self.j2.messages])}")
for player in [self.j1, self.j2]:
self.assertEqual(player.count(MessageToPlayer.Announce), 7,
f"{player} should see 7 announces: {player.print_msgs()}")
self.assertIn(MessageToPlayer.WinnerIs, player.messages, "Winner not announced")
self.assertIn(MessageToPlayer.Win, player.messages, "Win not told")
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