Hamburger breakoutOne-line
North Mini Code (free)
by CohereReasoning1 console error
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 1 min 33 s
- Cost
- $0
- Output tokens
- 15,784
- Spent thinking
- 14,177 tokens
- Speed
- 169.5 tok/s
- Console errors
- 1
- First token after
- 381 ms
- First visible text after
- 1 min 27 s
- Prompt tokens
- 346
- Code size
- 6.4 KB
- Finish reason
- stop
- Served by
- Cohere
- Model id
cohere/north-mini-code:free- Reasoning sent
model default- Max output
- 32,000 tokens
- Generated
- Nov 9, 2026
Console errors
Caught in the first 3 seconds after load, in a sandboxed iframe like the one above.
Failed to load resource: the server responded with a status of 404 (Not Found)
What it was asked
The prompt
“i want a breakout game where the paddle is a hamburger and every brick you break explodes into tons of little hearts”
System prompt, sent with every prompt
You are generating a self-contained web demo. It will be rendered automatically in a sandboxed iframe and compared side by side with the output of other AI models given the exact same task. Output rules: - Reply with exactly ONE fenced code block containing the complete file: ```html (or ```svg when the task asks for an SVG). No text before or after the code block. - Everything must be inline: CSS in <style>, JavaScript in <script>. No external files, images, fonts or network requests, except the CDN URLs explicitly allowed by the task. - Do not use localStorage, sessionStorage, cookies, IndexedDB, alert(), confirm(), prompt() or window.open(): they are blocked in the sandbox and will throw. - The page must look right in a 1280×800 viewport and adapt gracefully down to 375px wide. No horizontal scrollbar. - Something meaningful must be visible on first paint: a screenshot is taken automatically about 3 seconds after load, without any interaction. - Everything that makes the demo interesting must be visible within the first seconds: nothing hidden behind a high score, a later level or a long play session. - Games must start in a self-playing demo mode (a simple AI plays) behind a "Click or press a key to start" message, so the game can be judged in action without playing. - The iframe only receives keyboard events after the user clicks it: if the demo uses the keyboard, display a clear "Click or press a key to start" message. - The code must run with zero errors in the console of the latest Chrome.
Code run-1.html · 240 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Breakout Hamburger Paddle</title>
<style>
html, body { margin:0; padding:0; height:100%; background:#000; font-family:sans-serif; }
#container { position:relative; width:100%; max-width:1280px; margin:auto; padding-bottom:75%; /* 3:4 aspect */ height:0; overflow:hidden; }
#canvas { position:absolute; top:0; left:0; width:100%; height:100%; }
</style>
</head>
<body>
<div id="container">
<canvas id="canvas"></canvas>
</div>
<script>
window.addEventListener('load', init);
function init() {
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// Initial resize to fill container
function resizeCanvas() {
const container = canvas.parentElement;
const cw = container.clientWidth;
const ch = container.clientHeight;
canvas.width = cw;
canvas.height = ch;
}
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
/* ---------- Game constants ---------- */
const PADDLE_WIDTH = 120;
const PADDLE_HEIGHT = 20;
const BALL_RADIUS = 8;
const BRICK_WIDTH = 70;
const BRICK_HEIGHT = 20;
const GAP = 10;
const PADDING = 20;
const HEART_SIZE = 20;
const HEART_MAX_LIFE = 60;
const brickColors = ['#ff6b6b','#ffb3ba','#ff8080','#ff4d4d','#ff6f6f'];
/* ---------- Game state ---------- */
let started = false;
let animationId = null;
let ball = {x:0, y:0, vx:0, vy:0};
let paddle = {x:0, y:0};
let bricks = [];
let hearts = [];
/* ---------- Drawing helpers ---------- */
function drawPaddle(ctx, x, y) {
// hamburger shape
ctx.fillStyle = '#d9a35e'; // top bun
ctx.fillRect(x, y, PADDLE_WIDTH, 8);
ctx.fillStyle = '#c0392b'; // patty
ctx.fillRect(x, y+8, PADDLE_WIDTH, 4);
ctx.fillStyle = '#d9a35e'; // bottom bun
ctx.fillRect(x, y+12, PADDLE_WIDTH, 8);
}
function spawnHearts(cx, cy) {
const count = 20 + Math.floor(Math.random()*10);
for (let i=0;i<count;i++) {
const angle = Math.random() * Math.PI * 2;
const speed = 2 + Math.random()*2;
const vx = Math.cos(angle) * speed;
const vy = Math.sin(angle) * speed;
hearts.push({
x: cx,
y: cy,
vx: vx,
vy: vy,
life: HEART_MAX_LIFE,
maxLife: HEART_MAX_LIFE
});
}
}
/* ---------- Game start ---------- */
function startGame() {
if (started) return;
started = true;
// reset ball
ball.x = canvas.width/2;
ball.y = canvas.height * 0.7;
ball.vx = (Math.random() > 0.5 ? 1 : -1) * 5;
ball.vy = -5;
// reset paddle
paddle.x = (canvas.width - PADDLE_WIDTH) / 2;
paddle.y = canvas.height - PADDLE_HEIGHT;
// reset bricks
bricks = [];
const rows = 5;
const cols = 8;
const startX = 40;
const startY = 60;
for (let row=0; row<rows; row++) {
for (let col=0; col<cols; col++) {
bricks.push({
x: startX + col*(BRICK_WIDTH+GAP),
y: startY + row*(BRICK_HEIGHT+GAP),
w: BRICK_WIDTH,
h: BRICK_HEIGHT,
alive: true,
color: brickColors[Math.floor(Math.random()*brickColors.length)]
});
}
}
hearts = [];
if (!animationId) requestAnimationFrame(gameLoop);
}
/* ---------- Main game loop ---------- */
function gameLoop() {
if (!started) {
// show start screen
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.fillStyle = '#fff';
ctx.font = '24px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('Click or press a key to start', canvas.width/2, canvas.height/2);
animationId = requestAnimationFrame(gameLoop);
return;
}
// update hearts
for (let i = hearts.length-1; i>=0; i--) {
let h = hearts[i];
h.life--;
h.x += h.vx;
h.y += h.vy;
if (h.life <= 0) {
hearts.splice(i,1);
}
}
// update ball
ball.x += ball.vx;
ball.y += ball.vy;
// bounce off side walls
if (ball.x - BALL_RADIUS <= 0 || ball.x + BALL_RADIUS >= canvas.width) {
ball.vx = -ball.vx;
}
// bounce off top
if (ball.y - BALL_RADIUS <= 0) {
ball.vy = -ball.vy;
}
// bounce off paddle
if (ball.y + BALL_RADIUS >= paddle.y &&
ball.x >= paddle.x &&
ball.x <= paddle.x + PADDLE_WIDTH) {
ball.vy = -ball.vy;
}
// check brick collisions
for (let i=0; i<bricks.length; i++) {
let b = bricks[i];
if (!b.alive) continue;
if (ball.x > b.x && ball.x < b.x + b.w &&
ball.y > b.y && ball.y < b.y + b.h) {
ball.vy = -ball.vy;
b.alive = false;
spawnHearts(b.x + b.w/2, b.y + b.h/2);
// break; // uncomment if you want only one brick per frame
}
}
// AI: move paddle toward the ball
let targetX = ball.x - PADDLE_WIDTH/2;
if (targetX < 0) targetX = 0;
if (targetX > canvas.width - PADDLE_WIDTH) targetX = canvas.width - PADDLE_WIDTH;
paddle.x += (targetX - paddle.x) * 0.1;
// clamp
if (paddle.x < 0) paddle.x = 0;
if (paddle.x > canvas.width - PADDLE_WIDTH) paddle.x = canvas.width - PADDLE_WIDTH;
// drawing
ctx.clearRect(0,0,canvas.width,canvas.height);
// bricks
for (let b of bricks) {
if (b.alive) {
ctx.fillStyle = b.color;
ctx.fillRect(b.x, b.y, b.w, b.h);
}
}
// paddle
drawPaddle(ctx, paddle.x, paddle.y);
// hearts
for (let h of hearts) {
ctx.save();
ctx.globalAlpha = h.life / HEART_MAX_LIFE;
ctx.font = `${HEART_SIZE}px Arial`;
ctx.fillStyle = '#ff0000';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('♥', h.x, h.y);
ctx.restore();
}
// ball
ctx.fillStyle = '#00ff00';
ctx.beginPath();
ctx.arc(ball.x, ball.y, BALL_RADIUS, 0, Math.PI*2);
ctx.fill();
// win condition
const aliveCount = bricks.reduce((sum, b) => b.alive ? sum+1 : sum, 0);
if (aliveCount === 0) {
ctx.fillStyle = '#fff';
ctx.font = '32px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('You Win!', canvas.width/2, canvas.height/2);
}
animationId = requestAnimationFrame(gameLoop);
}
/* ---------- Input handlers ---------- */
function onClick(e) {
if (!started) {
startGame();
}
}
canvas.addEventListener('click', onClick);
}
</script>
</body>
</html>Reasoning trace (14,177 tokens)
The user wants a breakout game (like classic Breakout/Pong). The paddle is a hamburger, and each brick, when broken (i.e dítěte). We'll need to implement a simple breakout game with canvas. It must meet constraints: self-contained HTML page with inline CSS and JS, no external libraries unless via CDN allowed? The task does not specify any allowed CDNs. Usually, we cannot use external resources unless explicitly allowed. So we should avoid external CDNs. So we need pure HTML/CSS/JS.
We must display something visible immediately. Show the game in the viewport.
The breakout game should have a paddle (hamburger shape) that can be moved left/right by either user (touch? But need keyboard. According to guidelines: The iframe only receives keyboard events after the user clicks it: if the demo uses the keyboard, display a clear "Click or press a key to start" message.
The page should show a start message: "Click or press a key to start". Then upon click or key, start game.
Need to implement breakout with bricks (maybe 10 rows of bricks). When a brick is broken (hit by ball), it should explode into many small hearts (maybe hearts particles). The paddle is a hamburger: could draw a simple rectangle with top and bottom buns (maybe top and bottom arcs). Or simply a cartoon hamburger shape: a rectangle for patty, two circles for top bun, bottom bun. We'll need a drawing function to draw the paddle as a hamburger.
We need a ball, bricks with colors, hearts explosion. We'll need to animate.
Implementation plan:
- Use a Canvas element. Set size to something like 800x600, within 1280x800 viewport. Or adapt to container.
- Use requestAnimationFrame to animate.
- Game states: start screen "Click or press a key to start". Once user interacts (click on canvas, or any key), start game.
- Paddle: position at bottom of canvas. Draw a hamburger shape: perhaps we can draw a shape like: bottom bun is a rectangle with rounded corners (or just a rectangle). We'll use simple shapes: top bun is a circle (or an ellipse) at top, then a rectangle for patty, then bottom bun rectangle.
- Paddle should be able to move left/right based on left/right arrow keys (or maybe A/D). For keyboard: on keydown, if key is left/right, update velocity.
- Ball: thrown at start in center, with velocity x and y negative (upwards). The ball size maybe 10x10.
- Bricks: define a grid of bricks: maybe 8 columns, 5 rows. Each brick could be of size e.g., 70x20 with some spacing.
- Each brick has health? Actually in breakout when ball hits brick, it disappears. Here we want "explode into tons of little hearts". So when a brick is hit, we spawn many small heart shapes that move outward or appear briefly.
- The hearts: small hearts with red color, size maybe 4-6px, maybe with some animation of fade out after a short time.
- Collision detection: for paddle, ball, bricks. We'll need to detect collisions between ball and bricks. When ball hits brick, we can mark brick as destroyed and spawn hearts.
- For simplicity, we can treat bricks as rectangular objects; ball collides with brick via simple bounding box.
- When brick is destroyed, we spawn a certain number (maybe 20-30) of hearts at brick position, each with random direction outward.
- Hearts should animate for a few seconds then disappear. They could be drawn as a simple two overlapping circles or a path.
- For hearts, can use path: <path d="M10 10 Q5 5 10 0 Q15 5 10 10 Z" ...> Or just two circles.
- After all bricks are destroyed, we can show "You Win!" maybe.
- The paddle: the hamburger shape. The paddle can be longer than width of canvas? Use width of canvas minus some padding.
- When ball moves out of bottom, maybe game over? Or bounce off paddle.
- Additional details: we need to draw images from scratch.
- Write code in a single HTML file with inline <style> and <script>.
- Ensure no console errors.
- We'll need to handle the user click to start: show start message and start with "Click or press a (±) to start". The user can click anywhere or press any key (like space) to start.
- We'll need to set up event listeners for "click" on canvas (or document) to set start flag.
- Also need to capture keydown events for left/right.
- Ensure the start message is visible initially. On start, hide start message, begin game.
- The AI self-play: The game should start in a self-playing demo mode (i.e., after start, the game will automatically start and the paddle will move automatically? Actually guidelines: "Games must start in a self-playing demo mode (a simple AI plays) behind a "Click or press a key to start" message". That means after the user clicks or presses a key, the game starts automatically without user input? Hmm, but they also say "The iframe only receives keyboard events after the user clicks it: if the demo uses the keyboard, display a clear "Click or press a key to start" message." So we need to check if the game uses keyboard; if so, the user must click to start. But then they also want the game to start in a self-playing demo mode behind that message. So after click, the AI (like auto-move paddle) should start playing.
Thus, after user clicks on the page, we start the game with paddle moving automatically? Or the ball thrown? Actually the game would still start with ball moving and the paddle controlled by AI (maybe moving based on ball position). The user can still control later? But not necessary for demo. So we can implement simple AI that moves paddle towards predicted ball location.
Simplify: after start, we set paddle velocity automatically: each frame we move paddle to be at ball's x-coordinate (maybe with slight lag). This makes AI.
Alternatively, we can set paddle moving left-right constantly. But better to aim at ball.
Implementation details:
Game objects:
- Paddle: { x, y, width, height, image draw function: drawHamburger(ctx, x, y, width, height). We'll draw a hamburger shape maybe: top bun is an arc (semi-circle) on top side, patty rectangle, bottom bun rectangle.
- Ball: { x, y, radius, vx, vy }
- Bricks: array. Each brick: { x, y, width, height, alive: true }
- Hearts: array. Each heart: { x, y, vx, vy, radius, life (timer), maxLife }
Simplify ball movement:
- On start: set ball.x = canvas.width/2, ball.y = canvas.height * 0.7, ball.vx = random direction left or right, ball.vy = -5.
- On each frame, update ball.x += ball.vx, ball.y += ball.vy.
- Paddle: position at bottom (canvas.height - paddleHeight). Paddle.x should be updated by AI.
- Ball collision:
- Floor: if ball.y + radius >= canvas.height (ball bottom), then if ball.y >= paddle top? Actually floor is canvas bottom - paddleHeight? Actually floor is bottom of canvas, but paddle is at bottom.
- When ball hits paddle, we bounce (ball.vy = -ball.vy).
- When ball hits left/right walls, bounce.
tragen: if ball.x - radius <= 0: set vx = -vx; similarly for right.
- Brick collision: if ball intersects a brick, reflect velocity, set brick.dead and spawn hearts.
- For hearts spawn: when a brick is hit, we spawn N hearts (maybe 20). Each heart at the brick's x,y with random velocities outward.
- We need to draw hearts: maybe small red heart shape.
We need to handle collisions detection.
Implementation steps:
1. HTML structure: <canvas id="canvas">.
2. Style: maybe set canvas to fill container: set body margin/padding 0, canvas width = window.innerWidth? Actually we want viewport size 1280x800. We'll set canvas width 800, height 600, and center. Or we can set CSS to have canvas sized to the viewport. But simpler: set canvas width and height to viewport size. Or set container width/height.
But per guidelines, "The page must look right in a 1280×800 viewport and adapt gracefully down to 375px wide." So we can have container with width: min(100vw, 1280px) maybe.
Simplify: use <canvas id="c" width="800" height="600">.
Set canvas to have max-width 100% and auto.
3. CSS:
body { margin:0; overflow:hidden; background:#000; }
canvas { display:block; margin:0 auto; }
If needed, for small screens, shrink.
4. JS:
- Get canvas, ctx.
- Define constants: PADDLE_WIDTH=120, PADDLE_HEIGHT=20, BALL_RADIUS=10, BRICK_WIDTH=70, BRICK_HEIGHT=20, BRICK_ROWS=5, BRICK_COLS=8, GAP=10, PADDING=30.
- Compute brick positions.
- Draw functions:
- drawPaddle(ctx, x, y): draws hamburger shape.
- drawBall(ctx, x, y)
- drawBrick(ctx, brick)
- drawHeart(ctx, x, y, size): shape.
- Game state variables:
- ball, paddle, bricks, hearts, score (maybe for debugging). Score not needed.
- On startGame(): set ball initial properties, set paddle.x = (canvas.width - paddle.width)/2, set start flag true.
- Use requestAnimationFrame loop.
- In loop: clear canvas, draw paddle, bricks, hearts, ball. Update hearts: decrement life, remove if expired.
- Update ball position.
- Check collisions:
- With walls: left/right bounce.
- With paddle: if ball.y + radius >= paddle.y and ball.x between paddle.x and paddle.x+paddle.width, reflect vy upward: ball.vy = -abs(ball.vy) maybe.
- With bricks: for each brick if alive and collision detection: if ball.y - radius <= brick.y + brick.height and ball.y + radius >= brick.y and ball.x >= brick.x && ball.x <= brick.x + brick.width, then bounce ball accordingly, set brick.alive = false, spawn hearts.
- For hearts spawn: spawn hearts around brick center.
- Paddle AI: compute target position based on ball.x - paddle.width/2? Actually we can set paddle.x to ball.x - paddle.width/2 (center). Or maybe smooth movement: paddle.x += (ball.x - paddle.x - paddle.width/2) * 0.05.
- The paddle should not move outside canvas. Clip.
- The hearts drawing: small hearts that shrink and fade out.
- The hearts also move outwards: update heart.vx, heart.vy maybe small random.
- Implementation of hearts: each heart { x, y, vx, vy, radius, life, maxLife }. Each frame: x += vx, y+=vy, life--. Draw heart at (x,y). radius reduces as life reduces? Or constant.
- The hearts animation: after hitting brick, show them briefly. We need to make them visible for a few frames (maybe 60). For simplicity, we can just spawn them and then they fade out.
- Colors: hearts red (#FF0000), bricks maybe pastel.
- Show start overlay: draw "Click or press a key to start". Use white text. When start is false, draw this message and maybe instructions.
- On click or keydown (if start false): call startGame, set start true.
- Also remove event listeners? Not needed.
- Ensure no console errors.
Now we need to produce a single HTML file. Use <script> tag at bottom. Possibly include a <style> inside <head>.
We need to ensure that after page loads, something visible appears: the canvas with start screen. The game should start after user click.
Make sure to handle small screens: adjust canvas maybe using CSS to keep aspect ratio.
But we need a self-contained page; nothing external.
Now, in more detail, the drawing functions:
Hamburger:
- topBun: a half-circle on top of paddle: maybe rectangle with top curved.
Simplify: draw a rectangle for top bun, with rounded corners? Or draw two arcs.
We can draw the hamburger shape as three rectangles: top bun rectangle (maybe 120 width, 10 height) with "top" indicator? Actually we can draw a simple hamburger: two buns (top and bottom) as circles, and patty as rectangle between them.
Better: draw a top bun as a half-circle on top of the top of paddle, and bottom bun as half-circle at bottom.
But paddle is placed at bottom of screen, so maybe we draw hamburger oriented horizontally. For simplicity, draw:
- Top bun: a rectangle at top of paddle with rounded top corners.
- Patty: a rectangle in middle.
- Bottom bun: a rectangle at bottom.
Let's approximate: Paddle has height 20. We can draw:
topBunHeight = 6, pattyHeight = 6, bottomBunHeight = compleja.
Actually we could just draw a simple hamburger: a circle for top bun above the paddle, then a rectangle for patty, then a circle for bottom bun below.
But paddle height is small (20). Let's create shape:
topBun radius = 10? Actually we need to draw a simple burger shape: maybe use canvas arcs.
Simplify: draw a top bun as a half circle (radius 10) centered at paddle.x + paddle.width/2 and at y - paddle.height (above paddle). Then bottom bun as half circle at y + paddle.height (below paddle). Then patty as rectangle in between.
Better: Use separate shapes.
Let's define paddle height = 20. topBunHeight = 8, bottomBunHeight = 8, pattyHeight = 4.
We can draw:
- top bun: draw arc of radius 8 at center (x + width/2, y - 4) to create top half? Actually we can draw a circle shape for top bun above paddle: use fillRect for top bun? Or just rectangle.
Simplify: just draw a burger as three rectangles: topBunRect (width, 10), pattyRect (width, 10), bottomBunRect (width, 10). They stack vertically. This would just be a thick bar with three segments. But we can use color: top bun brown, patty red, bottom bun brown.
Thus, we can draw:
ctx.fillStyle = '#d9a35e'; // top bun
ctx.fillRect(x, y, width, 10);
ctx.fillStyle = '#c0392b'; // patty
ctx.fillRect(x, y+10, width, 8);
ctx.fillStyle = '#d9a35e'; // bottom bun
ctx.fillRect(x, y+18, width, 10);
Thus paddle looks like a burger. We'll set width maybe 100.
Make sure to round corners: we can use lineJoin.
We'll use border radius? Not needed.
Alternatively, we could draw using arcs.
But a simple rectangular burger is okay.
Thus we can draw paddle as a burger.
Alright.
Now implement hearts:
- Heart shape: simple two circles overlapped or a path.
Define drawHeart(ctx, x, y, r):
We can use path: Move to (x, y + r), then draw arcs.
Better to use a pre-defined shape: The heart shape is not trivial. But we can use canvas drawing with a simple shape:
Define heart shape using two circles: the left half of heart: draw a circle at (x + r/2, y) radius r/2, and right half similarly. Or simpler: use a shape comprised of two arcs.
Alternatively, we can use a simple star shape? But we want hearts.
Simplify: draw small hearts using a preloaded image? Not possible.
But we can draw a heart shape using a combination of arcs:
Heart shape: Outline can be approximated as:
- top point left side, then down left, then across, then down right.
Better to use a shape:
```javascript
function drawHeart(ctx, x,(TEST) {
ctx.beginPath();
ctx.moveTo(x, y + r); // top middle
ctx.lineTo(x - r, y); // left top
ctx.lineTo(x, y); // apex?
}
```
Better to use known heart path: In SVG, you can use <path d="M10 10 Q5 5 10 0 Q15 5 10 10 Z" but that's more for shape of heart shape.
Actually for canvas we can use path with approximate shape:
We can do:
ctx.beginPath();
ctx.moveTo(x, y + 0.3*r);
ctx.lineTo(x - 0.5*r, y - 0.5*r);
ctx.lineTo(x + 0.5*r, y - 0.5*r);
ctx.lineTo(x, y + 0.3*r);
ctx.closePath();
But this may not be accurate.
Alternative: We can just draw a small filled circle for heart (like a red dot) to represent a heart. But the user expects hearts.
Better to draw a heart shape using two circles: left half and right half.
Heart shape: use two circles: one for left side, one for right side.
We can draw:
- left circle: center at (x - r/2, y) radius r/2
- right circle: center at (x + r/2, y) radius r/2
But that just forms a shape like two half circles side by side, looks like a "8"? Not exactly heart shape. Actually two half circles placed next to each other forms a shape like a "dumbell"? But we can offset vertically.
Better: Use a typical heart shape defined by path points.
We can approximate using three points: (x, y), (x - r, y - r), (x + r, y - r), then arcs.
Simplify: For simplicity, we can draw a small heart using a path that consists of two circles overlapping. We can use something like:
ctx.beginPath();
ctx.moveTo(x, y);
ctx.arcTo(x - r, y - r, x + r, y - r, r); // not correct.
Let's consider using the classic heart path formula in canvas:
The heart shape can be drawn as:
```javascript
function drawHeart(ctx, x, y, size) {
ctx.beginPath();
ctx.moveTo(x, y);
ctx.bezierCurveTo(x - size/2, y - size/2, x - size, y + size/2, x, y + size);
ctx.bezierCurveTo(x + size, y + size/2, x + size/2, y - size/2, x, y);
ctx.closePath();
}
```
But this is approximate.
Actually we can use known path: for a heart shape, you can draw two circles and subtract one. But approximating with bezier curves is fine.
Better: Use a simple shape: draw two circles overlapping:
- left circle: center (x - r/3, y), radius r
- right circle: center (x + r/3, y), radius r
But they'd overlap heavily.
Alternatively, we can just draw a simple heart shape using the path:
```javascript
ctx.beginPath();
ctx.moveTo(x, y+0.3*r);
ctx.bezierCurveTo(x - r, y+0.3*r, x - r, y+r, x, y+r);
ctx.bezierCurveTo(x + r, y+r, x + r, y+0.3*r, x, y+0.3*r);
ctx.closePath();
```
But the shape is not perfect.
Simplify: we could use a small heart drawn using the "♥" Unicode character (U+2665). We can fill text with that character: ctx.fillText('♥', x, y). That's easy! The canvas can draw text, and we can use that char. That would look like hearts. Good! So we can just draw hearts as text. That is simplest.
Thus, hearts: each heart has position (x, y) and size using fontSize. We'll draw hearts as text: ctx.fillText('♥', x - size/2, y). However, the position of text is baseline; we might need to adjust.
We can set ctx.font = size + 'px Arial'; then fillText. So hearts will be visible.
That is acceptable: hearts are little hearts drawn with text.
Thus, hearts will be red hearts.
We could also set heart radius small for scaling.
Implementation: hearts have properties:
{x, y, vx, vy, life, maxLife, size}. We can set size fixed, but we can animate size.
We'll just draw hearts with size 12px.
Also hearts will move outward from brick.
Thus, hearts will appear after brick broken.
The hearts need to disappear after maxLife frames (maybe 60 frames). We'll decrement life each frame. When life <=0, remove heart.
Now, ball and bricks collision detection details:
We can use simple axis detection:
Ball collides with brick if ball's bottom is above brick's top? Actually ball's bottom after moving is ball.y+radius. For brick at y, brick top is brick.y. Brick bottom is brick.y+brick.height. We can compute overlap: if ball.y - radius <= brick.y + brick.height && ball.y + radius >= brick.y and ball.x >= brick.x && ball.x <= brick.x+brick.width. This indicates intersection.
When collision occurs, we reverse ball's vertical velocity. For horizontal: maybe also reflect x velocity.
But we need to also reflect horizontal? In breakout, bricks are horizontal, so ball bounces off horizontally too. For simplicity, we reflect both.
But also need to avoid multiple collisions in one frame.
We'll just check each brick.
Also need to bounce off paddle: If ball.y+radius >= paddle.y (paddle top?) Actually paddle is at bottom, so paddle.y is near bottom. We'll define paddle.y is the top of paddle? Actually paddle rectangle at bottom. We'll set paddle.y such that paddle top is near bottom. Actually paddle is drawn at bottom of canvas. We'll compute paddle.y = canvas.height - paddle.height.
Ball collides with paddle if ball.y - radius <= paddle.y && ball.x >= paddle.x && ball.x <= paddle.x + paddle.width, then reverse vertical velocity.
But we need to also consider that the ball might be slightly below paddle when hitting.
Simplify: If ball.y + radius >= paddle.y - something? Actually ball is above paddle? In breakout, paddle is at bottom, ball above it.
Thus, we need to check if ball.y >= paddle.y (ball bottom touches paddle top). So if ball.y + radius >= paddle.y, and ball.x within paddle bounds, then reflect ball vy negative.
We'll do that.
Now implement bricks: create array of brick objects. They can have properties: x, y, w, h, alive.
Define bricks array as:
for each row i in 0..BRICK_ROWS-1:
for each col j in 0..BRICK_COLS-1:
brick.x = PADDING + j*(BRICK_WIDTH+GAP) + GAP maybe
brick.y = PADDING + i*(BRICK_HEIGHT+GAP) + GAP
We need to ensure they fit within canvas.
We also need to define brick colors: maybe gradient, but we can choose random pastel.
Define brickColors = ['#ff6b6b', '#ffb3ba', '#ff8080', '#ff4d4d', '#ff6f6f'].
We'll assign each brick a random color from that palette.
Alternatively, we could use different colors for each row.
For simplicity, all bricks have color '#ff6b6b' (a pink). Or we can random.
Now hearts spawn: When brick is hit, set brick.alive = false, then spawn hearts:
For n in 20..30: heart = {x: brick.x + brick.width/2 + random(-5,5), y: brick.y + brick.height/2 + random(-5,5), vx: random(-2,2), vy: random(-2,2), life: maxLife, maxLife: 60, size: 12}
We'll store hearts in array.
Now hearts update: for each heart, decrement life. If life <=0, remove.
We also need to move hearts: x += vx, y += vy.
We also may want to shrink hearts as they fade, but not required.
We also might want to apply gravity to hearts? Not needed.
Now paddle AI: move paddle.x toward ball.x. Actually we can set paddle.x = ball.x - paddle.width/2 (if we want center). But better to smooth: paddle.x += (ball.x - paddle.x - paddle.width/2) * 0.05.
We'll clamp paddle.x to [0, canvas.width - paddle.width].
Now ball velocity: When game starts, choose ball.vx randomly to left or right, ball.vy = -5 (upwards). Or maybe speed.
Ball speed: magnitude ~5.
Now start message:
When game not started (start == false), draw:
ctx.fillStyle = '#fff';
ctx.font = '24px Arial';
ctx.textAlign = 'center';
ctx.fillText('Click or press a key to start', canvas.width/2, canvas.height/2);
Also maybe draw "Press any key or click".
Now for keyboard events:
Add event listeners for 'keydown' and 'click' (or 'pointerdown' maybe). On any of those, if not started, call startGame.
Also attach 'mousemove'? Not needed.
Also need to handle keyboard for paddle movement? Since we have AI, maybe not needed. But they said "if the demo uses the keyboard, display a clear "Click or press a key to start" message." So we could also allow left/right keys to move paddle manually for fun? Actually AI can handle. But maybe we also allow arrow keys to move paddle as well. That would be okay.
But the instruction about keyboard events says: The iframe only receives keyboard events after the user clicks it: if the demo uses the keyboard, display a clear "Click or press a key to start" message. So if we plan to use keyboard for paddle movement, then we need to display that message. But we could also not use keyboard at all: we could just start the game after click, and paddle will be controlled by AI. Then we wouldn't need keyboard. That avoids confusion. However, the instruction says "if the demo uses the keyboard, display a clear "Click or press a الجزء" message." So we can avoid keyboard usage entirely. That simplifies: we only use click to start. That also satisfies self-playing AI. We can just start game on click without keyboard. There's no need for key events. So we can avoid keydown listeners.
Thus, we only need to listen for click on canvas to start.
Will that satisfy? It says "The iframe only receives keyboard events after the user clicks it: if the demo uses the keyboard, display a clear "Click or press a key to start" message." So if we do not use keyboard, then we don't need to consider that. So it's okay.
Thus, we will implement:
- window.addEventListener('click', onClick) maybe on canvas.
- On click, if not started, call startGame and set started true.
Also if we want to support space bar press to start? Not needed.
Now implementing the canvas.
One more nuance: The game should self-play behind start message. So after click, the ball moves, paddle AI moves, hearts spawn.
We need to ensure that after click, the start message disappears. So we can set a boolean started.
We must also ensure that the canvas is focusable? Since we don't use keyboard, not needed.
Now, the page must adapt to smaller viewport widths. To keep everything centered, we can use CSS:
#container { position:relative; width:800px; height: Branca... Actually we can use CSS to set canvas size relative to viewport, but easier: let canvas be fixed size 800x600. And container center.
We can set:
<body>
<canvas id="canvas" width="800" height="600"></canvas>
</body>
<style>
body { margin:0; background:#000; display:flex; justify-content:center; align-items:center; height:100vh; }
canvas { display:block; }
</style>
But adapt down to 375px width. If we use flex centering, the canvas will be scaled? Actually canvas is fixed pixel size, but CSS width maybe set to 100% of container. The canvas will render at its native resolution, but container may be smaller, causing scaling (pixelated). However, guidelines say "The page must look right in a 1280×800 viewport and adapt gracefully down to 375px wide. No horizontal scrollbar." So we need to ensure that the content remains visible and fits within width.
We could make canvas responsive: set its width to min(100vw, 800) maybe. But canvas width property is fixed in HTML; we could set style width to 100% and height auto to maintain aspect ratio.
But we cannot rely on CSS to upscale canvas to pixel-damaged resolution? We could set canvas to use high DPR? But it's okay.
Simplify: set container width: min(100vw, 1280). For small screens, the canvas will shrink but may appear small. But we can set the canvas width to 100% of container, and adjust its internal scaling? Actually we can set canvas width and height via CSS to fill container, but then we need to keep aspect ratio. Usually we set container aspect ratio using padding-bottom hack or transform scale.
Alternatively, we can make canvas width: 100vw (full width), and height: auto maintain aspect ratio.
But the breakout game may require a certain resolution.
To keep things simple and meet requirements, we can use a container with max-width: 1280px and width:100%; height: auto, then set canvas width = 100% and height = auto, but maintain aspect ratio using CSS (maybe set canvas to width=100%, height=auto). However, canvas will stretch, but we can also set object-fit: contain.
But maybe easier: Use fixed size canvas and center it; on small screens, the canvas will shrink due to flex centering? Actually using flex: justify-content:center; align-items:center; the canvas will be displayed at its original size (800x600) which may overflow horizontally if viewport width <800. To avoid horizontal scroll, we can set canvas width: 100vw; but then the scaling is pixelated. But maybe acceptable.
Alternatively, we can set container width to min(100vw,1280px) and set canvas width to container's width, and we can scale context using devicePixelRatio.
Let's consider using a container with width: min(100vw, 1280px). Set canvas width: container width (converted to CSS px). But we need to keep aspect ratio. We can set canvas width: 100% and height: 75% (to maintain 4:3 aspect ratio). Actually 800/600 = 4/3, so height = width * (3/4). So we can do:
#container { max-width:1280px; margin:auto; position:relative; padding-bottom:75%; /* 3/4 aspect */ height:0; }
#canvas { position:absolute; top:0; left:0; width:100%; height:100%; }
Thus canvas maintains aspect ratio. This will adapt to small screens, no horizontal scroll. This seems best.
We also need to ensure the canvas context coordinates scale appropriately; we can compute actual canvas pixel size via ctx = canvas.getContext('2d'); then we need to consider devicePixelRatio for crisp rendering. But we can ignore high DPI for now. The game will look okay.
Thus, we can implement:
<style>
body { margin:0; background:#000; font-family:sans-serif; overflow:hidden; }
#container { position:relative; width:100%; max-width:1280px; margin:auto; padding-bottom:75%; height:0; }
#canvas { position:absolute; top:0; left:0; width:100%; height:100%; }
</style>
Then inside script, we can get canvas = document.getElementById('canvas'); and ctx = canvas.getContext('2d'); and then perhaps scale context for crisp rendering: let dpr = window.devicePixelRatio || 1; canvas.width = canvas.clientWidth * dpr; canvas.height = canvas.clientHeight * dpr; ctx.scale(dpr, dpr). But if we set canvas width and height via CSS, they may not be set at runtime; but we can compute after load.
Better: after window load, we can compute canvas width and height based on container size.
Implementation: in script:
const container = document.getElementById('container'); // but we have canvas id
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
let dpr = window.devicePixelRatio || 1;
let w = canvas.clientWidth;
let h = canvas.clientHeight;
canvas.width = w * dpr;
canvas.height = h * dpr;
ctx.scale(dpr, dpr);
But note: we set canvas.width/height attributes, not style. Actually setting them to w*dr will override CSS width? Usually we need to set canvas width and height via attributes, and set CSS width/height accordingly. So we can set canvas.style.width = w + 'px'; canvas.style.height = h + 'px'; But we already set container width and height via CSS. However, for scaling, we can do as above.
Better: we can just not worry about DPR for now; simple rendering is fine.
We'll just use canvas.width = canvas.clientWidth; canvas.height = canvas.clientHeight; after container is displayed.
But we must ensure that we set canvas size after the page loads.
We can do:
window.addEventListener('load', function() {
resizeCanvas();
});
function resizeCanvas() {
let cw = canvas.clientWidth;
let ch = canvas.clientHeight;
canvas.width = cw;
canvas.height = ch;
}
Then all game logic uses canvas.width and canvas.height.
Now, drawing functions: They will use ctx, and coordinates based on canvas.width/height.
Now, we need to ensure that the start message appears before click. The canvas will be drawn with the start message.
Also, we need to ensure that on click, startGame is called.
One nuance: The click event should be attached to canvas element; but also we might attach to document. The instruction: "The iframe only receives keyboard events after the user clicks it: if the demo uses the keyboard, display a clear "Click or press a key to start" message." This refers to keyboard. For click, we can attach to canvas.
Thus:
canvas.addEventListener('click', function(e) { if (!started) startGame(); });
But we must ensure that the click doesn't propagate.
Now implement startGame:
function startGame() {
started = true;
// hide start message (the next frame, we will just draw game)
// Reset ball and paddle
ball.x = canvas.width/2;
ball.y = canvas.height * 0.7;
ball.vx = (Math.random() > 0.5 ? 1 : -1) * 5;
ball.vy = -5;
paddle.x = (canvas.width - paddle.width) / 2;
// Reset bricks
bricks = [];
const rows = 5;
const cols = 8;
const brickW = 70;
const brickH = 20;
const gap = 10;
const startX = 40;
const startY = 60;
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
bricks.push({
x: startX + col * (brickW + gap),
y: startY + row * (brickH + gap),
w: brickW,
h: brickH,
alive: true,
color: brickColors[Math.floor(Math.random()*brickColors.length)]
});
}
}
hearts = [];
// start animation loop if not already running
if (!animationId) requestAnimationFrame(gameLoop);
}
Define constants:
const PADDLE_WIDTH = 120;
const PADDLE_HEIGHT = 20;
const BALL_RADIUS = 8;
const BRICK_WIDTH = 70;
const BRICK_HEIGHT = 20;
const GAP = 10;
const PADDING = 20;
We also have hearts properties:
const HEART_SIZE = 20;
const HEART_MAX_LIFE = 60;
Now gameLoop function:
function gameLoop() {
if (!started) {
// draw start screen
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.fillStyle = '#fff';
ctx.font = '24px Arial';
ctx.textAlign = 'center';
ctx.fillText('Click or press a key to start', canvas.width/2, canvas.height/2);
animationId = requestAnimationFrame(gameLoop);
return;
}
// Update hearts
for (let i = hearts.length-1; i>=0; i--) {
let h = hearts[i];
h.life--;
h.x += h.vx;
h.y += h.vy;
// optional: shrink radius? but hearts are text; maybe just fade via opacity.
if (h.life <= 0) {
hearts.splice(i,1);
}
}
// Update ball
ball.x += ball.vx;
ball.y += ball.vy;
// Bounce off walls
if (ball.x - BALL_RADIUS <= 0 || ball.x + BALL_RADIUS >= canvas.width) {
ball.vx = -ball.vx;
}
if (ball.y - BALL_RADIUS <= 0) { // top
ball.vy = -ball.vy;
}
// Bounce off paddle
if (ball.y + BALL_RADIUS >= paddle.y && ball.x >= paddle.x && ball.x <= paddle.x + PADDLE_WIDTH) {
ball.vy = -ball.vy;
}
// Check brick collisions
for (let i = 0; i < bricks.length; i++) {
let b = bricks[i];
if (!b.alive) continue;
// simple collision detection
if (ball.x > b.x && ball.x < b.x + b.w && ball.y > b.y && ball.y < b.y + b.h) {
// bounce
ball.vy = -ball.vy;
// destroy brick
b.alive = false;
// spawn hearts
spawnHearts(b.x + b.w/2, b.y + b.h/2);
// optional: break after one brick per frame to avoid multiple hits
// break;
}
}
// AI: move paddle towards ball's x
let targetX = ball.x - PADDLE_WIDTH/2;
if (targetX < 0) targetX = 0;
if (targetX > canvas.width - PADDLE_WIDTH) targetX = canvas.width - PADDLE_WIDTH;
paddle.x += (targetX - paddle.x) * 0.1; // smooth movement
// Clamp paddle X
if (paddle.x < 0) paddle.x = 0;
if (paddle.x > canvas.width - PADDLE_WIDTH) paddle.x = canvas.width - PADDLE_WIDTH;
// Draw everything
ctx.clearRect(0,0,canvas.width,canvas.height);
// Draw bricks
for (let b of bricks) {
if (b.alive) {
ctx.fillStyle = b.color;
ctx.fillRect(b.x, b.y, b.w, b.h);
}
}
// Draw paddle (hamburger)
drawPaddle(ctx, paddle.x, paddle.y);
// Draw hearts
for (let h of hearts) {
ctx.save();
ctx.globalAlpha = h.life / HEART_MAX_LIFE;
ctx.font = `${HEART_SIZE}px Arial`;
ctx.fillStyle = '#ff0000';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('♥', h.x, h.y);
ctx.restore();
}
// Draw ball
ctx.fillStyle = '#00ff00';
ctx.beginPath();
ctx.arc(ball.x, ball.y, BALL_RADIUS, 0, Math.PI*2);
ctx.fill();
// Check win condition: if no alive bricks left, we could show win
let aliveCount = bricks.filter(b=>b.alive).length;
if (aliveCount === 0) {
// optionally display "You win"
ctx.fillStyle = '#fff';
ctx.font = '32px Arial';
ctx.textAlign = 'center';
ctx.fillText('You Win!', canvas.width/2, canvas.height/2);
}
// Continue loop
animationId = requestAnimationFrame(gameLoop);
}
Now drawPaddle function:
function drawPaddle(ctx, x, y) {
// Draw hamburger: top bun, patty, bottom bun
// Colors: top and bottom bun brown, patty red
ctx.fillStyle = '#d9a35e'; // top bun
ctx.fillRect(x, y, PADDLE_WIDTH, 10);
ctx.fillStyle = '#c0392b'; // patty
ctx.fillRect(x, y+10, PADDLE_WIDTH, 8);
ctx.fillStyle = '#d9a35e'; // bottom bun
ctx.fillRect(x, y+18, PADDLE_WIDTH, 10);
// maybe draw border
ctx.fillStyle = '#000';
ctx.fillRect(x, y, PADDLE_WIDTH, 20);
}
Now spawnHearts function:
function spawnHearts(cx, cy) {
const count = 20 + Math.floor(Math.random()*10);
for (let i = 0; i < count; i++) {
// random direction
let angle = Math.random()*Math.PI*2;
let speed = 2 + Math.random()*2;
let vx = Math.cos(angle) * speed;
let vy = Math.sin(angle) * speed;
hearts.push({
x: cx + vx*0.5, // small offset maybe
y: cy + vy*0.5,
vx: vx,
vy: vy,
life: HEART_MAX_LIFE,
maxLife: HEART_MAX_LIFE
});
}
}
Now handle start message: In the loop, if not started, draw start text and not update anything else. Also maybe hide start message after click? Actually we set started flag true in startGame and then loop will ignore start message.
Need to ensure startGame sets started to true and starts animation loop. Also we need to stop any previous animation loop.
Define global variables:
let canvas, ctx;
let started = false;
let animationId = null;
let ball = {x:0,y:0,vx:0,vy:0};
let paddle = {x:0,y:0};
let bricks = [];
let hearts = [];
let brickColors = ['#ff6b6b','#ffb3ba','#ff8080','#ff4d4d','#ff6f6f'];
Define constants at top.
We need to set paddle.y position at bottom: paddle.y = canvas.height - PADDLE_HEIGHT? But in drawPaddle we use y as top of paddle. So paddle.y = canvas.height - PADDLE_HEIGHT? Actually paddle.height = PADDLE_HEIGHT (20). So top y = canvas.height - PADDLE_HEIGHT.
Thus after startGame we need to set paddle.y = canvas.height - PADDLE_HEIGHT.
Now also we need to ensure that the canvas is correctly sized before startGame.
We can call resizeCanvas on window resize. So add event listener for window resize: call resizeCanvas and maybe reset animation.
Implementation details:
- On window load, call resizeCanvas, then set canvas, ctx variables, attach click listener.
Now we need to ensure that the startGame is called on click.
Potential issues: The canvas might not have clientWidth/Height until layout is done. We can call resizeCanvas on load and on resize.
Now some potential improvements: The hearts need to be drawn as text, but we need to set ctx.textBaseline or ctx.textAlign? Actually fillText expects baseline. We can set ctx.textBaseline = 'middle'.
Now we need to ensure that hearts fade out properly: globalAlpha set based on life.
Also hearts may be drawn with size HEART_SIZE. But that might be too large relative to canvas.
We can set HEART_SIZE to 20px.
Now the hearts appear at brick location. The hearts spawn offset ensures they start near the brick.
Now about win condition: If all bricks destroyed, we show "You Win!". That is nice.
Now ensure that the breakout game continues after win? Not needed.
Now ensure that paddle AI does not cause it to go off canvas.
Now need to verify that the canvas scales correctly on smaller screens. Use container approach.
Let's craft the HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Breakout Hamburger Paddle</title>
<style>
html, body { margin:0; padding:0; height:100%; background:#000; }
#container { position:relative; width:100%; max-width:1280px; margin:auto; padding-bottom:75%; /* 3:4 aspect */ height:0; overflow:hidden; }
#canvas { position:absolute; top:0; left:0; width:100%; height:100%; }
</style>
</head>
<body>
<div id="container">
<canvas id="canvas"></canvas>
</div>
<script>
... (the whole script)
</script>
</body>
</html>
Now inside script, we need to access canvas after page load.
Let's write the code.
Now we need to ensure no console errors. In code we use requestAnimationFrame, etc.
We also need to define all functions.
We need to ensure we don't call undefined functions.
We must include the script after canvas.
Also we must be careful about variable scope.
Now code details:
<script>
window.addEventListener('load', init);
function init() {
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// initial resize
resizeCanvas();
window.addEventListener('resize', resizeCanvas);
canvas.addEventListener('click', onClick);
// constants
const PADDLE_WIDTH = 120;
const PADDLE_HEIGHT = 20;
const BALL_RADIUS = 8;
const BRICK_WIDTH = 70;
const BRICK_HEIGHT = 20;
const GAP = 10;
const PADDING = 20;
const HEART_SIZE = 20;
const HEART_MAX_LIFE = 60;
const brickColors = ['#ff6b6b','#ffb3ba','#ff8080','#ff4d4d','#ff6f6f'];
let started = false;
let animationId = null;
let ball = {x:0, y:0, vx:0, vy:0};
let paddle = {x:0, y:0};
let bricks = [];
let hearts = [];
function resizeCanvas() {
const container = canvas.parentElement;
const cw = container.clientWidth;
const ch = container.clientHeight;
canvas.width = cw;
canvas.height = ch;
}
function drawPaddle(ctx, x, y) {
// hamburger
ctx.fillStyle = '#d9a35e'; // top bun
ctx.fillRect(x, y, PADDLE_WIDTH, 10);
ctx.fillStyle = '#c0392b'; // patty
ctx.fillRect(x, y+10, PADDLE_WIDTH, 8);
ctx.fillStyle = '#d9a35e'; // bottom bun
ctx.fillRect(x, y+18, PADDLE_WIDTH, 10);
// outline
ctx.fillStyle = '#000';
ctx.fillRect(x, y, PADDLE_WIDTH, PADDLE_HEIGHT);
}
function spawnHearts(cx, cy) {
const count = 20 + Math.floor(Math.random()*10);
for (let i=0;i<count;i++) {
const angle = Math.random() * Math.PI * 2;
const speed = 2 + Math.random()*2;
const vx = Math.cos(angle) * speed;
const vy = Math.sin(angle) * speed;
hearts.push({
x: cx,
y: cy,
vx: vx,
vy: vy,
life: HEART_MAX_LIFE,
maxLife: HEART_MAX_LIFE
});
}
}
function startGame() {
if (started) return;
started = true;
// reset ball
ball.x = canvas.width/2;
ball.y = canvas.height * 0.7;
ball.vx = (Math.random() > 0.5 ? 1 : -1) * 5;
ball.vy = -5;
// reset paddle
paddle.x = (canvas.width - PADDLE_WIDTH) / 2;
paddle.y = canvas.height - PADDLE_HEIGHT;
// reset bricks
bricks = [];
const rows = 5;
const cols = 8;
const startX = 40;
const startY = 60;
for (let row=0; row<rows; row++) {
for (let col=0; col<cols; col++) {
bricks.push({
x: startX + col*(BRICK_WIDTH+GAP),
y: startY + row*(BRICK_HEIGHT+GAP),
w: BRICK_WIDTH,
h: BRICK_HEIGHT,
alive: true,
color: brickColors[Math.floor(Math.random()*brickColors.length)]
});
}
}
hearts = [];
if (!animationId) requestAnimationFrame(gameLoop);
}
function gameLoop() {
if (!started) {
// draw start screen
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.fillStyle = '#fff';
ctx.font = '24px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('Click or press a key to start', canvas.width/2, canvas.height/2);
animationId = requestAnimationFrame(gameLoop);
return;
}
// update hearts
for (let i = hearts.length-1; i>=0; i--) {
let h = hearts[i];
h.life--;
h.x += h.vx;
h.y += h.vy;
if (h.life <= 0) {
hearts.splice(i,1);
}
}
// update ball
ball.x += ball.vx;
ball.y += ball.vy;
// bounce off side walls
if (ball.x - BALL_RADIUS <= 0 || ball.x + BALL_RADIUS >= canvas.width) {
ball.vx = -ball.vx;
}
// bounce off top
if (ball.y - BALL_RADIUS <= 0) {
ball.vy = -ball.vy;
}
// bounce off paddle
if (ball.y + BALL_RADIUS >= paddle.y && ball.x >= paddle.x && ball.x <= paddle.x + PADDLE_WIDTH) {
ball.vy = -ball.vy;
}
// check brick collisions
for (let i=0; i<bricks.length; i++) {
let b = bricks[i];
if (!b.alive) continue;
if (ball.x > b.x && ball.x < b.x + b.w && ball.y > b.y && ball.y < b.y + b.h) {
ball.vy = -ball.vy;
b.alive = false;
spawnHearts(b.x + b.w/2, b.y + b.h/2);
// optional: break after first brick hit
// break;
}
}
// AI paddle movement
let targetX = ball.x - PADDLE_WIDTH/2;
if (targetX < 0) targetX = 0;
if (targetX > canvas.width - PADDLE_WIDTH) targetX = canvas.width - PADDLE_WIDTH;
paddle.x += (targetX - paddle.x) * 0.1;
// clamp
if (paddle.x < 0) paddle.x = 0;
if (paddle.x > canvas.width - PADDLE_WIDTH) paddle.x = canvas.width - PADDLE_WIDTH;
// drawing
ctx.clearRect(0,0,canvas.width,canvas.height);
// bricks
for (let b of bricks) {
if (b.alive) {
ctx.fillStyle = b.color;
ctx.fillRect(b.x, b.y, b.w, b.h);
}
}
// paddle
drawPaddle(ctx, paddle.x, paddle.y);
// hearts
for (let h of hearts) {
ctx.save();
ctx.globalAlpha = h.life / HEART_MAX_LIFE;
ctx.font = `${HEART_SIZE}px Arial`;
ctx.fillStyle = '#ff0000';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('♥', h.x, h.y);
ctx.restore();
}
// ball
ctx.fillStyle = '#00ff00';
ctx.beginPath();
ctx.arc(ball.x, ball.y, BALL_RADIUS, 0, Math.PI*2);
ctx.fill();
// win check
const aliveCount = bricks.reduce((sum, b) => b.alive ? sum+1 : sum, 0);
if (aliveCount === 0) {
ctx.fillStyle = '#fff';
ctx.font = '32px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText('You Win!', canvas.width/2, canvas.height/2);
}
animationId = requestAnimationFrame(gameLoop);
}
function onClick(e) {
if (!started) {
startGame();
}
}
// initial start not started; wait for click.
}
</script>
Now check for any syntax errors.
Potential issues:
- The canvas width and height may be 0 initially if container not sized yet. But after load, container clientWidth and clientHeight are defined.
- We use const inside init; functions defined inside init have access to outer variables due to closure. That's okay.
- The drawPaddle function uses PADDLE_WIDTH etc. That's fine.
- The paddle width is PADDLE_WIDTH constant; but the drawPaddle uses PADDLE_WIDTH defined in outer scope. However, we defined it inside init; it's accessible.
- The hearts fade out using globalAlpha. We set ctx.textBaseline = 'middle' for hearts, but we also set ctx.textAlign = 'center'.
- In startGame, we set paddle.y = canvas.height - PADDLE_HEIGHT. That's correct.
- In gameLoop, we use paddle.y in paddle draw call.
- In gameLoop, we check brick collision: if ball.x > b.x && ball.x < b.x + b.w && ball.y > b.y && ball.y < b.y + b.h. That works.
- In hearts drawing, we use ctx.textBaseline = 'middle'. Good.
- The hearts are drawn at h.x, h.y. The coordinates are absolute in canvas pixel space.
- The hearts may be drawn at positions that go out of canvas. That's okay; they will be partially visible.
- The win condition draws "You Win!" but game continues? It doesn't stop; but we can leave it.
- The animation loop continues. When bricks are all destroyed, hearts may still be displayed from bricks that were destroyed earlier. That's okay.
- The paddle may be drawn even if game ended; but it's fine.
- The canvas container uses max-width 1280px; adapt down.
Potential improvements: The paddle is a simple rectangular burger shape; maybe we could add more detail, but not needed.
One note: In drawPaddle we draw a black rectangle after colored rectangles, causing a border? Actually we fill a black rectangle over the previous colors, which would cover them. That's not good. We wanted just the burger shape. So we should remove that black fill. Instead, we might want to draw the hamburger shape with the colored rectangles only. The black fill was intended maybe to give thickness, but it overwrote colors. We should not draw black rectangle. Remove that. Or we can draw border lines.
Let's just remove the black fillRect. So drawPaddle only draws colored rectangles.
Thus:
function drawPaddle(ctx, x, y) {
ctx.fillStyle = '#d9a35e';
ctx.fillRect(x, y, PADDLE_WIDTH, 10);
ctx.fillStyle = '#c0392b';
ctx.fillRect(x, y+10, PADDLE_WIDTH, 8);
ctx.fillStyle = '#d9a35e';
ctx.fillRect(x, y+18, PADDLE_WIDTH, 10);
}
Also we could add a thin black outline for definition, but not needed.
Now check for any leftover console errors: The code uses requestAnimationFrame and references animationId. All variables defined.
Now let's double-check the HTML structure.
We have <canvas id="canvas"></canvas> inside container. The container is <div id="container">.
In CSS, we set container to max-width 1280px, margin auto, padding-bottom 75% to maintain aspect ratio.
Now canvas has width 100% height 100% inside container.
Now in JavaScript, we attach resizeCanvas on window.resize. But we also need to call resizeCanvas on load. Good.
But there is a nuance: after we set canvas.width = cw, canvas.height = ch, the CSS style width/height are not updated. That's okay; we set canvas style to fill container, but the canvas dimensions (internal resolution) are set to cw,ch. So drawing will be scaled to fit container.
Now check for potential bug: The paddle.width PADDLE_WIDTH is defined as a constant. In drawPaddle we use that constant. It's accessible.
Now check for potential closure scope: inside init, we define many functions. In drawPaddle, we refer to PADDLE_WIDTH, PADDLE_HEIGHT. That's fine.
Now check for any undeclared variables: In gameLoop we refer to GAP, PADDING, etc. These are defined inside init and captured by inner functions. So all good.
Now check for potential naming conflict: In startGame we use const rows, cols. That's fine.
Now check for the hearts drawing: In hearts drawing, we use ctx.textBaseline = 'middle' and ctx.textAlign = 'center' inside loop. That's fine.
Now check for potential performance: loops over hearts and bricks each frame; it's fine.
Now check for startGame: we set started = true; but we also need to stop any previous animation? There is only one animation running. It's fine.
Now check for click handling: The canvas click listener calls onClick. However, because we have container and canvas, clicking on the container may also trigger click on canvas if the canvas occupies entire container. It's okay.
Now check for keyboard usage: we don't listen for keyboard events, so the requirement about keyboard not needed.
Now check for "self-playing demo mode (a simple AI plays) behind a "Click or press a key to start" message". Our AI moves paddle automatically towards ball. That's the self-playing demo. The start message appears before click, then after click, AI starts.
Thus meets requirements.
Now we need to ensure that code is entirely inside <script> tag. This script is placed at bottom of body.
Now we need to ensure that there is exactly one fenced code block (the HTML). The system expects one code block with the entire file.
Thus we should output something like:
```html
<!DOCTYPE html>
<html ...>
...
</html>
```
with all code inside.
We must ensure we output only the code block, no additional text before or after.
Thus the final answer will be a fenced code block with the HTML.
Now we need to ensure that we have no stray characters.
Let's put the code in the final answer.
Before that, we need to double-check and possibly debug some issues:
- In drawPaddle, the top bun is 10 height, patty 8, bottom bun 10, total height = 28, but PADDLE_HEIGHT is 20. So we overshot. Actually we need to adjust so that total height equals PADDLE_HEIGHT. So we need to adjust the heights to sum to 20. For example:
- topBunHeight = 8
- pattyHeight = 4
- bottomBunHeight = 8
Total 20.
Thus modify drawPaddle:
ctx.fillStyle = '#d9a35e'; // top bun
ctx.fillRect(x, y, PADDLE_WIDTH, 8);
ctx.fillStyle = '#c0392b'; // patty
ctx.fillRect(x, y+8, PADDLE_WIDTH, 4);
ctx.fillStyle = '#d9a35e'; // bottom bun
ctx.fillRect(x, y+12, PADDLE_WIDTH, 8);
Now total height = 8+4+8 = 20.
Thus fix.
Also the starting y position for paddle should be canvas.height - PADDLE_HEIGHT. So the top of paddle is at that y.
Now hearts spawn: The hearts might spawn at brick center (cx, cy). That's fine.
Now hearts might be drawn with text baseline middle, but need to set ctx.textBaseline correctly.
Also hearts need to be drawn with appropriate size. HEART_SIZE maybe 20px, which is fine.
Now check for the win condition: We check aliveCount === 0, then we draw "You Win!". But after that we still continue the loop, which will keep clearing canvas and drawing bricks (none), hearts (maybe still some), paddle, ball (still moving). It will redraw win text each frame. That may be okay.
But the ball will continue moving and paddle continues. Not ideal, but okay.
Alternatively, we could stop loop. But not needed.
Now check for any other errors.
One more thing: In startGame, we set paddle.y = canvas.height - PADDLE_HEIGHT. But PADDLE_HEIGHT is defined earlier. However, note that we used the variable name PADDLE_HEIGHT, but inside init we define const PADDLE_HEIGHT = 20. Good.
Now check the variable usage inside functions: In drawPaddle, we refer to PADDLE_WIDTH and PADDLE_HEIGHT. They are defined in init and are accessible inside drawPaddle because drawPaddle is defined within init and closure.
Yes.
Now check for requestAnimationFrame usage: It is called after started. That's fine.
Now check for code style: We have used let/const inside init. It's okay.
Now we need to ensure we don't have stray console errors like undefined variables.
Potential missing: In gameLoop, we refer to GAP, but we never use GAP except for bricks placement. Actually we compute brick spacing using GAP but not used in collision detection. That's fine.
Now check for "Click or press a key to start" text: It appears on canvas. The text is white on black background, visible.
Now check for viewport adaptation: The container has max-width 1280px and padding-bottom 75% to maintain aspect ratio. At width less than 375px, container width will be 100% (so full width), padding-bottom remains 75% relative to width, so height scales accordingly. The canvas fills container. Good.
Now check for potential scroll: The container has overflow:hidden, so no horizontal scroll.
Now check for "something meaningful must be visible on first paint": At load, the start screen shows the message. So it's visible.
Now check for "games must start in a self-playing demo mode (a simple AI plays) behind a "Click or press a key to start" message". Our startGame is triggered by click, not automatically. The requirement says "Games must start in a self-playing demo mode (a simple AI plays) behind a "Click or press a key to start" message". That seems to imply that after the user clicks or presses a key, the game should start automatically (the self-playing mode) behind that message (i.e., the message is shown until user clicks). That matches our approach: start message shown initially, after click, game begins with AI. So okay.
One nuance: The instruction "The iframe only receives keyboard events after the user clicks it: if the demo uses the keyboard, display a clear "Click or press a key to start" message." Since we don't use keyboard, it's fine.
Thus, the code should meet all requirements.
Now we need to output the HTML code in a single fenced code block.
We need to ensure that the code block is exactly ```html (with triple backticks). No preceding text.
Let's produce final answer.