Brick breaker · step-3.7-flash
Using the pre-provided phaser.min.js (global Phaser), build a playable brick-breaker game with a paddle (arrow keys or mouse), a bouncing ball, and a wall of breakable bricks with a score. Use ONLY procedurally generated graphics via the Phaser Graphics API or generated textures — no external image or audio assets whatsoever.
transcript · 8 messages
Using the pre-provided phaser.min.js (global Phaser), build a playable brick-breaker game with a paddle (arrow keys or mouse), a bouncing ball, and a wall of breakable bricks with a score. Use ONLY procedurally generated graphics via the Phaser Graphics API or generated textures — no external image or audio assets whatsoever.
thinking
The user wants a single-page brick-breaker (Breakout) web app built with Phaser, loaded from a local phaser.min.js. Everything must be self-contained in one index.html: CSS in <style>, JS in <script>, no external resources/CDNs, no image/audio assets. I need to check whether phaser.min.js exists, then create the game.
Key design choices:
- Platformer / paddle game
- Paddle: left/right arrow keys, or mouse/touch drag
- Ball physics: bouncing off walls, paddle, bricks.
- Brick wall: rows/columns, breakable on hit, score pop (text).
- Losing: ball goes below paddle. Restart or lose life.
- Use generated textures: create graphics objects and generate textures, or draw geometry at runtime via Camera, Graphics, etc. Since we have Phaser 3, we can use:
- scene.make.graphics({ add: false }) to draw a rectangle/circle, then generateTexture('name').
- Then use sprites or images from those generated textures.
- No external loading needed.
- Clean arcade physics.
- Simple scaling to fit canvas but fixed logical resolution (e.g., 800x600).
Let's inspect workspace to see if phaser.min.js exists.{"command":"ls -la /workspace"}total 2116 drwxrwxrwx 3 node node 96 Jul 22 17:05 . drwxr-xr-x 1 root root 4096 Jul 22 17:05 .. -rw-r--r-- 1 node node 1181917 Jul 22 17:05 phaser.min.js
thinking
Great, phaser.min.js exists. I need to write index.html with a complete game. Let's think about how to structure this.
Game design:
- Game title: "Blast Breaker"
- Scene: game scene
- Logical size: 800x640 or so. Let's use 800 x 600.
- Background dark slate color drawn via graphics.
Physics:
- Arcade physics.
- Paddle: a rectangle body with immovable.
- Ball: circle body, bounce on world bounds except bottom.
- Bricks: static bodies, break on ball collision.
- Score text top-left.
- Restart on ball loss.
Controls:
- Keyboard: left arrow / right arrow.
- Pointer: drag paddle horizontally.
- Touch: ideally pointer works.
Appearance:
- Paddle: gradient blue rectangle, generated texture.
- Ball: radial-gradient-ish effect, generated texture (white/cyan).
- Bricks: row colors (e.g., classic colors) generated textures.
- Background: subtle grid or just solid dark with vignette.
Procedural graphics generation:
- Use scene.make.graphics() then generateTexture(key, width, height).
- Paddle texture: draw rounded rect with gradient fill or simple fill with stroke.
- Ball texture: fill circle with lighter inner circle for highlight.
- Brick textures: fillRect with colors, optional border.
Restarting:
- When ball goes below paddle, reset ball and paddle or restart scene.
No image or audio assets. Use generated sprites.
Potential issues:
- Need to ensure no external fonts etc. Use system font (Arial, sans-serif) via CSS.
- Colors via fillColor.
Let's draft the code.
HTML structure:
- <html><head><meta charset="utf-8"><title>Blast Breaker</title><style> ... </style></head><body>
- <script src="phaser.min.js"></script>
- <script>
// game code
- </script>
- </body></html>
Style:
- margin: 0; padding: 0; background: #0b0f19; display: flex; justify-content: center; align-items: center; height: 100vh; overflow: hidden;
- maybe some shadow around canvas.
Phaser config:
- type: Phaser.AUTO,
- width: 800, height: 600,
- parent: maybe body (will be appended)
- backgroundColor: '#111827',
- physics:
default: 'arcade',
arcade: { gravity: { y: 0 }, debug: false }
- scale: not important but we can set scale mode to FIT or just let it be.
Scene class:
class GameScene extends Phaser.Scene {
constructor() { super('GameScene'); }
preload() {
// Generated textures here or in create before we use them.
// Typically can be done in create.
}
create() {
// 1. Input
this.cursors = this.input.keyboard.createCursorKeys();
// pointer move
this.input.on('pointermove', (pointer) => { ... });
// 2. Generate textures
this.generateTextures();
// 3. Background stars/particles via graphics? Could do simple dots. But keep it simple.
// 4. Paddle
this.paddle = this.physics.add.sprite(400, 560, 'paddle').setImmovable(true).setCollideWorldBounds(true);
this.paddle.body.allowGravity = false;
// 5. Ball
this.ball = this.physics.add.sprite(400, 520, 'ball');
this.ball.setCollideWorldBounds(true);
this.ball.setBounce(1);
// prevent ball from getting stuck? maybe set a velocity.
// 6. Bricks
this.bricks = this.physics.add.staticGroup();
const colors = [0xff595e, 0xffca3a, 0x8ac926, 0x1982c4, 0x6a4c93];
const rows = 5;
const cols = 9;
const brickWidth = 70;
const brickHeight = 26;
const offsetX = (800 - (cols * (brickWidth + 4))) / 2 + brickWidth/2;
const offsetY = 80;
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
const x = offsetX + c * (brickWidth + 4);
const y = offsetY + r * (brickHeight + 4);
const brick = this.bricks.create(x, y, 'brick_' + colors[r] );
// Actually we need unique textures per color or tinted. Easiest: create multiple textures.
}
}
// 7. Colliders
this.physics.add.collider(this.ball, this.paddle, this.hitPaddle, null, this);
this.physics.add.collider(this.ball, this.bricks, this.hitBrick, null, this);
// 8. Score
this.score = 0;
this.scoreText = this.add.text(16, 16, 'Score: 0', { fontSize: '24px', fill: '#ffffff' }).setDepth(1);
// 9. Launch ball on pointer down or space or click?
this.canLaunch = false; // after start.
this.input.on('pointerdown', () => { if(!this.ballLaunched) this.launchBall(); });
// 10. if ball goes below world
this.events.on('worldbounds', (body, blockedUp, blockedDown, blockedLeft, blockedRight) => { ... });
// But world bounds event may not be simplest. Instead in update check ball.y > 600 + margin.
}
generateTextures() {
// paddle
const g1 = this.make.graphics({ add: false });
g1.fillStyle(0x3b82f6);
g1.fillRoundedRect(0, 0, 100, 20, 10);
// add shine highlight
g1.fillStyle(0x93c5fd, 0.4);
g1.fillRoundedRect(4, 2, 92, 8, 6);
g1.generateTexture('paddle', 100, 20);
// ball
const g2 = this.make.graphics({ add: false });
g2.fillStyle(0xffffff);
g2.fillCircle(10, 10, 10);
g2.fillStyle(0xa5f3fc, 0.6);
g2.fillCircle(8, 7, 4);
g2.generateTexture('ball', 20, 20);
// bricks colors
const colors = [0xff5e5e, 0xffbe4f, 0x7be66a, 0x4fa1d6, 0x9b7cd8];
colors.forEach((c, i) => {
const g = this.make.graphics({ add: false });
g.fillStyle(c);
g.fillRect(0, 0, 70, 26);
g.fillStyle(0xffffff, 0.2);
g.fillRect(0, 0, 70, 6);
g.lineStyle(1, 0x000000, 0.15);
g.strokeRect(0, 0, 70, 26);
g.generateTexture('brick_' + i, 70, 26);
});
}
update() {
// paddle movement
const speed = 450;
if (this.cursors.left.isDown) {
this.paddle.setVelocityX(-speed);
} else if (this.cursors.right.isDown) {
this.paddle.setVelocityX(speed);
} else {
this.paddle.setVelocityX(0);
}
// pointer movement
if (this.input.activePointer.isDown) {
// We can directly set paddle x toward pointer x
const pointerX = Phaser.Math.Clamp(this.input.activePointer.x, 50, 750);
this.paddle.x = pointerX;
}
// ball out
if (this.ball.y > 620) {
this.resetBall();
}
}
launchBall() {
if (this.ballLaunched) return;
this.ballLaunched = true;
this.ball.setVelocity(Phaser.Math.Between(-100, 100), -350);
}
resetBall() {
this.ball.enableBody(false, 400, 520, true, true);
this.ball.setVelocity(0, 0);
this.ballLaunched = false;
}
hitPaddle(ball, paddle) {
// Adjust angle based on where it hits
let diff = ball.x - paddle.x;
// max angle 60 degrees
diff = Phaser.Math.Clamp(diff, -50, 50);
// normalize to -1..1
const angle = (diff / 50) * (Math.PI / 3); // 60 deg radians
const speed = 380;
ball.setVelocityX(Math.sin(angle) * speed);
ball.setVelocityY(-Math.cos(angle) * speed);
}
hitBrick(ball, brick) {
brick.disableBody(true, true);
this.score += 10;
this.scoreText.setText('Score: ' + this.score);
// Win?
if (this.bricks.countActive() === 0) {
this.showWin();
}
}
showWin() {
this.physics.pause();
this.add.text(260, 250, 'YOU WIN!', { fontSize: '48px', fill: '#fff' });
this.time.delayedCall(2000, () => {
this.scene.restart();
});
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
backgroundColor: '#111827',
parent: document.body,
physics: {
default: 'arcade',
arcade: { gravity: { y: 0 }, debug: false }
},
scene: GameScene
};
const game = new Phaser.Game(config);
Need to ensure no external loading and everything self-contained.
Also ensure pointer drag works cleanly: we can set velocity to 0 when pointer down.
Let's refine:
- Use a nice start message.
- Score formatting.
- Maybe add particle effect on brick destroy using simple small graphics? Could use emitter with 'ball' small? but to keep simple, just disable.
Be careful with "ball.disableBody" vs "destroy" in colliders. static group disableBody is fine.
World bounds:
- In arcade, setCollideWorldBounds(true) on ball. But by default, bottom will bounce. We don't want bottom bounce. We can set world bounds on physics? Actually Arcade Physics has "setCollideWorldBounds(true)" and the physics world itself has bounds. In Phaser 3, world bounds are defined by game size. We can listen to WorldBounds by adding `this.physics.world.on('worldbounds', ...)` but the default bounce on bottom will happen. To allow it to go below, we shouldn't set collide world bounds true, but check manually collision with top/left/right walls via invisible walls, OR setCollideWorldBounds and handle out-of-bounds manually.
Simpler: keep setCollideWorldBounds(false) and add 4 static invisible walls around the play area (top, left, right, maybe bottom?) plus a bottom sensor that triggers lose. But that's more work.
Alternative: use setCollideWorldBounds(true) and inside update, if ball.y > height + 20, treat as lost and reset. The ball will bounce off the bottom wall though. Not ideal.
Better: setCollideWorldBounds on ball only, and override `ball.body.checkCollision.down = false;` so it ignores bottom wall. Then it will pass through bottom. Perfect.
So:
this.ball.setCollideWorldBounds(true);
this.ball.body.checkCollision.down = false;
Then in update check if y > 600 + 20 -> lost.
Also ensure paddle body.allowGravity = false.
For mouse dragging paddle: we shouldn't directly set x while also applying velocity from keyboard. Could just let pointer control set velocity toward pointer. But direct position is easier for mouse. To avoid conflict, we can track: if pointer down, ignore keyboard. So:
update:
if (this.input.activePointer.isDown) {
const targetX = Phaser.Math.Clamp(this.input.activePointer.x, this.paddle.width/2, 800 - this.paddle.width/2);
this.paddle.x = targetX;
this.paddle.setVelocityX(0);
} else {
// keyboard
}
Touch works same as pointer.
Ball texture can be just a pixel with glow. But procedurally generated circle is fine.
Paddle texture: width 100, height 20. Coordinates: draw with fillStyle.
Bricks:
- 5 rows, 9 columns.
- Width 70, height 26, gap 4.
- Start x: (800 - (9*74)) /2 + 35 = (800 - 666)/2 +35 = 67 + 35 = 102? Let's compute: 9*(70+4)=666. center offset_x = (800-666)/2 +35 = 67 +35 = 102.
- y start 80.
- Generate 5 brick textures with different colors.
Score: use '20px' or '24px'.
Lives? Could add hearts. But just score is fine.
Maybe add "Press SPACE or Click to Launch" text. After launch hide it.
When ball lost, brief flash and reset. Or reset directly.
Let's add a simple background starfield using a static group of tiny white dots generated via graphics texture or small circles with alpha. Could use a simple render texture.
Procedural starfield:
- Create a 800x600 render texture or just loop with many 1px circles. But rendering 500 individual graphics would be slow if sprites. Simpler: use a single Graphics and draw many small circles, then generate a texture.
- E.g., const bg = this.make.graphics({add:false}); bg.fillStyle(0x0b1220); bg.fillRect(0,0,800,600); for stars: small circles with low alpha. Then generateTexture('bg',800,600); this.add.image(400,300,'bg');
Let's add a subtle grid too. That's easy.
Need to ensure the rectangular block for ball texture is larger than ball radius to avoid clip? generateTexture with circle drawn inside.
Writing the full code.
Potential issue: the ball.disableBody call inside collider may cause error if ball.enableBody is used. In Phaser 3, when destroying/disabling a physics sprite in arcade collider, you can call `brick.disableBody(true, true)`. It should be fine. `ball` object refrence is still valid. But we shouldn't call enableBody with bool? In Phaser 3.60+, disableBody(true,true) is fine. resetBall should use `ball.enableBody(false, 400, 520, true, true)`. Actually `enableBody(reset, x, y, enableGameObject, showGameObject)`. Let's confirm typical usage:
```js
this.ball.enableBody(false, 400, 520, true, true);
```
I think that's fine.
HitPaddle logic:
- Ensure ball doesn't get stuck inside paddle. In arcade collider, defaults should separate. The angle adjustment is immediate. Should be fine.
Edge case: ball moving too horizontal? It might get in a loop. BounceAngle in arcade is not present like in p2. But we manually set velocity each hit.
We should also stop ball from deep sag. After launch, gravity doesn't apply.
Let's write the HTML.
One more thing: We need to define global Phaser (it's already loaded). Need to set `new Phaser.Game(config)` after Phaser is globally available.
Also ensure no module bundler or import.
CSS (inline):
- Dark body background.
- Center canvas.
- No select.
Let's produce the file./workspace/index.html (11340 bytes)Successfully wrote 11340 bytes to /workspace/index.html
thinking
The file has been written successfully. Let's quickly verify its contents to ensure correctness and that everything is self-contained.
{"command":"head -n 50 /workspace/index.html && echo \"...\" && tail -n 20 /workspace/index.html"}<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Blast Breaker</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } html, body { height: 100%; background: #0b0f19; overflow: hidden; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; display: flex; align-items: center; justify-content: center; } canvas { display: block; border-radius: 12px; box-shadow: 0 20px 60px rgba(0,0,0,0.6), 0 0 0 1px rgba(255,255,255,0.05); max-height: 100vh; max-width: 100vw; } </style> </head> <body> <script src="phaser.min.js"></script> <script> class GameScene extends Phaser.Scene { constructor() { super('GameScene'); this.ballLaunched = false; this.lives = 3; this.score = 0; } create() { // Generate textures before using them this.generateTextures(); this.createBackground(); // score this.scoreText = this.add.text(20, 20, 'Score: 0', { fontSize: '22px', fontFamily: 'Arial, sans-serif', color: '#e2e8f0', stroke: '#000000', strokeThickness: 3 }).setDepth(1).setShadow(0, 2, 'rgba(0,0,0,0.6)', 0); ... height: 600, backgroundColor: '#0f172a', scale: { mode: Phaser.Scale.FIT, autoCenter: Phaser.Scale.CENTER_BOTH }, physics: { default: 'arcade', arcade: { gravity: { y: 0 }, debug: false } }, scene: [GameScene], transparent: false }; window.onload = () => { const game = new Phaser.Game(config); }; </script> </body> </html>
thinking
Everything looks good. I should give a concise summary of what was built.
`/workspace/index.html` is ready and fully self-contained. It loads the local `phaser.min.js` and implements a complete brick-breaker game: - **Procedurally generated assets only** — paddle, ball, bricks, and background starfield are all created with the Phaser Graphics API via `generateTexture`. - **Controls** — Arrow keys or click/tap and drag to move the paddle. - **Launch** — Press Space or click/tap the screen to launch the ball. - **Scoring** — Each brick broken adds 10 points, displayed in the top-left with a scoreboard. - **Lives** — Hearts UI in the top-right; losing the ball costs a life. - **States** — Game Over and Win screens with auto-restart. - **Polish** — Vignette background, brick particles on break, angled paddle deflection, speed normalization, and centralized canvas fitting.
TAKE IT TO CODE
A single self-contained HTML file — copy it and open in any browser, or reuse the prompt with your own pi-agent run.
RUN
EVALUATION
Fully functional brick-breaker with paddle, ball, colorful brick wall, score tracking, and lives, responding well to all inputs.
Vision grade · anthropic/claude-sonnet-5
MEASUREMENTS
Raw readings from the capture, not a verdict — the grade above is the only judgement. Percentages are how much of the screen changed; the measure under-reports sparse motion on a dark background.