Gartic Cellulart
for Gartic Phone
Gartic Cellulart is a Chromium/Gecko extension for the browser-based drawing game Gartic Phone that provides additional functions that integrate with the base game mechanically and stylistically.

This post will probably be updated a few times in the future because I want this post to document the many things this project has taught me, and maintaining and updating Cellulart in future will no doubt teach me more.
Observer: Prototypes
In order to get the most accurate and timely information possible about the state of the game, I learned to tap WebSockets. Quietly, of course.
As part of the initialization of a socket.io socket, the customary three-message handshake is exchanged, and we can use the opportunity to tap the connection.
Of course, I didn't know this when I first wrote the WebSocket tapper. I only had experience with Java and Python inheritance, and JavaScript's prototype chain seemed daunting, so I copied down some code from, if I remember correctly, a Reddit comment.
var currentWS;
const wsSend = window.WebSocket.prototype.send;
window.WebSocket.prototype.send = function (data) {
currentWS = this;
return wsSend.apply(this, arguments);
};
This worked right up until I needed to send my own messages over the connection. Gartic Phone's messages have a built-in message count that royally messes up your drawing if you naively inject strokes over the connection. Sorry, Jamie.
So, one upgrade to TypeScript
and a couple of hours of figuring out how prototypes worked
in which I tried overwriting the window's native WebSocket constructor
but couldn't reliably get it to work (possibly due to execution order) later,
I settled for extending the WebSocket prototype.
var currentWS: WebSocket | null = null;
const wsSend = window.WebSocket.prototype.send;
window.WebSocket.prototype.expressSend = function () {
return wsSend.apply(this, arguments as any);
};
window.WebSocket.prototype.send = function (data) {
currentWS = this;
const modifiedData = interceptOutgoing(data.toString());
if (!modifiedData) {
return;
}
arguments[0] = modifiedData;
return wsSend.apply(this, arguments as any);
};
Geom: Semaphores
The Geom module runs geometrize to decompose an image into geometric, Gartic-acceptable shapes. But it locks up the thread. So I send it off to a service worker (background script). Shutdown and image loss avoidance with keepalives aside, the shapes generated by geometrize arrive asynchronously, and need to be throttled so that they don't get sent at more than 8 strokes a second, which would trigger Gartic's autodraw detection.
As I would find out in Concurrent Programming next year, I would convergently discover the utility of semaphores. Of course, my implementation wasn't thread-safe, but each semaphore ("flag") was more or less controlled by a separate event listener, essentially preventing collisions, since two of the same event would (normally) not fire at once.
flags: { // Object that indicates whether we should attempt to send
interval: true, // Stroke sending throttling
queue: false, // Queue emptiness
pause: true, // Sending paused by user
mode: false, // Game in "drawing" phase
ws: false, // Active WebSocket captured
generate: true, // Shape generation (this actually doesn't have to do with sending)
notClearToSend() { // This is packaged with the flags
return !(this.interval && this.queue && this.pause && this.mode && this.ws)
}
},

Nowadays almost all of the stroke buffering and sending is handled in a separate component, and geometrize is free to generate strokes as fast as it likes - until you pause it, of course.
StrokeSender is, in my opinion, a nice piece of work, actually.
I know I made the same comment about the spaghetti that is Geom two years ago,
but I'm quite glad with the way StrokeSender turned out.
Especially the AbortController.
I'm going to have to retract those words in two years, aren't I?
Akasha: Compatibility
Hey, I was already playing catch-up with Gartic Phone, since for every new lobby settings preset they release, I have to go in and add in a set of Timer configurations posthaste.
The Akasha module allows you to save drawings to your device, upload them at a later date, and mirror them back to the server. Thirty minutes of drawing can be compressed into five or ten when played back at 8 pen strokes per second. Sometimes, I like to watch clips of people being flabbergasted by Akasha before going to sleep. ^_^
But now I also have to think about stroke format compatibility. Akasha saves out stroke data directly, meaning that if the way Gartic represents strokes changes, I have to detect that and employ a different data decoder when an older drawing is uploaded.
Here I have to give credit to Pudgergun (probably this one?) for providing some very old exported drawings from a different extension, giving me a legacy exported drawing format to consider and base Akasha's current exported drawing format off of. It ain't broke.
Overall
Reformatting the code multiple times was a rewarding experience because I was able to visibly see the quality of my code improving. I found myself treating refactoring.guru almost like a checklist (judiciously, of course, since not every pattern will enhance every codebase).
Cellulart also acclimated me to the feeling of vulnerability that comes with making open-source an unfinished product. In fact, as part of a wider indie-ification movement, I recently slapped GPL-3 onto Cellulart and moved it off of Github and onto Codeberg.
Thanks for joining me on the learning journey. Cellulart is available for Chrome or Firefox.
I got hit with two inexplicable one-star reviews recently so I'm not the happiest, but it's what it's, I suppose.



