Selected work · 11 / 11Game engineering · JavaScript

PolyFit: a Deterministic Falling‑Blocks Puzzle in Plain JavaScript

The game of this website, built from scratch with no server behind it. A deterministic engine turns every game into a short link: whoever opens it plays the same pieces against my game, replayed next to theirs, and their own browser checks the score by playing the game again.

My record game, replayed live from its link. Click to restart.
0servers: links and leaderboards live in the browser
62automated tests, every rule checked at every tick
1,362characters: a whole three-minute game as a link
1 msto replay three minutes and check the score
27,638points: my record, waiting to be beaten
01 / The idea

A game with no server behind it.

This website is static: a set of files, with no database and no code running on a server. I wanted a game on it that people would actually compete on: three minutes on the clock, one score, and my record to beat. Competition normally needs a server that stores the scores and checks them. PolyFit gets the same result in the browser, from a single property of its engine.

The genre is familiar on purpose, so that anyone who has played a falling‑blocks puzzle feels at home at once. The interest is in how it is made: plain JavaScript modules loaded directly by the browser, drawing on a canvas, sounds and music synthesized on the spot, and no frameworks, no libraries and no build step.

PolyFit in a desktop browser: the hold box, the score of 20,824 at second 146 of 180 and the Statistics panel on the left, the board with a stack of pieces and a falling Z with its ghost in the middle, the next five pieces and the scoring table on the right
Second 146 of my record game: hold, score and Statistics on the left, the board with the falling Z and its ghost, the next five pieces and the scoring table on the right.
02 / The rules

Modern rules, no surprises.

  • The 7‑bag. The seven pieces are shuffled in a bag and dealt one by one, then a new bag starts. Two copies of a piece are never more than 12 pieces apart, and after n pieces each one has been dealt ⌊n/7⌋ or ⌈n/7⌉ times: the Statistics panel never shows two counts more than one apart.
  • Turns with wall kicks. A turn tries the rotated piece where it is, then four offsets of the Super Rotation System, and keeps the first one that fits.
  • Hold, preview and ghost. One hold per piece, the next five pieces in view, and a dashed outline where the falling piece will land.
  • Lock delay. Half a second on the ground, restarted by a move or a turn up to 15 times; reaching a new lowest row gives the 15 resets back.
  • Gravity. Fifteen levels, one every 10 lines, on the curve t(ℓ) = (0.8 − 0.007(ℓ − 1))ℓ − 1 seconds per row: one row per second at level 1, more than two rows per tick at level 15.
MovePointsNotes
Single, Double, Triple100 · 300 · 500× level
PolyFit! (four lines)800× level; only the I piece can do it
T‑spin, 0 to 3 lines400 to 1,600× level; Mini 100 to 400
Back‑to‑back× 1.5a PolyFit! or a T‑spin with lines, after another with no plain clear in between
Combo50 × combo× level, for pieces in a row that clear lines
Perfect clear800 to 3,200× level, when the board is left empty
Soft and hard drop1 · 2 per rownot multiplied

T‑spins follow the 3‑corner rule: after a turn, at least three of the four cells diagonal to the center of the T are filled. Both corners on the side it points to make a full T‑spin; otherwise it is a Mini.

03 / Determinism

Same seed, same inputs, same game.

A game is fully determined by two things: the seed of its random generator and the list of its inputs, each one a tick, a button, and pressed or released. The rules run in fixed steps of 1/60 of a second, whatever the refresh rate of the screen. Gravity, the lock delay and the key repeat are whole numbers, never floating point; time exists only as a count of ticks; every random choice comes from Mulberry32, a seeded 32‑bit generator. So replay(seed, log) plays a game again and reaches the same board, score and statistics, to the last point.

game.js · replay()
export function replay(seed, log, seconds = GAME_SECONDS) {  const game = new Game({ seed, seconds });  game.start();  let i = 0;  while (game.state === 'playing') {    while (i < log.length && log[i][0] === game.ticks) {      const [, button, down] = log[i++];      if (down) game.press(button);      else game.release(button);    }    game.tick();    game.events.length = 0;  }  return game;}
The whole replay: the same engine, with the same inputs at the same ticks. The tests check it on full games, and one changed input gives another game.

The engine imports nothing outside its folder and never touches the page, so the same code runs in the game, in the tests and in the browser of anyone who opens a challenge link. The demo at the top of this page uses it too: it decodes the link of my record game and replays it, live.

05 / Racing a replay

Racing my game, tick by tick.

Whoever opens a link plays the same pieces, in the same order, while my game is replayed in a small panel next to the board: at every frame, the replayer advances the recorded game to the player's tick. Under the score, a split time shows the gap at that very moment, like the intermediate times of a race. Without a challenge, the same mechanism races the player's own best game, and Watch plays any recorded game on the board at one, two or four times the speed.

