diff --git a/experimental/2nub/src/client/App.tsx b/experimental/2nub/src/client/App.tsx index 93d7986..970891b 100644 --- a/experimental/2nub/src/client/App.tsx +++ b/experimental/2nub/src/client/App.tsx @@ -78,11 +78,14 @@ const App: React.FC = () => { return { ...prevGame, - players: prevGame.players.map(player => - player.id === currentPlayerId - ? { ...player, connected: false } - : player - ) + state: { + ...prevGame.state, + players: prevGame.state.players.map(player => + player.id === currentPlayerId + ? { ...player, connected: false } + : player + ) + } }; }); } diff --git a/experimental/2nub/src/client/components/GameBoard.tsx b/experimental/2nub/src/client/components/GameBoard.tsx index 5a257ed..003c23e 100644 --- a/experimental/2nub/src/client/components/GameBoard.tsx +++ b/experimental/2nub/src/client/components/GameBoard.tsx @@ -52,8 +52,8 @@ export const GameBoard: React.FC = ({ game, gameId, currentPlaye } console.debug("Game:", game); - const currentPlayer = currentPlayerId ? game.players.find(p => p.id === currentPlayerId) : null; - const playerList = game.players; + const currentPlayer = currentPlayerId ? game.state.players.find(p => p.id === currentPlayerId) : null; + const playerList = game.state.players; const isCurrentPlayerDisconnected = currentPlayer && !currentPlayer.connected; return ( @@ -63,7 +63,7 @@ export const GameBoard: React.FC = ({ game, gameId, currentPlaye

{game.name}

Game ID: {gameId} - Players: {game.players.length} + Players: {game.state.players.length} {game.state.state === 'lobby' ? 'Waiting' : game.state.state} @@ -89,7 +89,7 @@ export const GameBoard: React.FC = ({ game, gameId, currentPlaye

You are:

- {currentPlayer.name} (Seat {game.players.indexOf(currentPlayer) + 1}) + {currentPlayer.name} (Seat {game.state.players.indexOf(currentPlayer) + 1})

)} @@ -107,7 +107,7 @@ export const GameBoard: React.FC = ({ game, gameId, currentPlaye {player.name} {player.id === currentPlayerId && '(You)'}
- Seat {game.players.indexOf(player) + 1} + Seat {game.state.players.indexOf(player) + 1}
diff --git a/experimental/2nub/src/client/components/GameList.tsx b/experimental/2nub/src/client/components/GameList.tsx index 5f858d4..e2f25fc 100644 --- a/experimental/2nub/src/client/components/GameList.tsx +++ b/experimental/2nub/src/client/components/GameList.tsx @@ -104,20 +104,20 @@ export const GameList: React.FC = ({ games, onJoinGame, onRejoinG ID: {game.id} - Players: {game.players.length} + Players: {game.state.players.length} {getStatusDisplayName(game.state.state)}
- {game.players.length > 0 && ( + {game.state.players.length > 0 && (
Players:
- {game.players.map((player) => ( + {game.state.players.map((player) => ( - {player.name} (Seat {game.players.indexOf(player) + 1}) + {player.name} (Seat {game.state.players.indexOf(player) + 1}) ))}
@@ -129,7 +129,7 @@ export const GameList: React.FC = ({ games, onJoinGame, onRejoinG {joiningGameId === game.id ? (
{(() => { - const disconnectedPlayers = game.players.filter(p => !p.connected); + const disconnectedPlayers = game.state.players.filter(p => !p.connected); return (
handleJoinSubmit(e, game.id)} className="form"> {disconnectedPlayers.length > 0 && ( @@ -148,7 +148,7 @@ export const GameList: React.FC = ({ games, onJoinGame, onRejoinG onChange={() => handleExistingPlayerSelect(player.id)} /> - {player.name} (Seat {game.players.indexOf(player) + 1}) + {player.name} (Seat {game.state.players.indexOf(player) + 1}) ))} diff --git a/experimental/2nub/src/client/components/Onub.tsx b/experimental/2nub/src/client/components/Onub.tsx index 9662cb3..b2d6e43 100644 --- a/experimental/2nub/src/client/components/Onub.tsx +++ b/experimental/2nub/src/client/components/Onub.tsx @@ -28,7 +28,7 @@ export const Onub: React.FC = ({ game, currentPlayerId }) => { switch (game.state.state) { case 'lobby': - content = ; + content = ; break; case 'roleAssignment': content = ; diff --git a/experimental/2nub/src/client/components/OnubNight.tsx b/experimental/2nub/src/client/components/OnubNight.tsx index d76c492..6bac3d6 100644 --- a/experimental/2nub/src/client/components/OnubNight.tsx +++ b/experimental/2nub/src/client/components/OnubNight.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { GameStateClient, StateNight } from '../../types'; +import { GameStateClient, Player, StateNight } from '../../types'; import '../styles/main.scss'; interface OnubNightProps { @@ -10,6 +10,39 @@ interface OnubNightProps { export const OnubNight: React.FC = ({ stateNight, currentPlayerId }) => { const { turn } = stateNight; + const { originalRoleId } = stateNight.playerData[currentPlayerId]; + + const handleEndTurn = () => { + throw new Error('Not implemented yet'); + }; + + let content =
Unknown state
; + switch (originalRoleId) { + case 'werewolf': + function playerIdToName(id: Player["id"]) { + const player = stateNight.players.find(p => p.id === id); + return player ? player.name : 'Unknown Player'; + } + content =
+

The Werewolves on your team are:

+
    + {Object.entries(stateNight.playerData) + .filter(([playerId, playerState]) => playerState.originalRoleId === 'werewolf') + .map(([playerId, playerState]) =>
  • + {playerIdToName(playerId)} {playerId === currentPlayerId ? '(You)' : ''} +
  • ) + } +
+ +
; + break; + default: + content =
+

Your role does not have a night action.

+ +
+ } + return

Night

{!stateNight.playerIdsByWakeupOrder[turn].includes(currentPlayerId) ?

@@ -18,6 +51,7 @@ export const OnubNight: React.FC = ({ stateNight, currentPlayerI

It's your turn to wake up!

+ {content} }
; } \ No newline at end of file diff --git a/experimental/2nub/src/server/gameState.ts b/experimental/2nub/src/server/gameState.ts index c8604cf..dd33a8f 100644 --- a/experimental/2nub/src/server/gameState.ts +++ b/experimental/2nub/src/server/gameState.ts @@ -80,7 +80,6 @@ class GameStateManager { const game: GameState = { id, name, - players: [], gameLog: [], state: { state: 'lobby', @@ -93,6 +92,7 @@ class GameStateManager { }, }, }, + players: [], }, createdAt: new Date(), }; @@ -125,7 +125,7 @@ class GameStateManager { connected: true }; - game.players.push(player); + game.state.players.push(player); return player; } @@ -134,12 +134,12 @@ class GameStateManager { const game = this.games.get(gameId); if (!game) return false; - const playerIndex = game.players.findIndex(p => p.id === playerId); + const playerIndex = game.state.players.findIndex(p => p.id === playerId); if (playerIndex === -1) return false; - game.players.splice(playerIndex, 1); + game.state.players.splice(playerIndex, 1); - if (game.players.length === 0) { + if (game.state.players.length === 0) { this.games.delete(gameId); } @@ -150,7 +150,7 @@ class GameStateManager { const game = this.games.get(gameId); if (!game) return false; - const player = game.players.find(p => p.id === playerId); + const player = game.state.players.find(p => p.id === playerId); if (!player) return false; player.connected = connected; @@ -165,7 +165,7 @@ class GameStateManager { return null; } - const player = game.players.find(p => p.id === playerId); + const player = game.state.players.find(p => p.id === playerId); if (!player) { console.debug(`Rejoin failed: player ${playerId} not found in game ${gameId}`); return null; @@ -192,7 +192,7 @@ class GameStateManager { const game = this.games.get(gameId); if (!game) return false; - const player = game.players.find(p => p.id === playerId); + const player = game.state.players.find(p => p.id === playerId); if (!player) return false; // Remove any existing socket for this player @@ -257,7 +257,7 @@ class GameStateManager { // Only allow starting the game if invariants hold if (game.state.state !== 'lobby') return null; - else if (game.players.length + 3 !== game.state.ruleset.roleOrder.length) return null; + else if (game.state.players.length + 3 !== game.state.ruleset.roleOrder.length) return null; // There must be either 0 or 2 "50/50 duo cop" roles const count5050DuoCop = game.state.ruleset.roleOrder.filter(roleId => roleId === '50/50 duo cop').length; @@ -279,12 +279,12 @@ class GameStateManager { if (game.state.ruleset.special.maybeAllTanners.enabled) { if (flip(game.state.ruleset.special.maybeAllTanners.probability)) { // Set all players to Tanner - const playerIdsByWakeupOrder = shuffle(game.players.map(p => [p.id])); + const playerIdsByWakeupOrder = shuffle(game.state.players.map(p => [p.id])); game.state = { state: 'roleAssignment', playerIdsByWakeupOrder, playerData: Object.fromEntries( - game.players.map(player => [player.id, { + game.state.players.map(player => [player.id, { originalRoleId: 'tanner', currentRoleId: 'tanner', playerLog: [], @@ -293,8 +293,9 @@ class GameStateManager { centerCards: ['tanner', 'tanner', 'tanner'], ruleset: { ...game.state.ruleset }, playerConfirmations: Object.fromEntries( - game.players.map(player => [player.id, false]) + game.state.players.map(player => [player.id, false]) ), + players: [...game.state.players], }; return game; } @@ -320,7 +321,7 @@ class GameStateManager { const centerCards = shuffle(unshuffledCenterCards); const playerIdsByWakeupOrder: Player["id"][][] = []; - const playerIds = shuffle(game.players.map(p => p.id)); + const playerIds = shuffle(game.state.players.map(p => p.id)); const playerData: StateNight["playerData"] = Object.fromEntries( playerIds.map((id, index) => { if (index > 0 @@ -347,8 +348,9 @@ class GameStateManager { centerCards, ruleset: { ...game.state.ruleset }, playerConfirmations: Object.fromEntries( - game.players.map(player => [player.id, false]) + game.state.players.map(player => [player.id, false]) ), + players: [...game.state.players], } return game; @@ -377,6 +379,7 @@ class GameStateManager { centerCards: game.state.centerCards, ruleset: game.state.ruleset, turn: 0, + players: [...game.state.players], }; } diff --git a/experimental/2nub/src/types.ts b/experimental/2nub/src/types.ts index 819a22e..2bb8f01 100644 --- a/experimental/2nub/src/types.ts +++ b/experimental/2nub/src/types.ts @@ -42,6 +42,7 @@ export interface StateLobby { roleOrder: RoleId[]; special: SpecialRules; } + players: Player[]; }; export interface StateRoleAssignment { @@ -52,6 +53,7 @@ export interface StateRoleAssignment { centerCards: RoleId[]; ruleset: StateLobby["ruleset"]; playerConfirmations: Record; + players: StateLobby["players"]; } export interface StateNight { @@ -61,6 +63,7 @@ export interface StateNight { centerCards: StateRoleAssignment["centerCards"]; ruleset: StateRoleAssignment["ruleset"]; turn: number; + players: StateRoleAssignment["players"]; } export interface StateDay { @@ -68,6 +71,7 @@ export interface StateDay { playerData: StateNight["playerData"]; centerCards: StateNight["centerCards"]; ruleset: StateNight["ruleset"]; + players: StateNight["players"]; } export interface StateVoting { @@ -76,6 +80,7 @@ export interface StateVoting { playerData: StateDay["playerData"]; centerCards: StateDay["centerCards"]; ruleset: StateDay["ruleset"]; + players: StateDay["players"]; } export interface StateFinished { @@ -85,6 +90,7 @@ export interface StateFinished { playerData: StateVoting["playerData"]; centerCards: StateVoting["centerCards"]; ruleset: StateVoting["ruleset"]; + players: StateVoting["players"]; } // Client-side game state without ID (pure game data) @@ -95,7 +101,6 @@ export type GameStateClient = { name: string; gameLog: string[]; - players: Player[]; state: StateLobby | StateRoleAssignment | StateNight | StateDay | StateVoting | StateFinished; }