feat(messages): Manager messages to player

parent b8cdc814
from http.client import HTTPException
import socketio
from fastapi import FastAPI, APIRouter
from fastapi.exceptions import RequestValidationError
from starlette.exceptions import HTTPException
from starlette.responses import PlainTextResponse
from server.game.lobby import LobbyManager
from server.model.data import Game
# Game state
from server.model.players import RandomPlayer
from server.ws import sio
# Server
app = FastAPI()
router = APIRouter()
......@@ -39,15 +33,6 @@ async def hello_world():
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)
......
......@@ -5,7 +5,7 @@ from socketio import AsyncServer
from server.game.manager import ClientManager
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
......@@ -38,6 +38,7 @@ class LobbyManager(ClientManager):
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
print(f"{player} ready to play! ({self.nb_ready} people ready)")
......
......@@ -9,6 +9,7 @@ class ClientManager(ABC):
"""
A listener of game state that notifies clients.
"""
def __init__(self):
self.players: List[Player] = []
......@@ -18,5 +19,8 @@ class ClientManager(ABC):
self.send(p, message)
@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 ")
......@@ -4,9 +4,13 @@ from enum import Enum
class MessageToPlayer(Enum):
Waiting = "WAITING_ROOM"
Ready = "READY_ROOM"
GiveHand = "GIVE_HAND"
WaitTurn = "WAITING_TURN"
YourTurn = "YOUR_TURN"
Announce = "ANNOUNCE"
LoseRound = "LOSE_ROUND"
Win = "WINNER"
WinnerIs = "WINNER_IS"
Lose = "LOSER"
......
from random import randrange, shuffle
from typing import List
from server.model.card import Card, Value, Color
class Deck:
def __init__(self):
self.cards = [Card(v, c) for v in Value for c in Color]
def __init__(self, cards=None):
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 = []
def __len__(self):
......
......@@ -3,10 +3,11 @@ from collections import defaultdict
from typing import List, Dict, Optional
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.hand import Hand
from server.model.players import Player
from server.model.players import Player, Announce
class Game:
......@@ -22,6 +23,17 @@ class Game:
self.players: List[Player] = players
self.defeats: Dict[Player, int] = defaultdict(lambda: 0)
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
def global_hand(self) -> Hand:
......@@ -35,13 +47,14 @@ class Game:
if self.defeats[loser] == 5:
print(f"{loser} is eliminated!")
self.message(MessageToPlayer.Lose, loser)
self.players.remove(loser)
else:
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]
self.message(MessageToPlayer.Win, winner)
self.message(MessageToPlayer.WinnerIs, extra=winner)
print(f"Game over - {winner.name} wins with {len(winner.hand)} cards!")
def new_turn(self) -> Player:
......@@ -52,11 +65,19 @@ class Game:
"""
# Distribution
self.deck.reset()
self.message(MessageToPlayer.WaitTurn)
for current_player in self.players:
current_player.clear()
for i in range(self.defeats[current_player] + 1):
current_player.give(self.deck.random_card())
print(f"Drew {current_player.hand} for {current_player}.")
count_player_cards = self.defeats[current_player] + 1
print(f"Drawing {count_player_cards} card(s) for {current_player}: ", end="")
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
self.current_bet = None
......@@ -64,6 +85,9 @@ class Game:
for current_player in itertools.cycle(self.players):
loser = self.play_turn(current_player, last_player)
if loser is not None:
self.players.remove(loser)
self.players.insert(0, loser)
self.message(MessageToPlayer.LoseRound, loser)
return loser
last_player = current_player
......@@ -117,13 +141,25 @@ class Game:
print(f"| {current_player}'s turn >")
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
# FIXME: Wait for player announce? Maybe just sleep 10?
announce = current_player.announce(self.current_bet)
if announce.bet:
self.current_bet = announce.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
# 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:
......@@ -134,6 +170,7 @@ class Game:
# Valid 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?
menteur = self.is_menteur(self.current_bet)
......
......@@ -19,7 +19,7 @@ def full(value_aux: Value,
return Hand([
Card(value_aux, Color.Hearts),
Card(value_aux, Color.Clubs),
Card(value_aux, Color.Spades),
Card(value_aux, Color.Diamonds),
Card(value_par, Color.Hearts),
Card(value_par, Color.Clubs)
])
......
fastapi==0.53.2
uvicorn==0.11.3
python-socketio==4.5.1
from unittest import TestCase
from server.model.data import Game
from server.model.game import Game
from server.model.players import MenteurPlayer, NaivePlayer, RandomPlayer
......@@ -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.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.")
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