game workflow corrected
This commit is contained in:
@@ -47,7 +47,7 @@ The SerpentRace game system uses a **hybrid architecture**:
|
||||
|
||||
1. GAME CREATION (REST)
|
||||
│
|
||||
├─ POST /api/v1/game/start
|
||||
├─ POST /api/games/start
|
||||
│ ├─ Gamemaster creates game with deck selection
|
||||
│ ├─ Game Code generated (6 characters)
|
||||
│ └─ Game state: "waiting"
|
||||
@@ -56,7 +56,7 @@ The SerpentRace game system uses a **hybrid architecture**:
|
||||
|
||||
2. PLAYER JOINING (REST + WebSocket)
|
||||
│
|
||||
├─ POST /api/v1/game/join
|
||||
├─ POST /api/games/join
|
||||
│ ├─ Validate game code
|
||||
│ ├─ Check game type (PUBLIC/PRIVATE/ORGANIZATION)
|
||||
│ ├─ Add player to game.players[]
|
||||
@@ -75,7 +75,7 @@ The SerpentRace game system uses a **hybrid architecture**:
|
||||
|
||||
3. GAME START (REST)
|
||||
│
|
||||
├─ POST /api/v1/game/:gameId/start
|
||||
├─ POST /api/games/:gameId/start
|
||||
│ ├─ Only gamemaster can start
|
||||
│ ├─ Check minimum players (2+)
|
||||
│ ├─ Generate board (100 fields with pattern)
|
||||
@@ -209,7 +209,7 @@ Authorization: Bearer <access_token>
|
||||
|
||||
### 1. Create Game
|
||||
|
||||
**Endpoint**: `POST /api/v1/game/start`
|
||||
**Endpoint**: `POST /api/games/start`
|
||||
**Auth**: Required
|
||||
**Description**: Create a new game session with selected decks
|
||||
|
||||
@@ -252,7 +252,7 @@ Authorization: Bearer <access_token>
|
||||
|
||||
### 2. Join Game
|
||||
|
||||
**Endpoint**: `POST /api/v1/game/join`
|
||||
**Endpoint**: `POST /api/games/join`
|
||||
**Auth**: Optional (depends on game type)
|
||||
**Description**: Join an existing game using game code
|
||||
|
||||
@@ -307,7 +307,7 @@ Authorization: Bearer <access_token>
|
||||
|
||||
### 3. Start Game Play
|
||||
|
||||
**Endpoint**: `POST /api/v1/game/:gameId/start`
|
||||
**Endpoint**: `POST /api/games/:gameId/start`
|
||||
**Auth**: Required (only gamemaster)
|
||||
**Description**: Start the actual gameplay after all players are ready
|
||||
|
||||
@@ -388,11 +388,14 @@ const socket = io('http://localhost:3000/game', {
|
||||
// Client → Server: Initial join
|
||||
socket.emit('game:join', { gameToken: string });
|
||||
|
||||
// Server → Client: Authentication success
|
||||
socket.on('authenticated', {
|
||||
// Server → Client: Authentication success (renamed from 'authenticated')
|
||||
socket.on('game:joined', {
|
||||
gameCode: string;
|
||||
playerName: string;
|
||||
message: string;
|
||||
gameId: string;
|
||||
playerId?: string;
|
||||
timestamp: string;
|
||||
});
|
||||
|
||||
// Server → All Players: Player joined
|
||||
@@ -427,6 +430,144 @@ socket.on('game:player-ready', {
|
||||
allReady: boolean;
|
||||
timestamp: string;
|
||||
});
|
||||
|
||||
// Server → All Players: All players ready (can start game)
|
||||
socket.on('game:all-ready', {
|
||||
message: string;
|
||||
readyCount: number;
|
||||
totalPlayers: number;
|
||||
timestamp: string;
|
||||
});
|
||||
```
|
||||
|
||||
#### Player Approval System (Private Games Only)
|
||||
|
||||
```typescript
|
||||
// Server → Pending Player: Waiting for gamemaster approval
|
||||
socket.on('game:pending-approval', {
|
||||
message: string;
|
||||
gameCode: string;
|
||||
timestamp: string;
|
||||
});
|
||||
|
||||
// Server → Gamemaster: Player requesting to join
|
||||
socket.on('game:player-requesting-join', {
|
||||
playerName: string;
|
||||
playerId?: string;
|
||||
message: string;
|
||||
timestamp: string;
|
||||
});
|
||||
|
||||
// Client → Server: Gamemaster approves player
|
||||
socket.emit('game:approve-player', {
|
||||
gameCode: string;
|
||||
playerName: string;
|
||||
});
|
||||
|
||||
// Client → Server: Gamemaster rejects player
|
||||
socket.emit('game:reject-player', {
|
||||
gameCode: string;
|
||||
playerName: string;
|
||||
reason?: string;
|
||||
});
|
||||
|
||||
// Server → Approved Player: Join approved, can reconnect
|
||||
socket.on('game:approval-granted', {
|
||||
gameCode: string;
|
||||
playerName: string;
|
||||
message: string;
|
||||
timestamp: string;
|
||||
});
|
||||
|
||||
// Server → Rejected Player: Join denied
|
||||
socket.on('game:approval-denied', {
|
||||
gameCode: string;
|
||||
playerName: string;
|
||||
reason?: string;
|
||||
message: string;
|
||||
timestamp: string;
|
||||
});
|
||||
|
||||
// Server → All Players: Player was approved
|
||||
socket.on('game:player-approved', {
|
||||
playerName: string;
|
||||
playerId?: string;
|
||||
message: string;
|
||||
timestamp: string;
|
||||
});
|
||||
|
||||
// Client → Server: Join after approval (private games)
|
||||
socket.emit('game:join-approved', { gameToken: string });
|
||||
```
|
||||
|
||||
#### Game State Events
|
||||
|
||||
```typescript
|
||||
// Server → Individual Player: Current game state
|
||||
socket.on('game:state', {
|
||||
// Complete game state object
|
||||
gameCode: string;
|
||||
players: PlayerPosition[];
|
||||
currentTurn: number;
|
||||
currentPlayer: string;
|
||||
turnSequence: string[];
|
||||
// ... additional state data
|
||||
});
|
||||
|
||||
// Server → All Players: Game state update
|
||||
socket.on('game:state-update', {
|
||||
// Updated game state after action
|
||||
});
|
||||
```
|
||||
|
||||
#### Chat System
|
||||
|
||||
```typescript
|
||||
// Client → Server: Send chat message
|
||||
socket.emit('game:chat', {
|
||||
gameCode: string;
|
||||
message: string;
|
||||
});
|
||||
|
||||
// Server → All Players: Chat message received
|
||||
socket.on('game:chat-message', {
|
||||
playerName: string;
|
||||
playerId?: string;
|
||||
message: string;
|
||||
timestamp: string;
|
||||
});
|
||||
```
|
||||
|
||||
#### Player Disconnect Events
|
||||
|
||||
```typescript
|
||||
// Server → All Players: Player disconnected
|
||||
socket.on('game:player-disconnected', {
|
||||
playerName: string;
|
||||
playerId?: string;
|
||||
message: string;
|
||||
timestamp: string;
|
||||
});
|
||||
|
||||
// Server → All Players: Player disconnected during card answer
|
||||
socket.on('game:player-disconnected-during-card', {
|
||||
playerName: string;
|
||||
playerId: string;
|
||||
message: string;
|
||||
timestamp: string;
|
||||
});
|
||||
|
||||
// Client → Server: Leave game voluntarily
|
||||
socket.emit('game:leave', { gameCode: string });
|
||||
|
||||
// Server → All Players: Player left game
|
||||
socket.on('game:player-left', {
|
||||
playerName: string;
|
||||
playerId?: string;
|
||||
message: string;
|
||||
playerCount: number;
|
||||
timestamp: string;
|
||||
});
|
||||
```
|
||||
|
||||
#### Game Start Notification
|
||||
@@ -605,6 +746,14 @@ socket.emit('game:position-guess', {
|
||||
guessedPosition: number;
|
||||
});
|
||||
|
||||
// Server → All Players: Player is guessing (notification)
|
||||
socket.on('game:player-guessing', {
|
||||
playerId: string;
|
||||
playerName: string;
|
||||
message: string;
|
||||
timestamp: string;
|
||||
});
|
||||
|
||||
// Server → All Players: Player's guess broadcast
|
||||
socket.on('game:position-guess-broadcast', {
|
||||
playerId: string;
|
||||
@@ -1548,20 +1697,40 @@ private async advanceTurn(gameCode: string): Promise<void> {
|
||||
|
||||
**Formula**:
|
||||
```
|
||||
finalPosition = currentPosition + dice + stepValue + patternModifier
|
||||
finalPosition = currentPosition + (stepValue × dice) + patternModifier
|
||||
```
|
||||
|
||||
**Pattern Modifiers by Zone**:
|
||||
**Pattern Modifiers by Position & Field Type**:
|
||||
```typescript
|
||||
private getPatternModifier(position: number): number {
|
||||
if (position <= 20) return 2; // Positions 1-20
|
||||
if (position <= 40) return -1; // Positions 21-40
|
||||
if (position <= 60) return 1; // Positions 41-60
|
||||
if (position <= 80) return -2; // Positions 61-80
|
||||
return 3; // Positions 81-100
|
||||
private getPatternModifier(position: number, positiveField: boolean): number {
|
||||
// Dynamic pattern-based modifiers for engaging gameplay
|
||||
// Sign depends on field type: positive field = positive modifier, negative field = negative modifier
|
||||
|
||||
if (position % 10 === 0) {
|
||||
return 0; // Positions ending in 0 (10, 20, 30...) - No modifier
|
||||
} else if (position % 10 === 5) {
|
||||
return positiveField ? 3 : -3; // Positions ending in 5 (15, 25, 35...) - ±3 modifier
|
||||
} else if (position % 3 === 0) {
|
||||
return positiveField ? 2 : -2; // Positions divisible by 3 (9, 12, 21...) - ±2 modifier
|
||||
} else if (position % 2 === 1) {
|
||||
return positiveField ? 1 : -1; // Odd positions (1, 7, 11...) - ±1 modifier
|
||||
} else {
|
||||
return 0; // Other even positions - No modifier
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**How Field Type is Determined**:
|
||||
- `positiveField = true` when `stepValue > 0` (positive field)
|
||||
- `positiveField = false` when `stepValue < 0` (negative field)
|
||||
|
||||
**Why This Design**:
|
||||
- **Dynamic**: Every position has different calculation rules based on patterns
|
||||
- **Learnable**: Players can recognize patterns (ends in 5, divisible by 3, etc.)
|
||||
- **Field-Dependent**: Positive fields give positive modifiers, negative fields give negative modifiers
|
||||
- **Skill-Based**: Requires mental calculation and pattern recognition under time pressure (30s)
|
||||
- **Not Trivial**: Information is available but requires active processing
|
||||
|
||||
### Guess Requirement Logic
|
||||
|
||||
```typescript
|
||||
@@ -1608,24 +1777,38 @@ private determineGuessRequirement(
|
||||
|
||||
### Calculation Examples
|
||||
|
||||
**Example 1**: Position 15, dice 4, stepValue 2
|
||||
**Example 1**: Position 15 (ends in 5), positive field, dice 4, stepValue 2
|
||||
```
|
||||
patternModifier = 2 (position 15 is in zone 1-20)
|
||||
calculation = 15 + 4 + 2 + 2 = 23
|
||||
positiveField = true (stepValue 2 > 0)
|
||||
patternModifier = 3 (position ends in 5, positive field)
|
||||
calculation = 15 + (2 × 4) + 3 = 15 + 8 + 3 = 26
|
||||
```
|
||||
|
||||
**Example 2**: Position 35, dice 6, stepValue 1
|
||||
**Example 2**: Position 35 (ends in 5), negative field, dice 6, stepValue -1
|
||||
```
|
||||
patternModifier = -1 (position 35 is in zone 21-40)
|
||||
calculation = 35 + 6 + 1 - 1 = 41
|
||||
positiveField = false (stepValue -1 < 0)
|
||||
patternModifier = -3 (position ends in 5, negative field)
|
||||
calculation = 35 + (-1 × 6) + (-3) = 35 - 6 - 3 = 26
|
||||
```
|
||||
|
||||
**Example 3**: Position 75 (joker), stepValue 3
|
||||
**Example 3**: Position 21 (divisible by 3), positive field, dice 5, stepValue 2
|
||||
```
|
||||
dice = 6 (always for jokers)
|
||||
patternModifier = -2 (position 75 is in zone 61-80)
|
||||
calculation = 75 + 6 + 3 - 2 = 82
|
||||
if wrong guess: 82 - 2 = 80
|
||||
positiveField = true (stepValue 2 > 0)
|
||||
patternModifier = 2 (position divisible by 3, positive field)
|
||||
calculation = 21 + (2 × 5) + 2 = 21 + 10 + 2 = 33
|
||||
```
|
||||
|
||||
**Example 4**: Position 20 (ends in 0), any field type, dice 4, stepValue 2
|
||||
```
|
||||
patternModifier = 0 (position ends in 0, always 0)
|
||||
calculation = 20 + (2 × 4) + 0 = 20 + 8 = 28
|
||||
```
|
||||
|
||||
**Example 5**: Position 7 (odd), negative field, dice 3, stepValue -2
|
||||
```
|
||||
positiveField = false (stepValue -2 < 0)
|
||||
patternModifier = -1 (position is odd, negative field)
|
||||
calculation = 7 + (-2 × 3) + (-1) = 7 - 6 - 1 = 0 → clamped to 1
|
||||
```
|
||||
|
||||
---
|
||||
@@ -1943,21 +2126,39 @@ VALUES (
|
||||
| Event | Data | Description |
|
||||
|-------|------|-------------|
|
||||
| `game:join` | `{ gameToken: string }` | Join game room with auth token |
|
||||
| `game:leave` | `{ gameCode: string }` | Leave game voluntarily |
|
||||
| `game:ready` | `{ gameCode: string, ready: boolean }` | Mark player as ready/not ready |
|
||||
| `game:approve-player` | `{ gameCode: string, playerName: string }` | Gamemaster approves player (PRIVATE) |
|
||||
| `game:reject-player` | `{ gameCode: string, playerName: string, reason?: string }` | Gamemaster rejects player (PRIVATE) |
|
||||
| `game:join-approved` | `{ gameToken: string }` | Join after approval (PRIVATE) |
|
||||
| `game:chat` | `{ gameCode: string, message: string }` | Send chat message |
|
||||
| `game:action` | `{ gameCode: string, action: string, data?: any }` | Generic game action |
|
||||
| `game:dice-roll` | `{ gameCode: string, diceValue: number }` | Roll dice (1-6) |
|
||||
| `game:card-answer` | `{ gameCode: string, answer: any }` | Submit card answer |
|
||||
| `game:gamemaster-decision` | `{ gameCode: string, requestId: string, decision: string }` | Gamemaster decision on joker |
|
||||
| `game:position-guess` | `{ gameCode: string, guessedPosition: number }` | Submit position guess (question) |
|
||||
| `game:joker-position-guess` | `{ gameCode: string, guessedPosition: number }` | Submit position guess (joker) |
|
||||
| `game:leave` | `{ gameCode: string }` | Leave game |
|
||||
|
||||
### Server → Client Events
|
||||
|
||||
| Event | Audience | Description |
|
||||
|-------|----------|-------------|
|
||||
| `authenticated` | Individual | Auth success, joined rooms |
|
||||
| `game:joined` | Individual | Successful join, joined rooms |
|
||||
| `game:state` | Individual | Current game state sent |
|
||||
| `game:pending-approval` | Individual | Waiting for gamemaster approval (PRIVATE) |
|
||||
| `game:approval-granted` | Individual | Join request approved (PRIVATE) |
|
||||
| `game:approval-denied` | Individual | Join request rejected (PRIVATE) |
|
||||
| `game:player-joined` | All | Player joined game |
|
||||
| `game:player-left` | All | Player left game |
|
||||
| `game:player-disconnected` | All | Player disconnected unexpectedly |
|
||||
| `game:player-disconnected-during-card` | All | Player disconnected during card answer |
|
||||
| `game:player-requesting-join` | Gamemaster | Player wants to join (PRIVATE) |
|
||||
| `game:player-approved` | All | Player was approved by gamemaster |
|
||||
| `game:player-ready` | All | Player ready status changed |
|
||||
| `game:all-ready` | All | All players ready |
|
||||
| `game:chat-message` | All | Chat message from player |
|
||||
| `game:action-result` | All | Generic action result |
|
||||
| `game:state-update` | All | Game state updated |
|
||||
| `game:started` | All | Game started, board generated |
|
||||
| `game:turn-changed` | All | Turn advanced to next player |
|
||||
| `game:your-turn` | Individual | Your turn notification |
|
||||
@@ -1965,9 +2166,12 @@ VALUES (
|
||||
| `game:player-moved` | All | Player moved to new position |
|
||||
| `game:card-drawn` | All | Card drawn (question shown) |
|
||||
| `game:card-drawn-self` | Individual | Interactive card data |
|
||||
| `game:card-result` | All | Card result (for LUCK cards) |
|
||||
| `game:card-timeout` | All | Player timed out on card answer |
|
||||
| `game:answer-submitted` | All | Answer submitted (pre-validation) |
|
||||
| `game:answer-validated` | All | Answer validation result |
|
||||
| `game:position-guess-request` | Individual | Request position guess |
|
||||
| `game:player-guessing` | All | Player is calculating guess |
|
||||
| `game:position-guess-broadcast` | All | Player's guess shown |
|
||||
| `game:guess-result` | All | Guess result with calculation |
|
||||
| `game:no-movement` | All | Player didn't move |
|
||||
@@ -1976,8 +2180,10 @@ VALUES (
|
||||
| `game:joker-drawn` | All | Joker card drawn |
|
||||
| `game:gamemaster-decision-request` | Gamemaster | Decision request |
|
||||
| `game:gamemaster-decision-result` | All | Decision result |
|
||||
| `game:gamemaster-timeout` | All | Gamemaster timed out on decision |
|
||||
| `game:joker-position-guess-request` | Individual | Joker position guess request |
|
||||
| `game:joker-complete` | All | Joker card processing complete |
|
||||
| `game:joker-error` | All | Joker card error |
|
||||
| `game:extra-turn-remaining` | All | Player using extra turn |
|
||||
| `game:players-skipped` | All | Players skipped (lost turns) |
|
||||
| `game:ended` | All | Game ended, winner declared |
|
||||
|
||||
Reference in New Issue
Block a user