Back to Projects

David's Rummy

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).

Motivations & Goals

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 :

A good technical exercise

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.

Why not use arrays?

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.

Networking

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.

Gallery

Click an image to enlarge.

Index