Skip to content

Commit

Permalink
First commit
Browse files Browse the repository at this point in the history
  • Loading branch information
luloxi committed Jan 6, 2024
1 parent 0ba8162 commit 4177484
Show file tree
Hide file tree
Showing 5 changed files with 440 additions and 130 deletions.
52 changes: 15 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,43 +1,27 @@
# 🏗 Scaffold-ETH 2
# ⭕ Tic Tac Toe ❌

🧪 An open-source, up-to-date toolkit for building decentralized applications (dapps) on the Ethereum blockchain. It's designed to make it easier for developers to create and deploy smart contracts and build user interfaces that interact with those contracts.
dApp for betting on the outcome of a Tic Tac Toe game.

⚙️ Built using NextJS, RainbowKit, Hardhat, Wagmi, and Typescript.
- Frontend inspired by [Sportsbook](https://github.com/luloxi/sportsbook)
- Board (pending development by BuidlGuidl members)
- Game idea by [freeCodeCamp Frontend Web Development Tutorial](https://www.youtube.com/watch?v=MsnQ5uepIaE)

-**Contract Hot Reload**: Your frontend auto-adapts to your smart contract as you edit it.
- 🔥 **Burner Wallet & Local Faucet**: Quickly test your application with a burner wallet and local faucet.
- 🔐 **Integration with Wallet Providers**: Connect to different wallet providers and interact with the Ethereum network.
## Development notes

## Contents

- [Requirements](#requirements)
- [Quickstart](#quickstart)
- [Deploying your Smart Contracts to a Live Network](#deploying-your-smart-contracts-to-a-live-network)
- [Deploying your NextJS App](#deploying-your-nextjs-app)
- [Interacting with your Smart Contracts: SE-2 Custom Hooks](#interacting-with-your-smart-contracts-se-2-custom-hooks)
- [Disabling Type & Linting Error Checks](#disabling-type-and-linting-error-checks)
- [Disabling commit checks](#disabling-commit-checks)
- [Deploying to Vercel without any checks](#deploying-to-vercel-without-any-checks)
- [Disabling Github Workflow](#disabling-github-workflow)
- [Contributing to Scaffold-ETH 2](#contributing-to-scaffold-eth-2)

## Requirements

Before you begin, you need to install the following tools:

- [Node (v18 LTS)](https://nodejs.org/en/download/)
- Yarn ([v1](https://classic.yarnpkg.com/en/docs/install/) or [v2+](https://yarnpkg.com/getting-started/install))
- [Git](https://git-scm.com/downloads)
- 🐣 Project being developed by [Newbies Lounge](https://lulox.notion.site/Newbie-s-Lounge-68ea7c4c5f1a4ec29786be6a76516878)
- 👷‍♀️ To view current development tasks, [join this Trello board](https://trello.com/invite/b/s0vot1BA/ATTI366c508087a404ccf9343def4d76d1ce6F7899AA/newbies-lounge).
- 🧰 To chat with other buidlers about this project, [join our Telegram group](https://t.me/+FwCZPG51UhwzOTZh)
- 🛠️ To collaborate, [fork and pull](https://github.com/susam/gitpr) a request to this repo.

## Quickstart

To get started with Scaffold-ETH 2, follow the steps below:
To get started with Tic Tac Toe development, follow the steps below:

1. Clone this repo & install dependencies

```
git clone https://github.com/scaffold-eth/scaffold-eth-2.git
cd scaffold-eth-2
git clone https://github.com/luloxi/TicTacToe.git
cd TicTacToe
yarn install
```

Expand All @@ -47,16 +31,12 @@ yarn install
yarn chain
```

This command starts a local Ethereum network using Hardhat. The network runs on your local machine and can be used for testing and development. You can customize the network configuration in `hardhat.config.ts`.

3. On a second terminal, deploy the test contract:

```
yarn deploy
```

This command deploys a test smart contract to the local network. The contract is located in `packages/hardhat/contracts` and can be modified to suit your needs. The `yarn deploy` command uses the deploy script located in `packages/hardhat/deploy` to deploy the contract to the network. You can also customize the deploy script.

4. On a third terminal, start your NextJS app:

```
Expand All @@ -65,11 +45,9 @@ yarn start

Visit your app on: `http://localhost:3000`. You can interact with your smart contract using the contract component or the example ui in the frontend. You can tweak the app config in `packages/nextjs/scaffold.config.ts`.

Run smart contract test with `yarn hardhat:test`
## Smart contract tests (pending deveopment)

- Edit your smart contract `YourContract.sol` in `packages/hardhat/contracts`
- Edit your frontend in `packages/nextjs/pages`
- Edit your deployment scripts in `packages/hardhat/deploy`
Run smart contract test with `yarn hardhat:test`

## Deploying your Smart Contracts to a Live Network

Expand Down
187 changes: 187 additions & 0 deletions packages/hardhat/contracts/TicTacToe.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

/**
* @title A Tic Tac Toe game
* @author Lulox
* @notice This contract is for creating a bet between two parts on the outcome of a Tic Tac Toe Game
*/
contract TicTacToe {
uint256 public gameIdCounter = 1;

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

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
uint8 moves; // Counter or the number of moves made
}

mapping(uint256 => Game) public games;
uint8[3][8] private winConditions = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8], // Rows
[0, 3, 6],
[1, 4, 7],
[2, 5, 8], // Columns
[0, 4, 8],
[2, 4, 6] // Diagonals
];

event GameCreated(uint256 indexed gameId, address indexed player1, address indexed player2, uint256 bet);
event GameAccepted(uint256 indexed gameId, address indexed team1, address indexed team2);
event MoveMade(uint256 indexed gameId, address indexed player, uint8 position);
event GameFinished(uint256 indexed gameId, address indexed winner, GameState state);

/* MODIFIERS */

modifier onlyPlayers(uint256 gameId) {
require(msg.sender == games[gameId].player1 || msg.sender == games[gameId].player2, "Not a player");
_;
}

modifier onlyValidMove(uint256 gameId, uint8 position) {
require(games[gameId].board[position] == 0, "Invalid move");
require(position < 9, "Invalid position");
_;
}

modifier gameNotCancelled(uint256 gameId) {
require(games[gameId].state != GameState.CANCELED, "Game is canceled");
_;
}

/* EXTERNAL AND PUBLIC FUNCTIONS */

function createGame(address _player2) external payable {
// gameId = keccak256(abi.encodePacked(gameIdCounter, block.timestamp, msg.sender, _player2));

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++;
}

function makeMove(uint256 _gameId, uint8 position)
external
payable
onlyPlayers(_gameId)
gameNotCancelled(_gameId)
onlyValidMove(_gameId, position)
{
if (games[_gameId].player2 == msg.sender && games[_gameId].state == 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(position < 9, "Invalid position");

uint8 currentPlayerSymbol = games[_gameId].moves % 2 == 0 ? 1 : 2;
games[_gameId].board[position] = currentPlayerSymbol;
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);
}

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

// Check row
if (
games[gameId].board[row * 3] == playerSymbol && games[gameId].board[row * 3 + 1] == playerSymbol
&& games[gameId].board[row * 3 + 2] == playerSymbol
) {
return true;
}

// Check column
if (
games[gameId].board[col] == playerSymbol && games[gameId].board[col + 3] == playerSymbol
&& games[gameId].board[col + 6] == playerSymbol
) {
return true;
}

// Check diagonals
if (
(row == col || row + col == 2)
&& (
(
games[gameId].board[0] == playerSymbol && games[gameId].board[4] == playerSymbol
&& games[gameId].board[8] == playerSymbol
)
|| (
games[gameId].board[2] == playerSymbol && games[gameId].board[4] == playerSymbol
&& games[gameId].board[6] == playerSymbol
)
)
) {
return true;
}

return false;
}

/* VIEW AND PURE FUNCTIONS */

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

function getBoard(uint256 _gameId) external view returns (uint8[9] memory) {
return games[_gameId].board;
}
}
87 changes: 0 additions & 87 deletions packages/hardhat/contracts/YourContract.sol

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { DeployFunction } from "hardhat-deploy/types";
*
* @param hre HardhatRuntimeEnvironment object.
*/
const deployYourContract: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const deployTicTacToe: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
/*
On localhost, the deployer account is the one that comes with Hardhat, which is already funded.
Expand All @@ -21,10 +21,10 @@ const deployYourContract: DeployFunction = async function (hre: HardhatRuntimeEn
const { deployer } = await hre.getNamedAccounts();
const { deploy } = hre.deployments;

await deploy("YourContract", {
await deploy("TicTacToe", {
from: deployer,
// Contract constructor arguments
args: [deployer],
args: [],
log: true,
// autoMine: can be passed to the deploy function to make the deployment process faster on local networks by
// automatically mining the contract deployment transaction. There is no effect on live networks.
Expand All @@ -35,8 +35,8 @@ const deployYourContract: DeployFunction = async function (hre: HardhatRuntimeEn
// const yourContract = await hre.ethers.getContract("YourContract", deployer);
};

export default deployYourContract;
export default deployTicTacToe;

// Tags are useful if you have multiple deploy files and only want to run one of them.
// e.g. yarn deploy --tags YourContract
deployYourContract.tags = ["YourContract"];
deployTicTacToe.tags = ["TicTacToe"];
Loading

0 comments on commit 4177484

Please sign in to comment.