import { useState, useEffect } from "react"; import Web3 from "web3"; import { Button } from "./Button"; import { Input } from "./Input"; import { GameDetails } from "./GameModal"; import { showToast } from "@/app/lib/toast"; interface CommitProps { account: string; contract: any; config: Config | null; web3: Web3 | null; setStatus: (status: string) => void; selectedMove: string | null; setSelectedMove: (move: string | null) => void; secret: string; whoAmI: "player1" | "player2" | ""; gameDetails: GameDetails | null; setSecret: (secret: string) => void; savePlayMove: (playMove: string) => void; } type MoveName = "Rock" | "Paper" | "Scissors"; const MOVES: Record = { "1": { name: "Rock", icon: "✊" }, "2": { name: "Paper", icon: "✋" }, "3": { name: "Scissors", icon: "✌️" }, }; export default function Commit({ account, contract, config, web3, setStatus, selectedMove, setSelectedMove, secret, setSecret, savePlayMove, whoAmI, gameDetails }: Readonly) { const [loading, setLoading] = useState(false); const [playMove, setPlayMove] = useState(""); const [selfPlayed, setSelfPlayed] = useState(""); const [opponentPlayed, setOpponentPlayed] = useState(""); const [bothPlayed, setBothPlayed] = useState(""); const [autoCheckInterval, setAutoCheckInterval] = useState(null); const [moveSubmitted, setMoveSubmitted] = useState(false); const [commitTimeLeft, setCommitTimeLeft] = useState(0); const [timeoutExpired, setTimeoutExpired] = useState(false); // Update encrypted move when move or secret changes useEffect(() => { if (selectedMove && secret) { const clearMove = `${selectedMove}-${secret}`; // Use keccak256 (Ethereum's standard hash function) const hash = Web3.utils.keccak256(clearMove); setPlayMove(hash); // Persist to sessionStorage through parent savePlayMove(hash); } }, [selectedMove, secret, savePlayMove]); // Auto-check if both players have committed and trigger callback useEffect(() => { if (!contract || !account || !whoAmI || !gameDetails) { // Clear interval if conditions not met or already both played if (autoCheckInterval) clearInterval(autoCheckInterval); setAutoCheckInterval(null); return; } const checkSelfPlayed = async () => { try { const encrMove = gameDetails[whoAmI === "player1" ? "playerA" : "playerB"].encrMove; setSelfPlayed(Number(encrMove) !== 0 ? "true" : "false"); } catch (err: any) { console.error("Auto-check self played failed:", err.message); } }; checkSelfPlayed(); const checkOpponentPlayed = async () => { try { const opponentKey = whoAmI === "player1" ? "playerB" : "playerA"; const encrMove = gameDetails[opponentKey].encrMove; setOpponentPlayed(Number(encrMove) !== 0 ? "true" : "false"); } catch (err: any) { console.error("Auto-check opponent played failed:", err.message); } }; checkOpponentPlayed(); // Check immediately on mount or when dependencies change const checkBothPlayed = async () => { try { const playerAEncrMove = gameDetails.playerA.encrMove; const playerBEncrMove = gameDetails.playerB.encrMove; const res = Number(playerAEncrMove) !== 0 && Number(playerBEncrMove) !== 0; console.log("Both played check:", res); if (res) { setBothPlayed("true"); } } catch (err: any) { console.error("Auto-check failed:", err.message); } }; checkBothPlayed(); // Check commit timeout const checkCommitTimeout = async () => { try { const timeLeft = await contract.methods.commitTimeLeft(gameDetails.returnGameId).call(); console.log("Commit time left:", timeLeft); setCommitTimeLeft(Number(timeLeft)); if (Number(timeLeft) <= 0) { setTimeoutExpired(true); } } catch (err: any) { console.error("Commit timeout check failed:", err.message); } }; checkCommitTimeout(); // Set up interval to check every 2 seconds const interval = setInterval(() => { checkBothPlayed(); checkCommitTimeout(); }, 2000); setAutoCheckInterval(interval); return () => { if (interval) clearInterval(interval); }; }, [contract, account, playMove, bothPlayed, gameDetails]); // Commit phase read-only handlers const handlePlay = async () => { if (!contract || !web3 || !account || !playMove) return; setLoading(true); try { // playMove should be a hex string (bytes32) const tx = contract.methods.play(gameDetails?.returnGameId, playMove); const gas = await tx.estimateGas({ from: account }); const result = await (globalThis as any).ethereum.request({ method: "eth_sendTransaction", params: [ { from: account, to: config?.GAME_CONTRACT_ADDRESS, data: tx.encodeABI(), gas: web3.utils.toHex(gas), chainId: web3.utils.toHex(await web3.eth.net.getId()), }, ], }); showToast("Play tx sent: " + result, "success"); setMoveSubmitted(true); } catch (err: any) { showToast("Play failed: " + err.message, "error"); } finally { setLoading(false); } }; const regenerateSecret = () => { const randomHex = Math.random().toString(16).slice(2, 18); setSecret(randomHex); }; const handleSecretChange = (value: string) => { setSecret(value); }; const handleMoveSelect = (move: string) => { setSelectedMove(move); }; const handleResolveTimeout = async () => { if (!contract || !web3 || !account) return; setLoading(true); try { const tx = contract.methods.resolveTimeout(gameDetails?.returnGameId); const gas = await tx.estimateGas({ from: account }); const result = await (globalThis as any).ethereum.request({ method: "eth_sendTransaction", params: [ { from: account, to: config?.GAME_CONTRACT_ADDRESS, data: tx.encodeABI(), gas: web3.utils.toHex(gas), chainId: web3.utils.toHex(await web3.eth.net.getId()), }, ], }); showToast("Timeout resolved: " + result, "success"); } catch (err: any) { console.error("Timeout resolution failed:", err); showToast("Timeout resolution failed: " + err.message, "error"); } finally { setLoading(false); } }; // Check if current player can resolve timeout (they committed, opponent didn't, and game is still active) const canResolveTimeout = timeoutExpired && selfPlayed === "true" && gameDetails?.isActive; // Check if game is finished due to timeout const isGameFinishedByTimeout = !gameDetails?.isActive && (Number(gameDetails?.outcome) === 4 || Number(gameDetails?.outcome) === 5); // Determine if current player won/lost the timeout const didIWinTimeout = (whoAmI === "player2" && Number(gameDetails?.outcome) === 4) || (whoAmI === "player1" && Number(gameDetails?.outcome) === 5); return (
{/* Show timeout result after game is inactive */} {isGameFinishedByTimeout ? (
{didIWinTimeout ? (

🎉 Victory by Timeout!

Your opponent failed to commit in time. You claimed victory!

) : (

⏱️ Timeout Loss

You failed to commit in time. Your opponent claimed victory!

)}
) : ( <> {/* Timeout Warning - Only show to eligible player */} {timeoutExpired && canResolveTimeout ? (

⏱️ Commit phase timeout expired!

The opponent failed to commit in time. You can claim victory!

) : timeoutExpired && !canResolveTimeout ? ( // Show waiting message to opponent

Opponent claiming victory...

The timeout has expired. Waiting for the opponent to resolve.

) : ( <> {/* Time Left Display */} {commitTimeLeft > 0 && opponentPlayed === "true" && (

⏱️ Time Left: {commitTimeLeft}s

)} {moveSubmitted || selfPlayed === "true" ? ( // Waiting animation after move is submitted

Waiting for opponent...

Your move has been submitted. Stand by while the other player commits.

) : ( <> {/* Move Selection */}

Choose your move:

{(["1", "2", "3"] as const).map((move) => ( ))}
{/* Secret Input */}
handleSecretChange(e.target.value)} placeholder="Your secret passphrase" className="flex-1" />

Keep this secret safe! It's needed to reveal your move later.

{/* Encrypted Move Display */}
{playMove || "Select a move and enter a secret"}
{/* Action Buttons */}
)} )} )}
); }