![coding-1853305_1280.jpg](https://images.hive.blog/DQmeBMvQ9RAne3j2qH1RrtDaWEyQ2bZo81ik3wsNXYGbp3E/coding-1853305_1280.jpg)
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`
![tablesqlgames.png](https://images.hive.blog/DQmVCMyNZax6xd6kCc2VZijGjMa9Qbt8Myeev7jF8KmcqXe/image.png)
`moves`
![tablesqlmoves.png](https://images.hive.blog/DQmVwyDZg1N3KEteDgfA5NdMHeUkjpngHZEmizBXxhh3XPs/image.png)
`requests`
![image.png](https://images.hive.blog/DQmQLxfwKEfWSQ614vWb6qfUNVMSyiFgnKEh5nSWcKYFQQv/image.png)
*** #### 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

See: Making a Decentralized Game on Hive - Part 3 by @mahdiyari