create account

Making a Decentralized Game on Hive - Part 3 by mahdiyari

View this thread on: hive.blogpeakd.comecency.com
· @mahdiyari · (edited)
$175.93
Making a Decentralized Game on Hive - Part 3
<center>![coding-1853305_1280.jpg](https://images.hive.blog/DQmeBMvQ9RAne3j2qH1RrtDaWEyQ2bZo81ik3wsNXYGbp3E/coding-1853305_1280.jpg)</center>

This part took a little longer because it was actually time-consuming (also I took a little break). The process is getting harder so it might take a few days longer than expected for the coming parts but I will do my best.

In the previous parts, we made a simple front-end with login functionality and initialized our API and back-end application with streaming. ([Part1](https://hive.blog/hive-139531/@mahdiyari/making-a-decentralized-game-on-hive-tic-tac-toe-part-1) and [Part2](https://hive.blog/hive-139531/@mahdiyari/making-a-decentralized-game-on-hive-part-2))
## MySQL Setup
You can use apps like [AMPPS](https://www.ampps.com/) which comes with MySQL and other tools like PHPMyAdmin (one of the best MySQL management apps) or install MySQL directly. I have AMMPS on windows and use MySQL docker on Linux. 

MySQL docker installation:
```
docker pull mysql/mysql-server:latest
```
I create a folder `/root/mysql-docker1` and put the MySQL config file there `my.cnf` and another folder `data` for holding the database files.
Running on port `127.0.0.1:3306`:
```
docker run --name=mysql1 \
--mount type=bind,src=/root/mysql-docker1/my.cnf,dst=/etc/my.cnf \
--mount type=bind,src=/root/mysql-docker1/data,dst=/var/lib/mysql \
-p 127.0.0.1:3306:3306 -d mysql/mysql-server:latest
```
There are different ways to tune your MySQL server based on your hardware which I'm not going to talk about. The following config is for medium-range hardware (32GB ram).
`my.cnf`:
```
[mysqld]
skip_name_resolve
user=mysql
default_authentication_plugin = mysql_native_password

symbolic-links=0
character_set_server=utf8mb4
collation_server=utf8mb4_general_ci
innodb_max_dirty_pages_pct = 90
innodb_max_dirty_pages_pct_lwm = 10
innodb_flush_neighbors = 0
innodb_undo_log_truncate=off
max_connect_errors = 1000000

# InnoDB Settings
innodb_file_per_table
innodb_log_files_in_group       = 2
innodb_open_files               = 4000
default_storage_engine          = InnoDB
innodb_buffer_pool_instances    = 8     # Use 1 instance per 1GB of InnoDB pool size
innodb_buffer_pool_size         = 16G    # Use up to 70-80% of RAM
innodb_flush_method             = O_DIRECT_NO_FSYNC
innodb_log_buffer_size          = 64M
innodb_log_file_size            = 10G
innodb_stats_on_metadata        = 0

# tune
innodb_doublewrite= 1
innodb_thread_concurrency       = 0
innodb_flush_log_at_trx_commit  = 0
innodb_lru_scan_depth           = 2048
innodb_page_cleaners            = 4
join_buffer_size                = 256K
sort_buffer_size                = 256K
innodb_use_native_aio           = 1
innodb_stats_persistent         = 1

innodb_adaptive_flushing        = 1
innodb_read_io_threads          = 16
innodb_write_io_threads         = 16
innodb_io_capacity              = 1500
innodb_io_capacity_max          = 2500
innodb_purge_threads            = 4
innodb_adaptive_hash_index      = 0
max_prepared_stmt_count         = 1000000
innodb_monitor_enable           = '%'
performance_schema              = ON
key_buffer_size = 512M
# Connection Settings
max_connections                 = 2000   # UPD
back_log                        = 3000
interactive_timeout             = 180
wait_timeout                    = 10
table_open_cache                = 200000 # UPD
table_open_cache_instances      = 64
open_files_limit                = 100000 # UPD
```
Note: restart MySQL server after updating the`my.cnf` file.

MySQL password on AMMPS is `mysql` and on docker setup, I think you get the password from `docker logs mysql1`. Anyway, there are many documentations about MySQL already on the internet.

Create a database `tictactoe`. It's easier with tools like PHPMyAdmin if you are using AMPPS. Or try HeidiSQL.
Here is the SQL command for creating the database:
```
CREATE DATABASE `tictactoe`;
```
***
## Development
Let's create a config file for holding MySQL login information for our app. I will put this file as `config.example.js` in repository and you have to rename it manually.
`config.js`:
```
const config = {
  dbName: 'tictactoe',
  dbUser: 'root',
  dbPassword: 'password',
  dbHost: '127.0.0.1',
  dbPort: 3306
}

module.exports = config
```
***
I have a personal code for MySQL connection pooling. It simply makes a custom async function just like the original connect function of [mysqljs](https://github.com/mysqljs/mysql) library but for pooling connections.
`helpers/mysql.js`:
```
const mysql = require('mysql')
const config = require('../config')
const pool = mysql.createPool({
  connectionLimit: 5,
  host: config.dbHost,
  port: config.dbPort,
  user: config.dbUser,
  password: config.dbPassword,
  database: config.dbName,
  charset: 'utf8mb4'
})

// Rewriting MySQL query method as a promise
const con = {}
con.query = async (query, val) => {
  if (val) {
    const qu = await new Promise((resolve, reject) => {
      pool.query(query, val, (error, results) => {
        if (error) reject(new Error(error))
        resolve(results)
      })
    })
    return qu
  } else {
    const qu = await new Promise((resolve, reject) => {
      pool.query(query, (error, results) => {
        if (error) reject(new Error(error))
        resolve(results)
      })
    })
    return qu
  }
}

module.exports = con
```
It creates a pool of 5 connections which is more than enough for our game.
And of course:
```
npm install mysql
```
***
#### Initializing database
We make a function to create necessary tables if they don't exist already.
`helpers/initDatabase.js`:
```
const mysql = require('./mysql')

/**
 * id, game_id, player1, player2, starting_player, status, winner
 */
const tableGames =
  'CREATE TABLE IF NOT EXISTS `tictactoe`.`games` ( `id` INT NOT NULL AUTO_INCREMENT , ' +
  '`game_id` TINYTEXT NOT NULL , ' +
  '`player1` TINYTEXT NOT NULL , `player2` TINYTEXT NULL DEFAULT NULL , ' +
  '`starting_player` TINYTEXT NOT NULL , `status` TINYTEXT NULL , ' +
  '`winner` TINYTEXT NULL DEFAULT NULL , PRIMARY KEY  (`id`)) ' +
  'ENGINE = InnoDB CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;'

/**
 * id, game_id, player, col, row
 */
const tableMoves =
  'CREATE TABLE IF NOT EXISTS `tictactoe`.`moves` ( `id` INT NOT NULL AUTO_INCREMENT , ' +
  '`game_id` TINYTEXT NOT NULL , `player` TINYTEXT NOT NULL , ' +
  '`col` INT(1) NOT NULL , `row` INT(1) NOT NULL , ' +
  'PRIMARY KEY  (`id`)) ENGINE = InnoDB CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;'

const tableRquests =
  'CREATE TABLE IF NOT EXISTS `tictactoe`.`requests` ( `id` INT NOT NULL AUTO_INCREMENT , ' +
  '`game_id` TINYTEXT NOT NULL , `player` TINYTEXT NOT NULL , `status` TINYTEXT NOT NULL , ' +
  'PRIMARY KEY  (`id`)) ENGINE = InnoDB CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;'

const initDatabase = async () => {
  await mysql.query(tableGames)
  await mysql.query(tableMoves)
  await mysql.query(tableRquests)
}

module.exports = initDatabase
```

#### Created tables
`games`
<center>![tablesqlgames.png](https://images.hive.blog/DQmVCMyNZax6xd6kCc2VZijGjMa9Qbt8Myeev7jF8KmcqXe/image.png)</center>

`moves`
<center>![tablesqlmoves.png](https://images.hive.blog/DQmVwyDZg1N3KEteDgfA5NdMHeUkjpngHZEmizBXxhh3XPs/image.png)</center>

`requests`
<center>![image.png](https://images.hive.blog/DQmQLxfwKEfWSQ614vWb6qfUNVMSyiFgnKEh5nSWcKYFQQv/image.png)</center>
***
#### Updating main application
Now we can complete the game methods in `index.js`:
`createGame`
```
const createGame = async (data, user) => {
  if (!data || !data.id || !data.starting_player) {
    return
  }
  // validating
  if (
    data.id.length !== 20 ||
    (data.starting_player !== 'first' && data.starting_player !== 'second')
  ) {
    return
  }
  // Check already existing games
  const duplicate = await mysql.query(
    'SELECT `id` FROM `games` WHERE `game_id`= ?',
    [data.id]
  )
  if (duplicate && Array.isArray(duplicate) && duplicate.length > 0) {
    return
  }
  // Add game to database
  await mysql.query(
    'INSERT INTO `games`(`game_id`, `player1`, `starting_player`, `status`) VALUES (?, ?, ?, ?)',
    [data.id, user, data.starting_player, 'waiting']
  )
}
```
***
`requestJoin`
```
const requestJoin = async (data, user) => {
  if (!data || !data.id || !data.id.length !== 20) {
    return
  }
  // Check game id in database
  const game = await mysql.query(
    'SELECT `player1` FROM `games` WHERE `game_id`= ? AND `status`= ?',
    [data.id, 'waiting']
  )
  if (!game || !Array.isArray(game) || game.length < 1) {
    return
  }
  // Players can not play with themselves
  if (game[0].player1 === user) {
    return
  }
  // Check already open requests
  const requests = await mysql.query(
    'SELECT `id` FROM `requests` WHERE `game_id`= ? AND (`player`= ? OR `status`= ?)',
    [data.id, user, 'accepted']
  )
  if (requests && Array.isArray(requests) && requests.length > 0) {
    return
  }
  // Request join game
  await mysql.query(
    'INSERT INTO `requests`(`game_id`, `player`, `status`) VALUES (?, ?, ?)',
    [data.id, user, 'waiting']
  )
}
```
***
`acceptRequest`
```
const acceptRequest = async (data, user) => {
  if (!data || !data.id || !data.player || !data.id.length !== 20) {
    return
  }
  // Validate game in database
  const game = await mysql.query(
    'SELECT `player1` FROM `games` WHERE `game_id`= ? AND `status`= ?',
    [data.id, 'waiting']
  )
  if (!game || !Array.isArray(game) || game.length < 1) {
    return
  }
  const requests = await mysql.query(
    'SELECT `id` FROM `requests` WHERE `game_id`= ? AND `player`= ? AND `status`= ?',
    [data.id, data.player, 'waiting']
  )
  if (!requests || !Array.isArray(requests) || requests.length < 1) {
    return
  }
  // Accept the join request and update game status
  await mysql.query(
    'UPDATE `games` SET `player2`=?,`status`=? WHERE `game_id`=?',
    [data.player, 'running', data.id]
  )
  await mysql.query(
    'UPDATE `requests` SET `status`=? WHERE `game_id`=? AND `player`=?',
    ['accepted', data.id, data.player]
  )
}
```
***
Some updates to `processData`:
```
const processData = (jsonData, postingAuths) => {
  try {
    if (!jsonData) {
      return
    }
    const data = JSON.parse(jsonData)
    if (!data || !data.action || !data.app) {
      return
    }
    if (
      !postingAuths ||
      !Array.isArray(postingAuths) ||
      postingAuths.length < 1
    ) {
      return
    }
    const user = postingAuths[0]
    if (data.action === 'create_game') {
      createGame(data, user)
    } else if (data.action === 'request_join') {
      requestJoin(data, user)
    } else if (data.action === 'accept_request') {
      acceptRequest(data, user)
    } else if (data.action === 'play') {
      play(data, user)
    }
  } catch (e) {
    // error might be on JSON.parse and wrong json format
    return null
  }
}
```
And streaming function:
```
try {
  stream.streamBlockOperations((ops) => {
    if (ops) {
      const op = ops[0]
      if (op && op[0] === 'custom_json' && op[1].id === 'tictactoe') {
        processData(op[1].json, op[1].required_posting_auths)
      }
    }
  })
} catch (e) {
  throw new Error(e)
}
```
***
I think it's enough for this part. Let's finish before I sleep on the keyboard.

We set up the MySQL server and made a script to create 3 tables. Our back-end is now processing data into the database and 3 main functions are working as expected. Creating a game, Requesting to join a game, and accepting the requests.

We have to create the front-end for these functions in the next part. I think the hard part is going to be the `play` function which holds the game rules.

Upvote if you like and leave a comment. Make sure to follow me and share the post.

Thanks for reading.
***
[GitLab](https://gitlab.com/mahdiyari/decentralized-game-on-hive)
[Part1](https://hive.blog/hive-139531/@mahdiyari/making-a-decentralized-game-on-hive-tic-tac-toe-part-1)
[Part2](https://hive.blog/hive-139531/@mahdiyari/making-a-decentralized-game-on-hive-part-2)

[Next part >>](https://hive.blog/hive-169321/@mahdiyari/making-a-decentralized-game-on-hive-part-4)
***
**Vote for my witness:**
- https://wallet.hive.blog/~witnesses
- https://peakd.com/witnesses
- https://ecency.com/witnesses
👍  , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , and 535 others
properties (23)
authormahdiyari
permlinkmaking-a-decentralized-game-on-hive-part-3
categoryhive-139531
json_metadata{"tags":["dev","development","game","decentralized","tutorial","technology"],"image":["https://images.hive.blog/DQmeBMvQ9RAne3j2qH1RrtDaWEyQ2bZo81ik3wsNXYGbp3E/coding-1853305_1280.jpg","https://images.hive.blog/DQmVCMyNZax6xd6kCc2VZijGjMa9Qbt8Myeev7jF8KmcqXe/image.png","https://images.hive.blog/DQmVwyDZg1N3KEteDgfA5NdMHeUkjpngHZEmizBXxhh3XPs/image.png","https://images.hive.blog/DQmQLxfwKEfWSQ614vWb6qfUNVMSyiFgnKEh5nSWcKYFQQv/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-28 20:31:39
last_update2021-04-04 08:25:03
depth0
children4
last_payout2021-04-04 20:31:39
cashout_time1969-12-31 23:59:59
total_payout_value95.262 HBD
curator_payout_value80.668 HBD
pending_payout_value0.000 HBD
promoted0.000 HBD
body_length12,225
author_reputation199,858,009,060,549
root_title"Making a Decentralized Game on Hive - Part 3"
beneficiaries[]
max_accepted_payout1,000,000.000 HBD
percent_hbd10,000
post_id102,665,392
net_rshares144,115,149,946,979
author_curate_reward""
vote details (599)
@poshbot ·
https://twitter.com/MahdiYari4/status/1376273141530558476
properties (22)
authorposhbot
permlinkre-making-a-decentralized-game-on-hive-part-3-20210328t204108z
categoryhive-139531
json_metadata"{"app": "beem/0.24.20"}"
created2021-03-28 20:41:06
last_update2021-03-28 20:41:06
depth1
children0
last_payout2021-04-04 20:41: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_length57
author_reputation5,554,335,374,496
root_title"Making a Decentralized Game on Hive - Part 3"
beneficiaries[]
max_accepted_payout1,000,000.000 HBD
percent_hbd10,000
post_id102,665,565
net_rshares0
@quixoticflux ·
$0.13
This looks very complicated to my non-programmer mind. So much code, so many things to keep track of. Good luck with getting the first playable version done, I hope to try it once it's ready :-)
👍  
properties (23)
authorquixoticflux
permlinkqqsm79
categoryhive-139531
json_metadata{"app":"hiveblog/0.1"}
created2021-03-30 17:35:33
last_update2021-03-30 17:35:33
depth1
children0
last_payout2021-04-06 17:35:33
cashout_time1969-12-31 23:59:59
total_payout_value0.066 HBD
curator_payout_value0.066 HBD
pending_payout_value0.000 HBD
promoted0.000 HBD
body_length194
author_reputation25,571,749,785,491
root_title"Making a Decentralized Game on Hive - Part 3"
beneficiaries[]
max_accepted_payout1,000,000.000 HBD
percent_hbd10,000
post_id102,704,976
net_rshares205,663,598,626
author_curate_reward""
vote details (1)
@socialite ·
Is the custom_json object we considering as a nft and we have to mint it on the blockchain
properties (22)
authorsocialite
permlinkryv8ib
categoryhive-139531
json_metadata{"app":"hiveblog/0.1"}
created2023-08-04 12:11:03
last_update2023-08-04 12:11:03
depth1
children0
last_payout2023-08-11 12:11:03
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_length90
author_reputation0
root_title"Making a Decentralized Game on Hive - Part 3"
beneficiaries[]
max_accepted_payout1,000,000.000 HBD
percent_hbd10,000
post_id125,932,502
net_rshares0
@zelensky ·
$0.02
!WINE
👍  
properties (23)
authorzelensky
permlinkqqpuib
categoryhive-139531
json_metadata{"app":"hiveblog/0.1"}
created2021-03-29 05:42:09
last_update2021-03-29 05:42:09
depth1
children0
last_payout2021-04-05 05:42:09
cashout_time1969-12-31 23:59:59
total_payout_value0.012 HBD
curator_payout_value0.012 HBD
pending_payout_value0.000 HBD
promoted0.000 HBD
body_length5
author_reputation486,864,775,001
root_title"Making a Decentralized Game on Hive - Part 3"
beneficiaries
0.
accounthiveonboard
weight100
1.
accounttheycallmedan
weight100
max_accepted_payout1,000,000.000 HBD
percent_hbd10,000
post_id102,672,555
net_rshares37,554,429,677
author_curate_reward""
vote details (1)