Guessing Number Game using Hyperswarm.

Giuliana Olmos - Apr 23 - - Dev Community

Hello 🤗
Today, I will explain how to create a guessing number game using Javascript, NodeJS, and Hyperswam. I will also talk about this package, how it works, and what it is for.

Smart Homer

I will leave the table content here to help you understand how this post's topics are divided more easily.

Table Of Contents

  1. HyperSwarm
  2. The structure of the game
  3. Code
  4. Demo
  5. Bye Bye

HyperSwarm

What is Hyperswarm? What is it for?
Well, first of all, I wanted to talk about this package that Tether created. Hyperswam is a package that helps you create peers to find and connect between them, announcing a common 'topic.'

What is a peer? It is the way we call the participants in a P2P network.
If you want to know more about this, you can check my post, where I go through the topic :D

These are some important links.
Github => https://github.com/holepunchto/hyperswarm
Documentation => https://docs.pears.com/building-blocks/hyperswarm


The structure of the game

Hyperswam is an excellent tool for creating P2P networks, BUT this game will use peers in a server-client architecture. It is easy to understand how the peers interact in this type of architecture if client-server is the only architecture you know.

The flow is very simple:

  • The server will randomly choose a number between 1 and 100.
  • The first user that connects to the game will start the game.
  • The following users can join and play in the same party.
  • Every time a player guesses a number, the server makes a check and updates the game's status, then sends the updated status to all the connected players.
  • When someone guesses the number, the server notifies the players, and the game starts again.

Guessing game flow


Code

Let's go

Writting in the computer


Prepare environment

Before starting, you should have Node installed on your computer.

You can download it from here.

After this, open the terminal in the folder where you want to start your project. Run

npm init
Enter fullscreen mode Exit fullscreen mode

and complete all the steps to initiate your node project.

Then, you have to install two packages to start to work.

npm install hyperswarm readline
Enter fullscreen mode Exit fullscreen mode

Readline will provide an interface for reading data from the console. This means we can guess the number using the console without creating a front end.


Index.js - Main function

Once we have installed the packages, we will start to work on our index.js

const { CLI } = require("./cli");
const { Server } = require("./server");
const { Client } = require("./client");

async function main() {
  const cli = new CLI();
  const nickname = await cli.askTerminal("What is your nickname? ");

  const lowerCaseNickname = nickname.toLowerCase();

  if (lowerCaseNickname === "server") {
    //const server = new Server();
  } else {
    //const client = new Client(nickname);
  }
  console.log(`Welcome to the game, ${nickname}!`);
}

main();

Enter fullscreen mode Exit fullscreen mode

Also, we need to create a module for reading that gets exported as CLI. This code creates the interface in the console.

cli.js

const rl = require("readline");
let readline;

class CLI {
  constructor() {}

  async askTerminal(question) {
    if (!readline) {
      readline = rl.createInterface({
        input: process.stdin,
        output: process.stdout,
      });
    }
    return new Promise((resolve) => {
      readline.question(question, (input) => resolve(input));
    });
  }
}

module.exports = { CLI };

Enter fullscreen mode Exit fullscreen mode

Then, run your project with npm run start

The console will ask you for your nickname; if you type "server," you will start the peer that will work as the server in charge of managing the game. Otherwise, you will start the peer that acts as a client (or player) to join the game.

Welcome terminal
Welcome Player

We haven't added the code to create the client and the server yet, so you will just see a console log with your nickname.


Server

The server is a class. I will create a new instance of the server module when the user types "server" in the console after running npm run start.

This is a basic UML of the class.


+---------------------+
|      Server         |
+---------------------+
| -server: Hyperswarm |
| -players: Map       |
| -clients: Array     |
| -game: Game         |
+---------------------+
| +constructor()      |
| +handleConnection(  |
|   Socket, PeerInfo) |
| +isValidGuess(int)  |
| +respontToClients(  |
|   String)           |
| +initializeGame()   |
+---------------------+

