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; // MinusOne mode props selectedMove1?: string | null; setSelectedMove1?: (move: string | null) => void; selectedMove2?: string | null; setSelectedMove2?: (move: string | null) => void; secret1?: string; setSecret1?: (secret: string) => void; secret2?: string; setSecret2?: (secret: string) => void; isWithdrawPhase?: boolean; } 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, selectedMove1, setSelectedMove1, selectedMove2, setSelectedMove2, secret1, setSecret1, secret2, setSecret2, isWithdrawPhase = false, }: Readonly) { const [loading, setLoading] = useState(false); const [playMove, setPlayMove] = useState(""); const [playMove1, setPlayMove1] = useState(""); const [playMove2, setPlayMove2] = 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); const isMinusOne = gameDetails?.gameMode === "minusone"; // Update encrypted move when move or secret changes useEffect(() => { if (isMinusOne && !isWithdrawPhase) { // MinusOne initial commit: two moves if (selectedMove1 && secret1) { const clearMove1 = `${selectedMove1}-${secret1}`; const hash1 = Web3.utils.keccak256(clearMove1); setPlayMove1(hash1); } if (selectedMove2 && secret2) { const clearMove2 = `${selectedMove2}-${secret2}`; const hash2 = Web3.utils.keccak256(clearMove2); setPlayMove2(hash2); } } else if (selectedMove && secret) { // Classic mode or withdrawal phase: single move/choice const clearMove = `${selectedMove}-${secret}`; const hash = Web3.utils.keccak256(clearMove); setPlayMove(hash); if (!isWithdrawPhase) { savePlayMove(hash); } } }, [selectedMove, secret, selectedMove1, secret1, selectedMove2, secret2, isMinusOne, isWithdrawPhase, 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 player = gameDetails[whoAmI === "player1" ? "playerA" : "playerB"]; if (isMinusOne && !isWithdrawPhase) { // Check hash1 for initial commit phase setSelfPlayed(player.hash1 && Number(player.hash1) !== 0 ? "true" : "false"); } else if (isMinusOne && isWithdrawPhase) { // Check wHash for withdrawal commit phase setSelfPlayed(player.wHash && Number(player.wHash) !== 0 ? "true" : "false"); } else { // Classic mode: check encrMove setSelfPlayed(player.encrMove && Number(player.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 opponent = gameDetails[opponentKey]; if (isMinusOne && !isWithdrawPhase) { setOpponentPlayed(opponent.hash1 && Number(opponent.hash1) !== 0 ? "true" : "false"); } else if (isMinusOne && isWithdrawPhase) { setOpponentPlayed(opponent.wHash && Number(opponent.wHash) !== 0 ? "true" : "false"); } else { setOpponentPlayed(opponent.encrMove && Number(opponent.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 { let res: boolean = false; if (isMinusOne && !isWithdrawPhase) { const playerAHash = gameDetails.playerA.hash1; const playerBHash = gameDetails.playerB.hash1; res = !!(playerAHash && playerBHash && Number(playerAHash) !== 0 && Number(playerBHash) !== 0); } else if (isMinusOne && isWithdrawPhase) { const playerAWHash = gameDetails.playerA.wHash; const playerBWHash = gameDetails.playerB.wHash; res = !!(playerAWHash && playerBWHash && Number(playerAWHash) !== 0 && Number(playerBWHash) !== 0); } else { const playerAEncrMove = gameDetails.playerA.encrMove; const playerBEncrMove = gameDetails.playerB.encrMove; res = !!(playerAEncrMove && playerBEncrMove && 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 { let timeLeft; if (isMinusOne) { // MinusOne uses getTimeLeft() for all phases timeLeft = await contract.methods.getTimeLeft(gameDetails.returnGameId).call(); } else { // Classic uses commitTimeLeft() 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) return; setLoading(true); try { let tx; if (isMinusOne && !isWithdrawPhase) { // MinusOne initial commit: commitInitialMoves(gameId, hash1, hash2) if (!playMove1 || !playMove2) { showToast("Please select both moves and enter secrets", "error"); return; } tx = contract.methods.commitInitialMoves(gameDetails?.returnGameId, playMove1, playMove2); } else if (isMinusOne && isWithdrawPhase) { // MinusOne withdrawal commit: commitWithdraw(gameId, wHash) if (!playMove) { showToast("Please select withdrawal choice and enter secret", "error"); return; } tx = contract.methods.commitWithdraw(gameDetails?.returnGameId, playMove); } else { // Classic mode: play(gameId, hash) if (!playMove) { showToast("Please select a move and enter secret", "error"); return; } 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("Commit tx sent: " + result, "success"); setMoveSubmitted(true); } catch (err: any) { showToast("Commit failed: " + err.message, "error"); } finally { setLoading(false); } }; const regenerateSecret = () => { const randomHex = Math.random().toString(16).slice(2, 18); if (isMinusOne && !isWithdrawPhase) { // For MinusOne, we might want separate secrets // For now, let's keep them simple setSecret1?.(randomHex); } else { setSecret(randomHex); } }; const regenerateSecret2 = () => { const randomHex = Math.random().toString(16).slice(2, 18); setSecret2?.(randomHex); }; const handleSecretChange = (value: string) => { setSecret(value); }; const handleSecret1Change = (value: string) => { setSecret1?.(value); }; const handleSecret2Change = (value: string) => { setSecret2?.(value); }; const handleMoveSelect = (move: string) => { setSelectedMove(move); }; const handleMove1Select = (move: string) => { setSelectedMove1?.(move); }; const handleMove2Select = (move: string) => { setSelectedMove2?.(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 (
{/* Player Info */} {gameDetails && (

You

{whoAmI === "player1" ? gameDetails.playerA.nickname : gameDetails.playerB.nickname}

VS

Opponent

{whoAmI === "player1" ? gameDetails.playerB.nickname || "Waiting..." : gameDetails.playerA.nickname}

)} {/* 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 - Different UI for MinusOne vs Classic */} {isMinusOne && !isWithdrawPhase ? ( // MinusOne Mode: Select TWO different moves <>

Choose your FIRST move:

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

Choose your SECOND move (must be different):

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

Keep both secrets safe! You'll need them to reveal your moves later.

) : isWithdrawPhase ? ( // Withdrawal Phase: Choose which move to withdraw (1 or 2) <>

🎯 Withdrawal Phase

Choose which move to WITHDRAW (1 or 2). The remaining move will be used in the final battle!

Which move do you want to withdraw?

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

This secret is for your withdrawal choice. Keep it safe!

) : ( // Classic Mode: Select ONE move <>

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 */}
{isMinusOne && !isWithdrawPhase ? ( <>

Move 1:

{playMove1 || "Select first move and enter secret"}

Move 2:

{playMove2 || "Select second move and enter secret"}
) : (
{playMove || "Select a move/choice and enter a secret"}
)}
{/* Action Buttons */}
)} )} )}
); }