Adding a new game

Every game on this site is a configuration object passed to the shared engine in assets/js/engine.js. You do not write game code to add a game - you describe one.

1. Add an entry to _src/games_data.py

{
  "slug": "my-new-game",
  "title": "My New Game",
  "h1": "My New Game - Play Free Online",
  "meta_title": "My New Game - Play Free Online | Brick Breaker Games",
  "meta_desc": "150 characters describing what makes this game different.",
  "short": "One line for the game card.",
  "description": "Two lines for JSON-LD and the data file.",
  "category": "Classic",                       # display label
  "categories": ["classic", "skill"],          # filter chips, lowercase
  "difficulty": "Medium",                      # Easy | Medium | Hard | Expert
  "controls": "Keyboard, mouse and touch",
  "control_keys": ["keyboard", "mouse", "touch"],
  "hook": "Short badge line",
  "objective": "What the player is trying to do.",
  "what_makes_it_different": "A paragraph. Be specific - this is the anti-clone section.",
  "tips": [ ... 5 tips ... ],
  "faq": [ (question, answer), ... 4 items ... ],
  "related_games": ["slug", "slug", "slug"],
  "related_articles": ["slug", "slug", "slug"],
  "primary_category": "brick-breaker-games",   # one of the six category slugs
  "engine": { ... see below ... }
}

2. Engine configuration reference

KeyTypeMeaning
modestringLabel only: levels, endless, timed, puzzle, survival
livesintLives. Use 0 with shots for puzzle modes
shotsintTotal ball budget for the run; replaces lives
aimboolShow a sweeping aim line and launch along it
ballSpeednumberStarting speed in logical units per second (260-380 is the useful range)
speedPerLevelnumberAdded to base speed each level
accelPerHitnumberSpeed added on every paddle return
maxBallSpeed / minBallSpeednumberHard limits
paddleWidth / paddleSpeednumber92-116 and 520-660 are the tuned ranges
ballRadiusnumber6-8
powerUpsarrayAny of widen, shrink, multi, slow, fast, fire, life, score2, magnet, shield
powerUpChance0-1Drop probability per destroyed brick
powerUpDurationsecondsTimed effects, default 10
timeLimit / timeRefill / timeBonusnumber/bool/numberCountdown seconds, refill each level, points per leftover second
descendEverysecondsPush the whole field down one row on this interval
endlessboolCycle the level list forever instead of ending
comboScoringboolMultiplier up to 10x, reset on paddle contact
levelBonus / livesBonusnumberEnd-of-level points
seedintFixes randomised layouts so puzzles are repeatable
themeobjectbricks[], bg, wall, paddle, ball, plus optional glow, round, flat, grid, unbreakable, explosive, bonus, mystery, boss, aim
levelsarraySee below

3. Level objects

{
  "name": "The Tunnel",
  "pattern": "tunnel",     # rows pyramid diamond checker fortress tunnel
                           # zigzag waves columns scatter arch
  "rows": 6, "cols": 11,
  "mix": "nnnnsp",         # weighted string: one character per weight unit
  "brickHeight": 22,       # optional
  "top": 26,               # optional offset below the top wall
  "moving": true,          # optional: slide selected rows horizontally
  "movingEvery": 2, "movingAmp": 30, "movingSpeed": 0.7
}

Brick type characters:

CharBrickHitsPoints
nNormal110
sStrong225
tReinforced345
uUnbreakable-0
bBonus160
pPower (always drops)115
xExplosive130
mMystery235
BBoss12400
.Empty--

mix can also be an object: {"rows": ["s","n","u"], "edge": "u", "core": "x", "fallback": "n"}. edge and core only apply to the fortress pattern.

4. Make it genuinely different

The rule for this site: a new game must differ in a rule, not only in colours. Before adding one, write the "what makes it different" paragraph first. If you cannot write a specific, honest paragraph, the game is a reskin and should not be published.

Levers that produce real differences: failure system (lives / ball budget / timer / descent), scoring rule (flat / combo / time bonus), power-up pool including harmful capsules, brick mix (unbreakable and explosive change level shape most), acceleration, and aim-based launching.

5. Rebuild

py _src/build.py

This creates games/<slug>/index.html, generates the featured image, adds the game to data/games.json and the search index, adds it to the directory, the XML sitemap and the HTML sitemap.

6. Test before publishing

7. The automated smoke test

Several of those checks are automated. py _src/build.py --smoke writes _smoke.html to the project root with every game's real configuration injected. Opening it drives each game headlessly for five minutes of simulated time with a tracking paddle, then reports per game whether the engine constructed, whether bricks were destroyed, how many levels cleared, whether the ball ever left the playfield, and whether any JavaScript error was thrown.

# terminal 1
py _m http.server 8000

# terminal 2 - any Chromium browser works
chrome --headless=new --virtual-time-budget=40000 --dump-dom http://localhost:8000/_smoke.html

Or simply open http://localhost:8000/_smoke.html in a normal browser and read the output. A healthy line looks like:

arcade-breakout: PASS frames=18000 state=running score=1780 levelClears=0
                 bricksStart=65 bricksLeft=2 escaped=0
What it catches that manual play does not. The most valuable failure it reports is FAIL(no score) with bricks remaining - that means the ball physically cannot reach part of the level. This is exactly how the sealed-core bug in the original fortress pattern was found: an unbreakable perimeter with no gate made every brick inside it unreachable, and the level could never be completed. The pattern generator now always leaves a two-column gate in the bottom wall.

_smoke.html is disallowed in robots.txt and does not need to be uploaded. It is regenerated whenever you pass --smoke, so it always tests the current configurations rather than a stale copy.