Skip to content

Commit

Permalink
Added comments to the contract and did some ordering
Browse files Browse the repository at this point in the history
  • Loading branch information
luloxi committed Jan 11, 2024
1 parent d7e09a8 commit dfb1fa5
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 59 deletions.
120 changes: 64 additions & 56 deletions packages/hardhat/contracts/TicTacToe.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,32 @@
pragma solidity 0.8.17;

/**
* @title A Tic Tac Toe game
* @title TicTacToe
* @author Lulox
* @notice This contract is for creating a bet between two parts on the outcome of a Tic Tac Toe Game
* @notice A betting TicTacToe contract.
* @dev Currently for using with one transaction per move,
* in a future may be replaced with signatures
*/

contract TicTacToe {
uint256 public gameIdCounter = 1;
uint256 public gameIdCounter = 0;

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

struct Game {
address player1;
address player2;
GameState state;
uint256 bet;
uint256 lastMoveTime;
bool player1Withdrawn; // Indicates whether player 1 has withdrawn or not
bool player2Withdrawn; // Indicates whether player 2 has withdrawn or not
uint8[9] board; // 0: empty, 1: X, 2: O
bool player1Withdrawn;
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
}

Expand All @@ -39,93 +40,79 @@ contract TicTacToe {

/* MODIFIERS */

modifier onlyPlayers(uint256 gameId) {
require(msg.sender == games[gameId].player1 || msg.sender == games[gameId].player2, "Not a player");
modifier checkForTie(uint256 gameId) {
// If all moves were used and no victory was gotten
if (games[gameId].moves == 9) {
// Set the game as a Tie and finish it so prizes can be withdrawn
finishGame(gameId, address(0));
}
_;
}

modifier onlyValidMove(uint256 gameId, uint8 position) {
require(games[gameId].board[position] == 0, "Invalid move");
require(position < 9, "Invalid position");
modifier onlyPlayers(uint256 gameId) {
require(msg.sender == games[gameId].player1 || msg.sender == games[gameId].player2, "Not a player");
_;
}

modifier gameNotCancelled(uint256 gameId) {
require(games[gameId].state != GameState.CANCELED, "Game was canceled!");
modifier onlyValidMove(uint256 gameId, uint8 position) {
require(position < 9, "Position not valid");
require(games[gameId].board[position] == 0, "Position not empty");

_;
}

/* EXTERNAL AND PUBLIC FUNCTIONS */

function createGame(address _player2) external payable {
// Increase gameIdCounter by one, as a new game is created
gameIdCounter++;
// Fill the information as a blank game with the data for the Game struct
games[gameIdCounter] = Game({
player1: msg.sender,
player2: _player2,
state: GameState.PENDING,
bet: msg.value,
lastMoveTime: block.timestamp,
player1Withdrawn: false,
player2Withdrawn: false,
board: [0, 0, 0, 0, 0, 0, 0, 0, 0],
moves: 0
});

emit GameCreated(gameIdCounter, msg.sender, _player2, msg.value);
gameIdCounter++;
// Emit an event
// can be used by the frontend to know that something happened and react to it
emit GameCreated(gameIdCounter, msg.sender, _player2, msg.value);
}

function makeMove(uint256 _gameId, uint8 position)
external
payable
checkForTie(_gameId)
onlyPlayers(_gameId)
gameNotCancelled(_gameId)
onlyValidMove(_gameId, position)
{
if (games[_gameId].player2 == msg.sender && games[_gameId].state == GameState.PENDING) {
GameState currentState = games[_gameId].state;

if (currentState == GameState.PENDING) {
acceptGame(_gameId);
} else {
require(msg.value == 0, "Cannot send ETH with move");
require(games[_gameId].state == GameState.PLAYING, "Game not in progress");
require(currentState == GameState.PLAYING, "Game already ended!");
}

require(position < 9, "Invalid position");

uint8 currentPlayerSymbol = games[_gameId].moves % 2 == 0 ? 1 : 2;
games[_gameId].board[position] = currentPlayerSymbol;
// Determine the current Player symbol
uint8 currentPlayer = games[_gameId].moves % 2 == 0 ? 1 : 2;
// Add the corresponding mark in the position of the board
games[_gameId].board[position] = currentPlayer;
// Check if after adding that symbol, a win is achieved, and react to it if that's the case
checkWin(_gameId, position, currentPlayer);
// And add 1 to the number of moves made in the game
games[_gameId].moves++;
games[_gameId].lastMoveTime = block.timestamp;

emit MoveMade(_gameId, msg.sender, position);

// Check for win
if (checkWin(_gameId, position, currentPlayerSymbol)) {
finishGame(_gameId, msg.sender, currentPlayerSymbol == 1 ? GameState.PLAYER1WON : GameState.PLAYER2WON);
} else if (games[_gameId].moves == 9) {
// Check for a draw
finishGame(_gameId, address(0), GameState.TIE);
}

}

/* INTERNAL FUNCTIONS */

function acceptGame(uint256 _gameId) internal {
require(games[_gameId].state == GameState.PENDING, "Game not in pending state");
require(games[_gameId].player2 == msg.sender, "Not player2");
require(msg.value == games[_gameId].bet, "Haven't sent enough ETH!");

games[_gameId].state = GameState.PLAYING;

emit GameAccepted(_gameId, games[_gameId].player1, games[_gameId].player2);
}

function finishGame(uint256 gameId, address winner, GameState state) internal {
games[gameId].state = state;
emit GameFinished(gameId, winner, state);
}

// This used to be the array to check against if there was a win condition already.
// It is now replaced by the internal function checkWin
//
// uint8[3][8] private winConditions = [
// [0, 1, 2],
// [3, 4, 5],
Expand All @@ -137,7 +124,7 @@ contract TicTacToe {
// [2, 4, 6] // Diagonals
// ];

function checkWin(uint256 gameId, uint8 position, uint8 playerSymbol) internal view returns (bool) {
function checkWin(uint256 gameId, uint8 position, uint8 playerSymbol) internal returns (bool) {
uint8 row = position / 3;
uint8 col = position % 3;

Expand All @@ -146,6 +133,7 @@ contract TicTacToe {
games[gameId].board[row * 3] == playerSymbol && games[gameId].board[row * 3 + 1] == playerSymbol
&& games[gameId].board[row * 3 + 2] == playerSymbol
) {
finishGame(gameId, msg.sender);
return true;
}

Expand All @@ -154,6 +142,7 @@ contract TicTacToe {
games[gameId].board[col] == playerSymbol && games[gameId].board[col + 3] == playerSymbol
&& games[gameId].board[col + 6] == playerSymbol
) {
finishGame(gameId, msg.sender);
return true;
}

Expand All @@ -170,16 +159,35 @@ contract TicTacToe {
&& games[gameId].board[6] == playerSymbol
)
)

) {
finishGame(gameId, msg.sender);
return true;
}

return false;
}

function acceptGame(uint256 _gameId) internal {
require(games[_gameId].player2 == msg.sender, "You must be player 2 to accept!");
require(msg.value == games[_gameId].bet, "You haven't sent enough ETH to accept!");

games[_gameId].state = GameState.PLAYING;

emit GameAccepted(_gameId, games[_gameId].player1, games[_gameId].player2);
}

function finishGame(uint256 gameId, address winner) internal {
// Incliude a check for state assuming the winner will be the msg.sender
// In the case of a tie call with address(0) as the winner, add a condition for that too

GameState state = games[gameId].state;
emit GameFinished(gameId, winner, state);
}

/* VIEW AND PURE FUNCTIONS */

function getCurrentPlayer(uint256 _gameId) external view returns (uint256) {
function getCurrentPlayer(uint256 _gameId) public view returns (uint256) {
return games[_gameId].moves % 2 == 0 ? 1 : 2;
}

Expand Down
10 changes: 8 additions & 2 deletions packages/nextjs/components/tictactoe/TicTacToeBoard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
import { Address } from "../scaffold-eth";
import { Button, Flex, Grid } from "@chakra-ui/react";
import { ethers } from "ethers";
import { useScaffoldContractWrite } from "~~/hooks/scaffold-eth";
import { useScaffoldContractRead, useScaffoldContractWrite } from "~~/hooks/scaffold-eth";
import { MoveMadeProps, TicTacToeBoardProps } from "~~/types/TicTacToeTypes";

const TicTacToeBoard: React.FC<TicTacToeBoardProps> = ({
Expand All @@ -16,7 +16,13 @@ const TicTacToeBoard: React.FC<TicTacToeBoardProps> = ({
const [betPayment, setBetPayment] = useState<number>(game.bet);
const [board, setBoard] = useState<number[]>(Array(9).fill(0)); // Initialize an empty board

console.log("Moves list for game ID #", game.gameId, ": ", movesList);
const { data: getBoard } = useScaffoldContractRead({
contractName: "TicTacToe",
functionName: "getBoard",
args: [BigInt(game.gameId)],
});

console.log("getBoard reads for #", game.gameId, ": ", getBoard);

const { writeAsync: makeMove } = useScaffoldContractWrite({
contractName: "TicTacToe",
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: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853",
address: "0x0165878A594ca255338adfa4d48449f69242Eb8F",
abi: [
{
anonymous: false,
Expand Down

0 comments on commit dfb1fa5

Please sign in to comment.