12 KiB
Example Gameplay: Rock-Paper-Scissors Minus One
This document provides a step-by-step playbook for two players (Alice and Bob) to play the "Minus One" variant. Follow along to understand the exact order of operations and function calls.
Quick Reference
Move Encoding
1= Rock2= Paper3= Scissors
Game Flow
1. Registration (both players)
2. Initial Commit (both players)
3. First Reveal (both players)
4. Withdrawal Commit (both players)
5. Withdrawal Reveal (both players)
6. Get Outcome (either player)
Hash Generation Tool
🔗 https://emn178.github.io/online-tools/keccak_256.html
- Input Type: UTF-8
- Output Type: Hex
- Format:
<move>-<password>(e.g.,1-mypassfor Rock)
Game Rules
- Each player commits 2 different moves (e.g., Rock + Paper)
- Both players reveal their 2 moves
- Each player secretly withdraws 1 move (commits which index: 1 or 2)
- Both players reveal which move they withdrew
- The remaining move for each player battles to determine winner
Example Game Scenario
Players:
- 👩 Alice (will choose Rock + Paper, keep Rock)
- 👨 Bob (will choose Rock + Scissors, keep Scissors)
Expected Winner: Alice (Rock beats Scissors)
Minimum Bet: 0.01 ETH (10000000000000000 wei)
🎮 PHASE 1: REGISTRATION
Goal: Both players join the game with their nicknames and bets
Step 1.1: Alice Registers (Player 1)
Function: register(uint gameId, string memory name)
Alice's Input:
gameId: 0 (creates new game)
name: "Alice"
value: 0.01 ETH
Smart Contract Call:
register(0, "Alice");
// Send 0.01 ETH with transaction
Returns: (1, 1)
- Position: Player 1
- Game ID: 1
✅ Alice is now registered as Player 1 in Game 1
Step 1.2: Bob Registers (Player 2)
Function: register(uint gameId, string memory name)
Bob's Input:
gameId: 1 (joins Alice's game)
name: "Bob"
value: 0.01 ETH (must match Alice's bet!)
Smart Contract Call:
register(1, "Bob");
// Send 0.01 ETH with transaction
Returns: (2, 1)
- Position: Player 2
- Game ID: 1
✅ Bob is now registered as Player 2 ✅ Game automatically advances to InitialCommit phase
🎮 PHASE 2: INITIAL COMMIT
Goal: Both players commit their 2 encrypted moves (hashes)
Step 2.1: Alice Prepares Her Hashes (Off-chain)
Alice's Strategy:
- Move 1: Rock (1)
- Move 2: Paper (2)
Generate Hash for Move 1 (Rock):
- Go to: https://emn178.github.io/online-tools/keccak_256.html
- Input:
1-alicepass1(UTF-8) - Output:
0x7364263d5fc729b4709129564a2c516f2eb40f55d8704860e46f597c91b6b264
Generate Hash for Move 2 (Paper):
- Input:
2-alicepass2(UTF-8) - Output:
0xc9ad7a6c99b3f5af24991d9c66c0cc2b731869f4b7399653f0820f99e34d45ef
💾 Alice must save these:
- Cleartext for reveal:
1-alicepass1and2-alicepass2 - Hashes for commit:
0x7364...and0xc9ad...
Step 2.2: Alice Commits
Function: commitInitialMoves(uint gameId, bytes32 hash1, bytes32 hash2)
Alice's Input:
gameId: 1
hash1: 0x7364263d5fc729b4709129564a2c516f2eb40f55d8704860e46f597c91b6b264
hash2: 0xc9ad7a6c99b3f5af24991d9c66c0cc2b731869f4b7399653f0820f99e34d45ef
Smart Contract Call:
commitInitialMoves(
1,
"0x7364263d5fc729b4709129564a2c516f2eb40f55d8704860e46f597c91b6b264",
"0xc9ad7a6c99b3f5af24991d9c66c0cc2b731869f4b7399653f0820f99e34d45ef"
);
Returns: true
✅ Alice has committed her two moves
Step 2.3: Bob Prepares His Hashes (Off-chain)
Bob's Strategy:
- Move 1: Rock (1)
- Move 2: Scissors (3)
Generate Hash for Move 1 (Rock):
- Go to: https://emn178.github.io/online-tools/keccak_256.html
- Input:
1-bobsecret1(UTF-8) - Output:
0x16864f12ec74b4fac1cd9fd5b0db1959e4df91cf55e9180cc13cdf20a134af16
Generate Hash for Move 2 (Scissors):
- Input:
3-bobsecret2(UTF-8) - Output:
0x86d1def3d3f9bed5f2de22161040c10c75dcb52f263f3c3ec08cdc8ba10d2103
💾 Bob must save these:
- Cleartext for reveal:
1-bobsecret1and3-bobsecret2 - Hashes for commit:
0x1686...and0x86d1...
Step 2.4: Bob Commits
Function: commitInitialMoves(uint gameId, bytes32 hash1, bytes32 hash2)
Bob's Input:
gameId: 1
hash1: 0x16864f12ec74b4fac1cd9fd5b0db1959e4df91cf55e9180cc13cdf20a134af16
hash2: 0x86d1def3d3f9bed5f2de22161040c10c75dcb52f263f3c3ec08cdc8ba10d2103
Smart Contract Call:
commitInitialMoves(
1,
"0x16864f12ec74b4fac1cd9fd5b0db1959e4df91cf55e9180cc13cdf20a134af16",
"0x86d1def3d3f9bed5f2de22161040c10c75dcb52f263f3c3ec08cdc8ba10d2103"
);
Returns: true
✅ Bob has committed his two moves ✅ Game automatically advances to FirstReveal phase
🎮 PHASE 3: FIRST REVEAL
Goal: Both players reveal their original cleartext strings to prove their moves
Step 3.1: Alice Reveals
Function: revealInitialMoves(uint gameId, string memory clear1, string memory clear2)
Alice's Input:
gameId: 1
clear1: "1-alicepass1" (the exact string she hashed)
clear2: "2-alicepass2" (the exact string she hashed)
Smart Contract Call:
revealInitialMoves(1, "1-alicepass1", "2-alicepass2");
Returns: (Rock, Paper) (enum values for moves 1 and 3)
✅ Alice's moves are now public:
- Move 1: Rock
- Move 2: Paper
Step 3.2: Bob Reveals
Function: revealInitialMoves(uint gameId, string memory clear1, string memory clear2)
Bob's Input:
gameId: 1
clear1: "1-bobsecret1" (the exact string he hashed)
clear2: "3-bobsecret2" (the exact string he hashed)
Smart Contract Call:
revealInitialMoves(1, "1-bobsecret1", "3-bobsecret2");
Returns: (Rock, Scissors) (enum values for moves 1 and 3)
✅ Bob's moves are now public:
- Move 1: Rock
- Move 2: Scissors
Current Game State:
- Alice has: Rock (move 1) and Paper (move 2)
- Bob has: Rock (move 1) and Scissors (move 2)
✅ Game automatically advances to WithdrawalCommit phase
🎮 PHASE 4: WITHDRAWAL COMMIT
Goal: Both players secretly commit which move to withdraw (1 or 2)
Step 4.1: Alice Prepares Withdrawal Hash (Off-chain)
Alice's Decision:
- She wants to keep Rock (move 1)
- So she will withdraw move index 2 (Paper)
Generate Withdrawal Hash:
- Go to: https://emn178.github.io/online-tools/keccak_256.html
- Input:
2-alicewith(UTF-8) ← Note: "2" is the index to withdraw - Output:
0x7c9ff59a4d298766d9480c130f6ed67c06f135a1c2f45326583593bb9c2e6363
💾 Alice must save:
- Cleartext:
2-alicewith - Hash:
0x9a3f...
Step 4.2: Alice Commits Withdrawal
Function: commitWithdraw(uint gameId, bytes32 wHash)
Alice's Input:
gameId: 1
wHash: 0x7c9ff59a4d298766d9480c130f6ed67c06f135a1c2f45326583593bb9c2e6363
Smart Contract Call:
commitWithdraw(
1,
"0x7c9ff59a4d298766d9480c130f6ed67c06f135a1c2f45326583593bb9c2e6363"
);
Returns: true
✅ Alice has secretly committed to withdraw move 2
Step 4.3: Bob Prepares Withdrawal Hash (Off-chain)
Bob's Decision:
- He wants to keep Scissors (move 2)
- So he will withdraw move index 1 (Rock)
Generate Withdrawal Hash:
- Go to: https://emn178.github.io/online-tools/keccak_256.html
- Input:
1-bobwith(UTF-8) ← Note: "1" is the index to withdraw - Output:
0x29e944e8859972eb35622d3149120d878d7931aaa9ac2e213d0b321ee564b7dc
💾 Bob must save:
- Cleartext:
1-bobwith - Hash:
0x29e9...
Step 4.4: Bob Commits Withdrawal
Function: commitWithdraw(uint gameId, bytes32 wHash)
Bob's Input:
gameId: 1
wHash: 0x29e944e8859972eb35622d3149120d878d7931aaa9ac2e213d0b321ee564b7dc
Smart Contract Call:
commitWithdraw(
1,
"0x29e944e8859972eb35622d3149120d878d7931aaa9ac2e213d0b321ee564b7dc"
);
Returns: true
✅ Bob has secretly committed to withdraw move 1 ✅ Game automatically advances to WithdrawalReveal phase
🎮 PHASE 5: WITHDRAWAL REVEAL
Goal: Both players reveal which move they withdrew
Step 5.1: Alice Reveals Withdrawal
Function: withdrawMove(uint gameId, string memory clear)
Alice's Input:
gameId: 1
clear: "2-alicewith" (the exact string she hashed for withdrawal)
Smart Contract Call:
withdrawMove(1, "2-alicewith");
Returns: 2
✅ Alice withdrew move 2 (Paper) ✅ Alice keeps: Rock (move 1)
Step 5.2: Bob Reveals Withdrawal
Function: withdrawMove(uint gameId, string memory clear)
Bob's Input:
gameId: 1
clear: "1-bobwith" (the exact string he hashed for withdrawal)
Smart Contract Call:
withdrawMove(1, "1-bobwith");
Returns: 1
✅ Bob withdrew move 1 (Rock) ✅ Bob keeps: Scissors (move 2)
Final Battle:
- Alice's final move: Rock
- Bob's final move: Scissors
- Winner: Alice! (Rock beats Scissors)
✅ Game automatically determines outcome and advances to Done phase
🎮 PHASE 6: GET OUTCOME
Goal: Either player calls to finalize and receive payout
Step 6.1: Get Outcome (Either Player Can Call)
Function: getOutcome(uint gameId)
Input:
gameId: 1
Smart Contract Call:
getOutcome(1);
Returns: Outcomes.A (Alice wins)
What Happens:
- Contract calculates winner: Rock beats Scissors
- Alice receives 0.02 ETH (both players' bets)
- Game is marked inactive
- Contract balance reduced by payout
✅ Game Complete! Alice won and received 0.02 ETH
📊 Summary Table
| Step | Who Acts | Function | Key Inputs | Result |
|---|---|---|---|---|
| 1.1 | Alice | register(0, "Alice") |
gameId=0, name="Alice", value=0.01 ETH | Player 1 in Game 1 |
| 1.2 | Bob | register(1, "Bob") |
gameId=1, name="Bob", value=0.01 ETH | Player 2 in Game 1 |
| 2.1 | Alice | commitInitialMoves(1, hash1, hash2) |
Hashes of "1-alicepass1" and "2-alicepass2" | Committed |
| 2.2 | Bob | commitInitialMoves(1, hash1, hash2) |
Hashes of "1-bobsecret1" and "3-bobsecret2" | Committed |
| 3.1 | Alice | revealInitialMoves(1, clear1, clear2) |
"1-alicepass1", "2-alicepass2" | Reveals Rock + Paper |
| 3.2 | Bob | revealInitialMoves(1, clear1, clear2) |
"1-bobsecret1", "3-bobsecret2" | Reveals Rock + Scissors |
| 4.1 | Alice | commitWithdraw(1, wHash) |
Hash of "2-alicewith" | Committed withdrawal |
| 4.2 | Bob | commitWithdraw(1, wHash) |
Hash of "1-bobwith" | Committed withdrawal |
| 5.1 | Alice | withdrawMove(1, clear) |
"2-alicewith" | Withdrew Paper, keeps Rock |
| 5.2 | Bob | withdrawMove(1, clear) |
"1-bobwith" | Withdrew Rock, keeps Scissors |
| 6.1 | Either | getOutcome(1) |
gameId=1 | Alice wins, gets 0.02 ETH |
⚠️ Important Notes
Must Save These Strings!
- Initial moves cleartext: You need them in Phase 3
- Withdrawal cleartext: You need it in Phase 5
- If you lose these strings, you cannot reveal and will timeout!
Order Matters
- Either player can act first within each phase
- Game won't advance until both players complete the current phase
- You have 10 minutes per phase (commit or reveal)
Common Mistakes
❌ Using different strings in reveal than in commit → "Hash mismatch"
❌ Committing same move twice (e.g., Rock + Rock) → "Moves must differ"
❌ Second player betting different amount → "Bet must match initial"
❌ Withdrawing index 0 or 3 → "Index must be 1 or 2"
❌ Losing your cleartext strings → Cannot reveal, will timeout
Timeout Protection
If your opponent doesn't act within 10 minutes:
resolveTimeout(1); // Call this to win by default