flowchart TD
A[Start: Breakout Blocks Lesson] --> B[Lesson 1: Paddle and Base Blocks]
B --> B1[Make the Paddle]
B1 --> B2[Move the Paddle]
B --> C[Interactive Demos]
C --> C1[Paddle Movement]
C1 --> C2[Ball Bouncing]
C2 --> C3[Paddle + Ball]
C3 --> C4[Mini Breakout]
B --> D[Lesson 2: Power-Up Block + Timer]
D --> D1[Add Special Bricks]
D1 --> D2[Draw and Drop Power-Up]
D2 --> D3[Show Timer]
C4 --> E[Full Power-Up Demo]
D --> F[Exploration Activities]
E --> G[Complete Breakout Game]
style A fill:#ffffff,stroke:#000000,stroke-width:3px,color:#000000
style B fill:#e3f2fd,stroke:#1976d2,stroke-width:2px,color:#000000
style B1 fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px,color:#000000
style B2 fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px,color:#000000
style C fill:#e8f5e8,stroke:#388e3c,stroke-width:2px,color:#000000
style C1 fill:#f1f8e9,stroke:#66bb6a,stroke-width:2px,color:#000000
style C2 fill:#f1f8e9,stroke:#66bb6a,stroke-width:2px,color:#000000
style C3 fill:#f1f8e9,stroke:#66bb6a,stroke-width:2px,color:#000000
style C4 fill:#f1f8e9,stroke:#66bb6a,stroke-width:2px,color:#000000
style D fill:#fff3e0,stroke:#f57c00,stroke-width:2px,color:#000000
style D1 fill:#fff8e1,stroke:#ffb74d,stroke-width:2px,color:#000000
style D2 fill:#fff8e1,stroke:#ffb74d,stroke-width:2px,color:#000000
style D3 fill:#fff8e1,stroke:#ffb74d,stroke-width:2px,color:#000000
style E fill:#fce4ec,stroke:#c2185b,stroke-width:2px,color:#000000
style F fill:#f9fbe7,stroke:#827717,stroke-width:2px,color:#000000
style G fill:#ffebee,stroke:#d32f2f,stroke-width:3px,color:#000000
π Click this for full source code
Lesson 1: Paddle and Base Blocks
Goal: Learn how to move a rectangle across the canvas and understand basic drawing and input handling.
Step 1: Make the paddle
We draw a rectangle at the bottom of the canvas.
let paddleHeight = 10;
let basePaddleWidth = 75;
let paddleWidth = basePaddleWidth;
let paddleX = (canvas.width - paddleWidth) / 2;
function drawPaddle() {
ctx.beginPath();
ctx.rect(paddleX, canvas.height - paddleHeight, paddleWidth, paddleHeight);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
}
β Theory Check (after Step 1)
1) In an HTML5 Canvas, where is the origin (0,0) located?
2) Which call starts a new vector drawing path?
3) Which signature correctly defines a rectangle on Canvas?
Step 2: Move the paddle
We listen for keyboard input and update the paddleX position.
let rightPressed = false;
let leftPressed = false;
document.addEventListener("keydown", keyDownHandler);
document.addEventListener("keyup", keyUpHandler);
function keyDownHandler(e) {
if (e.key === "Right" || e.key === "ArrowRight") rightPressed = true;
else if (e.key === "Left" || e.key === "ArrowLeft") leftPressed = true;
}
function keyUpHandler(e) {
if (e.key === "Right" || e.key === "ArrowRight") rightPressed = false;
else if (e.key === "Left" || e.key === "ArrowLeft") leftPressed = false;
}
function updatePaddle() {
if (rightPressed && paddleX < canvas.width - paddleWidth) paddleX += 7;
else if (leftPressed && paddleX > 0) paddleX -= 7;
}
Explore:
- Change speed and observe responsiveness.
- Try switching keys; how does it affect UX?
β Theory Check (after Step 2)
1) What best describes event-driven programming in this context?
2) Why track both keydown and keyup?
3) The boundary checks on paddleX illustrate which concept?
Lesson 2: Power-Up Block + Timer
Goal: Use arrays/objects to model state, randomness to vary behavior, and time to manage temporary effects.
Step 1: Add special bricks that drop a power-up when broken.
- We randomly give some bricks a powerUp property:
let bricks = [];
const powerUpChance = 0.3; // 30% chance
for (let c = 0; c < brickColumnCount; c++) {
bricks[c] = [];
for (let r = 0; r < brickRowCount; r++) {
const hasPowerUp = Math.random() < powerUpChance;
bricks[c][r] = { x: 0, y: 0, status: 1, powerUp: hasPowerUp };
}
}
When a power-up brick is hit, it spawns a falling power-up:
if (b.powerUp) {
powerUps.push({ x: b.x + brickWidth/2, y: b.y, active: true });
}
β Theory Check (L2 β’ Step 1)
1) In an array like bricks[c][r], what do c and r typically represent?
2) The expression Math.random() < p models:
3) Using objects like {x, y, status, powerUp} is an example of:
Step 2: Draw and drop the power-up
- Each power-up is drawn and updated per animation frame.
let powerUps = [];
let activePowerUp = null;
let powerUpTimer = 0;
const powerUpDuration = 5000; // 5 seconds
function drawPowerUps() {
for (let p of powerUps) {
if (p.active) {
ctx.beginPath();
ctx.arc(p.x, p.y, 10, 0, Math.PI * 2);
ctx.fillStyle = "gold";
ctx.fill();
ctx.closePath();
ctx.fillStyle = "black";
ctx.font = "bold 14px Arial";
ctx.textAlign = "center";
ctx.fillText("P", p.x, p.y);
// per-frame update (basic Euler integration)
p.y += 1.5;
// basic AABB hit test against the paddle rectangle
if (p.y >= canvas.height - paddleHeight &&
p.x > paddleX && p.x < paddleX + paddleWidth) {
p.active = false;
activePowerUp = "Wide Paddle";
powerUpTimer = Date.now();
}
}
}
}
β Theory Check (L2 β’ Step 2)
1) Updating position each frame by adding a velocity is an example of:
2) Why must shapes/text be redrawn every animation frame on Canvas?
3) Checking if a point lies within a rectangle is called:
Step 3: Show timer
- Draw the time left on screen.
function drawPowerUpTimer() {
if (activePowerUp) {
let elapsed = Date.now() - powerUpTimer;
let remaining = Math.max(0, powerUpDuration - elapsed);
// Draw bar
ctx.fillStyle = "gray";
ctx.fillRect(canvas.width - 20, 20, 10, 100);
ctx.fillStyle = "lime";
let fillHeight = (remaining / powerUpDuration) * 100;
ctx.fillRect(canvas.width - 20, 120 - fillHeight, 10, fillHeight);
if (remaining <= 0) {
activePowerUp = null;
paddleWidth = basePaddleWidth; // reset
}
}
}
Explore:
- Make the timer 10 seconds instead of 5.
- Try different effects (ex: double player speed).
β Theory Check (L2 β’ Step 3)
1) A countdown visual that maps remaining time to a bar height is an example of:
2) Why use Math.max(0, remaining) before drawing?
3) Resetting the paddle when the timer ends demonstrates: