Brick breaker · seed-2.0-mini
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 · 4 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
Got it, let's tackle this. First, the user wants a self-contained brick breaker game in index.html using Phaser, no external assets, only Phaser's Graphics API.
First, I need to structure the HTML: include the phaser.min.js script tag, then a style tag for any basic layout, then the script with the game config.
Wait, first, let's outline the game components:
1. Phaser 3 game setup: canvas will fill the window, so set scale mode to fit window.
2. Paddle: use Graphics to draw a rectangle, maybe gray, movable with arrow keys or mouse.
3. Ball: small circle, white, bounces off walls, paddle, bricks.
4. Bricks: rows of colored rectangles, breakable when hit by ball. Each brick can be a Graphics generated texture, or drawn on the fly? Wait, better to use generateTexture so we can have static sprites for bricks, that's more efficient, but since we can't use external assets, generate the textures via Graphics.
5. Score text: use Phaser's Text game object, track score when bricks are broken.
6. Game over / win conditions: when all bricks are broken, or ball falls below paddle.
First, let's start with the basic HTML structure. The index.html will have:
- A style tag to remove margins, make the body full screen, no scrollbars.
- Script tag for phaser.min.js (since it's provided, just src="phaser.min.js")
- Then the main script for the game.
Wait, let's think about the paddle: let's make it about 150px wide, 20px tall, positioned at the bottom middle of the screen. Controls: left/right arrows, and mouse follow so it's easier.
Ball: 12px diameter, starts above the paddle, maybe velocity set at an angle.
Bricks: let's do 8 columns, 6 rows? Wait, no, 10 columns, 5 rows, each brick 70px wide, 25px tall, with padding between them. Colors per row: different colors for each row, like red, orange, yellow, green, blue, purple? Wait, 5 rows, so 5 colors.
Score: display at top left, white text, size 24px.
Let's outline the Phaser scene:
- Preload: wait, no preload needed, since all assets are generated via Graphics.
- Create: set up the paddle, ball, bricks, score text, input handlers.
- Update: handle paddle movement, check for ball collisions, check if ball is out of bounds, check win condition.
Wait, collision handling: Phaser has Arcade Physics, so we need to enable Arcade Physics for all objects.
Let's step through the code:
First, the HTML boilerplate:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Brick Breaker</title>
<style>
body { margin: 0; padding: 0; overflow: hidden; }
</style>
</head>
<body>
<script src="phaser.min.js"></script>
<script>
// Game code here
</script>
</body>
</html>
Now, inside the script tag:
First, define the game config:
const config = {
type: Phaser.AUTO,
width: window.innerWidth,
height: window.innerHeight,
scene: {
preload: preload,
create: create,
update: update
},
physics: {
default: 'arcade',
arcade: {
gravity: { y: 0 },
debug: false
}
}
};
new Phaser.Game(config);
Then preload function: wait, no preload needed, since we generate all textures. Wait, but for the bricks, we can generate a texture for each color, or draw them on the fly? Wait, better to generate a brick texture once per color, then reuse sprites.
Wait, let's make a helper function to generate a brick texture:
function generateBrickTexture(scene, color, width, height) {
const graphics = scene.add.graphics();
graphics.fillStyle(color, 1);
graphics.fillRect(0, 0, width, height);
graphics.lineStyle(2, 0x000000, 1);
graphics.strokeRect(0, 0, width, height);
const textureKey = `brick_${color.toString(16)}`;
graphics.generateTexture(textureKey, width, height);
graphics.destroy();
return textureKey;
}
Then in create:
First, set up the scale to resize if window changes:
window.addEventListener('resize', () => {
game.scale.resize(window.innerWidth, window.innerHeight);
});
Wait, but game is the instance, so maybe better to get it from the scene? Or just store the game instance. Wait, alternatively, in the create function, we can do:
this.scale.on('resize', (gameSize) => {
// adjust game objects? Maybe, but for simplicity, let's keep it simple first, fixed size? No, user probably wants full screen. Wait, but maybe just use 800x600 as a standard size? Wait no, the user said procedurally generated, so better to make it responsive. But maybe for simplicity, let's use 800x600, but adjust so that the paddle, bricks fit. Wait, no, let's make it adapt to window size, but let's calculate the brick size based on window width.
Wait, let's adjust: let's say brick width is (window.innerWidth * 0.9) / 10, so 10 columns, 90% of screen width, padding between bricks. Brick height is 25px. That way it's responsive.
Wait, let's rework the create function:
function preload() {
// Nothing, all textures generated
}
function create() {
// Store references
this.ball = null;
this.paddle = null;
this.bricks = null;
this.score = 0;
this.scoreText = null;
// Create score text
this.scoreText = this.add.text(16, 16, 'Score: 0', { fontSize: '24px', fill: '#ffffff' });
// Create paddle graphics
const paddleWidth = 150;
const paddleHeight = 20;
const paddleGraphics = this.add.graphics();
paddleGraphics.fillStyle(0x888888, 1);
paddleGraphics.fillRect(0, 0, paddleWidth, paddleHeight);
paddleGraphics.lineStyle(2, 0xffffff, 1);
paddleGraphics.strokeRect(0, 0, paddleWidth, paddleHeight);
paddleGraphics.generateTexture('paddle', paddleWidth, paddleHeight);
paddleGraphics.destroy();
// Create paddle sprite
this.paddle = this.physics.add.sprite(this.sys.game.config.width / 2, this.sys.game.config.height - 50, 'paddle');
this.paddle.setImmovable(true);
this.paddle.body.allowGravity = false;
// Create ball graphics
const ballSize = 12;
const ballGraphics = this.add.graphics();
ballGraphics.fillStyle(0xffffff, 1);
ballGraphics.fillCircle(ballSize/2, ballSize/2, ballSize/2);
ballGraphics.generateTexture('ball', ballSize, ballSize);
ballGraphics.destroy();
// Create ball sprite
this.ball = this.physics.add.sprite(this.sys.game.config.width / 2, this.sys.game.config.height - 70, 'ball');
this.ball.setCollideWorldBounds(true);
this.ball.body.bounce.set(1);
this.ball.setVelocity(200, -300);
// Create bricks: 10 columns, 5 rows
const brickCols = 10;
const brickRows = 5;
const brickWidth = (this.sys.game.config.width * 0.9) / brickCols;
const brickHeight = 25;
const brickPadding = 4;
const brickStartX = (this.sys.game.config.width - (brickWidth * brickCols + brickPadding * (brickCols -1))) / 2;
const brickStartY = 50;
// Brick colors per row
const brickColors = [0xff0000, 0xff8800, 0xffff00, 0x00ff00, 0x00aaff];
// Create brick group
this.bricks = this.physics.add.staticGroup();
for (let row = 0; row < brickRows; row++) {
for (let col = 0; col < brickCols; col++) {
const x = brickStartX + col * (brickWidth + brickPadding);
const y = brickStartY + row * (brickHeight + brickPadding);
// Generate texture for this color if not already done
const color = brickColors[row];
const textureKey = `brick_${color.toString(16)}`;
if (!this.textures.exists(textureKey)) {
const gfx = this.add.graphics();
gfx.fillStyle(color, 1);
gfx.fillRect(0, 0, brickWidth, brickHeight);
gfx.lineStyle(2, 0x000000, 1);
gfx.strokeRect(0, 0, brickWidth, brickHeight);
gfx.generateTexture(textureKey, brickWidth, brickHeight);
gfx.destroy();
}
// Create brick sprite
const brick = this.bricks.create(x, y, textureKey);
brick.body.allowGravity = false;
}
}
// Add collisions: ball vs paddle, ball vs bricks
this.physics.add.collider(this.ball, this.paddle, hitPaddle, null, this);
this.physics.add.collider(this.ball, this.bricks, hitBrick, null, this);
// Input controls: mouse and keyboard
this.input.keyboard.on('keydown_LEFT', () => {
this.paddle.setVelocityX(-500);
});
this.input.keyboard.on('keydown_RIGHT', () => {
this.paddle.setVelocityX(500);
});
this.input.keyboard.on('keyup_LEFT', () => {
this.paddle.setVelocityX(0);
});
this.input.keyboard.on('keyup_RIGHT', () => {
this.paddle.setVelocityX(0);
});
// Mouse control: follow mouse X
this.input.on('pointermove', (pointer) => {
this.paddle.x = Phaser.Math.Clamp(pointer.x, paddleWidth/2, this.sys.game.config.width - paddleWidth/2);
});
// Check if ball falls below screen
this.time.addEvent({
delay: 100,
callback: checkBallOut,
callbackScope: this,
loop: true
});
// Check win condition
checkWinCondition.call(this);
}
Then the helper functions: hitPaddle, hitBrick, checkBallOut, checkWinCondition.
First, hitPaddle: when ball hits paddle, adjust the angle based on where it hit the paddle, so it's more fun. Like, if it hits left side, go left, right side go right.
function hitPaddle(ball, paddle) {
// Calculate offset from center of paddle
const diff = ball.x - paddle.x;
ball.setVelocityX(5 * diff);
// Make sure Y velocity is always negative (going up)
if (ball.body.velocity.y > 0) {
ball.setVelocityY(-ball.body.velocity.y);
}
}
Then hitBrick: when ball hits a brick, destroy the brick, add to score, update text.
function hitBrick(ball, brick) {
brick.disableBody(true, true);
this.score += 10;
this.scoreText.setText(`Score: ${this.score}`);
}
CheckBallOut: if ball goes below the paddle (or below screen), reset the ball.
function checkBallOut() {
if (this.ball.y > this.sys.game.config.height + 20) {
// Reset ball position
this.ball.setPosition(this.paddle.x, this.paddle.y - 20);
this.ball.setVelocity(200, -300);
// Subtract score? Or just reset? Maybe subtract 50 points?
this.score = Math.max(0, this.score - 50);
this.scoreText.setText(`Score: ${this.score}`);
}
}
CheckWinCondition: if all bricks are destroyed, show win text.
function checkWinCondition() {
const activeBricks = this.bricks.getChildren().filter(brick => brick.active).length;
if (activeBricks === 0) {
this.add.text(this.sys.game.config.width / 2, this.sys.game.config.height / 2, 'You Win!', { fontSize: '64px', fill: '#ffffff' }).setOrigin(0.5);
this.ball.setVelocity(0, 0);
this.paddle.setVelocity(0, 0);
} else {
this.time.addEvent({
delay: 1000,
callback: checkWinCondition,
callbackScope: this,
loop: false
});
}
}
Wait, also, in the update function: we need to handle keyboard movement for paddle, because the keydown events only fire once, not hold. Oh right, so the keyboard control should be in update, not just keydown/keyup.
Ah, right! So let's add a variable to track left/right keys: in create, add:
this.cursors = this.input.keyboard.createCursorKeys();
Then in update function:
function update() {
// Handle keyboard movement
if (this.cursors.left.isDown) {
this.paddle.setVelocityX(-500);
} else if (this.cursors.right.isDown) {
this.paddle.setVelocityX(500);
} else {
this.paddle.setVelocityX(0);
}
// Clamp paddle to screen bounds
this.paddle.x = Phaser.Math.Clamp(this.paddle.x, 75, this.sys.game.config.width -75);
}
Wait, because paddleWidth is 150, so half is 75, so clamp to 75 to width-75 so it doesn't go off screen.
Also, the mouse control: in the pointermove handler, we set paddle.x to pointer.x, but clamp it, so that's already handled.
Wait, let's fix the paddle clamp in both keyboard and mouse: for keyboard, in update, we should clamp the paddle position, same as mouse.
Yes, so the update function's paddle clamp is good.
Also, when we reset the ball, we should set its position to the paddle's x, so it's on top of the paddle, then give it a random angle? Or fixed, but the current code sets velocity 200, -300, which is okay.
Wait, also, the ball's world bounds collision: we set setCollideWorldBounds(true), so it bounces off left, right, top walls. Perfect, because the bottom is the out of bounds, which we check in checkBallOut.
Now, let's put all this together, and make sure there are no errors.
Wait, also, in the generateBrickTexture, but actually in the create function, we generate the brick textures on the fly for each color, so that's okay, because we check if the texture exists before creating it.
Wait, another thing: when we create the paddle texture, we use 'paddle' as the key, that's correct.
Also, the ball texture is 'ball', correct.
Now, let's test for possible issues:
- When the ball hits the paddle, the hitPaddle function adjusts the X velocity based on the difference between ball.x and paddle.x, that's a common brick breaker mechanic, makes the game more playable.
- The score is updated when a brick is hit, correct.
- The win condition checks if all bricks are disabled, then shows a win message, stops the ball and paddle.
- The ball reset when it goes out of bounds: subtracts 50 points, which is a penalty for missing the ball.
Now, let's write all this into the index.html file.
Wait, also, the initial code: the config's width and height are window.innerWidth and window.innerHeight, but when the window resizes, we should update the game's scale, which we added a listener for.
Wait, but in Phaser 3, the scale manager has a resize event, so maybe better to use that: in create, add:
this.scale.on('resize', (gameSize) => {
// Update any game objects that depend on size, like bricks, paddle position?
// But for simplicity, let's just let the physics handle it, but actually, the bricks are positioned on create, so resizing won't adjust them. Oh, right, that's a problem. But maybe for a simple game, we can just fix the game size to 800x600, which is standard, so that the layout is consistent. That's easier for a demo.
Ah, right, maybe better to use a fixed size of 800x600, so that the bricks and paddle are properly laid out, instead of responsive. Because if we make it responsive, the brick positions will be wrong on resize. So let's change the config to width: 800, height: 600, that's more standard for a brick breaker game.
Let's adjust the config:
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
scene: {
preload: preload,
create: create,
update: update
},
physics: {
default: 'arcade',
arcade: {
gravity: { y: 0 },
debug: false
}
}
};
That's better, because then all the positions are fixed, and the scale can be set to fit the window: add scale: { mode: Phaser.Scale.FIT, autoCenter: Phaser.Scale.CENTER_BOTH } to the config. Oh right! Phaser has a scale mode that fits the canvas to the window, which is perfect.
So update the config:
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
scale: {
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH
},
scene: {
preload: preload,
create: create,
update: update
},
physics: {
default: 'arcade',
arcade: {
gravity: { y: 0 },
debug: false
}
}
};
That way, the canvas is always fitted to the window, centered, and 800x600 logical size, so all the positions are consistent.
Great, that's better. Now, the paddle position: this.sys.game.config.height - 50, which is 600-50=550, correct. The ball starts at 600-70=530, which is above the paddle, correct.
Brick start Y is 50, so first row is at 50, then each row is 25+4=29px apart, so 5 rows up to 50 + 4*(25+4) = 50 + 4*29= 50+116=166, which is okay.
Now, let's adjust the paddle clamp: since paddleWidth is 150, half is 75, so clamp to 75 to 800-75=725, which is correct.
In the hitPaddle function: ball.setVelocityX(5 * diff); wait, 5 * diff might be too much? Let's see, diff can be up to 400 (if paddle is at 400, ball is at 725, diff is 325, so X velocity would be 1625, which is way too fast. Oh, right, that's a problem. Let's scale it down: maybe 0.3 * diff, so max velocity is 0.3*75=22.5? No, wait, better to use a multiplier like 8, but clamp the X velocity. Let's adjust the hitPaddle function:
function hitPaddle(ball, paddle) {
// Calculate offset from center of paddle, range -0.5 to 0.5
const diff = (ball.x - paddle.x) / (paddle.displayWidth / 2);
// Set X velocity based on diff, max 300
ball.setVelocityX(300 * diff);
// Ensure Y velocity is always upward
if (ball.body.velocity.y > 0) {
ball.setVelocityY(-ball.body.velocity.y);
}
}
Yes, that's better, because diff is normalized between -1 and 1, so 300 * diff gives a max X velocity of 300, which is reasonable.
Also, the initial ball velocity: let's set it to 300, -250, so it's not too fast. Let's change:
this.ball.setVelocity(300, -250);
That's better.
Now, let's also add a start screen? No, the user just wants a playable game, so when the game loads, the ball is already moving, which is okay.
Wait, another thing: when the ball hits the paddle, the Y velocity is reversed if it's positive, which ensures that the ball doesn't get stuck going down through the paddle.
Now, let's put all the code together into the index.html file.
Also, make sure that all the functions are defined inside the scene, or passed correctly. Wait, in the create function, when we add the time event for checkBallOut, we use callbackScope: this, which is correct, so 'this' inside checkBallOut refers to the scene. Same for checkWinCondition.
Wait, the checkWinFunction: when we call it, we use checkWinCondition.call(this), which sets the 'this' context to the scene, correct. Then, the time event in checkWinCondition uses callbackScope: this, which is correct.
Now, let's write the full code:
First, the HTML:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Brick Breaker</title>
<style>
body { margin: 0; padding: 0; overflow: hidden; background: #1a1a1a; }
</style>
</head>
<body>
<script src="phaser.min.js"></script>
<script>
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
scale: {
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH
},
scene: {
preload: preload,
create: create,
update: update
},
physics: {
default: 'arcade',
arcade: {
gravity: { y: 0 },
debug: false
}
}
};
const game = new Phaser.Game(config);
function preload() {
// All textures generated via Graphics API
}
function create() {
this.ball = null;
this.paddle = null;
this.bricks = null;
this.score = 0;
this.scoreText = null;
this.cursors = this.input.keyboard.createCursorKeys();
// Create score display
this.scoreText = this.add.text(16, 16, 'Score: 0', {
fontSize: '24px',
fill: '#ffffff',
fontFamily: 'Arial'
});
// Create paddle texture
const paddleWidth = 150;
const paddleHeight = 20;
const paddleGraphics = this.add.graphics();
paddleGraphics.fillStyle(0x888888, 1);
paddleGraphics.fillRect(0, 0, paddleWidth, paddleHeight);
paddleGraphics.lineStyle(2, 0xffffff, 1);
paddleGraphics.strokeRect(0, 0, paddleWidth, paddleHeight);
paddleGraphics.generateTexture('paddle', paddleWidth, paddleHeight);
paddleGraphics.destroy();
// Create paddle sprite
this.paddle = this.physics.add.sprite(400, 550, 'paddle');
this.paddle.setImmovable(true);
this.paddle.body.allowGravity = false;
// Create ball texture
const ballSize = 12;
const ballGraphics = this.add.graphics();
ballGraphics.fillStyle(0xffffff, 1);
ballGraphics.fillCircle(ballSize / 2, ballSize / 2, ballSize / 2);
ballGraphics.generateTexture('ball', ballSize, ballSize);
ballGraphics.destroy();
// Create ball sprite
this.ball = this.physics.add.sprite(400, 530, 'ball');
this.ball.setCollideWorldBounds(true);
this.ball.body.bounce.set(1);
this.ball.setVelocity(300, -250);
// Create brick configuration
const brickCols = 10;
const brickRows = 5;
const brickWidth = 70;
const brickHeight = 25;
const brickPadding = 4;
const brickStartX = (800 - (brickWidth * brickCols + brickPadding * (brickCols - 1))) / 2;
const brickStartY = 50;
// Brick colors for each row
const brickColors = [0xff4444, 0xff8844, 0xffff44, 0x44ff44, 0x4488ff];
// Create brick group
this.bricks = this.physics.add.staticGroup();
// Generate bricks
for (let row = 0; row < brickRows; row++) {
for (let col = 0; col < brickCols; col++) {
const x = brickStartX + col * (brickWidth + brickPadding);
const y = brickStartY + row * (brickHeight + brickPadding);
const color = brickColors[row];
const textureKey = `brick_${color.toString(16)}`;
// Create texture if it doesn't exist
if (!this.textures.exists(textureKey)) {
const gfx = this.add.graphics();
gfx.fillStyle(color, 1);
gfx.fillRect(0, 0, brickWidth, brickHeight);
gfx.lineStyle(1, 0x000000, 1);
gfx.strokeRect(0, 0, brickWidth, brickHeight);
gfx.generateTexture(textureKey, brickWidth, brickHeight);
gfx.destroy();
}
// Add brick to group
const brick = this.bricks.create(x, y, textureKey);
brick.body.allowGravity = false;
}
}
// Set up collisions
this.physics.add.collider(this.ball, this.paddle, hitPaddle, null, this);
this.physics.add.collider(this.ball, this.bricks, hitBrick, null, this);
// Set up input controls
// Mouse control: follow mouse X position
this.input.on('pointermove', (pointer) => {
this.paddle.x = Phaser.Math.Clamp(pointer.x, paddleWidth / 2, 800 - paddleWidth / 2);
});
// Check if ball is out of bounds
this.time.addEvent({
delay: 100,
callback: checkBallOut,
callbackScope: this,
loop: true
});
// Check win condition
checkWinCondition.call(this);
}
function update() {
// Keyboard control for paddle
if (this.cursors.left.isDown) {
this.paddle.setVelocityX(-500);
} else if (this.cursors.right.isDown()) {
this.paddle.setVelocityX(500);
} else {
this.paddle.setVelocityX(0);
}
// Clamp paddle to screen bounds
this.paddle.x = Phaser.Math.Clamp(this.paddle.x, 75, 725);
}
// Collision handler for paddle
function hitPaddle(ball, paddle) {
// Calculate normalized offset from paddle center
const offset = (ball.x - paddle.x) / (paddle.displayWidth / 2);
// Set horizontal velocity based on offset
ball.setVelocityX(300 * offset);
// Ensure ball always bounces upward
if (ball.body.velocity.y > 0) {
ball.setVelocityY(-ball.body.velocity.y);
}
}
// Collision handler for bricks
function hitBrick(ball, brick) {
brick.disableBody(true, true);
this.score += 10;
this.scoreText.setText(`Score: ${this.score}`);
}
// Check if ball fell below the screen
function checkBallOut() {
if (this.ball.y > 620) {
// Reset ball to paddle position
this.ball.setPosition(this.paddle.x, 530);
this.ball.setVelocity(300, -250);
// Subtract penalty points
this.score = Math.max(0, this.score - 50);
this.scoreText.setText(`Score: ${this.score}`);
}
}
// Check if all bricks are destroyed
function checkWinCondition() {
const activeBricks = this.bricks.getChildren().filter(brick => brick.active).length;
if (activeBricks === 0) {
this.add.text(400, 300, 'You Win!', {
fontSize: '64px',
fill: '#ffffff',
fontFamily: 'Arial'
}).setOrigin(0.5);
// Stop ball and paddle
this.ball.setVelocity(0, 0);
this.paddle.setVelocity(0, 0);
} else {
// Check again after 1 second
this.time.addEvent({
delay: 1000,
callback: checkWinCondition,
callbackScope: this,
loop: false
});
}
}
</script>
</body>
</html>
Wait, in the update function, there's a typo: this.cursors.right.isDown(), no, it's this.cursors.right.isDown, without parentheses. Oh right! Because cursors.right is a key object, and isDown is a property, not a function. So fix that line:
} else if (this.cursors.right.isDown) {
Yes, that's a mistake. Let's correct that in the update function:
function update() {
// Keyboard control for paddle
if (this.cursors.left.isDown) {
this.paddle.setVelocityX(-500);
} else if (this.cursors.right.isDown) {
this.paddle.setVelocityX(500);
} else {
this.paddle.setVelocityX(0);
}
// Clamp paddle to screen bounds
this.paddle.x = Phaser.Math.Clamp(this.paddle.x, 75, 725);
}
That's fixed.
Another thing: in the checkBallOut function, the threshold is 620, which is 600 + 20, so the ball is 20px below the screen, correct.
Also, the brick line style: I changed it to 1px, which is better than 2px, looks cleaner.
Also, added fontFamily to the text objects, so they render properly.
Now, let's test this code: it uses only Phaser's Graphics API to generate all textures, no external assets. The game has:
- Paddle controlled by arrow keys or mouse
- Bouncing ball that bounces off walls and paddle
- Wall of colored breakable bricks
- Score tracking
- Penalty for missing the ball
- Win condition when all bricks are broken
- Responsive canvas that fits the window
Perfect, that's exactly what the user asked for.
Now, I need to write this code into the index.html file in the current directory (/tmp/ws). So use the write tool, with path: "/tmp/ws/index.html", and content as the full HTML code above./tmp/ws/index.html (8020 bytes)Successfully wrote 8020 bytes to /tmp/ws/index.html
thinking
Great, the file was written successfully. Let's verify that everything is correct: the index.html file is self-contained, uses phaser.min.js (loaded via script tag with src="phaser.min.js"), all graphics are generated via Phaser's Graphics API, no external assets. The game has all the required features: paddle with arrow key and mouse controls, bouncing ball, breakable bricks, score tracking, win condition, and ball reset on miss. Now, I should confirm that the game is ready to run, and let the user know that they can open index.html in a browser to play it.
Perfect! I've created a fully self-contained brick breaker game in `/tmp/ws/index.html`: 1. **No external assets**: All graphics are generated entirely using Phaser's Graphics API, no images/audio loaded from external sources 2. **Core gameplay**: - Paddle controlled with left/right arrow keys *or* mouse movement - Bouncing ball that bounces off walls, paddle, and bricks - 5 rows of colorful breakable bricks - Score tracking that increases when bricks are destroyed, with penalties for missing the ball - Win condition when all bricks are broken 3. **Responsive design**: The canvas fits automatically to your browser window while maintaining the 800x600 logical game size To play the game, just open `index.html` in any modern web browser that has the included `phaser.min.js` file in the same directory.
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
Classic brick-breaker with paddle, ball physics, and colorful bricks breaking over time; ball animates and bricks disappear correctly.
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.