From cff227ac68739334a43b9d81455d03cced9d3178 Mon Sep 17 00:00:00 2001 From: smaubio <118258478+smaubio@users.noreply.github.com> Date: Fri, 28 Nov 2025 16:45:49 +0100 Subject: [PATCH] draft of new mode --- crypto_clash_contract/contracts/Game.sol | 8 +- .../contracts/GameMinusOne.sol | 709 ++++++++++++++++++ crypto_clash_frontend/app/clash/GameList.tsx | 33 +- crypto_clash_frontend/app/clash/GameModal.tsx | 1 + 4 files changed, 744 insertions(+), 7 deletions(-) create mode 100644 crypto_clash_contract/contracts/GameMinusOne.sol diff --git a/crypto_clash_contract/contracts/Game.sol b/crypto_clash_contract/contracts/Game.sol index 5dcaded..909f715 100644 --- a/crypto_clash_contract/contracts/Game.sol +++ b/crypto_clash_contract/contracts/Game.sol @@ -40,6 +40,7 @@ contract Game { uint initialBet; uint gameId; bool isActive; + string gameMode; // "classic" for this contract } // Mapping from game ID to game state @@ -112,6 +113,7 @@ contract Game { games[gameId].gameId = gameId; games[gameId].isActive = true; + games[gameId].gameMode = "classic"; gameIds.push(gameId); return gameId; @@ -454,7 +456,8 @@ contract Game { uint initialBet, Outcomes outcome, bool isActive, - uint returnGameId + uint returnGameId, + string memory gameMode ) { GameState storage game = games[gameId]; @@ -465,7 +468,8 @@ contract Game { game.initialBet, game.outcome, game.isActive, - game.gameId + game.gameId, + game.gameMode ); } diff --git a/crypto_clash_contract/contracts/GameMinusOne.sol b/crypto_clash_contract/contracts/GameMinusOne.sol new file mode 100644 index 0000000..ff43658 --- /dev/null +++ b/crypto_clash_contract/contracts/GameMinusOne.sol @@ -0,0 +1,709 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.7.3; + +contract GameMinusOne { + uint public constant BET_MIN = 1e16; // The minimum bet (0.01 ETH) + uint public constant REVEAL_TIMEOUT = 10 minutes; // Max delay of revelation phase + uint public constant COMMIT_TIMEOUT = 10 minutes; // Max delay of commit phase + uint public constant WITHDRAW_TIMEOUT = 10 minutes; // Max delay for withdrawal phase + + enum Moves { + None, + Rock, + Paper, + Scissors + } + + enum Outcomes { + None, + PlayerA, + PlayerB, + Draw, + PlayerATimeout, + PlayerBTimeout + } // Possible outcomes + + enum GamePhase { + Registration, // Waiting for players + InitialCommit, // Players commit 2 moves each + FirstReveal, // Players reveal both moves + Withdrawal, // Players choose which move to withdraw + FinalCommit, // Players commit their remaining move again (for fairness) + FinalReveal, // Players reveal final move + Completed // Game finished + } + + struct Player { + address payable addr; + uint bet; + // Initial phase - 2 moves + bytes32 encrMove1; + bytes32 encrMove2; + Moves move1; + Moves move2; + // Withdrawal phase + uint withdrawnMoveIndex; // 1 or 2 + // Final phase + bytes32 encrFinalMove; + Moves finalMove; + string nickname; + } + + struct GameState { + Player playerA; + Player playerB; + Outcomes outcome; + GamePhase phase; + uint firstCommit; + uint firstReveal; + uint firstWithdraw; + uint firstFinalCommit; + uint firstFinalReveal; + uint initialBet; + uint gameId; + bool isActive; + string gameMode; // "minusone" for this contract + } + + // 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; + + // ------------------------- Registration ------------------------- // + + modifier validBet(uint gameId) { + require(msg.value >= BET_MIN, "Minimum bet not met"); + require( + games[gameId].initialBet == 0 || + msg.value == games[gameId].initialBet, + "Bet value must match initial bet" + ); + _; + } + + // Register a player to an existing game or create a new game. + // If gameId is 0, player will join or create the first available game. + // Return player's ID and game ID upon successful registration. + function register( + uint gameId, + string memory nickname + ) + public + payable + validBet(gameId) + returns (uint playerId, uint returnGameId) + { + // If gameId is 0, find an open game or create a new one + if (gameId == 0) { + gameId = createNewGame(); + } + + require(games[gameId].isActive, "Game is not active"); + require(games[gameId].phase == GamePhase.Registration, "Game already started"); + require(bytes(nickname).length > 0, "Nickname cannot be empty"); + require(bytes(nickname).length <= 20, "Nickname too long (max 20 characters)"); + + GameState storage game = games[gameId]; + + if (game.playerA.addr == address(0x0)) { + game.playerA.addr = payable(msg.sender); + game.playerA.nickname = nickname; + game.initialBet = msg.value; + return (1, gameId); + } else if (game.playerB.addr == address(0x0)) { + require( + msg.sender != game.playerA.addr, + "Cannot play against yourself" + ); + game.playerB.addr = payable(msg.sender); + game.playerB.nickname = nickname; + + // Both players registered, automatically start the game + game.phase = GamePhase.InitialCommit; + + return (2, gameId); + } + + revert("Game is full"); + } + + + // Create a new game + function createNewGame() private returns (uint) { + uint gameId = nextGameId; + nextGameId++; + + games[gameId].gameId = gameId; + games[gameId].isActive = true; + games[gameId].phase = GamePhase.Registration; + games[gameId].gameMode = "minusone"; + gameIds.push(gameId); + + return gameId; + } + + // ------------------------- Initial Commit (2 moves) ------------------------- // + + modifier isRegistered(uint gameId) { + require(gameId != 0, "Invalid game ID"); + require( + msg.sender == games[gameId].playerA.addr || + msg.sender == games[gameId].playerB.addr, + "Player not registered in this game" + ); + _; + } + + // Commit two moves for the initial phase + // encrMove1 and encrMove2 must be "<1|2|3>-password" hashed with keccak256 + function commitInitialMoves(uint gameId, bytes32 encrMove1, bytes32 encrMove2) + public + isRegistered(gameId) + returns (bool) + { + GameState storage game = games[gameId]; + + require(game.isActive, "Game is no longer active"); + require(game.phase == GamePhase.InitialCommit, "Not in initial commit phase"); + require(encrMove1 != bytes32(0) && encrMove2 != bytes32(0), "Encrypted moves cannot be zero"); + require(encrMove1 != encrMove2, "Both moves must be different"); + + // Check timeout + if (game.firstCommit != 0) { + require(block.timestamp <= game.firstCommit + COMMIT_TIMEOUT, "Commit phase timeout expired"); + } + + // Track first commit timestamp + if (game.firstCommit == 0) { + game.firstCommit = block.timestamp; + } + + // Store encrypted moves + if (msg.sender == game.playerA.addr) { + require(game.playerA.encrMove1 == bytes32(0), "Player A already committed"); + game.playerA.encrMove1 = encrMove1; + game.playerA.encrMove2 = encrMove2; + } else if (msg.sender == game.playerB.addr) { + require(game.playerB.encrMove1 == bytes32(0), "Player B already committed"); + game.playerB.encrMove1 = encrMove1; + game.playerB.encrMove2 = encrMove2; + } else { + revert("Caller not registered"); + } + + // Check if both players have committed, advance to reveal phase + if (game.playerA.encrMove1 != bytes32(0) && game.playerB.encrMove1 != bytes32(0)) { + game.phase = GamePhase.FirstReveal; + game.firstReveal = 0; // Reset for reveal phase + } + + return true; + } + + // ------------------------- First Reveal (2 moves) ------------------------- // + + // Reveal both initial moves + // clearMove1 and clearMove2 must be the original strings used for hashing + function revealInitialMoves(uint gameId, string memory clearMove1, string memory clearMove2) + public + isRegistered(gameId) + returns (Moves, Moves) + { + GameState storage game = games[gameId]; + + require(game.isActive, "Game is no longer active"); + require(game.phase == GamePhase.FirstReveal, "Not in first reveal phase"); + + // Check timeout + if (game.firstReveal != 0) { + require(block.timestamp <= game.firstReveal + REVEAL_TIMEOUT, "Reveal phase timeout expired"); + } + + bytes32 encrMove1 = keccak256(abi.encodePacked(clearMove1)); + bytes32 encrMove2 = keccak256(abi.encodePacked(clearMove2)); + Moves move1 = Moves(getFirstChar(clearMove1)); + Moves move2 = Moves(getFirstChar(clearMove2)); + + require(move1 != Moves.None && move2 != Moves.None, "Invalid moves"); + require(move1 != move2, "Both moves must be different"); + + // Verify and store moves + if (msg.sender == game.playerA.addr) { + require(encrMove1 == game.playerA.encrMove1 && encrMove2 == game.playerA.encrMove2, + "Hash mismatch for Player A"); + game.playerA.move1 = move1; + game.playerA.move2 = move2; + } else if (msg.sender == game.playerB.addr) { + require(encrMove1 == game.playerB.encrMove1 && encrMove2 == game.playerB.encrMove2, + "Hash mismatch for Player B"); + game.playerB.move1 = move1; + game.playerB.move2 = move2; + } else { + revert("Caller not registered"); + } + + // Start reveal timer on first reveal + if (game.firstReveal == 0) { + game.firstReveal = block.timestamp; + } + + // Check if both players have revealed, advance to withdrawal phase + if (game.playerA.move1 != Moves.None && game.playerB.move1 != Moves.None) { + game.phase = GamePhase.Withdrawal; + game.firstWithdraw = 0; // Reset for withdrawal phase + } + + return (move1, move2); + } + + // ------------------------- Withdrawal Phase ------------------------- // + + // Choose which move to withdraw (1 or 2) + function withdrawMove(uint gameId, uint moveIndex) + public + isRegistered(gameId) + returns (bool) + { + GameState storage game = games[gameId]; + + require(game.isActive, "Game is no longer active"); + require(game.phase == GamePhase.Withdrawal, "Not in withdrawal phase"); + require(moveIndex == 1 || moveIndex == 2, "Move index must be 1 or 2"); + + // Check timeout + if (game.firstWithdraw != 0) { + require(block.timestamp <= game.firstWithdraw + WITHDRAW_TIMEOUT, "Withdrawal phase timeout expired"); + } + + // Store withdrawal choice + if (msg.sender == game.playerA.addr) { + require(game.playerA.withdrawnMoveIndex == 0, "Player A already withdrew"); + game.playerA.withdrawnMoveIndex = moveIndex; + } else if (msg.sender == game.playerB.addr) { + require(game.playerB.withdrawnMoveIndex == 0, "Player B already withdrew"); + game.playerB.withdrawnMoveIndex = moveIndex; + } else { + revert("Caller not registered"); + } + + // Start withdrawal timer on first withdrawal + if (game.firstWithdraw == 0) { + game.firstWithdraw = block.timestamp; + } + + // Check if both players have withdrawn, advance to final commit phase + if (game.playerA.withdrawnMoveIndex != 0 && game.playerB.withdrawnMoveIndex != 0) { + game.phase = GamePhase.FinalCommit; + game.firstFinalCommit = 0; // Reset for final commit + } + + return true; + } + + // ------------------------- Final Commit (remaining move) ------------------------- // + + // Commit the remaining move again (for fairness and to prevent cheating) + function commitFinalMove(uint gameId, bytes32 encrFinalMove) + public + isRegistered(gameId) + returns (bool) + { + GameState storage game = games[gameId]; + + require(game.isActive, "Game is no longer active"); + require(game.phase == GamePhase.FinalCommit, "Not in final commit phase"); + require(encrFinalMove != bytes32(0), "Encrypted move cannot be zero"); + + // Check timeout + if (game.firstFinalCommit != 0) { + require(block.timestamp <= game.firstFinalCommit + COMMIT_TIMEOUT, "Final commit timeout expired"); + } + + // Store encrypted final move + if (msg.sender == game.playerA.addr) { + require(game.playerA.encrFinalMove == bytes32(0), "Player A already committed final move"); + game.playerA.encrFinalMove = encrFinalMove; + } else if (msg.sender == game.playerB.addr) { + require(game.playerB.encrFinalMove == bytes32(0), "Player B already committed final move"); + game.playerB.encrFinalMove = encrFinalMove; + } else { + revert("Caller not registered"); + } + + // Start final commit timer on first commit + if (game.firstFinalCommit == 0) { + game.firstFinalCommit = block.timestamp; + } + + // Check if both players have committed, advance to final reveal phase + if (game.playerA.encrFinalMove != bytes32(0) && game.playerB.encrFinalMove != bytes32(0)) { + game.phase = GamePhase.FinalReveal; + game.firstFinalReveal = 0; // Reset for final reveal + } + + return true; + } + + // ------------------------- Final Reveal ------------------------- // + + // Reveal the final move and determine winner + function revealFinalMove(uint gameId, string memory clearFinalMove) + public + isRegistered(gameId) + returns (Moves) + { + GameState storage game = games[gameId]; + + require(game.isActive, "Game is no longer active"); + require(game.phase == GamePhase.FinalReveal, "Not in final reveal phase"); + + // Check timeout + if (game.firstFinalReveal != 0) { + require(block.timestamp <= game.firstFinalReveal + REVEAL_TIMEOUT, "Final reveal timeout expired"); + } + + bytes32 encrFinalMove = keccak256(abi.encodePacked(clearFinalMove)); + Moves finalMove = Moves(getFirstChar(clearFinalMove)); + + require(finalMove != Moves.None, "Invalid move"); + + // Verify and store final move + if (msg.sender == game.playerA.addr) { + require(encrFinalMove == game.playerA.encrFinalMove, "Hash mismatch for Player A final move"); + // Verify this is the non-withdrawn move + Moves expectedMove = game.playerA.withdrawnMoveIndex == 1 ? game.playerA.move2 : game.playerA.move1; + require(finalMove == expectedMove, "Final move must be the non-withdrawn move"); + game.playerA.finalMove = finalMove; + } else if (msg.sender == game.playerB.addr) { + require(encrFinalMove == game.playerB.encrFinalMove, "Hash mismatch for Player B final move"); + // Verify this is the non-withdrawn move + Moves expectedMove = game.playerB.withdrawnMoveIndex == 1 ? game.playerB.move2 : game.playerB.move1; + require(finalMove == expectedMove, "Final move must be the non-withdrawn move"); + game.playerB.finalMove = finalMove; + } else { + revert("Caller not registered"); + } + + // Start final reveal timer on first reveal + if (game.firstFinalReveal == 0) { + game.firstFinalReveal = block.timestamp; + } + + // Check if both players have revealed final moves, determine outcome + if (game.playerA.finalMove != Moves.None && game.playerB.finalMove != Moves.None) { + determineOutcome(gameId); + } + + return finalMove; + } + + // Determine the final outcome based on both players' final moves + function determineOutcome(uint gameId) private { + GameState storage game = games[gameId]; + + if (game.playerA.finalMove == game.playerB.finalMove) { + game.outcome = Outcomes.Draw; + } else if ( + (game.playerA.finalMove == Moves.Rock && game.playerB.finalMove == Moves.Scissors) || + (game.playerA.finalMove == Moves.Paper && game.playerB.finalMove == Moves.Rock) || + (game.playerA.finalMove == Moves.Scissors && game.playerB.finalMove == Moves.Paper) + ) { + game.outcome = Outcomes.PlayerA; + } else { + game.outcome = Outcomes.PlayerB; + } + + game.phase = GamePhase.Completed; + } + + // Return first character of a given string. + // Returns 0 if the string is empty or the first character is not '1','2' or '3'. + function getFirstChar(string memory str) private pure returns (uint) { + bytes memory b = bytes(str); + if (b.length == 0) { + return 0; + } + bytes1 firstByte = b[0]; + if (firstByte == 0x31) { + return 1; + } else if (firstByte == 0x32) { + return 2; + } else if (firstByte == 0x33) { + return 3; + } else { + return 0; + } + } + + // ------------------------- Result ------------------------- // + + // Compute the outcome and pay the winner(s). + // Return the outcome. + function getOutcome(uint gameId) public returns (Outcomes) { + GameState storage game = games[gameId]; + + require(game.isActive, "Game is not active"); + require(game.phase == GamePhase.Completed, "Game not completed yet"); + require(game.outcome != Outcomes.None, "Outcome not yet determined"); + + address payable addrA = game.playerA.addr; + address payable addrB = game.playerB.addr; + uint betPlayerA = game.initialBet; + + // Reset and cleanup + resetGame(gameId); // Reset game before paying to avoid reentrancy attacks + pay(addrA, addrB, betPlayerA, game.outcome); + + return game.outcome; + } + + // Pay the winner(s). + function pay( + address payable addrA, + address payable addrB, + uint betPlayerA, + Outcomes outcome + ) private { + if (outcome == Outcomes.PlayerA) { + addrA.transfer(betPlayerA * 2); + } else if (outcome == Outcomes.PlayerB) { + addrB.transfer(betPlayerA * 2); + } else { + addrA.transfer(betPlayerA); + addrB.transfer(betPlayerA); + } + } + + // Pay to one player and slash the other (timeout resolution). + function payWithSlash( + address payable winner, + address payable loser, + uint betAmount + ) private { + // Winner gets both bets + winner.transfer(betAmount * 2); + // Loser gets nothing (slashed) + } + + // Reset a specific game. + function resetGame(uint gameId) private { + GameState storage game = games[gameId]; + + // Mark game as inactive + game.isActive = false; + + // Note: We keep the game data in the mapping for reference + // but players are now free to join other games + } + + // ------------------------- Helpers ------------------------- // + + // Return contract balance + function getContractBalance() public view returns (uint) { + return address(this).balance; + } + + // Return player's ID in their active game + function whoAmI(uint gameId) public view returns (uint) { + if (gameId == 0) { + return 0; + } + + GameState storage game = games[gameId]; + if (msg.sender == game.playerA.addr) { + return 1; + } else if (msg.sender == game.playerB.addr) { + return 2; + } else { + return 0; + } + } + + // Get the current phase of the game + function getGamePhase(uint gameId) public view returns (GamePhase) { + return games[gameId].phase; + } + + // Get time remaining in current phase + function getTimeLeft(uint gameId) public view returns (int) { + if (gameId == 0) return 0; + + GameState storage game = games[gameId]; + + if (game.phase == GamePhase.InitialCommit && game.firstCommit != 0) { + uint deadline = game.firstCommit + COMMIT_TIMEOUT; + if (block.timestamp >= deadline) return 0; + return int(deadline - block.timestamp); + } else if (game.phase == GamePhase.FirstReveal && game.firstReveal != 0) { + uint deadline = game.firstReveal + REVEAL_TIMEOUT; + if (block.timestamp >= deadline) return 0; + return int(deadline - block.timestamp); + } else if (game.phase == GamePhase.Withdrawal && game.firstWithdraw != 0) { + uint deadline = game.firstWithdraw + WITHDRAW_TIMEOUT; + if (block.timestamp >= deadline) return 0; + return int(deadline - block.timestamp); + } else if (game.phase == GamePhase.FinalCommit && game.firstFinalCommit != 0) { + uint deadline = game.firstFinalCommit + COMMIT_TIMEOUT; + if (block.timestamp >= deadline) return 0; + return int(deadline - block.timestamp); + } else if (game.phase == GamePhase.FinalReveal && game.firstFinalReveal != 0) { + uint deadline = game.firstFinalReveal + REVEAL_TIMEOUT; + if (block.timestamp >= deadline) return 0; + return int(deadline - block.timestamp); + } + + return int(COMMIT_TIMEOUT); // Default timeout + } + + // Resolve a game that has timed out + function resolveTimeout(uint gameId) public isRegistered(gameId) { + GameState storage game = games[gameId]; + require(game.isActive, "Game is not active"); + + address caller = msg.sender; + address payable winner = payable(caller); + + bool timeoutOccurred = false; + + // Check for timeout in various phases + if (game.phase == GamePhase.InitialCommit && game.firstCommit != 0) { + if (block.timestamp > game.firstCommit + COMMIT_TIMEOUT) { + // Player who didn't commit loses + if (caller == game.playerA.addr && game.playerB.encrMove1 == bytes32(0)) { + game.outcome = Outcomes.PlayerA; + timeoutOccurred = true; + } else if (caller == game.playerB.addr && game.playerA.encrMove1 == bytes32(0)) { + game.outcome = Outcomes.PlayerB; + timeoutOccurred = true; + } + } + } else if (game.phase == GamePhase.FirstReveal && game.firstReveal != 0) { + if (block.timestamp > game.firstReveal + REVEAL_TIMEOUT) { + // Player who didn't reveal loses + if (caller == game.playerA.addr && game.playerB.move1 == Moves.None) { + game.outcome = Outcomes.PlayerA; + timeoutOccurred = true; + } else if (caller == game.playerB.addr && game.playerA.move1 == Moves.None) { + game.outcome = Outcomes.PlayerB; + timeoutOccurred = true; + } + } + } else if (game.phase == GamePhase.Withdrawal && game.firstWithdraw != 0) { + if (block.timestamp > game.firstWithdraw + WITHDRAW_TIMEOUT) { + // Player who didn't withdraw loses + if (caller == game.playerA.addr && game.playerB.withdrawnMoveIndex == 0) { + game.outcome = Outcomes.PlayerA; + timeoutOccurred = true; + } else if (caller == game.playerB.addr && game.playerA.withdrawnMoveIndex == 0) { + game.outcome = Outcomes.PlayerB; + timeoutOccurred = true; + } + } + } else if (game.phase == GamePhase.FinalCommit && game.firstFinalCommit != 0) { + if (block.timestamp > game.firstFinalCommit + COMMIT_TIMEOUT) { + // Player who didn't commit final move loses + if (caller == game.playerA.addr && game.playerB.encrFinalMove == bytes32(0)) { + game.outcome = Outcomes.PlayerA; + timeoutOccurred = true; + } else if (caller == game.playerB.addr && game.playerA.encrFinalMove == bytes32(0)) { + game.outcome = Outcomes.PlayerB; + timeoutOccurred = true; + } + } + } else if (game.phase == GamePhase.FinalReveal && game.firstFinalReveal != 0) { + if (block.timestamp > game.firstFinalReveal + REVEAL_TIMEOUT) { + // Player who didn't reveal final move loses + if (caller == game.playerA.addr && game.playerB.finalMove == Moves.None) { + game.outcome = Outcomes.PlayerA; + timeoutOccurred = true; + } else if (caller == game.playerB.addr && game.playerA.finalMove == Moves.None) { + game.outcome = Outcomes.PlayerB; + timeoutOccurred = true; + } + } + } + + require(timeoutOccurred, "No timeout has occurred or caller is not the non-offending player"); + + address payable loser = winner == game.playerA.addr ? game.playerB.addr : game.playerA.addr; + + // Reset game + resetGame(gameId); + + // Pay winner and slash offender + payWithSlash(winner, loser, game.initialBet); + } + + // ------------------------- Game Management ------------------------- // + + // Get details of a specific game (for viewing any game) + function getGameDetails( + uint gameId + ) + public + view + returns ( + Player memory playerA, + Player memory playerB, + uint initialBet, + Outcomes outcome, + GamePhase phase, + bool isActive, + uint returnGameId, + string memory gameMode + ) + { + GameState storage game = games[gameId]; + require(game.gameId != 0, "Game does not exist"); + return ( + game.playerA, + game.playerB, + game.initialBet, + game.outcome, + game.phase, + game.isActive, + game.gameId, + game.gameMode + ); + } + + // Get all active game IDs + function getActiveGameIds() public view returns (uint[] memory) { + uint activeCount = 0; + + // Count active games + for (uint i = 0; i < gameIds.length; i++) { + if (games[gameIds[i]].isActive) { + activeCount++; + } + } + + // Build array of active game IDs + uint[] memory activeIds = new uint[](activeCount); + uint index = 0; + for (uint i = 0; i < gameIds.length; i++) { + if (games[gameIds[i]].isActive) { + activeIds[index] = gameIds[i]; + index++; + } + } + + return activeIds; + } + + // Advance game to initial commit phase (called automatically after both players register) + function startGame(uint gameId) public { + GameState storage game = games[gameId]; + require(game.isActive, "Game is not active"); + require(game.phase == GamePhase.Registration, "Game already started"); + require(game.playerA.addr != address(0) && game.playerB.addr != address(0), "Both players must be registered"); + + game.phase = GamePhase.InitialCommit; + } +} diff --git a/crypto_clash_frontend/app/clash/GameList.tsx b/crypto_clash_frontend/app/clash/GameList.tsx index a4e1531..90bc422 100644 --- a/crypto_clash_frontend/app/clash/GameList.tsx +++ b/crypto_clash_frontend/app/clash/GameList.tsx @@ -26,6 +26,7 @@ export default function GameList({ const [loading, setLoading] = useState(false); const [newGameBet, setNewGameBet] = useState("0.01"); const [newGameNickname, setNewGameNickname] = useState(""); + const [gameMode, setGameMode] = useState("classic"); // "classic" or "minusone" const [joinNicknames, setJoinNicknames] = useState>(new Map()); const [refreshInterval, setRefreshInterval] = useState(null); const [userGameIds, setUserGameIds] = useState>(new Set()); @@ -39,7 +40,16 @@ export default function GameList({ for (const gameId of activeGameIds) { const details = await contract.methods.getGameDetails(gameId).call(); - gameDetails.push(details); + // Map contract response to GameDetails type + gameDetails.push({ + playerA: details[0], + playerB: details[1], + initialBet: details[2], + outcome: Number(details[3]), + isActive: details[4], + returnGameId: Number(details[5]), + gameMode: details[6] || "classic", // Fallback to "classic" for older contracts + }); } setGames(gameDetails); @@ -210,6 +220,14 @@ export default function GameList({ onChange={(e) => setNewGameBet(e.target.value)} className="flex-1 min-w-[200px]" /> +