Update data structure diagram to specify Rock-Paper-Scissors game mechanics and enhance clarity on game state management

This commit is contained in:
averel10
2025-11-22 09:46:29 +01:00
parent 197d9e6e22
commit 2cb995bb5b

View File

@@ -2,7 +2,7 @@
## Overview ## Overview
This diagram illustrates how the Game contract manages multiple concurrent games using mappings and arrays. This diagram illustrates how the Game contract manages multiple concurrent Rock-Paper-Scissors games using mappings, arrays, and structs.
## Data Structure Visualization ## Data Structure Visualization
@@ -12,71 +12,50 @@ This diagram illustrates how the Game contract manages multiple concurrent games
└─────────────────────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────────────────────┐
│ 1. playerToActiveGame (mapping: address => uint) │ 1. games (mapping: uint => GameState)
│ Maps each player address to their active game ID │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Player Address → Game ID │
│ ┌──────────────────┐ ┌───────┐ │
│ │ 0xABC...123 │ ──────→ │ 1 │ │
│ └──────────────────┘ └───────┘ │
│ │
│ ┌──────────────────┐ ┌───────┐ │
│ │ 0xDEF...456 │ ──────→ │ 1 │ (same game) │
│ └──────────────────┘ └───────┘ │
│ │
│ ┌──────────────────┐ ┌───────┐ │
│ │ 0xGHI...789 │ ──────→ │ 2 │ │
│ └──────────────────┘ └───────┘ │
│ │
│ ┌──────────────────┐ ┌───────┐ │
│ │ 0xJKL...012 │ ──────→ │ 0 │ (no active game) │
│ └──────────────────┘ └───────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ 2. games (mapping: uint => GameState) │
│ Maps game ID to the complete game state │ │ Maps game ID to the complete game state │
├─────────────────────────────────────────────────────────────────────────────┤ ├─────────────────────────────────────────────────────────────────────────────┤
│ │ │ │
│ Game ID Game State │ │ Game ID Game State │
│ ┌───────┐ ┌────────────────────────────────────┐ │ ┌───────┐ ┌──────────────────────────────────────┐ │
│ │ 1 │ ───────────→ │ GameState { │ │ │ │ 1 │ ───────────→ │ GameState { │ │
│ └───────┘ │ gameId: 1 │ │ │ └───────┘ │ gameId: 1 │ │
│ │ isActive: true │ │ │ │ isActive: true │ │
│ │ playerA: { │ │ firstReveal: 0 │ │
│ │ initialBet: 0.01 ETH │ │
│ │ outcome: None │ │
│ │ playerA: Player { │ │
│ │ addr: 0xABC...123 │ │ │ │ addr: 0xABC...123 │ │
│ │ bet: 0.01 ETH │ │ │ │ bet: 0.01 ETH │ │
│ │ encrMove: 0x4f2a... │ │ │ │ encrMove: 0x4f2a... │ │
│ │ move: Rock │ │ move: None │ │
│ │ nickname: "Alice" │ │
│ │ } │ │ │ │ } │ │
│ │ playerB: { │ │ playerB: Player {
│ │ addr: 0xDEF...456 │ │ │ │ addr: 0xDEF...456 │ │
│ │ bet: 0.01 ETH │ │ │ │ bet: 0.01 ETH │ │
│ │ encrMove: 0x8b3c... │ │ │ │ encrMove: 0x8b3c... │ │
│ │ move: Paper │ │ move: None │ │
│ │ nickname: "Bob" │ │
│ │ } │ │ │ │ } │ │
│ │ outcome: PlayerB │ │
│ │ firstReveal: 1699876543 │ │
│ │ initialBet: 0.01 ETH │ │
│ │ } │ │ │ │ } │ │
│ └────────────────────────────────────┘ │ └──────────────────────────────────────┘ │
│ │ │ │
│ ┌───────┐ ┌────────────────────────────────────┐ │ ┌───────┐ ┌──────────────────────────────────────┐ │
│ │ 2 │ ───────────→ │ GameState { │ │ │ │ 2 │ ───────────→ │ GameState { │ │
│ └───────┘ │ gameId: 2 │ │ │ └───────┘ │ gameId: 2 │ │
│ │ isActive: true │ │ │ │ isActive: true │ │
│ │ playerA: { addr: 0xGHI...789 } │ │ │ │ playerA: { addr: 0xGHI...789 } │ │
│ │ playerB: { addr: 0x000...000 } │ │ │ │ playerB: { addr: 0x000...000 } │ │
│ │ outcome: None │ │ (waiting for playerB) │ │
│ │ ... │ │ │ │ ... │ │
│ │ } │ │ │ │ } │ │
│ └────────────────────────────────────┘ │ └──────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────────────────────┐
3. gameIds (array: uint[]) │ 2. gameIds (array: uint[]) │
│ Tracks all game IDs for enumeration │ Tracks all game IDs ever created (for enumeration)
├─────────────────────────────────────────────────────────────────────────────┤ ├─────────────────────────────────────────────────────────────────────────────┤
│ │ │ │
│ Index: 0 1 2 3 4 │ │ Index: 0 1 2 3 4 │
@@ -84,36 +63,19 @@ This diagram illustrates how the Game contract manages multiple concurrent games
│ Value: │ 1 │ │ 2 │ │ 3 │ │ 4 │ │ 5 │ ... │ │ Value: │ 1 │ │ 2 │ │ 3 │ │ 4 │ │ 5 │ ... │
│ └───┘ └───┘ └───┘ └───┘ └───┘ │ │ └───┘ └───┘ └───┘ └───┘ └───┘ │
│ │ │ │
Used to iterate over all games (active and inactive) Entries are never removed (enables iteration over all games)
│ Use isActive flag to distinguish active from completed games │
└─────────────────────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────────────────────┐
4. pastGames (array: GameState[]) 3. nextGameId (uint counter)
│ Stores completed games for historical reference │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Index: 0 1 │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ GameState { │ │ GameState { │ │
│ │ gameId: 1 │ │ gameId: 3 │ │
│ │ isActive: false │ │ isActive: false │ │
│ │ playerA: ... │ │ playerA: ... │ │
│ │ playerB: ... │ │ playerB: ... │ │
│ │ outcome: PlayerB │ │ outcome: Draw │ │
│ │ } │ │ } │ │
│ └─────────────────────┘ └─────────────────────┘ │
│ │
│ Grows as games are completed via getOutcome() │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ 5. nextGameId (uint counter) │
│ Auto-incrementing counter for unique game IDs │ │ Auto-incrementing counter for unique game IDs │
├─────────────────────────────────────────────────────────────────────────────┤ ├─────────────────────────────────────────────────────────────────────────────┤
│ │ │ │
│ Current Value: 6 ──→ Next game created will have ID = 6 │ │ Current Value: 6 ──→ Next game created will have ID = 6 │
│ │ │ │
│ Increments with each new game: createNewGame() │ │ Increments with each new game: createNewGame() │
│ Starts at 1 and never resets (prevents ID collisions) │
└─────────────────────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────────────────────┘
``` ```
@@ -124,49 +86,49 @@ This diagram illustrates how the Game contract manages multiple concurrent games
│ PLAYER GAME LIFECYCLE │ │ PLAYER GAME LIFECYCLE │
└──────────────────────────────────────────────────────────────────────────┘ └──────────────────────────────────────────────────────────────────────────┘
Player: 0xABC...123 Player: 0xABC...123 (msg.sender)
│ register(0) │ register(gameId, {value: 0.01 ETH})
┌─────────────────────────────────────┐ ┌─────────────────────────────────────────────────────
│ 1. Check playerToActiveGame │ 1. Register with Game
0xABC...123 → 0 (not in game) - If gameId == 0: createNewGame()
└─────────────────────────────────────┘ │ - Validate bet >= BET_MIN (0.01 ETH) │
│ - If playerA slot empty: become playerA │
│ - Else if playerB slot empty: become playerB │
│ - Else: revert "Game is full" │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────┐ ┌─────────────────────────────────────────────────────
│ 2. findOrCreateGame() │ 2. Commit Phase
- Search for open game play(gameId, encrMove)
│ - Or create new game ID: 1 │ - Store encrypted move for player
└─────────────────────────────────────┘ │ - Wait for both players to commit │
└─────────────────────────────────────────────────────┘
│ (once both players committed)
┌─────────────────────────────────────┐ ┌─────────────────────────────────────────────────────
│ 3. Update mappings │ 3. Reveal Phase
playerToActiveGame[0xABC] = 1 reveal(gameId, "move-password")
games[1].playerA = 0xABC...123 - Hash and verify move
games[1].isActive = true - Store clear move
gameIds.push(1) - Start firstReveal timer on first reveal
└─────────────────────────────────────┘ │ - Auto-calculate outcome if both revealed │
└─────────────────────────────────────────────────────┘
play(encrMove) → reveal(clearMove) (once both players revealed OR timeout reached)
┌─────────────────────────────────────┐ ┌─────────────────────────────────────────────────────
│ 4. Game progresses │ 4. Game completion
Both players commit & reveal getOutcome(gameId)
└─────────────────────────────────────┘ │ - Verify reveal phase ended │
│ - Mark game as inactive
│ getOutcome() - Transfer winnings (2x bet for winner, │
1x bet each for draw) │
┌─────────────────────────────────────┐ │ - Return outcome (PlayerA/PlayerB/Draw) │
│ 5. Game completion │ └─────────────────────────────────────────────────────┘
│ - Calculate winner │
│ - pastGames.push(games[1]) │
│ - playerToActiveGame[0xABC] = 0 │
│ - playerToActiveGame[0xDEF] = 0 │
│ - games[1].isActive = false │
│ - Pay winners │
└─────────────────────────────────────┘
Player can register for new game Player can register for new game
@@ -176,19 +138,10 @@ This diagram illustrates how the Game contract manages multiple concurrent games
``` ```
┌───────────────────────────────┐ ┌───────────────────────────────┐
│ Player Addresses │
│ (External participants) │
└──────────────┬────────────────┘
playerToActiveGame
│ (mapping)
┌───────────────────────────────┐
│ Game IDs │ │ Game IDs │
│ (1, 2, 3, 4, 5...) │ │ (1, 2, 3, 4, 5...) │
└──────────────┬────────────────┘ └──────────────┬────────────────┘
┌──────────────┴────────────────┐ ┌──────────────┴────────────────┐
│ │ │ │
games (mapping) gameIds (array) games (mapping) gameIds (array)
@@ -197,61 +150,73 @@ This diagram illustrates how the Game contract manages multiple concurrent games
┌─────────────────────┐ ┌────────────────┐ ┌─────────────────────┐ ┌────────────────┐
│ GameState Objects │ │ For iteration │ │ GameState Objects │ │ For iteration │
│ - Player data │ │ over all games│ │ - Player data │ │ over all games│
│ - Moves │ └────────────────┘ │ - Moves │ │ (active/ │
│ - Outcomes │ │ - Outcomes │ │ inactive) │
│ - isActive flag │ │ - isActive flag │ └────────────────┘
└──────────┬──────────┘ │ - Timestamps │
└─────────────────────┘
When game completes (isActive = false)
┌──────────────────────┐
│ pastGames array │
│ (Historical record) │
└──────────────────────┘
``` ```
## Key Relationships ## Key Relationships
1. **playerToActiveGame → games**: 1. **gameIds array → games mapping**:
- A player's address maps to a game ID - `gameIds` maintains a list of all game IDs created
- That game ID is used to access the full game state in `games` mapping - Each ID in `gameIds[i]` can be used to look up the game state: `games[gameIds[i]]`
- Enables iteration over all games regardless of active status
2. **gameIds array**: 2. **GameState structure**:
- Maintains list of all game IDs ever created - Each game has exactly 2 players (playerA and playerB)
- Enables iteration over games (e.g., `getActiveGameIds()`) - Players are stored as `Player` structs with address, bet amount, moves, and nickname
- Never removes entries, only marks games inactive - The `isActive` flag indicates if the game is ongoing or completed
- `outcome` is calculated when both players reveal or after reveal timeout
3. **pastGames array**: 3. **nextGameId counter**:
- Ensures unique game IDs across all games
- Increments with each new game creation
- Never resets, preventing ID collisions even in long-running contracts
- Snapshot of completed games
- Grows with each completed game
- Provides historical game data
4. **nextGameId counter**:
- Ensures unique game IDs
- Increments with each new game
- Never resets, preventing ID collisions
## Data Flow Example: Two Players Join Game ## Data Flow Example: Two Players Join Game
``` ```
Step 1: Player A registers Step 1: Player A registers with gameId=0 (create new game)
playerToActiveGame[PlayerA] = 0 → 1 createNewGame() → gameId = 1, nextGameId = 2
games[1] = { playerA: PlayerA, playerB: null, isActive: true } games[1] = {
gameId: 1,
isActive: true,
playerA: { addr: PlayerA, bet: 0.01 ETH, encrMove: 0, move: None, nickname: "" },
playerB: { addr: 0x0, bet: 0, encrMove: 0, move: None, nickname: "" },
outcome: None,
firstReveal: 0,
initialBet: 0
}
gameIds = [1] gameIds = [1]
Step 2: Player B joins same game Step 2: Player B joins game 1
playerToActiveGame[PlayerB] = 0 → 1 games[1] = {
games[1] = { playerA: PlayerA, playerB: PlayerB, isActive: true } gameId: 1,
gameIds = [1] (unchanged) isActive: true,
playerA: { addr: PlayerA, bet: 0.01 ETH, ... },
playerB: { addr: PlayerB, bet: 0.01 ETH, ... },
outcome: None,
...
}
gameIds = [1] (unchanged - game ID already exists)
Step 3: Game completes Step 3: Players commit moves
pastGames.push(games[1]) → pastGames[0] = games[1] game.playerA.encrMove = hash("1-password")
playerToActiveGame[PlayerA] = 1 → 0 game.playerB.encrMove = hash("2-password")
playerToActiveGame[PlayerB] = 1 → 0
games[1].isActive = true → false Step 4: Players reveal moves
gameIds = [1] (unchanged, but game is inactive) game.playerA.move = Rock (1)
game.playerB.move = Paper (2)
game.firstReveal = block.timestamp
game.outcome = PlayerB (Paper beats Rock)
Step 5: Game completes via getOutcome()
games[1].isActive = false
Transfer 0.02 ETH to PlayerB
gameIds = [1] (unchanged - game ID remains for historical reference)
``` ```