Introduction
oombi, a beloved multiplayer card game in Sri Lanka[1], has captured the hearts of players for generations. Seeking to bring this traditional game to a global audience, I embarked on a solo development journey to create an online oombi game. Using NextJS, NodeJS, and SocketIO, I crafted a web-based platform that offers an immersive experience for players worldwide.
With NextJS as the frontend framework, I built a responsive and dynamic user interface, ensuring a smooth and engaging gameplay experience. On the backend, I harnessed the power of NodeJS to handle multiple game sessions and manage player interactions efficiently.
To enable real-time communication, SocketIO[2] facilitated instant updates and synchronized gameplay among participants. Players could enjoy the thrill of oombi with friends, coworkers, and strangers, regardless of their location.
By combining NextJS, NodeJS, and SocketIO, I successfully created an online oombi game that stays true to the essence of the traditional card game. The project not only provides a platform for remote play but also contributes to the preservation and promotion of Sri Lankan cultural heritage in the digital age.

Requirement Gathering
Before embarking on the development of the online oombi game, a crucial step was gathering the requirements to shape the player experience and ensure the game met expectations. This phase involved careful consideration of the core elements of oombi and the desired features for the online adaptation.
First and foremost, understanding the rules and mechanics of the traditional oombi game was essential. This involved researching and analyzing how the game is traditionally played, including the deck composition, card values, bidding process, and trick-taking gameplay. By gaining a deep understanding of the game's fundamentals, I could accurately translate them into the online version.
Next, I identified the key objectives for the online oombi game. These objectives included creating an immersive multiplayer experience, enabling real-time gameplay, providing intuitive user interfaces, and ensuring accessibility for players of all skill levels. The goal was to capture the essence of oombi while enhancing it with modern conveniences and global connectivity.
To gather requirements, I engaged with potential players and sought their input through surveys, interviews, and user feedback sessions. Their insights helped shape the desired features and functionality of the online game. Some of the crucial aspects considered during this process included:
- Multiplayer functionality: Determining the desired number of players per game, whether it would be played in partnerships or individually, and accommodating both local and global player connections.
- Real-time communication: Identifying the need for live chat, avatar customization, and interactive elements to replicate the social aspect of playing oombi in person.
- User-friendly interface: Designing a visually appealing and intuitive interface that allows players to easily navigate the game, view their hand and the table, and understand game status and scores.
- Platform compatibility: Considering compatibility with different devices and browsers to ensure accessibility for a wide range of players.
- Game customization: Providing options for players to adjust settings such as game speed, difficulty levels, and visual themes to cater to individual preferences.
By gathering these requirements, I gained a comprehensive understanding of the desired player experience and could prioritize the development efforts accordingly. The requirements served as a guiding framework throughout the development process, ensuring that the online oombi game would meet the expectations of its target audience.
The requirement gathering phase laid the foundation for an engaging and immersive online oombi experience. By actively involving players in the process, I could tailor the game to their needs and preferences, resulting in a highly enjoyable and authentic gameplay experience that stays true to the spirit of the traditional card game.
Existing Systems
There are few oombi games available, but I found that most of them were offline, outdated or lacked certain features. I also wanted to create a customized version of the game that would be unique to my users.
- Omi Mobile Game : Link!
System Architecture

