Building a game for Crazy8s
Your game runs on a platform where players stake real money. That single fact shapes everything here.
Start with the trust model. Ten minutes there will save you a rejected upload, because it explains the one rule the whole SDK is built around:
Your game reports what the player did. The platform decides what that was worth.
Want to build something first and read later? Follow the tutorial — it builds a complete, publishable game from an empty directory, explaining each piece as it goes. It also links starter templates for every supported engine, if you would rather begin from something that already runs.
The path, end to end
| # | Step | Where |
|---|---|---|
| 1 | Understand what your game may and may not decide | Trust model |
| 2 | Pick an engine and build the client half | Engine guides |
| 3 | Write the server rules that decide outcomes | Trust model |
| 4 | Make the rules reproducible, and prove it | Determinism |
| 5 | Upload, test on staging, request review | Publishing lifecycle |
Each of those pages assumes you have read the one before it. Read in order the first time.
Building a front end rather than a game? That is a different job with a different contract — see building a client and the server API reference. A game never calls the server directly.
What the platform decides, not you
Before you design anything, know which levers are not yours. These are identical for every game on the platform, and a submission that sets its own is rejected rather than corrected — silently replacing a value you chose is how a game ships paying out something nobody intended.
| Decision | Value | Why it is not yours |
|---|---|---|
| Stake tiers | E5, E10, E20 | A player comparing two games should not have to read the small print on each |
| Practice play | Free, capped per day | You choose whether to offer it; the cap is ours |
| Platform rake | 10% of the pot | Setting this would be setting how much of a player's money you keep |
| Pool payout | 1st 60%, 2nd 20%, 3rd 10%, platform 10% | Same rake as a head-to-head match, so a player's expected return does not depend on which shape of game they picked |
| Your share | 50% of the platform's rake | See Revenue |
You do choose: the category, the pool size, the tick rate, whether practice is offered, and everything about how the game actually plays.
Pick your engine
| Engine | Best for | Guide |
|---|---|---|
| Excalibur.js | Code-first developers. The most direct fit — determinism is something you write rather than work around. | Guide |
| Defold | The smallest builds, around 1MB empty, leaving the most room for assets. | Guide |
| GDevelop | Getting playable fastest, or if the visual editor is what makes the game possible for you. Expect more JavaScript blocks than usual. | Guide |
Any engine that exports a static HTML5 bundle can work. These three have guides because they cover most needs and each has different pitfalls.
What you ship
Two things, and it is worth being clear that they are separate:
- A client bundle — a directory with
manifest.jsonat its root, which renders the game and collects input. - A server rules module — an ordinary Nakama match handler that decides what actually happened.
| Requirement | Limit |
|---|---|
| Total bundle size | 10 MB, measured expanded — compressing harder will not help |
| Thumbnail | Exactly 512×512, PNG or WebP |
| Time to interactive on a mid-range phone | 30 seconds |
| Network access at runtime | None — the sandbox blocks it |
| Storage access | None — the sandbox blocks it |
| Server rules | Required, and they must certify as deterministic |
The rules run on our infrastructure, not the player's device. That is what makes results trustworthy, and why they must be provably deterministic before they can decide anything.
Your frame is the whole screen
The platform runs a game full screen from the moment it loads until it ends. Your bundle gets a frame the size of the device, and the platform's own chrome — the title, the stake, the status line — sits in a bar above that frame rather than over it. Nothing the platform draws will ever cover what you draw.
Two things follow, and both bit the first-party games before they were fixed:
Centre down the screen, not just across it. A phone frame is tall. A layout that packs to the top leaves the bottom half empty, and the HUD — where almost every game puts its score and its clock — ends up at the very top edge.
body {
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
/* `safe` falls back to flex-start when the content is taller than the frame,
so a short landscape window cannot push your HUD out of reach. Declare the
plain value first for browsers that do not know `safe`. */
justify-content: safe center;
}
A grid layout gets the same from place-content: center.
Size the board from the height as well as the width. Sizing from width alone is fine until the frame is short — a phone held sideways, or any desktop window — and then the board is taller than the space and your HUD is the part that goes.
:root {
/* One board width, referenced by the board and by anything meant to line up
with it. The third term is what the height allows, through the aspect
ratio; the reserve covers your padding, gaps, HUD and any hint line. */
--board: min(94vw, 26rem, (100dvh - 7rem) * 376 / 502);
}
#wrap { width: var(--board); aspect-ratio: 376 / 502; }
.hud { width: var(--board); }
100dvh, not 100vh: mobile browser chrome collapses as the page scrolls and vh is the
taller of the two measurements, so a board sized to it is cut off until the address bar
hides.
Do not call the Fullscreen API. The platform has already given you the screen, that API needs a user gesture it cannot get on load, and it does not exist on iPhone Safari.
The three rules
Certification enforces all of them, so a mistake here is a rejected upload rather than a production incident.
1. Use the SDK's random generator. Never Math.random(), never your engine's. Five
players in a pool must face the identical puzzle, and the server must be able to rebuild
it from the same seed.
2. Drive logic from ticks, never elapsed time. No delta, dt, Date.now() or
performance.now() in anything that changes the game state. Rendering may use frame time
freely — the distinction is between what the player sees and what the game decides.
2a. But pace the loop with createLoop, not with requestAnimationFrame. A clock must
not decide what happens on a tick; it must decide how many ticks have happened. One
tick per animation frame ties your game's speed to the player's refresh rate — 144Hz plays
two and a half times faster than 60Hz. Every game here shipped with that bug.
3. Make your score an integer. Floats can differ in the last bit between devices, and the comparison against the server's replay is exact.
Each is explained, with the failure it prevents, in determinism.
Game categories
| Category | How it plays | Who decides the result |
|---|---|---|
async-pool |
Solo against a seeded puzzle; players pooled | Server replays your input log |
realtime-authoritative |
Live, turn-based, server holds the state | Server, entirely |
realtime-simulated |
Live with client-side physics | Both clients simulate; server compares |
Start with async-pool. It has the fewest moving parts and the clearest contract.
Before you upload
pnpm certify path/to/your-bundle # structure, size, forbidden APIs
pnpm test # determinism — the check that matters
Run the determinism check against your built bundle, not only your source. A build step can introduce non-determinism the source does not have.
Publishing
Upload through the developer portal, test on staging with play money, then request review. A platform operator plays your game and reads your source before it can take real stakes.
Once an operator publishes it, your game appears in the player catalogue immediately — there is no deploy to wait for.
Full detail: the publishing lifecycle.
A worked example
games/hello-blitz in this repository is a complete, certified game — manifest, harness,
deterministic rules, and tests. It is small enough to read in one sitting, and copying its
shape is faster than starting from scratch.
Note in particular that it blocks re-tapping the same tile. Without that, replaying one input a thousand times would score a thousand times — the kind of farming bug review exists to catch, and worth designing out from the start.
Related
- Trust model — what your game may and may not decide
- Writing the server rules — the half that decides outcomes
- The games, as a course — every first-party game, in the order they teach
- Pitfalls, and how they were fixed — what went wrong building them, in symptom order
- Making a game feel good — movement, feedback and sound, and the traps in each
- Determinism — proving your rules are reproducible
- Publishing lifecycle — review, updates, revenue
- Building a client — for a front end, not a game
- Server API reference — every RPC, and who may call it
- Engine guides: Excalibur.js · Defold · GDevelop
