I built a mobile game that doesn't want your whole day
Most mobile games are designed to keep you. Energy meters that refill in four hours.
Streak counters that guilt you. A little character that gets sad if you leave.
I wanted the opposite: something you open for two minutes, finish, and put down without
the app trying to talk you out of it. That turned into
**[Mnemoza](https://play.google.com/store/apps/details?id=com.mnemoza.app)** — a memory
card game where you flip two cards, find the matching pair, and clear the board. It is
free, it works entirely offline, and it does not ask for an account.
Every scope decision came back to one question: can you finish this before your coffee gets cold? A Very Easy board is eight cards and takes under a minute. Very Hard is thirty-six and takes a few. Either way you reach an ending, and the ending is an actual ending — there is no next chapter, no "come back in six hours to claim your reward." The modes follow the same rule. **Solo** has no time limit and no way to lose. **Timed** adds a clock if you want pressure. **Memorize** shows you the entire board face-up for a few seconds and then hides it, which turns a casual game into something genuinely hard. And **2 Players** works on one phone, taking turns — no second device, no network, no accounts. That last one gets more use than I expected, mostly with children. There is a **Daily Challenge** too: one board per day, generated from the date itself, so everybody in the world gets the same one. No server involved — the date *is* the seed.
108 drawings, none of them a bitmap
The part I am most pleased with is the artwork, and it is also the part nobody notices,
which is roughly the definition of good artwork.
Six decks — Fruits, Animals, Shapes, Ocean, Space, Nature — eighteen cards each. That is 108 individual illustrations, and every one of them is vector paths written by hand rather
than an exported image.
That choice paid off three times over. The art stays perfectly crisp at any card size on any screen. The 3D board and the flat 2D board read the same drawing data, so the two renderers cannot drift apart. And adding a new deck is data, not an asset pipeline.
The constraint that shaped every drawing: on a Hard board a card is about 60dp. At that size the silhouette is all that survives. So each item leads with the shape a child recognises before anything else — the apple's dimple, the banana's curve — and interior detail is deliberately sparse.
The board is real 3D, and it degrades
The card flip is an actual Y-axis rotation of a mesh in a 3D scene, running on React Three Fiber. It is a small detail that makes the whole thing feel physical rather than like two images swapping places. But a 3D scene is a liability on a phone you have never tested. So the board falls back to a flat 2D renderer automatically if the graphics context fails — and you can also switch to it deliberately in Settings if you prefer something lighter. Reduced-motion users get a cross-fade instead of a rotation. The game never depends on an animation having finished. Accessibility got the same treatment throughout: large touch targets, readable type, strong contrast, and no state that is signalled by colour alone. It is in five languages — English, Turkish, Spanish, French and Simplified Chinese.
Two things that cost me a weekend
Not everything went smoothly, and the two that stung are worth passing on.
**Release builds are a different application.** Android's R8 shrinks and renames your code for production. Turning it on cut 6.5 MB off the download, which felt free. Then the board crashed instantly, because Expo constructs native views by reflection — it looks up a constructor by name, and R8 had renamed it. The debug build was fine because R8 never runs there. I now install every build-configuration change on a real phone and open it before I believe it.
**One object literal cost 30% of my frames.** The board started feeling sticky, so I
profiled it. Janky frames were at 35.96%. The cause was an object created inline in JSX —
a new object every render — which meant a timer tick upstream was re-rendering the entire
3D scene four times a second. Wrapping it in `useMemo` took ten minutes and dropped jank to
3.45%. Ten minutes to fix, two hours to find; that ratio is most of my debugging life.
Designing for the two-minute break
108 drawings, none of them a bitmap
The part I am most pleased with is the artwork, and it is also the part nobody notices,
which is roughly the definition of good artwork.
Six decks — Fruits, Animals, Shapes, Ocean, Space, Nature — eighteen cards each. That is 108 individual illustrations, and every one of them is vector paths written by hand rather
than an exported image.
That choice paid off three times over. The art stays perfectly crisp at any card size on any screen. The 3D board and the flat 2D board read the same drawing data, so the two renderers cannot drift apart. And adding a new deck is data, not an asset pipeline.
The constraint that shaped every drawing: on a Hard board a card is about 60dp. At that size the silhouette is all that survives. So each item leads with the shape a child recognises before anything else — the apple's dimple, the banana's curve — and interior detail is deliberately sparse.
The board is real 3D, and it degrades
The card flip is an actual Y-axis rotation of a mesh in a 3D scene, running on React Three Fiber. It is a small detail that makes the whole thing feel physical rather than like two images swapping places. But a 3D scene is a liability on a phone you have never tested. So the board falls back to a flat 2D renderer automatically if the graphics context fails — and you can also switch to it deliberately in Settings if you prefer something lighter. Reduced-motion users get a cross-fade instead of a rotation. The game never depends on an animation having finished. Accessibility got the same treatment throughout: large touch targets, readable type, strong contrast, and no state that is signalled by colour alone. It is in five languages — English, Turkish, Spanish, French and Simplified Chinese.
Two things that cost me a weekend
Not everything went smoothly, and the two that stung are worth passing on.
**Release builds are a different application.** Android's R8 shrinks and renames your code for production. Turning it on cut 6.5 MB off the download, which felt free. Then the board crashed instantly, because Expo constructs native views by reflection — it looks up a constructor by name, and R8 had renamed it. The debug build was fine because R8 never runs there. I now install every build-configuration change on a real phone and open it before I believe it.
**One object literal cost 30% of my frames.** The board started feeling sticky, so I
profiled it. Janky frames were at 35.96%. The cause was an object created inline in JSX —
a new object every render — which meant a timer tick upstream was re-rendering the entire
3D scene four times a second. Wrapping it in `useMemo` took ten minutes and dropped jank to
3.45%. Ten minutes to fix, two hours to find; that ratio is most of my debugging life.
I had already built the features I shipped
This one still makes me laugh.
Deciding what to add before launch, I went looking through my own code and found a function
called `starRating()` — it grades a finished board from one to three stars. It was written.
It had unit tests. Its own comment said *"Star rating for the result screen."*
Nothing called it. Not a single line.
The same thing had happened to an entire game mode. The state machine had a `previewing phase that shows every card face-up and then hides them: implemented, tested, board correctly locked during the preview. Every difficulty set the preview duration to zero and no mode existed to switch it on.
So two of the three things in my launch release were features I had already built and forgotten to expose. Stars took an afternoon. Memorize mode took one config entry and a countdown banner.
If you have been building something for a while, read your own code before you plan your next sprint. Mine had a to-do list hidden inside it.
About the ads, plainly
The game is free and shows ads, and I would rather tell you exactly where than have you
find out.
Never during a round. Never on the board itself. Never between turns in two-player, because that is the one moment where two people are playing together and it is often a parent and a child. There is nothing at all for your first few games. After that an occasional full-screen ad appears *between* rounds, with a minimum three-minute gap.
That policy costs real revenue — I know roughly how much, because I can see what a more aggressive setup would earn. It stays because a calm game that interrupts you is not a calm game, and I would rather have the version I actually want to use.
What shipping taught me
Building took a few weeks. Getting from "it works on my phone" to "it is on the store" took about the same again — closed testing with twelve people for fourteen days before you may even *apply* to publish, content ratings, data safety declarations, ad verification, screenshots in five languages.
None of it is hard. All of it is unskippable, and none of it is in any tutorial about making a game. If you are planning something similar: budget the same time for shipping as for building. Not a week. The same time.
The other thing I would tell myself is smaller but more useful. Anything in your app that looks like information must actually be connected to something. I shipped a Settings toggle that did nothing at all, and a version number that was a hard-coded string still claiming `0.1.0` four releases later. Both looked like features. Neither was.
Mnemoza is free on Google Play, works offline, and needs no account:
Start on Easy with the Fruits deck. Then try Memorize on Very Hard — thirty-six cards,
about eleven seconds to study them, and then they are gone. I have cleared it exactly
twice.
If you play it, I would genuinely like to know what you think. It is a small game, and
small games get better on feedback.
Comments
Post a Comment