← All documentation

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:

  1. A client bundle — a directory with manifest.json at its root, which renders the game and collects input.
  2. 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