![game-pixabay.jpg](https://images.hive.blog/DQmSeL2wLvGPWHqcLVX4ZtfhqidWhj4GVNiVDC2LsHX6jNX/game-4648923_1280.jpg)
Games are fun and most people like to play different kinds of games. I'm not sure about the game we are building. Whether it be fun or not, the purpose of this project is the tutorial. To have a step-by-step guide that developers can use as a reference in building apps on Hive. Check out previous posts: *** #### Building the transactions We will build the back-end for the front-end that we built in the previous post. All goes into `js/app.js`. ``` const random = (length = 20) => { const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' let str = '' for (let i = 0; i < length; i++) { str += chars.charAt(Math.floor(Math.random() * chars.length)) } return str } ``` A simple function to generate a random string. We will use the random string for the game_id. *** ``` const createGame = async () => { const button = document.getElementById('create-game-btn') button.setAttribute('disabled', 'true') const errorOutput = document.getElementById('create-game-error') const successOutput = document.getElementById('create-game-success') errorOutput.innerHTML = '' successOutput.innerHTML = '' try { const game = { app: 'tictactoe/0.0.1', action: 'create_game', id: random(20), starting_player: document.getElementById('starting-player').value } const operations = [ [ 'custom_json', { required_auths: [], required_posting_auths: [userData.username], id: 'tictactoe', json: JSON.stringify(game) } ] ] const tx = new hiveTx.Transaction() await tx.create(operations) const privateKey = hiveTx.PrivateKey.from(userData.key) tx.sign(privateKey) const result = await tx.broadcast() if (result && result.result && result.result.block_num) { successOutput.innerHTML = 'Success! Click to see' } else { errorOutput.innerHTML = 'Error! Check console for details. Press Ctrl+Shift+J' console.error(result) } } catch (e) { errorOutput.innerHTML = 'Error! Check console for details. Press Ctrl+Shift+J' console.error(e) } button.removeAttribute('disabled') } ``` We create the transaction by using the [hive-tx](https://www.npmjs.com/package/hive-tx) library then sign and broadcast it. We put the game link in the success message and show it to the user. Now users can create the game and see the list of games. We create the `game.html` page for users to play the game. *** #### Game page We can add the game board, moves history, and the game stats like the winner, player1, and player2. I think we can make this page accessible by the game_id, like `/game.html?id=game_id_here`. Let's create the easier parts first. `game.html`: We use the head from `index.html` and the same navbar code. ``` <!DOCTYPE html> Tic-Tac-Toe on Hive blockchain ``` Since we use `app.js` here too, we have to modify 2 lines in `app.js`: ``` // Run the script only in homepage if (!window.location.pathname.match(/game.html$/)) { loadTheGames() setInterval(() => loadTheGames(), 5000) } ``` In the above code, we can define which scripts to run on the homepage and which run on the game page. We need an API for retrieving the game details by game_id. Let's set that up in the back-end. `api/game.js`: ``` const mysql = require('../helpers/mysql') const express = require('express') const router = express.Router() router.get('/game/:id', async (req, res) => { try { const id = req.params.id if (!id || id.length !== 20 || !id.match(/^[a-zA-Z0-9]+$/)) { return res.json({ id: 0, error: 'Wrong id.' }) } const game = await mysql.query( 'SELECT `game_id`, `player1`, `player2`, `starting_player`, `status`, `winner` FROM `games`' + 'WHERE `game_id`=?', [id] ) if (!game || !Array.isArray(game) || game.length < 1) { return res.json({ id: 1, game: [] }) } return res.json({ id: 1, game }) } catch (e) { return res.json({ id: 0, error: 'Unexpected error.' }) } }) module.exports = router ``` The above code is similar to the other APIs we set up. Nothing new here. Now we can show the game details on the game.html page. ``` const getGameDetails = async (id) => { const data = await APICall('/game/' + id) if (data && data.id === 0) { document.getElementById('details-error').innerHTML = data.error } else if (data && data.id === 1) { const game = data.game[0] document.getElementById('game-details').innerHTML = ` ${game.player1} ${game.player2} ${game.starting_player} ${game.status} ${game.winner} ` if (game.player1 === userData.username) { document.getElementById('req-message-1').style.display = 'block' document.getElementById('req-message-2').style.display = 'none' } } } ``` And running the above function: ``` const queryString = window.location.search const urlParams = new URLSearchParams(queryString) // Run the script only in homepage if (!window.location.pathname.match(/game.html$/)) { loadTheGames() setInterval(() => loadTheGames(), 5000) } else { // Run the script only in game page if (urlParams.has('id')) { getGameDetails(urlParams.get('id')) } } ``` We get the `id` from the page URL and use it in the API call. Then display the data in an HTML table. ```
Player1 Player2 Starting player Status Winner
``` *** We can get the join requests from API and show them beside the game so player1 can accept one of the coming requests and start the game. A simple HTML table: ```
Player Status Action
``` And the API call: ``` const getRequests = async (id, creator = false) => { const data = await APICall('/requests/' + id) if (data && data.id === 0) { document.getElementById('requests-error').innerHTML = data.error } else if (data && data.id === 1) { let temp = '' for (let i = 0; i < data.requests.length; i++) { const request = data.requests[i] temp += ` ${request.player} ${request.status}` if (creator) { // Add an Accept button if the visitor is player1 (creator) temp += ` ` } else { temp += '---' } temp += '' } if (data.requests.length < 1) { temp = 'None' } document.getElementById('request-list').innerHTML = temp } } ``` We can call the `getRequests` function inside the `getGameDetails` function because we can know when the user (visitor) is the creator of the game aka player1. Then show them an `Accept` button based on that so the player1 can accept the request. ``` const getGameDetails = async (id) => { ... // skipped unchanged lines if (game.player1 === userData.username) { document.getElementById('req-message-1').style.display = 'block' document.getElementById('req-message-2').style.display = 'none' getRequests(id, true) } else { getRequests(id, false) } } } ``` Also, let's make both functions run with an interval to auto-update the data. ``` // Run the script only in homepage if (!window.location.pathname.match(/game.html$/)) { loadTheGames() setInterval(() => loadTheGames(), 5000) } else { // Run the script only in game page if (urlParams.has('id')) { getGameDetails(urlParams.get('id')) setInterval(() => getGameDetails(urlParams.get('id')), 5000) } } ``` *** We added the `accept` button so let's add its function and transaction too. ``` const acceptRequest = async (id, player) => { const success = document.getElementById('requests-success') const error = document.getElementById('requests-error') if (!userData.username) { return } try { const accept = { app: 'tictactoe/0.0.1', action: 'accept_request', id, player } const operations = [ [ 'custom_json', { required_auths: [], required_posting_auths: [userData.username], id: 'tictactoe', json: JSON.stringify(accept) } ] ] const tx = new hiveTx.Transaction() await tx.create(operations) const privateKey = hiveTx.PrivateKey.from(userData.key) tx.sign(privateKey) const result = await tx.broadcast() if (result && result.result && result.result.block_num) { success.innerHTML = 'Success! Game started.' } else { error.innerHTML = 'Error! Check console for details. Press Ctrl+Shift+J' console.error(result) } } catch (e) { error.innerHTML = 'Error! Check console for details. Press Ctrl+Shift+J' console.error(e) } } ``` *** Now let's add a button for other users to join the game. ``` ``` And build the transaction for it: ``` const joinGame = async (gameId) => { const success = document.getElementById('join-success') const error = document.getElementById('join-error') if (!urlParams.has('id')) { return } const id = urlParams.get('id') try { const joinReq = { app: 'tictactoe/0.0.1', action: 'request_join', id } const operations = [ [ 'custom_json', { required_auths: [], required_posting_auths: [userData.username], id: 'tictactoe', json: JSON.stringify(joinReq) } ] ] const tx = new hiveTx.Transaction() await tx.create(operations) const privateKey = hiveTx.PrivateKey.from(userData.key) tx.sign(privateKey) const result = await tx.broadcast() if (result && result.result && result.result.block_num) { success.innerHTML = 'Success! Your request submitted.' } else { error.innerHTML = 'Error! Check console for details. Press Ctrl+Shift+J' console.error(result) } } catch (e) { error.innerHTML = 'Error! Check console for details. Press Ctrl+Shift+J' console.error(e) } } ``` *** ## Next part We finally finished most of the functions needed for starting the game. I think the only remaining challenge is the gameplay. We can use Canvas for the front-end graphical gameplay. I already built a project with Canvas but there is nothing easy about coding. It's still a challenge. Let me list the remaining tasks: - Gameplay front-end - Gameplay back-end - resync method for the database - Front-end polishing We are getting to the end of this project and I think we can finish it in the next post and finally play this boring game at least once. Making tutorials and coding at the same time is really hard and tiring. I might continue writing this kind of series for different projects if there is enough interest but the target audience is too small. So let me know in the comments what you think. I wouldn't mind building an interesting app or game which can be some kind of tutorial too. TBH after writing the above line I wanted to remove it. Let's see what happens. Thanks for reading. Make sure to follow me and share the post. Upvote if you like and leave a comment. Enjoy the cat.
![cat-pixabay.jpg](https://images.hive.blog/DQmSnpWeu5yxjzddzEavE4c61BkweaXrEJepuv4LHyMKpg1/cat-4611189_1280.jpg)
Images source: pixabay.com *** [GitLab](https://gitlab.com/mahdiyari/decentralized-game-on-hive) [Part 1](https://hive.blog/hive-139531/@mahdiyari/making-a-decentralized-game-on-hive-tic-tac-toe-part-1) [Part 2](https://hive.blog/hive-139531/@mahdiyari/making-a-decentralized-game-on-hive-part-2) [Part 3](https://hive.blog/hive-139531/@mahdiyari/making-a-decentralized-game-on-hive-part-3) [Part 4](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 5 by @mahdiyari