# How to Build Tic Tac Toe Game with Solidity

By [Sergen](https://paragraph.com/@0xsergen) · 2022-10-13

---

After famous _DeFi Summer_, P2E (Play to Earn) games become very popular. Thus, building a game on blockchain with Solidity is both essential and funny. I had to develop a project as a final project of the practicum that is organized by [Patika.dev](http://patika.dev/) and I picked building Tic Tac Toe game which is very simple and entertaining.

I am in a journey of a being a Web3 developer, I had developed some projects with following tutorials and lessons but it would be my first dApp to code from scratch. So, chose easier game to code. Enough talking, let's start!

Note: I assume that reader has basic knowledge about blockchains and development tools. I try to explain everything step by step but if you need much more detailed and organized _course_, check [the article](https://sergio2098.hashnode.dev/my-web3-development-journey-1#heading-researchs-for-being-developer) which I listed great resources.

> The goal of the game is to be the first player to get three in a row on a 3-by-3 grid.

If you wonder how it will look after building, check [my dApp](https://patika-akbank-web3-final-case.vercel.app/) or [GitHub repo](https://github.com/sergio2098/patika-akbank-web3-final-case).

#### Rules

*   Player who create a game is defined as Player 1.
    
*   Game starts by Player 1.
    
*   Reward pool is determined by player who create a game. $$(Reward Pool = 2 \* Entry Fee)$$
    
*   Winner gets all reward pool excluding commission.
    
*   If there is no winner (in case of draw) each player get their entry fee back. _No commission_ for draw.
    
*   If anyone doesn't join an existing game for 1 day at least, creator of that game (Player 1) will be able to cancel that game and get their entry fee back.
    

Coding
------

This is our guideline to follow while developing a dApp.

1.  **Preparing local development environment.** This part may be confusing for beginners, so I try to explain everything step by step. We will have two folders in our project folder, `hardhat` and `frontend`. We use `hardhat` while doing stuffs related to smart contracts, use `frontend` while building our website.
    
2.  **Writing a smart contract.** We will write all logic related to the game in there. It's where **Solidity** plays a role. **How to** create a game, make a move, decide winner, give reward to winner...
    
3.  **Testing smart contract logic** before deploy to the blockchain. This part is generally ignored but, trust me, it saves a lot of time and finding bugs is much more easy with testing. It's where **Hardhat** plays a role.
    
4.  **Deploying smart contract to the blockchain.** We'll play our game on testnets such as Goerli _(Ethereum testnet)_, Fuji _(Avalanche testnet)_. There is no real fund on testnets, so you and others can try your dApp without using real money. Anyone in the world will be able to play our game. It's where **Hardhat** plays a role.
    
5.  **Building a website** which will let people easily connect their wallet and play our game by calling functions on our smart contract. It's where **Node.JS, React.JS and ethers.js** play a role.
    

It's easy, right? Let's build it!

### Preparing Local Development Environment

Open a terminal, create a project folder where you want in your local. Then create hardhat folder and get in.

    mkdir tic-tac-toe
    cd tic-tac-toe
    mkdir hardhat
    cd hardhat
    

in `hardhat` folder;

*   `npm init --yes` to initialize our project
    
*   `npm i --save-dev hardhat` to install hardhat
    
*   `npx hardhat` to run hardhat. Say yes to everything when you are asked.
    
*   `npm install --save-dev @nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers` to install the modules will be used in the project.
    

If you want to learn more about `npm`, [this article](https://nodesource.com/blog/an-absolute-beginners-guide-to-using-npm/) explains essential topics very clearly.

Our folder structure is ready! Let's write a code!

### Writing a Smart Contract

Delete `Lock.sol` under `contracts` folder, then, create `TicTacToe.sol` which will be our fancy game contract. 😎

There will be `Games` array which will include `Game` structs which holds parameters below;

*   Game ID,
    
*   Creating time,
    
*   Player1 address,
    
*   Player2 address,
    
*   Reward pool,
    
*   isStarted boolean,
    
*   Next move address,
    
*   Game board (3x3) matrix,
    
*   Winner
    

There will be `startGame`, `joinGame`, `makeMove` functions to play the game. Also, `isGameFinished` function will be used to check a game is finished after a move. And, of course, `claimRewards` function for claim earned rewards. 💸

If I explain all functions step by step, article would have to be very long. So, whole code in `TicTacToe.sol` is accessible [here](https://github.com/sergio2098/patika-akbank-web3-final-case/blob/main/hardhat/contracts/TicTacToe.sol). Please read comments and try to understand. In case of any trouble, [contact me](https://twitter.com/sergio_2098).

### Testing Smart Contract Logic

After (while) writing our contracts, we should test if our contract works properly. For example, we expect that `isStarted` boolean must be _true_ if _Player2_ joins an existing game. It's possible to deploy your contract to blockchain and then test it by yourself but it is much more easy to simulate blockchain thanks to test skills of Hardhat. To create a test file, delete existing file in `test` folder, create a new file `TicTacToe.test.js`.

The code snippet below is for testing of game starting and selecting game winner properly. `beforeEach` part runs every time automatically, so you don't need to copy-paste it if you test 10 different situation.

Copy the code into your test file, and run `npx hardhat test` in your terminal while you are in `hardhat` folder of the project.

    const { assert, expect } = require("chai");
    const { network, deployments, ethers } = require("hardhat");
    const { BigNumber, utils } = require("ethers");
    
    describe("TicTacToe Tests", function () {
      let contractFactory, contract;
    
      beforeEach(async () => {
        accounts = await ethers.getSigners();
        deployer = accounts[0];
        player1 = accounts[1];
        player2 = accounts[2];
        // Returns a new connection to the contract
        contractFactory = await ethers.getContractFactory("TicTacToe"); 
        contract = await contractFactory.deploy(5, 100);
        // Returns a new instance of the contract connected to player
        contractPlayer1 = contract.connect(player1); 
        contractPlayer2 = contract.connect(player2); 
      });
    
      it("create a game, join that game with different account", async () => {
        // Ideally, we'd separate these out so that only 1 assert per "it" block
        // And ideally, we'd make this check everything
        await contractPlayer1.startGame({ value: ethers.utils.parseEther("0.1") });
        await contractPlayer2.joinGame(1, {
          value: ethers.utils.parseEther("0.1"),
        });
        const game = await contract.games(1);
        assert.equal(game.isStarted, true);
      });
    
      it("play the game until P1 wins by row.", async () => {
        await contractPlayer1.startGame({ value: ethers.utils.parseEther("0.1") });
        await contractPlayer2.joinGame(1, {
          value: ethers.utils.parseEther("0.1"),
        });
        await contractPlayer1.makeMove(0, 0, 1); // first player play (0,0) cell at 1. game
        await contractPlayer2.makeMove(1, 1, 1); // second player play (1,1) cell at 1. game
        await contractPlayer1.makeMove(0, 1, 1);
        await contractPlayer2.makeMove(1, 2, 1);
        await contractPlayer1.makeMove(0, 2, 1); 
        const game = await contract.games(1);
        const gameBoard = await contract.getGameBoard(1);
        assert.equal(game.winner, 1);
      });
    });
    

You should see output like that if everything goes well. Crucial part here is `assert.equal()`. You check whether `game.winner` equals to first player (if you are confused, check `Winner` enum in our smart contract) with `assert` function. \[img=\]

![](https://storage.googleapis.com/papyrus_images/871236c0d8d4b7d9d1aecb508c7fac97246e6a3645e08bd4065a974c6d69cab9.png)

I've added much more testing while developing this dApp. [Check here](https://github.com/sergio2098/patika-akbank-web3-final-case/blob/main/hardhat/test/TicTacToe.test.js) to access all tests that I made.

![](https://storage.googleapis.com/papyrus_images/3bc3ba8dff60c82c2c628ba5d28242e4a34be5be28c98a69b05ae6948273fd62.png)

### Deploying smart contract We wrote the contract, tested it. We're ready to deploy it! `scripts` folder comes to the play now. We edit existing `deploy.js` file. If you don't have any file there, create a new one.

It basically get account information (signer information is needed to create a transaction on blockchain), get our contract information, then deploy it.

If you say _Why there is 5 and 100 here?_ `await contractFactory.deploy(5, 100)`, please check input parameters of `constructor` in our smart contract.

Let's run `npx hardhat run scripts/deploy.js` in terminal. Hardhat will deploy our contract to localhost. _Wait! You said that we deploy it to blockchain to make it accessible public!_ Yes, we will deploy it to blockchain as well. Just scroll down a bit.

      const main = async () => {
      const [deployer] = await hre.ethers.getSigners();
      const accountBalance = await deployer.getBalance();
    
      console.log("Deploying contracts with account: ", deployer.address);
      console.log("Account balance: ", accountBalance.toString());
    
      const contractFactory = await hre.ethers.getContractFactory("TicTacToe");
      const contract = await contractFactory.deploy(5, 100);
      await contract.deployed();
    
      console.log("Contract address: ", contract.address);
    };
    
    const runMain = async () => {
      try {
        await main();
        process.exit(0);
      } catch (error) {
        console.log(error);
        process.exit(1);
      }
    };
    
    runMain();
    

To deploy smart contracts to blockchains (whether it is mainnet or testnet), you need;

*   a wallet (like Metamask),
    
*   RPC URL to connect blockchains.
    

It is required to pass your private key into the deploy file. BUT, YOU SHOULD NOT TO SHOW YOUR PRIVATE KEY PUBLICLY. NEVER. So, you should use `dotenv` package to avoid uploading the file that includes your private key into public such as GitHub.

Also, to get RPC URL, you will need to create an account on RPC Providers like Alchemy, Quicknode, Infura etc. I think you are a bit confused. No problem, please check [Buildspace's page](https://buildspace.so/p/build-solidity-web3-app/lessons/deploy-smart-contract-to-testnet) that explains how to use `dotenv` and get RPC URL. It's very well documented.

Okay, you have an account, enough fund on testnet (we continue with Goerli), installed `dotenv` and got your RPC URL as well. Sample .env file below.

    # .env file
    PRIVATE_KEY="YOUR_PRIVATE_KEY"
    RPC_URL="YOUR_RPC_URL"
    

Now, we can edit `hardhat.config.js` file about informations which blockchain(s) we want to deploy our smart contracts on.

    require("@nomicfoundation/hardhat-toolbox");
    require("@nomicfoundation/hardhat-chai-matchers");
    require("@nomiclabs/hardhat-ethers");
    
    // Initialize `dotenv` with the `.config()` function
    require("dotenv").config({ path: ".env" });
    
    // Environment variables should now be available
    // under `process.env`
    const PRIVATE_KEY = process.env.PRIVATE_KEY;
    const RPC_URL = process.env.RPC_URL;
    
    // Show an error if environment variables are missing
    if (!PRIVATE_KEY) {
      console.error("Missing PRIVATE_KEY environment variable");
    }
    
    if (!RPC_URL) {
      console.error("Missing RPC_URL environment variable");
    }
    
    module.exports = {
      solidity: "0.8.17",
      networks: {
        goerli: {
          url: RPC_URL,
          accounts: [PRIVATE_KEY],
        },
      },
    };
    

After that, all needed to run deploy code that we run before with a small but powerful addition. `npx hardhat run scripts/deploy.js --network goerli`

It may take some time based on network activity around 2-3 minutes. Then, your contract address will be prompted out on terminal.

Go to [goerli version of etherscan](https://goerli.etherscan.io/) and search for your contract address.

CONGRATS!!! You have created a smart contract, tested it, deploy into a testnet. It is now accessible globally.

The only think we need to do is building a website to make it easy for our users.

I stop this article here since it became too long than I thought. 🤯 I'll continue later for front-end.

If you like this content, please give a comment, share it and tag me. If you don't like, please give a feedback.

I'm doing #100DaysOfWeb3 and #100DaysOfCode challenge on Twitter, and it is 57. Day! 😮 Come and join my journey!

[https://twitter.com/sergio\_2098](https://twitter.com/sergio_2098)

---

*Originally published on [Sergen](https://paragraph.com/@0xsergen/how-to-build-tic-tac-toe-game-with-solidity)*
