mirror of
https://github.com/averel10/crypto_clash.git
synced 2026-03-12 10:58:11 +01:00
Update data structure diagram to specify Rock-Paper-Scissors game mechanics and enhance clarity on game state management
This commit is contained in:
@@ -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 │ │
|
||||||
│ │ addr: 0xABC...123 │ │
|
│ │ initialBet: 0.01 ETH │ │
|
||||||
│ │ bet: 0.01 ETH │ │
|
│ │ outcome: None │ │
|
||||||
│ │ encrMove: 0x4f2a... │ │
|
│ │ playerA: Player { │ │
|
||||||
│ │ move: Rock │ │
|
│ │ addr: 0xABC...123 │ │
|
||||||
│ │ } │ │
|
│ │ bet: 0.01 ETH │ │
|
||||||
│ │ playerB: { │ │
|
│ │ encrMove: 0x4f2a... │ │
|
||||||
│ │ addr: 0xDEF...456 │ │
|
│ │ move: None │ │
|
||||||
│ │ bet: 0.01 ETH │ │
|
│ │ nickname: "Alice" │ │
|
||||||
│ │ encrMove: 0x8b3c... │ │
|
│ │ } │ │
|
||||||
│ │ move: Paper │ │
|
│ │ playerB: Player { │ │
|
||||||
│ │ } │ │
|
│ │ addr: 0xDEF...456 │ │
|
||||||
│ │ outcome: PlayerB │ │
|
│ │ bet: 0.01 ETH │ │
|
||||||
│ │ firstReveal: 1699876543 │ │
|
│ │ encrMove: 0x8b3c... │ │
|
||||||
│ │ initialBet: 0.01 ETH │ │
|
│ │ move: None │ │
|
||||||
│ │ } │ │
|
│ │ nickname: "Bob" │ │
|
||||||
│ └────────────────────────────────────┘ │
|
│ │ } │ │
|
||||||
|
│ │ } │ │
|
||||||
|
│ └──────────────────────────────────────┘ │
|
||||||
│ │
|
│ │
|
||||||
│ ┌───────┐ ┌────────────────────────────────────┐ │
|
│ ┌───────┐ ┌──────────────────────────────────────┐ │
|
||||||
│ │ 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)
|
||||||
```
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user