ORIGINAL SOURCE / MODEL 02
Gemma 4 31B — 生成コード原文
生成された index.html の内容をそのままテキスト表示しています。このページではコードを実行しません。ゲームの評価・修正は行っていません。
ゲームを開く際は音量にご注意ください。動作を保証するものではありません。
index.htmlORIGINAL / UNEDITED
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Block Breaker</title>
<style>
body {
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #222;
color: white;
font-family: 'Arial', sans-serif;
overflow: hidden;
}
canvas {
border: 4px solid #fff;
background-color: #000;
box-shadow: 0 0 20px rgba(255,255,255,0.2);
cursor: none;
}
</style>
</head>
<body>
<canvas id="gameCanvas"></canvas>
<script>
/**
* Block Breaker - Single File Edition
*/
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
// --- 設定 ---
const CANVAS_WIDTH = 480;
const CANVAS_HEIGHT = 600;
canvas.width = CANVAS_WIDTH;
canvas.height = CANVAS_HEIGHT;
const PADDLE_WIDTH = 80;
const PADDLE_HEIGHT = 12;
const BALL_RADIUS = 6;
const BRICK_ROWS = 5;
const BRICK_COLS = 8;
const BRICK_PADDING = 10;
const BRICK_OFFSET_TOP = 50;
const BRICK_OFFSET_LEFT = 30;
const INITIAL_LIVES = 5;
const COLORS = ['#FF4D4D', '#FFB347', '#FFFF66', '#66FF66', '#4D94FF'];
// ゲーム状態
let gameState = 'OPENING'; // OPENING, PLAYING, GAMEOVER
let score = 0;
let lives = INITIAL_LIVES;
// オブジェクト
const paddle = {
x: (CANVAS_WIDTH - PADDLE_WIDTH) / 2,
y: CANVAS_HEIGHT - 30,
width: PADDLE_WIDTH,
height: PADDLE_HEIGHT
};
const ball = {
x: canvas.width / 2,
y: canvas.height - 40,
dx: 3,
dy: -3,
radius: BALL_RADIUS
};
let bricks = [];
// 入力制御
let rightPressed = false;
let leftPressed = false;
// --- オーディオエンジン (Web Audio API) ---
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
function playTone(freq, duration, type = 'sine', volume = 0.1) {
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = type;
osc.frequency.setValueAtTime(freq, audioCtx.currentTime);
gain.gain.setValueAtTime(volume, 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 playSound(type) {
switch(type) {
case 'paddle': playTone(200, 0.1, 'square', 0.05); break;
case 'brick': playTone(400, 0.1, 'sine', 0.1); break;
case 'wall': playTone(150, 0.1, 'sine', 0.05); break;
case 'lose': playTone(100, 0.3, 'sawtooth', 0.1); break;
case 'win': playTone(600, 0.4, 'triangle', 0.1); break;
}
}
// BGM シンプルなループ
let bgmInterval = null;
function startBGM() {
if (bgmInterval) return;
const notes = [261.63, 329.63, 392.00, 523.25]; // C E G C
let step = 0;
bgmInterval = setInterval(() => {
if (gameState === 'PLAYING') {
playTone(notes[step % notes.length], 0.5, 'triangle', 0.02);
step++;
}
}, 500);
}
function stopBGM() {
clearInterval(bgmInterval);
bgmInterval = null;
}
// --- 初期化 ---
function initBricks() {
bricks = [];
for (let c = 0; c < BRICK_COLS; c++) {
bricks[c] = [];
for (let r = 0; r < BRICK_ROWS; r++) {
bricks[c][r] = { x: 0, y: 0, status: 1 };
}
}
}
function resetBall() {
ball.x = canvas.width / 2;
ball.y = paddle.y - ball.radius;
ball.dx = 3 * (Math.random() > 0.5 ? 1 : -1);
ball.dy = -3;
}
function resetGame() {
score = 0;
lives = INITIAL_LIVES;
initBricks();
resetBall();
}
// --- イベントリスナー ---
document.addEventListener('keydown', (e) => {
if (e.key === 'Right' || e.key === 'ArrowRight') rightPressed = true;
else if (e.key === 'Left' || e.key === 'ArrowLeft') leftPressed = true;
});
document.addEventListener('keyup', (e) => {
if (e.key === 'Right' || e.key === 'ArrowRight') rightPressed = false;
else if (e.key === 'Left' || e.key === 'ArrowLeft') leftPressed = false;
});
document.addEventListener('mousemove', (e) => {
const relativeX = e.clientX - canvas.offsetLeft;
if (relativeX > 0 && relativeX < canvas.width) {
paddle.x = relativeX - paddle.width / 2;
}
});
document.addEventListener('mousedown', () => {
if (gameState === 'OPENING') {
audioCtx.resume();
gameState = 'PLAYING';
resetGame();
startBGM();
} else if (gameState === 'GAMEOVER') {
// ここでは再開のみ。終了はブラウザを閉じる想定だが、UIで案内
}
});
document.addEventListener('keypress', (e) => {
if (gameState === 'OPENING' && e.key === ' ') {
audioCtx.resume();
gameState = 'PLAYING';
resetGame();
startBGM();
} else if (gameState === 'GAMEOVER' && e.key === ' ') {
gameState = 'PLAYING';
resetGame();
startBGM();
}
});
// --- 更新処理 ---
function collisionDetection() {
for (let c = 0; c < BRICK_COLS; c++) {
for (let r = 0; r < BRICK_ROWS; r++) {
const b = bricks[c][r];
if (b.status === 1) {
if (ball.x > b.x && ball.x < b.x + (CANVAS_WIDTH - (BRICK_OFFSET_LEFT * 2) - (BRICK_PADDING * (BRICK_COLS - 1))) / BRICK_COLS &&
ball.y > b.y && ball.y < b.y + 20) {
ball.dy = -ball.dy;
b.status = 0;
score++;
playSound('brick');
if (score === BRICK_ROWS * BRICK_COLS) {
gameState = 'GAMEOVER'; // Win state simple handle
playSound('win');
stopBGM();
}
}
}
}
}
}
function update() {
if (gameState !== 'PLAYING') return;
// ラケット移動
if (rightPressed && paddle.x < canvas.width - paddle.width) paddle.x += 7;
else if (leftPressed && paddle.x > 0) paddle.x -= 7;
// ボール移動
ball.x += ball.dx;
ball.y += ball.dy;
// 壁衝突
if (ball.x + ball.dx > canvas.width - ball.radius || ball.x + ball.dx < ball.radius) {
ball.dx = -ball.dx;
playSound('wall');
}
if (ball.y + ball.dy < ball.radius) {
ball.dy = -ball.dy;
playSound('wall');
} else if (ball.y + ball.dy > canvas.height - ball.radius) {
// ラケット衝突判定
if (ball.x > paddle.x && ball.x < paddle.x + paddle.width) {
// 跳ね返る方向を変える: 中心からの距離で角度調整
const hitPoint = (ball.x - (paddle.x + paddle.width / 2)) / (paddle.width / 2);
ball.dx = hitPoint * 4;
ball.dy = -ball.dy;
playSound('paddle');
} else {
lives--;
playSound('lose');
if (!lives) {
gameState = 'GAMEOVER';
stopBGM();
} else {
resetBall();
}
}
}
collisionDetection();
}
// --- 描画処理 ---
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (gameState === 'OPENING') {
ctx.fillStyle = 'white';
ctx.font = '30px Arial';
ctx.textAlign = 'center';
ctx.fillText('BLOCK BREAKER', canvas.width / 2, canvas.height / 2 - 40);
ctx.font = '20px Arial';
ctx.fillText('Click or Press SPACE to Start', canvas.width / 2, canvas.height / 2 + 20);
} else if (gameState === 'GAMEOVER') {
ctx.fillStyle = 'white';
ctx.font = '30px Arial';
ctx.textAlign = 'center';
ctx.fillText(lives <= 0 ? 'GAME OVER' : 'YOU WIN!', canvas.width / 2, canvas.height / 2 - 40);
ctx.font = '20px Arial';
ctx.fillText('Press SPACE to Restart', canvas.width / 2, canvas.height / 2 + 20);
ctx.fillText('or Close Window to Exit', canvas.width / 2, canvas.height / 2 + 50);
} else {
// ラケット描画
ctx.beginPath();
ctx.rect(paddle.x, paddle.y, paddle.width, paddle.height);
ctx.fillStyle = '#fff';
ctx.fill();
ctx.closePath();
// ボール描画
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
ctx.fillStyle = '#fff';
ctx.fill();
ctx.closePath();
// ブロック描画
const bWidth = (CANVAS_WIDTH - (BRICK_OFFSET_LEFT * 2) - (BRICK_PADDING * (BRICK_COLS - 1))) / BRICK_COLS;
const bHeight = 20;
for (let c = 0; c < BRICK_COLS; c++) {
for (let r = 0; r < BRICK_ROWS; r++) {
if (bricks[c][r].status === 1) {
const bx = c * (bWidth + BRICK_PADDING) + BRICK_OFFSET_LEFT;
const by = r * (bHeight + BRICK_PADDING) + BRICK_OFFSET_TOP;
bricks[c][r].x = bx;
bricks[c][r].y = by;
ctx.beginPath();
ctx.rect(bx, by, bWidth, bHeight);
ctx.fillStyle = COLORS[r];
ctx.fill();
ctx.closePath();
}
}
}
// UI
ctx.fillStyle = 'white';
ctx.font = '16px Arial';
ctx.textAlign = 'left';
ctx.fillText(`Score: ${score}`, 20, 30);
ctx.textAlign = 'right';
ctx.fillText(`Lives: ${lives}`, canvas.width - 20, 30);
}
update();
requestAnimationFrame(draw);
}
initBricks();
draw();
</script>
</body>
</html>