Enter fullscreen mode Exit fullscreen mode

Now, there are some lines that I want to comment on. Server is an instance of Hyperswarm.

So, as I mentioned before, the peers need a topic to connect with each other. To join the topic, we need to use

swarm.join(topic, [options])
Enter fullscreen mode Exit fullscreen mode

topic is a 32-byte Buffer, and options is an object that contains both options server and client and a boolean to indicate how the peer should act.

In this case,

{
   server: true,
   client: false,
}
Enter fullscreen mode Exit fullscreen mode

For the topic, I will create a doc called constant.js

const GAME_CHANNEL = "hyperswarm-guessing-game";

module.exports = {
  GAME_CHANNEL,
};
Enter fullscreen mode Exit fullscreen mode

Once the peer connects to the network, we must indicate how to react to some events. In this case, we will use

swarm.on('connection', (socket, peerInfo) => {})
Enter fullscreen mode Exit fullscreen mode

Emitted whenever the swarm connects to a new peer. This means the server will do something every time a new peer (a player) connects with them.

Inside that function, I will handle the connections to store the data from the players. I will use that information to determine which connections the servers had and where I have to send messages with the game's status.
Also, we will have a socket.on(), which is the server receiving and showing the messages from players.

const Hyperswarm = require("hyperswarm");
const { GAME_CHANNEL } = require("./constants");
const { Game } = require("./game");

class Server {
  constructor() {
    this.server = new Hyperswarm();
    this.players = new Map();
    this.clients = [];
    this.game = new Game();

    const topic = Buffer.alloc(32).fill(GAME_CHANNEL);

    this.server.join(topic, {
      server: true,
      client: false,
    });

    this.handleConnection = this.handleConnection.bind(this);
    this.server.on("connection", this.handleConnection);
  }

  handleConnection(socket, peerInfo) {
    console.log("New connection ");
    const publicKey = peerInfo.publicKey.toString("hex");
    if (this.players.size) {
      if (!this.players.has(publicKey)) {
        console.log("New player ");
        this.players.set(publicKey, true);
        this.clients.push(socket);
      }
      this.respontToClients(
        this.game.lastClue ?? "Guess a number between 1 and 100:"
      );
    } else {
      console.log("First player");
      this.players.set(publicKey, true);
      this.clients.push(socket);
      this.initializeGame();
    }

    socket.on("data", (data) => {
      const jsonData = JSON.parse(data.toString());
      console.log(`Server: ${jsonData.nickname} guessed ${jsonData.guess}`);

      const guessedNumber = parseInt(jsonData.guess);
      if (this.isValidGuess(guessedNumber)) {
        if (this.game.isStarted) {
          if (guessedNumber === this.game.numberToGuess) {
            const message = `User ${jsonData.nickname} guessed ${jsonData.guess} and it's correct!\n The game is over! \n A new game will start soon.`;
            this.respontToClients(message);
            this.game.isEnded = true;
            this.initializeGame();
          } else {
            if (guessedNumber > this.game.numberToGuess) {
              this.game.lastClue = `User ${jsonData.nickname} guessed ${jsonData.guess} and it's too high!`;
            } else if (guessedNumber < this.game.numberToGuess) {
              this.game.lastClue = `User ${jsonData.nickname} guessed ${jsonData.guess} and it's too low!`;
            }
            this.respontToClients(this.game.lastClue);
          }
        }
      } else {
        const message = `User ${jsonData.nickname} guessed ${jsonData.guess} and it's not a valid guess. Please guess a number between 1 and 100.`;
        this.respontToClients(message);
      }
    });
  }

  isValidGuess(guess) {
    if (guess < 1 || guess > 100) {
      return false;
    }
    return true;
  }

  respontToClients(message) {
    for (const client of this.clients) {
      client.write(
        JSON.stringify({
          type: "game-update",
          message,
        })
      );
    }
  }

