Fully onchain Tic-Tac-Toe

Let's build Trustless Tic Tac Toe, a decentralized, unstoppable, and open game.

  • create a new match.

  • seek and match with another player based on Elo ranking.

  • play Tic Tac Toe.

  • update Elo ranking to use for better matching in the future matches.

You can treat this Tic Tac Toe tutorial as a template for implementing a PvP turn based game so you can build your own future games.

Write the Tic Tac Toe smart contracts

It turns out that writing the Tic Tac Toe smart contract is pretty simple.

First, we need to extend TurnBasedGame contract that implemented the following standard functions that will need for building PvP turn based games:

  • turnDuration: the duration of each turn

  • turnTimePivot: the starting time of the current turn

  • player1TimePool, player2TimePool: the total game time (countdown) for the two players

  • findMatch: the game only allows a maximum of one waiting match. If there is one available then use joinMatch, if not then use createMatch

  • createMatch: create a new match, may need to override to add game setup logic, player state

  • joinMatch: join an existing match

  • cancelMatch: cancel the match, can only be called after createMatch but before any player joins

  • affirmTimeOut: Normally, when a timeout occurs, the losing client will call resign to trigger the endGame function. Suppose the losing client is disconnected, the winning client can proactively call affirmTimeOut to end the game.

  • resign, offerDraw, cancelDraw, rejectDraw, acceptDraw: functions as their names suggest, no specific notes

  • getTurn: returns (bool, int256, int256) which includes:

    • the current player's turn (true for player1, false for player2)

    • the remaining time for the current turn (can be negative when the match times out)

    • the total remaining time for the current player's turn

  • The initial ELO of a new player is set to 1500 by default in the DEFAULT_ELO variable.

Of course, you can also override these functions to your custom logic if needed. The complete TurnBasedGame smart contract code can be found at:

Next, we need to implement an additional move function for players to submit their moves. This function should include game logic, check for timeouts, and update the turn in MatchData. If the game reaches a win/loss result, the endGame function should be called. When you build your own game, you will need to implement this function for your game logic.

function makeMove(uint256 _matchId, uint _xCoordinate, uint _yCoordinate, bool _checkWinner) external notTimeOutMatch(_matchId) notEndedMatch(_matchId) {
        Game storage game = games[_matchId];
        bool p1Turn = matches[_matchId].turn;

        if (game.board[_xCoordinate][_yCoordinate] != Players.None) {
            revert MoveProhibited();
        }

        unchecked {
            uint40 timeUsed = uint40(block.timestamp - matches[_matchId].turnTimePivot);

            if (p1Turn) {
                if (msg.sender != matches[_matchId].player1) revert MoveProhibited();
                matches[_matchId].player1TimePool -= timeUsed;
                game.board[_xCoordinate][_yCoordinate] = Players.PlayerOne;
            } else {
                if (msg.sender != matches[_matchId].player2) revert MoveProhibited();
                matches[_matchId].player2TimePool -= timeUsed;
                game.board[_xCoordinate][_yCoordinate] = Players.PlayerTwo;
            }
            matches[_matchId].totalMoved++;
            matches[_matchId].turn = !p1Turn;
            matches[_matchId].turnTimePivot = uint40(block.timestamp);

            emit Move(_matchId, msg.sender, uint8(_xCoordinate), uint8(_yCoordinate), timeUsed);

            if (_checkWinner) {
                Winners winner = calculateWinner(_matchId, _xCoordinate, _yCoordinate);
                if (winner != Winners.None) {
                    MatchResult result = MatchResult(uint8(winner));
                    endGame(_matchId, result);
                }
            }
        }
    }

The complete TicTacToe smart contract code can be found at:

Deploy the contracts

We've published the game’s repository to Github for you to get started.

git clone https://github.com/TrustlessComputer/tic-tac-toe-game-template

To compile the contracts, use the built-in hardhat compile task.

cd contracts
npm install
npx hardhat compile

Finally, to deploy the contracts, review config file hardhat.config.ts. The network configs should look like this.

networks: {
    nos: {
      url: "https://node.l2.trustless.computer/",
      accounts: {
        mnemonic: "<your mnemonic with funds>"
      },
      timeout: 100_000,
    },
    blockscoutVerify: {
      blockscoutURL: "https://explorer.l2.trustless.computer/",
      ...
    }
  }

Run the deploy scripts using hardhat-deploy.

cd contracts
npx hardhat run --network nos scripts/1-TicTacToe.ts
(i) Make sure the accounts in hardhat.config.ts have some TC.

Build an UI to interact with the Tic Tac Toe contracts

There are two things the game UI needs to support so players can interact with Tic Tac Toe contracts above via the UI:

  • Creating an internal NOS wallet. This is an EVM-compatible wallet, players need to buy from topup page and send TC token to the wallet so that they can create transactions to the Tic Tac Toe contract and pay NOS network gas fee.

  • Creating transactions for creating a new game, making a move to the Tic Tac Toe contract via web3 libraries and then displaying the game state on the UI.

The complete Tic Tac Toe game UI code can be found at:

Build the source code:

yarn install
yarn dev

and view it on browser at http://localhost:6010. The game UI should look like this:

We have deployed the full Tic Tac Toe source code on NOS mainnet, you can experience the final result at: https://newbitcoincity.com/tic-tac-toe

Happy building!

Last updated