Andrej Karpathy coined the term vibe coding in early 2025 to describe a specific workflow: describe what you want in natural language, let an AI agent generate the code, review what it produced, iterate, repeat. Collins English Dictionary named it their Word of the Year for 2025. By 2026 it’s not a novelty. Airbnb reports that AI now writes around 60% of its new code.
This is a real, tested build. Every prompt is shown. Every code block was verified before publication, including the collision physics and the win and loss conditions, which ran through automated tests rather than being assumed to work. The result is a complete, playable Breakout game in a single HTML file.
Pick Your AI Coding Tool First
Before you write your first prompt, pick one assistant and stay with it. Every prompt in this guide works identically across all of them.
- Claude Code: terminal-native agent that writes and tests code directly in your project folder
- Cursor: AI-native code editor for people who want the same power inside a familiar IDE
- Replit Agent: walks through a visible plan before writing code, useful for understanding the why behind each step
- Bolt.new and v0: fastest path to a prototype if you don’t want to touch a terminal at all
To use Claude Code specifically: install it with npm install -g @anthropic-ai/claude-code, then run claude from inside an empty project folder to start a session.
Why Breakout Is the Right Project
Atari released Breakout on May 13, 1976. Nolan Bushnell and Steve Bristow conceived it as a single-player evolution of Pong. Steve Wozniak built the original hardware, working alongside Steve Jobs, with a chip count so aggressively minimized that Atari’s own manufacturing line reportedly couldn’t replicate it and had to ship a version with more components. Wozniak and Jobs founded Apple that same year, and Breakout’s hardware logic directly influenced parts of the original Apple II’s design.
The game is a good vibe coding target because it’s complex enough to need real logic but simple enough to understand end to end. A wall of bricks at the top. A ball bouncing around. A paddle at the bottom you move left and right. Ball hits brick, brick is destroyed, you score points. Ball falls past your paddle, you lose a life. Destroy every brick, you win. Run out of lives, game over.
Prerequisites: a modern browser (Chrome, Firefox, or Safari), a text editor, and one AI assistant. Zero build tools, zero frameworks, zero dependencies. The entire game is one HTML file you open in a browser.