A race against my record game in the dark theme, 18 seconds before the end: the player's board in the middle, and on the left my game replayed in a small panel on the same pieces, with the gap of the moment under the score
A race against my record game, 18 seconds before the end: on the left my game, replayed on the same pieces; under the score, the gap at that moment.
06 / Leaderboards

Scores nobody can make up.

  • Top 10 on this device. Like the high-score table of an arcade cabinet: the best ten games of the browser, with names, each stored with its game so that it can be watched again.
  • Hall of Fame. The players who beat my record and send me their link. The links are part of the source code, and every visitor's browser replays them when the page opens, waiting whenever a game is running, and sorts them by the replayed score.
  • What a replay proves. That a score is exactly what its inputs produce under the rules. It cannot prove that a human played: the automatic player of the tests scores up to 307,192 points in three minutes, more than eleven times my record. That is why the Hall of Fame is curated by hand.
07 / The interface

Keyboard, touch, every screen.

On a desktop the board is as tall as the window allows, with the panels on both sides; on a phone the numbers become a strip above it and the Statistics a strip below the touch buttons. The keyboard is read by physical key, so the controls stay in place on every layout. On touch screens the board itself is the controller: a drag moves the piece, a tap turns it, a flick down drops it and a flick up holds it, told apart by distance, duration and speed. A mark inside every piece tells them apart without color, the game respects a request for less motion, a pause hides the board, and leaving the tab pauses at once: the clock never runs unseen.

PolyFit on a phone: the score, time, level and lines in a strip at the top, the board on the left with hold and next pieces in a narrow column on its right, and three touch buttons below
On a phone, at the same moment of the game.
The results screen of my record game: 27,638 points, the main numbers of the game, the points minute by minute, where the points came from, the seven pieces with 17 or 18 of each and two remarks
“Your game, analyzed”: the points minute by minute, where they came from and the pieces of the game.
08 / Sound and music

Music without audio files.

Every sound is made when it plays, with the Web Audio API: short notes on a pentatonic scale, so that sounds which overlap never clash. The music is Korobeiniki, a Russian folk song of 1861 in the public domain, in an 8‑bit arrangement with a second part of my own: 24 bars at 150 beats per minute, on four voices. A pulse wave carries the melody, a quieter one follows it a third below, a triangle wave jumps between the root of each chord and its octave, and the drums are a falling triangle wave for the kick and filtered bursts of noise from a 15‑bit shift register, the way the old sound chips made it.

Piano roll of the first eight bars of the music: the melody in dark blue, the second voice a third below in light blue, the bass jumping by octaves in orange, and the kick, snare and hi-hat below
The first eight bars of the score: melody, second voice, bass and drums. From the report.

The notes are scheduled on the audio clock about 0.2 seconds ahead, so a late frame never makes the music stutter. It plays only while the clock of the game runs, stops at a pause and resumes where it was, and in the last 30 seconds it plays 15% faster. Measured in the browser, the sound effects stand about 6.8 dB above the music, so they always come through.

09 / Tests and results

62 tests and a record to beat.

The tests run in the browser with a harness of 49 lines written for the project, on the very modules of the game. The long ones do not check a final result only: after every single tick of ten games played by an automatic player and three games of random key mashing, they check that no full row is left, the falling piece never overlaps the stack, the score equals the sum of its parts and never goes down, the level matches the lines, and every piece is counted once by the Statistics. The automatic player scores the board with six features introduced by Pierre Dellacherie, weighted as published by Islam El‑Ashi, and plays through the real inputs, exactly like a human.

Test fileTestsWhat they check
engine13generator, bag, rotations and kicks, line clears, gravity
game20spawning, key repeat, drops, lock delay, hold, T‑spins, the end
scoring10every row of the points table, chains and combos
share11links, fingerprints, damaged links, the replayer, the top 10
music4the score, the bars, the second voice
stress4full games with every rule at every tick, replays identical

My record game, 27,638 points, is public as a link. In 180 seconds it placed 121 pieces, 0.67 per second, with 831 inputs and six PolyFit!s. The level multiplies every clear, so the score grew faster as the game went on: 3,530 points in the first minute, 11,796 in the second and 12,312 in the third, where a single PolyFit! at level 4 was worth 3,200 points.

The score of my record game over its 180 seconds: 3,530 points in the first minute, 11,796 in the second and 12,312 in the third, up to 27,638, with the six PolyFit!s marked
My record game over time: the steps are line clears, the orange dots the six PolyFit!s. From the report.

The page of the game is 31 files, 183 KB, about 64 KB compressed. A whole three-minute game replays in 1.0 ms, the median of 20 replays of the record game in Chrome on an Apple M5 Max: a Hall of Fame of a hundred games replays in about a tenth of a second.

Three minutes. One score.

The game, its 62 tests, the music and the 19‑page report are on GitHub, and my record is still waiting.

Next case study · 01 / 11High-Performance Mehrotra Lasso Solver