How I Created the Online Game “Backgammon” (Part One)
Hi all!
From time to time I am strongly drawn to game development and I even published a couple of articles on my favorite Habr (here and here) about what comes out of it. And once again, when the desire overcame everything else, for some reason I decided that I could make a “simple” game of “long backgammon” in a few days. If you are interested in what came out of it, then sit back, today I will tell you how it all began.
Long backgammon is a game that I have been playing from time to time since childhood, the variety of combinations, the randomness of the dice and the dynamically changing tactics of the opponent make this game endlessly interesting for me, and in general people have been playing backgammon for over 3000 years.
After the game was chosen, I decided to make a list of what I want to see in “my” backgammon. I definitely want to play with friends and I like to talk during the game on general topics, so it will definitely be multiplayer with voice communication. Having thought that it will not always be possible to find a “free” friend to play with, I decided that a game mode with a bot is definitely necessary. This is how the minimum functionality of the application was determined.
In my professional activities, I develop architectures for various applications, manage the development process, and do the programming itself, both on the client side and on the server. Due to the established tradition, I use one programming language for both the client and the server, and if earlier it was JavaScript, then for the last few years I have been using TypeScript. My “standard set” includes PostgreSQL databases, Redis, S3 file storage in conjunction with CDN, and Node.js as a server-side code execution environment. As practice has shown, this set allows me to effectively solve problems in the vast majority of cases that I encounter. We have decided on the stack of technologies used, let's move on.
What any application whose users must be identifiable usually starts with – right, with authorization. At the moment, I believe that the most secure method I know of is to use a combination of accessToken (JWT) and refreshToken, without explicitly storing both keys on the client side. In my case, this is achievable by using the mechanism of storing refreshToken in an http-only cookie, which cannot be read from the browser side due to security policy, and therefore no code embedded in the page will have access to the key. In order to be able to use the same account on different devices or browsers, refreshToken is used together with the device identifier, the so-called “fingerprint”. This mechanism allows you to bind sessions to devices and be able to “logout” a particular device by deleting the session and, accordingly, its refreshToken. There is plenty of information on the web about how this is all implemented on the client and server side, so I will skip the details of this process, but if the topic is interesting, you can talk in the comments or even write a separate article. In the end, we are done with what kind of authorization will be, let's move on.
Considering the need for two-way communication between the client and the server in online game mode, it is necessary to decide how we will transfer information to the server and from it to the clients. Previously, I was an ardent supporter of HTTP transport, which in the vast majority of cases wins in performance and ease of use, but in our case we have a game in which information comes to clients in event mode, and in general we do not want to constantly pull the server with requests like which of my friends is online now, and in general to periodically ask the server if my opponent in the game is “alive”, because he has not been online for a long time. Of course, it would be possible to implement sending all data to the server using HTTP, and data to the client through a connection to server-sent events (SSE), especially since now we have HTTP / 2 which significantly saves the time of each request to the server, using the existing connection many times, which allows us to exclude “lengthy” processes of handshake and certificate verification, exchange of encryption keys and other invisible processes that make our life safer on the network. However, despite all this, over the past few years I have been actively using two-way communication based on WebSocket in various projects, having discovered a wonderful library at one time uWebSockets.jswhich in my opinion is the standard of how to use sockets in web development. Not only does it use server resources very efficiently, its arsenal includes processing of HTTP requests, which together allows you to build a universal back-end for almost any project, from the simplest to large and complex. In addition, WebSocket, unlike SSE, allows us to transfer binary data, which increases efficiency by significantly reducing the volume of transmitted traffic. Of course, you can complain about the fact that in many corporate networks WebSocket traffic is blocked, while SSE works, but we are making a game and this is not for work. In general, we have also decided on data transfer, but I would also like to separately draw attention to how I support data typing at the transport level, especially since TypeScript is used on both sides. We can discuss for a long time what is better to use for serialization of transmitted data – binary precompiled protobuf or, for example, just json, here everyone has their own preferences, but for me this choice was made several years ago, when I wrote for one of my projects librarywhich allows you to describe any typed data structures and effectively serialize/deserialize them into binary form without preliminary compilation. This library is used both on the client and on the server, the protocol is described on the server side and when connecting, the client receives all the necessary data schemes, which it can subsequently operate with the server. This mechanism allows you to forget about the data validation stage both on the client and on the server, we simply will not be able to assemble a packet to send to the server or client with incorrect data types, as well as decode it. Again, if the topic is interesting, you can collect material for a separate story on this library. Note this in the comments.
We seem to have sorted out the technologies used and the server-side stack, it's time to think about the client part of the application. Since one of the conditions is to work on all platforms, and writing native applications for each of them is too long, here the old familiar TypeScript, HTML templates and CSS design were chosen without alternatives. I will say right away that I am infinitely grateful to the people who make Next.js, this is a truly crazy framework for creating front-end, my team and I have used it to build both very complex and large projects and simple one-pagers many times – universal, fast, beautiful and a lot of everything “out of the box”. Of course, there are things that are very difficult to solve when using this tool, but in the vast majority of cases everything is great. I made this digression because it is worth noting the fact that I sometimes like to do something differently than I would do if I were implementing a commercial project, because where else, if not in your own sandbox, can you frolic as you please. So this time I decided to write my own small engine to manage the entire UI and gameplay, using, so to speak, a non-standard approach, but in accordance with W3C standards.
To develop the front I decided to use esbuildit quickly compiles TypeScript, has a built-in watcher for source code changes and a web server for distributing compiled JavaScript code, builds source maps and has a bunch of other interesting features. I also use it to build and minify the production version of the application. By the way, it was not for nothing that I ended the previous paragraph with words about the W3C, over the past few years this organization has done a lot for the development of technologies and the industry as a whole, this is especially noticeable in comparison with what was at the dawn of web development, and I remember very well the early 2000s, when not only fetch was not there, but in general you could only think about loading data from the server in the format of using iframe. So, the entire game engine is built around simple functions available in all modern browsers. Currently, the project uses the library howler.js for loading and playing sound, everything else is a pure experiment in creating an application on TypeScript without using the usual Redux, i18n and other delights of modern web development.
I think that's enough for the first part, if you're interested in finding out what happened next, leave messages in the comments, and the topic of the next part of the story can be noted in the voting below.
You can view the current status of the project and play by clicking on link.