---
description: "Classic Breakout arcade game with paddle physics, brick collision, and scoring. Intermediate HTML5 Canvas tutorial."
---

# Canvas Breakout Clone

> **Difficulty:** Intermediate | **Runtime:** HTML5 Canvas | **Output:** Playable game

Teach an AI agent to build a classic Breakout arcade game with paddle controls, ball physics, brick collision detection, scoring, and game-over handling.

## What You'll Build

A self-contained HTML file implementing a fully playable Breakout clone:
- Rows of colored bricks at the top of the screen
- A paddle controlled by mouse movement
- A ball that bounces off walls, paddle, and bricks
- Score tracking and game-over state
- Restart capability

## Core Concepts

### 1. Game State

```javascript
{
  ball: { x, y, dx, dy, radius },
  paddle: { x, y, width, height },
  bricks: [{ x, y, width, height, color, alive }],
  score: number,
  lives: number,
  running: boolean
}
```

### 2. Game Loop Architecture

```
Input → Update Physics → Detect Collisions → Render → Repeat
```

Each frame at 60fps:
1. Read mouse position for paddle
2. Move ball by `(dx, dy)`
3. Check wall bounces (reflect dx or dy)
4. Check paddle collision (reflect dy, adjust dx by hit position)
5. Check brick collisions (destroy brick, reflect ball, add score)
6. Check ball-below-paddle (lose life)
7. Clear and redraw everything

### 3. Brick Layout

Bricks arranged in a grid:
- **Columns:** 10 across, with gaps
- **Rows:** 5 deep, each row a different color
- **Colors:** Red, Orange, Yellow, Green, Cyan (top to bottom)

### 4. Collision Detection

For brick collisions, use AABB (Axis-Aligned Bounding Box):
```
ball overlaps brick if:
  ball.x + radius > brick.x AND
  ball.x - radius < brick.x + brick.width AND
  ball.y + radius > brick.y AND
  ball.y - radius < brick.y + brick.height
```

## Instructions

1. Set up a fixed-size canvas (800x600)
2. Create the brick grid with colors
3. Implement paddle that follows mouse X position
4. Add ball with initial diagonal velocity
5. Handle wall bounces, paddle bounces, and brick collisions
6. Track score (10 points per brick) and display it
7. Detect game over (ball falls below paddle)
8. Add click-to-restart functionality

## Parameters

| Parameter | Default | Description |
|-----------|---------|-------------|
| `BRICK_ROWS` | 5 | Number of brick rows |
| `BRICK_COLS` | 10 | Number of brick columns |
| `BALL_SPEED` | 5 | Initial ball speed |
| `PADDLE_WIDTH` | 100 | Paddle width in pixels |

---

## Bundled Files

### `breakout.html`

