Socket-Agar! -coding an online multiplayer game from scratch
A real-time multiplayer online game inspired by agar.io and built using socket.io ( with bots! )
HashNode-Hacks Entry
About the approach and Inspiration:
This project is a really fun way to display the power and versatility of Web Socket based communication. Web Sockets are different from AJAX requests. In traditional AJAX requests (you might have encountered these in the form of GET, POST, PUT requests for a REST API) the communication link between the server and the Client disconnects after completion of the request (like when you receive a status code 200, 404 etc.). In contrast, in Web Socket based communication, the client and server remain connected. This helps in cases when you require real time communication happening! A widely used example is the notifications you receive from a text-messaging app.
Socket.IO is a popular JS library used to take advantage of real-time communication. It comes with various convenient features like dealing with proxies and fallback to "polling" instead of web-sockets if something goes wrong. We'll be using this library to implement both our ends.
This quarantine, people have found really fun ways to interact with their loved ones, like playing Among us or Ludo online. This project is inspired from the widely popular online game agar.io and is implemented purely using real time socket-based communication. You and your friends, anywhere in the world are on the same game-map and competing against each other to get the highest spot on the leader board, in real time!
This project is also a very visual representation of how important running time of algorithms is, because we don't want our game to feel laggy while serving as many players as we can. This problem is often overlooked in traditional web projects as no one really notices the difference between 5ms response time and 500ms response time, but here it matters a lot as our clients and the server contact really frequently!
Hosted on Heroku (expect some latency as i'm using a hobby dyno based in US)
The UI is completely cross platform and mobile friendly, accepts touch input instead of mouse on mobile!
Tech Stack used:
- Node JS Back-end
- Vanilla JavaScript Front-end
- Socket.IO for real time communication
- Auth0 for secure authentication
- PostgreSQL
- Express JS
- Bootstrap 4, CSS 3, HTML 5, jQuery
Rules of the game:
- Move your mouse/touch on the screen to move your character.
- Absorb orbs by running over them in order to grow your character.
- The larger you get the slower you are.
- Objective: Absorb other players to get even larger but not lose speed.
- The larger player absorbs the smaller player.
Check out the game hosted on Heroku get a better grasp!
From the rules themselves we can deduce that we need the following features in our app:
- Physics based collision detection between orbs and the players.
- Cheating prevention else players who know some JavaScript will simply teleport when they are going to lost!
- We must add some AI driven bots to the game else it will be too lonely if there is no one to play with!
How i made this project:
The frontend:
1. The Canvas:. โ๏ธ
The HTML canvas element is our game-world (with all the players and orbs) on which we "draw" using jQuery, each time the "tick-tock" events occur (explained below).
The "tick": ๐ The moving game is based on a very simple approach. The server keeps track of the position of the orbs and the stats of the players (their name, size, color, speed, radius, score, etc) and sends this data to all the clients every 16 milliseconds . The client then renders this data on the canvas element on screen. The client "draws" this image every 16 ms.
The "tock": ๐ The clien calculates the "vector" of the player from mouse (or touch input in case of mobile) and sends this data to the server. The vector basically contains the data of the direction in which the player wishes to go. The server then normalizes the vector (to prevent cheating) and updates the location of every player using the speed. Its like the old school dx = v.dt displacement-velocity relation you might have learned in high school! This is a socket.IO event called "tock" which sends the vector data as payload to the server.
So this "tick-tock" cycle has a period of 16ms hence the image on the screen moves at 60 frames per second!
2. Authentication:
The client has the feature to either play as a guest or login via Auth0 which also supports google OAuth login. The authtication is based on JSON web tokens and is used to access protected GET and POST routes on the server. (check out public/auth.js to learn how it all works)
3. Stats and Leader-boards:
The leader-board is publicly available which can be accessed by a simple GET request to "/leaderboard" . During the game, a live leaderboard is also maintained on a side screen. Logged in players can also view their stats like total number of players killed, max score, etc, via a GET request to the protected "/stats" route.
When a player is killed live, a notification is sent to every player in form of an "playerKilled" socketIO event.
When a player dies, A game over splash screen is shown with his stats and a play-again button.
Login, leader-boards, Player Stats are all rendered on a Bootstrap modal above the canvas element.
All logged in players can view their stats saved in the database.
The BackEnd:
1. The classes:
We have some JavaScript class definitions in /classes to make our code more approachable. Player Class: This class saves the socketID of the player, and an instance of the PlayerConfig and PlayerData class.
PlayerConfig Class: This class has the private data of the player like the speed, vector, zoom, etc. which need not be sent to everyone.
PlayerData class: This class has the public data of the player which is sent to everyone in the "tick" event every 16ms which is used by the client to render the game on screen. This split is primarily done to decrease the overhead on the server so that it requires less band-with speed to play.
Orb: Contains the randomly-decided position of the orbs and the color.
2. socketMain.js ๐
This is where all the socket-based communication happens. the "tick" event is sent every 16 ms from here to every socket which contains as payload the players array, which is simply the array of all PlayerData objects. This is achieved via a simple setInterval.
This file also handles events like player death and disconnect in which case it removes the player's data from memory and saves the score data of the player into the postgres database.
The "tock" event is listened to in this file which updates the "vector" of the player sent to it by the client.
A playerInfo map is also maintained in this file which is nothing but a hashmap (Fast O(1) lookup times! ) that maps a player's socketID to its corresponding player object, this is used in expressMain to save the login info of the users and also in checkCollisions to check if the dying player is a bot so we can add a new one (read botLogic section) .
3. checkCollisions.js ๐ฅ
This is where the physics happens! every "tick" event, the server runs a simple brute force O(n^2) algorithm to find out if any collisions happened between the players or with the orbs. To make this a little bit faster the base check is the AABB collision detection test (read more here here ). If this check passes the traditional Pythagoras Theorem is invoked to check if the objects really collide. If an orb collision happened, the player's score is increased and if a player collision happens the bigger player absorbs the smaller player (game over for smaller player :(, but he can always click play again!)
4. botLogic.js ๐ค
Bots and AI! Well our approach is fairly simple, the bots are players whose movement (via vectors) is controlled by the server on the naive greedy logic: "Go to the Orb or a smaller player whichever is the closest, in order to absorb it" .
The Bot-Algorithm:
On each cycle of 16ms, for each bot, we do the following: find the nearest orb or player by looping through all orbs and players and keeping track of the minimum distance ecountered. After finding the orb/player at minimum distance from this bot, determine the vector between the bot and the orb/player and move the bot according to this vector.
Optimination: The above algorithm as it is, is not scalable. Why? Calculation of all distances is way too slow because we are using the Pythagoras formula : distance = sqrt((botX-orbX)^2 + (botY-orbY)^2). To make this faster I just used the approximate distance formula: distance โ |botX-orbX| + |botY-orbY|. This is way faster because now we aren't calculating the square root (larger overhead) for thousands of orbs and players, which made the first formula slower.
Bots have no fear of other players bigger than them but they are still quite efficient as they always follow the shortest possible path to the nearest orb.
Bots have random names generated from the API: randomuser.me
Bots are added only when the first player logs in and a new bot is added if a bot dies to keep the flow of the game. All bots are removed if there is no human player currently playing the game to reduce the overhead on the server when its idle.
Note: I have kept the speed of bots lower than player speed else they are too over-powered. It is interesting how well bots can play with such simple programming!
5. expressMain.js
Controls the AJAX requests. like "/leaderboard" which gives the leaderboard data and "/login" which logs the player into the app so it can access protected routes and view stats.
6. Auth0 authentication
Auth0 is used for authentication for enhanced security and also to make our work less tedious (so we can focus on the math stuff : ) . No session data is saved since its JWT-based authentication, so less overhead on server, also provides direct access to various social identity providers like Google for seamless login. Players don't have to login each time as the token is stored in browser storage.
Phew, After all this the app is ready! Hosted on Heroku using a free-dyno and heroku postgres! The game settings are fully customizable by the admin using config vars!
The author,
Aman Ali,
B.E. Computer Science and MSc. Physics
BITS Pilani, Pilani Campus
#HacksBITSPilani
#Hacksbitspilani