diff --git a/experimental/2nub/src/client/components/Onub.tsx b/experimental/2nub/src/client/components/Onub.tsx index 76d8b7c..1110f7c 100644 --- a/experimental/2nub/src/client/components/Onub.tsx +++ b/experimental/2nub/src/client/components/Onub.tsx @@ -1,5 +1,6 @@ import React, { useState } from 'react'; import { GameStateClient } from '../../types'; +import { OnubLobby } from './OnubLobby'; interface OnubProps { game: GameStateClient; @@ -7,32 +8,42 @@ interface OnubProps { } export const Onub: React.FC = ({ game, currentPlayerId }) => { + let content =
+

+ This is where the actual game would be implemented. The boilerplate provides: +

+ +
+ Ready for your game logic! Add your game mechanics, rules, and UI components here. +
+
; + + switch (game.state.state) { + case 'lobby': + content = ; + default: + //throw new Error(`Onub component: Unsupported game state "${game.state.state}"`); + } + return (

Game Area

-

- This is where the actual game would be implemented. The boilerplate provides: -

-
    -
  • Real-time player connection status
  • -
  • Game state synchronization via WebSockets
  • -
  • CRUD operations for games and players
  • -
  • Clean separation of server and client logic
  • -
-
- Ready for your game logic! Add your game mechanics, rules, and UI components here. -
+ {content}
); } \ No newline at end of file diff --git a/experimental/2nub/src/client/components/OnubLobby.tsx b/experimental/2nub/src/client/components/OnubLobby.tsx new file mode 100644 index 0000000..0188444 --- /dev/null +++ b/experimental/2nub/src/client/components/OnubLobby.tsx @@ -0,0 +1,125 @@ +import React, { useState } from 'react'; +import { GameStateClient, RoleId, ROLES, StateLobby } from '../../types'; + +interface OnubLobbyProps { + stateLobby: StateLobby; + playerCount: number; + currentPlayerId: string; +} + +export const OnubLobby: React.FC = ({ stateLobby, playerCount, currentPlayerId }) => { + console.debug("Player count: ", playerCount); + + function makeRoleAddHandler(roleId: RoleId): React.MouseEventHandler { + return (e: React.MouseEvent) => { + e.preventDefault(); + //setRoles(roles => [...roles, roleId]); + } + } + + function makeRoleMoveUpHandler(index: number): React.MouseEventHandler { + return (e: React.MouseEvent) => { + e.preventDefault(); + if (index <= 0) return; + //setRoles(roles => { + // const newRoles = [...roles]; + // [newRoles[index - 1], newRoles[index]] = [newRoles[index], newRoles[index - 1]]; + // return newRoles; + //}); + } + } + + function makeRoleMoveDownHandler(index: number): React.MouseEventHandler { + return (e: React.MouseEvent) => { + e.preventDefault(); + //setRoles(roles => { + // if (index >= roles.length - 1) return roles; + // const newRoles = [...roles]; + // [newRoles[index + 1], newRoles[index]] = [newRoles[index], newRoles[index + 1]]; + // return newRoles; + //}); + } + } + + function makeRoleDeleteHandler(index: number): React.MouseEventHandler { + return (e: React.MouseEvent) => { + e.preventDefault(); + //setRoles(roles => { + // const newRoles = [...roles]; + // newRoles.splice(index, 1); + // return newRoles; + //}); + } + } + + function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + throw new Error('Not implemented: handleSubmit in OnubLobby'); + } + + return
+

Lobby

+

+ Waiting for players to join... +

+
+

Roles Available to be Added

+
    + {ROLES.map(roleId => ( +
  • + {roleId}{" "} + +
  • + ))} +
+

Order of Roles

+
    + {stateLobby.ruleset.roleOrder.map((roleId, index) => ( +
  1. + + + + {roleId} +
  2. + ))} +
+

Special Rules

+ +

Start Game

+ +
+
; +} \ No newline at end of file diff --git a/experimental/2nub/src/client/hooks/useWebSocket.ts b/experimental/2nub/src/client/hooks/useWebSocket.ts index 12bb5e1..d667260 100644 --- a/experimental/2nub/src/client/hooks/useWebSocket.ts +++ b/experimental/2nub/src/client/hooks/useWebSocket.ts @@ -1,6 +1,7 @@ import { useState, useEffect, useRef, useCallback } from 'react'; import { io, Socket } from 'socket.io-client'; import { API_BASE_URL } from '../config/api'; +import { StateLobby } from '../../types'; interface UseWebSocketOptions { onConnect: () => void; @@ -16,6 +17,7 @@ interface UseWebSocketReturn { connect: () => void; disconnect: () => void; authenticatePlayer: (gameId: string, playerId: string) => void; + updateRuleset: (ruleset: StateLobby["ruleset"]) => void; } export function useWebSocket(options: UseWebSocketOptions): UseWebSocketReturn { diff --git a/experimental/2nub/src/server/gameState.ts b/experimental/2nub/src/server/gameState.ts index 7ac3f0e..8ded7c6 100644 --- a/experimental/2nub/src/server/gameState.ts +++ b/experimental/2nub/src/server/gameState.ts @@ -17,9 +17,19 @@ class GameStateManager { id, name, players: [], - roleOrder: [], gameLog: [], - state: { state: 'lobby' } as StateLobby, + state: { + state: 'lobby', + ruleset: { + roleOrder: [], + special: { + maybeAllTanners: { + enabled: false, + probability: 0.05 + }, + }, + }, + }, createdAt: new Date(), lastActivity: new Date() }; diff --git a/experimental/2nub/src/types.ts b/experimental/2nub/src/types.ts index f024ff2..8e8b85d 100644 --- a/experimental/2nub/src/types.ts +++ b/experimental/2nub/src/types.ts @@ -1,4 +1,4 @@ -const ROLES = [ +export const ROLES = [ //"copycat", //"doppelganger", "werewolf", @@ -11,9 +11,17 @@ const ROLES = [ "hunter", "mason", "insomniac", + "50/50 duo cop" ] as const; export type RoleId = typeof ROLES[number]; +export type SpecialRules = { + maybeAllTanners: { + enabled: boolean; + probability: number; + }; +}; + export interface Player { id: string; name: string; @@ -28,18 +36,24 @@ export interface PlayerState { export interface StateLobby { state: 'lobby'; + ruleset: { + roleOrder: RoleId[]; + special: SpecialRules; + } }; export interface StateNight { state: 'night'; playerData: Record; centerCards: RoleId[]; + ruleset: StateLobby["ruleset"]; } export interface StateDay { state: 'day'; playerData: StateNight["playerData"]; centerCards: StateNight["centerCards"]; + ruleset: StateNight["ruleset"]; } export interface StateVoting { @@ -47,6 +61,7 @@ export interface StateVoting { votes: Record; playerData: StateDay["playerData"]; centerCards: StateDay["centerCards"]; + ruleset: StateDay["ruleset"]; } export interface StateFinished { @@ -55,6 +70,7 @@ export interface StateFinished { votes: StateVoting["votes"]; playerData: StateVoting["playerData"]; centerCards: StateVoting["centerCards"]; + ruleset: StateVoting["ruleset"]; } // Client-side game state without ID (pure game data) @@ -67,7 +83,6 @@ export type GameStateClient = { gameLog: string[]; players: Player[]; - roleOrder: RoleId[]; state: StateLobby | StateNight | StateDay | StateVoting | StateFinished; }