My friends Eduardo and Heitor soon joined in on the fun, and we ended up building a simple game where you controlled a box-like character and moved around around in an open 2D world. This eventually turned into The Box World, a Club Penguin-like lounge game where you can create rooms, invite your friends, walk around and chat with your buddies by sending messages in the room chat.
Around the same time, I happened to find a pretty neat .io game, TankRoyale.io, and it didn’t take me long to realize that I might be able build my own version with the technologies that I already knew. So that’s exactly what I did, and thus Tank Battle was born!
Over the next couple of months, I would work on the project sporadically, implementing cool features, learning tons of new things, and of course, breaking my code hundreds of times, doing complete project rewrites, and debugging all sorts of unforeseen issues. Nevertheless, I managed to power through it all and finally completed the game in early October of 2021.
As of writing this, Tank Battle is online and you can check it out here.
Update: As of Feb. 4th, 2022, Tank Battle has been taken down due to unforeseen costs with running AWS EC2 cloud servers. I’m working on getting the game back online ASAP. Sorry for the inconvenience. 😓
Table of Contents
Before I began this long and arduous web/game development journey, my primary goal was just to get a small prototype working to show off to my friends. However, as with literally any software side project ever, feature creep started to kick in.
I started thinking to myself: what features and technologies could I add to make this game cooler? The project’s scope grew quickly and my goals started expanding. How about a fully fledged database? Maybe even a user authentication system? An API for fetching leaderboard rankings, player stats and user data? Things like that.
Even without any prior experience in any of those things, eventually I decided that these extra features added useful fucntionality to the “end product”, so to speak, and warranted some extra blood, sweat and tears to bring them to life. And so, filled with detemmienation, I added them to my to-do list and started implementing some extra spicy features to the game.
In the end, these were the final goals I decided on:
- Real-time communication over Websockets with a simple message exchange model
- Graphics with p5.js
- Game rooms that anyone can create and join
- Game matches/rounds with a match state lifecycle:
- Waiting: waits for a minium number of players to join
- Pre-match: lasts a couple of seconds before starting the match starts
- Match running: the actual game itself
- Post-match: lasts a couple of seconds before after the match ends
- Award points to the players at the end of each match
- An Elo-like rating system and a public leaderboard
- Simple and secure user signup/login system with JWTs and password hashing/salting
- A SQL database with a basic schema for storing user data
- A simple and funtional but aesthetically pleasing UI with only vanilla HTML and CSS
- Frontend written entirely in vanilla JS - no additional libraries expect when absolutely necessary
Fortunately, I was actually able to meet nearly 100% of the goals I had outlined for this project, even the ones that were added to the to-do list during the later stages of development. As a guy who still sees himself as an “advanced beginner” programmer, seeing this project through to the end was incredibly rewarding and satisfying.
How I built it
Since Tank Battle is essentially another fullstack web app created by yours truly, you can definitely expect some of the usual suspects to be driving the inner workings of this project. However, there are a couple of very important special guests this time round, without whom this project wouldn’t be complete.
As with all my web dev projects so far, I’m using vanilla HTML, CSS and JS on the frontend. No fancy frameworks like React/Vue/Angular here.
The frontend code is also pretty straightforward - most of it is just HTTP requests made with the Fetch API and DOM manipulation with vanilla JS.
Other that that, there’s only two other special things I’d like to note here. Firstly, (and as I’ve said before) I’m using p5.js for the graphics in the game room page - this includes doing things like drawing the rooms, players, bullets, obstacles and game prompts. Secondly, I’m using the native browser WebSockets API for exchanging JSON-encoded messages over WebSockets with the main game server. (an alternative to this would be a library like socket.io).
Building the frontend for Tank Battle was a relatively simple task - all I had to do was build a minimal UI with vanilla HTML and CSS and and write some code to perform a couple of HTTP requests. But building a reliable, feature-rich, modular and easily maintainable backend though, that was the real challenge here.
Serving static files (mostly client-side scripts, stylesheets and a favicon) with the Express static file middleware
Handling API requests for leaderboard rankings and user data using the Express Router
Connecting to a MySQL 8.0 database with a connection pool and perform SQL queries using mysql2
Authenticating users with JSON Web Tokens
Creating new player accounts and keeping their passwords safe by hashing and salting them with bcrypt before storing them in the MySQL database.
The other Node program is the main game server. It acts as a WebSockets server and also runs all of the game logic. Out of the two Node servers, the main game server is the one doing the heavylifting here, including:
Handling all of the near real-time bidirectional communication by exchanging messages with clients (i.e. players and spectators) over Websockets using the ws package.
Managing the internal states of all game rooms, including room metadata (number of players, current match state, etc.), spectators, players, obstacles, and bullets.
Running the main game loop, which consists of:
Updating the match state for all game rooms according to the match state lifecycle (i.e. switching between “Waiting for players to join”, “Pre-match”, “Match running”, “Post-match” states when appropriate)
Receiving user inputs and queueing them up for later processing - this includes player movement, shots, room spectation and room join requests.
Running all game physics - this includes player motion, projectile motion and ricochets (i.e. reflecting off walls and obtacle edges), and collision detection between different entities (players, solid obstacles, bullets, room walls)
Sending the current game state back to all players and spectators in all rooms
A very important aspect that made the game’s backend waaay easier to work on is being able to split your app into multiple files. This might sound trivial, but separating your app logic into discrete, manageable units allows you to plan your system at a higher level first and get into the nitty-gritty coding later, which is essential for larger projects like Tank Battle. Using multiple files makes your code easier to maintain and expand on in the future, and it’s a lot less distracting.
The backend is hosted on an AWS EC2 instance (a free-tier eligible one, of course!) located in São Paulo, in order to minimize latency. Most of the people who will try out Tank Battle are most likely going to be relatives or close friends of mine who live here in Brazil anyways, so I might as well deploy my app to a nearby host.
If you’d like to self-host your own version of Tank Battle, I’ve got detailed instructions here.
New technologies that I learned
Tank Battle is by far the most complex and demanding software project I’ve worked on to date. I’ve learned a lot of really cool things during the game’s development cycle, and honestly, I would’ve never thought I would have accomplished all of this.
I’ve been using the Node.js + Express combo for literally 100% of my web projects for the past year and so far it’s worked remarkably well. This time round, though, I found that using the Express Router made a huge difference in making my webserver code more modular and maintainable by splitting all of the route logic into separate files.
The Router also proved to be very useful for making a simple API. It only has a couple of endpoints, notably,
/lb, which returns a JSON with the leaderboard data, and
/user, which returns a JSON with player stats.
I also got to try out some very useful npm packages, like
bcrypt for secure password storage,
mysql2 for querying data from a MySQL database,
jsonwebtoken for user authentication,
validator for validating user emails,
dotenv for environment variables, and more.
Authentication systems for web services are notoriously difficult to implement by yourself in a production setting. If you plan on shipping your code as part of an actual product, you’ll most likely have to use an Identity-as-a-Service provider like Google’s Firebase Auth, Auth0, Okta, Cognito by AWS, etc (take a look at this great article by Alessando Segala for more on this). That being said, since Tank Battle isn’t a serious product (like, at all), I can totally get away with just using JSON Web Tokens (JWT) and web cookies for user auth.
When a user fills out the login form, the browser makes a
POST request to
/login with a JSON containing the the user’s username and password. Once the webserver receives the request, it checks the database to see if the username/password combination is valid, in which case it will send a JWT inside a cookie back to the user’s browser.
While this approach might be prone to a CSRF attack, it’s simple and quite effective, and any vulnerabilities should be mitigated by using the
Secure cookie attributes.
Relational Databases and SQL
I took this opportunity to try out the SQL language and a relational database system for the first time. If you’re looking for a quick and easy way to get started with SQL too, I highly suggest following Khan Academy’s awesome Intro to SQL course. Trust me, you won’t regret it.
When it comes to setting up an actual SQL database for your app, though, two common open-source options come to mind: MySQL and SQLite. These are completely different relational database systems: while MySQL is a fully-fledged program (i.e. a database server) that requires a local or remote TCP connection in order to connect to the database and perform SQL queries, a SQLite database is contained entirely inside a single file. For more on MySQL vs. SQlite, take a look at this comparison by Hostinger.
In the end, I chose MySQL Server 8.0 as it’s more commonly used with Node.js than SQLite. As far as Node packages go, I’m using
mysql2 to connect to MySQL (don’t forget to use connection pooling!) and perform queries. You could also use an Object-Relational Mapper (ORM) like
sequelize, which works well with lots of different flavors of SQL, including MySQL, SQLite and others.
Being able to write asynchronous code is a necessity for any web application in the 21st century. Present-day JS (both server-side and client-side) already have support for modern
await syntax since the introduction of ECMAScript 2017. Many Node frameworks and libraries like Express,
jsonwebtoken make extensive use of Promises and async functions.
Client-Server game model
The core of the client-server architecture that’s powering Tank Battle is heavily inspired by a series of fantastic articles from Gabriel Gambetta, a senior software engineer at Google Zürich, titled Fast-Paced Multiplayer.
He explains the basics of the Client-Server game architecture and proposes the use of an authoritative server and “dumb” clients as the basis for making online multiplayer games, in addition to describing techniques such as client-side prediction, server reconciliation, entity interpolation, and lag compensation to minimize lag in online games.
If you’re interested in making your own online multiplayer games as well, I highly recommend reading Gambetta’s articles - they’re remarkably well written and explain challenging technical concepts in a clear manner. They helped me a lot while I was building Tank Battle’s main game server. Kudos to you, Mr. Gambetta!
Elo Rating System (Simple Multiplayer Elo)
The Elo rating system was created in the 1960s by Hungarian-American physics professor Arpad Elo for rating FIDE chess players, but nowadays Elo-based ratings system are widely used in MMOs as a means to quantify a specefic player’s “true” abilities and to predict how well they will fare against other players.
A very popular Elo-based system in use today is Microsoft’s TrueSkill rating system. Being proprietary software, though, you’d have to pay Big M some hefty dough to get a license to use TrueSkill in your own project. Fortunately, Tom Kerrigan, an independent iOS app developer from Seattle, WA, created a much simpler (and free!) implementation of a multiplayer rating system which he calls Simple Multiplayer Elo. Here’s how he describes it:
I’ve come up with a simple and effective way to apply the two-player Elo system to multiplayer scenarios:
- At the end of a game, make a list of all the players and sort it by performance.
- Think of each player as having played two matches: a loss vs. the player right above him on the list, and a win vs. the player right below him.
- Update each player’s rating accordingly using the two-player Elo equations.
I call this method “Simple Multiplayer Elo” (SME) and am making it public domain.
Implementing SME from scratch is very straightforward - all you need is to loop over your ranked list of players and run the classic Elo algorithm on pairs of players in a “top-down” manner: first players 1 and 2, then players 2 and 3, etc. The reverse (“bottom-up”) approach (first players
n-1, then players
n-2, etc.) will yield very similar results (although not exactly the same as “top-down”).
To be honest, I’m not very good when it comes to statistics, so I can’t argue on how SME fares agaisnt other ratings systems. Tom claims that his system “approaches ideal accuracy much faster than TrueSkill. Eventually TrueSkill produces ratings that are almost perfect (after hundreds of rounds) but at that point the difference is marginal”.
If you’re using SME in your project, be sure to send an email to Tom! He’s a chill dude.
Even though Tank Battle is, for all intents and purposes, a done deal, there are two very important aspects of this project that I’d consider revisiting in the near-ish future.
Ironically, one of the game’s biggest shortcomings is also one of its main “selling points”: networking. If the players have slow internet, or are on opposite sides of the Earth, the game can suffer from lag. Sometimes, it can render the game totally unplayable, even for players with decent broadband.
But as I’ve mentioned earlier, one of the main topics that Mr. Gambetta discusses in his Fast-Paced Multiplayer articles are implementations of techniques to minimize lag in online games, such as client-side prediction, server reconciliation, entity interpolation, and lag compensation, which could drastically improve the game’s networking performance.
Unfortunately, adding these extra features to game will be quite time-consuming, and I think this project has already reached its point of diminishing returns with respect to development time and the sense of self-accomplishment that I get from working on it.
Improving the UI/UX
In its current state, the game’s UI does the bare minimum of what it need to do. I managed to implement a somewhat consistent design across the game’s webpages, but that could certainly use some work - the website as a whole just feels kind of hacked together and unpolished.
I also learned the hard way that HTML DOM manipulation with vanilla JS is ridiculously tedious. I’m guessing libraries like JQuery could help, but I wanted to build my frontend without any extra libraries (as stated in the project’s goals). Even so, it might be high time to try out a proper frontend framework like React to minimize these headaches in future projects.
Overall, I’d say that working on Tank Battle has a been a very positive experience, despite the sheer amount of time that it took to finish this project. I learned a lot of really cool stuff though, so I hope I can find similarly interesting and challenging projects to tackle in the next couple of months.
Over the course of making this game, I’ve come to realize that web apps are still the undisputed king of software in the 2020’s and that’s not going to change anytime soon. Facebook, Twitter, Discord, Instagram, Google Docs, Twitch, Spotify, and even VS Code are all web apps made for browsers or that have been adapted as native desktop apps (using Electron, for instance) or mobile apps.
Being a competent web dev will ensure that your apps and ideas can reach a massive pool of potential users, so learning web technologies is an absolute must for the up-and-coming software engineer. Plus, companies are still offering a LOT of money for expert web dev ninjas.
That being said, as someone who hasn’t even started college yet, I’m not sure I’d be happy working as a full-time web developer. Sure, having that sort of skillset is very valuable (and honestly, pretty awesome too), but surely there must be cooler things to do than styling webpages with Sass or choosing the best React State library, whatever the hell that means.
Doing web dev for Tank Battle has been fun, but I’m looking forward to exploring other things now. Maybe some low-level C/C++ coding on microcontrollers? Studying computer ISAs? Embedded Linux? Something else? Honestly, I don’t know. Guess we’ll have to see. If you’ve somehow made it this far, I’d love to hear your suggestions.
Thanks for reading! Lucca out. ✌