mirror of
https://github.com/averel10/crypto_clash.git
synced 2026-03-12 10:58:11 +01:00
implement multi game mode and document it
This commit is contained in:
168
docs/MULTI_GAME_IMPLEMENTATION.md
Normal file
168
docs/MULTI_GAME_IMPLEMENTATION.md
Normal file
@@ -0,0 +1,168 @@
|
||||
# Multi-Game Implementation Summary
|
||||
|
||||

|
||||
|
||||
## Overview
|
||||
|
||||
The Game smart contract has been updated to support multiple concurrent games. Each player can participate in only one active game at a time, and games are identified by unique game IDs and tracked by player addresses.
|
||||
|
||||
## Key Changes
|
||||
|
||||
### 1. Data Structure Updates
|
||||
|
||||
**Previous Structure:**
|
||||
|
||||
- Single `currentGame` variable
|
||||
|
||||
**New Structure:**
|
||||
|
||||
See [Data Structure Diagram](data-structure-diagram.md) for visual representation.
|
||||
|
||||
```solidity
|
||||
// Mapping from player address to their active game ID
|
||||
mapping(address => uint) private playerToActiveGame;
|
||||
|
||||
// Mapping from game ID to game state
|
||||
mapping(uint => GameState) private games;
|
||||
|
||||
// Array to track all game IDs (for enumeration)
|
||||
uint[] private gameIds;
|
||||
|
||||
// Counter for generating unique game IDs
|
||||
uint private nextGameId = 1;
|
||||
|
||||
// Array to store completed games
|
||||
GameState[] private pastGames;
|
||||
```
|
||||
|
||||
**GameState struct updated with:**
|
||||
|
||||
- `uint gameId` - Unique identifier for each game
|
||||
- `bool isActive` - Flag to track if game is currently active
|
||||
|
||||
### 2. Registration System
|
||||
|
||||
**New `register(uint gameId)` function:**
|
||||
|
||||
- If `gameId = 0`: Automatically finds an open game or creates a new one
|
||||
- If `gameId > 0`: Joins the specified game (if valid and has space)
|
||||
- Returns both player ID (1 or 2) and the game ID
|
||||
- Enforces one active game per address
|
||||
|
||||
**Helper functions:**
|
||||
|
||||
- `findOrCreateGame()` - Finds a game with one player or creates new game
|
||||
- `createNewGame()` - Creates a new game with unique ID
|
||||
|
||||
### 3. Game Flow Updates
|
||||
|
||||
All game functions now work with the player's active game:
|
||||
|
||||
- **`play(bytes32 encrMove)`** - Commits move to player's active game
|
||||
- **`reveal(string memory clearMove)`** - Reveals move in player's active game
|
||||
- **`getOutcome()`** - Calculates outcome for player's active game
|
||||
|
||||
### 4. Game Lifecycle
|
||||
|
||||
**Active Games:**
|
||||
|
||||
- Players are automatically assigned to their active game via `playerToActiveGame` mapping
|
||||
- All modifiers check the player's active game ID
|
||||
|
||||
**Game Completion:**
|
||||
|
||||
- When `getOutcome()` is called:
|
||||
1. Game outcome is calculated
|
||||
2. Game is moved to `pastGames` array
|
||||
3. `resetGame()` clears player mappings and marks game as inactive
|
||||
4. Winners are paid
|
||||
5. Players are free to join new games
|
||||
|
||||
### 5. New Helper Functions
|
||||
|
||||
**Game Management:**
|
||||
|
||||
- `getMyActiveGameId()` - Returns caller's active game ID
|
||||
- `getGameDetails(uint gameId)` - View any game's details
|
||||
- `getActiveGameIds()` - Returns array of all active game IDs
|
||||
- `getPastGamesCount()` - Returns number of completed games
|
||||
- `getPastGame(uint index)` - Returns details of a past game
|
||||
|
||||
**Updated Helper Functions:**
|
||||
All existing helper functions now operate on the caller's active game:
|
||||
|
||||
- `whoAmI()` - Returns player ID in their active game
|
||||
- `bothPlayed()` - Checks if both players committed in caller's game
|
||||
- `bothRevealed()` - Checks if both players revealed in caller's game
|
||||
- `playerARevealed()` - Check player A status in caller's game
|
||||
- `playerBRevealed()` - Check player B status in caller's game
|
||||
- `revealTimeLeft()` - Time remaining in caller's game
|
||||
- `getLastWinner()` - Outcome of caller's game
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Example 1: Auto-join or create game
|
||||
|
||||
```solidity
|
||||
// Player registers with gameId = 0 to auto-find/create game
|
||||
(uint playerId, uint gameId) = game.register{value: 0.01 ether}(0);
|
||||
// Returns: (1, 1) if creating new game, or (2, X) if joining existing game
|
||||
```
|
||||
|
||||
### Example 2: Join specific game
|
||||
|
||||
```solidity
|
||||
// Player joins game ID 5
|
||||
(uint playerId, uint gameId) = game.register{value: 0.01 ether}(5);
|
||||
// Returns: (2, 5) if successful
|
||||
```
|
||||
|
||||
### Example 3: Query active games
|
||||
|
||||
```solidity
|
||||
// Get all active game IDs
|
||||
uint[] memory activeGames = game.getActiveGameIds();
|
||||
|
||||
// Check details of a specific game
|
||||
(address playerA, address playerB, uint bet, Outcomes outcome, bool isActive)
|
||||
= game.getGameDetails(gameId);
|
||||
```
|
||||
|
||||
### Example 4: View game history
|
||||
|
||||
```solidity
|
||||
// Get number of completed games
|
||||
uint totalPastGames = game.getPastGamesCount();
|
||||
|
||||
// Get details of a specific past game
|
||||
(address playerA, address playerB, uint bet, Outcomes outcome)
|
||||
= game.getPastGame(0);
|
||||
```
|
||||
|
||||
## Benefits
|
||||
|
||||
1. **Concurrent Games**: Multiple games can run simultaneously
|
||||
2. **Player Isolation**: Each player can only be in one game at a time
|
||||
3. **Game Tracking**: All games are tracked with unique IDs
|
||||
4. **History**: Completed games are preserved in `pastGames`
|
||||
5. **Flexibility**: Players can auto-join available games or specify game IDs
|
||||
6. **Backwards Compatible**: Existing game flow (commit-reveal-outcome) unchanged
|
||||
|
||||
## Security Considerations
|
||||
|
||||
1. **Reentrancy Protection**: Payment happens after game state is reset
|
||||
2. **One Game Per Address**: Enforced via `notAlreadyInGame` modifier
|
||||
3. **Game Isolation**: Players can only interact with their active game
|
||||
4. **State Consistency**: Game marked inactive before clearing mappings
|
||||
|
||||
## Migration Notes
|
||||
|
||||
**Breaking Changes:**
|
||||
|
||||
- `register()` now returns `(uint playerId, uint gameId)` instead of just `uint playerId`
|
||||
- `register()` now requires a `uint gameId` parameter (use 0 for auto-join)
|
||||
|
||||
**Non-Breaking:**
|
||||
|
||||
- All other function signatures remain the same
|
||||
- Existing game flow unchanged
|
||||
257
docs/data-structure-diagram.md
Normal file
257
docs/data-structure-diagram.md
Normal file
@@ -0,0 +1,257 @@
|
||||
# Game Contract Data Structure Diagram
|
||||
|
||||
## Overview
|
||||
|
||||
This diagram illustrates how the Game contract manages multiple concurrent games using mappings and arrays.
|
||||
|
||||
## Data Structure Visualization
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ GAME CONTRACT STATE │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 1. playerToActiveGame (mapping: address => uint) │
|
||||
│ 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 │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Game ID Game State │
|
||||
│ ┌───────┐ ┌────────────────────────────────────┐ │
|
||||
│ │ 1 │ ───────────→ │ GameState { │ │
|
||||
│ └───────┘ │ gameId: 1 │ │
|
||||
│ │ isActive: true │ │
|
||||
│ │ playerA: { │ │
|
||||
│ │ addr: 0xABC...123 │ │
|
||||
│ │ bet: 0.01 ETH │ │
|
||||
│ │ encrMove: 0x4f2a... │ │
|
||||
│ │ move: Rock │ │
|
||||
│ │ } │ │
|
||||
│ │ playerB: { │ │
|
||||
│ │ addr: 0xDEF...456 │ │
|
||||
│ │ bet: 0.01 ETH │ │
|
||||
│ │ encrMove: 0x8b3c... │ │
|
||||
│ │ move: Paper │ │
|
||||
│ │ } │ │
|
||||
│ │ outcome: PlayerB │ │
|
||||
│ │ firstReveal: 1699876543 │ │
|
||||
│ │ initialBet: 0.01 ETH │ │
|
||||
│ │ } │ │
|
||||
│ └────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌───────┐ ┌────────────────────────────────────┐ │
|
||||
│ │ 2 │ ───────────→ │ GameState { │ │
|
||||
│ └───────┘ │ gameId: 2 │ │
|
||||
│ │ isActive: true │ │
|
||||
│ │ playerA: { addr: 0xGHI...789 } │ │
|
||||
│ │ playerB: { addr: 0x000...000 } │ │
|
||||
│ │ outcome: None │ │
|
||||
│ │ ... │ │
|
||||
│ │ } │ │
|
||||
│ └────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 3. gameIds (array: uint[]) │
|
||||
│ Tracks all game IDs for enumeration │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Index: 0 1 2 3 4 │
|
||||
│ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ │
|
||||
│ Value: │ 1 │ │ 2 │ │ 3 │ │ 4 │ │ 5 │ ... │
|
||||
│ └───┘ └───┘ └───┘ └───┘ └───┘ │
|
||||
│ │
|
||||
│ Used to iterate over all games (active and inactive) │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 4. pastGames (array: GameState[]) │
|
||||
│ 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 │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Current Value: 6 ──→ Next game created will have ID = 6 │
|
||||
│ │
|
||||
│ Increments with each new game: createNewGame() │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Flow Diagram: Player Lifecycle
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────────────┐
|
||||
│ PLAYER GAME LIFECYCLE │
|
||||
└──────────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
Player: 0xABC...123
|
||||
│
|
||||
│ register(0)
|
||||
↓
|
||||
┌─────────────────────────────────────┐
|
||||
│ 1. Check playerToActiveGame │
|
||||
│ 0xABC...123 → 0 (not in game) │
|
||||
└─────────────────────────────────────┘
|
||||
│
|
||||
↓
|
||||
┌─────────────────────────────────────┐
|
||||
│ 2. findOrCreateGame() │
|
||||
│ - Search for open game │
|
||||
│ - Or create new game ID: 1 │
|
||||
└─────────────────────────────────────┘
|
||||
│
|
||||
↓
|
||||
┌─────────────────────────────────────┐
|
||||
│ 3. Update mappings │
|
||||
│ playerToActiveGame[0xABC] = 1 │
|
||||
│ games[1].playerA = 0xABC...123 │
|
||||
│ games[1].isActive = true │
|
||||
│ gameIds.push(1) │
|
||||
└─────────────────────────────────────┘
|
||||
│
|
||||
│ play(encrMove) → reveal(clearMove)
|
||||
↓
|
||||
┌─────────────────────────────────────┐
|
||||
│ 4. Game progresses │
|
||||
│ Both players commit & reveal │
|
||||
└─────────────────────────────────────┘
|
||||
│
|
||||
│ getOutcome()
|
||||
↓
|
||||
┌─────────────────────────────────────┐
|
||||
│ 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
|
||||
```
|
||||
|
||||
## Relationship Diagram
|
||||
|
||||
```
|
||||
┌───────────────────────────────┐
|
||||
│ Player Addresses │
|
||||
│ (External participants) │
|
||||
└──────────────┬────────────────┘
|
||||
│
|
||||
playerToActiveGame
|
||||
│ (mapping)
|
||||
↓
|
||||
┌───────────────────────────────┐
|
||||
│ Game IDs │
|
||||
│ (1, 2, 3, 4, 5...) │
|
||||
└──────────────┬────────────────┘
|
||||
│
|
||||
│
|
||||
┌──────────────┴────────────────┐
|
||||
│ │
|
||||
games (mapping) gameIds (array)
|
||||
│ │
|
||||
↓ ↓
|
||||
┌─────────────────────┐ ┌────────────────┐
|
||||
│ GameState Objects │ │ For iteration │
|
||||
│ - Player data │ │ over all games│
|
||||
│ - Moves │ └────────────────┘
|
||||
│ - Outcomes │
|
||||
│ - isActive flag │
|
||||
└──────────┬──────────┘
|
||||
│
|
||||
When game completes (isActive = false)
|
||||
│
|
||||
↓
|
||||
┌──────────────────────┐
|
||||
│ pastGames array │
|
||||
│ (Historical record) │
|
||||
└──────────────────────┘
|
||||
```
|
||||
|
||||
## Key Relationships
|
||||
|
||||
1. **playerToActiveGame → games**:
|
||||
|
||||
- A player's address maps to a game ID
|
||||
- That game ID is used to access the full game state in `games` mapping
|
||||
|
||||
2. **gameIds array**:
|
||||
|
||||
- Maintains list of all game IDs ever created
|
||||
- Enables iteration over games (e.g., `getActiveGameIds()`)
|
||||
- Never removes entries, only marks games inactive
|
||||
|
||||
3. **pastGames array**:
|
||||
|
||||
- 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
|
||||
|
||||
```
|
||||
Step 1: Player A registers
|
||||
playerToActiveGame[PlayerA] = 0 → 1
|
||||
games[1] = { playerA: PlayerA, playerB: null, isActive: true }
|
||||
gameIds = [1]
|
||||
|
||||
Step 2: Player B joins same game
|
||||
playerToActiveGame[PlayerB] = 0 → 1
|
||||
games[1] = { playerA: PlayerA, playerB: PlayerB, isActive: true }
|
||||
gameIds = [1] (unchanged)
|
||||
|
||||
Step 3: Game completes
|
||||
pastGames.push(games[1]) → pastGames[0] = games[1]
|
||||
playerToActiveGame[PlayerA] = 1 → 0
|
||||
playerToActiveGame[PlayerB] = 1 → 0
|
||||
games[1].isActive = true → false
|
||||
gameIds = [1] (unchanged, but game is inactive)
|
||||
```
|
||||
BIN
docs/res/Gameloop.jpg
Normal file
BIN
docs/res/Gameloop.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 442 KiB |
Reference in New Issue
Block a user