Skip to content
marabunta-labsPublic

Repository files navigation

TYPOTRIS

A Tetris board where the gaps left by the pieces spell your message.

▶ Try it live

Every character is precomputed as a self-contained unit — a fixed list of drops that stands on a whole divider row and leaves a whole divider row behind — so a message is assembled by stacking them. Nothing is searched at runtime: typing HI WORLD renders in under 10 ms.

Typotris building "HI WORLD"

Typotris app

How it works

Line Clears mode (default)

Each letter is built one row at a time inside a four-row band. Three of the four rows are throwaway fillers; the one being kept is the row of the picture. A vertical I piece closes the band by filling an open column that runs through all four rows, completing the three fillers at once. They vanish (that's the line clear), and the kept row drops into place — with its gaps intact, hanging over empty space, which no gravity-only build could ever produce.

The recipe for every character was solved once offline (scripts/gen-recipes.ts, about 40 seconds) and stored in a 21 kB table. At runtime the table is read, the moves are offset to the right height, and the physics verifies each drop against the real board.

Stencil mode

Pure gravity, no lines ever cleared. Enclosed counters (the inside of an O, the triangle in an A) are tied to the outside of the letter with a one-cell bridge — like stencil lettering on crates.

Features

Feature What it does
5 themes ARCADE (seven colours), WHITE, NEON, AMBER, PAPER. Uniform-colour themes carry a cell outline so you can still see individual pieces.
Spinning pieces Each piece spawns a few rotations short of its landing one and turns on the way down. Only the landing rotation touches the board.
Next-piece queue Five upcoming pieces in the side panel.
Simulated score Points per line (100/300/500/800 × level), combo bonus, level from lines cleared.
Vertical / Horizontal One tall board, or one board per character side by side.
Top down / Bottom up Vertical only. Reading upward is the same picture with the characters in the other order.
One by one / All at once Horizontal only. Playing all boards together takes as long as the slowest letter instead of the sum.
Loop When the message finishes it flashes, wipes (downward, upward, or flash-and-gone, alternating), and starts over.
Replay Restart from an empty board without changing anything else.
Wipe transitions Changing the message, mode or layout clears the old picture before the new one begins.
PNG export The board as it looks right now.
GIF export The whole build as an animated GIF: pieces falling, lines flashing, your colours, your layout. Rebuilt offscreen, not screen-recorded. Typically 400–700 kB, under a second to produce.
Demo on load The page cycles through a showcase of ornamental messages until you type something. The speed slider works on the demo too.
Ornamental glyphs Arrows (↑↓←→), a 2×2 checkerboard (▚) and a heart (♥). The checkerboard is the showpiece: every block it leaves is fenced in by gaps on all four sides.
Responsive layout Controls on the left, board on the right from lg up. The controls stick to the viewport. On mobile they stack with the input first.
Title + footer TYPOTRIS at the top, Marabunta Labs 2026 with links at the bottom.

Getting started

npm install
npm run dev

Open http://localhost:3000.

Scripts

Script What it does
npm run dev Development server (Next.js + Turbopack).
npm run build Production build.
npm run recipes Regenerate the per-character recipe table (~40 s).
npm run recipes:check Replay every character through the physics and compare cell by cell.
npm run gif:check Encode a set of test GIFs and decode them back, pixel for pixel.
npm run player:check Verify the player, the loop, the parallel stage and the GIF export.
npm run check Lint + all three check scripts.

Project structure

src/
  app/
    page.tsx            UI: controls, layout, showcase, footer
    globals.css         Themed animated background, grid, scanlines
  components/
    TetrisGrid.tsx      Canvas, animation loop, imperative handle, solve logic
    SidePanel.tsx       Next-piece queue and score
  lib/
    pixel-font.ts       9×8 font data (letters, digits, punctuation, ornaments)
    unit-layout.ts      Geometry of a stackable unit: rows, parity, notches
    builder.ts          Depth-first tiling search (shared by both modes)
    builder-unit.ts     Offline: one unit's recipe, row by row with line clears
    tetris-board.ts     Core physics: drop, place, clear, profile
    tetris-pieces.ts    The seven tetrominoes
    tetris-solver.ts    Entry point: solve(text, mode) → moves + target
    recipes.ts          Runtime: stack precomputed units, verify each drop
    recipes.generated.ts  The table (generated, do not edit)
    recipe-codec.ts     4-char-per-drop encoding
    player.ts           Player, Stage, layout, piece geometry
    stage-draw.ts       Paints a Stage to a canvas
    gif.ts              GIF89a + LZW encoder (hand-written, no dependencies)
    gif-export.ts       Offscreen replay → GIF frames
    themes.ts           Five palettes
scripts/
    gen-recipes.ts      Generate the recipe table
    check-recipes.ts    Verify recipes against the physics
    check-gif.ts        GIF round-trip (encode → decode → compare)
    check-player.ts     Player, stage, loop, parallel, upward-reading, GIF
    gif-decode.ts       GIF reader (for the check scripts only)

How the arithmetic works

A unit has to use a whole number of pieces (4 cells each) and cleared rows (10 cells each). The cell count of a unit is glyph + 9·separators − notches, and 10 ≡ 2 (mod 4), so the clears always contribute an even number. That forces the unit's own count to be even. Odd glyphs (30 of the 48) get an extra separator row to flip the parity. The remaining freedom — how many rows to build by gravity vs. by a clearing band — is a single bit, pinned by the arithmetic, and directCandidates in unit-layout.ts offers both options.

Credits

Made by Marabunta Labs · 2026

GitHub

Releases

Packages

Contributors

Languages