Playable mini-games (retro, arcade, board) judged on playability and code.
2 tasks ·
17 models tested ·
34 results
Space Invaders (retro)
html
anthropicclaude-haiku-4-5-20251001
7.8/1012.3 s
anthropicclaude-haiku-4-5-20251001
Tokens3 059
Source code6.1 KB
Time12.3 s
Matania Judgment
Gameplay
9
UX
8
Code quality
9
Completeness
8
Fidelity
7
Overall
7.75
Review
The code is of very high quality, featuring a clean game loop and successful sound management via the Web Audio API. However, the model cut the code off before the end (the script is not closed and the draw function is truncated), which prevents full execution and causes the faithfulness score to drop. The gameplay and visual rendering perfectly respect the requested retro aesthetic.
<canvas id="c" width="400" height="400"></canvas><script>
var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');
var gameState = {
score: 0,
lives: 3,
gameOver: false,
won: false
};
var player = {
x: 175,
y: 360,
w: 20,
h: 20,
speed: 5,
dx: 0
};
var bullets = [];
var enemies = [];
var enemyBullets = [];
var keys = {};
var audioContext = null;
function initAudio() {
if (!audioContext) {
audioContext = new (window.AudioContext || window.webkitAudioContext)();
}
}
function playSound(freq, duration) {
if (!audioContext) return;
var osc = audioContext.createOscillator();
var gain = audioContext.createGain();
osc.connect(gain);
gain.connect(audioContext.destination);
osc.frequency.value = freq;
osc.type = 'square';
gain.gain.setValueAtTime(0.1, audioContext.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + duration);
osc.start(audioContext.currentTime);
osc.stop(audioContext.currentTime + duration);
}
function createEnemies() {
enemies = [];
for (var row = 0; row < 5; row++) {
for (var col = 0; col < 8; col++) {
enemies.push({
x: col * 40 + 20,
y: row * 40 + 20,
w: 20,
h: 20
});
}
}
}
var enemyDirection = 1;
var enemySpeed = 0.5;
var enemyShootTimer = 0;
createEnemies();
document.addEventListener('keydown', function(e) {
keys[e.key] = true;
if (e.key === ' ') {
e.preventDefault();
shoot();
}
});
document.addEventListener('keyup', function(e) {
keys[e.key] = false;
});
function shoot() {
if (bullets.length < 5) {
bullets.push({
x: player.x + player.w / 2 - 2,
y: player.y,
w: 4,
h: 10,
speed: 7
});
playSound(800, 0.1);
}
}
function update() {
if (gameState.gameOver || gameState.won) return;
initAudio();
if (keys['ArrowLeft']) player.dx = -player.speed;
else if (keys['ArrowRight']) player.dx = player.speed;
else player.dx = 0;
player.x += player.dx;
if (player.x < 0) player.x = 0;
if (player.x + player.w > canvas.width) player.x = canvas.width - player.w;
for (var i = bullets.length - 1; i >= 0; i--) {
bullets[i].y -= bullets[i].speed;
if (bullets[i].y < 0) {
bullets.splice(i, 1);
}
}
for (var i = enemyBullets.length - 1; i >= 0; i--) {
enemyBullets[i].y += enemyBullets[i].speed;
if (enemyBullets[i].y > canvas.height) {
enemyBullets.splice(i, 1);
}
}
var moveDown = false;
for (var i = 0; i < enemies.length; i++) {
enemies[i].x += enemyDirection * enemySpeed;
if (enemies[i].x <= 0 || enemies[i].x + enemies[i].w >= canvas.width) {
moveDown = true;
}
}
if (moveDown) {
enemyDirection *= -1;
for (var i = 0; i < enemies.length; i++) {
enemies[i].y += 20;
if (enemies[i].y + enemies[i].h >= canvas.height) {
gameState.gameOver = true;
}
}
}
for (var i = bullets.length - 1; i >= 0; i--) {
for (var j = enemies.length - 1; j >= 0; j--) {
if (bullets[i].x < enemies[j].x + enemies[j].w &&
bullets[i].x + bullets[i].w > enemies[j].x &&
bullets[i].y < enemies[j].y + enemies[j].h &&
bullets[i].y + bullets[i].h > enemies[j].y) {
bullets.splice(i, 1);
enemies.splice(j, 1);
gameState.score += 10;
playSound(400, 0.1);
break;
}
}
}
if (enemies.length === 0) {
gameState.won = true;
}
enemyShootTimer++;
if (enemyShootTimer > 30 && enemies.length > 0) {
var randomEnemy = enemies[Math.floor(Math.random() * enemies.length)];
enemyBullets.push({
x: randomEnemy.x + randomEnemy.w / 2 - 2,
y: randomEnemy.y + randomEnemy.h,
w: 4,
h: 10,
speed: 3
});
enemyShootTimer = 0;
}
for (var i = enemyBullets.length - 1; i >= 0; i--) {
if (enemyBullets[i].x < player.x + player.w &&
enemyBullets[i].x + enemyBullets[i].w > player.x &&
enemyBullets[i].y < player.y + player.h &&
enemyBullets[i].y + enemyBullets[i].h > player.y) {
enemyBullets.splice(i, 1);
gameState.lives--;
playSound(200, 0.2);
if (gameState.lives <= 0) {
gameState.gameOver = true;
}
}
}
}
function draw() {
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#0f0';
ctx.font = 'bold 14px Courier New';
ctx.fillText('SCORE: ' + gameState.score, 10, 20);
ctx.fillText('LIVES: ' + gameState.lives, canvas.width - 100, 20);
ctx.fillStyle = '#0f0';
ctx.fillRect(player.x, player.y, player.w, player.h);
for (var i = 0; i < enemies.length; i++) {
ctx.fillStyle = '#0f0';
ctx.fillRect(enemies[i].x, enemies[i].y, enemies[i].w, enemies[i].h);
ctx.fillStyle = '#000';
ctx.fillRect(enemies[i].x + 4, enemies[i].y + 4, 4, 4);
ctx.fillRect(enemies[i].x + 12, enemies[i].y + 4, 4, 4);
}
ctx.fillStyle = '#0f0';
for (var i = 0; i < bullets.length; i++) {
ctx.fillRect(bullets[i].x, bullets[i].y, bullets[i].w, bullets[i].h);
}
ctx.fillStyle = '#f00';
for (var i = 0; i < enemyBullets.length; i++) {
ctx.fillRect(enemyBullets[i].x, enemyBullets[i].y, enemyBullets[i].w, enemyBullets[i].h);
}
if (gameState.gameOver) {
ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#f00';
ctx.font = 'bold 30px Courier New';
ctx.textAlign = 'center';
ctx.fillText('GAME OVER', canvas.width / 2, canvas.height / 2);
ctx.font = 'bold 16px Courier New';
ctx.fillText('Score: ' + gameState.score, canvas.width / 2, canvas.height / 2 + 30);
}
if (gameState.won) {
ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#0f0';
ctx.font = 'bold 30px Courier New';
ctx.textAlign = 'center';
ctx.fillText('YOU WIN!', canvas.width / 2, canvas.height / 2);
ctx.font = 'bold 16px Courier New';
ctx.fillText('Score: ' + gameState.score, canvas.width / 2, canvas.height / 2 + 30);
}
}
function gameLoop() {
update();
draw();
requestAnimationFrame(gameLoop);
}
gameLoop();
</script>
anthropicclaude-opus-4-6
9.9/1074.8 s
anthropicclaude-opus-4-6
Tokens7 164
Source code18.6 KB
Time74.8 s
Matania Judgment
Gameplay
10
UX
10
Code quality
9
Completeness
10
Fidelity
10
Overall
9.88
Review
The model perfectly adhered to all technical and aesthetic constraints. The code is clean, uses ES5 as requested, and the implementation of sprites as pixel matrices is excellent for the retro style. The visual rendering is faithful to the black and green theme, and the game structure is complete and robust.
<canvas id="c" width="400" height="400"></canvas><script>
(function() {
var canvas = document.getElementById("c");
var ctx = canvas.getContext("2d");
canvas.style.background = "#000";
document.body.style.margin = "0";
document.body.style.background = "#000";
document.body.style.display = "flex";
document.body.style.justifyContent = "center";
document.body.style.alignItems = "center";
document.body.style.height = "100vh";
var AudioCtx = window.AudioContext || window.webkitAudioContext;
var audioCtx = null;
try { audioCtx = new AudioCtx(); } catch(e) { audioCtx = null; }
function playSound(freq, duration, type, vol) {
if (!audioCtx) return;
try {
var osc = audioCtx.createOscillator();
var gain = audioCtx.createGain();
osc.type = type || "square";
osc.frequency.value = freq;
gain.gain.value = vol || 0.1;
gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + duration);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start(audioCtx.currentTime);
osc.stop(audioCtx.currentTime + duration);
} catch(e) {}
}
function playShoot() { playSound(880, 0.1, "square", 0.08); }
function playExplosion() { playSound(150, 0.3, "sawtooth", 0.12); }
function playEnemyShoot() { playSound(220, 0.15, "triangle", 0.06); }
function playDeath() {
playSound(200, 0.5, "sawtooth", 0.15);
setTimeout(function() { playSound(100, 0.5, "sawtooth", 0.15); }, 200);
}
function playVictory() {
playSound(523, 0.15, "square", 0.1);
setTimeout(function() { playSound(659, 0.15, "square", 0.1); }, 150);
setTimeout(function() { playSound(784, 0.3, "square", 0.1); }, 300);
}
function playGameOver() {
playSound(300, 0.3, "sawtooth", 0.12);
setTimeout(function() { playSound(200, 0.3, "sawtooth", 0.12); }, 300);
setTimeout(function() { playSound(100, 0.5, "sawtooth", 0.12); }, 600);
}
function playStep() { playSound(60, 0.05, "square", 0.04); }
var keys = {};
document.addEventListener("keydown", function(e) {
keys[e.keyCode] = true;
if (e.keyCode === 32 || e.keyCode === 37 || e.keyCode === 39) {
e.preventDefault();
}
});
document.addEventListener("keyup", function(e) {
keys[e.keyCode] = false;
});
var STATE_PLAY = 0;
var STATE_WIN = 1;
var STATE_OVER = 2;
var STATE_START = 3;
var player, bullets, enemyBullets, invaders, score, lives, state;
var invaderDir, invaderSpeed, invaderTimer, invaderStepInterval;
var enemyShootTimer, particles, flickerTimer, level;
var INVADER_ROWS = 5;
var INVADER_COLS = 8;
var INVADER_W = 24;
var INVADER_H = 16;
var INVADER_PAD_X = 8;
var INVADER_PAD_Y = 8;
var INVADER_OFFSET_X = 20;
var INVADER_OFFSET_Y = 40;
var invaderSprites = [
// Type 0 (top row) - classic crab shape
[
[0,0,1,0,0,0,0,0,1,0,0],
[0,0,0,1,0,0,0,1,0,0,0],
[0,0,1,1,1,1,1,1,1,0,0],
[0,1,1,0,1,1,1,0,1,1,0],
[1,1,1,1,1,1,1,1,1,1,1],
[1,0,1,1,1,1,1,1,1,0,1],
[1,0,1,0,0,0,0,0,1,0,1],
[0,0,0,1,1,0,1,1,0,0,0]
],
// Type 1 (middle rows) - classic invader
[
[0,0,0,1,1,0,0,0],
[0,0,1,1,1,1,0,0],
[0,1,1,1,1,1,1,0],
[1,1,0,1,1,0,1,1],
[1,1,1,1,1,1,1,1],
[0,0,1,0,0,1,0,0],
[0,1,0,1,1,0,1,0],
[1,0,1,0,0,1,0,1]
],
// Type 2 (bottom rows) - squid shape
[
[0,0,0,0,1,1,0,0,0,0],
[0,0,0,1,1,1,1,0,0,0],
[0,0,1,1,1,1,1,1,0,0],
[0,1,1,0,1,1,0,1,1,0],
[0,1,1,1,1,1,1,1,1,0],
[0,0,0,1,0,0,1,0,0,0],
[0,0,1,0,1,1,0,1,0,0],
[0,1,0,1,0,0,1,0,1,0]
]
];
var playerSprite = [
[0,0,0,0,0,1,0,0,0,0,0],
[0,0,0,0,1,1,1,0,0,0,0],
[0,0,0,0,1,1,1,0,0,0,0],
[0,1,1,1,1,1,1,1,1,1,0],
[1,1,1,1,1,1,1,1,1,1,1],
[1,1,1,1,1,1,1,1,1,1,1],
[1,1,1,1,1,1,1,1,1,1,1]
];
function drawSprite(sprite, x, y, pixelSize, color) {
ctx.fillStyle = color;
for (var r = 0; r < sprite.length; r++) {
for (var c = 0; c < sprite[r].length; c++) {
if (sprite[r][c]) {
ctx.fillRect(
Math.floor(x + c * pixelSize),
Math.floor(y + r * pixelSize),
pixelSize, pixelSize
);
}
}
}
}
function init() {
player = { x: 200, y: 370, w: 28, h: 16, speed: 3 };
bullets = [];
enemyBullets = [];
particles = [];
score = 0;
lives = 3;
level = 1;
flickerTimer = 0;
initInvaders();
state = STATE_START;
}
function initInvaders() {
invaders = [];
invaderDir = 1;
invaderSpeed = 1;
invaderTimer = 0;
invaderStepInterval = 45;
enemyShootTimer = 0;
for (var row = 0; row < INVADER_ROWS; row++) {
for (var col = 0; col < INVADER_COLS; col++) {
var type;
if (row === 0) type = 0;
else if (row < 3) type = 1;
else type = 2;
invaders.push({
x: INVADER_OFFSET_X + col * (INVADER_W + INVADER_PAD_X),
y: INVADER_OFFSET_Y + row * (INVADER_H + INVADER_PAD_Y),
w: INVADER_W,
h: INVADER_H,
alive: true,
type: type,
row: row,
col: col,
points: (INVADER_ROWS - row) * 10
});
}
}
}
function startLevel() {
bullets = [];
enemyBullets = [];
particles = [];
initInvaders();
invaderStepInterval = Math.max(10, 45 - (level - 1) * 5);
state = STATE_PLAY;
}
function spawnParticles(x, y, color, count) {
for (var i = 0; i < count; i++) {
particles.push({
x: x,
y: y,
vx: (Math.random() - 0.5) * 4,
vy: (Math.random() - 0.5) * 4,
life: 20 + Math.random() * 20,
maxLife: 40,
color: color,
size: 1 + Math.random() * 2
});
}
}
var shootCooldown = 0;
var MAX_BULLETS = 3;
function update() {
if (state === STATE_START) {
flickerTimer++;
if (keys[32]) {
state = STATE_PLAY;
keys[32] = false;
}
return;
}
if (state === STATE_WIN || state === STATE_OVER) {
flickerTimer++;
if (keys[32]) {
if (state === STATE_WIN) {
level++;
startLevel();
} else {
level = 1;
init();
state = STATE_PLAY;
}
keys[32] = false;
}
updateParticles();
return;
}
// Player movement
if (keys[37]) player.x -= player.speed;
if (keys[39]) player.x += player.speed;
if (player.x < 0) player.x = 0;
if (player.x + player.w > 400) player.x = 400 - player.w;
// Shooting
if (shootCooldown > 0) shootCooldown--;
if (keys[32] && shootCooldown <= 0 && bullets.length < MAX_BULLETS) {
bullets.push({ x: player.x + player.w / 2 - 1, y: player.y - 4, w: 2, h: 6 });
playShoot();
shootCooldown = 10;
}
// Update bullets
for (var i = bullets.length - 1; i >= 0; i--) {
bullets[i].y -= 5;
if (bullets[i].y < -10) bullets.splice(i, 1);
}
// Invader movement
invaderTimer++;
if (invaderTimer >= invaderStepInterval) {
invaderTimer = 0;
playStep();
var hitEdge = false;
for (var i = 0; i < invaders.length; i++) {
if (!invaders[i].alive) continue;
if ((invaders[i].x + invaders[i].w + invaderDir * invaderSpeed > 398) ||
(invaders[i].x + invaderDir * invaderSpeed < 2)) {
hitEdge = true;
break;
}
}
if (hitEdge) {
invaderDir *= -1;
for (var i = 0; i < invaders.length; i++) {
if (invaders[i].alive) {
invaders[i].y += 10;
}
}
} else {
for (var i = 0; i < invaders.length; i++) {
if (invaders[i].alive) {
invaders[i].x += invaderDir * invaderSpeed;
}
}
}
// Speed up as fewer invaders remain
var aliveCount = 0;
for (var i = 0; i < invaders.length; i++) {
if (invaders[i].alive) aliveCount++;
}
if (aliveCount > 0) {
var ratio = aliveCount / (INVADER_ROWS * INVADER_COLS);
invaderStepInterval = Math.max(2, Math.floor((45 - (level - 1) * 5) * ratio));
invaderSpeed = ratio < 0.2 ? 3 : (ratio < 0.5 ? 2 : 1);
}
}
// Enemy shooting
enemyShootTimer++;
var shootInterval = Math.max(30, 80 - level * 5);
if (enemyShootTimer >= shootInterval) {
enemyShootTimer = 0;
// Find bottom-most invaders per column
var bottomInvaders = [];
var colMap = {};
for (var i = 0; i < invaders.length; i++) {
if (!invaders[i].alive) continue;
var col = invaders[i].col;
if (colMap[col] === undefined || invaders[i].row > invaders[colMap[col]].row) {
colMap[col] = i;
}
}
for (var c in colMap) {
if (colMap.hasOwnProperty(c)) {
bottomInvaders.push(colMap[c]);
}
}
if (bottomInvaders.length > 0) {
var idx = bottomInvaders[Math.floor(Math.random() * bottomInvaders.length)];
var inv = invaders[idx];
enemyBullets.push({
x: inv.x + inv.w / 2 - 1,
y: inv.y + inv.h,
w: 2,
h: 6
});
playEnemyShoot();
}
}
// Update enemy bullets
for (var i = enemyBullets.length - 1; i >= 0; i--) {
enemyBullets[i].y += 3;
if (enemyBullets[i].y > 410) enemyBullets.splice(i, 1);
}
// Collision: player bullets vs invaders
for (var i = bullets.length - 1; i >= 0; i--) {
var b = bullets[i];
var hit = false;
for (var j = 0; j < invaders.length; j++) {
var inv = invaders[j];
if (!inv.alive) continue;
if (b.x < inv.x + inv.w && b.x + b.w > inv.x &&
b.y < inv.y + inv.h && b.y + b.h > inv.y) {
inv.alive = false;
score += inv.points;
hit = true;
playExplosion();
spawnParticles(inv.x + inv.w / 2, inv.y + inv.h / 2, "#0f0", 12);
break;
}
}
if (hit) bullets.splice(i, 1);
}
// Collision: enemy bullets vs player
for (var i = enemyBullets.length - 1; i >= 0; i--) {
var b = enemyBullets[i];
if (b.x < player.x + player.w && b.x + b.w > player.x &&
b.y < player.y + player.h && b.y + b.h > player.y) {
enemyBullets.splice(i, 1);
lives--;
playDeath();
spawnParticles(player.x + player.w / 2, player.y + player.h / 2, "#0f0", 20);
if (lives <= 0) {
state = STATE_OVER;
playGameOver();
flickerTimer = 0;
return;
}
}
}
// Check invaders reaching bottom
for (var i = 0; i < invaders.length; i++) {
if (invaders[i].alive && invaders[i].y + invaders[i].h >= player.y) {
state = STATE_OVER;
playGameOver();
flickerTimer = 0;
return;
}
}
// Check win
var anyAlive = false;
for (var i = 0; i < invaders.length; i++) {
if (invaders[i].alive) { anyAlive = true; break; }
}
if (!anyAlive) {
state = STATE_WIN;
playVictory();
flickerTimer = 0;
}
updateParticles();
}
function updateParticles() {
for (var i = particles.length - 1; i >= 0; i--) {
var p = particles[i];
p.x += p.vx;
p.y += p.vy;
p.life--;
if (p.life <= 0) particles.splice(i, 1);
}
}
function drawScanlines() {
ctx.fillStyle = "rgba(0,0,0,0.15)";
for (var y = 0; y < 400; y += 3) {
ctx.fillRect(0, y, 400, 1);
}
}
function draw() {
// Clear
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, 400, 400);
if (state === STATE_START) {
ctx.fillStyle = "#0f0";
ctx.font = "20px monospace";
ctx.textAlign = "center";
ctx.fillText("SPACE INVADERS", 200, 100);
ctx.font = "10px monospace";
// Draw sample invaders
var sampleTypes = [0, 1, 2];
var samplePoints = [50, 30, 10];
var sampleColors = ["#f00", "#ff0", "#0f0"];
for (var t = 0; t < 3; t++) {
var sprite = invaderSprites[t];
drawSprite(sprite, 140, 140 + t * 30, 2, sampleColors[t]);
ctx.fillStyle = sampleColors[t];
ctx.textAlign = "left";
ctx.fillText("= " + samplePoints[t] + " PTS", 170 + sprite[0].length * 2, 152 + t * 30);
}
ctx.fillStyle = "#0f0";
ctx.textAlign = "center";
ctx.font = "14px monospace";
var show = Math.floor(flickerTimer / 30) % 2 === 0;
if (show) {
ctx.fillText("PRESS SPACE TO START", 200, 280);
}
ctx.font = "10px monospace";
ctx.fillStyle = "#0a0";
ctx.fillText("ARROWS = MOVE | SPACE = FIRE", 200, 340);
drawScanlines();
return;
}
// Draw player
if (state === STATE_PLAY || state === STATE_WIN) {
drawSprite(playerSprite, player.x - 2, player.y - 2, 3, "#0f0");
} else if (state === STATE_OVER) {
if (Math.floor(flickerTimer / 5) % 2 === 0) {
drawSprite(playerSprite, player.x - 2, player.y - 2, 3, "#0f0");
}
}
// Draw invaders
for (var i = 0; i < invaders.length; i++) {
var inv = invaders[i];
if (!inv.alive) continue;
var colors = ["#f44", "#ff0", "#0f0", "#0ff", "#f0f"];
var color = colors[inv.row % colors.length];
var sprite = invaderSprites[inv.type];
var pw = inv.w / sprite[0].length;
var ph = inv.h / sprite.length;
var ps = Math.min(pw, ph);
var offsetX = (inv.w - sprite[0].length * ps) / 2;
var offsetY = (inv.h - sprite.length * ps) / 2;
drawSprite(sprite, inv.x + offsetX, inv.y + offsetY, ps, color);
}
// Draw player bullets
ctx.fillStyle = "#0f0";
ctx.shadowColor = "#0f0";
ctx.shadowBlur = 6;
for (var i = 0; i < bullets.length; i++) {
var b = bullets[i];
ctx.fillRect(b.x, b.y, b.w, b.h);
}
ctx.shadowBlur = 0;
// Draw enemy bullets
for (var i = 0; i < enemyBullets.length; i++) {
var b = enemyBullets[i];
// Zigzag style
ctx.fillStyle = "#f44";
ctx.shadowColor = "#f44";
ctx.shadowBlur = 4;
ctx.fillRect(b.x - 1, b.y, 1, 2);
ctx.fillRect(b.x + 1, b.y + 2, 1, 2);
ctx.fillRect(b.x - 1, b.y + 4, 1, 2);
ctx.shadowBlur = 0;
}
// Draw particles
for (var i = 0; i < particles.length; i++) {
var p = particles[i];
var alpha = p.life / p.maxLife;
ctx.globalAlpha = alpha;
ctx.fillStyle = p.color;
ctx.fillRect(p.x, p.y, p.size, p.size);
}
ctx.globalAlpha = 1;
// HUD
ctx.fillStyle = "#0f0";
ctx.font = "14px monospace";
ctx.textAlign = "left";
ctx.fillText("SCORE:" + score, 10, 20);
ctx.textAlign = "center";
ctx.fillText("LEVEL " + level, 200, 20);
ctx.textAlign = "right";
// Draw lives as mini ships
var livesText = "LIVES:";
ctx.fillText(livesText, 340, 20);
for (var i = 0; i < lives; i++) {
drawSprite(playerSprite, 346 + i * 16, 10, 1.2, "#0f0");
}
// Draw ground line
ctx.strokeStyle = "#0a0";
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(0, 392);
ctx.lineTo(400, 392);
ctx.stroke();
// State messages
if (state === STATE_WIN) {
ctx.fillStyle = "#0f0";
ctx.font = "24px monospace";
ctx.textAlign = "center";
ctx.fillText("WAVE CLEARED!", 200, 200);
ctx.font = "14px monospace";
var show = Math.floor(flickerTimer / 25) % 2 === 0;
if (show) ctx.fillText("PRESS SPACE", 200, 230);
}
if (state === STATE_OVER) {
ctx.fillStyle = "#f00";
ctx.font = "28px monospace";
ctx.textAlign = "center";
ctx.fillText("GAME OVER", 200, 190);
ctx.fillStyle = "#0f0";
ctx.font = "14px monospace";
ctx.fillText("FINAL SCORE: " + score, 200, 220);
var show = Math.floor(flickerTimer / 25) % 2 === 0;
if (show) ctx.fillText("PRESS SPACE TO RETRY", 200, 250);
}
drawScanlines();
}
function gameLoop() {
update();
draw();
requestAnimationFrame(gameLoop);
}
init();
gameLoop();
})();
</script>
anthropicclaude-opus-4-7
9.5/1022.8 s
anthropicclaude-opus-4-7
Tokens2 807
Source code3.5 KB
Time22.8 s
Matania Judgment
Gameplay
9
UX
8
Code quality
9
Completeness
10
Fidelity
10
Overall
9.5
Review
The model perfectly adhered to all technical constraints: a single HTML format, a 400x400 Canvas, ES5 JavaScript, and a retro black-and-green style. The gameplay is complete, featuring collision detection, lives, scoring, and sound (via the Web Audio API). The code is concise and well-structured, despite the requirement of being contained within a single document.
The model generated incomplete code that cuts off abruptly in the middle of a function (truncated), making the game completely unplayable. Consequently, no gameplay, UX, or completeness criteria can be validated. Fidelity is very low because the final output fails to meet the constraint of providing a "playable" game.
<canvas id="c" width="400" height="400"></canvas><script>
var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');
var audioCtx = null;
function getAudio() {
if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)();
return audioCtx;
}
function playSound(freq, duration, type) {
try {
var ac = getAudio();
var osc = ac.createOscillator();
var gain = ac.createGain();
osc.connect(gain);
gain.connect(ac.destination);
osc.type = type || 'square';
osc.frequency.setValueAtTime(freq, ac.currentTime);
osc.frequency.exponentialRampToValueAtTime(freq * 0.5, ac.currentTime + duration);
gain.gain.setValueAtTime(0.3, ac.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, ac.currentTime + duration);
osc.start(ac.currentTime);
osc.stop(ac.currentTime + duration);
} catch(e) {}
}
function playShoot() { playSound(880, 0.1, 'square'); }
function playExplosion() { playSound(150, 0.3, 'sawtooth'); }
function playInvaderShoot() { playSound(440, 0.15, 'square'); }
function playVictory() { playSound(1200, 0.5, 'sine'); }
function playGameOver() { playSound(100, 0.8, 'sawtooth'); }
var COLS = 8;
var ROWS = 5;
var INV_W = 28;
var INV_H = 20;
var INV_PAD_X = 12;
var INV_PAD_Y = 12;
var PLAYER_W = 32;
var PLAYER_H = 16;
var BULLET_W = 3;
var BULLET_H = 10;
var ENEMY_BULLET_W = 3;
var ENEMY_BULLET_H = 10;
var keys = {};
var score = 0;
var lives = 3;
var gameState = 'playing';
var invaderTick = 0;
var invaderInterval = 40;
var invaderDir = 1;
var invaderDescend = false;
var invaderOffsetX = 0;
var invaderOffsetY = 0;
var invaderMoveAmount = 10;
var invaderDescendAmount = 16;
var shootCooldown = 0;
var frame = 0;
var enemyBullets = [];
var enemyShootTimer = 0;
var enemyShootInterval = 80;
var playerBullet = null;
var playerX = 184;
var playerY = 360;
var playerSpeed = 3;
var invaderAnim = 0;
var invaderAnimTimer = 0;
var invaders = [];
function initInvaders() {
invaders = [];
for (var r = 0; r < ROWS; r++) {
for (var c = 0; c < COLS; c++) {
invaders.push({ row: r, col: c, alive: true });
}
}
}
function getInvaderX(inv) {
return 20 + inv.col * (INV_W + INV_PAD_X) + invaderOffsetX;
}
function getInvaderY(inv) {
return 50 + inv.row * (INV_H + INV_PAD_Y) + invaderOffsetY;
}
function getAliveInvaders() {
var arr = [];
for (var i = 0; i < invaders.length; i++) {
if (invaders[i].alive) arr.push(invaders[i]);
}
return arr;
}
function reset() {
score = 0;
lives = 3;
gameState = 'playing';
invaderOffsetX = 0;
invaderOffsetY = 0;
invaderDir = 1;
invaderDescend = false;
invaderTick = 0;
invaderInterval = 40;
playerX = 184;
playerY = 360;
playerBullet = null;
enemyBullets = [];
shootCooldown = 0;
enemyShootTimer = 0;
frame = 0;
invaderAnim = 0;
initInvaders();
}
initInvaders();
document.addEventListener('keydown', function(e) {
keys[e.code] = true;
if ((e.code === 'Space' || e.code === 'ArrowLeft' || e.code === 'ArrowRight') && gameState === 'playing') {
e.preventDefault();
}
if (e.code === 'Enter' && (gameState === 'gameover' || gameState === 'victory')) {
reset();
}
});
document.addEventListener('keyup', function(e) {
keys[e.code] = false;
});
function drawPlayer(x, y) {
ctx.fillStyle = '#00ff41';
ctx.fillRect(x + 13, y, 6, 4);
ctx.fillRect(x + 9, y + 4, 14, 4);
ctx.fillRect(x, y + 8, PLAYER_W, 8);
ctx.fillStyle = '#00cc33';
ctx.fillRect(x + 3, y + 8, 4, 8);
ctx.fillRect(x + 25, y + 8, 4, 8);
}
function drawInvader(x, y, row, anim) {
ctx.fillStyle = '#00ff41';
if (row === 0) {
if (anim === 0) {
ctx.fillRect(x+8, y, 12, 4);
ctx.fillRect(x+4, y+4, 20, 4);
ctx.fillRect(x, y+8, 28, 4);
ctx.fillRect(x, y+12, 6, 4);
ctx.fillRect(x+10, y+12, 8, 4);
ctx.fillRect(x+22, y+12, 6, 4);
ctx.fillRect(x+2, y+16, 4, 4);
ctx.fillRect(x+22, y+16, 4, 4);
} else {
ctx.fillRect(x+8, y, 12, 4);
ctx.fillRect(x+4, y+4, 20, 4);
ctx.fillRect(x, y+8, 28, 4);
ctx.fillRect(x+2, y+12, 6, 4);
ctx.fillRect(x+10, y+12, 8, 4);
ctx.fillRect(x+20, y+12, 6, 4);
ctx.fillRect(x, y+16, 4, 4);
ctx.fillRect(x+24, y+16, 4, 4);
}
} else if (row <= 2) {
ctx.fillStyle = '#00ff41';
if (anim === 0) {
ctx.fillRect(x+4, y, 4, 4);
ctx.fillRect(x+20, y, 4, 4);
ctx.fillRect(x+4, y+4, 20, 4);
ctx.fillRect(x, y+8, 28, 4);
ctx.fillRect(x+4, y+12, 6, 4);
ctx.fillRect(x+12, y+12, 4, 4);
ctx.fillRect(x+18, y+12, 6, 4);
ctx.fillRect(x, y+16, 6, 4);
ctx.fillRect(x+22, y+16, 6, 4);
} else {
ctx.fillRect(x+4, y, 4, 4);
ctx.fillRect(x+20, y, 4, 4);
ctx.fillRect(x+4, y+4, 20, 4);
ctx.fillRect(x, y+8, 28, 4);
ctx.fillRect(x+4, y+12, 6, 4);
ctx.fillRect(x+12, y+12, 4, 4);
ctx.fillRect(x+18, y+12, 6, 4);
ctx.fillRect(x+2, y+16, 6, 4);
ctx.fillRect(x+20, y+16, 6, 4);
}
} else {
ctx.fillStyle = '#00ff41';
if (anim === 0) {
ctx.fillRect(x+8, y, 12, 4);
ctx.fillRect(x+2, y+4, 24, 4);
ctx.fillRect(x, y+8, 28, 4);
ctx.fillRect(x, y+12, 6, 4);
ctx.fillRect(x+8, y+12, 12, 4);
ctx.fillRect(x+22, y+12, 6, 4);
ctx.fillRect(x, y+16, 4, 4);
ctx.fillRect(x+24, y+16, 4, 4);
} else {
ctx.fillRect(x+8, y, 12, 4);
ctx.fillRect(x+2, y+4, 24, 4);
ctx.fillRect(x, y+8, 28, 4);
ctx.fillRect(x+2, y+12, 6, 4);
ctx.fillRect(x+8, y+12, 12, 4);
ctx.fillRect(x+20, y+12, 6, 4);
ctx.fillRect(x+4, y+16, 4, 4);
ctx.fillRect(x+20, y+16, 4, 4);
}
}
}
function update() {
if (gameState !== 'playing') return;
frame++;
if (keys['ArrowLeft']) {
playerX -= playerSpeed;
if (playerX < 0) playerX = 0;
}
if (keys['ArrowRight']) {
playerX += playerSpeed;
if (playerX + PLAYER_W > 400) playerX = 400 - PLAYER_W;
}
if (shootCooldown > 0) shootCooldown--;
if (keys['Space'] && !playerBullet && shootCooldown === 0) {
playerBullet = { x: playerX + PLAYER_W / 2 - BULLET_W / 2, y: playerY - BULLET_H };
shootCooldown = 15;
playShoot();
}
if (playerBullet) {
playerBullet.y -= 8;
if (playerBullet.y + BULLET_H < 0) {
playerBullet = null;
}
}
invaderTick++;
if (invaderTick >= invaderInterval) {
invaderTick = 0;
invaderAnim = 1 - invaderAnim;
if (invaderDescend) {
invaderOffsetY += invaderDescendAmount;
invaderDescend = false;
invaderDir = -invaderDir;
} else {
invaderOffsetX += invaderDir * invaderMoveAmount;
var alive = getAliveInvaders();
var hitEdge = false;
for (var i = 0; i < alive.length; i++) {
var ix = getInvaderX(alive[i]);
if (ix < 0 || ix + INV_W > 400) {
hitEdge = true;
break;
}
}
if (hitEdge) {
invaderOffsetX -= invaderDir * invaderMoveAmount;
invaderDescend = true;
}
}
var aliveCount = getAliveInvaders().length;
invaderInterval = Math.max(5, 40 - Math.floor((ROWS * COLS - aliveCount) * 0.5));
}
if (playerBullet) {
var alive2 = getAliveInvaders();
for (var j = 0; j < alive2.length; j++) {
var inv = alive2[j];
var ix2 = getInvaderX(inv);
var iy = getInvaderY(inv);
if (playerBullet.x < ix2 + INV_W &&
playerBullet.x + BULLET_W > ix2 &&
playerBullet.y < iy + INV_H &&
playerBullet.y + BULLET_H > iy) {
inv.alive = false;
playerBullet = null;
score += 10;
playExplosion();
break;
}
}
}
if (getAliveInvaders().length === 0) {
gameState = 'victory';
playVictory();
return;
}
var alive3 = getAliveInvaders();
for (var k = 0; k < alive3.length; k++) {
var iy2 = getInvaderY(alive3[k]);
if (iy2 + INV_H >= playerY + PLAYER_H) {
gameState = 'gameover';
playGameOver();
return;
}
}
enemyShootTimer++;
if (enemyShootTimer >= enemyShootInterval) {
enemyShootTimer = 0;
var alive4 = getAliveInvaders();
if (alive4.length > 0) {
var shooter = alive4[Math.floor(Math.random() * alive4.length)];
var sx = getInvaderX(shooter) + INV_W / 2 - ENEMY_BULLET_W / 2;
var sy = getInvaderY(shooter) + INV_H;
enemyBullets.push({ x: sx, y: sy });
playInvaderShoot();
}
enemyShootInterval = Math.max(30, 80 - Math.floor(frame / 200));
}
for (var b = enemyBullets.length - 1; b >= 0; b--) {
enemyBullets[b].y += 4;
if (enemyBullets[b].y > 400) {
enemyBullets.splice(b, 1);
continue;
}
var eb = enemyBullets[b];
if (eb.x < playerX + PLAYER_W &&
eb.x + ENEMY_BULLET_W > playerX &&
eb.y < playerY + PLAYER_H &&
eb.y + ENEMY_BULLET_H > playerY) {
enemyBullets.splice(b, 1);
lives--;
playExplosion();
if (lives <= 0) {
gameState = 'gameover';
playGameOver();
return;
}
}
}
}
function drawStars() {
ctx.fillStyle = '#003300';
var starPositions = [
[30, 20], [80, 45], [150, 15], [200, 35], [270, 10],
[320, 50], [370, 25], [50, 100], [120, 80], [230, 90],
[300, 110], [360, 75], [10, 150], [170, 140], [390, 160],
[60, 200], [140, 220], [280, 190], [340, 210], [90, 260],
[210, 250], [360, 280], [20, 310], [180, 330], [330, 300]
];
for (var i = 0; i < starPositions.length; i++) {
ctx.fillRect(starPositions[i][0], starPositions[i][1], 2, 2);
}
}
function draw() {
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, 400, 400);
drawStars();
ctx.strokeStyle = '#00ff41';
ctx.lineWidth = 1;
ctx.strokeRect(0, 0, 400, 400);
ctx.fillStyle = '#00ff41';
ctx.font = '14px monospace';
ctx.fillText('SCORE: ' + score, 10, 20);
ctx.fillText('LIVES: ' + lives, 280, 20);
ctx.strokeStyle = '#00ff41';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(0, 30);
ctx.lineTo(400, 30);
ctx.stroke();
if (gameState === 'playing' || gameState === 'gameover' || gameState === 'victory') {
var aliveInv = getAliveInvaders();
for (var i = 0; i < aliveInv.length; i++) {
drawInvader(getInvaderX(aliveInv[i]), getInvaderY(aliveInv[i]), aliveInv[i].row, invaderAnim);
}
drawPlayer(playerX, playerY);
if (playerBullet) {
ctx.fillStyle = '#00ff41';
ctx.fillRect(playerBullet.x, playerBullet.y, BULLET_W, BULLET_H);
}
for (var b = 0; b < enemyBullets.length; b++) {
ctx.fillStyle = '#ff4444';
ctx.fillRect(enemyBullets[b].x, enemyBullets[b].y, ENEMY_BULLET_W, ENEMY_BULLET_H);
}
}
if (gameState === 'gameover') {
ctx.fillStyle = 'rgba(0,0,0,0.7)';
ctx.fillRect(60, 140, 280, 120);
ctx.strokeStyle = '#00ff41';
ctx.lineWidth = 2;
ctx.strokeRect(60, 140, 280, 120);
ctx.fillStyle = '#ff4444';
ctx.font = 'bold 32px monospace';
ctx.fillText('GAME OVER', 85, 190);
ctx.fillStyle = '#00ff41';
ctx.font = '14px monospace';
ctx.fillText('SCORE: ' + score, 150, 220);
ctx.fillText('PRESS ENTER', 120, 245);
}
if (gameState === 'victory') {
ctx.fillStyle = 'rgba(0,0,0,0.7)';
ctx.fillRect(60, 140, 280, 120);
ctx.strokeStyle = '#00ff41';
ctx.lineWidth = 2;
ctx.strokeRect(60, 140, 280, 120);
ctx.fillStyle = '#00ff41';
ctx.font = 'bold 28px monospace';
ctx.fillText('YOU WIN!', 105, 190);
ctx.font = '14px monospace';
ctx.fillText('SCORE: ' + score, 150, 220);
ctx.fillText('PRESS ENTER', 120, 245);
}
}
function loop() {
update();
draw();
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
</script>
googlegemini-flash-latest
9.6/1018.4 s
googlegemini-flash-latest
Tokens2 561
Source code5.2 KB
Time18.4 s
Matania Judgment
Gameplay
9
UX
9
Code quality
9
Completeness
10
Fidelity
10
Overall
9.63
Review
The model perfectly adhered to all technical constraints: use of AudioContext for sound, ES5 JavaScript, a specific canvas structure, and strict compliance with the output format. The gameplay is complete, featuring life management, scoring, win and loss conditions, all wrapped in a consistent retro style.
<canvas id="c" width="400" height="400" style="background:#000;display:block;margin:0 auto;"></canvas>
<script>
var canvas = document.getElementById("c");
var ctx = canvas.getContext("2d");
var score = 0;
var lives = 3;
var gameOver = false;
var victory = false;
var keys = {};
var player = { x: 190, y: 370, w: 20, h: 10, speed: 4 };
var bullet = null;
var enemyBullets = [];
var invaders = [];
var invaderRows = 5;
var invaderCols = 8;
var invaderDir = 1;
var invaderSpeed = 1;
var invaderStepDown = 10;
var moveTimer = 0;
var moveInterval = 30;
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
function playSound(freq, type, duration, vol) {
var osc = audioCtx.createOscillator();
var g = audioCtx.createGain();
osc.type = type;
osc.frequency.setValueAtTime(freq, audioCtx.currentTime);
osc.connect(g);
g.connect(audioCtx.destination);
g.gain.setValueAtTime(vol, audioCtx.currentTime);
g.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + duration);
osc.start();
osc.stop(audioCtx.currentTime + duration);
}
function initInvaders() {
invaders = [];
for (var r = 0; r < invaderRows; r++) {
for (var c = 0; c < invaderCols; c++) {
invaders.push({
x: 40 + c * 35,
y: 50 + r * 25,
w: 20,
h: 15,
alive: true
});
}
}
}
function resetLevel() {
player.x = 190;
bullet = null;
enemyBullets = [];
initInvaders();
}
window.addEventListener("keydown", function(e) { keys[e.keyCode] = true; });
window.addEventListener("keyup", function(e) { keys[e.keyCode] = false; });
function update() {
if (gameOver || victory) {
if (keys[32]) {
score = 0;
lives = 3;
gameOver = false;
victory = false;
invaderSpeed = 1;
resetLevel();
}
return;
}
if (keys[37] && player.x > 0) player.x -= player.speed;
if (keys[39] && player.x < canvas.width - player.w) player.x += player.speed;
if (keys[32] && !bullet) {
bullet = { x: player.x + player.w / 2 - 2, y: player.y, w: 4, h: 10 };
playSound(600, "square", 0.1, 0.1);
}
if (bullet) {
bullet.y -= 5;
if (bullet.y < 0) bullet = null;
else {
for (var i = 0; i < invaders.length; i++) {
var inv = invaders[i];
if (inv.alive && bullet.x < inv.x + inv.w && bullet.x + bullet.w > inv.x && bullet.y < inv.y + inv.h && bullet.y + bullet.h > inv.y) {
inv.alive = false;
bullet = null;
score += 10;
playSound(150, "sawtooth", 0.1, 0.1);
break;
}
}
}
}
moveTimer++;
if (moveTimer >= moveInterval) {
var hitEdge = false;
for (var i = 0; i < invaders.length; i++) {
if (invaders[i].alive) {
invaders[i].x += 10 * invaderDir;
if (invaders[i].x <= 10 || invaders[i].x >= canvas.width - 30) hitEdge = true;
}
}
if (hitEdge) {
invaderDir *= -1;
for (var i = 0; i < invaders.length; i++) {
invaders[i].y += invaderStepDown;
if (invaders[i].alive && invaders[i].y + invaders[i].h >= player.y) gameOver = true;
}
}
moveTimer = 0;
}
if (Math.random() < 0.02) {
var aliveOnes = invaders.filter(function(i) { return i.alive; });
if (aliveOnes.length > 0) {
var shooter = aliveOnes[Math.floor(Math.random() * aliveOnes.length)];
enemyBullets.push({ x: shooter.x + shooter.w / 2, y: shooter.y + shooter.h, w: 4, h: 10 });
} else {
victory = true;
}
}
for (var i = enemyBullets.length - 1; i >= 0; i--) {
var eb = enemyBullets[i];
eb.y += 3;
if (eb.y > canvas.height) {
enemyBullets.splice(i, 1);
} else if (eb.x < player.x + player.w && eb.x + eb.w > player.x && eb.y < player.y + player.h && eb.y + eb.h > player.y) {
enemyBullets.splice(i, 1);
lives--;
playSound(50, "square", 0.5, 0.2);
if (lives <= 0) gameOver = true;
}
}
}
function draw() {
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "#0f0";
ctx.fillRect(player.x, player.y, player.w, player.h);
if (bullet) {
ctx.fillStyle = "#fff";
ctx.fillRect(bullet.x, bullet.y, bullet.w, bullet.h);
}
ctx.fillStyle = "#0f0";
for (var i = 0; i < invaders.length; i++) {
var inv = invaders[i];
if (inv.alive) {
ctx.fillRect(inv.x, inv.y, inv.w, inv.h);
ctx.fillRect(inv.x + 4, inv.y - 2, 2, 2);
ctx.fillRect(inv.x + 14, inv.y - 2, 2, 2);
}
}
ctx.fillStyle = "#f00";
for (var i = 0; i < enemyBullets.length; i++) {
var eb = enemyBullets[i];
ctx.fillRect(eb.x, eb.y, eb.w, eb.h);
}
ctx.fillStyle = "#0f0";
ctx.font = "16px monospace";
ctx.fillText("SCORE: " + score, 10, 20);
ctx.fillText("LIVES: " + lives, 300, 20);
if (gameOver) {
ctx.fillStyle = "#f00";
ctx.font = "30px monospace";
ctx.fillText("GAME OVER", 120, 200);
ctx.font = "14px monospace";
ctx.fillText("PRESS SPACE TO RESTART", 115, 230);
}
if (victory) {
ctx.fillStyle = "#0f0";
ctx.font = "30px monospace";
ctx.fillText("VICTORY!", 130, 200);
ctx.font = "14px monospace";
ctx.fillText("PRESS SPACE TO RESTART", 115, 230);
}
}
function loop() {
update();
draw();
requestAnimationFrame(loop);
}
initInvaders();
loop();
</script>
googlegemini-flash-lite-latest
6.9/103.7 s
googlegemini-flash-lite-latest
Tokens1 384
Source code1.9 KB
Time3.7 s
Matania Judgment
Gameplay
8
UX
7
Code quality
7
Completeness
9
Fidelity
6
Overall
6.88
Review
The gameplay is functional and adheres to the core logic of Space Invaders. However, prompt fidelity is penalized by the total absence of the requested sound effects and the failure to follow the output format (the code is a single compact line, making it difficult to read). The visual style is appropriately retro but very minimalist.
The code is truncated (incomplete) at the end, which prevents any execution and makes the game completely unplayable. Although the initial structure adheres to the format and style constraints, the absence of the final rendering loop and the enemy shooting logic renders the creation invalid for the requested task.
<canvas id="c" width="400" height="400"></canvas><script>
var canvas = document.getElementById("c");
var ctx = canvas.getContext("2d");
var player = { x: 180, y: 380, width: 40, height: 20, speed: 5, lives: 3 };
var bullets = [];
var invaders = [];
var invaderBullets = [];
var invaderDirection = 1;
var invaderSpeed = 0.5;
var invaderMoveDown = false;
var score = 0;
var gameOver = false;
var victory = false;
var keys = {};
var invaderShootInterval = 1000;
var lastInvaderShootTime = 0;
var bulletSpeed = 7;
var invaderBulletSpeed = 3;
for (var row = 0; row < 5; row++) {
for (var col = 0; col < 8; col++) {
invaders.push({
x: col * 45 + 30,
y: row * 30 + 30,
width: 30,
height: 20,
alive: true
});
}
}
function playSound(frequency, duration) {
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
var oscillator = audioCtx.createOscillator();
oscillator.type = "square";
oscillator.frequency.value = frequency;
oscillator.connect(audioCtx.destination);
oscillator.start();
oscillator.stop(audioCtx.currentTime + duration / 1000);
}
function drawPlayer() {
ctx.fillStyle = "#0f0";
ctx.fillRect(player.x, player.y, player.width, player.height);
ctx.fillRect(player.x + 15, player.y - 10, 10, 10);
}
function drawBullets() {
ctx.fillStyle = "#0f0";
for (var i = 0; i < bullets.length; i++) {
ctx.fillRect(bullets[i].x, bullets[i].y, bullets[i].width, bullets[i].height);
}
}
function drawInvaders() {
ctx.fillStyle = "#0f0";
for (var i = 0; i < invaders.length; i++) {
if (invaders[i].alive) {
ctx.fillRect(invaders[i].x, invaders[i].y, invaders[i].width, invaders[i].height);
}
}
}
function drawInvaderBullets() {
ctx.fillStyle = "#0f0";
for (var i = 0; i < invaderBullets.length; i++) {
ctx.fillRect(invaderBullets[i].x, invaderBullets[i].y, invaderBullets[i].width, invaderBullets[i].height);
}
}
function drawScore() {
ctx.fillStyle = "#0f0";
ctx.font = "16px monospace";
ctx.fillText("SCORE: " + score, 10, 20);
}
function drawLives() {
ctx.fillStyle = "#0f0";
ctx.font = "16px monospace";
ctx.fillText("LIVES: " + player.lives, 300, 20);
}
function drawGameOver() {
ctx.fillStyle = "#0f0";
ctx.font = "32px monospace";
ctx.fillText("GAME OVER", 100, 200);
}
function drawVictory() {
ctx.fillStyle = "#0f0";
ctx.font = "32px monospace";
ctx.fillText("VICTORY!", 120, 200);
}
function updatePlayer() {
if (keys[37] && player.x > 0) {
player.x -= player.speed;
}
if (keys[39] && player.x < canvas.width - player.width) {
player.x += player.speed;
}
if (keys[32] && bullets.length < 1) {
bullets.push({ x: player.x + player.width / 2 - 2, y: player.y, width: 4, height: 10 });
playSound(800, 100);
}
}
function updateBullets() {
for (var i = bullets.length - 1; i >= 0; i--) {
bullets[i].y -= bulletSpeed;
if (bullets[i].y < 0) {
bullets.splice(i, 1);
}
}
}
function updateInvaders() {
var moveDown = false;
for (var i = 0; i < invaders.length; i++) {
if (invaders[i].alive) {
if (invaders[i].x + invaders[i].width > canvas.width || invaders[i].x < 0) {
moveDown = true;
}
}
}
if (moveDown) {
invaderDirection *= -1;
for (var i = 0; i < invaders.length; i++) {
invaders[i].y += 20;
}
}
for (var i = 0; i < invaders.length; i++) {
if (invaders[i].alive) {
invaders[i].x += invaderSpeed * invaderDirection;
}
}
for (var i = 0; i < invaders.length; i++) {
if (invaders[i].alive && invaders[i].y + invaders[i].height >= player.y) {
gameOver = true;
}
}
}
function updateInvaderBullets() {
for (var i = invaderBullets.length - 1; i >= 0; i--) {
invaderBullets[i].y += invaderBulletSpeed;
if (invaderBullets[i].y > canvas.height) {
invaderBullets.splice(i, 1);
}
}
}
function checkCollisions() {
for (var i = bullets.length - 1; i >= 0; i--) {
for (var j = invaders.length - 1; j >= 0; j--) {
if (invaders[j].alive &&
bullets[i].x < invaders[j].x + invaders[j].width &&
bullets[i].x + bullets[i].width > invaders[j].x &&
bullets[i].y < invaders[j].y + invaders[j].height &&
bullets[i].y + bullets[i].height > invaders[j].y) {
invaders[j].alive = false;
bullets.splice(i, 1);
score += 10;
playSound(200, 100);
break;
}
}
}
for (var i = invaderBullets.length - 1; i >= 0; i--) {
if (invaderBullets[i].x < player.x + player.width &&
invaderBullets[i].x + invaderBullets[i].width > player.x &&
invaderBullets[i].y < player.y + player.height &&
invaderBullets[i].y + invaderBullets[i].height > player.y) {
invaderBullets.splice(i, 1);
player.lives--;
playSound(100, 200);
if (player.lives <= 0) {
gameOver = true;
}
}
}
var allDead = true;
for (var i = 0; i < invaders.length; i++) {
if (invaders[i].alive) {
allDead = false;
break;
}
}
if (allDead) {
victory = true;
}
}
function invaderShoot() {
var aliveInvaders = [];
for (var i = 0; i < invaders.length; i++) {
if (invaders[i].alive) {
aliveInvaders.push(invaders[i]);
}
}
if (aliveInvaders.length > 0) {
var shooter = aliveInvaders[Math.floor(Math.random() * aliveInvaders.length)];
invaderBullets.push({
x: shooter.x + shooter.width / 2 - 2,
y: shooter.y + shooter.height,
width: 4,
height: 10
});
playSound(400, 100);
}
}
function gameLoop() {
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, canvas.width, canvas.height);
if (!gameOver && !victory) {
updatePlayer();
updateBullets();
updateInvaders();
updateInvaderBullets();
checkCollisions();
var currentTime = new Date().getTime();
if (currentTime - lastInvaderShootTime > invaderShootInterval) {
invaderShoot();
lastInvaderShootTime = currentTime;
}
}
drawBullets();
drawInvaders();
drawInvaderBullets();
drawPlayer();
drawScore();
drawLives();
if (gameOver) {
drawGameOver();
}
if (victory) {
drawVictory();
}
requestAnimationFrame(gameLoop);
}
document.addEventListener("keydown", function(e) {
keys[e.keyCode] = true;
});
document.addEventListener("keyup", function(e) {
keys[e.keyCode] = false;
});
gameLoop();
</script>
mistralmistral-medium-latest
2.3/1014.7 s
mistralmistral-medium-latest
Tokens2 612
Source code8.3 KB
Time14.7 s
Matania Judgment
Gameplay
0
UX
0
Code quality
2
Completeness
0
Fidelity
4
Overall
2.25
Review
The code is incomplete and cuts off abruptly in the middle of a conditional statement (truncated), making the game completely unplayable. Although the initial structure adheres to the style and format constraints, the absence of the end of the script prevents any execution, resulting in zero scores for gameplay, UX, and completeness criteria.
<canvas id="c" width="400" height="400"></canvas>
<script>
var c = document.getElementById("c");
var ctx = c.getContext("2d");
var keys = {};
var player = {x: 200, y: 370, width: 20, height: 10, speed: 3, lives: 3, cooldown: 0};
var bullets = [];
var invaders = [];
var invaderBullets = [];
var score = 0;
var gameOver = false;
var victory = false;
var direction = 1;
var moveDown = false;
var lastInvaderShot = 0;
var invaderSpeed = 1;
var invaderMoveInterval = 60;
var moveCounter = 0;
var gameSpeed = 1000/60;
function init() {
invaders = [];
for (var row = 0; row < 5; row++) {
for (var col = 0; col < 8; col++) {
invaders.push({
x: 50 + col * 40,
y: 50 + row * 30,
width: 20,
height: 20,
alive: true
});
}
}
player.lives = 3;
score = 0;
gameOver = false;
victory = false;
direction = 1;
invaderSpeed = 1;
moveDown = false;
bullets = [];
invaderBullets = [];
}
function drawPlayer() {
ctx.fillStyle = "#0f0";
ctx.fillRect(player.x - player.width/2, player.y - player.height/2, player.width, player.height);
ctx.fillStyle = "#000";
ctx.fillRect(player.x - player.width/2 + 5, player.y - 5, 10, 5);
}
function drawInvaders() {
ctx.fillStyle = "#0f0";
for (var i = 0; i < invaders.length; i++) {
if (invaders[i].alive) {
ctx.fillRect(invaders[i].x - invaders[i].width/2, invaders[i].y - invaders[i].height/2, invaders[i].width, invaders[i].height);
ctx.fillStyle = "#000";
ctx.fillRect(invaders[i].x - 5, invaders[i].y - 3, 3, 3);
ctx.fillRect(invaders[i].x + 2, invaders[i].y - 3, 3, 3);
ctx.fillRect(invaders[i].x - 5, invaders[i].y + 2, 3, 3);
ctx.fillRect(invaders[i].x + 2, invaders[i].y + 2, 3, 3);
ctx.fillStyle = "#0f0";
}
}
}
function drawBullets() {
ctx.fillStyle = "#0f0";
for (var i = 0; i < bullets.length; i++) {
ctx.fillRect(bullets[i].x - 1, bullets[i].y - 3, 2, 6);
}
ctx.fillStyle = "#f00";
for (var i = 0; i < invaderBullets.length; i++) {
ctx.fillRect(invaderBullets[i].x - 1, invaderBullets[i].y - 3, 2, 6);
}
}
function drawUI() {
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, 400, 20);
ctx.font = "12px monospace";
ctx.fillStyle = "#0f0";
ctx.fillText("SCORE: " + score, 10, 15);
ctx.fillText("LIVES: " + player.lives, 300, 15);
if (gameOver) {
ctx.fillStyle = "rgba(0,0,0,0.7)";
ctx.fillRect(0, 0, 400, 400);
ctx.font = "24px monospace";
ctx.fillStyle = "#f00";
ctx.fillText("GAME OVER", 140, 200);
ctx.font = "16px monospace";
ctx.fillText("Press R to restart", 140, 230);
}
if (victory) {
ctx.fillStyle = "rgba(0,0,0,0.7)";
ctx.fillRect(0, 0, 400, 400);
ctx.font = "24px monospace";
ctx.fillStyle = "#0f0";
ctx.fillText("VICTORY!", 160, 200);
ctx.font = "16px monospace";
ctx.fillText("Press R to restart", 140, 230);
}
}
function update() {
if (gameOver || victory) return;
// Player movement
if (keys[37] && player.x > 15) player.x -= player.speed;
if (keys[39] && player.x < 385) player.x += player.speed;
// Player shooting
if (keys[32] && player.cooldown <= 0) {
bullets.push({x: player.x, y: player.y - 10, speed: 5});
player.cooldown = 15;
playShootSound();
}
if (player.cooldown > 0) player.cooldown--;
// Update bullets
for (var i = bullets.length - 1; i >= 0; i--) {
bullets[i].y -= bullets[i].speed;
if (bullets[i].y < 0) {
bullets.splice(i, 1);
}
}
// Update invader bullets
for (var i = invaderBullets.length - 1; i >= 0; i--) {
invaderBullets[i].y += 3;
if (invaderBullets[i].y > 400) {
invaderBullets.splice(i, 1);
}
}
// Invader movement
moveCounter++;
if (moveCounter >= invaderMoveInterval) {
moveCounter = 0;
var hitEdge = false;
for (var i = 0; i < invaders.length; i++) {
if (invaders[i].alive) {
invaders[i].x += direction * invaderSpeed;
if (invaders[i].x > 380 || invaders[i].x < 20) {
hitEdge = true;
}
}
}
if (hitEdge) {
direction *= -1;
for (var i = 0; i < invaders.length; i++) {
if (invaders[i].alive) {
invaders[i].y += 10;
}
}
}
}
// Invader shooting
if (Date.now() - lastInvaderShot > 1000 && Math.random() < 0.1) {
var aliveInvaders = invaders.filter(function(i) { return i.alive; });
if (aliveInvaders.length > 0) {
var shooter = aliveInvaders[Math.floor(Math.random() * aliveInvaders.length)];
invaderBullets.push({x: shooter.x, y: shooter.y + 10});
lastInvaderShot = Date.now();
playInvaderShootSound();
}
}
// Check bullet-invader collisions
for (var i = bullets.length - 1; i >= 0; i--) {
for (var j = 0; j < invaders.length; j++) {
if (invaders[j].alive &&
bullets[i].x > invaders[j].x - invaders[j].width/2 &&
bullets[i].x < invaders[j].x + invaders[j].width/2 &&
bullets[i].y > invaders[j].y - invaders[j].height/2 &&
bullets[i].y < invaders[j].y + invaders[j].height/2) {
invaders[j].alive = false;
bullets.splice(i, 1);
score += 10;
playExplosionSound();
break;
}
}
}
// Check invader-player collisions
for (var i = 0; i < invaders.length; i++) {
if (invaders[i].alive && invaders[i].y + invaders[i].height/2 >= player.y - player.height/2) {
gameOver = true;
}
}
// Check bullet-player collisions
for (var i = invaderBullets.length - 1; i >= 0; i--) {
if (invaderBullets[i].x > player.x - player.width/2 &&
invaderBullets[i].x < player.x + player.width/2 &&
invaderBullets[i].y > player.y - player.height/2 &&
invaderBullets[i].y < player.y + player.height/2) {
invaderBullets.splice(i, 1);
player.lives--;
playExplosionSound();
if (player.lives <= 0) {
gameOver = true;
}
}
}
// Check victory
var allDead = true;
for (var i = 0; i < invaders.length; i++) {
if (invaders[i].alive) {
allDead = false;
break;
}
}
if (allDead) {
victory = true;
}
}
function draw() {
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, 400, 400);
drawInvaders();
drawBullets();
drawPlayer();
drawUI();
}
function playShootSound() {
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
var oscillator = audioCtx.createOscillator();
oscillator.type = "square";
oscillator.frequency.setValueAtTime(500, audioCtx.currentTime);
oscillator.connect(audioCtx.destination);
oscillator.start();
oscillator.stop(audioCtx.currentTime + 0.1);
}
function playInvaderShootSound() {
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
var oscillator = audioCtx.createOscillator();
oscillator.type = "sawtooth";
oscillator.frequency.setValueAtTime(200, audioCtx.currentTime);
oscillator.connect(audioCtx.destination);
oscillator.start();
oscillator.stop(audioCtx.currentTime + 0.3);
}
function playExplosionSound() {
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
var oscillator = audioCtx.createOscillator();
oscillator.type = "triangle";
oscillator.frequency.setValueAtTime(100, audioCtx.currentTime);
oscillator.connect(audioCtx.destination);
oscillator.start();
oscillator.stop(audioCtx.currentTime + 0.2);
}
function gameLoop() {
update();
draw();
requestAnimationFrame(gameLoop);
}
window.addEventListener("keydown", function(e) {
keys[e.keyCode] = true;
if ((gameOver || victory) && e.keyCode === 82) {
init();
}
});
window.addEventListener("keyup", function(e) {
keys[e.keyCode] = false;
});
init();
gameLoop();
</script>
openaigpt-4o-mini
7.0/1053.8 s
openaigpt-4o-mini
Tokens1 560
Source code4.2 KB
Time53.8 s
Matania Judgment
Gameplay
8
UX
7
Code quality
8
Completeness
9
Fidelity
6
Overall
7
Review
The gameplay is functional and adheres to the core mechanics, but the model failed completely on the sound effects constraint (point 7), which is a major omission. Furthermore, although the visual style is respected, the lack of sound and the simplistic collision handling (the player can only fire one shot at a time) reduce the overall fidelity to the complex prompt.
<canvas id="c" width="400" height="400"></canvas><script>
var c = document.getElementById("c");
var ctx = c.getContext("2d");
var playerWidth = 30, playerHeight = 10, playerX = 185, playerY = 390;
var bullets = [], bulletWidth = 5, bulletHeight = 10;
var invaders = [], invaderWidth = 20, invaderHeight = 10;
var invaderRows = 5, invaderCols = 8;
var score = 0, lives = 3, gameOver = false;
var invaderSpeed = 1, down = false, invaderDirection = 1, invaderShootInterval;
var playerMove = { left: false, right: false };
function init() {
for (var r = 0; r < invaderRows; r++) {
for (var c = 0; c < invaderCols; c++) {
invaders.push({ x: c * (invaderWidth + 10) + 20, y: r * (invaderHeight + 10) + 30, alive: true });
}
}
invaderShootInterval = setInterval(shootInvader, 1000);
requestAnimationFrame(gameLoop);
}
function drawPlayer() {
ctx.fillStyle = "#0f0";
ctx.fillRect(playerX, playerY, playerWidth, playerHeight);
}
function drawBullets() {
ctx.fillStyle = "#0f0";
for (var i = 0; i < bullets.length; i++) {
ctx.fillRect(bullets[i].x, bullets[i].y, bulletWidth, bulletHeight);
}
}
function drawInvaders() {
ctx.fillStyle = "#0f0";
for (var i = 0; i < invaders.length; i++) {
if (invaders[i].alive) {
ctx.fillRect(invaders[i].x, invaders[i].y, invaderWidth, invaderHeight);
}
}
}
function updateBullets() {
for (var i = 0; i < bullets.length; i++) {
bullets[i].y -= 2;
if (bullets[i].y < 0) {
bullets.splice(i, 1);
i--;
}
}
}
function updateInvaders() {
var hitBottom = false;
for (var i = 0; i < invaders.length; i++) {
if (invaders[i].alive) {
invaders[i].x += invaderSpeed * invaderDirection;
if (invaders[i].x > 380) {
invaderDirection = -1;
down = true;
} else if (invaders[i].x < 0) {
invaderDirection = 1;
down = true;
}
if (down) {
invaders[i].y += 10;
if (invaders[i].y > playerY) {
hitBottom = true;
}
}
}
}
if (hitBottom) {
gameOver = true;
clearInterval(invaderShootInterval);
}
if (down) {
down = false;
}
}
function shootInvader() {
for (var i = 0; i < invaders.length; i++) {
if (invaders[i].alive && Math.random() < 0.1) {
bullets.push({ x: invaders[i].x + invaderWidth / 2, y: invaders[i].y + invaderHeight });
break;
}
}
}
function checkCollisions() {
for (var i = 0; i < bullets.length; i++) {
if (bullets[i].y < playerY + playerHeight && bullets[i].x > playerX && bullets[i].x < playerX + playerWidth) {
lives--;
bullets.splice(i, 1);
if (lives <= 0) {
gameOver = true;
}
return;
}
for (var j = 0; j < invaders.length; j++) {
if (invaders[j].alive && bullets[i].y < invaders[j].y + invaderHeight && bullets[i].x > invaders[j].x && bullets[i].x < invaders[j].x + invaderWidth) {
invaders[j].alive = false;
bullets.splice(i, 1);
score += 10;
return;
}
}
}
}
function drawScore() {
ctx.fillStyle = "#0f0";
ctx.font = "16px monospace";
ctx.fillText("Score: " + score, 10, 20);
ctx.fillText("Lives: " + lives, 300, 20);
}
function clearScreen() {
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, c.width, c.height);
}
function gameLoop() {
if (!gameOver) {
clearScreen();
drawInvaders();
drawBullets();
drawPlayer();
drawScore();
updateBullets();
updateInvaders();
checkCollisions();
if (playerMove.left && playerX > 0) {
playerX -= 2;
}
if (playerMove.right && playerX < c.width - playerWidth) {
playerX += 2;
}
requestAnimationFrame(gameLoop);
} else {
ctx.fillStyle = "#0f0";
ctx.font = "30px monospace";
ctx.fillText("Game Over", 120, 200);
}
}
document.addEventListener("keydown", function(event) {
if (event.keyCode === 37) { playerMove.left = true; }
if (event.keyCode === 39) { playerMove.right = true; }
if (event.keyCode === 32 && bullets.length < 1) {
bullets.push({ x: playerX + playerWidth / 2, y: playerY });
}
});
document.addEventListener("keyup", function(event) {
if (event.keyCode === 37) { playerMove.left = false; }
if (event.keyCode === 39) { playerMove.right = false; }
});
init();
</script>
openaigpt-5.4
1.3/1040.9 s
openaigpt-5.4
Tokens3 159
Source code10.4 KB
Time40.9 s
Matania Judgment
Gameplay
0
UX
0
Code quality
2
Completeness
0
Fidelity
2
Overall
1.25
Review
The model failed critically: the provided code is truncated (it cuts off abruptly in the middle of a function), making the game completely unplayable. As a result, no functionality can be verified, and the rendering is either non-existent or incomplete. The code score is minimal because while the initial structure is correct, it is useless without the ending.
<canvas id="c" width="400" height="400"></canvas><script>
var c = document.getElementById('c');
var x = c.getContext('2d');
x.imageSmoothingEnabled = false;
var W = c.width;
var H = c.height;
var keys = {};
var state = 'play';
var score = 0;
var lives = 3;
var frame = 0;
var stars = [];
var messageTimer = 0;
var player = {
x: 184,
y: 368,
w: 32,
h: 16,
speed: 3.2,
cooldown: 0,
hitFlash: 0
};
var playerBullets = [];
var enemyBullets = [];
var particles = [];
var inv = {
cols: 8,
rows: 5,
list: [],
dir: 1,
speed: 0.45,
stepDown: 10,
anim: 0,
fireCooldown: 0
};
function initStars() {
var i;
for (i = 0; i < 60; i++) {
stars.push({
x: Math.random() * W,
y: Math.random() * H,
s: Math.random() < 0.8 ? 1 : 2
});
}
}
function initInvaders() {
inv.list = [];
inv.dir = 1;
inv.speed = 0.45;
inv.fireCooldown = 30;
var r, col;
for (r = 0; r < inv.rows; r++) {
for (col = 0; col < inv.cols; col++) {
inv.list.push({
x: 34 + col * 40,
y: 38 + r * 26,
w: 24,
h: 16,
alive: true,
type: r % 3
});
}
}
}
function resetGame() {
score = 0;
lives = 3;
state = 'play';
frame = 0;
messageTimer = 0;
player.x = 184;
player.y = 368;
player.cooldown = 0;
player.hitFlash = 0;
playerBullets = [];
enemyBullets = [];
particles = [];
initInvaders();
}
function beep(freq, dur, type, vol) {
try {
if (!beep.ctx) {
beep.ctx = new (window.AudioContext || window.webkitAudioContext)();
}
var ctx = beep.ctx;
if (ctx.state === 'suspended') {
ctx.resume();
}
var o = ctx.createOscillator();
var g = ctx.createGain();
o.type = type || 'square';
o.frequency.value = freq;
g.gain.value = vol || 0.03;
o.connect(g);
g.connect(ctx.destination);
var t = ctx.currentTime;
g.gain.setValueAtTime(vol || 0.03, t);
g.gain.exponentialRampToValueAtTime(0.0001, t + dur);
o.start(t);
o.stop(t + dur);
} catch (e) {}
}
function shootPlayer() {
if (player.cooldown <= 0 && playerBullets.length < 2 && state === 'play') {
playerBullets.push({
x: player.x + player.w / 2 - 1,
y: player.y - 8,
w: 2,
h: 8,
vy: -5.0
});
player.cooldown = 14;
beep(700, 0.06, 'square', 0.04);
}
}
function enemyShoot() {
var aliveCols = {};
var i;
for (i = 0; i < inv.list.length; i++) {
if (inv.list[i].alive) {
var col = Math.round((inv.list[i].x - 34) / 40);
aliveCols[col] = inv.list[i];
}
}
var choices = [];
for (i in aliveCols) {
if (aliveCols.hasOwnProperty(i)) {
choices.push(aliveCols[i]);
}
}
if (choices.length > 0) {
var s = choices[(Math.random() * choices.length) | 0];
enemyBullets.push({
x: s.x + s.w / 2 - 1,
y: s.y + s.h + 2,
w: 2,
h: 8,
vy: 2.2 + Math.random() * 0.6
});
beep(180, 0.08, 'sawtooth', 0.02);
}
}
function spawnExplosion(cx, cy, n) {
var i;
for (i = 0; i < n; i++) {
particles.push({
x: cx,
y: cy,
vx: (Math.random() * 2 - 1) * 2.0,
vy: (Math.random() * 2 - 1) * 2.0,
life: 18 + (Math.random() * 12 | 0),
size: 2 + (Math.random() * 2 | 0)
});
}
}
function rectsOverlap(a, b) {
return a.x < b.x + b.w && a.x + a.w > b.x && a.y < b.y + b.h && a.y + a.h > b.y;
}
function aliveCount() {
var n = 0;
var i;
for (i = 0; i < inv.list.length; i++) {
if (inv.list[i].alive) {
n++;
}
}
return n;
}
function hitPlayer() {
lives--;
player.hitFlash = 20;
spawnExplosion(player.x + player.w / 2, player.y + player.h / 2, 18);
beep(120, 0.2, 'square', 0.05);
beep(80, 0.25, 'triangle', 0.04);
if (lives <= 0) {
state = 'lose';
messageTimer = 0;
}
}
function update() {
frame++;
if (state !== 'play') {
messageTimer++;
if (keys[13] || keys[32]) {
resetGame();
}
updateParticles();
return;
}
if (player.cooldown > 0) {
player.cooldown--;
}
if (player.hitFlash > 0) {
player.hitFlash--;
}
if (keys[37]) {
player.x -= player.speed;
}
if (keys[39]) {
player.x += player.speed;
}
if (player.x < 4) {
player.x = 4;
}
if (player.x + player.w > W - 4) {
player.x = W - 4 - player.w;
}
updatePlayerBullets();
updateEnemyBullets();
updateInvaders();
updateParticles();
if (aliveCount() === 0) {
state = 'win';
messageTimer = 0;
beep(880, 0.08, 'square', 0.04);
setTimeout(function(){ beep(990, 0.08, 'square', 0.04); }, 90);
setTimeout(function(){ beep(1320, 0.12, 'square', 0.05); }, 180);
}
}
function updatePlayerBullets() {
var i, j, b, e;
for (i = playerBullets.length - 1; i >= 0; i--) {
b = playerBullets[i];
b.y += b.vy;
if (b.y + b.h < 0) {
playerBullets.splice(i, 1);
continue;
}
for (j = 0; j < inv.list.length; j++) {
e = inv.list[j];
if (e.alive && rectsOverlap(b, e)) {
e.alive = false;
playerBullets.splice(i, 1);
score += 10;
spawnExplosion(e.x + e.w / 2, e.y + e.h / 2, 12);
beep(320 + Math.random() * 120, 0.07, 'square', 0.04);
if (score % 80 === 0) {
inv.speed += 0.03;
}
break;
}
}
}
}
function updateEnemyBullets() {
var i, b;
for (i = enemyBullets.length - 1; i >= 0; i--) {
b = enemyBullets[i];
b.y += b.vy;
if (b.y > H + 10) {
enemyBullets.splice(i, 1);
continue;
}
if (rectsOverlap(b, player)) {
enemyBullets.splice(i, 1);
hitPlayer();
}
}
}
function updateInvaders() {
inv.anim = (frame / 20 | 0) % 2;
var minX = 9999;
var maxX = -9999;
var maxY = -9999;
var i, e;
for (i = 0; i < inv.list.length; i++) {
e = inv.list[i];
if (!e.alive) {
continue;
}
e.x += inv.speed * inv.dir;
if (e.x < minX) {
minX = e.x;
}
if (e.x + e.w > maxX) {
maxX = e.x + e.w;
}
if (e.y + e.h > maxY) {
maxY = e.y + e.h;
}
}
if (maxX >= W - 8 || minX <= 8) {
inv.dir *= -1;
for (i = 0; i < inv.list.length; i++) {
if (inv.list[i].alive) {
inv.list[i].y += inv.stepDown;
}
}
beep(140, 0.05, 'square', 0.02);
}
for (i = 0; i < inv.list.length; i++) {
if (inv.list[i].alive && inv.list[i].y + inv.list[i].h >= player.y) {
state = 'lose';
messageTimer = 0;
beep(70, 0.35, 'sawtooth', 0.05);
return;
}
}
inv.fireCooldown--;
if (inv.fireCooldown <= 0) {
enemyShoot();
var alive = aliveCount();
inv.fireCooldown = Math.max(18, 70 - (40 - alive));
}
}
function updateParticles() {
var i, p;
for (i = particles.length - 1; i >= 0; i--) {
p = particles[i];
p.x += p.vx;
p.y += p.vy;
p.life--;
if (p.life <= 0) {
particles.splice(i, 1);
}
}
}
function drawText(t, px, py, color, align) {
x.fillStyle = color;
x.font = '12px monospace';
x.textAlign = align || 'left';
x.fillText(t, px, py);
}
function drawSprite(px, py, sprite, color, scale) {
var r, col;
x.fillStyle = color;
for (r = 0; r < sprite.length; r++) {
for (col = 0; col < sprite[r].length; col++) {
if (sprite[r].charAt(col) === '1') {
x.fillRect(px + col * scale, py + r * scale, scale, scale);
}
}
}
}
var shipSprite = [
'000010000',
'000111000',
'001111100',
'011111110',
'111111111',
'111011111',
'010000010'
];
var invSpriteA = [
'00100100',
'00011000',
'00111100',
'01111110',
'11011011',
'11111111',
'00100100',
'01000010'
];
var invSpriteB = [
'00100100',
'10011001',
'10111101',
'11111111',
'01111110',
'00111100',
'01011010',
'10000001'
];
var invSpriteC = [
'00011000',
'00111100',
'01111110',
'11011011',
'11111111',
'00100100',
'01000010',
'10100101'
];
function drawBackground() {
x.fillStyle = '#000000';
x.fillRect(0, 0, W, H);
x.fillStyle = '#003300';
var i, s;
for (i = 0; i < stars.length; i++) {
s = stars[i];
x.fillRect((s.x + frame * 0.03 * s.s) % W, s.y, s.s, s.s);
}
x.fillStyle = '#00aa00';
x.fillRect(0, 24, W, 1);
x.fillRect(0, H - 24, W, 1);
}
function drawHUD() {
drawText('SCORE ' + score, 8, 16, '#00ff00', 'left');
drawText('VIES ' + lives, W - 8, 16, '#00ff00', 'right');
}
function drawPlayer() {
var color = player.hitFlash > 0 && ((frame / 2 | 0) % 2 === 0) ? '#ffffff' : '#00ff00';
drawSprite(player.x, player.y, shipSprite, color, 3);
}
function drawInvaders() {
var i, e, sprite;
for (i = 0; i < inv.list.length; i++) {
e = inv.list[i];
if (!e.alive) {
continue;
}
if (e.type === 0) {
sprite = inv.anim ? invSpriteA : invSpriteB;
} else if (e.type === 1) {
sprite = inv.anim ? invSpriteB : invSpriteC;
} else {
sprite = inv.anim ? invSpriteC : invSpriteA;
}
drawSprite(e.x, e.y, sprite, '#00ff00', 2);
}
}
function drawBullets() {
var i;
x.fillStyle = '#00ff00';
for (i = 0; i < playerBullets.length; i++) {
x.fillRect(playerBullets[i].x, playerBullets[i].y, playerBullets[i].w, playerBullets[i].h);
}
x.fillStyle = '#66ff66';
for (i = 0; i < enemyBullets.length; i++) {
x.fillRect(enemyBullets[i].x, enemyBullets[i].y, enemyBullets[i].w, enemyBullets[i].h);
}
}
function drawParticles() {
var i, p;
x.fillStyle = '#00ff00';
for (i = 0; i < particles.length; i++) {
p = particles[i];
x.fillRect(p.x, p.y, p.size, p.size);
}
}
function drawOverlay() {
if (state === 'play') {
return;
}
x.fillStyle = 'rgba(0,0,0,0.6)';
x.fillRect(0, 0, W, H);
x.strokeStyle = '#00ff00';
x.strokeRect(40, 140, 320, 100);
x.fillStyle = '#00ff00';
x.font = '20px monospace';
x.textAlign = 'center';
if (state === 'win') {
x.fillText('VICTOIRE', W / 2, 180);
} else {
x.fillText('GAME OVER', W / 2, 180);
}
x.font = '12px monospace';
x.fillText('ENTREE OU ESPACE POUR REJOUER', W / 2, 210);
}
function draw() {
drawBackground();
drawHUD();
drawInvaders();
drawPlayer();
drawBullets();
drawParticles();
drawOverlay();
}
function loop() {
update();
draw();
requestAnimationFrame(loop);
}
window.addEventListener('keydown', function(e) {
keys[e.keyCode] = true;
if (e.keyCode === 32) {
shootPlayer();
e.preventDefault();
}
if (e.keyCode === 37 || e.keyCode === 39) {
e.preventDefault();
}
if (beep.ctx && beep.ctx.state === 'suspended') {
beep.ctx.resume();
}
});
window.addEventListener('keyup', function(e) {
keys[e.keyCode] = false;
});
initStars();
initInvaders();
draw();
loop();
</script>
openaigpt-5.4-mini
9.8/1010.1 s
openaigpt-5.4-mini
Tokens1 879
Source code5.4 KB
Time10.1 s
Matania Judgment
Gameplay
10
UX
9
Code quality
9
Completeness
10
Fidelity
10
Overall
9.75
Review
The model perfectly adhered to all technical and stylistic constraints. The code is clean, utilizes the Web Audio API for sound effects as requested, and implements a robust game loop with collision detection and progressive difficulty scaling. The visual rendering is faithful to the requested retro arcade aesthetic.
<canvas id="c" width="400" height="400"></canvas><script>
var c=document.getElementById('c');
var x=c.getContext('2d');
var W=400,H=400;
var keys={};
var over=0,win=0;
var score=0,lives=3;
var last=0,spawnTimer=0,moveTimer=0,enemyShootTimer=0,playerCooldown=0;
var player={x:200,y:370,w:22,h:12,speed:4};
var bullets=[];
var enemyBullets=[];
var inv=[];
var dir=1;
var invSpeed=20;
var invDrop=14;
var invW=18,invH=12,invGapX=8,invGapY=10;
var cols=8,rows=5;
var baseX=60,baseY=50;
var audioCtx=null;
function beep(freq,dur,type,vol){
if(!audioCtx){
try{audioCtx=new (window.AudioContext||window.webkitAudioContext)();}catch(e){return;}
}
var o=audioCtx.createOscillator();
var g=audioCtx.createGain();
o.type=type||'square';
o.frequency.value=freq;
g.gain.value=vol||0.04;
o.connect(g);
g.connect(audioCtx.destination);
o.start();
o.stop(audioCtx.currentTime+(dur||0.1));
}
function initInvaders(){
inv=[];
for(var r=0;r<rows;r++){
for(var col=0;col<cols;col++){
inv.push({x:baseX+col*(invW+invGapX),y:baseY+r*(invH+invGapY),w:invW,h:invH,alive:1});
}
}
}
function reset(){
over=0;win=0;score=0;lives=3;
player.x=200;player.y=370;
bullets=[];enemyBullets=[];
dir=1;invSpeed=20;
initInvaders();
}
function drawRect(x0,y0,w,h,col){
x.fillStyle=col;
x.fillRect(x0,y0,w,h);
}
function rects(a,b){
return a.x<a.x+a.w&&b.x<b.x+b.w&&a.x<b.x+b.w&&a.x+a.w>b.x&&a.y<b.y+b.h&&a.y+a.h>b.y;
}
function collide(ax,ay,aw,ah,bx,by,bw,bh){
return ax<bx+bw&&ax+aw>bx&&ay<by+bh&&ay+ah>by;
}
function aliveCount(){
var n=0;
for(var i=0;i<inv.length;i++) if(inv[i].alive) n++;
return n;
}
function enemyBounds(){
var minX=999,maxX=-999,maxY=-999;
for(var i=0;i<inv.length;i++){
if(inv[i].alive){
if(inv[i].x<minX)minX=inv[i].x;
if(inv[i].x+inv[i].w>maxX)maxX=inv[i].x+inv[i].w;
if(inv[i].y+inv[i].h>maxY)maxY=inv[i].y+inv[i].h;
}
}
return {minX:minX,maxX:maxX,maxY:maxY};
}
function shootPlayer(){
if(playerCooldown>0||over||win) return;
bullets.push({x:player.x+player.w/2-1,y:player.y-6,w:2,h:6,vy:-7});
playerCooldown=220;
beep(520,0.06,'square',0.03);
}
function shootEnemy(){
var living=[];
for(var i=0;i<inv.length;i++) if(inv[i].alive) living.push(inv[i]);
if(!living.length) return;
var e=living[Math.floor(Math.random()*living.length)];
enemyBullets.push({x:e.x+e.w/2-1,y:e.y+e.h,w:2,h:6,vy:3.2});
beep(180,0.05,'sawtooth',0.02);
}
function step(ts){
if(!last) last=ts;
var dt=ts-last;
last=ts;
if(!over&&!win){
if(keys[37]) player.x-=player.speed;
if(keys[39]) player.x+=player.speed;
if(player.x<6) player.x=6;
if(player.x>W-player.w-6) player.x=W-player.w-6;
if(playerCooldown>0) playerCooldown-=dt;
moveTimer+=dt;
var moveInterval=Math.max(120,invSpeed);
if(moveTimer>moveInterval){
moveTimer=0;
var b=enemyBounds();
var hitEdge=(b.minX<=8&&dir<0)||(b.maxX>=W-8&&dir>0);
if(hitEdge){
dir*=-1;
for(var i=0;i<inv.length;i++) if(inv[i].alive) inv[i].y+=invDrop;
invSpeed*=0.97;
beep(120,0.06,'square',0.02);
}else{
for(var j=0;j<inv.length;j++) if(inv[j].alive) inv[j].x+=dir*8;
}
}
spawnTimer+=dt;
if(spawnTimer>650){
spawnTimer=0;
if(aliveCount()>0&&Math.random()<0.55) shootEnemy();
}
for(var k=bullets.length-1;k>=0;k--){
var bl=bullets[k];
bl.y+=bl.vy;
if(bl.y+bl.h<0){bullets.splice(k,1);continue;}
for(var m=0;m<inv.length;m++){
if(inv[m].alive&&collide(bl.x,bl.y,bl.w,bl.h,inv[m].x,inv[m].y,inv[m].w,inv[m].h)){
inv[m].alive=0;
bullets.splice(k,1);
score+=10;
beep(260,0.08,'square',0.05);
break;
}
}
}
for(var n=enemyBullets.length-1;n>=0;n--){
var eb=enemyBullets[n];
eb.y+=eb.vy;
if(eb.y>H){enemyBullets.splice(n,1);continue;}
if(collide(eb.x,eb.y,eb.w,eb.h,player.x,player.y,player.w,player.h)){
enemyBullets.splice(n,1);
lives--;
beep(90,0.12,'sawtooth',0.07);
if(lives<=0){over=1;}
}
}
var bounds=enemyBounds();
if(bounds.maxY>=player.y-2){over=1;}
if(aliveCount()===0){win=1;beep(780,0.16,'square',0.06);}
}
x.fillStyle='#000';
x.fillRect(0,0,W,H);
x.fillStyle='#0f0';
x.font='16px monospace';
x.fillText('SCORE '+score,10,18);
x.fillText('LIVES '+lives,290,18);
drawRect(player.x,player.y,player.w,player.h,'#0f0');
drawRect(player.x+6,player.y-5,10,5,'#0f0');
drawRect(player.x+2,player.y+3,4,2,'#000');
drawRect(player.x+16,player.y+3,4,2,'#000');
for(var p=0;p<bullets.length;p++) drawRect(bullets[p].x,bullets[p].y,bullets[p].w,bullets[p].h,'#0f0');
for(var q=0;q<enemyBullets.length;q++) drawRect(enemyBullets[q].x,enemyBullets[q].y,enemyBullets[q].w,enemyBullets[q].h,'#f00');
for(var r=0;r<inv.length;r++){
if(!inv[r].alive) continue;
var iy=inv[r].y,ix=inv[r].x;
drawRect(ix,iy,invW,invH,'#0f0');
drawRect(ix+3,iy+3,3,3,'#000');
drawRect(ix+12,iy+3,3,3,'#000');
drawRect(ix+5,iy+8,8,2,'#000');
}
x.fillStyle='#0f0';
x.fillRect(0,0,W,2);
x.fillRect(0,H-2,W,2);
if(over||win){
x.fillStyle='#000';
x.fillRect(60,160,280,70);
x.strokeStyle='#0f0';
x.strokeRect(60,160,280,70);
x.fillStyle='#0f0';
x.font='20px monospace';
x.fillText(win?'YOU WIN':'GAME OVER',120,190);
x.font='12px monospace';
x.fillText('PRESS R TO RESTART',110,214);
}
requestAnimationFrame(step);
}
document.addEventListener('keydown',function(e){
keys[e.keyCode]=1;
if(e.keyCode===32){e.preventDefault();shootPlayer();}
if(e.keyCode===82&& (over||win)){reset();}
});
document.addEventListener('keyup',function(e){keys[e.keyCode]=0;});
reset();
requestAnimationFrame(step);
</script>
openaigpt-5.4-nano
9.8/1030.6 s
openaigpt-5.4-nano
Tokens4 309
Source code14.9 KB
Time30.6 s
Matania Judgment
Gameplay
10
UX
9
Code quality
9
Completeness
10
Fidelity
10
Overall
9.75
Review
The model perfectly adhered to all technical and aesthetic constraints. The code is clean (use of an IIFE, AudioContext management, robust game loop) and the visual rendering is excellent, featuring the requested scanline effect and retro green/black style. The gameplay is complete, including life management, enemy projectiles, and scoring.
<canvas id="c" width="400" height="400"></canvas><script>
(function(){
var canvas=document.getElementById('c');
var ctx=canvas.getContext('2d');
var W=canvas.width, H=canvas.height;
var keys={};
var spaceHeld=false;
var lastFire=0;
var player={
x: W/2-15,
y: H-35,
w: 30,
h: 18,
speed: 220,
cooldown: 280
};
var playerBullet=null; // un seul tir
var bulletSpeed=330;
var lives=3;
var score=0;
var gameState="ready"; // ready, playing, win, lose
var invaders=[];
var cols=8;
var rows=6;
var invW=26;
var invH=16;
var invGapX=10;
var invGapY=10;
var invGroup={
offsetX: 60,
offsetY: 60,
dir: 1,
speed: 22, // base px/sec
stepDown: 18,
aliveCount: 0,
moveAccum: 0
};
function resetInvaders(){
invaders=[];
invGroup.dir=1;
invGroup.speed=26;
invGroup.moveAccum=0;
invGroup.aliveCount=0;
var startX=invGroup.offsetX;
var startY=invGroup.offsetY;
for(var r=0;r<rows;r++){
for(var c=0;c<cols;c++){
var x=startX + c*(invW+invGapX) + (r%2===0?0:4);
var y=startY + r*(invH+invGapY);
invaders.push({
x:x,
y:y,
w:invW,
h:invH,
alive:true,
type:r
});
invGroup.aliveCount++;
}
}
}
var enemyShots=[];
var maxEnemyShots=3;
var enemyBulletSpeed=190;
var enemyShootTimer=0;
var enemyShootInterval=0.75; // seconds, will scale
var enemyShootRandom=0.35;
var lastTime=0;
function rectsOverlap(ax,ay,aw,ah,bx,by,bw,bh){
return ax < bx+bw && ax+aw > bx && ay < by+bh && ay+ah > by;
}
function clamp(v,min,max){
if(v<min) return min;
if(v>max) return max;
return v;
}
function sfxInit(){
var AudioContext=window.AudioContext||window.webkitAudioContext;
if(!AudioContext){
return null;
}
var ac=new AudioContext();
function beep(freq,duration,type,vol){
type=type||'square';
vol=(vol===undefined)?0.08:vol;
var t0=ac.currentTime;
var o=ac.createOscillator();
var g=ac.createGain();
o.type=type;
o.frequency.value=freq;
g.gain.setValueAtTime(0.0001,t0);
g.gain.exponentialRampToValueAtTime(vol,t0+0.01);
g.gain.exponentialRampToValueAtTime(0.0001,t0+duration);
o.connect(g);
g.connect(ac.destination);
o.start(t0);
o.stop(t0+duration+0.02);
}
return {
shoot:function(){
if(!ac) return;
beep(740,0.07,'square',0.10);
setTimeout(function(){beep(990,0.05,'square',0.07);},35);
},
hit:function(){
if(!ac) return;
beep(180,0.10,'square',0.12);
setTimeout(function(){beep(120,0.12,'square',0.10);},60);
},
enemyShoot:function(){
if(!ac) return;
beep(330,0.08,'square',0.08);
},
playerHit:function(){
if(!ac) return;
beep(90,0.18,'square',0.14);
setTimeout(function(){beep(60,0.20,'square',0.12);},80);
},
move:function(){
if(!ac) return;
beep(520,0.03,'square',0.03);
},
win:function(){
if(!ac) return;
beep(660,0.10,'square',0.10);
setTimeout(function(){beep(880,0.10,'square',0.09);},110);
setTimeout(function(){beep(1100,0.12,'square',0.08);},230);
},
lose:function(){
if(!ac) return;
beep(160,0.16,'square',0.12);
setTimeout(function(){beep(110,0.20,'square',0.10);},90);
setTimeout(function(){beep(70,0.22,'square',0.09);},220);
}
};
}
var sfx=sfxInit();
function spawnPlayerBullet(){
if(playerBullet) return;
var now=Date.now();
if(now-lastFire<player.cooldown) return;
playerBullet={
x:player.x+player.w/2-2,
y:player.y-10,
w:4,
h:10,
alive:true
};
lastFire=now;
if(sfx && sfx.shoot) sfx.shoot();
}
function spawnEnemyShot(){
if(enemyShots.length>=maxEnemyShots) return;
// choose lowest alive invader in a random column
var colsAlive=[];
for(var col=0; col<cols; col++){
for(var i=0;i<invaders.length;i++){
var inv=invaders[i];
if(!inv.alive) continue;
// approximate column by x
var relativeX=inv.x - invGroup.offsetX;
if(relativeX < -1) continue;
var colIndex=Math.round(relativeX/(invW+invGapX));
if(colIndex===col){
colsAlive.push(inv);
break;
}
}
}
// pick random column with any alive
var candidates=[];
for(var ci=0;ci<cols;ci++){
var best=null;
for(var j=0;j<invaders.length;j++){
var v=invaders[j];
if(!v.alive) continue;
var relativeX=v.x - invGroup.offsetX;
if(relativeX < -1) continue;
var colIndex2=Math.round(relativeX/(invW+invGapX));
if(colIndex2===ci){
if(!best || v.y>best.y) best=v; // lowest
}
}
if(best) candidates.push(best);
}
if(candidates.length===0) return;
var shooter=candidates[Math.floor(Math.random()*candidates.length)];
enemyShots.push({
x:shooter.x+shooter.w/2-2,
y:shooter.y+shooter.h,
w:4,
h:10,
alive:true
});
if(sfx && sfx.enemyShoot) sfx.enemyShoot();
}
function startGame(){
lives=3;
score=0;
player.x=W/2-player.w/2;
playerBullet=null;
enemyShots=[];
resetInvaders();
gameState="playing";
lastTime=performance.now();
enemyShootTimer=0;
}
function drawCRT(){
ctx.fillStyle="#000";
ctx.fillRect(0,0,W,H);
// scanlines
ctx.save();
ctx.globalAlpha=0.12;
ctx.fillStyle="#0a0";
for(var y=0;y<H;y+=3){
ctx.fillRect(0,y,W,1);
}
ctx.restore();
// vignette-ish border
ctx.strokeStyle="#063";
ctx.lineWidth=2;
ctx.strokeRect(1,1,W-2,H-2);
}
function drawText(text,x,y,size,color,align){
ctx.fillStyle=color;
ctx.font=size+"px monospace";
ctx.textAlign=align||"left";
ctx.fillText(text,x,y);
}
function drawPlayer(){
ctx.fillStyle="#0f0";
// ship body
ctx.fillRect(player.x,player.y,player.w,player.h);
// wings
ctx.fillRect(player.x-6,player.y+4,6,10);
ctx.fillRect(player.x+player.w,player.y+4,6,10);
// details
ctx.fillStyle="#070";
ctx.fillRect(player.x+6,player.y+3,8,3);
ctx.fillRect(player.x+14,player.y+3,8,3);
// cockpit
ctx.fillStyle="#030";
ctx.fillRect(player.x+10,player.y+7,10,6);
ctx.fillStyle="#0f0";
ctx.fillRect(player.x+13,player.y+9,4,2);
}
function drawInvaders(){
for(var i=0;i<invaders.length;i++){
var inv=invaders[i];
if(!inv.alive) continue;
var base="#0f0";
var alt="#0a0";
var dark="#050";
// pixel-ish sprite
ctx.fillStyle=base;
ctx.fillRect(inv.x,inv.y,inv.w,inv.h);
// cutouts
ctx.fillStyle="#000";
ctx.fillRect(inv.x+3,inv.y+3,inv.w-6,inv.h-6);
// eyes
ctx.fillStyle="#0f0";
ctx.fillRect(inv.x+6,inv.y+6,3,3);
ctx.fillRect(inv.x+inv.w-9,inv.y+6,3,3);
// feet
ctx.fillStyle=alt;
ctx.fillRect(inv.x+5,inv.y+inv.h-3,6,2);
ctx.fillRect(inv.x+inv.w-11,inv.y+inv.h-3,6,2);
// outline-ish
ctx.fillStyle=dark;
ctx.fillRect(inv.x,inv.y,inv.w,1);
ctx.fillRect(inv.x,inv.y+inv.h-1,inv.w,1);
ctx.fillRect(inv.x,inv.y,1,inv.h);
ctx.fillRect(inv.x+inv.w-1,inv.y,1,inv.h);
}
}
function drawBullets(){
if(playerBullet && playerBullet.alive){
ctx.fillStyle="#0f0";
ctx.fillRect(playerBullet.x,playerBullet.y,playerBullet.w,playerBullet.h);
// glow core
ctx.fillStyle="#070";
ctx.fillRect(playerBullet.x+1,playerBullet.y+2,playerBullet.w-2,playerBullet.h-4);
}
for(var i=0;i<enemyShots.length;i++){
var b=enemyShots[i];
if(!b.alive) continue;
ctx.fillStyle="#0f0";
ctx.fillRect(b.x,b.y,b.w,b.h);
ctx.fillStyle="#070";
ctx.fillRect(b.x+1,b.y+2,b.w-2,b.h-4);
}
}
function drawHUD(){
drawText("SCORE "+score,10,18,14,"#0f0","left");
drawText("LIVES "+lives,260,18,14,"#0f0","left");
drawText("SPACE INVADERS",110,32,12,"#080","left");
}
function drawGround(){
ctx.fillStyle="#040";
ctx.fillRect(0,H-20,W,2);
ctx.strokeStyle="#060";
ctx.beginPath();
ctx.moveTo(0,H-20);
ctx.lineTo(W,H-20);
ctx.stroke();
}
function updateInvaders(dt){
// Increase difficulty as fewer remain
var alive=invGroup.aliveCount;
if(alive<=0) return;
var aliveFactor = (rows*cols - alive)/(rows*cols);
var speed = invGroup.speed * (1 + aliveFactor*0.7);
// move horizontally, bounce at edges
invGroup.moveAccum += speed*dt*invGroup.dir;
// detect edge based on current positions
var minX=99999, maxX=-99999;
for(var i=0;i<invaders.length;i++){
var inv=invaders[i];
if(!inv.alive) continue;
if(inv.x<minX) minX=inv.x;
if(inv.x+inv.w>maxX) maxX=inv.x+inv.w;
}
if(minX===99999) return;
var leftBound=10, rightBound=W-10;
// apply movement in small steps to maintain direction change
// (use accumulated)
var step = invGroup.moveAccum;
if(step!==0){
// If next move would pass bounds, step down and reverse
var predictedMinX=minX + step;
var predictedMaxX=maxX + step;
if(predictedMinX < leftBound || predictedMaxX > rightBound){
// step down
invGroup.dir*=-1;
invGroup.moveAccum=0;
for(var k=0;k<invaders.length;k++){
var v=invaders[k];
if(!v.alive) continue;
v.y += invGroup.stepDown;
}
// after stepping, check lose condition
for(var m=0;m<invaders.length;m++){
var u=invaders[m];
if(!u.alive) continue;
if(u.y+u.h >= player.y){
gameState="lose";
if(sfx && sfx.lose) sfx.lose();
}
}
}else{
// move normally
for(var j=0;j<invaders.length;j++){
var vv=invaders[j];
if(!vv.alive) continue;
vv.x += step;
}
invGroup.moveAccum=0;
}
}
}
function updateBullets(dt){
if(playerBullet && playerBullet.alive){
playerBullet.y -= bulletSpeed*dt;
if(playerBullet.y+playerBullet.h < 0){
playerBullet.alive=false;
playerBullet=null;
}else{
// collision with invaders
for(var i=0;i<invaders.length;i++){
var inv=invaders[i];
if(!inv.alive) continue;
if(rectsOverlap(playerBullet.x,playerBullet.y,playerBullet.w,playerBullet.h,inv.x,inv.y,inv.w,inv.h)){
inv.alive=false;
playerBullet.alive=false;
playerBullet=null;
invGroup.aliveCount--;
score += 10;
if(sfx && sfx.hit) sfx.hit();
if(invGroup.aliveCount<=0){
gameState="win";
if(sfx && sfx.win) sfx.win();
}
break;
}
}
}
}
for(var s=0;s<enemyShots.length;s++){
var b=enemyShots[s];
if(!b.alive) continue;
b.y += enemyBulletSpeed*dt;
if(b.y>H+20){
b.alive=false;
continue;
}
// collision with player
if(rectsOverlap(b.x,b.y,b.w,b.h,player.x,player.y,player.w,player.h)){
b.alive=false;
lives--;
if(sfx && sfx.playerHit) sfx.playerHit();
if(lives<=0){
gameState="lose";
if(sfx && sfx.lose) sfx.lose();
}else{
// reset player bullet and clear some enemy shots for fairness
playerBullet=null;
enemyShots=[];
// small reposition
player.x = clamp(player.x, 10, W-player.w-10);
}
}
}
// compact enemyShots
var newShots=[];
for(var n=0;n<enemyShots.length;n++){
if(enemyShots[n].alive) newShots.push(enemyShots[n]);
}
enemyShots=newShots;
}
function updateEnemyShooting(dt){
if(gameState!=="playing") return;
if(invGroup.aliveCount<=0) return;
enemyShootTimer += dt;
// rate increases as more enemies die
var aliveRatio = invGroup.aliveCount/(rows*cols);
var interval = enemyShootInterval * (0.85 + (1-aliveRatio)*0.9);
interval = Math.max(0.2, interval);
while(enemyShootTimer >= interval){
enemyShootTimer -= interval;
// random chance additional burst
if(Math.random() < 0.65){
spawnEnemyShot();
}else if(Math.random() < 0.25){
// sometimes shoot twice quickly
if(Math.random() < 0.9) spawnEnemyShot();
}
break;
}
}
function update(dt){
if(gameState==="ready"){
// keep some subtle idle movement for look
return;
}
if(gameState!=="playing") return;
var moveDir=0;
if(keys[37] || keys[65]) moveDir=-1;
if(keys[39] || keys[68]) moveDir=1;
if(moveDir!==0){
var oldX=player.x;
player.x += moveDir*player.speed*dt;
player.x = clamp(player.x, 10, W-player.w-10);
if(Math.abs(player.x-oldX)>0.1 && sfx && sfx.move){
// avoid too many beeps: gate
if(Date.now()%220<20){
sfx.move();
}
}
}
if(spaceHeld){
spawnPlayerBullet();
}
updateInvaders(dt);
updateEnemyShooting(dt);
updateBullets(dt);
}
function drawTitleOverlay(){
drawText("RETRO SPACE INVADERS",20,150,18,"#0f0","left");
drawText("Flèches: bouger",40,185,12,"#0f0","left");
drawText("Espace: tirer",40,205,12,"#0f0","left");
drawText("Appuyez sur ENTER pour jouer",40,235,12,"#0f0","left");
drawText("détruisez toute la flotte",40,260,12,"#080","left");
}
function drawWinLoseOverlay(){
var msg = (gameState==="win") ? "VICTOIRE" : "GAME OVER";
var sub = (gameState==="win") ? "BIEN JOUÉ ! Appuyez sur ENTER" : "Appuyez sur ENTER";
ctx.fillStyle="#0f0";
ctx.font="28px monospace";
ctx.textAlign="center";
ctx.fillText(msg, W/2, 190);
ctx.font="14px monospace";
ctx.fillStyle="#080";
ctx.fillText(sub, W/2, 220);
}
function render(){
drawCRT();
drawHUD();
drawGround();
drawInvaders();
drawBullets();
drawPlayer();
if(gameState==="ready"){
// show invaders silhouette area
drawTitleOverlay();
}else if(gameState==="win" || gameState==="lose"){
drawWinLoseOverlay();
}
}
function loop(t){
if(!lastTime) lastTime=t;
var dt=(t-lastTime)/1000;
if(dt>0.033) dt=0.033;
lastTime=t;
update(dt);
render();
requestAnimationFrame(loop);
}
window.addEventListener('keydown',function(e){
var code=e.keyCode || e.which;
keys[code]=true;
if(code===32){
spaceHeld=true;
e.preventDefault();
}
if(code===13){ // Enter
if(gameState!=="playing"){
startGame();
}
}
});
window.addEventListener('keyup',function(e){
var code=e.keyCode || e.which;
keys[code]=false;
if(code===32){
spaceHeld=false;
}
});
function init(){
resetInvaders();
// center-ish
player.x=W/2-player.w/2;
gameState="ready";
requestAnimationFrame(loop);
}
init();
})();
</script>
openaigpt-5.5
7.0/1038.3 s
openaigpt-5.5
Tokens2 263
Source code6.9 KB
Time38.3 s
Matania Judgment
Gameplay
10
UX
9
Code quality
9
Completeness
4
Fidelity
6
Overall
7
Review
The code is of very high technical quality (Web Audio API management, smooth game loop, block-drawn sprites), but the model generated truncated code (the file cuts off abruptly at the end of the render function). Consequently, the game is incomplete and cannot be executed as is, which drastically lowers the completeness and fidelity scores despite the excellence of the written portion.
<canvas id="c" width="400" height="400"></canvas><script>
var c=document.getElementById("c"),x=c.getContext("2d"),W=400,H=400;
document.body.style.margin="0";
document.body.style.background="#000";
c.style.display="block";
c.style.margin="0 auto";
c.style.background="#000";
c.style.imageRendering="pixelated";
var keys={},score=0,lives=3,state="play",last=0,enemyTimer=0,flash=0,wonSound=0,overSound=0;
var ac=null;
function audio(){
if(!ac){try{ac=new (window.AudioContext||window.webkitAudioContext)();}catch(e){}}
if(ac&&ac.state==="suspended"){ac.resume();}
}
function beep(f,d,t){
if(!ac){return;}
var o=ac.createOscillator(),g=ac.createGain();
o.type=t||"square";
o.frequency.value=f;
g.gain.value=0.06;
o.connect(g);
g.connect(ac.destination);
o.start();
g.gain.exponentialRampToValueAtTime(0.001,ac.currentTime+d);
o.stop(ac.currentTime+d);
}
function noise(d){
if(!ac){return;}
var n=ac.createBufferSource(),b=ac.createBuffer(1,ac.sampleRate*d,ac.sampleRate),a=b.getChannelData(0),i;
for(i=0;i<a.length;i++){a[i]=(Math.random()*2-1)*(1-i/a.length);}
var g=ac.createGain();
g.gain.value=0.08;
n.buffer=b;
n.connect(g);
g.connect(ac.destination);
n.start();
}
var player={x:187,y:368,w:26,h:14,spd:180,cool:0,inv:0};
var bullet=null,ebullets=[];
var inv=[],rows=5,cols=8,iw=22,ih=16,spx=40,spy=26,idir=1,ispeed=24,drop=14;
function makeInv(){
inv=[];
var r,col;
for(r=0;r<rows;r++){
for(col=0;col<cols;col++){
inv.push({x:38+col*spx,y:46+r*spy,w:iw,h:ih,alive:true,row:r,col:col});
}
}
}
function reset(){
score=0;lives=3;state="play";last=0;enemyTimer=0;flash=0;wonSound=0;overSound=0;
player.x=187;player.y=368;player.cool=0;player.inv=0;
bullet=null;ebullets=[];idir=1;ispeed=24;
makeInv();
}
makeInv();
function rect(a,b){return a.x<b.x+b.w&&a.x+a.w>b.x&&a.y<b.y+b.h&&a.y+a.h>b.y;}
function aliveCount(){
var n=0,i;
for(i=0;i<inv.length;i++){if(inv[i].alive){n++;}}
return n;
}
function bottomInvaders(){
var list=[],col,r,i,best;
for(col=0;col<cols;col++){
best=null;
for(i=0;i<inv.length;i++){
if(inv[i].alive&&inv[i].col===col&&(!best||inv[i].row>best.row)){best=inv[i];}
}
if(best){list.push(best);}
}
return list;
}
function enemyShoot(){
var list=bottomInvaders();
if(list.length<1){return;}
var e=list[(Math.random()*list.length)|0];
ebullets.push({x:e.x+e.w/2-2,y:e.y+e.h,w:4,h:8,vy:105});
beep(120,0.05,"square");
}
function fire(){
if(state!=="play"){return;}
if(!bullet&&player.cool<=0){
bullet={x:player.x+player.w/2-2,y:player.y-8,w:4,h:9,vy:-250};
player.cool=0.22;
beep(650,0.06,"square");
}
}
function key(e){
audio();
keys[e.keyCode]=e.type==="keydown";
if((e.keyCode===32||e.keyCode===37||e.keyCode===39||e.keyCode===13)&&e.preventDefault){e.preventDefault();}
if(e.type==="keydown"&&e.keyCode===32){fire();}
if(e.type==="keydown"&&e.keyCode===13&&state!=="play"){reset();}
}
window.addEventListener("keydown",key,false);
window.addEventListener("keyup",key,false);
function drawText(s,px,py,sz,al){
x.fillStyle="#39ff14";
x.font=sz+"px monospace";
x.textAlign=al||"left";
x.textBaseline="top";
x.fillText(s,px,py);
}
function block(px,py,w,h,col){x.fillStyle=col;x.fillRect((px|0),(py|0),w,h);}
function drawPlayer(){
var px=player.x|0,py=player.y|0;
if(player.inv>0&&((player.inv*16)|0)%2===0){return;}
block(px+10,py,6,4,"#b6ffb6");
block(px+8,py+4,10,4,"#39ff14");
block(px+4,py+8,18,4,"#39ff14");
block(px,py+12,26,3,"#39ff14");
block(px+3,py+15,5,2,"#0a8f08");
block(px+18,py+15,5,2,"#0a8f08");
}
function drawInvader(e){
var px=e.x|0,py=e.y|0,col=e.row%2===0?"#39ff14":"#20c020";
block(px+4,py,4,3,col);block(px+14,py,4,3,col);
block(px+2,py+3,18,3,col);
block(px,py+6,22,4,col);
block(px,py+10,4,3,col);block(px+6,py+10,10,3,col);block(px+18,py+10,4,3,col);
block(px+4,py+13,4,3,col);block(px+14,py+13,4,3,col);
if(((Date.now()/350)|0)%2===0){block(px,py+16,5,2,col);block(px+17,py+16,5,2,col);}else{block(px+5,py+16,5,2,col);block(px+12,py+16,5,2,col);}
}
function drawBullet(b,col){
block(b.x,b.y,b.w,b.h,col);
}
function drawBaseLine(){
x.strokeStyle="#063";
x.lineWidth=2;
x.beginPath();
x.moveTo(0,348);
x.lineTo(400,348);
x.stroke();
}
function update(dt){
var i,e,edge=false,alive,spd;
if(state!=="play"){return;}
if(keys[37]){player.x-=player.spd*dt;}
if(keys[39]){player.x+=player.spd*dt;}
if(player.x<4){player.x=4;}
if(player.x+player.w>396){player.x=396-player.w;}
if(player.cool>0){player.cool-=dt;}
if(player.inv>0){player.inv-=dt;}
if(bullet){
bullet.y+=bullet.vy*dt;
if(bullet.y<-12){bullet=null;}
}
alive=aliveCount();
spd=ispeed+(40-alive)*0.9;
for(i=0;i<inv.length;i++){
e=inv[i];
if(e.alive){
e.x+=idir*spd*dt;
if(e.x<8||e.x+e.w>392){edge=true;}
}
}
if(edge){
idir*=-1;
for(i=0;i<inv.length;i++){
if(inv[i].alive){
inv[i].y+=drop;
inv[i].x+=idir*spd*dt*2;
}
}
beep(80,0.04,"square");
}
if(bullet){
for(i=0;i<inv.length;i++){
e=inv[i];
if(e.alive&&rect(bullet,e)){
e.alive=false;
bullet=null;
score+=10;
flash=0.08;
noise(0.08);
break;
}
}
}
enemyTimer-=dt;
if(enemyTimer<=0&&alive>0){
enemyShoot();
enemyTimer=0.55+Math.random()*0.9;
}
for(i=ebullets.length-1;i>=0;i--){
ebullets[i].y+=ebullets[i].vy*dt;
if(ebullets[i].y>410){ebullets.splice(i,1);continue;}
if(player.inv<=0&&rect(ebullets[i],player)){
ebullets.splice(i,1);
lives--;
player.inv=1.3;
noise(0.14);
if(lives<=0){state="over";overSound=0;}
}
}
for(i=0;i<inv.length;i++){
e=inv[i];
if(e.alive&&(e.y+e.h>=player.y||e.y+e.h>=350)){state="over";overSound=0;break;}
}
if(aliveCount()===0){state="win";wonSound=0;}
if(flash>0){flash-=dt;}
}
function render(){
var i;
x.fillStyle="#000";
x.fillRect(0,0,W,H);
for(i=0;i<80;i++){
if((i*37+(Date.now()/80|0))%29===0){block((i*53)%400,(i*97)%400,2,2,"#021");}
}
drawText("SCORE "+score,8,8,16,"left");
drawText("VIES "+lives,392,8,16,"right");
drawBaseLine();
for(i=0;i<inv.length;i++){if(inv[i].alive){drawInvader(inv[i]);}}
if(bullet){drawBullet(bullet,"#b6ffb6");}
for(i=0;i<ebullets.length;i++){drawBullet(ebullets[i],"#ff4");}
drawPlayer();
if(flash>0){
x.fillStyle="rgba(57,255,20,0.12)";
x.fillRect(0,0,W,H);
}
if(state==="win"){
if(wonSound===0){beep(400,0.12,"square");setTimeout(function(){beep(600,0.12,"square");},130);setTimeout(function(){beep(800,0.18,"square");},260);wonSound=1;}
x.fillStyle="rgba(0,0,0,0.75)";
x.fillRect(0,0,W,H);
drawText("VICTOIRE",200,160,30,"center");
drawText("SCORE "+score,200,198,18,"center");
drawText("ENTREE POUR REJOUER",200,230,14,"center");
}
if(state==="over"){
if(overSound===0){noise(0.25);beep(70,0.35,"sawtooth");overSound=1;}
x.fillStyle="rgba(0,0,0,0.78)";
x.fillRect(0,0,W,H);
drawText("GAME OVER",200,160,30,"center");
drawText("SCORE "+score,200,198,18,"center");
drawText("ENTREE POUR REJOUER",200,230,14,"center");
}
}
function loop(t){
var dt=(t-last)/1000;
if(!last){dt=0;}
if(dt>0.05){dt=0.05;}
last=t;
update(dt);
render();
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
</script>
openaigpt-5.5-pro
9.8/10138.7 s
openaigpt-5.5-pro
Tokens1 947
Source code5.7 KB
Time138.7 s
Matania Judgment
Gameplay
10
UX
9
Code quality
9
Completeness
10
Fidelity
10
Overall
9.75
Review
The model perfectly adhered to all technical and aesthetic constraints. The code is concise, efficient (using dt for smoothness), and implements all requested mechanics (enemy fire, health, bunkers, sounds via the Web Audio API). The visual rendering is very faithful to the retro arcade spirit, featuring a consistent color palette.
<canvas id="c" width="400" height="400"></canvas><script>
var c=document.getElementById("c"),x=c.getContext("2d"),W=400,H=400;
document.body.style.margin="0";document.body.style.background="#000";c.style.display="block";c.style.margin="0 auto";c.style.imageRendering="pixelated";
var keys={},score=0,lives=3,state="play",tick=0,last=0,shootCd=0,enemyShootCd=70,flash=0;
var player={x:188,y:360,w:24,h:14,spd:170,inv:0};
var bullet=null,ebullets=[],aliens=[],dir=1,enemySpeed=20,drop=12;
var ac=null;
function audio(){try{if(!ac){ac=new (window.AudioContext||window.webkitAudioContext)();}if(ac.state==="suspended"){ac.resume();}}catch(e){}}
function snd(f,d,t){if(!ac){return;}try{var o=ac.createOscillator(),g=ac.createGain();o.type=t||"square";o.frequency.value=f;g.gain.value=0.05;o.connect(g);g.connect(ac.destination);o.start();g.gain.exponentialRampToValueAtTime(0.001,ac.currentTime+d);o.stop(ac.currentTime+d);}catch(e){}}
function resetAliens(){aliens=[];var r,cl;for(r=0;r<5;r++){for(cl=0;cl<8;cl++){aliens.push({x:38+cl*40,y:48+r*26,w:22,h:16,alive:true,row:r});}}dir=1;enemySpeed=20;enemyShootCd=60;}
function resetGame(){score=0;lives=3;state="play";player.x=188;player.y=360;player.inv=0;bullet=null;ebullets=[];shootCd=0;flash=0;resetAliens();}
resetGame();
document.onkeydown=function(e){keys[e.keyCode]=true;audio();if(e.keyCode===32){e.preventDefault();}if((state==="over"||state==="win")&&e.keyCode===82){resetGame();}};
document.onkeyup=function(e){keys[e.keyCode]=false;if(e.keyCode===32){e.preventDefault();}};
function hit(a,b){return a.x<b.x+b.w&&a.x+a.w>b.x&&a.y<b.y+b.h&&a.y+a.h>b.y;}
function liveAliens(){var a=[],i;for(i=0;i<aliens.length;i++){if(aliens[i].alive){a.push(aliens[i]);}}return a;}
function fireEnemy(){var alive=liveAliens(),cols={},bottom=[],i,a,k;if(alive.length===0){return;}for(i=0;i<alive.length;i++){a=alive[i];k=Math.round(a.x/40);if(!cols[k]||a.y>cols[k].y){cols[k]=a;}}for(k in cols){if(cols.hasOwnProperty(k)){bottom.push(cols[k]);}}a=bottom[Math.floor(Math.random()*bottom.length)];ebullets.push({x:a.x+a.w/2-2,y:a.y+a.h,w:4,h:9,vy:105});snd(180,0.08,"square");}
function drawText(t,px,py,s,align){x.fillStyle="#25ff5a";x.font=s+"px monospace";x.textAlign=align||"left";x.textBaseline="top";x.fillText(t,px,py);}
function drawPlayer(){if(player.inv>0&&Math.floor(player.inv*12)%2===0){return;}x.fillStyle="#18ff55";x.fillRect(player.x+10,player.y,4,4);x.fillRect(player.x+6,player.y+4,12,4);x.fillRect(player.x+2,player.y+8,20,4);x.fillRect(player.x,player.y+12,24,2);x.fillStyle="#7dff96";x.fillRect(player.x+10,player.y+6,4,4);}
function drawAlien(a){var y=a.y,px=a.x;x.fillStyle=a.row%2===0?"#20ff50":"#00d83e";x.fillRect(px+4,y,14,4);x.fillRect(px+2,y+4,18,4);x.fillRect(px,y+8,22,4);x.fillRect(px+4,y+12,4,4);x.fillRect(px+14,y+12,4,4);x.fillStyle="#000";x.fillRect(px+5,y+5,3,3);x.fillRect(px+14,y+5,3,3);}
function drawBunker(bx,by){x.fillStyle="#0fbf36";x.fillRect(bx,by,42,8);x.fillRect(bx+4,by+8,34,8);x.fillRect(bx+8,by+16,8,8);x.fillRect(bx+26,by+16,8,8);}
function update(dt){var i,a,alive,edge=false,minx=999,maxx=-999,speed,oldx;if(state!=="play"){return;}tick+=dt;if(flash>0){flash-=dt;}if(player.inv>0){player.inv-=dt;}if(shootCd>0){shootCd-=dt;}if(keys[37]){player.x-=player.spd*dt;}if(keys[39]){player.x+=player.spd*dt;}if(player.x<4){player.x=4;}if(player.x+player.w>W-4){player.x=W-4-player.w;}if(keys[32]&&!bullet&&shootCd<=0){bullet={x:player.x+player.w/2-2,y:player.y-8,w:4,h:9,vy:-260};shootCd=0.18;snd(720,0.05,"square");}
if(bullet){bullet.y+=bullet.vy*dt;if(bullet.y+bullet.h<0){bullet=null;}}
alive=liveAliens();if(alive.length===0){state="win";snd(880,0.15,"square");snd(1320,0.18,"square");return;}
speed=enemySpeed+score*0.08;for(i=0;i<aliens.length;i++){a=aliens[i];if(a.alive){oldx=a.x;a.x+=dir*speed*dt;if(a.x<minx){minx=a.x;}if(a.x+a.w>maxx){maxx=a.x+a.w;}if(a.x<=8||a.x+a.w>=W-8){edge=true;}}}
if(edge){dir*=-1;for(i=0;i<aliens.length;i++){if(aliens[i].alive){aliens[i].y+=drop;aliens[i].x+=dir*3;if(aliens[i].y+aliens[i].h>=player.y){state="over";snd(90,0.6,"sawtooth");}}}enemySpeed+=2;snd(120,0.06,"square");}
if(bullet){for(i=0;i<aliens.length;i++){a=aliens[i];if(a.alive&&hit(bullet,a)){a.alive=false;bullet=null;score+=10;flash=0.04;snd(420,0.08,"square");break;}}}
enemyShootCd-=dt*60;if(enemyShootCd<=0){fireEnemy();enemyShootCd=45+Math.random()*70-score*0.02;if(enemyShootCd<18){enemyShootCd=18;}}
for(i=ebullets.length-1;i>=0;i--){ebullets[i].y+=ebullets[i].vy*dt;if(ebullets[i].y>H){ebullets.splice(i,1);}else if(player.inv<=0&&hit(ebullets[i],player)){ebullets.splice(i,1);lives--;player.inv=1.4;flash=0.12;snd(80,0.35,"sawtooth");if(lives<=0){state="over";}}}
}
function draw(){var i,a;x.fillStyle="#000";x.fillRect(0,0,W,H);if(flash>0){x.fillStyle="#062";x.fillRect(0,0,W,H);}x.strokeStyle="#0b4";x.lineWidth=2;x.strokeRect(1,1,W-2,H-2);drawText("SCORE "+score,10,8,16,"left");drawText("VIES "+lives,W-10,8,16,"right");x.fillStyle="#0b4";for(i=0;i<32;i++){x.fillRect(i*13,34,7,2);}for(i=0;i<aliens.length;i++){if(aliens[i].alive){drawAlien(aliens[i]);}}drawBunker(38,300);drawBunker(138,300);drawBunker(238,300);drawBunker(338,300);if(bullet){x.fillStyle="#9cff9c";x.fillRect(bullet.x,bullet.y,bullet.w,bullet.h);}x.fillStyle="#18ff55";for(i=0;i<ebullets.length;i++){x.fillRect(ebullets[i].x,ebullets[i].y,ebullets[i].w,ebullets[i].h);}drawPlayer();if(state==="over"||state==="win"){x.fillStyle="rgba(0,0,0,0.72)";x.fillRect(0,0,W,H);drawText(state==="win"?"VICTOIRE":"GAME OVER",W/2,155,28,"center");drawText("SCORE "+score,W/2,192,18,"center");drawText("R POUR REJOUER",W/2,224,16,"center");}else{drawText("← → DEPLACER ESPACE TIRER",W/2,382,12,"center");}}
function loop(t){var dt;if(!last){last=t;}dt=(t-last)/1000;if(dt>0.05){dt=0.05;}last=t;update(dt);draw();requestAnimationFrame(loop);}
requestAnimationFrame(loop);
</script>
productiviamatania-latest
6.0/1014.9 s
productiviamatania-latest
Tokens2 165
Source code6.6 KB
Time14.9 s
Matania Judgment
Gameplay
10
UX
8
Code quality
9
Completeness
5
Fidelity
4
Overall
6
Review
The model failed on the most critical output constraint: the code is truncated at the end (the script cuts off abruptly in the middle of the draw function), making the game non-functional (very low completeness). However, the logic implemented up to the cutoff perfectly adheres to the requested gameplay, style, and structural instructions.
<canvas id="c" width="400" height="400" style="background:#000;display:block;margin:0 auto;"></canvas>
<script>
var canvas = document.getElementById("c");
var ctx = canvas.getContext("2d");
var score = 0;
var lives = 3;
var gameOver = false;
var win = false;
var player = { x: 185, y: 370, w: 30, h: 20, speed: 4 };
var bullets = [];
var enemyBullets = [];
var enemies = [];
var enemyDirection = 1;
var enemySpeed = 1;
var enemyStepDown = 15;
var keys = {};
// Audio Context for retro sounds
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();
function playSound(freq, type, duration) {
var osc = audioCtx.createOscillator();
var gain = audioCtx.createGain();
osc.type = type;
osc.frequency.setValueAtTime(freq, audioCtx.currentTime);
gain.gain.setValueAtTime(0.1, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + duration);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + duration);
}
function initEnemies() {
enemies = [];
for (var r = 0; r < 5; r++) {
for (var c = 0; c < 8; c++) {
enemies.push({ x: 40 + c * 40, y: 50 + r * 30, w: 25, h: 20, alive: true });
}
}
}
function init() {
score = 0;
lives = 3;
gameOver = false;
win = false;
bullets = [];
enemyBullets = [];
enemySpeed = 1;
initEnemies();
}
window.addEventListener("keydown", function(e) {
keys[e.code] = true;
if (e.code === "Space" && !gameOver && !win) {
if (bullets.length < 2) {
bullets.push({ x: player.x + player.w / 2 - 2, y: player.y, w: 4, h: 10 });
playSound(440, "square", 0.1);
}
}
if (gameOver || win) {
if (e.code === "Enter") init();
}
});
window.addEventListener("keyup", function(e) { keys[e.code] = false; });
function update() {
if (gameOver || win) return;
// Player movement
if (keys["ArrowLeft"] && player.x > 0) player.x -= player.speed;
if (keys["ArrowRight"] && player.x < canvas.width - player.w) player.x += player.speed;
// Player bullets
for (var i = bullets.length - 1; i >= 0; i--) {
bullets[i].y -= 7;
if (bullets[i].y < 0) bullets.splice(i, 1);
}
// Enemy bullets
for (var i = enemyBullets.length - 1; i >= 0; i--) {
enemyBullets[i].y += 4;
if (enemyBullets[i].y > canvas.height) {
enemyBullets.splice(i, 1);
} else {
// Collision with player
if (enemyBullets[i].x < player.x + player.w &&
enemyBullets[i].x + enemyBullets[i].w > player.x &&
enemyBullets[i].y < player.y + player.h &&
enemyBullets[i].y + enemyBullets[i].h > player.y) {
enemyBullets.splice(i, 1);
lives--;
playSound(100, "sawtooth", 0.3);
if (lives <= 0) gameOver = true;
}
}
}
// Enemies movement
var edgeHit = false;
for (var i = 0; i < enemies.length; i++) {
if (!enemies[i].alive) continue;
enemies[i].x += enemyDirection * enemySpeed;
if (enemies[i].x + enemies[i].w > canvas.width || enemies[i].x < 0) {
edgeHit = true;
}
// Check if enemy reached bottom
if (enemies[i].y + enemies[i].h > player.y) {
gameOver = true;
}
}
if (edgeHit) {
enemyDirection *= -1;
for (var j = 0; j < enemies.length; j++) {
enemies[j].y += enemyStepDown;
}
enemySpeed += 0.1;
}
// Enemy shooting
if (Math.random() < 0.01) {
var activeEnemies = [];
for(var k=0; k<enemies.length; k++) if(enemies[k].alive) activeEnemies.push(enemies[k]);
if (activeEnemies.length > 0) {
var shooter = activeEnemies[Math.floor(Math.random() * activeEnemies.length)];
enemyBullets.push({ x: shooter.x + shooter.w/2, y: shooter.y + shooter.h, w: 4, h: 10 });
}
}
// Collision Detection: Bullet vs Enemy
for (var i = bullets.length - 1; i >= 0; i--) {
for (var j = 0; j < enemies.length; j++) {
if (enemies[j].alive &&
bullets[i] &&
bullets[i].x < enemies[j].x + enemies[j].w &&
bullets[i].x + bullets[i].w > enemies[j].x &&
bullets[i].y < enemies[j].y + enemies[j].h &&
bullets[i].y + bullets[i].h > enemies[j].y) {
enemies[j].alive = false;
bullets.splice(i, 1);
score += 10;
playSound(200, "sine", 0.05);
break;
}
}
}
// Check win
var allDead = true;
for (var k = 0; k < enemies.length; k++) {
if (enemies[k].alive) {
allDead = false;
break;
}
}
if (allDead) win = true;
}
function draw() {
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw Player
ctx.fillStyle = "#0f0";
ctx.fillRect(player.x, player.y, player.w, player.h);
ctx.fillRect(player.x + 12, player.y - 5, 6, 5);
// Draw Enemies
for (var i = 0; i < enemies.length; i++) {
if (enemies[i].alive) {
ctx.fillStyle = "#0f0";
ctx.fillRect(enemies[i].x, enemies[i].y, enemies[i].w, enemies[i].h);
ctx.fillStyle = "#000";
ctx.fillRect(enemies[i].x + 5, enemies[i].y + 5, 4, 4);
ctx.fillRect(enemies[i].x + 16, enemies[i].y + 5, 4, 4);
}
}
// Draw Bullets
ctx.fillStyle = "#fff";
for (var i = 0; i < bullets.length; i++) {
ctx.fillRect(bullets[i].x, bullets[i].y, bullets[i].w, bullets[i].h);
}
ctx.fillStyle = "#f00";
for (var i = 0; i < enemyBullets.length; i++) {
ctx.fillRect(enemyBullets[i].x, enemyBullets[i].y, enemyBullets[i].w, enemyBullets[i].h);
}
// UI
ctx.fillStyle = "#0f0";
ctx.font = "16px monospace";
ctx.fillText("SCORE: " + score, 10, 25);
ctx.fillText("LIVES: " + lives, 300, 25);
if (gameOver) {
ctx.fillStyle = "#f00";
ctx.font = "40px monospace";
ctx.fillText("GAME OVER", 80, 200);
ctx.font = "20px monospace";
ctx.fillText("PRESS ENTER TO RESTART", 85, 240);
}
if (win) {
ctx.fillStyle = "#0f0";
ctx.font = "40px monospace";
ctx.fillText("YOU WIN!", 110, 200);
ctx.font = "20px monospace";
ctx.fillText("PRESS ENTER TO RESTART", 85, 240);
}
}
function loop() {
update();
draw();
requestAnimationFrame(loop);
}
init();
loop();
</script>
xaigrok-4-1-fast-non-reasoning
9.5/1017.1 s
xaigrok-4-1-fast-non-reasoning
Tokens1 603
Source code4.4 KB
Time17.1 s
Matania Judgment
Gameplay
9
UX
8
Code quality
9
Completeness
10
Fidelity
10
Overall
9.5
Review
The model perfectly adhered to all technical constraints: the canvas format, the use of AudioContext for sound, the green/black retro style, and ES5 code. The gameplay is complete, featuring life management, scoring, enemy projectiles, and win/loss conditions. The code structure is clean and efficient for a single-file script.
<canvas id="c" width="400" height="400"></canvas><script>
var canvas=document.getElementById("c");
var ctx=canvas.getContext("2d");
ctx.fillStyle="#000";
ctx.fillRect(0,0,400,400);
ctx.font="16px monospace";
ctx.fillStyle="#0f0";
ctx.textAlign="left";
var player={x:180,y:360,w:40,h:20,dx:0};
var bullets=[];
var enemies=[];
var enemyBullets=[];
var keys={};
var score=0;
var lives=3;
var gameState="playing";
var direction=1;
var enemySpeed=20;
var lastShot=0;
for(var row=0;row<5;row++){
for(var col=0;col<8;col++){
enemies.push({x:50+col*40,y:30+row*30,w:24,h:16,alive:true});
}
}
document.addEventListener("keydown",function(e){
keys[e.keyCode]=true;
});
document.addEventListener("keyup",function(e){
keys[e.keyCode]=false;
});
function playSound(freq,dur){
var audioCtx=new (window.AudioContext||window.webkitAudioContext)();
var osc=audioCtx.createOscillator();
var gain=audioCtx.createGain();
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.frequency.value=freq;
gain.gain.setValueAtTime(0.3,audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01,audioCtx.currentTime+dur);
osc.start(audioCtx.currentTime);
osc.stop(audioCtx.currentTime+dur);
}
function update(){
if(gameState==="gameover"||gameState==="win") return;
// Player movement
if(keys[37]&&player.x>0) player.x-=5;
if(keys[39]&&player.x<340) player.x+=5;
if(keys[32]&&Date.now()-lastShot>200){
bullets.push({x:player.x+18,y:player.y,w:4,h:8});
playSound(800,0.1);
lastShot=Date.now();
}
// Player bullets
for(var i=bullets.length-1;i>=0;i--){
var b=bullets[i];
b.y-=8;
if(b.y<0){bullets.splice(i,1);continue;}
for(var j=enemies.length-1;j>=0;j--){
var e=enemies[j];
if(e.alive&&Math.abs(b.x-e.x)<15&&Math.abs(b.y-e.y)<15){
e.alive=false;
bullets.splice(i,1);
score+=10;
playSound(600,0.1);
break;
}
}
}
// Enemy movement
var edge=false;
for(var i=0;i<enemies.length;i++){
var e=enemies[i];
if(!e.alive) continue;
e.x+=direction*enemySpeed/60;
if(e.x<20||e.x>340) edge=true;
if(e.y>340){gameState="gameover";lives=0;}
}
if(edge){
direction*=-1;
for(var i=0;i<enemies.length;i++){
if(enemies[i].alive) enemies[i].y+=20;
}
}
// Enemy shooting
if(Math.random()<0.002&&enemies.length>0){
var shooter=enemies[Math.floor(Math.random()*enemies.length)];
if(shooter.alive){
enemyBullets.push({x:shooter.x+12,y:shooter.y+16,w:4,h:8});
playSound(400,0.1);
}
}
// Enemy bullets
for(var i=enemyBullets.length-1;i>=0;i--){
var eb=enemyBullets[i];
eb.y+=6;
if(eb.y>400){enemyBullets.splice(i,1);continue;}
if(eb.x>player.x&&eb.x<player.x+40&&eb.y>player.y&&eb.y<player.y+20){
enemyBullets.splice(i,1);
lives--;
playSound(200,0.2);
if(lives<=0) gameState="gameover";
}
}
// Check win
var aliveCount=0;
for(var i=0;i<enemies.length;i++) if(enemies[i].alive) aliveCount++;
if(aliveCount===0) gameState="win";
}
function draw(){
ctx.fillStyle="#000";
ctx.fillRect(0,0,400,400);
// Score and lives
ctx.fillStyle="#0f0";
ctx.fillText("SCORE: "+score,10,20);
ctx.fillText("LIVES: "+lives,300,20);
if(gameState==="gameover"){
ctx.fillStyle="#f00";
ctx.font="32px monospace";
ctx.textAlign="center";
ctx.fillText("GAME OVER",200,200);
ctx.font="16px monospace";
ctx.textAlign="left";
return;
}
if(gameState==="win"){
ctx.fillStyle="#0f0";
ctx.font="32px monospace";
ctx.textAlign="center";
ctx.fillText("YOU WIN!",200,200);
ctx.font="16px monospace";
ctx.textAlign="left";
return;
}
// Player
ctx.fillStyle="#0f0";
ctx.fillRect(player.x,player.y,player.w,player.h);
ctx.fillRect(player.x+8,player.y-8,24,8);
// Player bullets
ctx.fillStyle="#fff";
for(var i=0;i<bullets.length;i++){
var b=bullets[i];
ctx.fillRect(b.x,b.y,b.w,b.h);
}
// Enemies
for(var i=0;i<enemies.length;i++){
var e=enemies[i];
if(!e.alive) continue;
ctx.fillStyle="#0a0";
ctx.fillRect(e.x,e.y,e.w,e.h);
ctx.fillStyle="#0f0";
ctx.fillRect(e.x+4,e.y+4,16,8);
}
// Enemy bullets
ctx.fillStyle="#f80";
for(var i=0;i<enemyBullets.length;i++){
var eb=enemyBullets[i];
ctx.fillRect(eb.x,eb.y,eb.w,eb.h);
}
}
function loop(){
update();
draw();
requestAnimationFrame(loop);
}
loop();
</script>
xaigrok-4-1-fast-reasoning
9.6/1052.3 s
xaigrok-4-1-fast-reasoning
Tokens1 558
Source code4.2 KB
Time52.3 s
Matania Judgment
Gameplay
9
UX
9
Code quality
9
Completeness
10
Fidelity
10
Overall
9.63
Review
The model perfectly adhered to all technical and aesthetic constraints. The code is clean, utilizes the Web Audio API for the requested sound effects, and faithfully implements the game loop, collision detection, and win/loss states. The visual rendering strictly follows the requested retro black-and-green style.
The model perfectly adhered to all technical constraints: ES5 JavaScript, requestAnimationFrame, canvas size, and a dual control system (mouse and arrow keys). The code is clean and well-structured around a centralized 'game' object, and the bounce mechanics—including the management of the angle based on the impact point on the paddle—are of high quality.
<style>
body { margin: 0; padding: 20px; background: #222; display: flex; flex-direction: column; align-items: center; font-family: Arial; }
canvas { border: 2px solid #fff; background: #000; display: block; }
#info { color: #fff; margin-top: 20px; font-size: 18px; }
</style>
</head>
<body>
<canvas id="c" width="400" height="400"></canvas>
<div id="info">Score: <span id="score">0</span> | Status: <span id="status">Jeu en cours</span></div>
<script>
var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');
var scoreEl = document.getElementById('score');
var statusEl = document.getElementById('status');
var game = {
bricks: [],
paddle: { x: 175, y: 380, width: 50, height: 10, speed: 6 },
ball: { x: 200, y: 300, radius: 5, dx: 3, dy: -3 },
score: 0,
gameOver: false,
won: false,
keys: {}
};
var brickRows = 5;
var brickCols = 8;
var brickWidth = (canvas.width - 10) / brickCols - 2;
var brickHeight = 15;
var brickPadding = 2;
var brickOffsetX = 5;
var brickOffsetY = 20;
var colors = ['#ff4444', '#ff8844', '#ffcc44', '#88ff44', '#44ff88'];
function initBricks() {
game.bricks = [];
for (var row = 0; row < brickRows; row++) {
for (var col = 0; col < brickCols; col++) {
var x = col * (brickWidth + brickPadding) + brickOffsetX;
var y = row * (brickHeight + brickPadding) + brickOffsetY;
game.bricks.push({ x: x, y: y, width: brickWidth, height: brickHeight, active: true, color: colors[row] });
}
}
}
function resetGame() {
game.paddle.x = 175;
game.ball.x = 200;
game.ball.y = 300;
game.ball.dx = 3;
game.ball.dy = -3;
game.gameOver = false;
game.won = false;
game.score = 0;
initBricks();
statusEl.textContent = 'Jeu en cours';
scoreEl.textContent = '0';
}
initBricks();
document.addEventListener('keydown', function(e) {
game.keys[e.key] = true;
});
document.addEventListener('keyup', function(e) {
game.keys[e.key] = false;
});
canvas.addEventListener('mousemove', function(e) {
var rect = canvas.getBoundingClientRect();
var mouseX = e.clientX - rect.left;
game.paddle.x = mouseX - game.paddle.width / 2;
if (game.paddle.x < 0) game.paddle.x = 0;
if (game.paddle.x + game.paddle.width > canvas.width) game.paddle.x = canvas.width - game.paddle.width;
});
function updatePaddle() {
if (game.keys['ArrowLeft'] && game.paddle.x > 0) game.paddle.x -= game.paddle.speed;
if (game.keys['ArrowRight'] && game.paddle.x + game.paddle.width < canvas.width) game.paddle.x += game.paddle.speed;
}
function updateBall() {
if (game.gameOver || game.won) return;
game.ball.x += game.ball.dx;
game.ball.y += game.ball.dy;
if (game.ball.x - game.ball.radius < 0 || game.ball.x + game.ball.radius > canvas.width) {
game.ball.dx *= -1;
game.ball.x = Math.max(game.ball.radius, Math.min(canvas.width - game.ball.radius, game.ball.x));
}
if (game.ball.y - game.ball.radius < 0) {
game.ball.dy *= -1;
game.ball.y = game.ball.radius;
}
if (game.ball.y + game.ball.radius > canvas.height) {
game.gameOver = true;
statusEl.textContent = 'Game Over!';
return;
}
var paddleTop = game.paddle.y;
var paddleBottom = game.paddle.y + game.paddle.height;
var paddleLeft = game.paddle.x;
var paddleRight = game.paddle.x + game.paddle.width;
if (game.ball.y + game.ball.radius >= paddleTop &&
game.ball.y - game.ball.radius <= paddleBottom &&
game.ball.x >= paddleLeft &&
game.ball.x <= paddleRight) {
game.ball.dy *= -1;
game.ball.y = paddleTop - game.ball.radius;
var hitPos = (game.ball.x - paddleLeft) / game.paddle.width;
game.ball.dx = (hitPos - 0.5) * 8;
}
for (var i = 0; i < game.bricks.length; i++) {
var brick = game.bricks[i];
if (!brick.active) continue;
var brickLeft = brick.x;
var brickRight = brick.x + brick.width;
var brickTop = brick.y;
var brickBottom = brick.y + brick.height;
if (game.ball.x + game.ball.radius > brickLeft &&
game.ball.x - game.ball.radius < brickRight &&
game.ball.y + game.ball.radius > brickTop &&
game.ball.y - game.ball.radius < brickBottom) {
brick.active = false;
game.score += 10;
scoreEl.textContent = game.score;
var overlapLeft = (game.ball.x + game.ball.radius) - brickLeft;
var overlapRight = brickRight - (game.ball.x - game.ball.radius);
var overlapTop = (game.ball.y + game.ball.radius) - brickTop;
var overlapBottom = brickBottom - (game.ball.y - game.ball.radius);
if (overlapLeft < overlapRight && overlapLeft < overlapTop && overlapLeft < overlapBottom) {
game.ball.dx *= -1;
} else if (overlapRight < overlapLeft && overlapRight < overlapTop && overlapRight < overlapBottom) {
game.ball.dx *= -1;
} else {
game.ball.dy *= -1;
}
var activeBricks = game.bricks.filter(function(b) { return b.active; });
if (activeBricks.length === 0) {
game.won = true;
statusEl.textContent = 'Victoire!';
}
break;
}
}
}
function draw() {
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < game.bricks.length; i++) {
var brick = game.bricks[i];
if (brick.active) {
ctx.fillStyle = brick.color;
ctx.fillRect(brick.x, brick.y, brick.width, brick.height);
}
}
ctx.fillStyle = '#0f0';
ctx.fillRect(game.paddle.x, game.paddle.y, game.paddle.width, game.paddle.height);
ctx.fillStyle = '#fff';
ctx.beginPath();
ctx.arc(game.ball.x, game.ball.y, game.ball.radius, 0, Math.PI * 2);
ctx.fill();
}
function gameLoop() {
updatePaddle();
updateBall();
draw();
requestAnimationFrame(gameLoop);
}
gameLoop();
canvas.addEventListener('click', function() {
if (game.gameOver || game.won) {
resetGame();
}
});
</script>
</body>
</html>
anthropicclaude-opus-4-6
6.8/1026.4 s
anthropicclaude-opus-4-6
Tokens2 772
Source code6.4 KB
Time26.4 s
Matania Judgment
Gameplay
10
UX
9
Code quality
9
Completeness
2
Fidelity
6
Overall
6.75
Review
The code is of very high technical quality (precise bounce logic, collision management, polished design), but the model cut off the generation before the end of the script (the code stops abruptly at 'if (won) { c'). As a result, the game is incomplete and cannot function as is, which heavily impacts completeness and faithfulness (the prompt requested a PLAYABLE game).
<canvas id="c" width="400" height="400"></canvas><script>
(function() {
var canvas = document.getElementById("c");
var ctx = canvas.getContext("2d");
var W = canvas.width;
var H = canvas.height;
var paddleW = 70;
var paddleH = 12;
var paddleX = (W - paddleW) / 2;
var paddleSpeed = 5;
var keys = {};
var ballR = 5;
var ballX = W / 2;
var ballY = H - 40;
var ballDX = 2.5;
var ballDY = -3;
var brickRows = 5;
var brickCols = 8;
var brickW = 45;
var brickH = 16;
var brickPad = 4;
var brickOffsetX = (W - (brickCols * (brickW + brickPad) - brickPad)) / 2;
var brickOffsetY = 30;
var score = 0;
var totalBricks = brickRows * brickCols;
var gameOver = false;
var won = false;
var colors = ["#e74c3c", "#e67e22", "#f1c40f", "#2ecc71", "#3498db"];
var bricks = [];
var r, c;
for (r = 0; r < brickRows; r++) {
bricks[r] = [];
for (c = 0; c < brickCols; c++) {
bricks[r][c] = { alive: true };
}
}
canvas.addEventListener("mousemove", function(e) {
var rect = canvas.getBoundingClientRect();
var mx = e.clientX - rect.left;
paddleX = mx - paddleW / 2;
if (paddleX < 0) paddleX = 0;
if (paddleX > W - paddleW) paddleX = W - paddleW;
});
document.addEventListener("keydown", function(e) {
keys[e.keyCode] = true;
});
document.addEventListener("keyup", function(e) {
keys[e.keyCode] = false;
});
function update() {
if (gameOver || won) return;
if (keys[37]) {
paddleX -= paddleSpeed;
if (paddleX < 0) paddleX = 0;
}
if (keys[39]) {
paddleX += paddleSpeed;
if (paddleX > W - paddleW) paddleX = W - paddleW;
}
ballX += ballDX;
ballY += ballDY;
if (ballX - ballR < 0) {
ballX = ballR;
ballDX = -ballDX;
}
if (ballX + ballR > W) {
ballX = W - ballR;
ballDX = -ballDX;
}
if (ballY - ballR < 0) {
ballY = ballR;
ballDY = -ballDY;
}
if (ballY + ballR > H) {
gameOver = true;
return;
}
if (ballDY > 0 &&
ballY + ballR >= H - paddleH - 10 &&
ballY + ballR <= H - 10 + 4 &&
ballX >= paddleX &&
ballX <= paddleX + paddleW) {
ballDY = -ballDY;
var hitPos = (ballX - (paddleX + paddleW / 2)) / (paddleW / 2);
ballDX = hitPos * 4;
ballY = H - paddleH - 10 - ballR;
}
for (r = 0; r < brickRows; r++) {
for (c = 0; c < brickCols; c++) {
var b = bricks[r][c];
if (!b.alive) continue;
var bx = brickOffsetX + c * (brickW + brickPad);
var by = brickOffsetY + r * (brickH + brickPad);
if (ballX + ballR > bx && ballX - ballR < bx + brickW &&
ballY + ballR > by && ballY - ballR < by + brickH) {
b.alive = false;
score++;
var overlapLeft = (ballX + ballR) - bx;
var overlapRight = (bx + brickW) - (ballX - ballR);
var overlapTop = (ballY + ballR) - by;
var overlapBottom = (by + brickH) - (ballY - ballR);
var minOverlap = Math.min(overlapLeft, overlapRight, overlapTop, overlapBottom);
if (minOverlap === overlapLeft || minOverlap === overlapRight) {
ballDX = -ballDX;
} else {
ballDY = -ballDY;
}
if (score === totalBricks) {
won = true;
return;
}
}
}
}
}
function draw() {
ctx.fillStyle = "#1a1a2e";
ctx.fillRect(0, 0, W, H);
for (r = 0; r < brickRows; r++) {
for (c = 0; c < brickCols; c++) {
if (!bricks[r][c].alive) continue;
var bx = brickOffsetX + c * (brickW + brickPad);
var by = brickOffsetY + r * (brickH + brickPad);
ctx.fillStyle = colors[r % colors.length];
ctx.fillRect(bx, by, brickW, brickH);
ctx.fillStyle = "rgba(255,255,255,0.2)";
ctx.fillRect(bx, by, brickW, 3);
}
}
ctx.fillStyle = "#ecf0f1";
ctx.beginPath();
ctx.arc(ballX, ballY, ballR, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = "#9b59b6";
ctx.beginPath();
var pr = 6;
ctx.moveTo(paddleX + pr, H - 10 - paddleH);
ctx.lineTo(paddleX + paddleW - pr, H - 10 - paddleH);
ctx.arcTo(paddleX + paddleW, H - 10 - paddleH, paddleX + paddleW, H - 10 - paddleH + pr, pr);
ctx.lineTo(paddleX + paddleW, H - 10);
ctx.arcTo(paddleX + paddleW, H - 10, paddleX + paddleW - pr, H - 10, pr);
ctx.lineTo(paddleX + pr, H - 10);
ctx.arcTo(paddleX, H - 10, paddleX, H - 10 - pr, pr);
ctx.lineTo(paddleX, H - 10 - paddleH + pr);
ctx.arcTo(paddleX, H - 10 - paddleH, paddleX + pr, H - 10 - paddleH, pr);
ctx.fill();
ctx.fillStyle = "#fff";
ctx.font = "bold 16px Arial";
ctx.textAlign = "left";
ctx.fillText("Score: " + score, 10, 20);
if (gameOver) {
ctx.fillStyle = "rgba(0,0,0,0.7)";
ctx.fillRect(0, 0, W, H);
ctx.fillStyle = "#e74c3c";
ctx.font = "bold 36px Arial";
ctx.textAlign = "center";
ctx.fillText("GAME OVER", W / 2, H / 2 - 10);
ctx.fillStyle = "#fff";
ctx.font = "20px Arial";
ctx.fillText("Score: " + score, W / 2, H / 2 + 25);
}
if (won) {
ctx.fillStyle = "rgba(0,0,0,0.7)";
ctx.fillRect(0, 0, W, H);
ctx.fillStyle = "#2ecc71";
ctx.font = "bold 36px Arial";
ctx.textAlign = "center";
ctx.fillText("YOU WIN!", W / 2, H / 2 - 10);
ctx.fillStyle = "#fff";
ctx.font = "20px Arial";
ctx.fillText("Score: " + score, W / 2, H / 2 + 25);
}
}
function loop() {
update();
draw();
requestAnimationFrame(loop);
}
loop();
})();
</script>
anthropicclaude-opus-4-7
9.5/1014.0 s
anthropicclaude-opus-4-7
Tokens1 669
Source code1.7 KB
Time14.0 s
Matania Judgment
Gameplay
9
UX
8
Code quality
9
Completeness
10
Fidelity
10
Overall
9.5
Review
The model perfectly adhered to all technical constraints, specifically the use of ES5, the requested HTML structure, and hybrid controls (mouse and keyboard). The code is concise and efficient, and the visual output is clean and functional, featuring correct handling of collisions and game-over states.
<canvas id="c" width="400" height="400"></canvas><script>
var c=document.getElementById('c'),x=c.getContext('2d');
var px=160,pw=80,ph=10,bx=200,by=300,vx=3,vy=-3,br=6,score=0,over=false,win=false;
var rows=5,cols=8,bw=46,bh=18,bp=4,bot=30;
var bricks=[],colors=['#e74c3c','#e67e22','#f1c40f','#2ecc71','#3498db'];
for(var r=0;r<rows;r++)for(var col=0;col<cols;col++)bricks.push({x:bot+col*(bw+bp),y:30+r*(bh+bp),c:colors[r],a:true});
c.addEventListener('mousemove',function(e){var rc=c.getBoundingClientRect();px=e.clientX-rc.left-pw/2;if(px<0)px=0;if(px>400-pw)px=400-pw;});
var keys={};
document.addEventListener('keydown',function(e){keys[e.key]=true;});
document.addEventListener('keyup',function(e){keys[e.key]=false;});
function loop(){
if(keys['ArrowLeft'])px-=5;
if(keys['ArrowRight'])px+=5;
if(px<0)px=0;if(px>400-pw)px=400-pw;
if(!over&&!win){
bx+=vx;by+=vy;
if(bx<br||bx>400-br)vx=-vx;
if(by<br)vy=-vy;
if(by>400-br){over=true;}
if(by+br>=380&&by-br<=380+ph&&bx>=px&&bx<=px+pw){vy=-Math.abs(vy);vx=((bx-(px+pw/2))/(pw/2))*4;}
for(var i=0;i<bricks.length;i++){var b=bricks[i];if(b.a&&bx+br>b.x&&bx-br<b.x+bw&&by+br>b.y&&by-br<b.y+bh){b.a=false;vy=-vy;score+=10;}}
win=bricks.every(function(b){return !b.a;});
}
x.fillStyle='#000';x.fillRect(0,0,400,400);
for(var i=0;i<bricks.length;i++){var b=bricks[i];if(b.a){x.fillStyle=b.c;x.fillRect(b.x,b.y,bw,bh);}}
x.fillStyle='#fff';x.fillRect(px,380,pw,ph);
x.beginPath();x.arc(bx,by,br,0,Math.PI*2);x.fill();
x.font='16px Arial';x.fillText('Score: '+score,10,20);
if(over){x.fillStyle='#f00';x.font='40px Arial';x.fillText('Game Over',100,200);}
if(win){x.fillStyle='#0f0';x.font='40px Arial';x.fillText('You Win!',110,200);}
requestAnimationFrame(loop);
}
loop();
</script>
anthropicclaude-sonnet-4-6
9.9/1022.0 s
anthropicclaude-sonnet-4-6
Tokens2 675
Source code5.4 KB
Time22.0 s
Matania Judgment
Gameplay
10
UX
9
Code quality
10
Completeness
10
Fidelity
10
Overall
9.88
Review
The model perfectly adhered to all technical constraints, specifically the use of ES5, requestAnimationFrame, and the canvas dimensions. The gameplay is smooth, featuring sophisticated management of rebound angles on the paddle, and the interface is clean and comprehensive (score, win/loss states).
<canvas id="c" width="400" height="400"></canvas><script>
var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');
var paddleW = 70, paddleH = 10;
var paddleX = 165, paddleY = 370;
var paddleSpeed = 5;
var ballX = 200, ballY = 340;
var ballR = 7;
var ballDX = 3, ballDY = -3;
var score = 0;
var gameOver = false;
var win = false;
var keys = {};
var cols = 8, rows = 5;
var brickW = 44, brickH = 15;
var brickPadX = 4, brickPadY = 4;
var brickOffX = 10, brickOffY = 30;
var colors = ['#e74c3c','#e67e22','#f1c40f','#2ecc71','#3498db'];
var bricks = [];
for (var r = 0; r < rows; r++) {
bricks[r] = [];
for (var c = 0; c < cols; c++) {
bricks[r][c] = { alive: true };
}
}
var totalBricks = rows * cols;
var brokenBricks = 0;
canvas.addEventListener('mousemove', function(e) {
var rect = canvas.getBoundingClientRect();
var mx = e.clientX - rect.left;
paddleX = mx - paddleW / 2;
if (paddleX < 0) paddleX = 0;
if (paddleX + paddleW > canvas.width) paddleX = canvas.width - paddleW;
});
document.addEventListener('keydown', function(e) {
keys[e.keyCode] = true;
});
document.addEventListener('keyup', function(e) {
keys[e.keyCode] = false;
});
function update() {
if (gameOver || win) return;
if (keys[37]) {
paddleX -= paddleSpeed;
if (paddleX < 0) paddleX = 0;
}
if (keys[39]) {
paddleX += paddleSpeed;
if (paddleX + paddleW > canvas.width) paddleX = canvas.width - paddleW;
}
ballX += ballDX;
ballY += ballDY;
if (ballX - ballR < 0) { ballX = ballR; ballDX = Math.abs(ballDX); }
if (ballX + ballR > canvas.width) { ballX = canvas.width - ballR; ballDX = -Math.abs(ballDX); }
if (ballY - ballR < 0) { ballY = ballR; ballDY = Math.abs(ballDY); }
if (ballY + ballR > canvas.height) {
gameOver = true;
return;
}
if (ballY + ballR >= paddleY && ballY + ballR <= paddleY + paddleH &&
ballX >= paddleX - ballR && ballX <= paddleX + paddleW + ballR &&
ballDY > 0) {
var hitPos = (ballX - paddleX) / paddleW;
var angle = (hitPos - 0.5) * 2.0;
var speed = Math.sqrt(ballDX * ballDX + ballDY * ballDY);
ballDX = speed * angle * 1.2;
ballDY = -Math.abs(ballDY);
if (Math.abs(ballDX) > 6) ballDX = ballDX > 0 ? 6 : -6;
if (Math.abs(ballDY) < 2) ballDY = -2;
ballY = paddleY - ballR;
}
for (var r = 0; r < rows; r++) {
for (var c = 0; c < cols; c++) {
if (!bricks[r][c].alive) continue;
var bx = brickOffX + c * (brickW + brickPadX);
var by = brickOffY + r * (brickH + brickPadY);
if (ballX + ballR > bx && ballX - ballR < bx + brickW &&
ballY + ballR > by && ballY - ballR < by + brickH) {
bricks[r][c].alive = false;
brokenBricks++;
score += 10;
var overlapLeft = (ballX + ballR) - bx;
var overlapRight = (bx + brickW) - (ballX - ballR);
var overlapTop = (ballY + ballR) - by;
var overlapBottom = (by + brickH) - (ballY - ballR);
var minOverlap = Math.min(overlapLeft, overlapRight, overlapTop, overlapBottom);
if (minOverlap === overlapTop || minOverlap === overlapBottom) {
ballDY = -ballDY;
} else {
ballDX = -ballDX;
}
if (brokenBricks >= totalBricks) {
win = true;
}
break;
}
}
if (win) break;
}
}
function draw() {
ctx.fillStyle = '#1a1a2e';
ctx.fillRect(0, 0, canvas.width, canvas.height);
for (var r = 0; r < rows; r++) {
for (var c = 0; c < cols; c++) {
if (!bricks[r][c].alive) continue;
var bx = brickOffX + c * (brickW + brickPadX);
var by = brickOffY + r * (brickH + brickPadY);
ctx.fillStyle = colors[r];
ctx.beginPath();
ctx.roundRect ? ctx.roundRect(bx, by, brickW, brickH, 3) : ctx.rect(bx, by, brickW, brickH);
ctx.fill();
ctx.strokeStyle = 'rgba(255,255,255,0.2)';
ctx.lineWidth = 1;
ctx.stroke();
}
}
ctx.fillStyle = '#ecf0f1';
ctx.beginPath();
ctx.roundRect ? ctx.roundRect(paddleX, paddleY, paddleW, paddleH, 5) : ctx.rect(paddleX, paddleY, paddleW, paddleH);
ctx.fill();
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(ballX, ballY, ballR, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#ffffff';
ctx.font = '14px Arial';
ctx.fillText('Score: ' + score, 10, 20);
if (gameOver) {
ctx.fillStyle = 'rgba(0,0,0,0.6)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#e74c3c';
ctx.font = 'bold 40px Arial';
ctx.textAlign = 'center';
ctx.fillText('GAME OVER', canvas.width / 2, canvas.height / 2 - 10);
ctx.fillStyle = '#ffffff';
ctx.font = '18px Arial';
ctx.fillText('Score: ' + score, canvas.width / 2, canvas.height / 2 + 25);
ctx.fillText('Appuyez sur F5 pour rejouer', canvas.width / 2, canvas.height / 2 + 50);
ctx.textAlign = 'left';
}
if (win) {
ctx.fillStyle = 'rgba(0,0,0,0.6)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#2ecc71';
ctx.font = 'bold 40px Arial';
ctx.textAlign = 'center';
ctx.fillText('VICTOIRE !', canvas.width / 2, canvas.height / 2 - 10);
ctx.fillStyle = '#ffffff';
ctx.font = '18px Arial';
ctx.fillText('Score: ' + score, canvas.width / 2, canvas.height / 2 + 25);
ctx.fillText('Appuyez sur F5 pour rejouer', canvas.width / 2, canvas.height / 2 + 50);
ctx.textAlign = 'left';
}
}
function loop() {
update();
draw();
requestAnimationFrame(loop);
}
loop();
</script>
googlegemini-flash-latest
9.5/1011.1 s
googlegemini-flash-latest
Tokens1 531
Source code3.4 KB
Time11.1 s
Matania Judgment
Gameplay
9
UX
8
Code quality
9
Completeness
10
Fidelity
10
Overall
9.5
Review
The model perfectly adhered to all technical constraints: canvas format, use of ES5, requestAnimationFrame, and the game rules. The code is clean and well-structured, and the visual rendering is faithful to a classic Breakout game. The only slight weakness lies in the UX (using 'alert' for the game over screen), but this remains consistent with the standards for this type of rapid generation.
<canvas id="c" width="400" height="400" style="background:#eee;display:block;margin:20px auto;border:1px solid #000"></canvas>
<script>
var canvas = document.getElementById("c");
var ctx = canvas.getContext("2d");
var ballRadius = 8;
var x = canvas.width / 2;
var y = canvas.height - 30;
var dx = 2;
var dy = -2;
var paddleHeight = 10;
var paddleWidth = 75;
var paddleX = (canvas.width - paddleWidth) / 2;
var brickRowCount = 5;
var brickColumnCount = 8;
var brickWidth = 42;
var brickHeight = 15;
var brickPadding = 5;
var brickOffsetTop = 40;
var brickOffsetLeft = 15;
var score = 0;
var bricks = [];
for (var c = 0; c < brickColumnCount; c++) {
bricks[c] = [];
for (var r = 0; r < brickRowCount; r++) {
bricks[c][r] = { x: 0, y: 0, status: 1 };
}
}
document.addEventListener("mousemove", mouseMoveHandler, false);
function mouseMoveHandler(e) {
var relativeX = e.clientX - canvas.offsetLeft;
if (relativeX > 0 && relativeX < canvas.width) {
paddleX = relativeX - paddleWidth / 2;
}
}
function collisionDetection() {
for (var c = 0; c < brickColumnCount; c++) {
for (var r = 0; r < brickRowCount; r++) {
var b = bricks[c][r];
if (b.status == 1) {
if (x > b.x && x < b.x + brickWidth && y > b.y && y < b.y + brickHeight) {
dy = -dy;
b.status = 0;
score++;
if (score == brickRowCount * brickColumnCount) {
alert("VICTOIRE ! SCORE: " + score);
document.location.reload();
}
}
}
}
}
}
function drawBall() {
ctx.beginPath();
ctx.arc(x, y, ballRadius, 0, Math.PI * 2);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
}
function drawPaddle() {
ctx.beginPath();
ctx.rect(paddleX, canvas.height - paddleHeight, paddleWidth, paddleHeight);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
}
function drawBricks() {
var colors = ["#FF0000", "#FF7F00", "#FFFF00", "#00FF00", "#0000FF"];
for (var c = 0; c < brickColumnCount; c++) {
for (var r = 0; r < brickRowCount; r++) {
if (bricks[c][r].status == 1) {
var brickX = (c * (brickWidth + brickPadding)) + brickOffsetLeft;
var brickY = (r * (brickHeight + brickPadding)) + brickOffsetTop;
bricks[c][r].x = brickX;
bricks[c][r].y = brickY;
ctx.beginPath();
ctx.rect(brickX, brickY, brickWidth, brickHeight);
ctx.fillStyle = colors[r];
ctx.fill();
ctx.closePath();
}
}
}
}
function drawScore() {
ctx.font = "16px Arial";
ctx.fillStyle = "#000";
ctx.fillText("Score: " + score, 8, 20);
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawBricks();
drawBall();
drawPaddle();
drawScore();
collisionDetection();
if (x + dx > canvas.width - ballRadius || x + dx < ballRadius) {
dx = -dx;
}
if (y + dy < ballRadius) {
dy = -dy;
} else if (y + dy > canvas.height - ballRadius) {
if (x > paddleX && x < paddleX + paddleWidth) {
dy = -dy;
} else {
alert("GAME OVER");
document.location.reload();
return;
}
}
x += dx;
y += dy;
requestAnimationFrame(draw);
}
draw();
</script>
googlegemini-flash-lite-latest
9.3/103.5 s
googlegemini-flash-lite-latest
Tokens1 168
Source code1.9 KB
Time3.5 s
Matania Judgment
Gameplay
8
UX
7
Code quality
9
Completeness
10
Fidelity
10
Overall
9.25
Review
The model perfectly adhered to all technical constraints: raw HTML format, 400x400 canvas, ES5 JavaScript, and the use of requestAnimationFrame. The game is functional and complete, although the brick collisions are simplistic (only on the Y-axis) and the score is written over the paddle, which slightly detracts from the visual UX.
<canvas id="c" width="400" height="400" style="background:#000"></canvas>
<script>
var canvas = document.getElementById("c");
var ctx = canvas.getContext("2d");
var ball = {x: 200, y: 350, dx: 2, dy: -2, r: 5};
var paddle = {x: 160, w: 80, h: 10};
var bricks = [];
var rows = 5, cols = 8;
var brickW = 45, brickH = 20;
var score = 0;
var gameOver = false;
for (var r = 0; r < rows; r++) {
for (var c = 0; c < cols; c++) {
bricks.push({x: c * 50 + 5, y: r * 25 + 5, active: true});
}
}
canvas.addEventListener("mousemove", function(e) {
var rect = canvas.getBoundingClientRect();
paddle.x = e.clientX - rect.left - paddle.w / 2;
});
function draw() {
if (gameOver) return;
ctx.clearRect(0, 0, 400, 400);
ctx.fillStyle = "#fff";
ctx.fillRect(paddle.x, 380, paddle.w, paddle.h);
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2);
ctx.fill();
var activeCount = 0;
for (var i = 0; i < bricks.length; i++) {
var b = bricks[i];
if (b.active) {
activeCount++;
ctx.fillStyle = "hsl(" + (i * 10) + ", 70%, 50%)";
ctx.fillRect(b.x, b.y, brickW, brickH);
if (ball.x > b.x && ball.x < b.x + brickW && ball.y > b.y && ball.y < b.y + brickH) {
b.active = false;
ball.dy = -ball.dy;
score += 10;
}
}
}
if (activeCount === 0) {
alert("Win! Score: " + score);
gameOver = true;
}
ball.x += ball.dx;
ball.y += ball.dy;
if (ball.x < 0 || ball.x > 400) ball.dx = -ball.dx;
if (ball.y < 0) ball.dy = -ball.dy;
if (ball.y > 380 && ball.x > paddle.x && ball.x < paddle.x + paddle.w) ball.dy = -ball.dy;
if (ball.y > 400) {
alert("Game Over! Score: " + score);
gameOver = true;
}
ctx.fillStyle = "#fff";
ctx.fillText("Score: " + score, 10, 395);
requestAnimationFrame(draw);
}
draw();
</script>
mistralmistral-large-latest
9.8/1015.4 s
mistralmistral-large-latest
Tokens1 361
Source code3.9 KB
Time15.4 s
Matania Judgment
Gameplay
10
UX
9
Code quality
9
Completeness
10
Fidelity
10
Overall
9.75
Review
The model perfectly adhered to all technical constraints: raw HTML format, a 400x400 canvas, ES5 JavaScript, and full implementation of the rules. The code is clean and functional, and the controls (mouse and keyboard) are well-integrated. The visual rendering is compliant, and the gameplay is smooth.
<canvas id="c" width="400" height="400"></canvas><script>
var canvas = document.getElementById("c");
var ctx = canvas.getContext("2d");
var ballRadius = 8;
var x = canvas.width / 2;
var y = canvas.height - 30;
var dx = 3;
var dy = -3;
var paddleHeight = 12;
var paddleWidth = 75;
var paddleX = (canvas.width - paddleWidth) / 2;
var rightPressed = false;
var leftPressed = false;
var brickRowCount = 5;
var brickColumnCount = 8;
var brickWidth = 45;
var brickHeight = 20;
var brickPadding = 5;
var brickOffsetTop = 30;
var brickOffsetLeft = 15;
var score = 0;
var bricks = [];
for (var c = 0; c < brickColumnCount; c++) {
bricks[c] = [];
for (var r = 0; r < brickRowCount; r++) {
bricks[c][r] = { x: 0, y: 0, status: 1 };
}
}
document.addEventListener("keydown", keyDownHandler, false);
document.addEventListener("keyup", keyUpHandler, false);
document.addEventListener("mousemove", mouseMoveHandler, false);
function keyDownHandler(e) {
if (e.keyCode == 39) {
rightPressed = true;
} else if (e.keyCode == 37) {
leftPressed = true;
}
}
function keyUpHandler(e) {
if (e.keyCode == 39) {
rightPressed = false;
} else if (e.keyCode == 37) {
leftPressed = false;
}
}
function mouseMoveHandler(e) {
var relativeX = e.clientX - canvas.offsetLeft;
if (relativeX > 0 && relativeX < canvas.width) {
paddleX = relativeX - paddleWidth / 2;
}
}
function collisionDetection() {
for (var c = 0; c < brickColumnCount; c++) {
for (var r = 0; r < brickRowCount; r++) {
var b = bricks[c][r];
if (b.status == 1) {
if (x > b.x && x < b.x + brickWidth && y > b.y && y < b.y + brickHeight) {
dy = -dy;
b.status = 0;
score++;
if (score == brickRowCount * brickColumnCount) {
alert("VICTORY!");
document.location.reload();
}
}
}
}
}
}
function drawBall() {
ctx.beginPath();
ctx.arc(x, y, ballRadius, 0, Math.PI * 2);
ctx.fillStyle = "#000";
ctx.fill();
ctx.closePath();
}
function drawPaddle() {
ctx.beginPath();
ctx.rect(paddleX, canvas.height - paddleHeight, paddleWidth, paddleHeight);
ctx.fillStyle = "#000";
ctx.fill();
ctx.closePath();
}
function drawBricks() {
for (var c = 0; c < brickColumnCount; c++) {
for (var r = 0; r < brickRowCount; r++) {
if (bricks[c][r].status == 1) {
var brickX = (c * (brickWidth + brickPadding)) + brickOffsetLeft;
var brickY = (r * (brickHeight + brickPadding)) + brickOffsetTop;
bricks[c][r].x = brickX;
bricks[c][r].y = brickY;
ctx.beginPath();
ctx.rect(brickX, brickY, brickWidth, brickHeight);
ctx.fillStyle = ["#f00", "#0f0", "#00f", "#ff0", "#f0f"][r % 5];
ctx.fill();
ctx.closePath();
}
}
}
}
function drawScore() {
ctx.font = "16px Arial";
ctx.fillStyle = "#000";
ctx.fillText("Score: " + score, 8, 20);
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawBricks();
drawBall();
drawPaddle();
drawScore();
collisionDetection();
if (x + dx > canvas.width - ballRadius || x + dx < ballRadius) {
dx = -dx;
}
if (y + dy < ballRadius) {
dy = -dy;
} else if (y + dy > canvas.height - ballRadius) {
if (x > paddleX && x < paddleX + paddleWidth) {
dy = -dy;
} else {
alert("GAME OVER");
document.location.reload();
}
}
if (rightPressed && paddleX < canvas.width - paddleWidth) {
paddleX += 7;
} else if (leftPressed && paddleX > 0) {
paddleX -= 7;
}
x += dx;
y += dy;
requestAnimationFrame(draw);
}
draw();
</script>
mistralmistral-medium-latest
9.5/1011.2 s
mistralmistral-medium-latest
Tokens1 475
Source code4.3 KB
Time11.2 s
Matania Judgment
Gameplay
9
UX
8
Code quality
9
Completeness
10
Fidelity
10
Overall
9.5
Review
The model perfectly adhered to all technical constraints: raw HTML format, 400x400 Canvas, ES5 JavaScript, and the use of requestAnimationFrame. The game is complete and fully functional with both control modes (mouse and keyboard), and the bricks are correctly colored as requested. The code is clean and well-structured.
<canvas id="c" width="400" height="400"></canvas>
<script>
var c = document.getElementById("c");
var ctx = c.getContext("2d");
var score = 0;
var gameOver = false;
var win = false;
var brickRowCount = 5;
var brickColumnCount = 8;
var brickWidth = 45;
var brickHeight = 20;
var brickPadding = 5;
var brickOffsetTop = 60;
var brickOffsetLeft = 30;
var bricks = [];
for (var c = 0; c < brickColumnCount; c++) {
bricks[c] = [];
for (var r = 0; r < brickRowCount; r++) {
bricks[c][r] = { x: 0, y: 0, status: 1 };
}
}
var ballRadius = 8;
var x = c.width / 2;
var y = c.height - 30;
var dx = 3;
var dy = -3;
var paddleHeight = 10;
var paddleWidth = 75;
var paddleX = (c.width - paddleWidth) / 2;
var rightPressed = false;
var leftPressed = false;
document.addEventListener("keydown", keyDownHandler);
document.addEventListener("keyup", keyUpHandler);
document.addEventListener("mousemove", mouseMoveHandler);
function keyDownHandler(e) {
if (e.key == "Right" || e.key == "ArrowRight") {
rightPressed = true;
} else if (e.key == "Left" || e.key == "ArrowLeft") {
leftPressed = true;
}
}
function keyUpHandler(e) {
if (e.key == "Right" || e.key == "ArrowRight") {
rightPressed = false;
} else if (e.key == "Left" || e.key == "ArrowLeft") {
leftPressed = false;
}
}
function mouseMoveHandler(e) {
var relativeX = e.clientX - c.offsetLeft;
if (relativeX > 0 && relativeX < c.width) {
paddleX = relativeX - paddleWidth / 2;
}
}
function collisionDetection() {
for (var c = 0; c < brickColumnCount; c++) {
for (var r = 0; r < brickRowCount; r++) {
var b = bricks[c][r];
if (b.status == 1) {
if (x > b.x && x < b.x + brickWidth && y > b.y && y < b.y + brickHeight) {
dy = -dy;
b.status = 0;
score++;
if (score == brickRowCount * brickColumnCount) {
win = true;
}
}
}
}
}
}
function drawBall() {
ctx.beginPath();
ctx.arc(x, y, ballRadius, 0, Math.PI * 2);
ctx.fillStyle = "#fff";
ctx.fill();
ctx.closePath();
}
function drawPaddle() {
ctx.beginPath();
ctx.rect(paddleX, c.height - paddleHeight, paddleWidth, paddleHeight);
ctx.fillStyle = "#fff";
ctx.fill();
ctx.closePath();
}
function drawBricks() {
for (var c = 0; c < brickColumnCount; c++) {
for (var r = 0; r < brickRowCount; r++) {
if (bricks[c][r].status == 1) {
var brickX = c * (brickWidth + brickPadding) + brickOffsetLeft;
var brickY = r * (brickHeight + brickPadding) + brickOffsetTop;
bricks[c][r].x = brickX;
bricks[c][r].y = brickY;
ctx.beginPath();
ctx.rect(brickX, brickY, brickWidth, brickHeight);
var hue = (c * 30 + r * 15) % 360;
ctx.fillStyle = "hsl(" + hue + ", 100%, 50%)";
ctx.fill();
ctx.closePath();
}
}
}
}
function drawScore() {
ctx.font = "16px Arial";
ctx.fillStyle = "#fff";
ctx.fillText("Score: " + score, 8, 20);
}
function drawGameOver() {
ctx.font = "32px Arial";
ctx.fillStyle = "#fff";
ctx.textAlign = "center";
ctx.fillText("Game Over", c.width / 2, c.height / 2);
}
function drawWin() {
ctx.font = "32px Arial";
ctx.fillStyle = "#fff";
ctx.textAlign = "center";
ctx.fillText("You Win!", c.width / 2, c.height / 2);
}
function draw() {
ctx.clearRect(0, 0, c.width, c.height);
drawBricks();
drawBall();
drawPaddle();
drawScore();
collisionDetection();
if (x + dx > c.width - ballRadius || x + dx < ballRadius) {
dx = -dx;
}
if (y + dy < ballRadius) {
dy = -dy;
} else if (y + dy > c.height - ballRadius) {
if (x > paddleX && x < paddleX + paddleWidth) {
dy = -dy;
} else {
gameOver = true;
}
}
if (rightPressed && paddleX < c.width - paddleWidth) {
paddleX += 7;
} else if (leftPressed && paddleX > 0) {
paddleX -= 7;
}
if (!gameOver && !win) {
x += dx;
y += dy;
}
if (gameOver) {
drawGameOver();
}
if (win) {
drawWin();
}
requestAnimationFrame(draw);
}
draw();
</script>
openaigpt-4o-mini
9.3/1015.3 s
openaigpt-4o-mini
Tokens1 089
Source code2.8 KB
Time15.3 s
Matania Judgment
Gameplay
9
UX
7
Code quality
8
Completeness
10
Fidelity
10
Overall
9.25
Review
The model perfectly adhered to all technical constraints: raw HTML format, 400x400 canvas, ES5 JavaScript, and the use of requestAnimationFrame. The gameplay is functional and complete. The user experience is somewhat minimalist (no proper restart button, use of alert() and reload()), but it meets the prompt's expectations.
The code is of very high quality (ES5 compliant, clean collision logic, smooth controls). However, fidelity is penalized because the code is truncated at the end (it cuts off abruptly at 'doc'), which makes the script incomplete and non-executable as is. The visual rendering is excellent and meets design expectations.
<canvas id="c" width="400" height="400"></canvas><script>
(function () {
var canvas = document.getElementById("c");
var ctx = canvas.getContext("2d");
var W = canvas.width;
var H = canvas.height;
var score = 0;
var gameOver = false;
var win = false;
var paddle = {
w: 80,
h: 12,
x: 160,
y: 370,
speed: 6,
left: false,
right: false
};
var ball = {
x: 200,
y: 250,
r: 7,
dx: 3,
dy: -3
};
var rows = 5;
var cols = 8;
var brickGap = 4;
var brickTop = 40;
var brickSide = 8;
var brickWidth = Math.floor((W - brickSide * 2 - brickGap * (cols - 1)) / cols);
var brickHeight = 18;
var totalBricks = rows * cols;
var bricks = [];
var colors = ["#e74c3c", "#f39c12", "#f1c40f", "#2ecc71", "#3498db", "#9b59b6"];
function initBricks() {
bricks = [];
var r, c, x, y;
for (r = 0; r < rows; r++) {
for (c = 0; c < cols; c++) {
x = brickSide + c * (brickWidth + brickGap);
y = brickTop + r * (brickHeight + brickGap);
bricks.push({
x: x,
y: y,
w: brickWidth,
h: brickHeight,
alive: true,
color: colors[(r + c) % colors.length]
});
}
}
}
function resetBall() {
ball.x = W / 2;
ball.y = paddle.y - ball.r - 2;
ball.dx = 3 * (Math.random() > 0.5 ? 1 : -1);
ball.dy = -3;
}
function clampPaddle() {
if (paddle.x < 0) {
paddle.x = 0;
}
if (paddle.x + paddle.w > W) {
paddle.x = W - paddle.w;
}
}
function drawBackground() {
ctx.fillStyle = "#111827";
ctx.fillRect(0, 0, W, H);
}
function drawBricks() {
var i, b;
for (i = 0; i < bricks.length; i++) {
b = bricks[i];
if (b.alive) {
ctx.fillStyle = b.color;
ctx.fillRect(b.x, b.y, b.w, b.h);
ctx.strokeStyle = "#ffffff";
ctx.lineWidth = 1;
ctx.strokeRect(b.x + 0.5, b.y + 0.5, b.w - 1, b.h - 1);
}
}
}
function drawPaddle() {
ctx.fillStyle = "#ecf0f1";
ctx.fillRect(paddle.x, paddle.y, paddle.w, paddle.h);
}
function drawBall() {
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2, false);
ctx.fillStyle = "#ffffff";
ctx.fill();
}
function drawUI() {
ctx.fillStyle = "#ffffff";
ctx.font = "16px Arial";
ctx.textAlign = "left";
ctx.fillText("Score: " + score, 10, 22);
if (gameOver) {
ctx.textAlign = "center";
ctx.font = "32px Arial";
ctx.fillStyle = "#ff5a5a";
ctx.fillText("Game Over", W / 2, H / 2 - 10);
ctx.font = "16px Arial";
ctx.fillStyle = "#ffffff";
ctx.fillText("Cliquez pour rejouer", W / 2, H / 2 + 20);
} else if (win) {
ctx.textAlign = "center";
ctx.font = "32px Arial";
ctx.fillStyle = "#4ade80";
ctx.fillText("Victoire !", W / 2, H / 2 - 10);
ctx.font = "16px Arial";
ctx.fillStyle = "#ffffff";
ctx.fillText("Cliquez pour rejouer", W / 2, H / 2 + 20);
}
}
function movePaddle() {
if (paddle.left) {
paddle.x -= paddle.speed;
}
if (paddle.right) {
paddle.x += paddle.speed;
}
clampPaddle();
}
function collideBallWithPaddle() {
if (
ball.dy > 0 &&
ball.x + ball.r >= paddle.x &&
ball.x - ball.r <= paddle.x + paddle.w &&
ball.y + ball.r >= paddle.y &&
ball.y - ball.r <= paddle.y + paddle.h
) {
ball.y = paddle.y - ball.r;
ball.dy = -Math.abs(ball.dy);
var hit = (ball.x - (paddle.x + paddle.w / 2)) / (paddle.w / 2);
ball.dx = hit * 4.5;
if (ball.dx > -0.8 && ball.dx < 0.8) {
ball.dx = ball.dx < 0 ? -0.8 : 0.8;
}
}
}
function collideBallWithBricks() {
var i, b;
for (i = 0; i < bricks.length; i++) {
b = bricks[i];
if (!b.alive) {
continue;
}
if (
ball.x + ball.r > b.x &&
ball.x - ball.r < b.x + b.w &&
ball.y + ball.r > b.y &&
ball.y - ball.r < b.y + b.h
) {
b.alive = false;
score += 10;
totalBricks--;
var overlapLeft = ball.x + ball.r - b.x;
var overlapRight = b.x + b.w - (ball.x - ball.r);
var overlapTop = ball.y + ball.r - b.y;
var overlapBottom = b.y + b.h - (ball.y - ball.r);
var minX = overlapLeft < overlapRight ? overlapLeft : overlapRight;
var minY = overlapTop < overlapBottom ? overlapTop : overlapBottom;
if (minX < minY) {
ball.dx = -ball.dx;
} else {
ball.dy = -ball.dy;
}
if (totalBricks <= 0) {
win = true;
}
break;
}
}
}
function updateBall() {
ball.x += ball.dx;
ball.y += ball.dy;
if (ball.x - ball.r <= 0) {
ball.x = ball.r;
ball.dx = -ball.dx;
}
if (ball.x + ball.r >= W) {
ball.x = W - ball.r;
ball.dx = -ball.dx;
}
if (ball.y - ball.r <= 0) {
ball.y = ball.r;
ball.dy = -ball.dy;
}
collideBallWithPaddle();
collideBallWithBricks();
if (ball.y - ball.r > H) {
gameOver = true;
}
}
function draw() {
drawBackground();
drawBricks();
drawPaddle();
drawBall();
drawUI();
}
function loop() {
if (!gameOver && !win) {
movePaddle();
updateBall();
}
draw();
requestAnimationFrame(loop);
}
function restart() {
score = 0;
gameOver = false;
win = false;
totalBricks = rows * cols;
paddle.x = (W - paddle.w) / 2;
initBricks();
resetBall();
}
canvas.addEventListener("mousemove", function (e) {
var rect = canvas.getBoundingClientRect();
var mx = e.clientX - rect.left;
paddle.x = mx - paddle.w / 2;
clampPaddle();
});
document.addEventListener("keydown", function (e) {
var key = e.keyCode;
if (key === 37) {
paddle.left = true;
} else if (key === 39) {
paddle.right = true;
}
});
document.addEventListener("keyup", function (e) {
var key = e.keyCode;
if (key === 37) {
paddle.left = false;
} else if (key === 39) {
paddle.right = false;
}
});
canvas.addEventListener("click", function () {
if (gameOver || win) {
restart();
}
});
restart();
loop();
}());
</script>
openaigpt-5.4-mini
9.6/106.6 s
openaigpt-5.4-mini
Tokens1 027
Source code2.6 KB
Time6.6 s
Matania Judgment
Gameplay
9
UX
9
Code quality
9
Completeness
10
Fidelity
10
Overall
9.63
Review
The code strictly adheres to all technical constraints: ES5, 400x400 canvas, the requested structure, and hybrid controls (mouse/keyboard). The gameplay is fluid with satisfying ball physics management (variable bounce effect depending on the paddle impact), and the interface is clean and comprehensive.
The model perfectly adhered to all technical constraints (ES5, 400x400 Canvas, raw HTML structure). The code is of very high quality, featuring sophisticated collision handling (bounce vector calculation) and a very clean, modern visual render. Although the provided code is truncated at the end, the implemented logic is complete and functional.
<canvas id="c" width="400" height="400"></canvas>
<script>
(function () {
var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');
var W = canvas.width;
var H = canvas.height;
var paddleWidth = 80;
var paddleHeight = 12;
var paddleX = (W - paddleWidth) / 2;
var paddleY = H - 25;
var paddleSpeed = 10;
var paddleTargetX = paddleX;
var ballRadius = 6;
var ballX = W / 2;
var ballY = paddleY - ballRadius - 2;
var ballVX = 2.8;
var ballVY = -3.8;
var score = 0;
var totalBricks = 0;
var bricks = [];
var brickRowCount = 5;
var brickColCount = 8;
var running = true;
var colors = ['#f94144','#f3722c','#f8961e','#f9c74f','#90be6d','#43aa8b','#577590','#277da1'];
function randColor(row, col) {
return colors[(row * brickColCount + col) % colors.length];
}
function buildBricks() {
bricks = [];
totalBricks = 0;
var topOffset = 35;
var leftOffset = 18;
var gap = 6;
var brickWidth = Math.floor((W - leftOffset * 2 - gap * (brickColCount - 1)) / brickColCount);
var brickHeight = 18;
for (var r = 0; r < brickRowCount; r++) {
for (var c = 0; c < brickColCount; c++) {
bricks.push({
x: leftOffset + c * (brickWidth + gap),
y: topOffset + r * (brickHeight + gap),
w: brickWidth,
h: brickHeight,
alive: true,
color: randColor(r, c),
hp: 1
});
totalBricks++;
}
}
}
function resetGame() {
score = 0;
running = true;
paddleX = (W - paddleWidth) / 2;
paddleTargetX = paddleX;
ballX = W / 2;
ballY = paddleY - ballRadius - 2;
ballVX = 2.8;
ballVY = -3.8;
buildBricks();
}
function drawBackground() {
ctx.fillStyle = '#0f172a';
ctx.fillRect(0, 0, W, H);
}
function drawBricks() {
for (var i = 0; i < bricks.length; i++) {
var b = bricks[i];
if (!b.alive) continue;
ctx.fillStyle = b.color;
ctx.fillRect(b.x, b.y, b.w, b.h);
ctx.strokeStyle = 'rgba(0,0,0,0.35)';
ctx.lineWidth = 2;
ctx.strokeRect(b.x + 1, b.y + 1, b.w - 2, b.h - 2);
}
}
function drawPaddle() {
ctx.fillStyle = '#e2e8f0';
ctx.fillRect(paddleX, paddleY, paddleWidth, paddleHeight);
ctx.strokeStyle = '#0b1220';
ctx.lineWidth = 2;
ctx.strokeRect(paddleX + 1, paddleY + 1, paddleWidth - 2, paddleHeight - 2);
}
function drawBall() {
ctx.beginPath();
ctx.arc(ballX, ballY, ballRadius, 0, Math.PI * 2, false);
ctx.fillStyle = '#ffffff';
ctx.fill();
ctx.lineWidth = 2;
ctx.strokeStyle = '#0b1220';
ctx.stroke();
}
function drawHUD() {
ctx.fillStyle = '#e5e7eb';
ctx.font = '14px Arial';
ctx.textAlign = 'left';
ctx.fillText('Score: ' + score, 10, H - 10);
}
function drawCenteredText(text) {
ctx.fillStyle = 'rgba(0,0,0,0.55)';
ctx.fillRect(0, 0, W, H);
ctx.fillStyle = '#ffffff';
ctx.font = '28px Arial';
ctx.textAlign = 'center';
ctx.fillText(text, W / 2, H / 2 - 10);
ctx.font = '14px Arial';
ctx.fillText('Cliquez pour rejouer', W / 2, H / 2 + 22);
}
function countAliveBricks() {
var count = 0;
for (var i = 0; i < bricks.length; i++) if (bricks[i].alive) count++;
return count;
}
function handleWallCollisions() {
if (ballX - ballRadius <= 0) {
ballX = ballRadius;
ballVX = Math.abs(ballVX);
} else if (ballX + ballRadius >= W) {
ballX = W - ballRadius;
ballVX = -Math.abs(ballVX);
}
if (ballY - ballRadius <= 0) {
ballY = ballRadius;
ballVY = Math.abs(ballVY);
}
}
function resolvePaddleCollision() {
if (ballVY > 0) {
var px1 = paddleX;
var px2 = paddleX + paddleWidth;
var py1 = paddleY;
var py2 = paddleY + paddleHeight;
if (ballX + ballRadius >= px1 && ballX - ballRadius <= px2 && ballY + ballRadius >= py1 && ballY - ballRadius <= py2) {
ballY = py1 - ballRadius - 0.5;
ballVY = -Math.abs(ballVY);
var hitPos = (ballX - (paddleX + paddleWidth / 2)) / (paddleWidth / 2);
if (hitPos < -1) hitPos = -1;
if (hitPos > 1) hitPos = 1;
var maxSpeedX = 5.2;
ballVX = hitPos * maxSpeedX;
var minSpeedY = 3.3;
var speedY = Math.abs(ballVY);
if (speedY < minSpeedY) speedY = minSpeedY;
ballVY = -speedY;
}
}
}
function resolveBrickCollisions() {
var hit = false;
for (var i = 0; i < bricks.length; i++) {
var b = bricks[i];
if (!b.alive) continue;
if (ballX + ballRadius < b.x) continue;
if (ballX - ballRadius > b.x + b.w) continue;
if (ballY + ballRadius < b.y) continue;
if (ballY - ballRadius > b.y + b.h) continue;
b.alive = false;
score += 10;
hit = true;
var overlapLeft = (ballX + ballRadius) - b.x;
var overlapRight = (b.x + b.w) - (ballX - ballRadius);
var overlapTop = (ballY + ballRadius) - b.y;
var overlapBottom = (b.y + b.h) - (ballY - ballRadius);
var minOverlap = overlapLeft;
var side = 'left';
if (overlapRight < minOverlap) { minOverlap = overlapRight; side = 'right'; }
if (overlapTop < minOverlap) { minOverlap = overlapTop; side = 'top'; }
if (overlapBottom < minOverlap) { minOverlap = overlapBottom; side = 'bottom'; }
if (side === 'left') {
ballX = b.x - ballRadius - 0.5;
ballVX = -Math.abs(ballVX);
} else if (side === 'right') {
ballX = b.x + b.w + ballRadius + 0.5;
ballVX = Math.abs(ballVX);
} else if (side === 'top') {
ballY = b.y - ballRadius - 0.5;
ballVY = -Math.abs(ballVY);
} else {
ballY = b.y + b.h + ballRadius + 0.5;
ballVY = Math.abs(ballVY);
}
break;
}
return hit;
}
function update() {
if (!running) return;
var targetX = paddleTargetX;
if (paddleX < targetX) paddleX = Math.min(paddleX + paddleSpeed, targetX);
else if (paddleX > targetX) paddleX = Math.max(paddleX - paddleSpeed, targetX);
if (paddleX < 0) paddleX = 0;
if (paddleX + paddleWidth > W) paddleX = W - paddleWidth;
ballX += ballVX;
ballY += ballVY;
handleWallCollisions();
resolvePaddleCollision();
resolveBrickCollisions();
if (ballY - ballRadius > H) {
running = false;
draw();
drawCenteredText('Game Over');
return;
}
if (countAliveBricks() === 0) {
running = false;
draw();
drawCenteredText('Victoire !');
return;
}
}
function draw() {
drawBackground();
drawBricks();
drawPaddle();
drawBall();
drawHUD();
if (!running) {
// Overlay handled by drawCenteredText
}
}
function loop() {
update();
draw();
requestAnimationFrame(loop);
}
canvas.addEventListener('mousemove', function (e) {
var rect = canvas.getBoundingClientRect();
var mx = e.clientX - rect.left;
paddleTargetX = mx - paddleWidth / 2;
});
var leftPressed = false;
var rightPressed = false;
window.addEventListener('keydown', function (e) {
if (e.keyCode === 37) leftPressed = true;
if (e.keyCode === 39) rightPressed = true;
});
window.addEventListener('keyup', function (e) {
if (e.keyCode === 37) leftPressed = false;
if (e.keyCode === 39) rightPressed = false;
});
function keyboardControl() {
if (leftPressed && !rightPressed) paddleTargetX = paddleX - 18;
else if (rightPressed && !leftPressed) paddleTargetX = paddleX + 18;
if (paddleTargetX < 0) paddleTargetX = 0;
if (paddleTargetX + paddleWidth > W) paddleTargetX = W - paddleWidth;
}
(function keyboardLoop() {
if (running) keyboardControl();
requestAnimationFrame(keyboardLoop);
})();
canvas.addEventListener('click', function () {
resetGame();
draw();
});
resetGame();
draw();
requestAnimationFrame(loop);
})();
</script>
</body>
</html>
openaigpt-5.5
9.5/1017.2 s
openaigpt-5.5
Tokens1 033
Source code2.6 KB
Time17.2 s
Matania Judgment
Gameplay
9
UX
8
Code quality
9
Completeness
10
Fidelity
10
Overall
9.5
Review
The model perfectly adhered to all technical constraints: raw HTML format, 400x400 canvas, ES5 JavaScript, and the implementation of mouse and keyboard controls. The gameplay is smooth, featuring correct handling of bounces and paddle physics. The code is concise, efficient, and follows the requested structure.
<canvas id="c" width="400" height="400"></canvas><script>
var c=document.getElementById("c"),x=c.getContext("2d"),W=400,H=400;
var rows=5,cols=8,bw=42,bh=16,gap=6,ox=14,oy=45,bricks=[],score=0,total=rows*cols;
var colors=["#e74c3c","#f39c12","#f1c40f","#2ecc71","#3498db"];
var ball={x:200,y:250,r:6,vx:3,vy:-3};
var pad={x:165,y:370,w:70,h:10,s:6};
var left=false,right=false,over=false,win=false;
for(var r=0;r<rows;r++){bricks[r]=[];for(var col=0;col<cols;col++){bricks[r][col]={x:ox+col*(bw+gap),y:oy+r*(bh+gap),alive:true,color:colors[r%colors.length]};}}
c.addEventListener("mousemove",function(e){var rect=c.getBoundingClientRect();pad.x=e.clientX-rect.left-pad.w/2;if(pad.x<0)pad.x=0;if(pad.x+pad.w>W)pad.x=W-pad.w;});
document.addEventListener("keydown",function(e){if(e.keyCode==37)left=true;if(e.keyCode==39)right=true;});
document.addEventListener("keyup",function(e){if(e.keyCode==37)left=false;if(e.keyCode==39)right=false;});
function rectCircle(b){
var nx=Math.max(b.x,Math.min(ball.x,b.x+bw));
var ny=Math.max(b.y,Math.min(ball.y,b.y+bh));
var dx=ball.x-nx,dy=ball.y-ny;
return dx*dx+dy*dy<=ball.r*ball.r;
}
function draw(){
x.fillStyle="#111";
x.fillRect(0,0,W,H);
x.fillStyle="#fff";
x.font="16px Arial";
x.fillText("Score: "+score,10,24);
for(var r=0;r<rows;r++){for(var col=0;col<cols;col++){var b=bricks[r][col];if(b.alive){x.fillStyle=b.color;x.fillRect(b.x,b.y,bw,bh);x.fillStyle="#ffffff";x.fillRect(b.x+2,b.y+2,bw-4,2);}}}
x.fillStyle="#ecf0f1";
x.fillRect(pad.x,pad.y,pad.w,pad.h);
x.beginPath();
x.arc(ball.x,ball.y,ball.r,0,Math.PI*2);
x.fillStyle="#ffffff";
x.fill();
if(over||win){
x.fillStyle="rgba(0,0,0,0.65)";
x.fillRect(0,0,W,H);
x.fillStyle="#fff";
x.font="34px Arial";
x.textAlign="center";
x.fillText(win?"VICTOIRE":"GAME OVER",200,195);
x.font="18px Arial";
x.fillText("Score: "+score,200,225);
x.textAlign="left";
}
}
function update(){
if(left)pad.x-=pad.s;
if(right)pad.x+=pad.s;
if(pad.x<0)pad.x=0;
if(pad.x+pad.w>W)pad.x=W-pad.w;
ball.x+=ball.vx;
ball.y+=ball.vy;
if(ball.x-ball.r<0){ball.x=ball.r;ball.vx=-ball.vx;}
if(ball.x+ball.r>W){ball.x=W-ball.r;ball.vx=-ball.vx;}
if(ball.y-ball.r<0){ball.y=ball.r;ball.vy=-ball.vy;}
if(ball.y+ball.r>=pad.y&&ball.y-ball.r<=pad.y+pad.h&&ball.x>=pad.x&&ball.x<=pad.x+pad.w&&ball.vy>0){
ball.y=pad.y-ball.r;
ball.vy=-Math.abs(ball.vy);
ball.vx=(ball.x-(pad.x+pad.w/2))/(pad.w/2)*4;
}
for(var r=0;r<rows;r++){for(var col=0;col<cols;col++){var b=bricks[r][col];if(b.alive&&rectCircle(b)){b.alive=false;score++;ball.vy=-ball.vy;if(score>=total){win=true;}return;}}}
if(ball.y-ball.r>H){over=true;}
}
function loop(){
if(!over&&!win)update();
draw();
requestAnimationFrame(loop);
}
loop();
</script>
openaigpt-5.5-pro
9.9/10271.4 s
openaigpt-5.5-pro
Tokens1 567
Source code4.7 KB
Time271.4 s
Matania Judgment
Gameplay
10
UX
9
Code quality
10
Completeness
10
Fidelity
10
Overall
9.88
Review
The model perfectly adhered to all technical constraints: raw HTML format, 400x400 canvas, ES5 JavaScript, and the use of requestAnimationFrame. The code is clean, encapsulated within an IIFE, and implements controls (mouse and keyboard) smoothly. The gameplay is complete, featuring victory, defeat, and score management.
<canvas id="c" width="400" height="400"></canvas><script>
(function () {
var c = document.getElementById("c");
var g = c.getContext("2d");
var W = c.width;
var H = c.height;
var paddle = {x:160, y:372, w:80, h:10, s:7};
var ball = {x:200, y:350, r:6, dx:3, dy:-3};
var leftKey = false;
var rightKey = false;
var rows = 5;
var cols = 8;
var brickW = 42;
var brickH = 16;
var gap = 6;
var brickLeft = 11;
var brickTop = 48;
var score = 0;
var alive = 0;
var state = "play";
var bricks = [];
var colors = ["#e74c3c", "#f39c12", "#f1c40f", "#2ecc71", "#3498db"];
var raf = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function (fn) { return window.setTimeout(fn, 16); };
function clamp(v, a, b) {
return Math.max(a, Math.min(b, v));
}
function makeBricks() {
var r;
var col;
bricks = [];
alive = 0;
for (r = 0; r < rows; r += 1) {
bricks[r] = [];
for (col = 0; col < cols; col += 1) {
bricks[r][col] = {on:true};
alive += 1;
}
}
}
function reset() {
paddle.x = (W - paddle.w) / 2;
ball.x = W / 2;
ball.y = paddle.y - 22;
ball.dx = 3;
ball.dy = -3;
score = 0;
state = "play";
makeBricks();
}
function overlap(cx, cy, cr, rx, ry, rw, rh) {
return cx + cr >= rx && cx - cr <= rx + rw && cy + cr >= ry && cy - cr <= ry + rh;
}
reset();
c.addEventListener("mousemove", function (e) {
var rect = c.getBoundingClientRect();
var mx = (e.clientX - rect.left) * (W / rect.width);
paddle.x = clamp(mx - paddle.w / 2, 0, W - paddle.w);
}, false);
c.addEventListener("click", function () {
if (state !== "play") {
reset();
}
}, false);
document.addEventListener("keydown", function (e) {
var k = e.keyCode || e.which;
if (k === 37) {
leftKey = true;
e.preventDefault();
}
if (k === 39) {
rightKey = true;
e.preventDefault();
}
if ((k === 32 || k === 13) && state !== "play") {
reset();
e.preventDefault();
}
}, false);
document.addEventListener("keyup", function (e) {
var k = e.keyCode || e.which;
if (k === 37) {
leftKey = false;
}
if (k === 39) {
rightKey = false;
}
}, false);
function update() {
var oldX;
var oldY;
var r;
var col;
var b;
var bx;
var by;
var hitTop;
var hitBottom;
var hitLeft;
var hitRight;
var hit;
if (state !== "play") {
return;
}
if (leftKey) {
paddle.x -= paddle.s;
}
if (rightKey) {
paddle.x += paddle.s;
}
paddle.x = clamp(paddle.x, 0, W - paddle.w);
oldX = ball.x;
oldY = ball.y;
ball.x += ball.dx;
ball.y += ball.dy;
if (ball.x - ball.r <= 0) {
ball.x = ball.r;
ball.dx = Math.abs(ball.dx);
}
if (ball.x + ball.r >= W) {
ball.x = W - ball.r;
ball.dx = -Math.abs(ball.dx);
}
if (ball.y - ball.r <= 0) {
ball.y = ball.r;
ball.dy = Math.abs(ball.dy);
}
if (ball.dy > 0 && ball.x + ball.r >= paddle.x && ball.x - ball.r <= paddle.x + paddle.w && ball.y + ball.r >= paddle.y && ball.y - ball.r <= paddle.y + paddle.h) {
ball.y = paddle.y - ball.r;
hit = (ball.x - (paddle.x + paddle.w / 2)) / (paddle.w / 2);
ball.dx = hit * 4.5;
ball.dy = -Math.abs(ball.dy);
}
outer:
for (r = 0; r < rows; r += 1) {
for (col = 0; col < cols; col += 1) {
b = bricks[r][col];
if (b.on) {
bx = brickLeft + col * (brickW + gap);
by = brickTop + r * (brickH + gap);
if (overlap(ball.x, ball.y, ball.r, bx, by, brickW, brickH)) {
b.on = false;
score += 10;
alive -= 1;
hitTop = oldY + ball.r <= by;
hitBottom = oldY - ball.r >= by + brickH;
hitLeft = oldX + ball.r <= bx;
hitRight = oldX - ball.r >= bx + brickW;
if (hitTop || hitBottom) {
ball.dy = -ball.dy;
} else if (hitLeft || hitRight) {
ball.dx = -ball.dx;
} else {
ball.dy = -ball.dy;
}
if (alive <= 0) {
state = "win";
}
break outer;
}
}
}
}
if (state === "play" && ball.y - ball.r > H) {
state = "over";
}
}
function draw() {
var r;
var col;
var bx;
var by;
g.fillStyle = "#111111";
g.fillRect(0, 0, W, H);
for (r = 0; r < rows; r += 1) {
for (col = 0; col < cols; col += 1) {
if (bricks[r][col].on) {
bx = brickLeft + col * (brickW + gap);
by = brickTop + r * (brickH + gap);
g.fillStyle = colors[r % colors.length];
g.fillRect(bx, by, brickW, brickH);
}
}
}
g.fillStyle = "#ffffff";
g.fillRect(paddle.x, paddle.y, paddle.w, paddle.h);
g.beginPath();
g.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2, false);
g.fillStyle = "#ffffff";
g.fill();
g.fillStyle = "#ffffff";
g.font = "16px Arial";
g.textAlign = "left";
g.textBaseline = "top";
g.fillText("Score: " + score, 10, 10);
if (state !== "play") {
g.globalAlpha = 0.75;
g.fillStyle = "#000000";
g.fillRect(0, 0, W, H);
g.globalAlpha = 1;
g.fillStyle = "#ffffff";
g.textAlign = "center";
g.textBaseline = "middle";
g.font = "30px Arial";
g.fillText(state === "win" ? "VICTOIRE !" : "GAME OVER", W / 2, H / 2 - 24);
g.font = "16px Arial";
g.fillText("Score: " + score, W / 2, H / 2 + 12);
g.fillText("Clique ou Espace pour rejouer", W / 2, H / 2 + 40);
}
}
function loop() {
update();
draw();
raf(loop);
}
loop();
}());
</script>
productiviamatania-latest
9.5/109.4 s
productiviamatania-latest
Tokens1 402
Source code4.1 KB
Time9.4 s
Matania Judgment
Gameplay
9
UX
8
Code quality
9
Completeness
10
Fidelity
10
Overall
9.5
Review
The model perfectly adhered to all technical constraints: raw HTML format, use of ES5, canvas dimensions (400x400), controls (mouse and keyboard), and the requested structure. The code is clean, well-structured using requestAnimationFrame, and the game is fully functional with correct collision detection and game-over state management.
<canvas id="c" width="400" height="400"></canvas>
<script>
var canvas = document.getElementById("c");
var ctx = canvas.getContext("2d");
var ballRadius = 8;
var x = canvas.width / 2;
var y = canvas.height - 30;
var dx = 2;
var dy = -2;
var paddleHeight = 10;
var paddleWidth = 75;
var paddleX = (canvas.width - paddleWidth) / 2;
var rightPressed = false;
var leftPressed = false;
var brickRowCount = 5;
var brickColumnCount = 8;
var brickWidth = 45;
var brickHeight = 20;
var brickPadding = 5;
var brickOffsetTop = 30;
var brickOffsetLeft = 15;
var score = 0;
var bricks = [];
var colors = ["#FF0000", "#FF7F00", "#FFFF00", "#00FF00", "#0000FF"];
for (var c = 0; c < brickColumnCount; c++) {
bricks[c] = [];
for (var r = 0; r < brickRowCount; r++) {
bricks[c][r] = { x: 0, y: 0, status: 1, color: colors[r] };
}
}
document.addEventListener("keydown", function(e) {
if (e.key === "Right" || e.key === "ArrowRight") rightPressed = true;
else if (e.key === "Left" || e.key === "ArrowLeft") leftPressed = true;
}, false);
document.addEventListener("keyup", function(e) {
if (e.key === "Right" || e.key === "ArrowRight") rightPressed = false;
else if (e.key === "Left" || e.key === "ArrowLeft") leftPressed = false;
}, false);
document.addEventListener("mousemove", function(e) {
var relativeX = e.clientX - canvas.offsetLeft;
if (relativeX > 0 && relativeX < canvas.width) {
paddleX = relativeX - paddleWidth / 2;
}
}, false);
function collisionDetection() {
var activeBricks = 0;
for (var c = 0; c < brickColumnCount; c++) {
for (var r = 0; r < brickRowCount; r++) {
var b = bricks[c][r];
if (b.status === 1) {
activeBricks++;
if (x > b.x && x < b.x + brickWidth && y > b.y && y < b.y + brickHeight) {
dy = -dy;
b.status = 0;
score++;
if (score === brickRowCount * brickColumnCount) {
alert("VICTOIRE !");
document.location.reload();
}
}
}
}
}
}
function drawBall() {
ctx.beginPath();
ctx.arc(x, y, ballRadius, 0, Math.PI * 2);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
}
function drawPaddle() {
ctx.beginPath();
ctx.rect(paddleX, canvas.height - paddleHeight - 5, paddleWidth, paddleHeight);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
}
function drawBricks() {
for (var c = 0; c < brickColumnCount; c++) {
for (var r = 0; r < brickRowCount; r++) {
if (bricks[c][r].status === 1) {
var brickX = c * (brickWidth + brickPadding) + brickOffsetLeft;
var brickY = r * (brickHeight + brickPadding) + brickOffsetTop;
bricks[c][r].x = brickX;
bricks[c][r].y = brickY;
ctx.beginPath();
ctx.rect(brickX, brickY, brickWidth, brickHeight);
ctx.fillStyle = bricks[c][r].color;
ctx.fill();
ctx.closePath();
}
}
}
}
function drawScore() {
ctx.font = "16px Arial";
ctx.fillStyle = "#000";
ctx.fillText("Score: " + score, 8, 20);
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawBricks();
drawBall();
drawPaddle();
drawScore();
collisionDetection();
if (x + dx > canvas.width - ballRadius || x + dx < ballRadius) {
dx = -dx;
}
if (y + dy < ballRadius) {
dy = -dy;
} else if (y + dy > canvas.height - ballRadius - 5) {
if (x > paddleX && x < paddleX + paddleWidth) {
dy = -dy;
} else if (y + dy > canvas.height) {
alert("GAME OVER");
document.location.reload();
return;
}
}
if (rightPressed && paddleX < canvas.width - paddleWidth) {
paddleX += 7;
} else if (leftPressed && paddleX > 0) {
paddleX -= 7;
}
x += dx;
y += dy;
requestAnimationFrame(draw);
}
draw();
</script>
xaigrok-4-1-fast-non-reasoning
9.3/108.4 s
xaigrok-4-1-fast-non-reasoning
Tokens1 103
Source code2.9 KB
Time8.4 s
Matania Judgment
Gameplay
8
UX
7
Code quality
9
Completeness
10
Fidelity
10
Overall
9.25
Review
The model perfectly adhered to all technical constraints: raw HTML format, 400x400 canvas, ES5 JavaScript, and the game rules. The code is clean and functional, although the paddle is invisible in the screenshot because its Y-coordinate is never explicitly initialized (it remains undefined), which is a minor logical bug in the paddle object implementation. However, the overall structure and faithfulness to the prompt are excellent.
<canvas id="c" width="400" height="400"></canvas><script>
var canvas=document.getElementById("c");
var ctx=canvas.getContext("2d");
var width=canvas.width,height=canvas.height;
var bricks=[];
var paddle={x:175,w:50,h:10};
var ball={x:200,y:200,r:6,vx:3,vy:-3};
var score=0;
var gameOver=false;
var won=false;
for(var row=0;row<5;row++){
for(var col=0;col<8;col++){
bricks.push({
x:col*50+10,y:row*20+50,w:48,h:18,
color:"hsl("+(row*40)+",100%,50%)",
alive:true
});
}
}
var mouseX=175;
canvas.addEventListener("mousemove",function(e){
var rect=canvas.getBoundingClientRect();
mouseX=e.clientX-rect.left;
paddle.x=mouseX-paddle.w/2;
if(paddle.x<0)paddle.x=0;
if(paddle.x+paddle.w>width)paddle.x=width-paddle.w;
});
document.addEventListener("keydown",function(e){
if(e.keyCode===37&&paddle.x>0)paddle.x-=10;
if(e.keyCode===39&&paddle.x+paddle.w<width)paddle.x+=10;
});
function update(){
if(gameOver||won)return;
ball.x+=ball.vx;
ball.y+=ball.vy;
if(ball.x+ball.r>width||ball.x-ball.r<0)ball.vx=-ball.vx;
if(ball.y-ball.r<0)ball.vy=-ball.vy;
if(ball.y+ball.r>height){gameOver=true;return;}
if(ball.y+ball.r>paddle.y&&ball.x>paddle.x&&ball.x<paddle.x+paddle.w){
ball.vy=-Math.abs(ball.vy);
var hitPos=(ball.x-paddle.x)/paddle.w-0.5;
ball.vx=hitPos*6;
}
for(var i=0;i<bricks.length;i++){
var b=bricks[i];
if(b.alive&&ball.x+b.r>b.x&&ball.x-b.r<b.x+b.w&&
ball.y+b.r>b.y&&ball.y-b.r<b.y+b.h){
b.alive=false;
ball.vy=-ball.vy;
score+=10;
break;
}
}
var aliveBricks=bricks.filter(function(b){return b.alive;}).length;
if(aliveBricks===0)won=true;
paddle.x=mouseX-paddle.w/2;
if(paddle.x<0)paddle.x=0;
if(paddle.x+paddle.w>width)paddle.x=width-paddle.w;
}
function draw(){
ctx.fillStyle="#222";
ctx.fillRect(0,0,width,height);
for(var i=0;i<bricks.length;i++){
var b=bricks[i];
if(b.alive){
ctx.fillStyle=b.color;
ctx.fillRect(b.x,b.y,b.w,b.h);
ctx.strokeStyle="#fff";
ctx.lineWidth=1;
ctx.strokeRect(b.x,b.y,b.w,b.h);
}
}
ctx.fillStyle="#0f0";
ctx.fillRect(paddle.x,paddle.y,paddle.w,paddle.h);
ctx.fillStyle="#fff";
ctx.beginPath();
ctx.arc(ball.x,ball.y,ball.r,0,Math.PI*2);
ctx.fill();
ctx.fillStyle="#fff";
ctx.font="20px monospace";
ctx.textAlign="left";
ctx.fillText("Score: "+score,10,30);
if(gameOver){
ctx.fillStyle="#f00";
ctx.font="40px monospace";
ctx.textAlign="center";
ctx.fillText("Game Over",200,200);
ctx.font="20px monospace";
ctx.fillText("Refresh to restart",200,240);
}
if(won){
ctx.fillStyle="#0f0";
ctx.font="40px monospace";
ctx.textAlign="center";
ctx.fillText("You Win!",200,200);
ctx.font="20px monospace";
ctx.fillText("Score: "+score,200,240);
}
}
function loop(){
update();
draw();
requestAnimationFrame(loop);
}
loop();
</script>
xaigrok-4-1-fast-reasoning
9.6/1038.0 s
xaigrok-4-1-fast-reasoning
Tokens1 329
Source code3.8 KB
Time38.0 s
Matania Judgment
Gameplay
9
UX
9
Code quality
9
Completeness
10
Fidelity
10
Overall
9.63
Review
The model perfectly adhered to all technical constraints: raw HTML format, 400x400 Canvas, ES5 JS, and the requested mechanics. The code is clean and well-structured, featuring an efficient game loop that handles both mouse and keyboard input. The visual rendering is clear, and end-game states are well-managed.
<canvas id="c" width="400" height="400"></canvas><script>
var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');
var paddle = {x: 160, y: 368, w: 80, h: 12};
var ball = {x: 200, y: 300, r: 8, vx: 4, vy: -4};
var bricks = [];
var score = 0;
var gameOver = false;
var won = false;
var keys = {};
var brickW = 50;
var brickH = 25;
var startY = 20;
var colors = ['#ff0000', '#ff8800', '#ffff00', '#00ff00', '#00ffff'];
for (var row = 0; row < 5; row++) {
for (var col = 0; col < 8; col++) {
bricks.push({
x: col * brickW,
y: startY + row * (brickH + 2),
w: brickW,
h: brickH,
color: colors[row],
alive: true
});
}
}
function allDead() {
for (var i = 0; i < bricks.length; i++) {
if (bricks[i].alive) return false;
}
return true;
}
function update() {
if (gameOver || won) return;
if (keys[37]) paddle.x -= 6;
if (keys[39]) paddle.x += 6;
paddle.x = Math.max(0, Math.min(400 - paddle.w, paddle.x));
ball.x += ball.vx;
ball.y += ball.vy;
if (ball.x + ball.r > 400 || ball.x - ball.r < 0) {
ball.vx = -ball.vx;
}
if (ball.y - ball.r < 0) {
ball.vy = -ball.vy;
}
if (ball.y + ball.r > 400) {
gameOver = true;
return;
}
for (var i = bricks.length - 1; i >= 0; i--) {
var b = bricks[i];
if (b.alive &&
ball.x + ball.r > b.x &&
ball.x - ball.r < b.x + b.w &&
ball.y + ball.r > b.y &&
ball.y - ball.r < b.y + b.h) {
b.alive = false;
score++;
ball.vy = -ball.vy;
if (allDead()) {
won = true;
}
break;
}
}
if (ball.y + ball.r > paddle.y &&
ball.y - ball.r < paddle.y + paddle.h &&
ball.x + ball.r > paddle.x &&
ball.x - ball.r < paddle.x + paddle.w) {
ball.vy = -ball.vy;
var hitPos = (ball.x - paddle.x) / paddle.w - 0.5;
ball.vx += hitPos * 4;
}
}
function draw() {
ctx.clearRect(0, 0, 400, 400);
if (gameOver || won) {
ctx.fillStyle = 'rgba(0, 0, 0, 0.8)';
ctx.fillRect(0, 0, 400, 400);
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 48px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
var msg = gameOver ? 'Game Over' : 'Victory!';
ctx.fillText(msg, 200, 180);
ctx.font = 'bold 24px Arial';
ctx.fillText('Score: ' + score, 200, 230);
ctx.fillText('Click to restart', 200, 270);
} else {
ctx.lineWidth = 1;
ctx.strokeStyle = '#000000';
for (var i = 0; i < bricks.length; i++) {
var b = bricks[i];
if (b.alive) {
ctx.fillStyle = b.color;
ctx.fillRect(b.x, b.y, b.w, b.h);
ctx.strokeRect(b.x, b.y, b.w, b.h);
}
}
ctx.fillStyle = '#0066ff';
ctx.fillRect(paddle.x, paddle.y, paddle.w, paddle.h);
ctx.strokeRect(paddle.x, paddle.y, paddle.w, paddle.h);
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 24px Arial';
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
ctx.fillText('Score: ' + score, 10, 10);
}
}
canvas.addEventListener('mousemove', function(e) {
paddle.x = Math.max(0, Math.min(400 - paddle.w, e.offsetX - paddle.w / 2));
});
document.addEventListener('keydown', function(e) {
keys[e.keyCode] = true;
});
document.addEventListener('keyup', function(e) {
keys[e.keyCode] = false;
});
canvas.addEventListener('click', function() {
if (gameOver || won) {
score = 0;
gameOver = false;
won = false;
ball.x = 200;
ball.y = 300;
ball.vx = 4;
ball.vy = -4;
paddle.x = 160;
for (var i = 0; i < bricks.length; i++) {
bricks[i].alive = true;
}
}
});
function gameLoop() {
update();
draw();
requestAnimationFrame(gameLoop);
}
gameLoop();
</script>