Skip to content

Commit

Permalink
Game is playable, still lacks withdraw mechanism
Browse files Browse the repository at this point in the history
  • Loading branch information
luloxi committed Jan 13, 2024
1 parent 07ed606 commit 26c45d1
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 147 deletions.
1 change: 1 addition & 0 deletions packages/nextjs/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@
# More info: https://nextjs.org/docs/pages/building-your-application/configuring/environment-variables
NEXT_PUBLIC_ALCHEMY_API_KEY=
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=
NEXT_PUBLIC_DEPLOY_BLOCK=
60 changes: 27 additions & 33 deletions packages/nextjs/components/tictactoe/TicTacToeBoard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@ import { ethers } from "ethers";
import { useScaffoldContractRead, useScaffoldContractWrite } from "~~/hooks/scaffold-eth";
import { TicTacToeBoardProps } from "~~/types/TicTacToeTypes";

const TicTacToeBoard: React.FC<TicTacToeBoardProps> = ({ game, isGameAccepted, isGameFinished }) => {
const [position, setPosition] = useState<number>(0);
const TicTacToeBoard: React.FC<TicTacToeBoardProps> = ({
game,
isGameAccepted,
isGameFinished,
currentPlayer,
// movesMade,
}) => {
const [board, setBoard] = useState<number[]>(Array(9).fill(0)); // Initialize an empty board

const { data: boardFromContract } = useScaffoldContractRead({
Expand All @@ -15,16 +20,7 @@ const TicTacToeBoard: React.FC<TicTacToeBoardProps> = ({ game, isGameAccepted, i
args: [BigInt(game.gameId)],
});

// const { data: numberOfMoves } = useScaffoldContractRead({
// contractName: "TicTacToe",
// functionName: "getNumberOfMoves",
// args: [BigInt(game.gameId)],
// });

console.log("boardFromContract: ", boardFromContract);

useEffect(() => {
// Update the local board based on the latest data from the contract
if (boardFromContract) {
setBoard(boardFromContract.map(Number));
}
Expand All @@ -33,7 +29,7 @@ const TicTacToeBoard: React.FC<TicTacToeBoardProps> = ({ game, isGameAccepted, i
const { writeAsync: makeMove } = useScaffoldContractWrite({
contractName: "TicTacToe",
functionName: "makeMove",
args: [BigInt(game.gameId), position],
args: [BigInt(game.gameId), 0],
});

const { writeAsync: acceptGame } = useScaffoldContractWrite({
Expand All @@ -43,14 +39,6 @@ const TicTacToeBoard: React.FC<TicTacToeBoardProps> = ({ game, isGameAccepted, i
value: BigInt(game.bet),
});

const handleMakeMove = async () => {
try {
await makeMove();
} catch (error) {
console.error("Error making move:", error);
}
};

return (
<Box key={game.gameId}>
<Flex fontSize={24} textColor={"red"} alignItems={"center"} justifyContent={"center"} paddingTop={3}>
Expand All @@ -60,8 +48,11 @@ const TicTacToeBoard: React.FC<TicTacToeBoardProps> = ({ game, isGameAccepted, i
</Box>
</Flex>
<Flex direction="row" justifyContent={"center"} textAlign={"center"} gap={6} padding={3}>
<Address address={game.player1} /> {isGameAccepted ? "is playing against" : "has challenged"}{" "}
<Address address={game.player2} />
<Address address={game.player1} />{" "}
{isGameAccepted ? (isGameFinished ? "played against" : "is playing against") : "challenged"}
<>
<Address address={game.player2} />
</>
</Flex>
{isGameAccepted ? (
""
Expand All @@ -75,12 +66,17 @@ const TicTacToeBoard: React.FC<TicTacToeBoardProps> = ({ game, isGameAccepted, i
paddingBottom={3}
>
<Box>
Each player bets: <br /> {parseFloat(ethers.formatEther(game.bet.toString())).toFixed(4)} ETH
Each player bets: <br />{" "}
<strong>{parseFloat(ethers.formatEther(game.bet.toString())).toFixed(4)} ETH</strong>
</Box>
<Box>
<Button colorScheme={"green"} onClick={() => acceptGame()}>
Accept game
</Button>
{game.player2 === currentPlayer ? (
<Button colorScheme={"green"} onClick={() => acceptGame()}>
Accept game
</Button>
) : (
""
)}
</Box>
</Flex>
)}
Expand All @@ -94,20 +90,21 @@ const TicTacToeBoard: React.FC<TicTacToeBoardProps> = ({ game, isGameAccepted, i
paddingBottom={3}
>
<Box>
Each player betted: <br /> {parseFloat(ethers.formatEther(game.bet.toString())).toFixed(4)} ETH
Each player betted: <br />{" "}
<strong>{parseFloat(ethers.formatEther(game.bet.toString())).toFixed(4)} ETH</strong>
</Box>

<Box>
Game state: <br />
{isGameFinished ? "Finished" : "Not finished"}
{isGameFinished ? <strong>Finished</strong> : <strong>Not finished</strong>}
</Box>
</Flex>
) : (
""
)}
{/* Render the Tic Tac Toe board here */}
{isGameAccepted ? (
<Grid templateColumns="repeat(3, 1fr)" gap={2}>
<Grid templateColumns="repeat(3, 1fr)" justifyItems={"center"} gap={2}>
{board.map((cell, index) => (
<Button
key={index}
Expand All @@ -119,10 +116,7 @@ const TicTacToeBoard: React.FC<TicTacToeBoardProps> = ({ game, isGameAccepted, i
height={70}
disabled={cell !== 0 || !isGameAccepted || isGameFinished}
onClick={() => {
if (!isGameFinished) {
setPosition(index);
handleMakeMove();
}
makeMove({ args: [BigInt(game.gameId), index] });
}}
>
{cell === 1 ? "✖️" : cell === 2 ? "⭕" : ""}
Expand Down
2 changes: 1 addition & 1 deletion packages/nextjs/contracts/deployedContracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { GenericContractsDeclaration } from "~~/utils/scaffold-eth/contract";
const deployedContracts = {
31337: {
TicTacToe: {
address: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0",
address: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853",
abi: [
{
anonymous: false,
Expand Down
138 changes: 27 additions & 111 deletions packages/nextjs/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,138 +1,47 @@
import { useEffect, useState } from "react";
import { GameAcceptedProps, GameCreatedProps, GameFinishedProps } from "../types/TicTacToeTypes";
import { Card, CardBody, Flex, Heading } from "@chakra-ui/react";
import type { NextPage } from "next";
import { useAccount } from "wagmi";
import { MetaHeader } from "~~/components/MetaHeader";
import CreateChallengeBox from "~~/components/tictactoe/CreateChallengeBox";
import TicTacToeBoard from "~~/components/tictactoe/TicTacToeBoard";
import { useScaffoldContractRead, useScaffoldEventHistory, useScaffoldEventSubscriber } from "~~/hooks/scaffold-eth";
import { useScaffoldEventHistory } from "~~/hooks/scaffold-eth";

const Home: NextPage = () => {
const [gameHistory, setGameHistory] = useState<GameCreatedProps[]>([]);
const [gameAcceptedHistory, setGameAcceptedHistory] = useState<GameAcceptedProps[]>([]);
const [gameFinishedHistory, setGameFinishedHistory] = useState<GameFinishedProps[]>([]);
const { address: connectedAddress } = useAccount();

const { data: gameIdCounter } = useScaffoldContractRead({
contractName: "TicTacToe",
functionName: "gameIdCounter",
args: undefined,
});

console.log("gameIdCounter: ", gameIdCounter);

// Event history hooks
const { data: GameCreatedHistory } = useScaffoldEventHistory({
contractName: "TicTacToe",
eventName: "GameCreated",
fromBlock: BigInt(process.env.NEXT_PUBLIC_DEPLOY_BLOCK || "0"),
blockData: false,
watch: true,
});

const { data: GameAcceptedHistory } = useScaffoldEventHistory({
contractName: "TicTacToe",
eventName: "GameAccepted",
fromBlock: BigInt(process.env.NEXT_PUBLIC_DEPLOY_BLOCK || "0"),
blockData: false,
watch: true,
});

const { data: GameFinishedHistory } = useScaffoldEventHistory({
contractName: "TicTacToe",
eventName: "GameFinished",
fromBlock: BigInt(process.env.NEXT_PUBLIC_DEPLOY_BLOCK || "0"),
blockData: false,
watch: true,
});

// useEffect for dynamic updating? (not sure if needed)
useEffect(() => {
const mappedHistory = GameCreatedHistory?.map(event => ({
gameId: parseInt(event.args[0].toString()),
player1: event.args[1],
player2: event.args[2],
bet: parseInt(event.args[3].toString()),
})) as GameCreatedProps[];
setGameHistory(mappedHistory);
}, [GameCreatedHistory]);

useEffect(() => {
const mappedHistory = GameAcceptedHistory?.map(event => ({
gameId: parseInt(event.args[0].toString()),
player1: event.args[1],
player2: event.args[2],
})) as GameAcceptedProps[];
setGameAcceptedHistory(mappedHistory);
}, [GameAcceptedHistory]);

useEffect(() => {
const mappedHistory = GameFinishedHistory?.map(event => ({
gameId: parseInt(event.args[0].toString()),
winner: event.args[1],
state: parseInt(event.args[2].toString()),
})) as GameFinishedProps[];
setGameFinishedHistory(mappedHistory);
}, [GameFinishedHistory]);

// Event subscription hooks
useScaffoldEventSubscriber({
const { data: MoveMadeHistory } = useScaffoldEventHistory({
contractName: "TicTacToe",
eventName: "GameCreated",
listener: (logs: any[]) => {
setGameHistory(indexedHistory => {
const newGameCreated: GameCreatedProps = {
gameId: parseInt(logs[0].args[0].toString()),
player1: logs[0].args[1],
player2: logs[0].args[2],
bet: parseInt(logs[0].args[3].toString()),
};

// Check if the game with the same gameId already exists
const existingGame = indexedHistory.find(game => game.gameId === newGameCreated.gameId);

// If it doesn't exist, add the new game to the history
if (!existingGame) {
return [newGameCreated, ...indexedHistory];
}

// If it exists, return the existing history without adding a duplicate
return indexedHistory;
});
},
});

useScaffoldEventSubscriber({
contractName: "TicTacToe",
eventName: "GameAccepted",
listener: (logs: any[]) => {
setGameAcceptedHistory(indexedHistory => {
const newGameAccepted: GameAcceptedProps = {
gameId: parseInt(logs[0].args[0].toString()),
player1: logs[0].args[1],
player2: logs[0].args[2],
};
return [newGameAccepted, ...indexedHistory];
});
},
});

useScaffoldEventSubscriber({
contractName: "TicTacToe",
eventName: "GameFinished",
listener: (logs: any[]) => {
setGameFinishedHistory(indexedHistory => {
const newGameFinished: GameFinishedProps = {
gameId: parseInt(logs[0].args[0].toString()),
winner: logs[0].args[1],
state: parseInt(logs[0].args[2].toString()),
};
return [newGameFinished, ...indexedHistory];
});
},
eventName: "MoveMade",
fromBlock: BigInt(process.env.NEXT_PUBLIC_DEPLOY_BLOCK || "0"),
watch: true,
});

const gameCards = gameHistory?.map(game => {
const isGameAccepted = gameAcceptedHistory.some(acceptedGame => acceptedGame.gameId === game.gameId);
const isGameFinished = gameFinishedHistory.some(finishedGame => finishedGame.gameId === game.gameId);

return { game, isGameAccepted, isGameFinished };
const gameCards = GameCreatedHistory?.map(game => {
const isGameAccepted = GameAcceptedHistory?.some(acceptedGame => acceptedGame.args[0] === game.args[0]);
const isGameFinished = GameFinishedHistory?.some(finishedGame => finishedGame.args[0] === game.args[0]);
const movesMade = MoveMadeHistory?.filter(moveMade => moveMade.args[0] == game.args[0]);
return { game, isGameAccepted, isGameFinished, movesMade };
});

return (
Expand All @@ -141,7 +50,7 @@ const Home: NextPage = () => {
<div
style={{
position: "relative",
minHeight: "100vh",
minHeight: "80vh",
}}
>
<div
Expand All @@ -165,20 +74,27 @@ const Home: NextPage = () => {
width="container.sm"
maxWidth={{ base: "container.sm", sm: "container.sm", md: "container.md" }}
variant="solid"
maxHeight={{ base: "240", sm: "240", md: "480", lg: "720" }}
maxHeight={{ base: "240", sm: "240", md: "360", lg: "540" }}
overflow={"auto"}
textColor={"white"}
backgroundColor={"gray.900"}
>
<CardBody>
<Heading size="xl">⭕ See your active challenges! ❌</Heading>
<Flex direction="column" alignItems="center" justifyContent="center">
{gameCards?.map(({ game, isGameAccepted, isGameFinished }) => (
{gameCards?.map(({ game, isGameAccepted, isGameFinished, movesMade }) => (
<TicTacToeBoard
key={game.gameId}
game={game}
key={game.args[0]}
game={{
gameId: parseInt(game.args[0].toString()),
player1: game.args[1],
player2: game.args[2],
bet: parseInt(game.args[3].toString()),
}}
isGameAccepted={isGameAccepted}
isGameFinished={isGameFinished}
currentPlayer={connectedAddress}
movesMade={movesMade}
/>
))}
</Flex>
Expand Down
6 changes: 4 additions & 2 deletions packages/nextjs/types/TicTacToeTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export type GameFinishedProps = {

export type TicTacToeBoardProps = {
game: GameCreatedProps;
isGameAccepted: boolean;
isGameFinished: boolean;
isGameAccepted?: boolean;
isGameFinished?: boolean;
currentPlayer?: string;
movesMade?: any;
};

0 comments on commit 26c45d1

Please sign in to comment.