📜 ⬆️ ⬇️

Multiplayer game on Go via telnet

Hello! My name is Oleg and I am SRE. At some point, I wanted to improve my Go programming skills and write a small multiplayer game.

I didn’t want to do web animation or write an application for a mobile platform, so I decided to use telnet as my client’s favorite tool for system administrators.

Here's what happened:
')
image

Choice of technology


Telnet


About applications using telnet, I heard for a long time. For example, a small excerpt from Star Wars:

telnet towel.blinkenlights.nl 

But why telnet, you ask? After all, netcat is much cooler and more modern to add others. Not everyone knows, but telnet is not only a utility for establishing a TCP connection. This is a whole protocol that allows, for example, sending the entered data to the server without pressing Enter (line feed ANSII).

Since we want to use arrows to control the machine and we don’t want to press Enter each time to send a sequence of characters to the server - telnet is perfect for solving this problem.

As an option, you could use

 stty -icanon && nc <host> <port> 

but it is, frankly, a crutch.

Go


Why go? Besides the fact that I wanted to improve my skills, there are Goroutines in Go, which are ideal for writing multi-threaded applications. And since we want many people to play in parallel - there was no reason not to use Go.

I also read this article not so long ago and it was interesting for me to try visualizing Goroutines in the application.

Game design


Game process


The game is a multiplayer for up to 5 people in a round. If the number of players is less, the remaining slots will be represented by bots.

You need, playing a machine, to survive and destroy rivals. The player can collect bonuses or scatter bombs that accumulate over time.

Damage system


One of the interesting and controversial points is the damage system. We decided to take into account the difference in speed and the part of the car that hit.

Speed ​​may vary. When struck, it is reset to 1. When a trouble-free ride after a while, it increases to 5.

Now consider the situation where one player catches the other and crashes into the back of the car. The damage calculation formulas for players will be as follows:

 if player.Car.Borders.intersects(&opponent.Car.Borders) { switch player.Car.Borders.nextTo(&opponent.Car.Borders, 0) { case LEFT: //   switch player.Car.Direction { ... case RIGHT: //   player.Health -= DAMAGE_BACK * (maxSpeed - player.Car.Speed) ... case RIGHT: //   switch player.Car.Direction { case RIGHT: //   player.Health -= DAMAGE_FRONT * player.Car.Speed ... ... } 

That is, if player A moved at a speed of 4 and player B caught up with B at a speed of 5, player A will lose 2 * (5-4) = 2 . However, the attacking player will lose 4 * 5 = 20 .

But the most severe damage will be a side kick DAMAGE_SIDE = 6 and can go up to 24.

Development


I want to remind once again that the game was created as an educational exercise. If you have any suggestions for improvement, I will be happy to take them into account.

The source code is on github , but let's go over the main points:

Telnet


As I mentioned, we need telnet protocol support. To do this, on the application side, we need to “say hello” to the client using a sequence of bytes:

 telnetOptions := []byte{ 255, 253, 34, // IAC DO LINEMODE 255, 250, 34, 1, 0, 255, 240, // IAC SB LINEMODE MODE 0 IAC SE 255, 251, 1, // IAC WILL ECHO } _, err := conn.Write(telnetOptions) 

and count the answer (starts with 250).

After that, the telnet client will work with protocol support, which we needed. More details can be found in the source code .

Data is taken from RFC854 .

Rounds


When a new player connects to the game, we must add it to the round. The criteria can be very different - for us - just the availability of free space. The round here is an array of players. The whole structure looks like this:

 type Round struct { Players []Player FrameBuffer Symbols ... } 

If there are not enough players, add bots.

Channels


Channels in Go are a very convenient means of synchronization between Gorountine. We use them to prepare the round and the distribution of players:

 func (p *Player) checkBestRoundForPlayer(compileRoundChannel chan Round) { foundRoundForUser := false for i := 0; i < len(compileRoundChannel); i++ { select { case r := <-compileRoundChannel: //    if len(r.Players) < maxPlayersPerRound && !p.searchDuplicateName(&r) { ... r.Players = append(r.Players, *p) compileRoundChannel <- r foundRoundForUser = true break } else { compileRoundChannel <- r } default: } } if !foundRoundForUser { //    ... r := Round{...} r.Players = append(r.Players, *p) compileRoundChannel <- r } } 

Note that you do not need to use Mutex, despite the fact that this function is performed in separate Goroutine. Channels in Go will not allow you to subtract an object several times, so modifying one object is excluded in this case.

Framebuffer


All players, without exception, should receive complete information about what is happening in the game. So let's send the same image to everyone. Initially, the FrameBuffer type was just [] byte, but at some point we decided to add Emoji to make the game more colorful. And since each such character takes more than 1 byte - in fact, Symbols is an array of byte arrays.

 type Symbol struct { Color int Char []byte } type Symbols []Symbol 

So, here is an approximate algorithm for generating the contents of a FrameBuffer:


Bots


Artificial intelligence here, of course not. But the bots have been trained in some basic actions:


In general, the bots are pretty annoying, so it becomes more interesting to play.

Visualization


We need gotrace , which actually does all the work for us. It is full of examples and a detailed description of how to run Docker to trace routines in our application. What happened, you can see in a short video:

Here we see several channels and a whole bundle of Goroutine that manage the game logic and the channels between them through which they exchange information.

One of the main credo of Go is fully observed here:

Share memory by sharing.

How to play?


Unfortunately, on Windows there are some problems with emoji in the terminal. I did not have enough motivation to understand, since I use MacOS and Linux. But if someone knows the compatibility mode or something else - I will be glad to advice.

For everyone else:

 telnet protury.info 4242 


Total



Bonus


In fact, this is the second game I wrote. The first was single-player, but also very interesting:



Play:

 telnet protury.info 4243 

thank


I would like to say thank you to the company InnoGames, which provides InnoDay - all day long, when you can do these things, also to my colleagues who helped me in every way - Pavel Usov and Kajetan Staszkiewicz. And also to all who read to the end!

Source: https://habr.com/ru/post/330512/


All Articles