You might know the game rummy (or sometimes rami) from the popular tile-based game Rummikub. Ever wanted to play it with friends and family no matter where you are? Now you can!
David's Rummy is essentially the same game as Rummikub, without the branding for obvious reasons. Actually, an official game already exists, and it's playable both on PC and mobile, but I never found it pleasant or easy to use. It's also full of typical mobile game engagement farming, which clutters the UI, and makes it seem like the game itself is no longer the main focus.
The original tabletop Rummikub is a tile-based game for 2 to 4 players, where players take turns forming sets (tiles with same number of different colors) or runs (same color, consecutive numbers). The first to empty their rack wins, and then if you want, the remaining players can fight for second and third place. There are 106 tiles in total (104 numbered tiles in 4 colors, plus 2 jokers).
With studies, I was not always able to get around. We wondered with family and friends who know the game if we could play remotely, and without any headaches. I also love a good challenge, especially when it involves a game I really like. The goals were easily set :
How does one write a game like Rummikub?
The main constituent of Rummikub are the tiles. Tiles can exist in three places : the player's rack, the draw pile, or the table.
In the first two, they can exist freely and independently. On the table however, they must be present in blocks of three tiles at
least, and form a valid block (set or run).
My plan was to represent those blocks exactly like I did with my Solitaire : have each tile act as a node in a doubly linked list,
allowing for flexiblity in manipulation. The player would then use their rack as storage, but also as a buffer zone for building
blocks before submitting them, just like in real life.
Well, to be honest, I could have. But arrays involve constant memory allocation when shuffling blocks around,
and the only way around that is a custom, much more complex, memory allocator that must find contiguous ranges of tiles within
a larger memory pool. That pool is doomed to get fragmented, and cause other allocations.
My goal was to have the least mallocs during a game. It's much easier and fitting to create all 106 tiles by just allocating
a single block just big enough to store them all, and optimize the structure to fit all 106 tiles in the cache. Also, it avoids
duplication entirely! Tiles are never created during the game, only once, and all we ever touch are pointers (or indices).
Ultimately, rummy blocks exhibit properties of both ordered lists and puzzles made of individual pieces, so in theory, one could make
either implementation work. What decided it for me is the amount of operations required.
This does bring problems of its own, of course. If blocks were arrays, I'd have access to the amount of elements inside the array,
which exactly matches the amount of tiles forming the block. With a linked list, this explodes. It also does not contain any
information about the position on screen of the block, nor can we determine the block's width.
To fix this issue, blocks are supported by a block class, which remembers the first tile of a block, the amount, and the position.
These classes are the ones that emerge and die as blocks are formed and broken. In the worst case, all tiles are on the table and
not connected, so we allocate a pool of 106 blocks as well.
Designing the lobby, joining, leaving, placing tiles, sanitizing data and preventing cheats was the first real multiplayer programming
experience I had. I faced many issues, including maintaining a version number, updating the protocol, sharing information between
clients, and making sure that the server was the one source of truth.
It will be no surprise to those with experience, but you can never trust what a client sends you, as the server. The server centralizes
everything, and checks incoming data against its own state. Servers and clients always assume their logic is the same for their
respective counterpart, if the protocol version matches, that is.
I have yet to implement peer-to-peer connections to avoid the need for port-forwarding. It is on my to-do list, believe me.
Click an image to enlarge.
Main menu
Join screen. Players add trusted servers to their own list.
An ongoing game
A player trying to end their turn whilst an invalid block is present
Win screen
Index