The Build: 7 Prompts
The guide is broken into stages rather than one giant prompt. Asking for the whole game at once produces something harder to debug when it doesn’t quite work. One capability at a time keeps failures small and traceable. The full code is in this repo.
Step 1: Canvas and skeleton
Prompt: “Create a single HTML file for a Breakout game. Set up an HTML5 canvas 480 pixels wide and 400 pixels tall, centered on the page, with a dark background. Add a heading, and placeholder text elements for score and lives above the canvas. Don’t add any game logic yet, just the skeleton.”
<canvas id="gameCanvas" width="480" height="400"></canvas>
<div id="hud">
<span id="score">Score: 0</span>
<span id="lives">Lives: 3</span>
</div>The canvas element is where every visual frame gets drawn at the pixel level through JavaScript. Starting with just the skeleton, no logic, lets you confirm the page structure renders correctly before adding anything that could break. That matters more with AI-generated code because it’s easier to spot exactly where something went wrong when you’re checking one small piece at a time.
Step 2: Paddle
Prompt: “Add a paddle: an 80×10 pixel rectangle near the bottom of the canvas. Make it move left and right with the arrow keys, and keep it from moving off either edge.”
const paddle = { width: 80, height: 10, x: (canvas.width - 80) / 2,
y: canvas.height - 20, speed: 7, dx: 0 };
const keys = { left: false, right: false };
document.addEventListener("keydown", (e) => {
if (e.key === "ArrowLeft") keys.left = true;
if (e.key === "ArrowRight") keys.right = true;
});
document.addEventListener("keyup", (e) => {
if (e.key === "ArrowLeft") keys.left = false;
if (e.key === "ArrowRight") keys.right = false;
});
function updatePaddle() {
if (keys.left) paddle.dx = -paddle.speed;
else if (keys.right) paddle.dx = paddle.speed;
else paddle.dx = 0;
paddle.x += paddle.dx;
paddle.x = Math.max(0, Math.min(canvas.width - paddle.width, paddle.x));
}keydown and keyup listeners track which arrow keys are currently held, which is what makes the paddle move smoothly while a key is held rather than jumping once per keystroke. The last line of updatePaddle is the boundary check: Math.max and Math.min together clamp the paddle’s position so it can never move past either edge.
Step 3: Ball and physics
Prompt: “Add a ball: a small circle that starts near the bottom center of the canvas and moves diagonally. Make it bounce off the left, right, and top walls.”
const ball = { x: canvas.width / 2, y: canvas.height - 30,
radius: 6, speed: 4, dx: 3, dy: -3 };
function updateBall() {
ball.x += ball.dx;
ball.y += ball.dy;
if (ball.x + ball.radius > canvas.width || ball.x - ball.radius < 0) ball.dx *= -1;
if (ball.y - ball.radius < 0) ball.dy *= -1;
}dx and dy are the ball’s velocity per frame. The wall-bounce logic reverses direction by multiplying by -1 the moment the ball’s edge crosses a boundary. Using radius in the check is what stops the ball from visually clipping halfway into the wall before bouncing.
Step 4: Brick grid
Prompt: “Add a grid of bricks: 4 rows of 7 bricks each, near the top of the canvas, with small gaps between them. Each brick should track whether it’s been destroyed.”
const brickInfo = { rows: 4, cols: 7, width: 55, height: 18,
padding: 8, offsetTop: 40, offsetLeft: 20 };
let bricks = [];
function buildBricks() {
bricks = [];
for (let r = 0; r < brickInfo.rows; r++) {
for (let c = 0; c < brickInfo.cols; c++) {
bricks.push({
x: brickInfo.offsetLeft + c * (brickInfo.width + brickInfo.padding),
y: brickInfo.offsetTop + r * (brickInfo.height + brickInfo.padding),
width: brickInfo.width, height: brickInfo.height, destroyed: false,
});
}
}
}The nested loop places each brick using its row and column position multiplied by the brick’s dimension plus padding, producing an evenly spaced grid without 28 separately hand-placed rectangles. The destroyed: false flag on every brick is what Step 5’s collision logic and the win condition both depend on.
Step 5: Collision detection
This is the step worth taking seriously. A rushed vibe coding pass here produces a game that looks right but plays wrong.
Prompt: “Add collision detection: the ball should bounce off the paddle at an angle depending on where it hits (center bounces straight up, edges bounce at an angle), and destroy any brick it touches while bouncing away. A destroyed brick should never be hit again.”
function ballHitsPaddle(ball, paddle) {
return (
ball.y + ball.radius >= paddle.y &&
ball.y - ball.radius <= paddle.y + paddle.height &&
ball.x >= paddle.x && ball.x <= paddle.x + paddle.width
);
}
function paddleBounceAngle(ball, paddle) {
const hitPos = (ball.x - (paddle.x + paddle.width / 2)) / (paddle.width / 2);
const clamped = Math.max(-1, Math.min(1, hitPos));
return clamped * (Math.PI / 3); // up to 60 degrees at the paddle's edges
}
function brickCollision(ball, brick) {
if (brick.destroyed) return false;
return (
ball.x + ball.radius > brick.x && ball.x - ball.radius < brick.x + brick.width &&
ball.y + ball.radius > brick.y && ball.y - ball.radius < brick.y + brick.height
);
}paddleBounceAngle measures how far from the paddle’s center the ball landed, normalizes it to a range of -1 to 1, and converts it to an angle: a center hit bounces nearly straight up, an edge hit sends it off at up to 60 degrees. The first line of brickCollision, if (brick.destroyed) return false, is the single most important line in the file. Without it, a destroyed brick’s rectangle would still register collisions forever, making the ball behave as if invisible walls remain where bricks used to be.
Step 6: Scoring and lives
Prompt: “Wire the collision logic into the main update loop: increase the score by 10 for each brick destroyed, and lose a life when the ball falls past the paddle. Reset the ball’s position after losing a life, unless lives reach zero.”
for (const brick of bricks) {
if (brickCollision(ball, brick)) {
brick.destroyed = true;
ball.dy *= -1;
score += 10;
scoreEl.textContent = `Score: ${score}`;
break; // resolve only one brick per frame
}
}
if (ball.y - ball.radius > canvas.height) {
lives -= 1;
livesEl.textContent = `Lives: ${lives}`;
if (lives <= 0) {
gameOver = true;
} else {
resetBallAndPaddle();
}
}The break after a brick is destroyed is deliberate. Without it, if the ball overlapped two adjacent bricks in the same frame, both would be destroyed and both would flip the ball’s direction, canceling out into a visually wrong bounce. One collision per frame keeps physics predictable.
Step 7: Win and loss states
Prompt: “Add a win condition when every brick is destroyed, and stop the game loop entirely once the game is won or lost, displaying a message either way.”
if (bricks.every((b) => b.destroyed)) {
gameWon = true;
messageEl.textContent = "You Win! Refresh to play again.";
}
function loop() {
if (!gameOver && !gameWon) {
updatePaddle();
updateBall();
}
draw();
requestAnimationFrame(loop);
}Array.every() checks that every brick’s destroyed flag is true, a clean way to express “all of them” without a manual counter to keep in sync. The loop still calls draw() after the game ends but stops calling the update functions, so the final frame stays frozen on screen rather than the page becoming unresponsive.

Ship It for Free
Once it plays the way you want, getting it online costs nothing.
- GitHub Pages: push the HTML file to a repo, enable Pages in the repository settings, and it’s live at a public URL with no server to manage
- Netlify: drag and drop deployment if you’d rather skip Git entirely, same free static hosting deal
- itch.io: built specifically for indie game distribution, with a built-in audience that’s actually looking for this kind of project rather than a general web hosting crowd you have to explain the game to
The Real Skill Vibe Coding Teaches
Vibe coding didn’t remove the work of understanding what got built. It moved that work to a different place. You didn’t type out the collision math character by character, but you still had to know that a destroyed brick needs a guard clause, that a paddle needs boundary clamping, and that “it plays fine when I click around” is not the same claim as “the win and loss states are both verified correct.”
That distinction is what separates a real, working game from one that looks like it works until someone finds the bug you didn’t check for. The prompts are the easy part. Reading, questioning, and verifying what the AI hands back is the actual craft.


