Brick breaker · deepseek-v3.2
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 · 28 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
I'll first check the current directory to see what files exist, particularly phaser.min.js. Then I'll create a single index.html with embedded CSS and JS, using Phaser to create a brick breaker game with procedural graphics. Let's start.
{"command":"ls -la"}total 2116 drwxrwxrwx 3 node node 96 Jul 22 17:15 . drwxr-xr-x 1 root root 4096 Jul 22 17:15 .. -rw-r--r-- 1 node node 1181917 Jul 22 17:15 phaser.min.js
thinking
Great, phaser.min.js is present. I'll read it to confirm it's Phaser 3. Let's quickly check the first few lines.
{"path":"phaser.min.js","limit":5}[Line 1 is 1.1MB, exceeds 50.0KB limit. Use bash: sed -n '1p' phaser.min.js | head -c 51200]
thinking
Probably minified. We'll assume it's Phaser 3. Now I'll create the index.html file. The game will have:
- Canvas with fixed size (say 800x600)
- Paddle controlled by arrow keys or mouse (touch)
- Ball that bounces off walls, paddle, bricks
- Bricks arranged in a grid, each breakable, with colors
- Score display
- Lives maybe
- Game over/win states
- All graphics drawn with Phaser Graphics (rectangles, circles)
- Possibly generate textures for bricks, paddle, ball using Graphics and then use them as sprites.
We'll implement in a single HTML file. Let's craft the HTML skeleton, include phaser.min.js, and write the game code.
I'll create index.html now. Let's write content step by step. First, I'll write the full file in one go. Let's think about the structure:
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Phaser Brick Breaker</title>
<style>
body { margin: 0; padding: 0; background: #111; display: flex; justify-content: center; align-items: center; min-height: 100vh; }
canvas { display: block; }
</style>
</head>
<body>
<script src="phaser.min.js"></script>
<script>
// Game code
</script>
</body>
</html>
```
Now I need to write the game code. I'll use Phaser 3's config and scene. Let's write the full game.
I'll start with writing the file. Let's do it now.index.html (10099 bytes)Successfully wrote 10099 bytes to index.html
thinking
Now I should test the game by opening in a browser, but we can't. However, I can check for any obvious errors. Let's ensure the file includes proper references and the script works. I'll also verify that the generated textures are used correctly. There is a potential issue: generateTexture creates a texture in the texture manager, but we're destroying the graphics object immediately; that's fine. However, we must ensure we don't generate textures each frame. Also, the ball's worldbounds event: the ball hitting bottom triggers loseLife. However, we need to ensure the ball doesn't bounce off bottom wall (we already have worldbounds event). The ball is set to collide with world bounds, and we listen for worldbounds event, checking down flag. But there's a nuance: the ball may bounce off bottom before we lose a life? Actually, the event is triggered when the ball hits the world bounds. We check if down is true, then loseLife. However, we also need to prevent the ball from bouncing off bottom (since we want to reset). The ball's bounce is 1, so it will bounce. We can set a flag or set bounce to 0 for bottom? Instead, we can handle by checking if ball.y >= world bounds height and then call loseLife. Let's adjust: remove the worldbounds event and check in update if ball.y > config.height. But the worldbounds event may be fine: we loseLife and also reset ball. However, the ball may still bounce before we reset it? The event triggers after collision resolution? Might still bounce. Let's change: we'll check in update if ball.y > this.game.config.height - ball.height/2. If ballLaunched and ball.y > paddle.y + something, we loseLife. Simpler: In update, check if ball.y > this.game.config.height (ball off screen). That's typical. Let's modify the code. I'll edit the index.html file. First, let's read the current script portion to see exact lines. I'll extract the relevant parts and replace them. I'll use edit to replace the createBall function and the update function. Let's read the file first to ensure we have the exact content.
{"path":"index.html","offset":100,"limit":200}let livesText; let messageText; let graphics; // Preload - nothing to load, but we'll generate textures function preload() { // No external assets } // Create game objects function create() { // Create a graphics object for drawing shapes graphics = this.add.graphics(); // Create paddle createPaddle(this); // Create ball createBall(this); // Create bricks createBricks(this); // Input cursors = this.input.keyboard.createCursorKeys(); spaceKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE); this.input.on('pointermove', (pointer) => { paddle.x = Phaser.Math.Clamp(pointer.x, paddle.width/2, this.game.config.width - paddle.width/2); }); // UI scoreText = document.getElementById('score'); livesText = document.getElementById('lives'); messageText = document.getElementById('message'); // Launch ball on space this.input.keyboard.on('keydown-SPACE', launchBall, this); // Initial message showMessage('Press SPACE to launch ball', '#0ff'); } function createPaddle(scene) { // Generate a texture for paddle using graphics const paddleGraphics = scene.add.graphics(); paddleGraphics.fillStyle(0x00ffaa, 1); paddleGraphics.fillRoundedRect(0, 0, 100, 20, 10); paddleGraphics.generateTexture('paddle', 100, 20); paddleGraphics.destroy(); paddle = scene.physics.add.sprite(400, 550, 'paddle'); paddle.setImmovable(true); paddle.setCollideWorldBounds(true); } function createBall(scene) { // Generate ball texture const ballGraphics = scene.add.graphics(); ballGraphics.fillStyle(0xffaa00, 1); ballGraphics.fillCircle(16, 16, 16); ballGraphics.lineStyle(4, 0xffff00, 1); ballGraphics.strokeCircle(16, 16, 16); ballGraphics.generateTexture('ball', 32, 32); ballGraphics.destroy(); ball = scene.physics.add.sprite(400, 530, 'ball'); ball.setBounce(1); ball.setCollideWorldBounds(true); ball.setData('onPaddle', true); // Ball will bounce off walls scene.physics.world.on('worldbounds', (body, up, down, left, right) => { if (down) { // Ball hit bottom loseLife(scene); } }); } function createBricks(scene) { // Create a group for bricks bricks = scene.physics.add.group(); const brickWidth = 70; const brickHeight = 30; const brickMargin = 10; const brickOffsetTop = 80; const brickOffsetLeft = 50; const rows = 5; const cols = 10; // Generate brick texture variants const colors = [0xff5555, 0xffaa55, 0xffff55, 0x55ff55, 0x5555ff]; for (let r = 0; r < rows; r++) { // Create a texture for each row color const brickGraphics = scene.add.graphics(); brickGraphics.fillStyle(colors[r], 1); brickGraphics.fillRoundedRect(0, 0, brickWidth, brickHeight, 6); brickGraphics.lineStyle(2, 0xffffff, 0.5); brickGraphics.strokeRoundedRect(0, 0, brickWidth, brickHeight, 6); brickGraphics.generateTexture(`brick${r}`, brickWidth, brickHeight); brickGraphics.destroy(); for (let c = 0; c < cols; c++) { let brick = bricks.create( brickOffsetLeft + c * (brickWidth + brickMargin), brickOffsetTop + r * (brickHeight + brickMargin), `brick${r}` ); brick.setImmovable(true); brick.setData('points', (rows - r) * 10); } } } function launchBall() { if (ballLaunched) return; if (ball.getData('onPaddle')) { ball.setVelocity(Phaser.Math.Between(-200, 200), -300); ball.setData('onPaddle', false); ballLaunched = true; hideMessage(); } } function loseLife(scene) { lives--; updateUI(); if (lives <= 0) { gameOver(scene, false); } else { resetBall(scene); showMessage('Ball lost! Press SPACE to launch', '#ff5555'); } } function resetBall(scene) { ball.setVelocity(0); ball.setPosition(paddle.x, paddle.y - 20); ball.setData('onPaddle', true); ballLaunched = false; } function updateUI() { scoreText.textContent = `Score: ${score}`; livesText.textContent = `Lives: ${lives}`; } function showMessage(text, color = '#fff') { messageText.textContent = text; messageText.style.color = color; messageText.style.display = 'block'; } function hideMessage() { messageText.style.display = 'none'; } function gameOver(scene, win) { ball.setVelocity(0); ball.setVisible(false); if (win) { showMessage('YOU WIN! Score: ' + score, '#0ff'); } else { showMessage('GAME OVER', '#f00'); } // Restart option scene.input.keyboard.once('keydown-SPACE', () => { scene.scene.restart(); score = 0; lives = 3; updateUI(); }); showMessage((win ? 'YOU WIN!' : 'GAME OVER') + ' Press SPACE to restart', win ? '#0ff' : '#f00'); } // Update loop function update() { // Paddle movement with arrow keys if (cursors.left.isDown) { paddle.setVelocityX(-500); } else if (cursors.right.isDown) { paddle.setVelocityX(500); } else { paddle.setVelocityX(0); } // Keep ball on paddle before launch if (ball.getData('onPaddle')) { ball.x = paddle.x; } // Ball collision with paddle this.physics.world.collide(paddle, ball, (paddle, ball) => { // Adjust ball angle based on where it hits the paddle let diff = ball.x - paddle.x; ball.setVelocityX(diff * 5); // Ensure upward direction if (ball.body.velocity.y > 0) { ball.setVelocityY(-Math.abs(ball.body.velocity.y)); } }); [16 more lines in file. Use offset=300 to continue.]
thinking
Let's continue reading the rest of update and see if there's any missing collision. Let's read from line 300.
{"path":"index.html","offset":300,"limit":100}// Ball collision with bricks this.physics.world.collide(ball, bricks, (ball, brick) => { brick.destroy(); score += brick.getData('points'); updateUI(); // Check win condition if (bricks.countActive(true) === 0) { gameOver(this, true); } }); } </script> </body> </html>
thinking
Now I need to modify the createBall function to remove worldbounds event and add a check in update for ball going off bottom. Also we need to ensure ball doesn't bounce off bottom. We can set ball.setCollideWorldBounds(true) but we can also adjust bounce for bottom? Not directly. Instead, we can check in update if ball.y > config.height (or maybe ball.y > paddle.y + 50). Let's add a check after collisions.
Let's edit the createBall function. I'll locate the exact lines and replace.
First, find the exact old text for createBall function. I'll copy from the file lines around that function. Let's read a bit more around line 90-130. Let's use bash to grep line numbers. But easier: I'll edit with a specific oldText that is unique. Let's get the exact function from "function createBall(scene) {" to the closing "}". Let's read lines 90-130 again precisely.{"path":"index.html","offset":90,"limit":50}// Global variables let paddle; let ball; let bricks; let score = 0; let lives = 3; let ballLaunched = false; let cursors; let spaceKey; let scoreText; let livesText; let messageText; let graphics; // Preload - nothing to load, but we'll generate textures function preload() { // No external assets } // Create game objects function create() { // Create a graphics object for drawing shapes graphics = this.add.graphics(); // Create paddle createPaddle(this); // Create ball createBall(this); // Create bricks createBricks(this); // Input cursors = this.input.keyboard.createCursorKeys(); spaceKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE); this.input.on('pointermove', (pointer) => { paddle.x = Phaser.Math.Clamp(pointer.x, paddle.width/2, this.game.config.width - paddle.width/2); }); // UI scoreText = document.getElementById('score'); livesText = document.getElementById('lives'); messageText = document.getElementById('message'); // Launch ball on space this.input.keyboard.on('keydown-SPACE', launchBall, this); // Initial message showMessage('Press SPACE to launch ball', '#0ff'); } [176 more lines in file. Use offset=140 to continue.]
thinking
Let's get the exact createBall function lines. Use bash to find line numbers. Let's do:
{"command":"grep -n \"function createBall\" index.html"}153: function createBall(scene) {
thinking
Now let's view from line 153 to maybe 180.
{"path":"index.html","offset":153,"limit":40}function createBall(scene) { // Generate ball texture const ballGraphics = scene.add.graphics(); ballGraphics.fillStyle(0xffaa00, 1); ballGraphics.fillCircle(16, 16, 16); ballGraphics.lineStyle(4, 0xffff00, 1); ballGraphics.strokeCircle(16, 16, 16); ballGraphics.generateTexture('ball', 32, 32); ballGraphics.destroy(); ball = scene.physics.add.sprite(400, 530, 'ball'); ball.setBounce(1); ball.setCollideWorldBounds(true); ball.setData('onPaddle', true); // Ball will bounce off walls scene.physics.world.on('worldbounds', (body, up, down, left, right) => { if (down) { // Ball hit bottom loseLife(scene); } }); } function createBricks(scene) { // Create a group for bricks bricks = scene.physics.add.group(); const brickWidth = 70; const brickHeight = 30; const brickMargin = 10; const brickOffsetTop = 80; const brickOffsetLeft = 50; const rows = 5; const cols = 10; // Generate brick texture variants const colors = [0xff5555, 0xffaa55, 0xffff55, 0x55ff55, 0x5555ff]; for (let r = 0; r < rows; r++) { // Create a texture for each row color [123 more lines in file. Use offset=193 to continue.]
thinking
Great. I'll replace the createBall function with a version that does not use worldbounds event, and we'll handle bottom detection in update. Also we need to ensure ball doesn't bounce off bottom? Actually we can keep bounce 1 but we will reset ball immediately when it hits bottom. However, the bounce may cause the ball to bounce back up before we detect. We'll detect after collision? Let's just check in update if ball.y > config.height (i.e., ball off screen). That's safe because ball will have moved beyond the bottom bound. But with world bounds, the ball will be stopped at bottom border and bounce back. That's not what we want. So we should disable collision with bottom bound? Not easy. Instead, we can set ball.setCollideWorldBounds(true) but we can adjust bounce for bottom? Not per side. Alternative: we can set a custom boundary using physics walls: add a rectangle sensor at bottom that triggers loseLife. That's simpler. Let's add an invisible sprite at bottom as a sensor and collide with ball. But we can also check each frame if ball.y > config.height - ball.height/2 and ballLaunched. That's fine because the ball may be moving fast and skip beyond the bound in one frame; we still detect. Let's implement: In update, after collisions, check if ball.y > this.game.config.height + ball.height. Actually we can check if ball.y > this.game.config.height (since ball origin is center). Let's use ball.y > this.game.config.height + 20. Now we also need to ensure ball doesn't bounce off bottom before detection. We'll keep collideWorldBounds true but we can set bounce to 1 for all sides; ball will bounce and maybe go back up. That's okay because we will detect after it bounces? It will still be above bottom bound. However, if we detect after it passes beyond bottom bound (ball.y > height) it may never happen because world bounds prevent it from going beyond. So we need to disable collision with bottom bound. Let's set the world bounds to have collision on left, right, top only. How? Phaser Arcade physics doesn't have per-side bounds. But we can set ball.body.checkWorldBounds = true and then listen to worldbounds event, but we can override the bounce for bottom side? Not directly. We can instead create a custom collision group: create a rectangle at bottom as a static sprite with sensor true, and collide with ball. Let's do that: In createBall, create a bottom sensor sprite, add it to physics, set immovable, set sensor true. Then in update we can check collision between ball and bottomSensor. Simpler: In createBall, after creating ball, add a sprite at bottom: let bottomSensor = scene.physics.add.sprite(400, config.height + 10, null).setSize(config.width, 20).setVisible(false); bottomSensor.setImmovable(true); bottomSensor.body.allowGravity = false; bottomSensor.body.isSensor = true; Then in update, collide ball with bottomSensor and call loseLife. But we need to ensure collision only triggers once per fall. We'll use scene.physics.add.overlap(ball, bottomSensor, loseLife, null, scene). That's better. Let's implement. I'll edit createBall function and also add bottomSensor variable globally. Let's edit the file now. First, I'll add a global variable for bottomSensor. Find the global variables section (around line 80). Let's read from line 70 to 90.
{"path":"index.html"}<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Phaser Brick Breaker</title> <style> body { margin: 0; padding: 0; background: #111; display: flex; justify-content: center; align-items: center; min-height: 100vh; font-family: Arial, sans-serif; color: #fff; } #gameContainer { position: relative; } canvas { display: block; border: 2px solid #333; box-shadow: 0 0 20px rgba(0, 200, 255, 0.3); } #ui { position: absolute; top: 10px; left: 10px; font-size: 18px; text-shadow: 1px 1px 2px black; } #score, #lives { margin: 5px 0; } #message { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 36px; text-align: center; background: rgba(0, 0, 0, 0.8); padding: 20px 40px; border-radius: 10px; display: none; } #controls { margin-top: 10px; font-size: 14px; color: #aaa; } </style> </head> <body> <div id="gameContainer"> <div id="ui"> <div id="score">Score: 0</div> <div id="lives">Lives: 3</div> <div id="controls">Move: Arrow keys or Mouse | Space to launch</div> </div> <div id="message"></div> </div> <script src="phaser.min.js"></script> <script> // Game configuration const config = { type: Phaser.AUTO, width: 800, height: 600, parent: 'gameContainer', backgroundColor: '#0a0a1a', physics: { default: 'arcade', arcade: { gravity: { x: 0, y: 0 }, debug: false } }, scene: { preload: preload, create: create, update: update } }; let game = new Phaser.Game(config); // Global variables let paddle; let ball; let bricks; let score = 0; let lives = 3; let ballLaunched = false; let cursors; let spaceKey; let scoreText; let livesText; let messageText; let graphics; // Preload - nothing to load, but we'll generate textures function preload() { // No external assets } // Create game objects function create() { // Create a graphics object for drawing shapes graphics = this.add.graphics(); // Create paddle createPaddle(this); // Create ball createBall(this); // Create bricks createBricks(this); // Input cursors = this.input.keyboard.createCursorKeys(); spaceKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE); this.input.on('pointermove', (pointer) => { paddle.x = Phaser.Math.Clamp(pointer.x, paddle.width/2, this.game.config.width - paddle.width/2); }); // UI scoreText = document.getElementById('score'); livesText = document.getElementById('lives'); messageText = document.getElementById('message'); // Launch ball on space this.input.keyboard.on('keydown-SPACE', launchBall, this); // Initial message showMessage('Press SPACE to launch ball', '#0ff'); } function createPaddle(scene) { // Generate a texture for paddle using graphics const paddleGraphics = scene.add.graphics(); paddleGraphics.fillStyle(0x00ffaa, 1); paddleGraphics.fillRoundedRect(0, 0, 100, 20, 10); paddleGraphics.generateTexture('paddle', 100, 20); paddleGraphics.destroy(); paddle = scene.physics.add.sprite(400, 550, 'paddle'); paddle.setImmovable(true); paddle.setCollideWorldBounds(true); } function createBall(scene) { // Generate ball texture const ballGraphics = scene.add.graphics(); ballGraphics.fillStyle(0xffaa00, 1); ballGraphics.fillCircle(16, 16, 16); ballGraphics.lineStyle(4, 0xffff00, 1); ballGraphics.strokeCircle(16, 16, 16); ballGraphics.generateTexture('ball', 32, 32); ballGraphics.destroy(); ball = scene.physics.add.sprite(400, 530, 'ball'); ball.setBounce(1); ball.setCollideWorldBounds(true); ball.setData('onPaddle', true); // Ball will bounce off walls scene.physics.world.on('worldbounds', (body, up, down, left, right) => { if (down) { // Ball hit bottom loseLife(scene); } }); } function createBricks(scene) { // Create a group for bricks bricks = scene.physics.add.group(); const brickWidth = 70; const brickHeight = 30; const brickMargin = 10; const brickOffsetTop = 80; const brickOffsetLeft = 50; const rows = 5; const cols = 10; // Generate brick texture variants const colors = [0xff5555, 0xffaa55, 0xffff55, 0x55ff55, 0x5555ff]; for (let r = 0; r < rows; r++) { // Create a texture for each row color const brickGraphics = scene.add.graphics(); brickGraphics.fillStyle(colors[r], 1); brickGraphics.fillRoundedRect(0, 0, brickWidth, brickHeight, 6); brickGraphics.lineStyle(2, 0xffffff, 0.5); brickGraphics.strokeRoundedRect(0, 0, brickWidth, brickHeight, 6); brickGraphics.generateTexture(`brick${r}`, brickWidth, brickHeight); brickGraphics.destroy(); for (let c = 0; c < cols; c++) { let brick = bricks.create( brickOffsetLeft + c * (brickWidth + brickMargin), brickOffsetTop + r * (brickHeight + brickMargin), `brick${r}` ); brick.setImmovable(true); brick.setData('points', (rows - r) * 10); } } } function launchBall() { if (ballLaunched) return; if (ball.getData('onPaddle')) { ball.setVelocity(Phaser.Math.Between(-200, 200), -300); ball.setData('onPaddle', false); ballLaunched = true; hideMessage(); } } function loseLife(scene) { lives--; updateUI(); if (lives <= 0) { gameOver(scene, false); } else { resetBall(scene); showMessage('Ball lost! Press SPACE to launch', '#ff5555'); } } function resetBall(scene) { ball.setVelocity(0); ball.setPosition(paddle.x, paddle.y - 20); ball.setData('onPaddle', true); ballLaunched = false; } function updateUI() { scoreText.textContent = `Score: ${score}`; livesText.textContent = `Lives: ${lives}`; } function showMessage(text, color = '#fff') { messageText.textContent = text; messageText.style.color = color; messageText.style.display = 'block'; } function hideMessage() { messageText.style.display = 'none'; } function gameOver(scene, win) { ball.setVelocity(0); ball.setVisible(false); if (win) { showMessage('YOU WIN! Score: ' + score, '#0ff'); } else { showMessage('GAME OVER', '#f00'); } // Restart option scene.input.keyboard.once('keydown-SPACE', () => { scene.scene.restart(); score = 0; lives = 3; updateUI(); }); showMessage((win ? 'YOU WIN!' : 'GAME OVER') + ' Press SPACE to restart', win ? '#0ff' : '#f00'); } // Update loop function update() { // Paddle movement with arrow keys if (cursors.left.isDown) { paddle.setVelocityX(-500); } else if (cursors.right.isDown) { paddle.setVelocityX(500); } else { paddle.setVelocityX(0); } // Keep ball on paddle before launch if (ball.getData('onPaddle')) { ball.x = paddle.x; } // Ball collision with paddle this.physics.world.collide(paddle, ball, (paddle, ball) => { // Adjust ball angle based on where it hits the paddle let diff = ball.x - paddle.x; ball.setVelocityX(diff * 5); // Ensure upward direction if (ball.body.velocity.y > 0) { ball.setVelocityY(-Math.abs(ball.body.velocity.y)); } }); // Ball collision with bricks this.physics.world.collide(ball, bricks, (ball, brick) => { brick.destroy(); score += brick.getData('points'); updateUI(); // Check win condition if (bricks.countActive(true) === 0) { gameOver(this, true); } }); } </script> </body> </html>
thinking
Now I need to make edits. I'll do multiple edits in one call. First, add a global variable for bottomSensor. Look for the line "let graphics;" and add after it. Second, modify createBall to remove worldbounds event and add bottomSensor. Third, modify update to include overlap detection with bottomSensor (or we can use collide). Actually we can add overlap in create function after ball and bottomSensor created. Let's add overlap in createBall after creating bottomSensor. Let's write the edits. Edit 1: add bottomSensor variable. Find the exact line "let graphics;" and add after it. Let's locate: line near 80? Let's search. Use bash to get line number:
{"command":"grep -n \"let graphics;\" index.html"}102: let graphics;
thinking
Great. Now I'll replace that line with adding bottomSensor. Let's make sure we also add a comment maybe. I'll replace "let graphics;" with "let graphics;\n let bottomSensor;". Let's do that
{"edits":[{"newText":" let graphics;\n let bottomSensor;","oldText":" let graphics;"}],"path":"index.html"}Successfully replaced 1 block(s) in index.html.
thinking
Now edit createBall function to replace the whole function. Let's get the exact old text from line 153 to line 180 (the closing brace). I'll copy the exact lines from the file to ensure uniqueness. Let's read from line 153 to line 180.
{"path":"index.html"}<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Phaser Brick Breaker</title> <style> body { margin: 0; padding: 0; background: #111; display: flex; justify-content: center; align-items: center; min-height: 100vh; font-family: Arial, sans-serif; color: #fff; } #gameContainer { position: relative; } canvas { display: block; border: 2px solid #333; box-shadow: 0 0 20px rgba(0, 200, 255, 0.3); } #ui { position: absolute; top: 10px; left: 10px; font-size: 18px; text-shadow: 1px 1px 2px black; } #score, #lives { margin: 5px 0; } #message { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 36px; text-align: center; background: rgba(0, 0, 0, 0.8); padding: 20px 40px; border-radius: 10px; display: none; } #controls { margin-top: 10px; font-size: 14px; color: #aaa; } </style> </head> <body> <div id="gameContainer"> <div id="ui"> <div id="score">Score: 0</div> <div id="lives">Lives: 3</div> <div id="controls">Move: Arrow keys or Mouse | Space to launch</div> </div> <div id="message"></div> </div> <script src="phaser.min.js"></script> <script> // Game configuration const config = { type: Phaser.AUTO, width: 800, height: 600, parent: 'gameContainer', backgroundColor: '#0a0a1a', physics: { default: 'arcade', arcade: { gravity: { x: 0, y: 0 }, debug: false } }, scene: { preload: preload, create: create, update: update } }; let game = new Phaser.Game(config); // Global variables let paddle; let ball; let bricks; let score = 0; let lives = 3; let ballLaunched = false; let cursors; let spaceKey; let scoreText; let livesText; let messageText; let graphics; let bottomSensor; // Preload - nothing to load, but we'll generate textures function preload() { // No external assets } // Create game objects function create() { // Create a graphics object for drawing shapes graphics = this.add.graphics(); // Create paddle createPaddle(this); // Create ball createBall(this); // Create bricks createBricks(this); // Input cursors = this.input.keyboard.createCursorKeys(); spaceKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE); this.input.on('pointermove', (pointer) => { paddle.x = Phaser.Math.Clamp(pointer.x, paddle.width/2, this.game.config.width - paddle.width/2); }); // UI scoreText = document.getElementById('score'); livesText = document.getElementById('lives'); messageText = document.getElementById('message'); // Launch ball on space this.input.keyboard.on('keydown-SPACE', launchBall, this); // Initial message showMessage('Press SPACE to launch ball', '#0ff'); } function createPaddle(scene) { // Generate a texture for paddle using graphics const paddleGraphics = scene.add.graphics(); paddleGraphics.fillStyle(0x00ffaa, 1); paddleGraphics.fillRoundedRect(0, 0, 100, 20, 10); paddleGraphics.generateTexture('paddle', 100, 20); paddleGraphics.destroy(); paddle = scene.physics.add.sprite(400, 550, 'paddle'); paddle.setImmovable(true); paddle.setCollideWorldBounds(true); } function createBall(scene) { // Generate ball texture const ballGraphics = scene.add.graphics(); ballGraphics.fillStyle(0xffaa00, 1); ballGraphics.fillCircle(16, 16, 16); ballGraphics.lineStyle(4, 0xffff00, 1); ballGraphics.strokeCircle(16, 16, 16); ballGraphics.generateTexture('ball', 32, 32); ballGraphics.destroy(); ball = scene.physics.add.sprite(400, 530, 'ball'); ball.setBounce(1); ball.setCollideWorldBounds(true); ball.setData('onPaddle', true); // Ball will bounce off walls scene.physics.world.on('worldbounds', (body, up, down, left, right) => { if (down) { // Ball hit bottom loseLife(scene); } }); } function createBricks(scene) { // Create a group for bricks bricks = scene.physics.add.group(); const brickWidth = 70; const brickHeight = 30; const brickMargin = 10; const brickOffsetTop = 80; const brickOffsetLeft = 50; const rows = 5; const cols = 10; // Generate brick texture variants const colors = [0xff5555, 0xffaa55, 0xffff55, 0x55ff55, 0x5555ff]; for (let r = 0; r < rows; r++) { // Create a texture for each row color const brickGraphics = scene.add.graphics(); brickGraphics.fillStyle(colors[r], 1); brickGraphics.fillRoundedRect(0, 0, brickWidth, brickHeight, 6); brickGraphics.lineStyle(2, 0xffffff, 0.5); brickGraphics.strokeRoundedRect(0, 0, brickWidth, brickHeight, 6); brickGraphics.generateTexture(`brick${r}`, brickWidth, brickHeight); brickGraphics.destroy(); for (let c = 0; c < cols; c++) { let brick = bricks.create( brickOffsetLeft + c * (brickWidth + brickMargin), brickOffsetTop + r * (brickHeight + brickMargin), `brick${r}` ); brick.setImmovable(true); brick.setData('points', (rows - r) * 10); } } } function launchBall() { if (ballLaunched) return; if (ball.getData('onPaddle')) { ball.setVelocity(Phaser.Math.Between(-200, 200), -300); ball.setData('onPaddle', false); ballLaunched = true; hideMessage(); } } function loseLife(scene) { lives--; updateUI(); if (lives <= 0) { gameOver(scene, false); } else { resetBall(scene); showMessage('Ball lost! Press SPACE to launch', '#ff5555'); } } function resetBall(scene) { ball.setVelocity(0); ball.setPosition(paddle.x, paddle.y - 20); ball.setData('onPaddle', true); ballLaunched = false; } function updateUI() { scoreText.textContent = `Score: ${score}`; livesText.textContent = `Lives: ${lives}`; } function showMessage(text, color = '#fff') { messageText.textContent = text; messageText.style.color = color; messageText.style.display = 'block'; } function hideMessage() { messageText.style.display = 'none'; } function gameOver(scene, win) { ball.setVelocity(0); ball.setVisible(false); if (win) { showMessage('YOU WIN! Score: ' + score, '#0ff'); } else { showMessage('GAME OVER', '#f00'); } // Restart option scene.input.keyboard.once('keydown-SPACE', () => { scene.scene.restart(); score = 0; lives = 3; updateUI(); }); showMessage((win ? 'YOU WIN!' : 'GAME OVER') + ' Press SPACE to restart', win ? '#0ff' : '#f00'); } // Update loop function update() { // Paddle movement with arrow keys if (cursors.left.isDown) { paddle.setVelocityX(-500); } else if (cursors.right.isDown) { paddle.setVelocityX(500); } else { paddle.setVelocityX(0); } // Keep ball on paddle before launch if (ball.getData('onPaddle')) { ball.x = paddle.x; } // Ball collision with paddle this.physics.world.collide(paddle, ball, (paddle, ball) => { // Adjust ball angle based on where it hits the paddle let diff = ball.x - paddle.x; ball.setVelocityX(diff * 5); // Ensure upward direction if (ball.body.velocity.y > 0) { ball.setVelocityY(-Math.abs(ball.body.velocity.y)); } }); // Ball collision with bricks this.physics.world.collide(ball, bricks, (ball, brick) => { brick.destroy(); score += brick.getData('points'); updateUI(); // Check win condition if (bricks.countActive(true) === 0) { gameOver(this, true); } }); } </script> </body> </html>
thinking
Now I need to replace the createBall function. Let's find its exact start and end. I'll search for the line "function createBall(scene) {" and the closing brace before "function createBricks". Let's write the new function.
I'll write a new createBall that creates bottomSensor and sets up overlap. Also we need to add bottomSensor in the scene. We'll add bottomSensor as a static sprite with sensor true. We'll also disable its gravity and visibility.
Let's craft the new function:
```
function createBall(scene) {
// Generate ball texture
const ballGraphics = scene.add.graphics();
ballGraphics.fillStyle(0xffaa00, 1);
ballGraphics.fillCircle(16, 16, 16);
ballGraphics.lineStyle(4, 0xffff00, 1);
ballGraphics.strokeCircle(16, 16, 16);
ballGraphics.generateTexture('ball', 32, 32);
ballGraphics.destroy();
ball = scene.physics.add.sprite(400, 530, 'ball');
ball.setBounce(1);
ball.setCollideWorldBounds(true);
ball.setData('onPaddle', true);
// Create bottom sensor for losing lives
bottomSensor = scene.physics.add.sprite(config.width / 2, config.height + 10, null);
bottomSensor.setDisplaySize(config.width, 20);
bottomSensor.setVisible(false);
bottomSensor.setImmovable(true);
bottomSensor.body.allowGravity = false;
bottomSensor.body.isSensor = true;
// Overlap between ball and bottom sensor
scene.physics.add.overlap(ball, bottomSensor, () => {
loseLife(scene);
});
}
```
But we must ensure config is accessible; we have config variable globally. Also we need to set bottomSensor position correctly: y = config.height + 10 (just below screen). Width = config.width, height 20.
Now we also need to remove the worldbounds event listener. That's already removed.
Now we need to adjust loseLife to pass the scene (we already have scene parameter). However, the overlap callback may call loseLife with ball and bottomSensor arguments. We'll ignore them.
Let's write the edit. First, get the exact old text. We'll use the lines from "function createBall(scene) {" to the line before "function createBricks(scene) {". Let's compute the exact string by reading from line 153 to line 180 again (but after previous edit, line numbers may have shifted). Let's get the exact old text by using a unique marker: the line " // Ball will bounce off walls". We'll replace from that line to the end of function.
Better to replace the entire function. Let's extract the exact old text from the file now. Use bash to get lines between function createBall and next function. Let's do a quick grep with context. But we can manually copy from the file we just read. I'll copy the function exactly as shown in the read output (from line after createPaddle to before createBricks). Let's write the old text:
```
function createBall(scene) {
// Generate ball texture
const ballGraphics = scene.add.graphics();
ballGraphics.fillStyle(0xffaa00, 1);
ballGraphics.fillCircle(16, 16, 16);
ballGraphics.lineStyle(4, 0xffff00, 1);
ballGraphics.strokeCircle(16, 16, 16);
ballGraphics.generateTexture('ball', 32, 32);
ballGraphics.destroy();
ball = scene.physics.add.sprite(400, 530, 'ball');
ball.setBounce(1);
ball.setCollideWorldBounds(true);
ball.setData('onPaddle', true);
// Ball will bounce off walls
scene.physics.world.on('worldbounds', (body, up, down, left, right) => {
if (down) {
// Ball hit bottom
loseLife(scene);
}
});
}
```
Now we need to replace with new function. Let's write the edit. We'll also need to ensure we have config variable accessible; it's global, so we can use config.width etc.
Now, we also need to adjust the paddle movement: currently paddle.setVelocityX is used but also we have pointermove that directly sets paddle.x. That's fine. However, paddle velocity might conflict with pointer movement? Actually pointer movement sets paddle.x directly, overriding velocity. That's okay.
Now we need to also adjust the gameOver function: we have duplicate showMessage calls. The gameOver function shows message twice (once inside if/else, then again after). That's redundant. Also we need to hide the ball and maybe reset. We'll fix that.
But first, let's replace createBall.
Let's do edit now. We'll also need to adjust the loseLife function to receive scene parameter (already). The overlap callback passes scene? We'll use arrow function that calls loseLife(scene). However, scene variable is captured from outer scope (createBall). That's fine.
Now edit. Let's execute{"edits":[{"newText":" function createBall(scene) {\n // Generate ball texture\n const ballGraphics = scene.addSuccessfully replaced 1 block(s) in index.html.
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
Brick breaker renders and paddle moves; ball launches with Space but score shows NaN and bricks vanish oddly, indicating bugs.
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.