```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Canvas Breakout Clone</title>
<style>
  * { margin: 0; padding: 0; }
  body { background: #111; display: flex; justify-content: center; align-items: center; height: 100vh; }
  canvas { border: 2px solid #333; cursor: none; }
</style>
</head>
<body>
<canvas id="c" width="800" height="600"></canvas>
<script>
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
const W = 800, H = 600;

const BRICK_ROWS = 5, BRICK_COLS = 10;
const BRICK_W = 70, BRICK_H = 20, BRICK_PAD = 6, BRICK_TOP = 50;
const BRICK_LEFT = (W - (BRICK_COLS * (BRICK_W + BRICK_PAD) - BRICK_PAD)) / 2;
const ROW_COLORS = ['#ef4444', '#f97316', '#eab308', '#22c55e', '#06b6d4'];

const PADDLE_W = 100, PADDLE_H = 12;
const BALL_R = 6, BALL_SPEED = 5;

let ball, paddle, bricks, score, lives, running, gameOver;

function init() {
  ball = { x: W / 2, y: H - 60, dx: BALL_SPEED * 0.7, dy: -BALL_SPEED * 0.7 };
  paddle = { x: W / 2 - PADDLE_W / 2, y: H - 30 };
  bricks = [];
  for (let r = 0; r < BRICK_ROWS; r++) {
    for (let c = 0; c < BRICK_COLS; c++) {
      bricks.push({
        x: BRICK_LEFT + c * (BRICK_W + BRICK_PAD),
        y: BRICK_TOP + r * (BRICK_H + BRICK_PAD),
        w: BRICK_W, h: BRICK_H,
        color: ROW_COLORS[r],
        alive: true,
      });
    }
  }
  score = 0;
  lives = 3;
  running = true;
  gameOver = false;
}

canvas.addEventListener('mousemove', (e) => {
  const rect = canvas.getBoundingClientRect();
  paddle.x = Math.max(0, Math.min(W - PADDLE_W, e.clientX - rect.left - PADDLE_W / 2));
});

canvas.addEventListener('click', () => {
  if (gameOver) { init(); }
});

function update() {
  if (!running) return;
  ball.x += ball.dx;
  ball.y += ball.dy;

  // Wall bounces
  if (ball.x - BALL_R < 0 || ball.x + BALL_R > W) ball.dx = -ball.dx;
  if (ball.y - BALL_R < 0) ball.dy = -ball.dy;

  // Paddle collision
  if (
    ball.dy > 0 &&
    ball.y + BALL_R >= paddle.y &&
    ball.y + BALL_R <= paddle.y + PADDLE_H + 4 &&
    ball.x >= paddle.x &&
    ball.x <= paddle.x + PADDLE_W
  ) {
    ball.dy = -Math.abs(ball.dy);
    const hit = (ball.x - (paddle.x + PADDLE_W / 2)) / (PADDLE_W / 2);
    ball.dx = hit * BALL_SPEED;
  }

  // Brick collision
  for (const b of bricks) {
    if (!b.alive) continue;
    if (
      ball.x + BALL_R > b.x && ball.x - BALL_R < b.x + b.w &&
      ball.y + BALL_R > b.y && ball.y - BALL_R < b.y + b.h
    ) {
      b.alive = false;
      ball.dy = -ball.dy;
      score += 10;
      break;
    }
  }

  // Ball below paddle
  if (ball.y > H + 20) {
    lives--;
    if (lives <= 0) {
      running = false;
      gameOver = true;
    } else {
      ball.x = W / 2;
      ball.y = H - 60;
      ball.dx = BALL_SPEED * 0.7;
      ball.dy = -BALL_SPEED * 0.7;
    }
  }

  // Win condition
  if (bricks.every((b) => !b.alive)) {
    running = false;
    gameOver = true;
  }
}

function draw() {
  ctx.fillStyle = '#111';
  ctx.fillRect(0, 0, W, H);

  // Bricks
  for (const b of bricks) {
    if (!b.alive) continue;
    ctx.fillStyle = b.color;
    ctx.fillRect(b.x, b.y, b.w, b.h);
    ctx.strokeStyle = 'rgba(255,255,255,0.15)';
    ctx.strokeRect(b.x, b.y, b.w, b.h);
  }

  // Paddle
  ctx.fillStyle = '#a855f7';
  ctx.fillRect(paddle.x, paddle.y, PADDLE_W, PADDLE_H);

  // Ball
  ctx.fillStyle = '#fff';
  ctx.beginPath();
  ctx.arc(ball.x, ball.y, BALL_R, 0, Math.PI * 2);
  ctx.fill();

  // Score + Lives
  ctx.fillStyle = '#888';
  ctx.font = '14px monospace';
  ctx.fillText(`Score: ${score}  Lives: ${lives}`, 10, 20);

  if (gameOver) {
    ctx.fillStyle = 'rgba(0,0,0,0.7)';
    ctx.fillRect(0, 0, W, H);
    ctx.fillStyle = '#fff';
    ctx.font = 'bold 32px sans-serif';
    ctx.textAlign = 'center';
    const msg = bricks.every((b) => !b.alive) ? 'YOU WIN!' : 'GAME OVER';
    ctx.fillText(msg, W / 2, H / 2 - 10);
    ctx.font = '16px sans-serif';
    ctx.fillText(`Score: ${score} — Click to restart`, W / 2, H / 2 + 25);
    ctx.textAlign = 'left';
  }
}

function loop() {
  update();
  draw();
  requestAnimationFrame(loop);
}

init();
loop();
</script>
</body>
</html>
```

### `README.md`

```md
# Canvas Breakout Clone

A classic Breakout arcade game built with HTML5 Canvas.

## Playing

Open `breakout.html` in any modern browser. Move your mouse to control the paddle. Click to restart after game over.

## Rules

- Break all bricks to win
- Each brick is worth 10 points
- You have 3 lives
- Ball speeds up based on where it hits the paddle
```
