Skip to content

Commit

Permalink
Several contract and frontend changes
Browse files Browse the repository at this point in the history
  • Loading branch information
luloxi committed Jan 15, 2024
1 parent 4af35af commit bb73995
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 60 deletions.
117 changes: 66 additions & 51 deletions packages/hardhat/contracts/TicTacToe.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "hardhat/console.sol";

/**
* @title TicTacToe
Expand All @@ -13,14 +12,15 @@ import "hardhat/console.sol";

contract TicTacToe {
uint256 public gameIdCounter = 0;
uint256 public immutable timeOutValue = 10 seconds;
uint256 public immutable timeOutValue = 20 minutes;

enum GameState {
PENDING,
PLAYING,
PLAYER1WON,
PLAYER2WON,
TIE
TIE,
DELETED
}

struct Game {
Expand All @@ -32,7 +32,7 @@ contract TicTacToe {
bool player2Withdrawn;
uint8[9] board; // 0 (no player): empty, 1 (player 1): X, 2 (player 2): O
uint8 moves; // Counter or the number of moves made
uint256 lastTimePlayed;
uint256 lastTimePlayed;
}

mapping(uint256 => Game) public games;
Expand All @@ -48,10 +48,7 @@ contract TicTacToe {
address indexed team1,
address indexed team2
);
event GameDeleted(
uint256 indexed gameId,
address indexed player1
);
event GameDeleted(uint256 indexed gameId);
event MoveMade(
uint256 indexed gameId,
address indexed player,
Expand Down Expand Up @@ -118,7 +115,7 @@ contract TicTacToe {
player2Withdrawn: false,
board: [0, 0, 0, 0, 0, 0, 0, 0, 0],
moves: 0,
lastTimePlayed : 0
lastTimePlayed: 0
});

// This event can be used by the frontend to know that something happened and react to it
Expand All @@ -137,52 +134,61 @@ contract TicTacToe {
game.bet == msg.value,
"You haven't sent the required ETH to accept"
);
// Set the initial time count to check the timeout
games[_gameId].lastTimePlayed = block.timestamp;
// Set the initial time count to check the timeout
games[_gameId].lastTimePlayed = block.timestamp;

// Set the game state to PLAYING and emit an event
games[_gameId].state = GameState.PLAYING;
emit GameAccepted(_gameId, game.player1, game.player2);
}

function deleteGame(uint256 _gameId) external {
Game memory game = games[_gameId];
require(game.player1 == msg.sender, "You must be player 1 to delete the request");
require(
game.state == GameState.PENDING,
"Game must be PENDING to be deleted"
);
require(
block.timestamp - games[_gameId].lastTimePlayed <= timeOutValue,
"Timeout value hasnt been reached yet!"
);

games[_gameId].state = GameState.TIE;
uint256 paidBetAmount = calculatePrize(_gameId);
require(paidBetAmount > 0, "Invalid bet amount");
// Transfer the bet to the player
payable(msg.sender).transfer(paidBetAmount);
emit GameDeleted(_gameId, game.player1);
}

function makeMove(
uint256 _gameId,
uint8 position
) external onlyValidMove(_gameId, position) {
// Determine the current Player symbol
// 1 is player1, 2 is player2
uint8 playerSymbol = games[_gameId].moves % 2 == 0 ? 1 : 2;
// Add the corresponding mark in the position of the game board
games[_gameId].board[position] = playerSymbol;
// And add 1 to the number of moves made in the game
games[_gameId].moves++;
games[_gameId].lastTimePlayed = block.timestamp;
// Add the corresponding mark in the position of the game board
games[_gameId].board[position] = playerSymbol;
// And add 1 to the number of moves made in the game
games[_gameId].moves++;
games[_gameId].lastTimePlayed = block.timestamp;

emit MoveMade(_gameId, msg.sender, position);
emit MoveMade(_gameId, msg.sender, position);
// Check if after adding that symbol, a win is achieved, and react to it if that's the case
checkWin(_gameId, position, msg.sender);
}

// For when game wasn't accepted and player wants to recover betted ammount
function deleteGame(uint256 _gameId) external {
Game memory game = games[_gameId];
require(
msg.sender == game.player1,
"You must be player 1 to delete the request"
);
require(
game.state == GameState.PENDING,
"Game must be PENDING to be deleted"
);

games[_gameId].state = GameState.DELETED;

payable(msg.sender).transfer(game.bet);
emit GameDeleted(_gameId);
}

// To incentivize players to keep playing or not quit half a game to not allow the other part to withdraw prize
function winByTimeout(uint256 _gameId) external {
require(
block.timestamp - games[_gameId].lastTimePlayed > timeOutValue,
"Timeout value hasnt been reached yet!"
);
games[_gameId].moves % 2 == 0
? finishGame(_gameId, games[_gameId].player2)
: finishGame(_gameId, games[_gameId].player1);
}

// Function to withdraw the prize based on game state
function withdrawPrize(uint256 _gameId) external {
Game storage game = games[_gameId];
Expand All @@ -202,7 +208,7 @@ contract TicTacToe {
"You have already withdrawn the prize!"
);
game.player1Withdrawn = true;
}
}

// WITHDRAW RULES FOR PLAYER 2 VICTORY
if (game.state == GameState.PLAYER2WON && msg.sender == game.player2) {
Expand All @@ -211,17 +217,23 @@ contract TicTacToe {
"You have already withdrawn the prize!"
);
game.player2Withdrawn = true;
}
}

// WITHDRAW RULES FOR TIE RESULT
if (game.state == GameState.TIE) {
if (msg.sender == game.player1) {
require(!game.player1Withdrawn,"You have already withdrawn the prize!");
require(
!game.player1Withdrawn,
"You have already withdrawn the prize!"
);
game.player1Withdrawn = true;
} else if (msg.sender == game.player2) {
require(!game.player2Withdrawn, "You have already withdrawn the prize!");
require(
!game.player2Withdrawn,
"You have already withdrawn the prize!"
);
game.player2Withdrawn = true;
}
}
}

// Calculate and transfer the prize based on the game state
Expand All @@ -234,11 +246,6 @@ contract TicTacToe {

/* INTERNAL FUNCTIONS */

