I’ve been doing software development as a frontend engineer for CARFAX’s Used Car Listings product for almost a year now. In large part, I didn’t want to pick up new projects so I could focus on growing into my new career, and it’s been a fantastic first (almost) year! However, near the end of 2018, I started getting the itch for a new project and was feeling more comfortable at work, so I started wondering about another project. Something silly and fun, ideally!
And as it turns out, I made a choice over Christmas break while talking with my niece at my parents’ house. She and I both thought it would be cool to be able to make your own disco floors. She said her friends would probably like it, so I decided to make a disco floor app. Or rather, an app that can make and play disco floor patterns. Simple as that.
Project Goals
When I initialized the repo, I made some notes for myself on what the project should accomplish. Some are actually published in the repo README.
I would later learn that the below list did a pretty bad job of encapsulating the work I wanted to do, but we can save that for the “post mortem” section, yeah?
Anyway, overall, my goals for the app were as follows:
- learn basic, essential Webpack configuration
- use MobX in a greenfield project to see how clean it can look
- basic features
- ability to play a floor pattern, pre-built or user-built
- ability to add new dance floor patterns that persist locally
- ability to manage play speed of patterns
- ability to toggle pulse control (unlit tiles are lit, lit tiles are unlit)
- ability to toggle color inversion (set color classes to complementary values)
- ability to manage local floor patterns (delete unwanted)
Of the above goals, there’s only one I have yet to complete - the ability to delete local patterns. That will come with a later update along with other quality of life and UX improvements, but overall I completed what I set out to do with the app.
Technical Issues
Over the course of the ~4 months where I was on again, off again writing the disco floor, I had a few issues worth mentioning.
On Data Structure & Saving Dev Time
One of the initial questions that I struggled with was how best to represent the basic structure of a disco pattern. A two-dimensional array made the most sense for a matrix-like object like a tiled floor, but that’s only a single frame. A ‘pattern’ in the way a disco floor works would be a series of frames - something like Pattern --< Frame --< Tile
. In other words, the representation needs more depth, so the Disco Floor app opts to have each pattern represented as an object, each frame represented as a unique, numerical key pointing to a two-dimensional array value.
const examplePatternWithTileObjects = {
0: [
// array for each row of tiles
[
{},
{},
{},
{},
{},
{},
{},
{},
{}, // row of tile objects
],
],
1: [
// etc
],
};
Further, each tile is defined as an object having two key-value pairs that would be referenced during UI interactions - color
and isLit
.
const exampleTile = {
color: "green",
isLit: true,
};
Additionally, I wanted to hand-write a few patterns to use as seed data for users to play with. It ended up being pretty frustrating and time-consuming to write as they got more complex and longer; I needed an abstraction to make it easier to write them from scratch (and is it turned out later, change them). A reference object (links to Github reference file) with a limited number of tiles (20) helped simplify things.
So with the reference, the patterns look something like…
const examplePatternWithNumberReferences = {
0: [
// array for each row of tiles
[
1,
5,
5,
5,
1,
5,
5,
5,
1, // row of tiles now represented by numbers
],
],
1: [
// etc
],
};
Overall, making this choice eased the frustration of writing patterns manually AND helped down the line when writing React components that would handle tile behavior. Each tile variation is referenced with a number from 1-20 when reading and setting patterns. Components that need the patterns import a pattern file and the reference file.
import { TILE_REFERENCE } from "../data/reference";
// ... component stuff
TILE_REREFENCE[1].color; // white
TILE_REREFENCE[1].isLit; // false
Starts to make sense why Python calls these things dictionaries :/
When Should You Break Up Stores?
Another question I grappled with during this project was the correct time to break up stores. At first, I had assumed I’d have two stores - an AppStore
and a FloorStore
.
Initially, I thought the AppStore could store some essential UI elements that would exist throughout the disco floor and the FloorStore would manage what I assumed would be the bulk of the logic that belonged to the creation of floor patterns. The early versions of the app were written that way, but as I added more of the features I had planned on, it started to feel more and more unwieldy; the AppStore remained small, the FloorStore was growing quickly.
As a result, I split the FloorStore into the PlayerStore
and the BuilderStore
. That felt like a more reasonable division because that accounted for the two largest ‘chunks’ of functionality: playing patterns and building them.
You might see where this is going, but ultimately, I ended up axing the AppStore altogether. Most of its code has moved into the PlayerStore, and the disco floor is structured based on the two main things the app does.
My gut tells me that I could have planned for this if I’d thought about it more. Regardless, the essential learning that came out of this experience is that stores should be split along the lines of functionality in an application and that there is a critical mass point at which stores ought to be split for the sake of keeping them from growing out of control.
Would It Be Faster to Pre-render All Frames and Change z-index?
I haven’t built out a proof of concept to know if this is true, but I’m putting it here from my own curiosity: would I be able to make silly faster player speeds like 8x and 16x if I lazily pre-rendered all the frames and simply swapped the z-indices out?
For context, the speeds I started with were 0.5x, 1x, 2x and 4x. 0.5 speed felt kind of lame, so I swapped it out for an 8x speed! Wooooo SPEEEEEEEED!!!! Speed…? Oh, well, after creating a few more complex patterns, it turns out that my app really struggled with 8x speed. Rerendering was choppy and unreliable, sadly. So instead of ripping the app apart and starting from scratch, I went back to half speed…
It was later that I started to think of the subtitle of this section as a solution to the issue with rerendering lagging behind. Since it takes a little time to start interacting with the app, it might be enough to simply render each frame on a different z-index and swap those values each time a frame ticks. I haven’t tested the solution, but it’s possible I’ll give it a try once I get through all the other features I still want to complete.
Speaking of which…
Possible Updates & Wrap Up
So what’ll be next? I mean, I have a few other projects I’m in the middle of, but I do plan to return to this project and finish it out.
Specifically:
- a full-fledged mobile experience
- a color selector in the builder (instead of having to click tiles)
- row, column and diagonal toggles (think Bingo)
- option to use the last frame instead of having a fresh start each frame
I initially started this project on a whim to learn some basic Webpack. It’s been fun, and my goal has been met. Maybe this isn’t as good a wrap up as could be - I’m still new to this whole thing! - but I’ll keep trying to share the things I learned. The way a wise friend once told me.
Thanks for reading!