ORIGINAL SOURCE / MODEL 02

Gemma 4 31B — 生成コード原文

生成された index.html の内容をそのままテキスト表示しています。このページではコードを実行しません。ゲームの評価・修正は行っていません。

10,028 bytes / 331 行 / 生成時間 46分24秒
SHA-256: 3aeb75ec0098b2c7901903d9a214f8d66122ac01cb8bd9e04073c99e4913849f

ゲームを開く際は音量にご注意ください。動作を保証するものではありません。

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>