diff --git a/Frontend/src/components/GamePools.tsx b/Frontend/src/components/GamePools.tsx index f1d86f1..6caaed9 100644 --- a/Frontend/src/components/GamePools.tsx +++ b/Frontend/src/components/GamePools.tsx @@ -71,45 +71,33 @@ const PoolsInterface: React.FC = () => { eventName: "PlayerJoined", onLogs: (logs) => { - if (!logs || logs.length === 0) return; - // Process each log entry - const processedEvents = logs - .map((log) => { - // @ts-ignore - if (!log.args) return null; - - const poolId = - // @ts-ignore - typeof log.args.poolId === "bigint" - // @ts-ignore - ? Number(log.args.poolId) - // @ts-ignore - : typeof log.args.poolId === "number" - // @ts-ignore - ? log.args.poolId - : undefined; - - // @ts-ignore - const player = typeof log.args.playerThatJoined === "string" - // @ts-ignore - ? (log.args.playerThatJoined as `0x${string}`) - : undefined; - - if (poolId === undefined || !player) return null; - - return { poolId, player, timestamp: Date.now() }; - }) - .filter((event) => event !== null); - - if (processedEvents.length === 0) return; - - // Update join events - this will trigger the useEffect below - // @ts-ignore - setJoinEvents((prev) => [...prev, ...processedEvents]); - - // Visual feedback remains the same - processedEvents.forEach((event) => { - // Show toast + logs.forEach((log) => { + console.log("Received logs:", logs); + if (!log.args) return; + + const poolId = + typeof log.args.poolId === "bigint" ? log.args.poolId : undefined; + const player = + typeof log.args.playerThatJoined === "string" + ? (log.args.playerThatJoined as `0x${string}`) + : undefined; + if (!poolId || !player) { + console.error("Invalid event data structure:", log.args); + return; + } + + console.log("Player joined pool:", { poolId: Number(poolId), player }); + + // Update UI + setParticipants((prev) => [...prev, player]); + // Update the currentParticipants count for the joined pool + setNewPools((prevPools) => + prevPools.map((pool) => + pool.id === Number(poolId) + ? { ...pool, currentParticipants: pool.currentParticipants + 1 } + : pool + ) + ); toast.custom(
@@ -118,10 +106,8 @@ const PoolsInterface: React.FC = () => {

New Challenger!

- {`${event.player.substring(0, 6)}...${event.player.substring( - 38 - )}`}{" "} - joined pool #{event.poolId} + {`${player.substring(0, 6)}...${player.substring(38)}`} joined + pool #{Number(poolId)}

, @@ -131,38 +117,34 @@ const PoolsInterface: React.FC = () => { } ); - // Show pulse animation + // Show pulse animation on the pool card setShowPulse((prev) => ({ ...prev, - [event.poolId]: true, + [Number(poolId)]: true, })); - // Remove pulse after animation completes + // Remove pulse after 2 seconds setTimeout(() => { setShowPulse((prev) => ({ ...prev, - [event.poolId]: false, + [Number(poolId)]: false, })); }, 2000); }); }, }); - useEffect(() => { if (joinEvents.length === 0) return; - - // Group by poolId to handle multiple events for the same pool const poolUpdates = {}; joinEvents.forEach((event) => { - // @ts-ignore + poolUpdates[event.poolId] = (poolUpdates[event.poolId] || 0) + 1; }); - // Apply all updates at once + setNewPools((prevPools) => prevPools.map((pool) => { - // @ts-ignore const increment = poolUpdates[pool.id] || 0; if (increment === 0) return pool; @@ -187,25 +169,22 @@ const PoolsInterface: React.FC = () => { eventName: "PointsAwarded", onLogs: (logs) => { logs.forEach((log) => { - // @ts-ignore + if (!log.args || typeof log.args !== "object") { return; } // Extract and validate player address - // @ts-ignore const player = typeof log.args.player === "string" ? log.args.player : undefined; if (!player || !isAddress(player)) { return; } // Extract and validate points - // @ts-ignore const points = log.args.points !== undefined ? BigInt(log.args.points) : undefined; if (points === undefined || points < 0n) { return; } setPoints(Number(points)); - // @ts-ignore const actionType = log.args.reason !== undefined ? Number(log.args.reason) : undefined; if (actionType === undefined || ![1, 2, 3].includes(actionType)) { return; @@ -215,7 +194,6 @@ const PoolsInterface: React.FC = () => { // Show pulse animation on the pool card setShowPulse((prev) => ({ ...prev, - // @ts-ignore [Number(poolId)]: true, })); @@ -223,7 +201,6 @@ const PoolsInterface: React.FC = () => { setTimeout(() => { setShowPulse((prev) => ({ ...prev, - // @ts-ignore [Number(poolId)]: false, })); }, 2000); @@ -320,7 +297,7 @@ const PoolsInterface: React.FC = () => { abi: ABI.abi, functionName: "joinPool", args: [BigInt(poolId)], - //@ts-ignore + value: entryFee, gas: BigInt(300000), }); @@ -335,7 +312,7 @@ const PoolsInterface: React.FC = () => { setSelectedPool(pool); setIsModalOpen(true); - //@ts-ignore + const stakeText = pool.stake?.replace("$", "") || "0"; setStakeAmount(parseInt(stakeText, 10) || 0); }; diff --git a/Frontend/src/hooks/ContractReadIn.tsx b/Frontend/src/hooks/ContractReadIn.tsx new file mode 100644 index 0000000..a632778 --- /dev/null +++ b/Frontend/src/hooks/ContractReadIn.tsx @@ -0,0 +1,51 @@ +import { useState, useEffect,useCallback } from "react"; +import { useReadContract } from "wagmi"; +import CoinTossABI from "../utils/contract/CoinToss.json"; +import { CORE_CONTRACT_ADDRESS } from "../utils/contract/contract"; + +export enum PlayerChoice { + NONE = 0, + HEADS = 1, + TAILS = 2, +} + +type PlayerRoundStatus = { + hasParticipated: boolean; + choice: PlayerChoice; + isLoading: boolean; + error: Error | null; +}; + +type PlayerStatus = [boolean, boolean, boolean, boolean]; + +// Custom Hook: Game Timer +// const useGameTimer = (initialTime: number, onTimerEnd: () => void) => { +// const [timer, setTimer] = useState(initialTime); + +// useEffect(() => { +// if (timer > 0) { +// const interval = setInterval(() => setTimer((prev) => prev - 1), 1000); +// return () => clearInterval(interval); +// } else { +// onTimerEnd(); +// } +// }, [timer, onTimerEnd]); + +// return { timer }; +// }; + +// Custom Hook: Contract Interactions +export const usePlayerStatus = (poolId: bigint, address: `0x${string}`) => { + const { + data: playerStatus, + refetch: refetchPlayerStatus, + isLoading: isStatusLoading, + } = useReadContract({ + address: CORE_CONTRACT_ADDRESS as `0x${string}`, + abi: CoinTossABI.abi, + functionName: "getPlayerStatus", + args: [poolId, address], + }); + + return { playerStatus, refetchPlayerStatus, isStatusLoading }; + }; diff --git a/Frontend/src/hooks/ContractWriteIn.tsx b/Frontend/src/hooks/ContractWriteIn.tsx new file mode 100644 index 0000000..c44f428 --- /dev/null +++ b/Frontend/src/hooks/ContractWriteIn.tsx @@ -0,0 +1,67 @@ +import { useState, useEffect,useCallback } from "react"; +import { useWriteContract,useWaitForTransactionReceipt } from "wagmi"; +import CoinTossABI from "../utils/contract/CoinToss.json"; +import { CORE_CONTRACT_ADDRESS } from "../utils/contract/contract"; + +export enum PlayerChoice { + NONE = 0, + HEADS = 1, + TAILS = 2, +} + +type PlayerRoundStatus = { + hasParticipated: boolean; + choice: PlayerChoice; + isLoading: boolean; + error: Error | null; +}; + +type PlayerStatus = [boolean, boolean, boolean, boolean]; + +// Custom Hook: Game Timer +const useGameTimer = (initialTime: number, onTimerEnd: () => void) => { + const [timer, setTimer] = useState(initialTime); + + useEffect(() => { + if (timer > 0) { + const interval = setInterval(() => setTimer((prev) => prev - 1), 1000); + return () => clearInterval(interval); + } else { + onTimerEnd(); + } + }, [timer, onTimerEnd]); + + return { timer }; +}; + +// Custom Hook: Contract Interactions +export const useContractInteraction = () => { + const { writeContract, data: hash, isPending, error } = useWriteContract(); + const { isLoading: isConfirming, isSuccess: isConfirmed } = useWaitForTransactionReceipt({ hash }); + + const makeSelection = useCallback( + (poolId: bigint, choice: PlayerChoice) => { + writeContract({ + address: CORE_CONTRACT_ADDRESS as `0x${string}`, + abi: CoinTossABI.abi, + functionName: "makeSelection", + args: [poolId, choice], + }); + }, + [writeContract] + ); + + const claimPrize = useCallback( + (poolId: bigint) => { + writeContract({ + address: CORE_CONTRACT_ADDRESS as `0x${string}`, + abi: CoinTossABI.abi, + functionName: "claimPrize", + args: [poolId], + }); + }, + [writeContract] + ); + + return { makeSelection, claimPrize, isPending, isConfirming, isConfirmed, error }; +}; diff --git a/Frontend/src/hooks/usePlayerRoundStatus.tsx b/Frontend/src/hooks/usePlayerRoundStatus.tsx deleted file mode 100644 index dd41749..0000000 --- a/Frontend/src/hooks/usePlayerRoundStatus.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { useState, useEffect } from "react"; -import { useReadContract, useAccount } from "wagmi"; -import CoinTossABI from "../utils/contract/CoinToss.json"; -import { CORE_CONTRACT_ADDRESS } from "../utils/contract/contract"; - -export enum PlayerChoice { - NONE = 0, - HEADS = 1, - TAILS = 2, -} - -type PlayerRoundStatus = { - hasParticipated: boolean; - choice: PlayerChoice; - isLoading: boolean; - error: Error | null; -}; - -export function usePlayerRoundStatus( - poolId: number, - round: number -): PlayerRoundStatus { - const { address } = useAccount(); - const [status, setStatus] = useState({ - hasParticipated: false, - choice: PlayerChoice.NONE, - isLoading: true, - error: null, - }); - - const { - data: roundParticipation, - isError, - error, - isLoading: isLoadingParticipation, - } = useReadContract({ - address: CORE_CONTRACT_ADDRESS as `0x${string}`, - abi: CoinTossABI.abi, - functionName: "roundParticipation", - args: [BigInt(poolId), BigInt(round), address], - query: { - enabled: !!address && round > 0, - }, - }); - - const { data: roundSelection, isLoading: isLoadingSelection } = - useReadContract({ - address: CORE_CONTRACT_ADDRESS as `0x${string}`, - abi: CoinTossABI.abi, - functionName: "roundSelection", - args: [BigInt(poolId), BigInt(round), address], - query: { - enabled: !!address && round > 0 && !!roundParticipation, - }, - }); - - useEffect(() => { - setStatus((prev) => ({ - ...prev, - hasParticipated: !!roundParticipation, - choice: roundSelection - ? (Number(roundSelection) as PlayerChoice) - : PlayerChoice.NONE, - isLoading: isLoadingParticipation || isLoadingSelection, - error: isError ? error : null, - })); - }, [ - roundParticipation, - roundSelection, - isLoadingParticipation, - isLoadingSelection, - isError, - error, - ]); - - return status; -} diff --git a/Frontend/src/hooks/useTimer.tsx b/Frontend/src/hooks/useTimer.tsx new file mode 100644 index 0000000..ccbf703 --- /dev/null +++ b/Frontend/src/hooks/useTimer.tsx @@ -0,0 +1,80 @@ +import { useState, useEffect, useCallback } from "react"; + +export const useTimer = (initialTime: number, onTimerEnd?: () => void) => { + const [timer, setTimer] = useState(initialTime); + const [isTimerActive, setIsTimerActive] = useState(false); + + + const startTimer = useCallback(() => { + setIsTimerActive(true); + }, []); + + + const stopTimer = useCallback(() => { + setIsTimerActive(false); + }, []); + + + const resetTimer = useCallback((newTime: number) => { + setTimer(newTime); + setIsTimerActive(true); + }, []); + + + useEffect(() => { + if (isTimerActive && timer > 0) { + const interval = setInterval(() => { + setTimer((prevTimer) => prevTimer - 1); + }, 1000); + return () => clearInterval(interval); + } else if (timer === 0 && isTimerActive) { + setIsTimerActive(false); + onTimerEnd?.(); + } + }, [isTimerActive, timer, onTimerEnd]); + + return { timer, isTimerActive, startTimer, stopTimer, resetTimer }; +}; + + + +// This should be inside your component +export const checkPreviousSubmission = useCallback((address,pool,setGameState,gameState,setHasLocalSubmission) => { + if (!address || !pool?.id) return false; + + const storageKey = `cointoss_submission_${address}_${pool.id}_${gameState.round}`; + const savedSubmission = localStorage.getItem(storageKey); + + if (savedSubmission) { + try { + const submission = JSON.parse(savedSubmission); + if (submission.round === gameState.round) { + setGameState((prevState) => ({ + ...prevState, + selectedChoice: submission.choice, + hasSubmitted: true + })); + setHasLocalSubmission(true); + return true; + } + } catch (error) { + console.error("Error parsing saved submission:", error); + } + } + return false; +}, [address, pool?.id, gameState.round, setGameState, setHasLocalSubmission]); + +// Function to save submission to local storage +exportconst saveSubmissionToLocalStorage = (choice: PlayerChoice) => { + if (!address || !pool?.id) return; + + const storageKey = `cointoss_submission_${address}_${pool.id}_${gameState.round}`; + const submission = { + choice, + round: gameState.round, + timestamp: Date.now() + }; + + localStorage.setItem(storageKey, JSON.stringify(submission)); + setHasLocalSubmission(true); +}; \ No newline at end of file diff --git a/Frontend/src/page/Play.tsx b/Frontend/src/page/Play.tsx index d4e242e..69e9a8c 100644 --- a/Frontend/src/page/Play.tsx +++ b/Frontend/src/page/Play.tsx @@ -10,7 +10,6 @@ import { import { useNavigate, useLocation } from "react-router-dom"; import CoinTossABI from "../utils/contract/CoinToss.json"; import { CORE_CONTRACT_ADDRESS } from "../utils/contract/contract"; - enum PlayerChoice { NONE = 0, HEADS = 1, @@ -18,39 +17,38 @@ enum PlayerChoice { } const PlayGame = () => { + const [coinRotation, setCoinRotation] = useState(0); const navigate = useNavigate(); const location = useLocation(); const pool = location.state.pools; const { address } = useAccount(); + const [gameState, setGameState] = useState({ + isTimerActive: true, + selectedChoice: null as PlayerChoice | null, + round: 1, + hasSubmitted: false, + isCoinFlipping: false, + isSubmitting: false, + isWaitingForOthers: false, + showClaimInterface: false, + showWinnerPopup: false, + }); - const [isTimerActive, setIsTimerActive] = useState(true); - const [selectedChoice, setSelectedChoice] = useState( - null - ); - const [round, setRound] = useState(1); const [timer, setTimer] = useState(20); - const [isEliminated, setIsEliminated] = useState(false); - const [hasSubmitted, setHasSubmitted] = useState(false); - const [isCoinFlipping, setIsCoinFlipping] = useState(false); - const [coinRotation, setCoinRotation] = useState(0); - const [isSubmitting, setIsSubmitting] = useState(false); const [notification, setNotification] = useState({ isVisible: false, isSuccess: false, message: "", subMessage: "", }); - const [lastCompletedRound, setLastCompletedRound] = useState(0); - const [isWaitingForOthers, setIsWaitingForOthers] = useState(false); - const [showClaimInterface, setShowClaimInterface] = useState(false); + const [showWinnerPopup, setShowWinnerPopup] = useState(false); + type PlayerStatus = [boolean, boolean, boolean, boolean]; - // Fetch player status - const [isWinner, setIsWinner] = useState(false); - const [showWinnerPopup, setShowWinnerPopup] = useState(false); + // _____________________________Fetch Player Status____________________________________ const { data: playerStatus, @@ -58,139 +56,127 @@ const PlayGame = () => { isLoading: isStatusLoading, } = useReadContract({ address: CORE_CONTRACT_ADDRESS as `0x${string}`, - - // @ts-ignore abi: CoinTossABI.abi, functionName: "getPlayerStatus", args: [BigInt(pool.id), address], }); - console.log(playerStatus); + + //___________________________Sending Transaction____________________________________ + + const { + writeContract, + data: hash, + isPending: txPending, + error + } = useWriteContract(); + + //__________________________ Wait for transaction confirmation________________________________________ + + const { + isLoading: isConfirming, + isSuccess: isConfirmed, + error: receiptError, + } = useWaitForTransactionReceipt({ hash }); + + + + - // @ts-ignore - const isParticipant = playerStatus ? playerStatus[0] : false; - - // @ts-ignore - const isEliminatedStatus = playerStatus ? playerStatus[1] : false; + console.log(playerStatus); + console.log(pool) - // @ts-ignore - const isWinnerStatus = playerStatus ? playerStatus[2] : false; +//___________________________Player Status Destructuring____________________________________ - // @ts-ignore + const isParticipant = playerStatus ? playerStatus[0] : false; + const isEliminated= playerStatus ? playerStatus[1] : false; + const isAWinner = playerStatus ? playerStatus[2] : false; const hasClaimed = playerStatus ? playerStatus[3] : false; - // Send transaction - const { - writeContract, - data: hash, - isPending: isWritePending, - error: writeError, - } = useWriteContract(); - - // Wait for transaction confirmation - const { - isLoading: isConfirming, - isSuccess: isConfirmed, - error: receiptError, - } = useWaitForTransactionReceipt({ hash }); const coinFlipInterval = useRef(null); //___________________ Handle timer logic________________________________ + useEffect(() => { - if (isTimerActive && timer > 0) { + if (gameState.isTimerActive && timer > 0) { const interval = setInterval(() => { setTimer((prevTimer) => prevTimer - 1); - }, 1000); + }, 2000); return () => clearInterval(interval); - } else if (timer === 0 && !isWaitingForOthers) { - setIsTimerActive(false); - - if (isWritePending || isConfirming) { + } else if (timer === 0) { + setGameState((prev) => ({ ...prev, isTimerActive: false })); + if (isConfirming) { showNotification( true, "Processing...", "Your choice has been submitted and is being processed" ); - } else if (writeError || receiptError) { + } else if (receiptError) { showNotification( false, "Transaction Failed", "Your transaction failed to process. Try Again" ); - // setTimeout(() => { - // navigate("/explore"); - // }, 3000); } else if (isConfirmed) { - setIsWaitingForOthers(true); + showNotification( + true, + "Success!", + "Your selection has been recorded!" + ); + setGameState((prev) => ({ ...prev, isWaitingForOthers: true })); } } }, [ - isTimerActive, + gameState.isTimerActive, timer, - isWaitingForOthers, - isWritePending, isConfirming, - writeError, - receiptError, isConfirmed, + receiptError ]); + //__________________________ Redirect Conditions________________________________ useEffect(() => { - // Check if we should redirect based on different conditions if (!pool) { - navigate("/explore"); // No pool data, redirect - } else if (hasClaimed) { - navigate("/explore"); // Already claimed, redirect - } else if (typeof pool.status === "number" && pool.status === 2) { - // Pool is CLOSED (status 2) - // Check if player is a winner before redirecting - refetchPlayerStatus().then((result) => { - // @ts-ignore - if (!(result.data && result.data[2])) { - // Not a winner, redirect - navigate("/explore"); - } - // If winner, let them stay to claim prize - }); - } else if ( - (typeof pool.status === "number" && pool.status !== 2) || - (typeof pool.status === "string" && pool.status !== "ACTIVE") - ) { - // Pool is not active and not closed (other status), redirect navigate("/explore"); + return; + } + if (hasClaimed) { + navigate("/explore"); + return; } - if (typeof pool?.status === "number" && pool.status === 2) { + const poolStatus = pool.status; + if (poolStatus === 2) { refetchPlayerStatus().then((result) => { - // @ts-ignore - if (result.data && result.data[2] && !result.data[3]) { - // Is winner and hasn't claimed - setShowClaimInterface(true); - - // @ts-ignore - } else if (!(result.data && result.data[2])) { + if (result.data) { + const [ isWinner, hasClaimed] = result.data; + if (isWinner && !hasClaimed) { + setShowClaimInterface(true); + } else { + + navigate("/explore"); + } + } else { navigate("/explore"); } }); } + // else { + // navigate("/explore"); + // } }, [pool, hasClaimed, navigate, refetchPlayerStatus]); - // Handle player elimination - useEffect(() => { - if (playerStatus) { - // @ts-ignore - const isPlayerEliminated = playerStatus[1]; - // @ts-ignore - const isPlayerWinner = playerStatus[2]; - // @ts-ignore - const hasPlayerClaimed = playerStatus[3]; + //____________________________ Handle player elimination_____________________________________________ + + useEffect(() => { + if (playerStatus) { + // Update elimination status - if (isPlayerEliminated && !isEliminated) { - setIsEliminated(true); - setIsTimerActive(false); + if (isParticipant && isEliminated) { + setGameState((prev) => ({ ...prev, isEliminated: true, isTimerActive: false })); showNotification( false, "Eliminated", @@ -198,65 +184,70 @@ const PlayGame = () => { ); setTimeout(() => { navigate("/explore"); - }, 3000); + }, 4000); } // Update winner status - if (isPlayerWinner && !isWinner) { + + if (isParticipant && isAWinner) { setIsWinner(true); setShowWinnerPopup(true); } - // Handle claimed status - if (hasPlayerClaimed && isPlayerWinner) { + + if (isEliminated && gameState.hasSubmitted && isAWinner) { navigate("/explore"); } // Handle pool status changes - if (pool?.status === 2 && isPlayerWinner && !showWinnerPopup) { + if (pool?.status === 2 && isParticipant && !showWinnerPopup) { setShowWinnerPopup(true); } } - }, [playerStatus, isEliminated, isWinner, showWinnerPopup, pool, navigate]); + }, [playerStatus,showWinnerPopup, pool, navigate]); - // Handle player winning the game + + //____________________________ Handle player winning the game_______________________________________ useEffect(() => { - if (isWinnerStatus && pool?.status === 2) { + if (isAWinner && pool?.status === 2) { setIsWinner(true); setShowWinnerPopup(true); } - }, [isWinnerStatus, pool]); + }, [isAWinner, pool]); // Add a polling mechanism to ensure we get updates even if events fail useEffect(() => { - if (isWaitingForOthers) { + if (gameState.isWaitingForOthers) { const interval = setInterval(() => { refetchPlayerStatus(); }, 5000); return () => clearInterval(interval); } - }, [isWaitingForOthers, refetchPlayerStatus]); + }, [gameState.isWaitingForOthers, refetchPlayerStatus]); // Handle player choice submission + const handleMakeChoice = async (selected: PlayerChoice) => { - if (!isTimerActive || timer <= 2 || isEliminated || hasSubmitted) return; + if ( timer <= 2 || isEliminated || gameState.hasSubmitted) return; - setSelectedChoice(selected); - setHasSubmitted(true); - startCoinAnimation(); + setGameState((prev) => ({ ...prev, selectedChoice: selected, hasSubmitted: true, isCoinFlipping: true })); + startCoinAnimation() await handleSubmit(selected); }; + + // ____________submitting the choice_____________________ + + const handleSubmit = async (selected: PlayerChoice) => { if (!selected) { showNotification(false, "Error", "Please select HEADS or TAILS"); return; } - try { - setIsSubmitting(true); - writeContract({ + setGameState((prev) => ({ ...prev, isSubmitting: true })); + writeContract({ address: CORE_CONTRACT_ADDRESS as `0x${string}`, abi: CoinTossABI.abi, functionName: "makeSelection", @@ -264,139 +255,98 @@ const PlayGame = () => { }); } catch (err: any) { const errorMessage = err.message || "Transaction failed"; - showNotification(false, "Transaction Error", errorMessage); - setIsSubmitting(false); + showNotification(false, "Transaction Error!!!", errorMessage); + setGameState((prev) => ({ + ...prev, + isSubmitting: false, + hasSubmitted: false, + selectedChoice: null, + })); stopCoinAnimation(); } }; + // Handle transaction confirmation useEffect(() => { if (isConfirmed) { - setIsSubmitting(false); - setSelectedChoice(null); + setGameState((prev) => ({ + ...prev, + isSubmitting: false, + isWaitingForOthers: true, + })); showNotification(true, "Success!", "Your selection has been recorded!"); - setIsWaitingForOthers(true); - setIsTimerActive(false); - setTimer(0); } - - if (writeError || receiptError) { - setIsSubmitting(false); - showNotification( - false, - "Transaction Failed", - "Your transaction failed to process." - ); + + if (receiptError) { + setGameState((prev) => ({ + ...prev, + isSubmitting: false, + hasSubmitted: false, + selectedChoice: null, + })); + showNotification(false, "Transaction Failed", "Your transaction failed to process!!."); } - }, [isConfirmed, writeError, receiptError]); - + }, [isConfirmed, receiptError]); // Handle RoundCompleted event - useWatchContractEvent({ address: CORE_CONTRACT_ADDRESS as `0x${string}`, abi: CoinTossABI.abi, eventName: "RoundCompleted", onLogs: (logs) => { console.log("RoundCompleted logs received:", logs); - for (const log of logs) { try { console.log("Processing log:", log); - - // Extract event arguments, handling both named and positional formats - // @ts-ignore + const args = log.args || {}; - - // Get poolId - try both named and indexed access + const eventPoolId = "poolId" in args ? Number(args.poolId) : "0" in args ? Number(args[0]) : undefined; - - // Get round number - const roundNumber = - "round" in args - ? Number(args.round) - : "1" in args - ? Number(args[1]) - : undefined; - - // Get winning selection - const winningSelection = - "winningSelection" in args - ? Number(args.winningSelection) - : "2" in args - ? Number(args[2]) - : undefined; - - console.log("Extracted data:", { - eventPoolId, - roundNumber, - winningSelection, - }); - + + const roundNumber ="round" in args ? Number(args.round) : "1" in args ? Number(args[1]) : undefined; + //Get winning selection + const winningSelection ="winningSelection" in args? Number(args.winningSelection) : "2" in args ? Number(args[2]): undefined; // Skip if we couldn't extract necessary data - if ( - eventPoolId === undefined || - roundNumber === undefined || - winningSelection === undefined + if (eventPoolId === undefined ||roundNumber === undefined ||winningSelection === undefined ) { - console.error("Could not extract complete data from event", log); continue; } - - // Check if this is a new round completion (avoid processing the same round multiple times) + if (eventPoolId === pool.id && roundNumber > lastCompletedRound) { console.log("New round completion detected for current pool!"); setLastCompletedRound(roundNumber); - // Stop animations immediately stopCoinAnimation(); - // Update UI state to indicate processing - setIsWaitingForOthers(false); - + setGameState((prev) => ({ ...prev, isWaitingForOthers: false })); // Determine if user survived based on their choice - const userChoice = selectedChoice; + const userChoice = gameState.selectedChoice; const userSurvived = userChoice === winningSelection; - - console.log("Round result:", { - userChoice, - winningSelection, - userSurvived, - }); - // Update eliminated status immediately if (!userSurvived) { - setIsEliminated(true); + setGameState((prev) => ({ ...prev, isEliminated: true })); } - // Force refresh player status from contract refetchPlayerStatus().then(() => { console.log("Player status refreshed after round completion"); }); - - // Show appropriate notification - showNotification( - userSurvived, + + showNotification(userSurvived, `Round ${roundNumber} Completed!`, userSurvived ? "You advanced to the next round!" : "You were eliminated!" ); - // Handle game state updates if (userSurvived) { - // Set timeout to allow notification to be seen setTimeout(() => { - setRound(roundNumber + 1); setTimer(20); - setIsTimerActive(true); - setHasSubmitted(false); - setSelectedChoice(null); + setGameState((prev) => ({ ...prev, isTimerActive: true, hasSubmitted: false,selectedChoice: null,round: roundNumber + 1})); }, 3000); } } @@ -410,15 +360,12 @@ const PlayGame = () => { } }, }); - // Handle PoolCompleted event useWatchContractEvent({ address: CORE_CONTRACT_ADDRESS as `0x${string}`, abi: CoinTossABI.abi, eventName: "PoolCompleted", onLogs: (logs) => { - console.log("PoolCompleted logs received:", logs); - for (const log of logs) { try { // @ts-ignore @@ -431,22 +378,17 @@ const PlayGame = () => { : undefined; if (eventPoolId === pool.id) { - console.log("Pool completed!"); - // Immediately check if the user is a winner refetchPlayerStatus().then((result) => { - // @ts-ignore if (result.data && result.data[2]) { // index 2 is isWinner - setIsWinner(true); setShowWinnerPopup(true); // Do NOT redirect winners - they need to claim their prize // Update game state to reflect completion - setIsTimerActive(false); + setGameState((prev) => ({ ...prev, isTimerActive: false,isWaitingForOthers: false})); setTimer(0); - setIsWaitingForOthers(false); - + // Show a notification that the game is complete and they won showNotification( true, @@ -486,7 +428,7 @@ const PlayGame = () => { // Start/stop coin animation const startCoinAnimation = () => { - setIsCoinFlipping(true); + setGameState((prev) => ({ ...prev, isCoinFlipping: true })); coinFlipInterval.current = setInterval(() => { setCoinRotation((prev) => (prev + 36) % 360); }, 100); @@ -497,7 +439,11 @@ const PlayGame = () => { clearInterval(coinFlipInterval.current); coinFlipInterval.current = null; } - setIsCoinFlipping(false); + setGameState((prev) => ({ + ...prev, + isCoinFlipping: false, + selectedChoice: null, + })); setCoinRotation(0); }; @@ -513,60 +459,40 @@ const PlayGame = () => { if (!isSuccess) { navigate("/explore"); } - }, 3000); + }, 5000); }; // Handle player winning the game useEffect(() => { - if (isWinnerStatus) { + if (isAWinner) { setIsWinner(true); setShowWinnerPopup(true); } - }, [isWinnerStatus]); + }, [isAWinner]); + + + // Handle token claim + const handleClaimPrize = async () => { + try { + setGameState((prev) => ({ ...prev, isSubmitting: true })); + writeContract({ + address: CORE_CONTRACT_ADDRESS as `0x${string}`, + abi: CoinTossABI.abi, + functionName: "claimPrize", + args: [BigInt(pool.id)], + }); + showNotification(true, "Success!", "Your prize has been claimed!"); + setShowWinnerPopup(false); + navigate("/explore"); + } catch (err: any) { + const errorMessage = err.message || "Transaction failed"; + showNotification(false, "Transaction Error", errorMessage); + } finally { + setGameState((prev) => ({ ...prev, isSubmitting: false })); + } + }; - // Handle token claim - const handleClaimPrize = async () => { - try { - setIsSubmitting(true); - writeContract({ - address: CORE_CONTRACT_ADDRESS as `0x${string}`, - abi: CoinTossABI.abi, - functionName: "claimPrize", - args: [BigInt(pool.id)], - }); - showNotification(true, "Success!", "Your prize has been claimed!"); - setShowWinnerPopup(false); - navigate("/explore"); - } catch (err: any) { - const errorMessage = err.message || "Transaction failed"; - showNotification(false, "Transaction Error", errorMessage); - } finally { - setIsSubmitting(false); - } - }; - if (showClaimInterface) { - return ( -
-
-
🏆
-

- Congratulations Winner! -

-

- This pool has ended and you are a winner! Claim your prize now. -

- -
-
- ); - } return (
{/* Top Game Status Bar */} @@ -574,11 +500,11 @@ const PlayGame = () => {
- {round} + {gameState.round}
ROUND
-
{round}
+
{gameState.round}
@@ -613,7 +539,7 @@ const PlayGame = () => {
{/* Coin Animation Area */} - {isCoinFlipping && ( + {gameState.isCoinFlipping && (
{
- {selectedChoice === PlayerChoice.HEADS && ( + {gameState.selectedChoice === PlayerChoice.HEADS && (
)}
diff --git a/Frontend/src/utils/formatedPlay.tsx b/Frontend/src/utils/formatedPlay.tsx new file mode 100644 index 0000000..3389679 --- /dev/null +++ b/Frontend/src/utils/formatedPlay.tsx @@ -0,0 +1,737 @@ +import { useState, useEffect, useRef } from "react"; +import { formatTime } from "../utils/utilFunction"; +import { + useWriteContract, + useWaitForTransactionReceipt, + useWatchContractEvent, + useReadContract, + useAccount, +} from "wagmi"; +import { useNavigate, useLocation } from "react-router-dom"; +import CoinTossABI from "../utils/contract/CoinToss.json"; +import { CORE_CONTRACT_ADDRESS } from "../utils/contract/contract"; +import { useContractInteraction } from "../hooks/ContractWriteIn"; +import {usePlayerStatus} from "../hooks/ContractReadIn"; +enum PlayerChoice { + NONE = 0, + HEADS = 1, + TAILS = 2, +} + +const PlayGame = () => { + const [coinRotation, setCoinRotation] = useState(0); + const navigate = useNavigate(); + const location = useLocation(); + const pool = location.state.pools; + const { address } = useAccount(); + const [gameState, setGameState] = useState({ + isTimerActive: true, + selectedChoice: null as PlayerChoice | null, + round: 1, + isEliminated: false, + hasSubmitted: false, + isCoinFlipping: false, + isSubmitting: false, + isWaitingForOthers: false, + showClaimInterface: false, + showWinnerPopup: false, + }); + + const [timer, setTimer] = useState(31); + const [notification, setNotification] = useState({ + isVisible: false, + isSuccess: false, + message: "", + subMessage: "", + }); + const [lastCompletedRound, setLastCompletedRound] = useState(0); + const [isWinner, setIsWinner] = useState(false); + const [showWinnerPopup, setShowWinnerPopup] = useState(false); + const { makeSelection, claimPrize, isPending, isConfirming, isConfirmed, error } = useContractInteraction(); + + const{ playerStatus, refetchPlayerStatus, isStatusLoading}=usePlayerStatus(pool.id, address as `0x${string}` ); + + type PlayerStatus = [boolean, boolean, boolean, boolean]; + + // Fetch player status + + // _____________________________Fetch Player Status____________________________________ + + + + console.log(playerStatus); + console.log(pool) + +//___________________________Player Status Destructuring____________________________________ + + const isParticipant = playerStatus ? playerStatus[0] : false; + const isEliminatedStatus = playerStatus ? playerStatus[1] : false; + const isWinnerStatus = playerStatus ? playerStatus[2] : false; + const hasClaimed = playerStatus ? playerStatus[3] : false; + + + const coinFlipInterval = useRef(null); + + //___________________ Handle timer logic________________________________ + + useEffect(() => { + if (gameState.isTimerActive && timer > 0) { + const interval = setInterval(() => { + setTimer((prevTimer) => prevTimer - 1); + }, 2000); + return () => clearInterval(interval); + } else if (timer === 0) { + setGameState((prev) => ({ ...prev, isTimerActive: false })); + if (isPending || isConfirming) { + showNotification( + true, + "Processing...", + "Your choice has been submitted and is being processed" + ); + } else if (error) { + showNotification( + false, + "Transaction Failed", + "Your transaction failed to process. Try Again" + ); + } else if (isConfirmed) { + showNotification( + true, + "Success!", + "Your selection has been recorded!" + ); + setGameState((prev) => ({ ...prev, isWaitingForOthers: true })); + } + } + }, [ + gameState.isTimerActive, + timer, + gameState.isWaitingForOthers, + isPending, + isConfirmed, + error + ]); + + //__________________________ Redirect Conditions________________________________ + + + useEffect(() => { + // Check if we should redirect based on different conditions + if (!pool) { + navigate("/explore"); + } else if (hasClaimed) { + navigate("/explore"); // Already claimed, redirect + } else if (typeof pool.status === "number" && pool.status === 2) { + // Pool is CLOSED (status 2) + // Check if player is a winner before redirecting + refetchPlayerStatus().then((result) => { + + if (!(result.data && result.data[2])) { + navigate("/explore"); + } + // If winner, let them stay to claim prize + }); + } else if ( + (typeof pool.status === "number" && pool.status !== 2) || + (typeof pool.status === "string" && pool.status !== "ACTIVE") + ) { + // Pool is not active and not closed (other status), redirect + navigate("/explore"); + } + if (typeof pool?.status === "number" && pool.status === 2) { + refetchPlayerStatus().then((result) => { + if (result.data && result.data[2] && !result.data[3]) { + // Is winner and hasn't claimed + setShowClaimInterface(true); + } else if (!(result.data && result.data[2])) { + navigate("/explore"); + } + }); + } + }, [pool, hasClaimed, navigate, refetchPlayerStatus]); + + + + + + + + //____________________________ Handle player elimination_____________________________________________ + + useEffect(() => { + if (playerStatus) { + + const isPlayerEliminated = playerStatus[1]; + const isPlayerWinner = playerStatus[2]; + const hasPlayerClaimed = playerStatus[3]; + + // Update elimination status + if (isPlayerEliminated && gameState.isEliminated) { + setGameState((prev) => ({ ...prev, isEliminated: true, isTimerActive: false })); + showNotification( + false, + "Eliminated", + "You have been eliminated from the pool." + ); + setTimeout(() => { + navigate("/explore"); + }, 3000); + } + + // Update winner status + + if (isPlayerWinner && !isWinner) { + setIsWinner(true); + setShowWinnerPopup(true); + } + + // Handle claimed status + if (hasPlayerClaimed && isPlayerWinner) { + navigate("/explore"); + } + + // Handle pool status changes + if (pool?.status === 2 && isPlayerWinner && !showWinnerPopup) { + setShowWinnerPopup(true); + } + } + }, [playerStatus, gameState.isEliminated, isWinner, showWinnerPopup, pool, navigate]); + + //____________________________ Handle player winning the game_______________________________________ + useEffect(() => { + if (isWinnerStatus && pool?.status === 2) { + setIsWinner(true); + setShowWinnerPopup(true); + } + }, [isWinnerStatus, pool]); + + // Add a polling mechanism to ensure we get updates even if events fail + useEffect(() => { + if (gameState.isWaitingForOthers) { + const interval = setInterval(() => { + refetchPlayerStatus(); + }, 5000); + + return () => clearInterval(interval); + } + }, [gameState.isWaitingForOthers, refetchPlayerStatus]); + + // Handle player choice submission + + const handleMakeChoice = async (selected: PlayerChoice) => { + if (!gameState.isTimerActive || timer <= 2 || gameState.isEliminated || gameState.hasSubmitted) return; + + setGameState((prev) => ({ ...prev, selectedChoice: selected, hasSubmitted: true, isCoinFlipping: true })); + startCoinAnimation() + await handleSubmit(selected); + }; + // ____________submitting the choice_____________________ + + const handleSubmit = async (selected: PlayerChoice) => { + if (!selected) { + showNotification(false, "Error", "Please select HEADS or TAILS"); + return; + } + + try { + setGameState((prev) => ({ ...prev, isSubmitting: true })); + makeSelection(BigInt(pool.id), selected); + } catch (err: any) { + const errorMessage = err.message || "Transaction failed"; + showNotification(false, "Transaction Error", errorMessage); + setGameState((prev) => ({ ...prev, isSubmitting: false })); + stopCoinAnimation() + } + }; + + // Handle transaction confirmation + useEffect(() => { + if (isConfirmed) { + setGameState((prev) => ({ + ...prev, + isSubmitting: false, + isWaitingForOthers: true, + })); + showNotification(true, "Success!", "Your selection has been recorded!"); + } + + if (error) { + setGameState((prev) => ({ ...prev, isSubmitting: false })); + showNotification(false, "Transaction Failed", "Your transaction failed to process."); + } + }, [isConfirmed, error]); + + // Handle RoundCompleted event + + useWatchContractEvent({ + address: CORE_CONTRACT_ADDRESS as `0x${string}`, + abi: CoinTossABI.abi, + eventName: "RoundCompleted", + onLogs: (logs) => { + console.log("RoundCompleted logs received:", logs); + + for (const log of logs) { + try { + console.log("Processing log:", log); + + // Extract event arguments, handling both named and positional formats + + const args = log.args || {}; + + // Get poolId - try both named and indexed access + const eventPoolId = + "poolId" in args + ? Number(args.poolId) + : "0" in args + ? Number(args[0]) + : undefined; + + // Get round number + const roundNumber = + "round" in args + ? Number(args.round) + : "1" in args + ? Number(args[1]) + : undefined; + + // Get winning selection + const winningSelection = + "winningSelection" in args + ? Number(args.winningSelection) + : "2" in args + ? Number(args[2]) + : undefined; + + console.log("Extracted data:", { + eventPoolId, + roundNumber, + winningSelection, + }); + + // Skip if we couldn't extract necessary data + if ( + eventPoolId === undefined || + roundNumber === undefined || + winningSelection === undefined + ) { + console.error("Could not extract complete data from event", log); + continue; + } + + // Check if this is a new round completion (avoid processing the same round multiple times) + if (eventPoolId === pool.id && roundNumber > lastCompletedRound) { + console.log("New round completion detected for current pool!"); + setLastCompletedRound(roundNumber); + + // Stop animations immediately + stopCoinAnimation(); + + // Update UI state to indicate processing + setGameState((prev) => ({ ...prev, isWaitingForOthers: false })); + + // Determine if user survived based on their choice + const userChoice = gameState.selectedChoice; + const userSurvived = userChoice === winningSelection; + + console.log("Round result:", { + userChoice, + winningSelection, + userSurvived, + }); + + // Update eliminated status immediately + if (!userSurvived) { + setGameState((prev) => ({ ...prev, isEliminated: true })); + } + + // Force refresh player status from contract + refetchPlayerStatus().then(() => { + console.log("Player status refreshed after round completion"); + }); + + // Show appropriate notification + showNotification( + userSurvived, + `Round ${roundNumber} Completed!`, + userSurvived + ? "You advanced to the next round!" + : "You were eliminated!" + ); + + // Handle game state updates + if (userSurvived) { + // Set timeout to allow notification to be seen + setTimeout(() => { + setGameState((prev) => ({ ...prev, round: roundNumber + 1 })); + setTimer(20); + setGameState((prev) => ({ ...prev, isTimerActive: true })); + setGameState((prev) => ({ ...prev, hasSubmitted: false })); + setGameState((prev) => ({ ...prev, selectedChoice: null })); + }, 3000); + } + } + } catch (error) { + console.error("Error processing event log:", error); + console.error( + "Error details:", + error instanceof Error ? error.message : String(error) + ); + } + } + }, + }); + + // Handle PoolCompleted event + useWatchContractEvent({ + address: CORE_CONTRACT_ADDRESS as `0x${string}`, + abi: CoinTossABI.abi, + eventName: "PoolCompleted", + onLogs: (logs) => { + console.log("PoolCompleted logs received:", logs); + + for (const log of logs) { + try { + // @ts-ignore + const args = log.args || {}; + const eventPoolId = + "poolId" in args + ? Number(args.poolId) + : "0" in args + ? Number(args[0]) + : undefined; + + if (eventPoolId === pool.id) { + console.log("Pool completed!"); + + // Immediately check if the user is a winner + refetchPlayerStatus().then((result) => { + // @ts-ignore + if (result.data && result.data[2]) { + // index 2 is isWinner + setIsWinner(true); + setShowWinnerPopup(true); + // Do NOT redirect winners - they need to claim their prize + + // Update game state to reflect completion + setGameState((prev) => ({ ...prev, isTimerActive: false,isWaitingForOthers: false})); + setTimer(0); + + + // Show a notification that the game is complete and they won + showNotification( + true, + "You Won!", + "The pool has ended and you are a winner!" + ); + } else { + // User didn't win - redirect after notification + showNotification( + false, + "Game Over", + "The pool has ended. Better luck next time!" + ); + setTimeout(() => { + navigate("/explore"); + }, 5000); + } + }); + } + } catch (error) { + console.error("Error processing PoolCompleted event:", error); + console.error( + "Error details:", + error instanceof Error ? error.message : String(error) + ); + + // Show a generic notification in case of error + showNotification( + false, + "Error", + "An error occurred while processing the game result" + ); + } + } + }, + }); + + // Start/stop coin animation + const startCoinAnimation = () => { + setGameState((prev) => ({ ...prev, isCoinFlipping: true })); + coinFlipInterval.current = setInterval(() => { + setCoinRotation((prev) => (prev + 36) % 360); + }, 100); + }; + + const stopCoinAnimation = () => { + if (coinFlipInterval.current) { + clearInterval(coinFlipInterval.current); + coinFlipInterval.current = null; + } + setGameState((prev) => ({ ...prev, isCoinFlipping: false })); + setCoinRotation(0); + }; + + // Show notification + const showNotification = ( + isSuccess: boolean, + message: string, + subMessage: string + ) => { + setNotification({ isVisible: true, isSuccess, message, subMessage }); + setTimeout(() => { + setNotification((prev) => ({ ...prev, isVisible: false })); + if (!isSuccess) { + navigate("/explore"); + } + }, 3000); + }; + + // Handle player winning the game + useEffect(() => { + if (isWinnerStatus) { + setIsWinner(true); + setShowWinnerPopup(true); + } + }, [isWinnerStatus]); + + // Handle token claim + const handleClaimPrize = async () => { + try { + setGameState((prev) => ({ ...prev, isSubmitting: true })); + await claimPrize(BigInt(pool.id)); + showNotification(true, "Success!", "Your prize has been claimed!"); + setShowWinnerPopup(false); + navigate("/explore"); + } catch (err: any) { + const errorMessage = err.message || "Transaction failed"; + showNotification(false, "Transaction Error", errorMessage); + } finally { + setGameState((prev) => ({ ...prev, isSubmitting: false })); + } + }; + + return ( +
+ {/* Top Game Status Bar */} +
+
+
+
+ {gameState.round} +
+
+
ROUND
+
{gameState.round}
+
+
+ +
+
REMAINING PLAYERS
+
+ {pool.currentParticipants} +
+
+ +
+
+ + POTENTIAL + + REWARD +
+ +
+ +120 Points +
+
+
+
+ + {/* Main Game Area */} +
+

CHOOSE WISELY

+

+ Only the minority will survive +

+
+ + {/* Coin Animation Area */} + {gameState.isCoinFlipping && ( +
+
+ {coinRotation % 180 < 90 ? "🪙" : "💰"} +
+
+ )} + +
+
+ {gameState.selectedChoice === PlayerChoice.HEADS && ( +
+ )} + +
+ + {/* VS Divider */} +
+
+
VS
+
+
+ + {/* Tails Option */} +
+ {gameState.selectedChoice === PlayerChoice.TAILS && ( +
+ )} + +
+
+ + {/* Timer Section with Enhanced Drama */} +
+
+
+
TIME REMAINING
+
+ {formatTime(timer)} +
+
+ +
+
+
+ +
+
+ Tip: The + minority option wins! +
+
+
+
+ + {/* Notification for round results */} + {notification.isVisible && ( +
+
+
+ {notification.isSuccess ? "🏆" : "❌"} +
+

+ {notification.message} +

+

+ {notification.subMessage} +

+
+
+ )} + + {gameState.isWaitingForOthers && ( +
+
+ Waiting for other players to make their selections... +
+
+ )} + + {/* Winner Pop-up */} + {showWinnerPopup && ( +
+
+
🏆
+

+ Congratulations! +

+

+ You are the winner of this pool! Claim your prize now. +

+ +
+
+ )} +
+ ); +}; + +export default PlayGame; diff --git a/Frontend/src/utils/oldPlay.tsx b/Frontend/src/utils/oldPlay.tsx new file mode 100644 index 0000000..facf803 --- /dev/null +++ b/Frontend/src/utils/oldPlay.tsx @@ -0,0 +1,801 @@ +import { useState, useEffect, useRef } from "react"; +import { formatTime } from "../utils/utilFunction"; +import { + useWriteContract, + useWaitForTransactionReceipt, + useWatchContractEvent, + useReadContract, + useAccount, +} from "wagmi"; +import { useNavigate, useLocation } from "react-router-dom"; +import CoinTossABI from "../utils/contract/CoinToss.json"; +import { CORE_CONTRACT_ADDRESS } from "../utils/contract/contract"; + +enum PlayerChoice { + NONE = 0, + HEADS = 1, + TAILS = 2, +} + +const PlayGame = () => { + + const navigate = useNavigate(); + const location = useLocation(); + const pool = location.state.pools; + const { address } = useAccount(); + + const [isTimerActive, setIsTimerActive] = useState(true); + const [selectedChoice, setSelectedChoice] = useState( + null + ); + + const [round, setRound] = useState(1); + const [timer, setTimer] = useState(31); + const [isEliminated, setIsEliminated] = useState(false); + const [hasSubmitted, setHasSubmitted] = useState(false); + const [isCoinFlipping, setIsCoinFlipping] = useState(false); + const [coinRotation, setCoinRotation] = useState(0); + const [isSubmitting, setIsSubmitting] = useState(false); + const [notification, setNotification] = useState({ + isVisible: false, + isSuccess: false, + message: "", + subMessage: "", + }); + + const [lastCompletedRound, setLastCompletedRound] = useState(0); + const [isWaitingForOthers, setIsWaitingForOthers] = useState(false); + const [showClaimInterface, setShowClaimInterface] = useState(false); + + type PlayerStatus = [boolean, boolean, boolean, boolean]; + + // Fetch player status + + const [isWinner, setIsWinner] = useState(false); + const [showWinnerPopup, setShowWinnerPopup] = useState(false); + + // _____________________________Fetch Player Status____________________________________ + + const { + data: playerStatus, + refetch: refetchPlayerStatus, + isLoading: isStatusLoading, + } = useReadContract({ + address: CORE_CONTRACT_ADDRESS as `0x${string}`, + abi: CoinTossABI.abi, + functionName: "getPlayerStatus", + args: [BigInt(pool.id), address], + }); + + console.log(playerStatus); + console.log(pool) + +//___________________________Player Status Destructuring____________________________________ + + const isParticipant = playerStatus ? playerStatus[0] : false; + const isEliminatedStatus = playerStatus ? playerStatus[1] : false; + const isWinnerStatus = playerStatus ? playerStatus[2] : false; + const hasClaimed = playerStatus ? playerStatus[3] : false; + + +//___________________________Sending Transaction____________________________________ + + const { + writeContract, + data: hash, + isPending: isWritePending, + error: writeError, + } = useWriteContract(); + + //__________________________ Wait for transaction confirmation________________________________________ + + const { + isLoading: isConfirming, + isSuccess: isConfirmed, + error: receiptError, + } = useWaitForTransactionReceipt({ hash }); + + const coinFlipInterval = useRef(null); + + //___________________ Handle timer logic________________________________ + + useEffect(() => { + if (isTimerActive && timer > 0) { + const interval = setInterval(() => { + setTimer((prevTimer) => prevTimer - 1); + }, 2000); + return () => clearInterval(interval); + } else if (timer === 0) { + setIsTimerActive(false); + if (isWritePending || isConfirming) { + showNotification( + true, + "Processing...", + "Your choice has been submitted and is being processed" + ); + } else if (writeError || receiptError) { + showNotification( + false, + "Transaction Failed", + "Your transaction failed to process. Try Again" + ); + } else if (isConfirmed) { + showNotification( + true, + "Success!", + "Your selection has been recorded!" + ); + setIsWaitingForOthers(true); + } + } + }, [ + isTimerActive, + timer, + isWaitingForOthers, + isWritePending, + isConfirming, + writeError, + receiptError, + isConfirmed, + ]); + + //__________________________ Redirect Conditions________________________________ + + + useEffect(() => { + // Check if we should redirect based on different conditions + if (!pool) { + navigate("/explore"); + } else if (hasClaimed) { + navigate("/explore"); // Already claimed, redirect + } else if (typeof pool.status === "number" && pool.status === 2) { + // Pool is CLOSED (status 2) + // Check if player is a winner before redirecting + refetchPlayerStatus().then((result) => { + + if (!(result.data && result.data[2])) { + navigate("/explore"); + } + // If winner, let them stay to claim prize + }); + } else if ( + (typeof pool.status === "number" && pool.status !== 2) || + (typeof pool.status === "string" && pool.status !== "ACTIVE") + ) { + // Pool is not active and not closed (other status), redirect + navigate("/explore"); + } + if (typeof pool?.status === "number" && pool.status === 2) { + refetchPlayerStatus().then((result) => { + if (result.data && result.data[2] && !result.data[3]) { + // Is winner and hasn't claimed + setShowClaimInterface(true); + } else if (!(result.data && result.data[2])) { + navigate("/explore"); + } + }); + } + }, [pool, hasClaimed, navigate, refetchPlayerStatus]); + + + + + + + + //____________________________ Handle player elimination_____________________________________________ + + useEffect(() => { + if (playerStatus) { + + const isPlayerEliminated = playerStatus[1]; + const isPlayerWinner = playerStatus[2]; + const hasPlayerClaimed = playerStatus[3]; + + // Update elimination status + if (isPlayerEliminated && !isEliminated) { + setIsEliminated(true); + setIsTimerActive(false); + showNotification( + false, + "Eliminated", + "You have been eliminated from the pool." + ); + setTimeout(() => { + navigate("/explore"); + }, 3000); + } + + // Update winner status + + if (isPlayerWinner && !isWinner) { + setIsWinner(true); + setShowWinnerPopup(true); + } + + // Handle claimed status + if (hasPlayerClaimed && isPlayerWinner) { + navigate("/explore"); + } + + // Handle pool status changes + if (pool?.status === 2 && isPlayerWinner && !showWinnerPopup) { + setShowWinnerPopup(true); + } + } + }, [playerStatus, isEliminated, isWinner, showWinnerPopup, pool, navigate]); + + // Handle player winning the game + useEffect(() => { + if (isWinnerStatus && pool?.status === 2) { + setIsWinner(true); + setShowWinnerPopup(true); + } + }, [isWinnerStatus, pool]); + + // Add a polling mechanism to ensure we get updates even if events fail + useEffect(() => { + if (isWaitingForOthers) { + const interval = setInterval(() => { + refetchPlayerStatus(); + }, 5000); + + return () => clearInterval(interval); + } + }, [isWaitingForOthers, refetchPlayerStatus]); + + // Handle player choice submission + const handleMakeChoice = async (selected: PlayerChoice) => { + if (!isTimerActive || timer <= 2 || isEliminated || hasSubmitted) return; + + setSelectedChoice(selected); + setHasSubmitted(true); + startCoinAnimation(); + await handleSubmit(selected); + }; + + // ____________submitting the choice_____________________ + + const handleSubmit = async (selected: PlayerChoice) => { + if (!selected) { + showNotification(false, "Error", "Please select HEADS or TAILS"); + return; + } + + try { + setIsSubmitting(true); + writeContract({ + address: CORE_CONTRACT_ADDRESS as `0x${string}`, + abi: CoinTossABI.abi, + functionName: "makeSelection", + args: [BigInt(pool.id), selected], + }); + } catch (err: any) { + const errorMessage = err.message || "Transaction failed"; + showNotification(false, "Transaction Error", errorMessage); + setIsSubmitting(false); + stopCoinAnimation(); + } + }; + + // Handle transaction confirmation + useEffect(() => { + if (isConfirmed) { + setIsSubmitting(false); + setSelectedChoice(null); + showNotification(true, "Success!", "Your selection has been recorded!"); + setIsWaitingForOthers(true); + setIsTimerActive(false); + } + + if (writeError || receiptError) { + setIsSubmitting(false); + showNotification( + false, + "Transaction Failed", + "Your transaction failed to process." + ); + } + }, [isConfirmed, writeError, receiptError]); + + // Handle RoundCompleted event + + useWatchContractEvent({ + address: CORE_CONTRACT_ADDRESS as `0x${string}`, + abi: CoinTossABI.abi, + eventName: "RoundCompleted", + onLogs: (logs) => { + console.log("RoundCompleted logs received:", logs); + + for (const log of logs) { + try { + console.log("Processing log:", log); + + // Extract event arguments, handling both named and positional formats + + const args = log.args || {}; + + // Get poolId - try both named and indexed access + const eventPoolId = + "poolId" in args + ? Number(args.poolId) + : "0" in args + ? Number(args[0]) + : undefined; + + // Get round number + const roundNumber = + "round" in args + ? Number(args.round) + : "1" in args + ? Number(args[1]) + : undefined; + + // Get winning selection + const winningSelection = + "winningSelection" in args + ? Number(args.winningSelection) + : "2" in args + ? Number(args[2]) + : undefined; + + console.log("Extracted data:", { + eventPoolId, + roundNumber, + winningSelection, + }); + + // Skip if we couldn't extract necessary data + if ( + eventPoolId === undefined || + roundNumber === undefined || + winningSelection === undefined + ) { + console.error("Could not extract complete data from event", log); + continue; + } + + // Check if this is a new round completion (avoid processing the same round multiple times) + if (eventPoolId === pool.id && roundNumber > lastCompletedRound) { + console.log("New round completion detected for current pool!"); + setLastCompletedRound(roundNumber); + + // Stop animations immediately + stopCoinAnimation(); + + // Update UI state to indicate processing + setIsWaitingForOthers(false); + + // Determine if user survived based on their choice + const userChoice = selectedChoice; + const userSurvived = userChoice === winningSelection; + + console.log("Round result:", { + userChoice, + winningSelection, + userSurvived, + }); + + // Update eliminated status immediately + if (!userSurvived) { + setIsEliminated(true); + } + + // Force refresh player status from contract + refetchPlayerStatus().then(() => { + console.log("Player status refreshed after round completion"); + }); + + // Show appropriate notification + showNotification( + userSurvived, + `Round ${roundNumber} Completed!`, + userSurvived + ? "You advanced to the next round!" + : "You were eliminated!" + ); + + // Handle game state updates + if (userSurvived) { + // Set timeout to allow notification to be seen + setTimeout(() => { + setRound(roundNumber + 1); + setTimer(20); + setIsTimerActive(true); + setHasSubmitted(false); + setSelectedChoice(null); + }, 3000); + } + } + } catch (error) { + console.error("Error processing event log:", error); + console.error( + "Error details:", + error instanceof Error ? error.message : String(error) + ); + } + } + }, + }); + + // Handle PoolCompleted event + useWatchContractEvent({ + address: CORE_CONTRACT_ADDRESS as `0x${string}`, + abi: CoinTossABI.abi, + eventName: "PoolCompleted", + onLogs: (logs) => { + console.log("PoolCompleted logs received:", logs); + + for (const log of logs) { + try { + // @ts-ignore + const args = log.args || {}; + const eventPoolId = + "poolId" in args + ? Number(args.poolId) + : "0" in args + ? Number(args[0]) + : undefined; + + if (eventPoolId === pool.id) { + console.log("Pool completed!"); + + // Immediately check if the user is a winner + refetchPlayerStatus().then((result) => { + // @ts-ignore + if (result.data && result.data[2]) { + // index 2 is isWinner + setIsWinner(true); + setShowWinnerPopup(true); + // Do NOT redirect winners - they need to claim their prize + + // Update game state to reflect completion + setIsTimerActive(false); + setTimer(0); + setIsWaitingForOthers(false); + + // Show a notification that the game is complete and they won + showNotification( + true, + "You Won!", + "The pool has ended and you are a winner!" + ); + } else { + // User didn't win - redirect after notification + showNotification( + false, + "Game Over", + "The pool has ended. Better luck next time!" + ); + setTimeout(() => { + navigate("/explore"); + }, 5000); + } + }); + } + } catch (error) { + console.error("Error processing PoolCompleted event:", error); + console.error( + "Error details:", + error instanceof Error ? error.message : String(error) + ); + + // Show a generic notification in case of error + showNotification( + false, + "Error", + "An error occurred while processing the game result" + ); + } + } + }, + }); + + // Start/stop coin animation + const startCoinAnimation = () => { + setIsCoinFlipping(true); + coinFlipInterval.current = setInterval(() => { + setCoinRotation((prev) => (prev + 36) % 360); + }, 100); + }; + + const stopCoinAnimation = () => { + if (coinFlipInterval.current) { + clearInterval(coinFlipInterval.current); + coinFlipInterval.current = null; + } + setIsCoinFlipping(false); + setCoinRotation(0); + }; + + // Show notification + const showNotification = ( + isSuccess: boolean, + message: string, + subMessage: string + ) => { + setNotification({ isVisible: true, isSuccess, message, subMessage }); + setTimeout(() => { + setNotification((prev) => ({ ...prev, isVisible: false })); + if (!isSuccess) { + navigate("/explore"); + } + }, 3000); + }; + + // Handle player winning the game + useEffect(() => { + if (isWinnerStatus) { + setIsWinner(true); + setShowWinnerPopup(true); + } + }, [isWinnerStatus]); + + // Handle token claim + const handleClaimPrize = async () => { + try { + setIsSubmitting(true); + writeContract({ + address: CORE_CONTRACT_ADDRESS as `0x${string}`, + abi: CoinTossABI.abi, + functionName: "claimPrize", + args: [BigInt(pool.id)], + }); + showNotification(true, "Success!", "Your prize has been claimed!"); + setShowWinnerPopup(false); + navigate("/explore"); + } catch (err: any) { + const errorMessage = err.message || "Transaction failed"; + showNotification(false, "Transaction Error", errorMessage); + } finally { + setIsSubmitting(false); + } + }; + + if (showClaimInterface) { + return ( +
+
+
🏆
+

+ Congratulations Winner! +

+

+ This pool has ended and you are a winner! Claim your prize now. +

+ +
+
+ ); + } + return ( +
+ {/* Top Game Status Bar */} +
+
+
+
+ {round} +
+
+
ROUND
+
{round}
+
+
+ +
+
REMAINING PLAYERS
+
+ {pool.currentParticipants} +
+
+ +
+
+ + POTENTIAL + + REWARD +
+ +
+ +120 Points +
+
+
+
+ + {/* Main Game Area */} +
+

CHOOSE WISELY

+

+ Only the minority will survive +

+
+ + {/* Coin Animation Area */} + {isCoinFlipping && ( +
+
+ {coinRotation % 180 < 90 ? "🪙" : "💰"} +
+
+ )} + +
+
+ {selectedChoice === PlayerChoice.HEADS && ( +
+ )} + +
+ + {/* VS Divider */} +
+
+
VS
+
+
+ + {/* Tails Option */} +
+ {selectedChoice === PlayerChoice.TAILS && ( +
+ )} + +
+
+ + {/* Timer Section with Enhanced Drama */} +
+
+
+
TIME REMAINING
+
+ {formatTime(timer)} +
+
+ +
+
+
+ +
+
+ Tip: The + minority option wins! +
+
+
+
+ + {/* Notification for round results */} + {notification.isVisible && ( +
+
+
+ {notification.isSuccess ? "🏆" : "❌"} +
+

+ {notification.message} +

+

+ {notification.subMessage} +

+
+
+ )} + + {isWaitingForOthers && ( +
+
+ Waiting for other players to make their selections... +
+
+ )} + + {/* Winner Pop-up */} + {showWinnerPopup && ( +
+
+
🏆
+

+ Congratulations! +

+

+ You are the winner of this pool! Claim your prize now. +

+ +
+
+ )} +
+ ); +}; + +export default PlayGame;