import { useState, useEffect } from "react"; import Web3 from "web3"; import { Button } from "./Button"; import { GameDetails } from "./GameModal"; import { showSuccessToast, showErrorToast } from "@/app/lib/toast"; interface RevealProps { account: string; contract: any; config: Config | null; web3: Web3 | null; setStatus: (status: string) => void; selectedMove: string | null; secret: string; gameDetails: GameDetails | null; whoAmI: "player1" | "player2" | ""; } type MoveName = "Rock" | "Paper" | "Scissors"; const MOVES: Record = { "1": { name: "Rock", icon: "✊" }, "2": { name: "Paper", icon: "✋" }, "3": { name: "Scissors", icon: "✌️" }, }; const OUTCOMES: Record = { 0: { name: "None", emoji: "❓", color: "gray" }, 1: { name: "You Won!", emoji: "🏆", color: "green" }, 2: { name: "You Lost", emoji: "😢", color: "red" }, 3: { name: "Draw", emoji: "🤝", color: "yellow" }, }; export default function Reveal({ account, contract, config, web3, setStatus, selectedMove, secret, gameDetails, whoAmI, }: Readonly) { const [loading, setLoading] = useState(false); const [selfRevealed, setSelfRevealed] = useState(false); const [opponentRevealed, setOpponentRevealed] = useState(false); const [bothRevealed, setBothRevealed] = useState(false); const [outcome, setOutcome] = useState(0); const [revealTimeLeft, setRevealTimeLeft] = useState(0); const [timeoutExpired, setTimeoutExpired] = useState(false); const clearMove = selectedMove && secret ? `${selectedMove}-${secret}` : ""; // Check game status on mount useEffect(() => { const setStateFromGameDetails = () => { if (!gameDetails) return; const playerARevealed = Number(gameDetails.playerA.move) !== 0; const playerBRevealed = Number(gameDetails.playerB.move) !== 0; setSelfRevealed( (whoAmI === "player1" && playerARevealed) || (whoAmI === "player2" && playerBRevealed) ); setOpponentRevealed( (whoAmI === "player1" && playerBRevealed) || (whoAmI === "player2" && playerARevealed) ); setBothRevealed(playerARevealed && playerBRevealed); if(bothRevealed){ if(Number(gameDetails.outcome) === 1 && whoAmI === "player1") setOutcome(1); else if(Number(gameDetails.outcome) === 2 && whoAmI === "player2") setOutcome(1); else if(Number(gameDetails.outcome) === 1 && whoAmI === "player2") setOutcome(2); else if(Number(gameDetails.outcome) === 2 && whoAmI === "player1") setOutcome(2); else setOutcome(3); } }; setStateFromGameDetails(); // Check reveal timeout const checkRevealTimeout = async () => { if (!contract || !gameDetails) return; try { const timeLeft = await contract.methods.revealTimeLeft(gameDetails.returnGameId).call(); setRevealTimeLeft(Number(timeLeft)); if (Number(timeLeft) <= 0) { setTimeoutExpired(true); } } catch (err: any) { console.error("Reveal timeout check failed:", err.message); } }; checkRevealTimeout(); const interval = setInterval(checkRevealTimeout, 2000); return () => clearInterval(interval); }, [gameDetails, contract, account, whoAmI]); const handleReveal = async () => { if (!contract || !web3 || !account || !clearMove) return; setLoading(true); try { const tx = contract.methods.reveal(gameDetails?.returnGameId, clearMove); 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()), }, ], }); showSuccessToast("Reveal tx sent: " + result); } catch (err: any) { console.error("Reveal failed:", err); showErrorToast("Reveal failed: " + err.message); } finally { setLoading(false); } }; const handleGetOutcome = async () => { if (!contract || !web3 || !account) return; setLoading(true); try { const tx = contract.methods.getOutcome(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()), }, ], }); showSuccessToast("Claim tx sent: " + result); } catch (err: any) { console.error(err); showErrorToast("Claim failed: " + err.message); } finally { setLoading(false); } }; 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()), }, ], }); showSuccessToast("Timeout resolved: " + result); } catch (err: any) { console.error(err); showErrorToast("Timeout resolution failed: " + err.message); } finally { setLoading(false); } }; const outcomeData = OUTCOMES[outcome] || OUTCOMES[0]; // 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 reveal in time. You claimed victory!

) : (

⏱️ Timeout Loss

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

)}
)} {!isGameFinishedByTimeout && ( <> {/* Your Move Section - Hidden when both revealed */} {!bothRevealed && !timeoutExpired && (

Your Move

{selectedMove ? (
{MOVES[selectedMove].icon} {MOVES[selectedMove].name}

Clear Move:

{clearMove}
) : (

No move selected yet

)}
)} {/* Timeout Warning */} {timeoutExpired && (

⏱️ Reveal phase timeout expired!

{selfRevealed ? "The opponent failed to reveal in time. You can claim victory!" : "You failed to reveal in time. The opponent can claim victory!"}

{selfRevealed && ( )}
)} {/* Game Status Section - Hidden when both revealed */} {!bothRevealed && !timeoutExpired && ( <>

{selfRevealed ? "✅" : "⏳"}

Me

{selfRevealed ? "Revealed" : "Waiting"}

{opponentRevealed ? "✅" : "⏳"}

Opponent

{opponentRevealed ? "Revealed" : "Waiting"}

⏱️

Time Left

{revealTimeLeft}s

)} {/* Reveal Section - Hidden when both revealed */} {!bothRevealed && !timeoutExpired && (

Reveal Your Move

Submit your clear move and secret to the blockchain. This proves you didn't cheat!

)} {/* Winner Section - Only show if both revealed */} {bothRevealed && (
{/* Moves Comparison */}

Final Moves

{/* Your Move (always on left) */}
{gameDetails && MOVES[String(whoAmI === "player1" ? gameDetails.playerA.move : gameDetails.playerB.move)]?.icon} You {gameDetails && MOVES[String(whoAmI === "player1" ? gameDetails.playerA.move : gameDetails.playerB.move)]?.name}
{/* VS */}
VS
{/* Opponent Move (always on right) */}
{gameDetails && MOVES[String(whoAmI === "player1" ? gameDetails.playerB.move : gameDetails.playerA.move)]?.icon} Opponent {gameDetails && MOVES[String(whoAmI === "player1" ? gameDetails.playerB.move : gameDetails.playerA.move)]?.name}
{/* Outcome Section */}

{outcomeData.emoji}

{outcomeData.name}

{/* Display ETH winnings */} {gameDetails && outcome !== 0 && (

{outcome === 1 ? "You Won" : outcome === 3 ? "Draw" : "You Lost"}

{outcome === 1 ? ( <> +{web3?.utils.fromWei(String(BigInt(gameDetails.initialBet) * BigInt(2)), "ether")} ETH ) : outcome === 3 ? ( <> +{web3?.utils.fromWei(gameDetails.initialBet, "ether")} ETH ) : ( <> -{web3?.utils.fromWei(gameDetails.initialBet, "ether")} ETH )}

)} {/* Show Claim Coins button only on win or draw and if game is active */} {(outcome === 1 || outcome === 3) && gameDetails?.isActive && ( )}
)} )}
); }