oombi.io is built upon a client-server architecture, ensuring a seamless and immersive multiplayer card game experience. The client-side is developed using NextJS, enabling responsive user interfaces and dynamic interactions. Communication between the client and server is facilitated through SocketIO, allowing real-time updates and synchronization. On the server-side, NodeJS handles the backend operations, while MongoDB serves as the database for storing user data and game state. Let's dive deeper into the architecture and explore its key components.
Client-Side: The client-side of oombi.io leverages NextJS, a popular React framework, which provides a foundation for building interactive user interfaces. NextJS offers features like server-side rendering, code splitting, and efficient client-side routing, ensuring optimal performance and responsiveness. The client communicates with the server using SocketIO, enabling real-time bidirectional communication between players and the game server.
Server-Side: The server-side of oombi.io is built using NodeJS, a scalable and event-driven runtime environment. To distribute the load effectively, node clusters are implemented, allowing multiple instances of the NodeJS application to run on different CPU cores. Clustering enhances performance and ensures efficient handling of concurrent game sessions. This approach allows for horizontal scalability, enabling the game to accommodate a growing number of players and maintain responsiveness.
const express = require("express");const cluster = require("cluster");const farmhash = require("farmhash");const port = 8080;const num_processes = require("os").cpus().length;if (cluster.isMaster) {let workers = [];let spawn = function (i) {workers[i] = cluster.fork();workers[i].on("exit", function (code, signal) {spawn(i);});};// Spawn workers.for (var i = 0; i < num_processes; i++) {spawn(i);}const worker_index = function (ip, len) {return farmhash.fingerprint32(ip) % len;};const server = net.createServer({ pauseOnConnect: true }, (connection) => {let worker = workers[worker_index(connection.remoteAddress, num_processes)];worker.send("sticky-session:connection", connection);});server.listen(port);console.log(`Master listening on port ${port}`);} else {// worker nodes}
Redis Integration: Redis, an in-memory data store, plays a vital role in the architecture of oombi.io. It is utilized to ensure that the client always connects to the correct node worker, even after a reconnection. Redis acts as a central cache and facilitates session management, allowing efficient routing of incoming client requests to the appropriate worker processes. This enhances the overall performance and reliability of the multiplayer experience.
const io = socketio(server, {cors: {origin: "*",},reconnection: true,maxReconnectionAttempts: 5,});instrument(io, {auth: false,});io.adapter(io_redis({ host: "localhost", port: 6379 }));
MongoDB Database: User data and game state in oombi.io are stored in a MongoDB database. MongoDB, a popular NoSQL database, offers flexibility and scalability for managing structured and unstructured data. By leveraging MongoDB, oombi.io can efficiently store and retrieve player profiles, game statistics, and the state of ongoing game sessions. The database serves as a reliable and persistent storage solution for the application's data needs.
Analytics and Logging: To gain insights into user behavior and optimize the game experience, oombi.io utilizes LogRocket and Google Analytics (GA) for analytics and logging. LogRocket records user sessions, allowing developers to replay and analyze user interactions for debugging and improvement purposes. GA provides valuable analytics on user engagement, traffic patterns, and game performance, enabling data-driven decision-making for further enhancements.
By combining NextJS, SocketIO, NodeJS clustering, Redis, MongoDB, and analytics tools, oombi.io achieves a scalable, real-time multiplayer experience. The architecture ensures efficient communication, distributed load management, reliable data storage, and actionable insights for continuous improvement. With this robust foundation, oombi.io provides an engaging and immersive online oombi game, connecting players from around the world and fostering a vibrant gaming community.
Features
Multiplayer functionality for up to four players
oombi.io allows you to play with up to four players, making it perfect for small gatherings or intimate gaming sessions with friends and family. Gather your fellow oombi enthusiasts and compete in thrilling multiplayer matches, adding an element of social interaction and friendly competition to your gameplay.

Real-time gameplay
Experience the thrill of real-time gameplay as you engage in intense oombi matches with other players. The seamless integration of SocketIO ensures instant updates and synchronization, allowing you to see your opponents' moves and react accordingly. Real-time gameplay adds excitement and spontaneity to each round, making every decision and trick count.

Spectator mode to watch you friends play
Not in the mood to play but still want to be part of the action? Oombi.io offers a spectator mode that allows you to watch your friends' games as they unfold. Join as a spectator and cheer on your friends, analyze their strategies, and learn from their gameplay. Spectator mode promotes a sense of community and enables social interaction even when you're not actively participating in a match.

Customizable settings such as game speed and player avatars
oombi.io understands the importance of tailoring the game to suit your preferences. With customizable settings, you can adjust various aspects of the gameplay experience. Set the game speed to match your desired pace, allowing for fast and thrilling matches or more relaxed gameplay. Additionally, personalize your player avatars, adding a touch of uniqueness and personality to your in-game presence.

And of course dark mode!
For players who prefer a darker aesthetic or want to game in low-light environments, oombi.io offers a stylish dark mode. Dark mode not only reduces eye strain but also creates a visually appealing atmosphere, immersing you further into the oombi gaming experience. Enjoy the game's vibrant colors and engaging visuals while benefiting from the comfort of a darker interface.

With these features, oombi.io elevates the traditional oombi card game into an engaging and customizable online multiplayer experience. Whether you're playing with friends, spectating their matches, adjusting settings to your liking, or immersing yourself in the captivating dark mode, oombi.io offers a platform that caters to your preferences and enhances your enjoyment of the game.
Implementation
I used the following technologies to develop our online oombi game:
- NextJS: A React framework for server-side rendering and client-side routing
- NodeJS: A JavaScript runtime for building server-side applications
- SocketIO: A real-time communication engine for web applications
- MongoDB: A NoSQL database for storing user data and game state
I chose NextJS and NodeJs because they are both popular, scalable, and have strong developer communities. Socket.IO was a natural choice for real-time multiplayer functionality, as it enables bidirectional communication between the server and client. I used MongoDB to store user data and game state, as it is a flexible and easy-to-use NoSQL database.
Building an entire multiplayer card game like Oombi involves both client-side and server-side development. On the client-side, you would use a framework like NextJS to create the game's user interface, handle user interactions, and communicate with the server using SocketIO. Here's an example of how you can implement the client-side functionality:
// client.jsimport { io } from 'socket.io-client';const socket = io();// Join the gamesocket.emit('joinGame', playerName);// Listen for game updates from the serversocket.on('gameUpdate', (gameState) => {// Update the UI with the updated game stateupdateGameUI(gameState);});// Handle player actionsfunction makeMove(card) {// Send the chosen card to the server for validationsocket.emit('makeMove', card);}
On the server-side, you would use NodeJS and SocketIO to handle game logic, manage player connections, and synchronize the game state among players. Here's an example of how you can implement the server-side functionality:
// server.jsconst io = require('socket.io')(server);// Track connected playersconst players = [];io.on('connection', (socket) => {// Handle player joining the gamesocket.on('joinGame', (playerName) => {// Create a player objectconst player = {id: socket.id,name: playerName,};// Add the player to the list of playersplayers.push(player);// Emit the game state to the newly joined playersocket.emit('gameUpdate', gameState);// Broadcast the updated player list to all clientsio.emit('playerList', players);});// Handle player making a movesocket.on('makeMove', (card) => {// Validate the move and update the game state accordingly// Broadcast the updated game state to all playersio.emit('gameUpdate', gameState);});// Handle player disconnectionsocket.on('disconnect', () => {// Remove the player from the list of playersconst index = players.findIndex((player) => player.id === socket.id);if (index !== -1) {players.splice(index, 1);// Broadcast the updated player list to all clientsio.emit('playerList', players);}});});
These code snippets demonstrate the implementation of the client-server interaction using SocketIO. The client-side code establishes a connection to the server, joins the game with a player name, listens for game updates, and handles player actions. The server-side code handles player joining and leaving the game, validates player moves, updates the game state, and broadcasts the updates to all connected clients.
Also I used Node clusters for load distribution, but to ensure that clients always connect to the correct Node worker even after a reconnect, you implemented sticky sessions with the help of Redis. Here's how I handled Node clustering with sticky sessions and integrated Redis:
Import the necessary modules: Start by importing the required modules for clustering, sticky sessions, SocketIO, and Redis:
const cluster = require('cluster');const numCPUs = require('os').cpus().length;const sticky = require('sticky-session');const socketIO = require('socket.io');const redisAdapter = require('socket.io-redis');
Check if the current process is the master or worker:
Next, determine whether the current process is the master or a worker using the cluster.isMaster property:
if (cluster.isMaster) {// Code for the master process// Fork worker processesfor (let i = 0; i < numCPUs; i++) {cluster.fork();}} else {// Code for the worker process// Create and start the serverconst server = app.listen(PORT, () => {console.log(`Worker ${cluster.worker.id} is running`);});// Initialize SocketIO with Redis adapterconst io = socketIO(server);io.adapter(redisAdapter({ host: 'localhost', port: 6379 }));// Handle SocketIO events and connectionsio.on('connection', (socket) => {// Handle socket events for the connected client// ...});}
Implement sticky sessions with Redis[3]:
To achieve sticky sessions, you can utilize the sticky module, which ensures that client connections are consistently routed to the same worker process. In combination with Redis, it allows for seamless reconnection to the correct Node worker even if the client temporarily disconnects. Here's an example of how to implement sticky sessions with Redis:
sticky.listen(server, PORT, {workers: numCPUs,proxyHeader: 'x-forwarded-for',env: { redisHost: 'localhost', redisPort: 6379 },}, (worker) => {// Code to execute when a worker is createdconsole.log(`Worker ${worker.id} started`);});
By integrating sticky sessions and Redis, you ensure that each client remains connected to the same worker process throughout their session, maintaining the integrity of their gameplay data and providing a seamless experience even if they temporarily disconnect and reconnect.
Please note that the code provided is a general representation of how to incorporate sticky sessions and Redis with Node clustering and SocketIO. You may need to modify the configuration based on your specific setup and Redis server details.
By utilizing Node clustering, sticky sessions, and Redis, you can effectively distribute the load across multiple Node workers, maintain session affinity for clients, and achieve seamless reconnection, resulting in improved performance, scalability, and reliability for your multiplayer card game.
Challenges
Here are some common challenges I faced and how I addressed them:
- Real-time synchronization: Ensuring that the game state is synchronized in real-time across all players can be challenging. Implementing a robust communication system using SocketIO allowed you to handle real-time updates, player interactions, and game events. You may have faced difficulties in handling concurrent actions, managing the order of operations, and minimizing latency to create a smooth and responsive gameplay experience.
- Network and latency issues: Dealing with network connectivity issues and varying latencies among players can impact the gameplay experience. Implementing measures such as server-side validation of actions, client-side prediction, and server reconciliation helped mitigate the impact of network delays. By carefully optimizing and testing your game's networking code, you aimed to provide a consistent experience across different network conditions.
- Scalability and performance: Building a multiplayer game that can handle multiple concurrent players while maintaining performance can be challenging. The use of Node clustering allowed you to distribute the workload across multiple CPU cores, improving scalability. You may have encountered issues related to load balancing, efficient resource utilization, and optimizing database queries to handle a large number of players and game instances.
- Security and cheating prevention: Preventing cheating and ensuring a fair gaming environment is crucial for multiplayer games. Implementing server-side validation, input sanitization, and anti-cheating measures helped protect the integrity of the game. You may have faced challenges in identifying and preventing various cheating techniques, as well as implementing secure authentication and authorization mechanisms.
- Cross-platform compatibility: Ensuring that your game functions properly across different devices and platforms can be a complex task. Testing and optimizing the game for various browsers, operating systems, and screen sizes can be time-consuming. Additionally, handling input from different devices and maintaining consistent gameplay experiences across platforms can present challenges.
- User experience and user interface design: Designing an intuitive user interface and delivering a positive user experience are crucial for the success of any game. Iterating on the UI/UX design, gathering feedback from players, and conducting usability testing helped you refine the interface and make it user-friendly. Balancing the need for clear information display and an uncluttered gameplay screen can be challenging.
By recognizing and addressing these challenges, you were able to overcome obstacles and create an engaging and enjoyable multiplayer card game experience for your players. The development process likely involved a combination of problem-solving, testing, iteration, and collaboration to ensure a high-quality end product.
Future Work
- AI opponents for single-player mode: In the future, I plan to introduce AI opponents to enhance the gameplay experience for solo players. By developing advanced AI algorithms, players will have the opportunity to challenge computer-controlled opponents with varying levels of difficulty. The AI opponents will be designed to simulate intelligent gameplay strategies, providing a compelling and engaging experience for those who prefer to play alone.
- Tournaments and leaderboards: To add a competitive element to the game, I have exciting plans to implement tournaments and leaderboards. Tournaments will allow players to participate in organized competitions, either against AI opponents or other human players. By showcasing their skills and earning points, players can climb up the leaderboards and strive for the top rankings. This feature will foster healthy competition and a sense of achievement within the game community.
- Customizable game rules and scoring: I believe in giving players the freedom to tailor the game to their preferences. As part of future development, I aim to introduce customizable game rules and scoring. This means that players will have the ability to modify various aspects of the game, such as starting hand size, turn timers, point systems, and bonus conditions. By allowing customization, players can create unique gameplay experiences that align with their personal preferences, enhancing replayability and enjoyment.
- Integration with social media platforms: To enhance player engagement and facilitate organic growth, I am planning to integrate the game with popular social media platforms. This integration will allow players to seamlessly share their game progress, high scores, and achievements with their friends and followers. Furthermore, by connecting the game to platforms such as Facebook or Twitter, players can invite and challenge their social connections, fostering a social gaming experience and expanding the game's community.
These exciting future work possibilities will not only add new dimensions to the gameplay experience but also cater to the diverse preferences of players. By introducing AI opponents, tournaments, customizable rules, and social media integration, I aim to create a dynamic and engaging environment that keeps players immersed and connected. Stay tuned for these upcoming features, as they will undoubtedly enhance the overall enjoyment and longevity of the game.
References
Enjoyed this post? Read the next one ↗