diff --git a/client/src/App.vue b/client/src/App.vue index dbcd62a..68a66b3 100644 --- a/client/src/App.vue +++ b/client/src/App.vue @@ -1,7 +1,17 @@ @@ -11,9 +21,8 @@ import Vuex from 'vuex' import 'es6-promise/auto' // Needed for Promise polyfill import VueSocketIO from 'vue-socket.io' - // import Game from "./components/Game"; import HelloWorld from "./components/HelloWorld"; - import ListenToSockets from "./Sockets"; + import Sockets from "./Sockets"; Vue.use(Vuex); @@ -54,7 +63,18 @@ name: "App", components: { HelloWorld, - ListenToSockets + Sockets + }, + data: function () { + return { + message: "WAITING" + } + }, + methods: { + sendMessage: function(message) { + console.log("User wants to send", message); + this.$socket.emit("message", message); + } } }; @@ -69,4 +89,8 @@ color: #2c3e50; margin-top: 60px; } + + #game { + margin-top: 20px; + } diff --git a/client/src/Sockets.vue b/client/src/Sockets.vue index 95e2d6c..676fbf3 100644 --- a/client/src/Sockets.vue +++ b/client/src/Sockets.vue @@ -44,10 +44,15 @@ pingServer() { // Send the "pingServer" event to the server. if (this.isConnected) { - this.$socket.emit('pingServer', 'PING!'); + this.sendMessage("Ping!"); console.log("Ping sent!"); } }, + sendMessage(message) { + this.$socket.emit('pingServer', message); + console.log("Message sent:", message); + + }, logOn() { this.$socket.onconnect(); }, diff --git a/client/src/components/Game.vue b/client/src/components/Game.vue index 3e5cae6..b27d35b 100644 --- a/client/src/components/Game.vue +++ b/client/src/components/Game.vue @@ -2,6 +2,7 @@

Game on!

{{ msg }}

+

Count: {{ count }}

diff --git a/server/app.py b/server/app.py index 966e34d..97d03ae 100644 --- a/server/app.py +++ b/server/app.py @@ -6,12 +6,12 @@ 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 -game = Game() # Server app = FastAPI() diff --git a/server/game/__init__.py b/server/game/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/server/game/__init__.py diff --git a/server/game/lobby.py b/server/game/lobby.py new file mode 100644 index 0000000..cde5843 --- /dev/null +++ b/server/game/lobby.py @@ -0,0 +1,52 @@ +from collections import defaultdict +from typing import List, Dict + +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.players import Player, Announce + + +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.games: List[Game] = [] + self.sio = sio + + @property + def nb_ready(self): + return len(self.players_ready) + + @property + def players_ready(self): + return [k for k, v in self.lobby.items() if v] + + async def add_player(self, player: Player, is_ready: bool = False) -> None: + self.lobby[player] = is_ready + print(f"Added {player} to a lobby with {len(self.lobby)} players.") + await self.sio.emit('messageChannel', "PONG") + + def wants_to_play(self, player: Player): + self.lobby[player] = True + print(f"{player} ready to play! ({self.nb_ready} people ready)") + + def announces(self, player: Player, announce: Announce): + self.last_announces[player] = 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)) + + 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 diff --git a/server/game/manager.py b/server/game/manager.py new file mode 100644 index 0000000..dff5c22 --- /dev/null +++ b/server/game/manager.py @@ -0,0 +1,22 @@ +from abc import ABC, abstractmethod +from typing import List, Optional + +from server.game.message import MessageToPlayer +from server.model.players import Player + + +class ClientManager(ABC): + """ + A listener of game state that notifies clients. + """ + def __init__(self): + self.players: List[Player] = [] + + def notify(self, message: MessageToPlayer, only_for: Optional[Player] = None) -> None: + to = [only_for] if only_for else self.players + for p in to: + self.send(p, message) + + @abstractmethod + def send(self, to: Player, message: MessageToPlayer, extra=None): + raise NotImplementedError("Send a message to clients ") diff --git a/server/game/message.py b/server/game/message.py new file mode 100644 index 0000000..544a263 --- /dev/null +++ b/server/game/message.py @@ -0,0 +1,17 @@ +from enum import Enum + + +class MessageToPlayer(Enum): + Waiting = "WAITING_ROOM" + Ready = "READY_ROOM" + WaitTurn = "WAITING_TURN" + YourTurn = "YOUR_TURN" + Win = "WINNER" + Lose = "LOSER" + + +class MessageFromPlayer(Enum): + Wating = "WAITING" + Ready = "READY" + Bet = "BET" + Menteur = "MENTEUR" diff --git a/server/model/data.py b/server/model/data.py index b19e9c3..8803794 100644 --- a/server/model/data.py +++ b/server/model/data.py @@ -2,6 +2,7 @@ import itertools from collections import defaultdict from typing import List, Dict, Optional +from server.game.manager import ClientManager from server.model.card import Card from server.model.deck import Deck from server.model.hand import Hand @@ -9,7 +10,11 @@ from server.model.players import Player class Game: - def __init__(self, players=None, deck: Deck = Deck()): + def __init__(self, + players=None, + deck: Deck = Deck(), + manager: Optional[ClientManager] = None + ): if players is None: players = [] diff --git a/server/ws.py b/server/ws.py index d284789..54643f1 100644 --- a/server/ws.py +++ b/server/ws.py @@ -1,21 +1,39 @@ +from typing import Optional + import socketio +from server.game.lobby import LobbyManager +from server.model.hand import Hand +from server.model.players import Player, Announce + sio = socketio.AsyncServer( async_mode='asgi', logger=True, cors_allowed_origins="*" # FIXME: CSRF Vulnerability ) +lobby = LobbyManager(sio) + + +class ClientPlayer(Player): + def __init__(self, lobby: LobbyManager): + super().__init__() + self.lobby = lobby + + async def announce(self, current_bet: Optional[Hand]) -> Announce: + return lobby.last_announces[self] @sio.event -def connect(sid, environ): +async def connect(sid, environ): print("[WS] Connect ", sid, environ) + player = ClientPlayer(lobby) + await lobby.add_player(player) @sio.event -async def chat_message(sid, data): +async def message(sid, data): print("[WS] Message ", data) - await sio.emit('reply', room=sid) + await sio.emit('messageChannel', "OK", room=sid) @sio.on("pingServer")