Guide to blockchain game development
Tags: blockchain, gamedev, webdev, tutorial
Published: 2021-11-18 19:07:24

**Written by [Ikeh Akinyemi](https://blog.logrocket.com/author/ikehakinyemi/)** ✏️

Blockchain development has grown and evolved rapidly over the past few years and is now being adopted across various spheres of software development. From decentralized applications (DApps), to decentralized finance (DeFi) software, to NFTs, to DAOs, blockchain technology has infiltrated a wide range of industries and serves many use cases.

In this tutorial, we’ll explore the emerging trend of blockchain game development. Blockchain-based games are also referred to as chain games. Once you understand the basic structure involved in writing a smart contract and deploying it to a blockchain, you can use the tools available within the crypto space to build games.

We’ll build a lottery game to demonstrate how game development on blockchain works. We’ll also review the basic structure for implementing transactions within a blockchain game. Then, we’ll deploy it to a testnet network.

## What is blockchain?

The underlying data structure of a blockchain is a chain of linked lists, or unique “blocks.” Each block that is added to the chain is automatically linked to the previous block added, and the previous block as well points to its predecessor.

This chain of linked list is itself a list of transactions. The process through which these blocks are agreed upon before they are added to the list-of-lists data structure is laid the key innovation that blockchains have given us: a protocol. This protocol helps the network decide how blocks are added to the chain.

This decision-making process gave birth to the decentralized nature of blockchain. Proof of work (PoW), proof of take (PoS), and proof of authority (PoA) are decentralized mechanisms through which these decisions are made and agreed on before a block gets added to the chain.

The cryptocurrencies that have emerged through these blockchains are a means to incentivize people to run software that secures the networks around these blockchains.

Blockchain platforms like [NEAR](https://near.org/) provide a cryptographically secure platform for storing, updating, and removing data from a blockchain using [smart contracts](https://blog.logrocket.com/smart-contract-programming-languages/).

## Web3 game development

Web3, in the context of blockchains, refers to decentralized apps that run on the blockchain. These are apps that allow anyone to participate without monetizing their personal data. With good knowledge of a programming language that is supported by any of these blockchains, we can start writing smart contracts to build game applications as DApps on the blockchain.

As the blockchain ecosystem evolves, new paradigms emerge. Drawing inspiration from the De-Fi ecosystem, the blockchain game ecosystem has also evolved to something known as GameFi. GameFi, also referred to as [play to earn](https://coinmarketcap.com/view/play-to-earn/), introduces a new way to game by turning its regular users into a governing force behind major decisions within the gaming industry.

GameFi facilitates a player-owned economy when it comes to trading valuables as well as generating additional income with tokens and [non-fungible tokens](https://blog.logrocket.com/how-to-create-nfts-with-javascript/). This means building communities around a particular game, and the users of these games can earn cryptocurrencies or assets that are valuable within the metaverse of the game (and outside it too).

## Writing smart contracts on the NEAR blockchain

For this tutorial, we’ll demonstrate how to build games on the NEAR blockchain by building a sample game project.

Within this game, we’ll explore how to set up the codebase structure and the programming languages needed to write smart contracts that run on the Near blockchain. When we’re all done, we’ll test our application on the local environment, then deploy our smart contract to a testnet.

We’re going to clone a starter kit codebase. This repository provides a basic boilerplate on top of which to write more smart contract code as we build the various features of our game.

```shell
git clone https://github.com/IkehAkinyemi/lottery-smart-contract.git
```


Once the above command is successfully executed, change the directory to the `lottery-smart-contract` folder. You can open it in any text editor; for this tutorial, we’ll use Visual Studio Code.

From the terminal, run the `code .` command within the folder directory.

![Folder Structure Starter Kit](https://blog.logrocket.com/wp-content/uploads/2021/11/folder-structure-starter-kit.png)

The above picture shows the basic folder structure for a NEAR project using [AssemblyScript](https://www.assemblyscript.org/) for its smart contract.

The `script` folder contains the shell source file to compile and deploy the smart contract to the blockchain. The `src` contains the `lottery` folder, inside of which we’ll write the necessary code for our smart contract.

The remaining files are configuration files that AssemblyScript needs to understand some of the types defined on Near. The `near-sdk-as` library is a collection of packages used to develop NEAR smart contracts in AssemblyScript.

## How to build a lottery game on the NEAR blockchain

With this game, we’ll explore some of the basic concepts of writing smart contracts on the Near blockchain using AssemblyScript.

Run the `yarn install` or `npm install` command to install the `near-sdk-as` library and any necessary dependencies.

Next, create a folder called `assembly`. Inside this folder, create two files: `index.ts` and `model.ts`. The `model.ts` file contains the different object types we’ll be using throughout our code in the `index.ts` file. The `model.ts` file contains the following:

```typescript
import { RNG } from "near-sdk-as";

@nearBindgen
export class Lottery {
  private luckyNum: u32 = 3;

  constructor() {
    const randGen = new RNG<u32>(1, u32.MAX_VALUE);
    this.id = "LO-" + randGen.next().toString();
  }
}
```

In the above code, we define a `Lottery` type. This represents the structure of the lottery game type. We’ll define inside it the different interfaces we want to make available — both the public and private interfaces — just like the private `luckyNum` variable that’s an unsigned integer.

Using the `RNG` (random number generator) object, we initialized the `this.id` variable of the game to a random number. And in the `randGen` variable, we’re just initializing the `RNG` object, while with the `randGen.next` function, we’re generating a random number using the seed values, `<u32>(1, u32.MAX_VALUE)`, that were passed into it.

### Defining function interfaces

Now let’s define the `play` feature of our game. This will contain the code snippet responsible for generating a random number within a set range of integers.

```typescript
import { RNG, logging } from "near-sdk-as";

@nearBindgen
export class Lottery {
  ...

  play(): bool {
    const randGen = new RNG<u32>(1, u32.MAX_VALUE);
    const pickNum = randGen.next();

    logging.log("You picked: " + pickedNum.toString());

    return pickedNum === this.luckyNum
  }
}
```

With the `play` function, any player can call it to generate a random number using the `RNG` object. Then, we imported the `logging` object, which gives us access to output values on the native console — that’s our local machine terminal.

The `play` function returns a `bool` value, and this `true` or `false` value is the result of comparing the `pickedNum` against `this.luckyNum` to determine whether the guessed number is equal to the `luckyNum` defined in the lottery game.

Next, we’ll define the `reset` function. As the name implies, this will enable us to reset the `this.luckyNum` to a new random number:

```typescript
...
@nearBindgen
export class Lottery {
  ...

  reset(): string {
    const randGen = new RNG<u32>(1, u32.MAX_VALUE);
    const randNum = randGen.next();
    assert(randNum !== this.luckyNum, "Rerun this function to generate a new random luckyNum");

    this.luckyNum = randNum;
    return "The luckyNum has been reset to another number";
  }
}
```

In the above code, we generated another new random number. Using the `assert` function, we compared it against the current `this.luckyNum` value.

If the comparison evaluates `true`, then the rest of the function’s code continues to execute. If not, the function halts at that point and returns the assertion message, `Rerun this function to generate a new random luckyNum`.

When the `assert` is true, we assign the variable `this.luckyNum` to the newly generated number, `randNum`.

### Defining the `Player` object

For each player of the lottery game, we’ll define a basic type structure. This structure presents the player within our game.

Update the `model.ts` file with the following code:

```typescript
import { RNG, logging, PersistentVector, Context } from "near-sdk-as";

export type AccountID = string;

@nearBindgen
export class Lottery {
  ...
}

@nearBindgen
export class Player {
  id: AccountId;
  guesses: PersistentVector<bool>;

  constructor(isRight: bool) {
    this.id = Context.sender;
    this.guesses = new PersistorVector<bool>("g"); // choose a unique prefix per account

    this.guesses.push(isRight);
  }
}
```

The `Player` object type contains two interfaces: the `this.id` variable, which is an `AccountID` type, and `this.guesses`, which is an array of boolean values.

The `PersistentVector` data structure is an array datatype. During initialization, we use the `Context` object to get the current caller of this smart contract through the `Context.sender` function. Then, we assign it to `this.id`.

For `this.guesses`, we initialize a new [`PersistentVector`](https://near.github.io/near-sdk-as/modules/_sdk_core_assembly_collections_persistentvector_.html) object and assign it to `this.guesses`. Then, using the `push` function interface available on `PersistorVector`, we append a new boolean value, `isRight`, into the `this.guesses` variable.

Let’s define other types and variables that we’ll use while defining the core functions in the next section:

```typescript
...
exsport const TxFee = u128.from("500000000000000000000000");
export const WinningPrize = u128.from("100000000000000000000000");
export const Gas: u64 = 20_000_000_000_000;

...

export const players = new PersistentMap<AccountID, Player>("p")
...
```

### Defining core game functions

Create an `index.ts` file inside the `assembly` folder. This is where we’ll define the core functions of our lottery game.

Inside the `index.ts` file, define a `pickANum` function, as shown below:

```typescript
import { TxFee, Lottery, Player, players } from "./model";
import { Context, u128 } from "near-sdk-as";

export function pickANum(): void {
  verifyDeposit(Context.attachedDeposit);
  const game = new Lottery();
  const guess = game.play();
  let player;

  if (players.contains(Context.sender)) {
    player = players.get(Context.sender) as Player;
    player.guesses.push(guess);
    players.set(Context.sender, player);
  } else {
    player = new Player(guess);
  }
}

function verifyDeposit(deposit: u128): void {
  assert(deposit >= TxFee, "You need 0.5 NEAR tokens to pick a number");
}
```

In the above function, we’re verifying a deposit of 0.5 NEAR tokens before any player of the lottery game can invoke any call to play a game on the smart contract. This way, our players are paying a certain amount of money before playing the game. Also, once a player plays, we update the profile of that player in the players data structure.

Next, let’s define the function that will handle paying a winning player by randomly generating the right number that’s equal to the `luckyNum`:

```typescript
import { TxFee, Lottery, Player, players, Gas, WinningPrize } from "./model";
import { Context, u128, ContractPromiseBatch, logging } from "near-sdk-as";

function on_payout_complete(): string {
  logging.log("This winner has successfully been paid");
}

export function payout(): void {
  const player = players.get(Context.sender) as Player;

  for (let x = 0; x < players.guesses.length; x++) { 
    if (player.guesses[x] === true) {
      const to_winner = ContractPromiseBatch.create(Context.sender);
      const self = Context.contractName;

      to_winner.transfer(WinningPrize);
      to_winner
        .then(self)
        .function_call("on_payout_complete", "{}", u128.Zero, Gas)
    }
  }
}
```

The above functions help us make transfer transactions to the winners of the lottery game. With the `ContractPromiseBatch` object, we create and set up a transfer transaction to the address we passed in as the argument to the `create` method. Then, with the `transfer` function, we make a transaction worth of the token, `WinningPrize`, that was passed into it.

Using the `function_call` function, we then schedule a function call for when the transaction has been successfully sent. For this game, the function we intend to call on a successful transaction is the `on_payout_complete` function.

For the purpose of this tutorial, we won’t focus on setting up a [NEAR Testnet](https://docs.near.org/docs/concepts/networks#testnet) or [Testnet wallet](https://wallet.testnet.near.org/), but I would encourage you to check out the links to learn more about the various networks that exists in the NEAR ecosystem.

For this demonstration, we’ll build our lottery game to generate the binary format `.wasm` file, then use the `near dev-deploy` command to deploy the smart contract.

### Building and deploying smart contracts

We’ll first build the smart contract using the `asb` command:

```shell
yarn asb
```

This is an alias command for the `yarn asb` `--verbose` `--nologo` command, as defined in the `package.json` file located in the root directory.

After we’ve successfully generated a `build` folder that contains a `lottery.wasm` file inside the `build/release/` folder, we can run the following command to deploy it:

```shell
near dev-deploy ./build/release/lottery.wasm 
```

This will deploy the smart contract and provide us with the contract name or ID, which we can use to interact with it on the frontend or through a shell file.

```typescript
$ near dev-deploy ./lottery.wasm                 
Starting deployment. Account id: dev-1635968803538-35727285470528, node: https://rpc.testnet.near.org, helper: https://helper.testnet.near.org, file: ./lottery.wasm
Transaction Id 4TWTTnLEx7hpPsVMfK31DDX3gVmG4dsqoMy7sA7ypHdo
To see the transaction in the transaction explorer, please open this url in your browser
https://explorer.testnet.near.org/transactions/4TWTTnLEx7hpPsVMfK31DDX3gVmG4dsqoMy7sA7ypHdo
Done deploying to dev-1635968803538-35727285470528
```

## Testing our blockchain game

I’ve written two unit tests to confirm that our application is actually functional. These two simple tests will create a lottery game and as well reset the `luckyNum` variable to a new random number.

The `/src/lottery/__test__` folder contains the test file. Run the test suite using the following command:

```shell
$ yarn test:unit
[Describe]: Checks for creating account

 [Success]: ✔ creates a new game
 [Success]: ✔ create and reset the luckyNum of a new game

    [File]: src/lottery/__tests__/index.unit.spec.ts
  [Groups]: 2 pass, 2 total
  [Result]: ✔ PASS
[Snapshot]: 0 total, 0 added, 0 removed, 0 different
 [Summary]: 2 pass,  0 fail, 2 total
    [Time]: 19.905ms
  [Result]: ✔ PASS
   [Files]: 1 total
  [Groups]: 2 count, 2 pass
   [Tests]: 2 pass, 0 fail, 2 total
    [Time]: 13907.01ms
Done in 14.90s.
```

## Conclusion

In this tutorial, we demonstrated how to create game applications on blockchain platforms. Blockchain-based games can be played either as multiplayer games or solo.

You can also extend the concept of blockchain games to include a metaverse — a digital world — around your game. The metaverse is a world where players can team up, create a governance, and even create currencies as a means for value exchange. You can mint NFTs or form DAO within a digital game world.

Check out the NEAR docs to see how to build a frontend to consume the game’s smart contract created in this tutorial. The full codebase of the smart contract is available on [GitHub](https://github.com/IkehAkinyemi/lottery-smart-contract).

---

## [LogRocket](https://logrocket.com/signup/): Full visibility into your web apps

[![LogRocket Dashboard Free Trial Banner](https://blog.logrocket.com/wp-content/uploads/2017/03/1d0cd-1s_rmyo6nbrasp-xtvbaxfg.png)](https://logrocket.com/signup/)

[LogRocket](https://logrocket.com/signup/) is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

[Try it for free](https://logrocket.com/signup/).