Guessing Number Game using Hyperswarm.
Tags: node, javascript, backend, hyperswarm
Published: 2024-04-23 18:48:06


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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fl7u568t33e927w0021s.jpg)

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

 <u>Table Of Contents</u>
    
1. [HyperSwarm](#hyperswarm)
2. [The structure of the game](#game-structure)
3. [Code](#code)
   + [Prepare environment](#code-environment)
   + [Index.js - Main function](#code-indexjs)
   + [Server](#code-serverjs)
   + [Client](#code-clientjs)
   + [Game](#code-gamejs)
4. [Demo](#demo)
5. [Bye Bye](#bye)

---

### HyperSwarm <a name="hyperswarm"></a>

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](https://dev.to/giulianaolmos/what-is-a-p2p-peer-to-peer-network-3mcc), 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 <a name="game-structure"></a>

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/or4p77p3mp4f3f9rejqy.jpg)

---

###  Code <a name="code"></a>

Let's go 

![Writting in the computer](https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExdGozZTJ5dGxob2k3Ym8zYWtod3N2Mm14dWMwb3E5emhiOHM0NGlleSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/gVlgj80ZLp9yo/giphy.gif)

---

####  Prepare environment <a name="code-environment"></a>

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

You can download it from [here](https://nodejs.org/en/download/).


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

```
npm init
```

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
```

**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 <a name="code-indexjs"></a>

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();

```

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 };

```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vx7kmwur14mkkgu5byso.png)
![Welcome Player](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tfzqsblipv9afdh9ip8d.png)


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 <a name="code-serverjs"></a>

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()   |
+---------------------+

```

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])
```

**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,
}
```

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

```
const GAME_CHANNEL = "hyperswarm-guessing-game";

module.exports = {
  GAME_CHANNEL,
};
```

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) => {})
```

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 };

```

---

####  Client <a name="code-clientjs"></a>

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

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

```
{
   server: false,
   client: true,
}
```

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 };
```

---

####  Game <a name="code-gamejs"></a>

```
+---------------------+
|        Game         |
+---------------------+
| -isStarted: Boolean |
| -numberToGuess: int |
| -isEnded: Boolean   |
| -lastGuess: int     |
| -lastClue: String   |
+---------------------+
| +constructor()      |
| +getNumberToGuess() |
| +startGame()        |
+---------------------+
```

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 };
```

---

####  Demo <a name="demo"></a>

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

This is the [link](https://github.com/GiulianaEOlmos/p2p-guessing-number-game) to the GitHub repo. 

Now, ✨we have a game working.✨


1 - Starting server
![Starting server](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/imagvr48mkvnn54np342.png)

2 - The first player joins the game
![The first player joins the game](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tjto2k329u6oy6utcved.png)

3 - The server starts the game
![The server starts the game](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ej6mvt7lugj2yl2lr3pa.png)

4 - The player guesses a number
![The player guesses a number](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/po6yeksoxfy3kziie8i8.png)

5 - Another player joins the game
![Another player joins the game](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rxibcffk7ho00mj788w3.png)

6 - The server is notified about the new player
![The server is notified about the new player](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jxnpgibn61d8mc2492qa.png)

7 - A resume of the game and the end.
![Giuli Terminal](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cmp8apsr4q6pgj6noqgs.png)

![Eva terminal](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gntny2d90ywi0khqnf6m.png)

![Server terminal](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s5v8024wcoozfxca4nvr.png)

---

####  Bye Bye <a name="bye"></a>

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](https://github.com/brymartinez/hyperswarm-guessing). 

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5pkq32o5lbr7dkxmovui.png)

See you in the next posts. 🚀