function winByTimeout(uint256 _gameId) internal {
require(block.timestamp - games[_gameId].lastTimePlayed > timeOutValue, "Timeout value hasnt been reached yet!");
games[_gameId].moves % 2 == 0 ? finishGame(_gameId, games[_gameId].player2) : finishGame(_gameId, games[_gameId].player1);
}

function checkWin(
uint256 _gameId,
uint8 _position,
Expand Down Expand Up @@ -330,7 +337,7 @@ contract TicTacToe {
return totalBet;
} else if (game.state == GameState.TIE) {
// In the case of a tie, split the total bet equally between players
return totalBet / 2;
return game.bet;
} else {
// Invalid game state
revert("Invalid game state");
Expand All @@ -351,15 +358,23 @@ contract TicTacToe {
return games[_gameId].board;
}

function getGameState(uint256 _gameId) public view returns (GameState) {
function getGameState(uint256 _gameId) public view returns (GameState) {
return games[_gameId].state;
}

function hasPlayer1WithdrawnPrize(uint256 _gameId) public view returns(bool) {
function getLastTimePlayed(uint256 _gameId) public view returns (uint256) {
return games[_gameId].lastTimePlayed;
}

function hasPlayer1WithdrawnPrize(
uint256 _gameId
) public view returns (bool) {
return games[_gameId].player1Withdrawn;
}

function hasPlayer2WithdrawnPrize(uint256 _gameId) public view returns(bool) {
function hasPlayer2WithdrawnPrize(
uint256 _gameId
) public view returns (bool) {
return games[_gameId].player2Withdrawn;
}
}
63 changes: 63 additions & 0 deletions packages/nextjs/components/tictactoe/TicTacToeBoard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ const TicTacToeBoard: React.FC<TicTacToeBoardProps> = ({
game,
isGameAccepted,
isGameFinished,
isGameDeleted,
currentPlayer,
// movesMade,
}) => {
const [board, setBoard] = useState<number[]>(Array(9).fill(0)); // Initialize an empty board
// const [timeRemaining, setTimeRemaining] = useState<string>("");

const { data: boardFromContract } = useScaffoldContractRead({
contractName: "TicTacToe",
Expand Down Expand Up @@ -44,6 +46,37 @@ const TicTacToeBoard: React.FC<TicTacToeBoardProps> = ({
args: [BigInt(game.gameId)],
});

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

// useEffect(() => {
// let deadline: number;

// if (lastTimePlayed) {
// deadline = parseInt(lastTimePlayed.toString(), 10);
// }

// const timer = setInterval(() => {
// const now = Math.floor(new Date().getTime() / 1000);

// if (now >= deadline) {
// setTimeRemaining("Deadline has passed");
// clearInterval(timer);
// } else {
// const timeRemainingSeconds = deadline - now;
// const hours = Math.floor(timeRemainingSeconds / 3600);
// const minutes = Math.floor((timeRemainingSeconds % 3600) / 60);
// const seconds = Math.floor(timeRemainingSeconds % 60);
// setTimeRemaining(`${hours}:${minutes}:${seconds}`);
// }
// }, 1000);

// return () => clearInterval(timer);
// }, [lastTimePlayed]);

const { writeAsync: makeMove } = useScaffoldContractWrite({
contractName: "TicTacToe",
functionName: "makeMove",
Expand All @@ -57,6 +90,18 @@ const TicTacToeBoard: React.FC<TicTacToeBoardProps> = ({
value: BigInt(game.bet),
});

const { writeAsync: deleteGame } = useScaffoldContractWrite({
contractName: "TicTacToe",
functionName: "deleteGame",
args: [BigInt(game.gameId)],
});

// const { writeAsync: winByTimeout } = useScaffoldContractWrite({
// contractName: "TicTacToe",
// functionName: "winByTimeout",
// args: [BigInt(game.gameId)],
// });

const { writeAsync: withdrawPrize } = useScaffoldContractWrite({
contractName: "TicTacToe",
functionName: "withdrawPrize",
Expand All @@ -76,8 +121,19 @@ const TicTacToeBoard: React.FC<TicTacToeBoardProps> = ({
{isGameAccepted ? (isGameFinished ? "played against" : "is playing against") : "challenged"}
<Address address={game.player2} />
</Flex>
{/* {isGameAccepted ? (
<Box alignItems={"center"} textAlign={"center"} justifyContent={"center"} textColor={"red"}>
{timeRemaining}
</Box>
) : (
""
)} */}
{isGameAccepted ? (
""
) : isGameDeleted ? (
<Box alignItems={"center"} textAlign={"center"} justifyContent={"center"} textColor={"red"}>
Game was deleted
</Box>
) : (
<Flex
direction="row"
Expand All @@ -96,6 +152,13 @@ const TicTacToeBoard: React.FC<TicTacToeBoardProps> = ({
<Button colorScheme={"green"} onClick={() => acceptGame()}>
Accept game
</Button>
) : game.player1 === currentPlayer ? (
<>
<Button colorScheme={"red"} onClick={() => deleteGame()}>
Delete game
</Button>
<Box>Recover your betted amount (if any)</Box>
</>
) : (
""
)}
Expand Down
27 changes: 20 additions & 7 deletions 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: "0x5FbDB2315678afecb367f032d93F642f64180aa3",
address: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512",
abi: [
{
anonymous: false,
Expand Down Expand Up @@ -74,12 +74,6 @@ const deployedContracts = {
name: "gameId",
type: "uint256",
},
{
indexed: true,
internalType: "address",
name: "player1",
type: "address",
},
],
name: "GameDeleted",
type: "event",
Expand Down Expand Up @@ -297,6 +291,25 @@ const deployedContracts = {
stateMutability: "view",
type: "function",
},
{
inputs: [
{
internalType: "uint256",
name: "_gameId",
type: "uint256",
},
],
name: "getLastTimePlayed",
outputs: [
{
internalType: "uint256",
name: "",
type: "uint256",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [
{
Expand Down
Loading

0 comments on commit bb73995

Please sign in to comment.