Install · Emulators · Devices · Verification · Building · Development · Design
Development guide
How the code is organised, how to write a new game, and how to test it.
Architecture
games/NNN-slug/src/<pkg>/<Canvas>.java the game: a subclass of gamekit.Game
games/NNN-slug/src/<pkg>/<Name>MIDlet.java generated by tools/scaffold.py
games/NNN-slug/game.properties metadata: the single source of truth for docs and the website
common/src/gamekit/ shared framework compiled into every JAR
emu/core/ MIDP subset used by the tests and the browser demos
Each JAR contains only its own game package plus gamekit, so every game builds and installs on its own.
gamekit
| Class | Purpose |
|---|---|
Game | Abstract full-screen Canvas with the game loop (tickMs = 50 ms, 20 updates per second), title menu, help pages, pause menu, game-over screen, input latching, best-score persistence and crash capture. |
GameMIDlet | Base MIDlet; the generated subclass just creates the game canvas. |
Gfx | Fonts (SMALL, SMALL_B, MEDIUM, LARGE), anchors, panels, bevels, bars, text wrapping, discs/rings, colour mixing and shading, palette sprites, time() formatting. |
Sfx | Short tones through Manager.playTone with a global on/off switch. |
Rnd | Small fast PRNG: nextInt, range, chance, shuffle. |
FMath | Integer maths: 256-unit angles, sin/cos scaled by 1024, atan2, sqrt, dist, clamp, sign, bits. |
TileMap, Body | Tile collision for platformers. |
Cards | Card faces and pips drawn at any scale. |
The Game lifecycle
Game owns the states TITLE, HELP, PLAY, PAUSE and OVER. A game implements:
protected String name(); // title shown on the title screen
protected String[] help(); // help pages; a line starting "- " is a heading
protected void newGame(); // reset state for a new round (mode is already set)
protected void update(); // one 50 ms tick of game logic
protected void draw(Graphics g); // draw the play screen (W x H)
Optional hooks: modes() (difficulty or variant names, picked on the title menu), drawTitleArt(), accent() (theme colour), hasScore(), lowerIsBetter(), formatScore(), footer(), pauseKeys().
Inside update() read input from the bit sets held, pressed (pressed since the last tick) and tapped:
| Bit | Keys |
|---|---|
K_UP, K_DOWN, K_LEFT, K_RIGHT | joystick, or 2 / 8 / 4 / 6 |
K_FIRE | joystick centre, or 5 |
K_STAR, K_POUND, K_SOFT | *, #, soft keys |
K_NUM0 << n | digit n (digit(n), digitPressed()) |
Note that 2/4/5/6/8 set both a direction bit and a digit bit. Games that use digits as commands should check digitPressed() before the direction bits, or avoid 2/4/6/8 for commands.
End a round with endGame(won) after setting score and optionally headline. The framework shows the result, stores a best score in RMS and offers again or menu.
Rules for phone-safe code
- Only CLDC 1.0 and MIDP 2.0 APIs. The build enforces this by compiling against the API stubs.
- No
floatordouble. Use fixed point (positions asx << 8) andFMath.tools/validate.pyrejects floating-point constants in a JAR. - Allocate in
newGame(), not inupdate()/draw(): old phones have small heaps and slow garbage collectors. - Lay everything out from
W,Hand font heights. Never hard-code a screen size. - Keep the JAR small: no image files beyond the generated icon; draw graphics with primitives or palette sprites (
Gfx.sprite).
Adding a game
- Pick the next id and a slug, for example
101-hex-hop. - Create
games/101-hex-hop/game.properties:game.id=101 game.slug=hex-hop game.name=Hex Hop game.jar=HexHop game.midlet=hexhop.HexHopMIDlet game.canvas=hexhop.HexGame game.category=Puzzle game.version=1.0.0 game.summary=One line for tables and cards. game.description=A paragraph for the README and the website. game.objective=What winning means. game.controls=2/4/6/8:Move|5:Jump|*:Pause menu game.modes=Easy|Hard game.tags=puzzle,hexagons game.featured=false game.demo.script=w500 shot:title 5 w300 gif:start rand:2500 gif:stop:demo shot:play - Write
games/101-hex-hop/src/hexhop/HexGame.javaextendinggamekit.Game. - Generate the MIDlet and build file, then build and test:
python tools/scaffold.py 101 python tools/build_all.py 101 python tools/run_tests.py 101 python tools/microemu_check.py 101 - Look at the screenshots in
games/101-hex-hop/build/test/<size>/for every size. python tools/build_all.py --target media 101, thenpython tools/gen_docs.pyandpython tools/validate.py.
Test scripts
game.demo.script (media) and the optional game.test.script (tests) drive the harness:
| Token | Meaning |
|---|---|
0-9, *, # | tap a key |
U D L R F | joystick up/down/left/right/fire |
SL SR | soft keys |
wNNN | wait NNN ms |
hK:NNN | hold key K for NNN ms |
rand:NNN | random keypad input for NNN ms |
shot:name | save a screenshot |
gif:start / gif:stop:name | record an animated GIF |
default | the standard test sequence |
Browser demos
emu/web implements the runtime's backend on top of window.j2me (see website/src/assets/js/j2me-runtime.js). The web Ant target compiles the game, gamekit, emu/core and emu/web with javac, then TeaVM turns the classes into one game.js. The same game code runs in the browser and on phones; only the MIDP implementation underneath differs, which is why the website labels these as Browser Demo and links to the original Java ME files.
Releasing
tools/release.py creates one GitHub Release per game (game-NNN-v1.0.0) with the JAR and JAD attached, and the milestone releases with the all-games ZIP. It needs the GitHub CLI (gh) logged in with permission to the repository. Re-running it skips releases that already exist.
Source: docs/development.md