My interest for combining unrelated features and innovating in unexplored directions helped me to create Tetris Rivals. Since 1984, the original Tetris has gone through several iterations. Aesthetical changes, tweaking the board size, and most importantly, the popularized multiplayer mode for competitive play. The goal in competitive play is to outlast or score higher than your opponent. Since that only requires you to focus on your game and play fast, it essentially becomes a single player game. In the spirit of bringing back the classic arcade games with a new twist for Hack-Cade, I decided to create the true multiplayer Tetris.
What it does differently
Here's an outline of what happens in the classic game:
- The game starts and you're furiously bringing down tiles
- Your opponent might not even be playing, but you have no time to focus on silly details
- You get better and master the game
- You repeat the best strategy, keep winning, and get bored
Here's what Tetris Rivals brings to the table:
- A true multiplayer experience with the OG sound tracks
- No email | No Account | No data - Hassle free play
- Ability to make 1v1 rooms with friends or queue in solo to make new friends
- The game starts on a larger board giving you more time and freedom
- You notice your opponent is about to clear 4 lines with the line tile
- Just before he places it, you turn it into a square and declare war
- A single strategy is useless due to the constant variability
- The introduction of new mechanics guarantee everlasting entertainment!
How I built it
- Conditional html rendering
- Event listener for game controls
- Game logic and construction (collision, validity, line clears, animation and opponent tile substitution)
- Client side computation to alleviate server load and reduce costs
- Socket.IO client implementation for real-time updates
The backend is a NodeJs server which operates on Socket.IO and MongoDB to implement web sockets. The web socket communication protocol is better suited for real-time data transfer over the https protocol. It keeps a channel open for communication while still offering security. The server is responsible for generating game codes, managing game state, and maintaining game rooms. This is accomplished through generating random 6 digit codes, storing them in a database, and listening/emitting events to sync all players. It actively listens to key game events such as:
These events (and more) continuously update the collection of game states stored at all times on the server. After any update to the game state, all (both) clients in the game room are notified and respectively update their UI. Once all the clients leave the room, it clears the state and deletes the code for future reuse.
Tetris Rivals is entirely hosted on the cloud using the Linode's suite of services. The following services from Linode are used:
- Linode (VPS)
- DNS Manager
- Firewall for VPS
The frontend is hosted on a Nginx web server on Linode running Ubuntu 20.04 - provisioned from scratch. The backend is also hosted on the same Linode but with forever - an auto restart tool for NodeJS servers in an event of a crash. The VPS above is attached to a custom domain from domain.com using the DNS manager. The game can be found at Tetris Rivals.
Lastly, the mongodb database is hosted on another Linode running Debian 9. This VPS was started from an image found on the Linode Marketplace. It is also supported by a firewall allowing public access from VPS#1 to port 27017
Challenges I ran into
When first designing the game, the flow of events was very challenging as this is my first game developed. The idea of maintaining states so a single server can allow a large amount of rooms was very difficult. Additionally, deciding between client-side computation and server-side computation was also difficult. To solve this, I referred to online tutorials such as this and this to learn more about multiplayer games. In the end, I created a global state object indexed with the game code for state management, and opted for client side computation to decrease load on the server. A downside to this is possible cheating in inspect elements.
Linking 2 Players
Before the single index.html page, I had 2 pages - one for home and another for the game. Once a player created a game, they were put in a specific room and navigated to the game.html page. However, when a second user tried to enter the room, it no longer existed. This is because Socket.io does not play nice with renders. To overcome this, I implemented conditional rendering on a single page for a persistent socket connection.
A minor challenge, but nonetheless took me 2 hours and a lot of research on how to accomplish this. The problem lied in adjusting the nameservers on the register's website to Linode's namespaces.
Accomplishments that I'm proud of
- Created a functioning multiplayer game
- Used Web Sockets instead of HTTPS
- Hosted the entire solution with no localhost
- Hackiness of the project is at an all time low
What I learned
I brushed up on my vanilla development skills. Learned a lot about linux command line and setting up instances with varying images. Learned about game development, self implemented state management, and the WEB SOCKETS!
What's next for Tetris Rivals
- Better error handling
- Public queue implementation
- Cleaner and better maintainable code
- A LOT OF PLAY TIME (I mean testing....) to identify bugs and fix them