  initializeGame() {
    this.game.startGame();
    this.respontToClients("Game started! Guess a number between 1 and 100:");
  }
}

module.exports = { Server };

Enter fullscreen mode Exit fullscreen mode

Client

+---------------------+
|      Client         |
+---------------------+
| -nickname: String   |
| -client: Hyperswarm |
| -cli: CLI           |
| -topic: Buffer      |
| -connection: Socket |
| -handleConnection:  |
|   Function          |
+---------------------+
| +constructor(String)|
| +handleConnection(  |
|   Socket, PeerInfo) |
| +askGuess()         |
+---------------------+
Enter fullscreen mode Exit fullscreen mode

In the client, I will use swarm.join(topic, [options]) where

{
   server: false,
   client: true,
}
Enter fullscreen mode Exit fullscreen mode

And swarm.on('connection', (socket, peerInfo) => {}) will inform us when we connect with the server.

const Hyperswarm = require("hyperswarm");
const { GAME_CHANNEL } = require("./constants");
const { CLI } = require("./cli");

class Client {
  constructor(nickname) {
    this.nickname = nickname;
    this.client = new Hyperswarm();
    this.cli = new CLI();

    this.topic = Buffer.alloc(32).fill(GAME_CHANNEL);
    this.client.join(this.topic, {
      server: false,
      client: true,
    });

    this.handleConnection = this.handleConnection.bind(this);
    this.client.on("connection", this.handleConnection);
  }

  handleConnection(socket, peerInfo) {
    console.log("Client connected to server!");
    this.connection = socket;
    socket.on("data", (data) => {
      const jsonData = JSON.parse(data.toString());

      if (jsonData.type === "game-update") {
        console.log(jsonData.message);
        this.askGuess();
      }
    });
  }

  askGuess() {
    this.cli.askTerminal("> ").then((number) => {
      this.connection.write(
        JSON.stringify({
          nickname: this.nickname,
          guess: number,
        })
      );
    });
  }
}

module.exports = { Client };
Enter fullscreen mode Exit fullscreen mode

Game

+---------------------+
|        Game         |
+---------------------+
| -isStarted: Boolean |
| -numberToGuess: int |
| -isEnded: Boolean   |
| -lastGuess: int     |
| -lastClue: String   |
+---------------------+
| +constructor()      |
| +getNumberToGuess() |
| +startGame()        |
+---------------------+
Enter fullscreen mode Exit fullscreen mode

In the server, we make an instance of the game. This is the code to initialize the game and the logic to choose the number.

class Game {
  constructor() {
    this.isStarted = false;
    this.numberToGuess = 0;
    this.isEnded = false;
    this.lastGuess = null;
    this.lastClue = null;
  }

  getNumberToGuess() {
    return Math.floor(Math.random() * 100) + 1;
  }

  startGame() {
    this.numberToGuess = this.getNumberToGuess();
    this.isStarted = true;
    console.log("Number to guess: ", this.numberToGuess);
  }
}

module.exports = { Game };
Enter fullscreen mode Exit fullscreen mode

Demo

Before running the code, you must remember to uncomment the lines in index.js.

This is the link to the GitHub repo.

Now, ✨we have a game working.✨

1 - Starting server
Starting server

2 - The first player joins the game
The first player joins the game

3 - The server starts the game
The server starts the game

4 - The player guesses a number
The player guesses a number

5 - Another player joins the game
Another player joins the game

6 - The server is notified about the new player
The server is notified about the new player

7 - A resume of the game and the end.
Giuli Terminal

Eva terminal

Server terminal


Bye Bye

I hope you enjoyed creating this game and have understood more about how hyperSwarm works.

I want to mention that I took the main idea from here.

Thank you so much for reading. 💖
Please let me know your thoughts and if you would add something else to the game.

Thank you image

See you in the next posts. 🚀

. . . . . . . . . . . . .