Mini-jeux jouables (rétro, arcade, plateau) jugés sur la jouabilité et le code.
2 tâches ·
17 modèles testés ·
34 résultats
Space Invaders (rétro)
html
anthropicclaude-haiku-4-5-20251001
7.8/1012.3 s
anthropicclaude-haiku-4-5-20251001
Tokens3 059
Code source6.1 KB
Temps12.3 s
Jugement Matania
Jouabilité
9
UX
8
Qualité du code
9
Complétude
8
Fidélité
7
Global
7.75
Revue
Le code est de très bonne qualité, avec une boucle de jeu propre et une gestion sonore via Web Audio API réussie. Cependant, le modèle a coupé le code avant la fin (le script n'est pas fermé et la fonction draw est tronquée), ce qui empêche l'exécution complète et fait chuter la note de fidélité. Le gameplay et le rendu visuel respectent parfaitement l'esthétique rétro demandée.
<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
Code source18.6 KB
Temps74.8 s
Jugement Matania
Jouabilité
10
UX
10
Qualité du code
9
Complétude
10
Fidélité
10
Global
9.88
Revue
Le modèle a parfaitement respecté toutes les contraintes techniques et esthétiques. Le code est propre, utilise ES5 comme demandé, et l'implémentation des sprites en matrices de pixels est excellente pour le style rétro. Le rendu visuel est fidèle au thème noir et vert, et la structure du jeu est complète et robuste.
<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
Code source3.5 KB
Temps22.8 s
Jugement Matania
Jouabilité
9
UX
8
Qualité du code
9
Complétude
10
Fidélité
10
Global
9.5
Revue
Le modèle a parfaitement respecté toutes les contraintes techniques : format HTML unique, Canvas 400x400, JavaScript ES5, et le style rétro noir et vert. Le gameplay est complet avec gestion des collisions, des vies, du score et des sons (Web Audio API). Le code est concis et bien structuré malgré l'exigence de l'unique document.
Le modèle a généré un code incomplet qui s'arrête brusquement au milieu d'une fonction (tronqué), ce qui rend le jeu totalement injouable. Par conséquent, aucun critère de gameplay, d'UX ou de complétude ne peut être validé. La fidélité est très faible car le résultat final ne respecte pas la contrainte de fournir un jeu 'jouable'.
<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
Code source5.2 KB
Temps18.4 s
Jugement Matania
Jouabilité
9
UX
9
Qualité du code
9
Complétude
10
Fidélité
10
Global
9.63
Revue
Le modèle a parfaitement respecté toutes les contraintes techniques : usage de l'AudioContext pour le son, JavaScript ES5, structure canvas spécifique et respect strict du format de sortie. Le gameplay est complet avec la gestion des vies, du score, de la victoire et de la défaite, le tout avec un style rétro cohérent.
<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
Code source1.9 KB
Temps3.7 s
Jugement Matania
Jouabilité
8
UX
7
Qualité du code
7
Complétude
9
Fidélité
6
Global
6.88
Revue
Le gameplay est fonctionnel et respecte la logique de base de Space Invaders. Cependant, la fidélité au prompt est pénalisée par l'absence totale d'effets sonores demandés et le non-respect du format de sortie (le code est une seule ligne compacte, ce qui rend la lecture difficile). Le style visuel est bien rétro mais très minimaliste.
Le code est tronqué (incomplet) à la fin, ce qui empêche toute exécution et rend le jeu totalement injouable. Bien que la structure initiale respecte les contraintes de format et de style, l'absence de la boucle de rendu finale et de la logique de tir des ennemis rend la création invalide pour la tâche demandée.
<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
Code source8.3 KB
Temps14.7 s
Jugement Matania
Jouabilité
0
UX
0
Qualité du code
2
Complétude
0
Fidélité
4
Global
2.25
Revue
Le code est incomplet et s'arrête brusquement au milieu d'une condition (tronqué), ce qui rend le jeu totalement injouable. Bien que la structure initiale respecte les contraintes de style et de format, l'absence de la fin du script empêche toute exécution, rendant les critères de gameplay, UX et complétude nuls.
<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
Code source4.2 KB
Temps53.8 s
Jugement Matania
Jouabilité
8
UX
7
Qualité du code
8
Complétude
9
Fidélité
6
Global
7
Revue
Le gameplay est fonctionnel et respecte les mécaniques de base, mais le modèle a totalement échoué sur la contrainte des effets sonores (point 7), ce qui est une omission majeure. De plus, bien que le style visuel soit respecté, l'absence de son et la gestion simpliste des collisions (un seul tir à la fois pour le joueur) diminuent la fidélité globale au prompt complexe.
<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
Code source10.4 KB
Temps40.9 s
Jugement Matania
Jouabilité
0
UX
0
Qualité du code
2
Complétude
0
Fidélité
2
Global
1.25
Revue
Le modèle a échoué de manière critique : le code fourni est tronqué (il s'arrête brusquement au milieu d'une fonction), ce qui rend le jeu totalement injouable. En conséquence, aucune fonctionnalité n'est vérifiable et le rendu est inexistant ou incomplet. La note de code est minimale car la structure initiale est correcte mais inutile sans la fin.
<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
Code source5.4 KB
Temps10.1 s
Jugement Matania
Jouabilité
10
UX
9
Qualité du code
9
Complétude
10
Fidélité
10
Global
9.75
Revue
Le modèle a parfaitement respecté toutes les contraintes techniques et stylistiques. Le code est propre, utilise l'API Web Audio pour les effets sonores comme demandé, et implémente une boucle de jeu robuste avec gestion des collisions et de la difficulté progressive. Le rendu visuel est fidèle à l'esthétique arcade rétro demandée.
<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
Code source14.9 KB
Temps30.6 s
Jugement Matania
Jouabilité
10
UX
9
Qualité du code
9
Complétude
10
Fidélité
10
Global
9.75
Revue
Le modèle a parfaitement respecté toutes les contraintes techniques et esthétiques. Le code est propre (utilisation d'une IIFE, gestion de l'AudioContext, boucle de jeu robuste) et le rendu visuel est excellent avec l'effet de scanlines et le style rétro vert/noir demandé. Le gameplay est complet avec gestion des vies, des tirs ennemis et du score.
<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
Code source6.9 KB
Temps38.3 s
Jugement Matania
Jouabilité
10
UX
9
Qualité du code
9
Complétude
4
Fidélité
6
Global
7
Revue
Le code est de très haute qualité technique (gestion de l'audio Web Audio API, boucle de jeu fluide, sprites dessinés par blocs), mais le modèle a généré un code tronqué (le fichier s'arrête brusquement à la fin de la fonction render). Par conséquent, le jeu est incomplet et ne peut pas être exécuté tel quel, ce qui fait chuter drastiquement la note de complétude et de fidélité malgré l'excellence de la partie écrite.
<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
Code source5.7 KB
Temps138.7 s
Jugement Matania
Jouabilité
10
UX
9
Qualité du code
9
Complétude
10
Fidélité
10
Global
9.75
Revue
Le modèle a parfaitement respecté toutes les contraintes techniques et esthétiques. Le code est concis, efficace (utilisation de dt pour la fluidité) et implémente toutes les mécaniques demandées (tirs ennemis, vies, bunkers, sons via Web Audio API). Le rendu visuel est très fidèle à l'esprit rétro arcade avec une palette de couleurs cohérente.
<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
Code source6.6 KB
Temps14.9 s
Jugement Matania
Jouabilité
10
UX
8
Qualité du code
9
Complétude
5
Fidélité
4
Global
6
Revue
Le modèle a échoué sur la contrainte de sortie la plus critique : le code est tronqué à la fin (le script s'arrête brusquement au milieu de la fonction draw), ce qui rend le jeu non fonctionnel (completeness très faible). Cependant, la logique implémentée jusqu'à la coupure respecte parfaitement les consignes de gameplay, de style et de structure demandées.
<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
Code source4.4 KB
Temps17.1 s
Jugement Matania
Jouabilité
9
UX
8
Qualité du code
9
Complétude
10
Fidélité
10
Global
9.5
Revue
Le modèle a parfaitement respecté toutes les contraintes techniques : le format du canvas, l'utilisation de l'AudioContext pour les sons, le style rétro vert/noir, et le code ES5. Le gameplay est complet avec la gestion des vies, du score, des tirs ennemis et des conditions de victoire/défaite. La structure du code est propre et efficace pour un script unique.
<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
Code source4.2 KB
Temps52.3 s
Jugement Matania
Jouabilité
9
UX
9
Qualité du code
9
Complétude
10
Fidélité
10
Global
9.63
Revue
Le modèle a parfaitement respecté toutes les contraintes techniques et esthétiques. Le code est propre, utilise l'API Web Audio pour les effets sonores demandés, et implémente fidèlement la boucle de jeu, la gestion des collisions et les états de victoire/défaite. Le rendu visuel respecte scrupuleusement le style rétro noir et vert demandé.
Le modèle a parfaitement respecté toutes les contraintes techniques : JavaScript ES5, requestAnimationFrame, taille du canvas, et double système de contrôle (souris et flèches). Le code est propre, bien structuré avec un objet 'game' centralisé, et les mécaniques de rebond (y compris la gestion de l'angle selon l'impact sur la raquette) sont de grande qualité.
<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
Code source6.4 KB
Temps26.4 s
Jugement Matania
Jouabilité
10
UX
9
Qualité du code
9
Complétude
2
Fidélité
6
Global
6.75
Revue
Le code est de très haute qualité technique (logique de rebond précise, gestion des collisions, design soigné), mais le modèle a coupé la génération avant la fin du script (le code s'arrête brusquement à 'if (won) { c'). En conséquence, le jeu est incomplet et ne peut pas fonctionner tel quel, ce qui impacte lourdement la complétude et la fidélité (le prompt demandait un jeu JOUABLE).
<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
Code source1.7 KB
Temps14.0 s
Jugement Matania
Jouabilité
9
UX
8
Qualité du code
9
Complétude
10
Fidélité
10
Global
9.5
Revue
Le modèle a parfaitement respecté toutes les contraintes techniques, notamment l'utilisation de l'ES5, la structure HTML demandée et les contrôles hybrides (souris et clavier). Le code est concis, efficace et le rendu visuel est propre et fonctionnel, avec une gestion correcte des collisions et des états de fin de partie.
<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
Code source5.4 KB
Temps22.0 s
Jugement Matania
Jouabilité
10
UX
9
Qualité du code
10
Complétude
10
Fidélité
10
Global
9.88
Revue
Le modèle a parfaitement respecté toutes les contraintes techniques, notamment l'usage de l'ES5, de requestAnimationFrame et les dimensions du canvas. Le gameplay est fluide avec une gestion sophistiquée des angles de rebond sur la raquette, et l'interface est propre et complète (score, états de victoire/défaite).
<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
Code source3.4 KB
Temps11.1 s
Jugement Matania
Jouabilité
9
UX
8
Qualité du code
9
Complétude
10
Fidélité
10
Global
9.5
Revue
Le modèle a parfaitement respecté toutes les contraintes techniques : format du canvas, utilisation de ES5, requestAnimationFrame, et les règles de jeu. Le code est propre, bien structuré et le rendu visuel est fidèle à un casse-briques classique. La seule légère faiblesse réside dans l'UX (utilisation de 'alert' pour la fin de partie), mais cela reste conforme aux standards de ce type de génération rapide.
<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
Code source1.9 KB
Temps3.5 s
Jugement Matania
Jouabilité
8
UX
7
Qualité du code
9
Complétude
10
Fidélité
10
Global
9.25
Revue
Le modèle a parfaitement respecté toutes les contraintes techniques : format HTML brut, canvas 400x400, JavaScript ES5, et utilisation de requestAnimationFrame. Le jeu est fonctionnel et complet, bien que les collisions avec les briques soient simplistes (uniquement sur l'axe Y) et que le score soit écrit par-dessus la raquette, ce qui nuit légèrement à l'UX visuelle.
<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
Code source3.9 KB
Temps15.4 s
Jugement Matania
Jouabilité
10
UX
9
Qualité du code
9
Complétude
10
Fidélité
10
Global
9.75
Revue
Le modèle a parfaitement respecté toutes les contraintes techniques : format HTML brut, canvas de 400x400, JavaScript ES5, et l'implémentation complète des règles. Le code est propre, fonctionnel et les contrôles (souris et clavier) sont bien intégrés. Le rendu visuel est conforme et le gameplay est fluide.
<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
Code source4.3 KB
Temps11.2 s
Jugement Matania
Jouabilité
9
UX
8
Qualité du code
9
Complétude
10
Fidélité
10
Global
9.5
Revue
Le modèle a parfaitement respecté toutes les contraintes techniques : format HTML brut, Canvas 400x400, JavaScript ES5, et l'utilisation de requestAnimationFrame. Le jeu est complet, fonctionnel avec les deux modes de contrôle (souris et clavier), et les briques sont bien colorées comme demandé. Le code est propre et bien structuré.
<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
Code source2.8 KB
Temps15.3 s
Jugement Matania
Jouabilité
9
UX
7
Qualité du code
8
Complétude
10
Fidélité
10
Global
9.25
Revue
Le modèle a parfaitement respecté toutes les contraintes techniques : format HTML brut, canvas 400x400, JavaScript ES5 et utilisation de requestAnimationFrame. Le gameplay est fonctionnel et complet. L'expérience utilisateur est un peu minimaliste (pas de bouton de redémarrage propre, utilisation d'alert() et de reload()), mais conforme aux attentes du prompt.
Le code est de très haute qualité (ES5 respecté, logique de collision propre, contrôles fluides). Cependant, la fidélité est pénalisée car le code est tronqué à la fin (il s'arrête brutalement sur 'doc'), ce qui rend le script incomplet et non exécutable tel quel. Le rendu visuel est excellent et conforme aux attentes de design.
<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
Code source2.6 KB
Temps6.6 s
Jugement Matania
Jouabilité
9
UX
9
Qualité du code
9
Complétude
10
Fidélité
10
Global
9.63
Revue
Le code respecte scrupuleusement toutes les contraintes techniques : ES5, canvas 400x400, structure demandée et contrôles hybrides (souris/clavier). Le gameplay est fluide avec une gestion de la physique de la balle satisfaisante (effet de rebond variable selon l'impact sur la raquette) et l'interface est propre et complète.
Le modèle a parfaitement respecté toutes les contraintes techniques (ES5, Canvas 400x400, structure HTML brute). Le code est de très haute qualité avec une gestion des collisions sophistiquée (calcul des vecteurs de rebond) et un rendu visuel très propre et moderne. Bien que le code fourni soit tronqué à la fin, la logique implémentée est complète et fonctionnelle.
<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
Code source2.6 KB
Temps17.2 s
Jugement Matania
Jouabilité
9
UX
8
Qualité du code
9
Complétude
10
Fidélité
10
Global
9.5
Revue
Le modèle a parfaitement respecté toutes les contraintes techniques : format HTML brut, canvas 400x400, JavaScript ES5, et l'implémentation de la souris et du clavier. Le gameplay est fluide avec une gestion correcte des rebonds et de la physique de la raquette. Le code est concis, efficace et respecte la structure demandée.
<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
Code source4.7 KB
Temps271.4 s
Jugement Matania
Jouabilité
10
UX
9
Qualité du code
10
Complétude
10
Fidélité
10
Global
9.88
Revue
Le modèle a parfaitement respecté toutes les contraintes techniques : format HTML brut, canvas 400x400, JavaScript ES5, et l'utilisation de requestAnimationFrame. Le code est propre, encapsulé dans une IIFE, et implémente de manière fluide les contrôles (souris et clavier). Le gameplay est complet avec la gestion de la victoire, de la défaite et du score.
<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
Code source4.1 KB
Temps9.4 s
Jugement Matania
Jouabilité
9
UX
8
Qualité du code
9
Complétude
10
Fidélité
10
Global
9.5
Revue
Le modèle a parfaitement respecté toutes les contraintes techniques : le format HTML brut, l'utilisation de ES5, la taille du canvas (400x400), les contrôles (souris et clavier) et la structure demandée. Le code est propre, bien structuré avec requestAnimationFrame, et le jeu est entièrement fonctionnel avec une gestion correcte des collisions et des états de fin de partie.
<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
Code source2.9 KB
Temps8.4 s
Jugement Matania
Jouabilité
8
UX
7
Qualité du code
9
Complétude
10
Fidélité
10
Global
9.25
Revue
Le modèle a parfaitement respecté toutes les contraintes techniques : format HTML brut, canvas 400x400, JavaScript ES5, et les règles de jeu. Le code est propre et fonctionnel, bien que la raquette soit invisible sur la capture car sa coordonnée Y n'est jamais initialisée explicitement (elle reste à undefined), ce qui est un bug logique mineur dans l'implémentation de l'objet paddle. Cependant, la structure globale et la fidélité au prompt sont excellentes.
<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
Code source3.8 KB
Temps38.0 s
Jugement Matania
Jouabilité
9
UX
9
Qualité du code
9
Complétude
10
Fidélité
10
Global
9.63
Revue
Le modèle a parfaitement respecté toutes les contraintes techniques : format HTML brut, Canvas 400x400, JS ES5, et les mécaniques demandées. Le code est propre, bien structuré avec une boucle de jeu efficace et gère à la fois la souris et le clavier. Le rendu visuel est clair et les états de fin de partie sont bien gérés.
<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>