Text Breakout
Classic Breakout, but the bricks are words. Smash them and watch the surviving text reflow instantly.
Every brick is a word from a paragraph, positioned using Pretext. When you destroy a word, the remaining text is re-layouted with layoutWithLines() and the bricks repack — a live demonstration of instant text relayout as a game mechanic.
prepareWithSegments()layoutWithLines() What this demonstrates
Text relayout as a game mechanic. Each word-brick is positioned using Pretext's line layout. When bricks are destroyed, surviving words are re-layouted instantly — the paragraph condenses in real time. This shows Pretext's ability to relayout cheaply and continuously.
Relevant Pretext API
prepareWithSegments()— measure all wordslayoutWithLines()— position words as lines, re-run on every brick destruction
How to play
Click to start. Move your mouse to control the paddle. Bounce the ball into word-bricks to destroy them. Watch the remaining words reflow into tighter lines as you play.
Quick start
import { prepareWithSegments, layoutWithLines, buildFont } from '@chenglou/pretext';
const font = buildFont(14, 'Inter, sans-serif');
const lineH = 22, brickMargin = 20;
const gameWidth = 800;
const maxW = gameWidth - brickMargin * 2;
// --- Step 1: Layout paragraph into lines, extract words as bricks ---
function initBricks(text) {
const prepared = prepareWithSegments(text, font);
const result = layoutWithLines(prepared, maxW, lineH);
const bricks = [];
for (let li = 0; li < result.lines.length; li++) {
const words = result.lines[li].text.trim().split(/\s+/);
let xOffset = brickMargin;
const y = brickMargin + li * (lineH + 4);
for (const word of words) {
if (!word) continue;
// Measure each word individually for precise brick width
const wp = prepareWithSegments(word, font);
const wl = layoutWithLines(wp, 9999, lineH);
const w = (wl.lines[0]?.width ?? 40) + 8;
bricks.push({ word, x: xOffset, y, w, h: lineH, alive: true });
xOffset += w + 4; // brick width + gap
}
}
return bricks;
}
let bricks = initBricks(paragraph);
// --- Step 2: Ball-brick collision detection ---
function checkCollisions(ballX, ballY, ballR) {
for (const brick of bricks) {
if (!brick.alive) continue;
if (ballX + ballR >= brick.x && ballX - ballR <= brick.x + brick.w &&
ballY + ballR >= brick.y && ballY - ballR <= brick.y + brick.h) {
brick.alive = false;
ballVY *= -1;
// --- Step 3: THE KEY INSIGHT — relayout as game mechanic ---
// Gather surviving words, rejoin, and re-layout from scratch
const survivingText = bricks
.filter(b => b.alive)
.map(b => b.word)
.join(' ');
// Pretext re-layouts the entire paragraph instantly
// Bricks repack into fewer lines as words are destroyed
bricks = initBricks(survivingText);
return; // one collision per frame
}
}
}
// Each animation frame: move ball, check collisions, re-render
// The paragraph visibly condenses as you destroy words — live relayout