create account

Making a Decentralized Game on Hive - Part 2 by mahdiyari

View this thread on: hive.blogpeakd.comecency.com
· @mahdiyari · (edited)
$88.37
Making a Decentralized Game on Hive - Part 2
<center>![code-944499_1280.jpg](https://images.hive.blog/DQmZn8iD8SQbqnN8RNGJku4SCKQH6pKAPBh6QqtBrby1mAN/code-944499_1280.jpg)</center>
Previously in [part 1](https://hive.blog/hive-139531/@mahdiyari/making-a-decentralized-game-on-hive-tic-tac-toe-part-1), we made a simple front-end and a login option with posting key.

In this part, we work on the back-end of the game. We use [Nodejs](https://nodejs.org/en/) for running our back-end codes. I assume you know how to create or run a Nodejs app. It's not complicated and I will cover most of it here.

## API server
`api/server.js` is the starting point of the API server. Let's initialize it with expressjs and some libraries for API usage.
```
const express = require('express')
const bodyParser = require('body-parser')
const hpp = require('hpp')
const helmet = require('helmet')
const app = express()

// more info: www.npmjs.com/package/hpp
app.use(hpp())
app.use(helmet())

// support json encoded bodies and encoded bodies
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))

app.use(function (req, res, next) {
  res.header(
    'Access-Control-Allow-Origin',
    'http://localhost https://tic-tac-toe.mahdiyari.info/'
  )
  res.header('Access-Control-Allow-Credentials', true)
  res.header(
    'Access-Control-Allow-Headers',
    'Origin, X-Requested-With, Content-Type, Accept, access_key'
  )
  next()
})

const port = process.env.PORT || 2021
const host = process.env.HOST || '127.0.0.1'
app.listen(port, host, () => {
  console.log(`Application started on ${host}:${port}`)
})
```
***
**EDIT: added this part later!**
I made a mistake in the above code and apparently, multiple values are not allowed in the `Access-Control-Allow-Origin` header. So I modified the code as below:
```
app.use(function (req, res, next) {
  const allowedOrigins = [
    'http://localhost',
    'https://tic-tac-toe.mahdiyari.info/'
  ]
  const origin = req.headers.origin
  if (allowedOrigins.includes(origin)) {
    res.header('Access-Control-Allow-Origin', origin)
  }
  res.header('Access-Control-Allow-Credentials', true)
  res.header(
    'Access-Control-Allow-Headers',
    'Origin, X-Requested-With, Content-Type, Accept, access_key'
  )
  next()
})
```
**Edit end.**
***
Don't forget to install npm packages.
```
npm install express
npm install body-parser
npm install hpp
npm install helmet
```
`hpp` and `helmet` are for increased security and `body-parser` for parsing request bodies for json encoded bodies and encoded bodies.

I also added `http://localhost` and `https://tic-tac-toe.mahdiyari.info/` to the `Access-Control-Allow-Origin` header. You can add another URL to receive API calls from or just put `*`. It basically limits the usage of the API server to the listed URLs.

Our API server will listen to `127.0.0.1:2021` by default. It does nothing at the moment. Let's continue with the main application.
***
## Main application
We will run 2 Nodejs apps. One is the API server and the other is the main application where streaming blocks and processing data is happening. The reason for splitting applications is to run the API server in cluster mode. With cluster mode, we can run one API server for each CPU core. It will help with load balancing and keep API running as fast as possible while serving many requests. The cluster mode is useful only on API servers and especially Expressjs apps.

We will need a helper to stream the blocks.
`helpers/streamBlock.js`:
```
const hiveTx = require('hive-tx')

const INTERVAL_TIME = 1000

const streamBlockNumber = async (cb) => {
  try {
    let lastBlock = 0
    setInterval(async () => {
      const gdgp = await hiveTx.call(
        'condenser_api.get_dynamic_global_properties'
      )
      if (
        gdgp &&
        gdgp.result &&
        gdgp.result.head_block_number &&
        !isNaN(gdgp.result.head_block_number)
      ) {
        if (gdgp.result.head_block_number > lastBlock) {
          lastBlock = gdgp.result.head_block_number
          cb(lastBlock)
        }
      }
    }, INTERVAL_TIME)
  } catch (e) {
    throw new Error(e)
  }
}

const streamBlockOperations = async (cb) => {
  try {
    streamBlockNumber(async (blockNumber) => {
      const result = await hiveTx.call('condenser_api.get_block', [
        blockNumber
      ])
      if (result.result) {
        const operations = result.result.transactions.map((transaction) => {
          return transaction.operations
        })
        if (operations.length > 0) {
          for (const operation of operations) {
            cb(operation)
          }
        }
      }
    })
  } catch (e) {
    throw new Error(e)
  }
}

module.exports = {
  streamBlockNumber,
  streamBlockOperations
}
```
install [hive-tx](https://www.npmjs.com/package/hive-tx): `npm install hive-tx`

We created 2 functions here. The first one `streamBlockNumber` makes a call to get `dynamic_global_properties` every `INTERVAL_TIME` which I set to 1000ms (1 second) then checks for newly produced block number. If the block number is increased, then it calls the callback function with the new block number. It's a helpful function for getting newly generated block numbers.

We use the first function inside the second function `streamBlockOperations` to get newly generated blocks and get operations inside that block by using the `condenser_api.get_block` method.

`streamBlockOperations` will call the callback function with newly added operations to the blockchain (except virtual operations).

`index.js`:
```
const stream = require('./helpers/streamBlock')

try {
  stream.streamBlockOperations((ops) => {
    if (ops) {
      const op = ops[0]
      if (op && op[0] === 'custom_json' && op[1].id === 'tictactoe') {
        processData(op[1].json)
      }
    }
  })
} catch (e) {
  throw new Error(e)
}
```
This will stream newly added operations to the blockchain and send the JSON from `custom_json` operations with the id of `tictactoe` to the `processData` function.
***
We should define game mechanics and their corresponding custom_json.

**Create a game**
```
{
  app: 'tictactoe/0.0.1'
  action: 'create_game',
  id: 'Random generated id',
  starting_player: 'first or second'
}
```
Create a game and wait for the other player to join.
***
**Request join a game**
```
{
  app: 'tictactoe/0.0.1',
  action: 'request_join',
  id: 'Game id'
}
```
Request to join an open game which is created by someone else.
***
**Accept join request**
```
{
  app: 'tictactoe/0.0.1',
  action: 'accept_request',
  id: 'Game id',
  player: 'username'
}
```
Accept the pending join request from another player to your created game. 
***
**Play**
```
{
  app: 'tictactoe/0.0.1',
  action: 'play',
  id: 'Game id',
  col: '1 to 3',
  row: '1 to 3'
}
```
Play or place an X/O on the board. `col` is the column and `row` is for the row of the placed X/O on the board.

<center>![tic-tac-toe-col-row.jpg](https://images.hive.blog/DQmYtN2du2ngnu51fernMHqvmp5VKSyZ5JkF6wRgS9MkubP/tic-tac-toe-col-row.jpg)</center>
***
Code implamantaion of the above in `index.js`:
```
const processData = (jsonData) => {
  try {
    if (!jsonData) {
      return
    }
    const data = JSON.parse(jsonData)
    if (!data || !data.action || !data.app) {
      return
    }
    if (data.action === 'create_game') {
      createGame(data)
    } else if (data.action === 'request_join') {
      requestJoin(data)
    } else if (data.action === 'accept_request') {
      acceptRequest(data)
    } else if (data.action === 'play') {
      play(data)
    }
  } catch (e) {
    // error might be on JSON.parse and wrong json format
    return null
  }
}
```
***
Let's create a function for each game mechanic.
**createGame**:
```
const createGame = (data) => {
  if (!data || !data.id || !data.starting_player) {
    return
  }
  // validating
  if (
    data.id.length !== 20 ||
    (data.starting_player !== 'first' && data.starting_player !== 'second')
  ) {
    return
  }
  // Add game to database
  console.log('Create a game with id ' + data.id)
}
```
***
**requestJoin**:
```
const requestJoin = (data) => {
  if (!data || !data.id || !data.id.length !== 20) {
    return
  }
  // Check game id in database
  // Join game
  console.log('A player joined game id ' + data.id)
}
```
***
**acceptRequest**:
```
const acceptRequest = (data) => {
  if (!data || !data.id || !data.player || !data.id.length !== 20) {
    return
  }
  // Validate game in database
  // Accept the join request
  console.log('Accepted join request game id ' + data.id)
}
```
***
**play**:
```
const play = (data) => {
  if (
    !data ||
    !data.id ||
    !data.col ||
    !data.row ||
    !data.id.length !== 20 ||
    data.col < 1 ||
    data.col > 3 ||
    data.row < 1 ||
    data.row > 3
  ) {
    return
  }
  // Validate game in database
  // Validate the player round
  // Play game
  console.log('Played game id ' + data.id)
}
```
***
The above functions are not doing anything at the moment. We will complete those functions after setting up the database in the next part.

We can test our code by broadcasting custom_json operations. Let's see if the streaming method and processing data works.
We can run the app by `node index.js`
https://hiveblocks.com/tx/44799e6a27c64e935f9072ecb576602330cb80b8
<center>![image.png](https://images.hive.blog/DQmSfMrgDbSPjUPWaPeekrWfDFTS6zfdkk4NMySjNQmh3xh/image.png)</center>
And here is the console.log() confirmation in our app:
<center>![image.png](https://images.hive.blog/DQmb4kymGnacj2H9D4f2jfU8xYWJdjcd1EKDZeLN6q96q8h/image.png)</center>
***
Follow me so you don't miss the next part. The final code of this part is on GitLab.

[GitLab Repository](https://gitlab.com/mahdiyari/decentralized-game-on-hive)
[Website](https://tic-tac-toe.mahdiyari.info/)

[Part 1](https://hive.blog/hive-139531/@mahdiyari/making-a-decentralized-game-on-hive-tic-tac-toe-part-1)

[Next part >>](https://hive.blog/hive-139531/@mahdiyari/making-a-decentralized-game-on-hive-part-3)
πŸ‘  , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , and 501 others
properties (23)
authormahdiyari
permlinkmaking-a-decentralized-game-on-hive-part-2
categoryhive-139531
json_metadata{"tags":["dev","development","game","tutorial"],"image":["https://images.hive.blog/DQmZn8iD8SQbqnN8RNGJku4SCKQH6pKAPBh6QqtBrby1mAN/code-944499_1280.jpg","https://images.hive.blog/DQmYtN2du2ngnu51fernMHqvmp5VKSyZ5JkF6wRgS9MkubP/tic-tac-toe-col-row.jpg","https://images.hive.blog/DQmSfMrgDbSPjUPWaPeekrWfDFTS6zfdkk4NMySjNQmh3xh/image.png","https://images.hive.blog/DQmb4kymGnacj2H9D4f2jfU8xYWJdjcd1EKDZeLN6q96q8h/image.png"],"links":["https://hive.blog/hive-139531/@mahdiyari/making-a-decentralized-game-on-hive-tic-tac-toe-part-1"],"app":"hiveblog/0.1","format":"markdown"}
created2021-03-16 15:22:15
last_update2021-04-04 08:24:36
depth0
children7
last_payout2021-03-23 15:22:15
cashout_time1969-12-31 23:59:59
total_payout_value47.500 HBD
curator_payout_value40.868 HBD
pending_payout_value0.000 HBD
promoted0.000 HBD
body_length9,961
author_reputation199,864,818,197,856
root_title"Making a Decentralized Game on Hive - Part 2"
beneficiaries[]
max_accepted_payout1,000,000.000 HBD
percent_hbd10,000
post_id102,404,690
net_rshares123,253,435,776,200
author_curate_reward""
vote details (565)
@darkflame ·
This is very interesting and relevant to my interests
properties (22)
authordarkflame
permlinkre-mahdiyari-qr3hqm
categoryhive-139531
json_metadata{"tags":["hive-139531"],"app":"peakd/2021.03.9"}
created2021-04-05 14:32:48
last_update2021-04-05 14:32:48
depth1
children0
last_payout2021-04-12 14:32:48
cashout_time1969-12-31 23:59:59
total_payout_value0.000 HBD
curator_payout_value0.000 HBD
pending_payout_value0.000 HBD
promoted0.000 HBD
body_length53
author_reputation92,162,133,686,391
root_title"Making a Decentralized Game on Hive - Part 2"
beneficiaries[]
max_accepted_payout1,000,000.000 HBD
percent_hbd10,000
post_id102,838,837
net_rshares0
@enjar ·
$0.24
Awesome, it’s great to see a series like this on Hive. 

I have cross-posted this into the [Game Development](https://peakd.com/c/hive-176981/created) community. Hopefully, some others working on making games will take a look at this. 
πŸ‘  
properties (23)
authorenjar
permlinkre-mahdiyari-qq4vy5
categoryhive-139531
json_metadata{"tags":["hive-139531"],"app":"peakd/2021.03.7"}
created2021-03-17 22:03:42
last_update2021-03-17 22:03:42
depth1
children0
last_payout2021-03-24 22:03:42
cashout_time1969-12-31 23:59:59
total_payout_value0.118 HBD
curator_payout_value0.119 HBD
pending_payout_value0.000 HBD
promoted0.000 HBD
body_length236
author_reputation1,201,767,882,344,922
root_title"Making a Decentralized Game on Hive - Part 2"
beneficiaries[]
max_accepted_payout1,000,000.000 HBD
percent_hbd10,000
post_id102,433,079
net_rshares487,831,223,068
author_curate_reward""
vote details (1)
@maakue ·
Hello again, trying to follow this but kinda stuck right now. I have opened 2 terminals, one I ran node api/server.js and the other node index.js. Not sure how to do this "We can test our code by broadcasting custom_json operations" Does this mean I need to write on the blockchain?

Also in streamBlocks, see below code snippet. It's like processing the latest block number, so is it possible when you broadcast the custom_operation, there's a chance of missing it if the block number it was included in is not the latest block ex. the block number increases too fast?
        if (gdgp.result.head_block_number > lastBlock) {
          lastBlock = gdgp.result.head_block_number
          cb(lastBlock)
        }

Lastly, my hive-tx config is  node: 'https://api.hive.blog', should i change to a testnet?
Sorry for the questions, really new to blockchain programming and JS.

TIA
properties (22)
authormaakue
permlinkr95ezw
categoryhive-139531
json_metadata{"links":["https://api.hive.blog"],"app":"hiveblog/0.1"}
created2022-03-22 13:33:33
last_update2022-03-22 13:33:33
depth1
children1
last_payout2022-03-29 13:33:33
cashout_time1969-12-31 23:59:59
total_payout_value0.000 HBD
curator_payout_value0.000 HBD
pending_payout_value0.000 HBD
promoted0.000 HBD
body_length879
author_reputation1,477,515,545,000
root_title"Making a Decentralized Game on Hive - Part 2"
beneficiaries[]
max_accepted_payout1,000,000.000 HBD
percent_hbd10,000
post_id111,585,108
net_rshares0
@mahdiyari ·
Read up the docs on devportal and try out different API calls. I might write another startup guide targeted for newer devs. I think this was more for the devs familiar with the Hive blockchain overall and how it works.

The code reads off of the chain so yes.

The block number won't change fast. There is a minimum of 3s between each block produced and we pull the last block less than that. But, another extra check to make sure the increase is not more than 1 would be better.

A testnet would be ideal but using mainnet for small things wouldn't matter. Mainly because it doesn't involve financial transactions.
πŸ‘  
properties (23)
authormahdiyari
permlinkr95ho7
categoryhive-139531
json_metadata{"app":"hiveblog/0.1"}
created2022-03-22 14:32:06
last_update2022-03-22 14:32:06
depth2
children0
last_payout2022-03-29 14:32:06
cashout_time1969-12-31 23:59:59
total_payout_value0.000 HBD
curator_payout_value0.000 HBD
pending_payout_value0.000 HBD
promoted0.000 HBD
body_length615
author_reputation199,864,818,197,856
root_title"Making a Decentralized Game on Hive - Part 2"
beneficiaries[]
max_accepted_payout1,000,000.000 HBD
percent_hbd10,000
post_id111,586,350
net_rshares3,856,728,327
author_curate_reward""
vote details (1)
@mineopoly ·
$0.22
How about a nice game of chess?

https://youtu.be/NHWjlCaIrQo
πŸ‘  
properties (23)
authormineopoly
permlinkre-mahdiyari-qq422s
categoryhive-139531
json_metadata{"tags":["hive-139531"],"app":"peakd/2021.03.7"}
created2021-03-17 11:18:33
last_update2021-03-17 11:18:33
depth1
children0
last_payout2021-03-24 11:18:33
cashout_time1969-12-31 23:59:59
total_payout_value0.111 HBD
curator_payout_value0.111 HBD
pending_payout_value0.000 HBD
promoted0.000 HBD
body_length61
author_reputation142,375,151,833,139
root_title"Making a Decentralized Game on Hive - Part 2"
beneficiaries[]
max_accepted_payout1,000,000.000 HBD
percent_hbd10,000
post_id102,423,209
net_rshares488,074,663,709
author_curate_reward""
vote details (1)
@poshbot ·
https://twitter.com/MahdiYari4/status/1371845238688452608
properties (22)
authorposhbot
permlinkre-making-a-decentralized-game-on-hive-part-2-20210316t152613z
categoryhive-139531
json_metadata"{"app": "beem/0.24.20"}"
created2021-03-16 15:26:12
last_update2021-03-16 15:26:12
depth1
children0
last_payout2021-03-23 15:26:12
cashout_time1969-12-31 23:59:59
total_payout_value0.000 HBD
curator_payout_value0.000 HBD
pending_payout_value0.000 HBD
promoted0.000 HBD
body_length57
author_reputation5,554,335,374,496
root_title"Making a Decentralized Game on Hive - Part 2"
beneficiaries[]
max_accepted_payout1,000,000.000 HBD
percent_hbd10,000
post_id102,404,741
net_rshares0
@poshbot ·
https://twitter.com/McSagel/status/1372145640851927042
properties (22)
authorposhbot
permlinkre-making-a-decentralized-game-on-hive-part-2-20210317t112028z
categoryhive-139531
json_metadata"{"app": "beem/0.24.20"}"
created2021-03-17 11:20:27
last_update2021-03-17 11:20:27
depth1
children0
last_payout2021-03-24 11:20:27
cashout_time1969-12-31 23:59:59
total_payout_value0.000 HBD
curator_payout_value0.000 HBD
pending_payout_value0.000 HBD
promoted0.000 HBD
body_length54
author_reputation5,554,335,374,496
root_title"Making a Decentralized Game on Hive - Part 2"
beneficiaries[]
max_accepted_payout1,000,000.000 HBD
percent_hbd10,000
post_id102,423,233
net_rshares0