はりねずみ

<style>
        body {
            font-family: 'Helvetica Neue', Arial, sans-serif;
            text-align: center;
            background-color: #f0f4f8;
            margin: 0;
            padding: 20px;
            user-select: none;
        }
        h1 { color: #333; margin-bottom: 5px; }
        
        /* 全体情報エリア */
        #info {
            display: flex;
            justify-content: center;
            align-items: center;
            gap: 15px;
            font-size: 16px;
            margin-bottom: 15px;
            font-weight: bold;
            flex-wrap: wrap;
        }

        /* スタミナゲージの外枠 */
        .stamina-container {
            display: flex;
            align-items: center;
            gap: 5px;
        }
        .stamina-bar-bg {
            width: 120px;
            height: 16px;
            background-color: #ddd;
            border-radius: 8px;
            overflow: hidden;
            border: 1px solid #aaa;
            box-shadow: inset 0 1px 3px rgba(0,0,0,0.2);
        }
        /* スタミナゲージの本体(動的に動く) */
        #stamina-bar {
            width: 100%;
            height: 100%;
            background-color: #2ed573;
            transition: width 0.1s linear, background-color 0.3s;
        }

        #gameCanvas {
            background-color: #fff;
            border: 4px solid #ccc;
            border-radius: 10px;
            display: block;
            margin: 0 auto;
            box-shadow: 0 4px 10px rgba(0,0,0,0.1);
        }
        #message {
            margin-top: 15px;
            font-size: 24px;
            font-weight: bold;
            color: #ff4757;
            height: 35px;
        }
        #btn-retry {
            margin-top: 10px;
            padding: 12px 24px;
            font-size: 18px;
            cursor: pointer;
            display: none;
            background-color: #2ed573;
            color: white;
            border: none;
            border-radius: 5px;
        }
        #controls {
            margin-top: 15px;
        }
        .btn-dir {
            padding: 15px 25px;
            font-size: 18px;
            margin: 5px;
            cursor: pointer;
            background-color: #1e90ff;
            color: white;
            border: none;
            border-radius: 5px;
            touch-action: manipulation;
        }
    </style>
</head>
<body>

    <h1 id="stage-title">🦔ハリネズミのリンゴ集め🍎</h1>
    
    <div id="info">
        <div>ステージ: <span id="stage-num">1</span></div>
        <div class="stamina-container">
            スタミナ: 
            <div class="stamina-bar-bg">
                <div id="stamina-bar"></div>
            </div>
            (<span id="stamina-text">25.0</span>)
        </div>
        <div>スコア: <span id="score">0</span> / 5</div>
        <div id="boss-hp-area" style="display:none;">| ボスHP: <span id="boss-hp">5</span></div>
    </div>

    <canvas id="gameCanvas" width="420" height="420"></canvas>

    <div id="message"></div>
    <button id="btn-retry" onclick="resetGame()">最初からあそぶ</button>

    <div id="controls">
        <div><button class="btn-dir" id="btn-up">↑</button></div>
        <button class="btn-dir" id="btn-left">←</button>
        <button class="btn-dir" id="btn-right">→</button>
        <div><button class="btn-dir" id="btn-down">↓</button></div>
    </div>

    <p style="color: #666; font-size: 14px; margin-top: 10px;">※サボテンを拾うとボスに自動発射!</p>

    <script>
        const canvas = document.getElementById('gameCanvas');
        const ctx = canvas.getContext('2d');

        const BOARD_SIZE = 7; 
        const CELL_SIZE = 60; 

        let currentStage = 1;
        let player = { x: 0, y: 0, dir: 'right' };
        let apple = { x: 3, y: 3 };
        let poops = []; 
        let puddles = []; 
        let launchers = []; 
        let needles = []; 
        
        // ボスをコブラに設定
        let boss = { x: 3, y: 1, vx: 0.06, vy: 0, hp: 5, width: 1.5, height: 1.5 };
        let maxStamina = 25.0; // 割合計算用の最大値スタミナ
        let stamina = 25.0;
        let score = 0;
        let isGameOver = false;
        let isClear = false;

        let keys = {};
        let touchDirs = { up: false, down: false, left: false, right: false };
        let launcherSpawnTimer = 0;
        let endingTimer = 0;

        function resetGame() {
            currentStage = 1;
            initStage();
        }

        function initStage() {
            player = { x: 0, y: BOARD_SIZE - 1, dir: 'right' };
            poops = [];
            puddles = [];
            launchers = [];
            needles = [];
            isGameOver = false;
            isClear = false;
            launcherSpawnTimer = 0;
            endingTimer = 0;
            score = 0; 

            document.getElementById('message').innerText = "";
            document.getElementById('btn-retry').style.display = "none";
            document.getElementById('stage-num').innerText = currentStage;
            document.getElementById('controls').style.display = "block";

            const bossArea = document.getElementById('boss-hp-area');

            if (currentStage === 1) {
                stamina = 25.0;
                maxStamina = 25.0;
                document.getElementById('stage-title').innerText = "STAGE 1:うんちを避けろ!💩";
                if(bossArea) bossArea.style.display = "none";
                spawnApple();
                spawnNewPoop(0.04);
            } else if (currentStage === 2) {
                stamina = 30.0; 
                maxStamina = 30.0;
                document.getElementById('stage-title').innerText = "STAGE 2:泥沼の罠!💧";
                if(bossArea) bossArea.style.display = "none";
                spawnApple();
                for(let i=0; i<2; i++) {
                    puddles.push({ 
                        x: Math.floor(Math.random() * (BOARD_SIZE - 2)) + 1, 
                        y: Math.floor(Math.random() * (BOARD_SIZE - 2)) + 1 
                    });
                }
                spawnNewPoop(0.04);
                spawnNewPoop(0.05);
            } else if (currentStage === 3) {
                stamina = 45.0;
                maxStamina = 45.0;
                boss.hp = 5;
                boss.x = BOARD_SIZE / 2 - boss.width / 2;
                boss.y = 0.5;
                document.getElementById('stage-title').innerText = "STAGE 3:対決!巨大コブラ🐍";
                if(bossArea) bossArea.style.display = "inline";
                const bossHpEl = document.getElementById('boss-hp');
                if(bossHpEl) bossHpEl.innerText = boss.hp;
                
                spawnNewPoop(0.03);
                spawnNewPoop(0.03);
                spawnLauncher(); 
            }
            updateStaminaGauge();
        }

        // スタミナゲージの見た目を更新する関数
        function updateStaminaGauge() {
            const bar = document.getElementById('stamina-bar');
            const text = document.getElementById('stamina-text');
            if (!bar || !text) return;

            text.innerText = stamina.toFixed(1);
            
            // 割合を計算してバーの幅を変更
            let percentage = (stamina / maxStamina) * 100;
            if (percentage < 0) percentage = 0;
            bar.style.width = percentage + "%";

            // 残量に応じて色を変化させる(緑 -> 黄 -> 赤)
            if (percentage > 50) {
                bar.style.backgroundcolor = "#2ed573"; // 緑
            } else if (percentage > 20) {
                bar.style.backgroundcolor = "#ffa500"; // オレンジ・黄
            } else {
                bar.style.backgroundcolor = "#ff4757"; // 赤
            }
        }

        function spawnApple() {
            let valid = false;
            let attempts = 0;
            while (!valid && attempts < 100) {
                attempts++;
                apple.x = Math.floor(Math.random() * BOARD_SIZE);
                apple.y = Math.floor(Math.random() * BOARD_SIZE);
                
                let nearPlayer = Math.abs(apple.x - player.x) < 1 && Math.abs(apple.y - player.y) < 1;
                let inPuddle = puddles.some(p => p.x === apple.x && p.y === apple.y);
                
                if (!nearPlayer && !inPuddle) valid = true;
            }
        }

        function spawnNewPoop(speed) {
            let px = Math.random() * (BOARD_SIZE - 1);
            let py = Math.random() * (BOARD_SIZE - 3); 
            const angle = Math.random() * Math.PI * 2;
            poops.push({ x: px, y: py, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed });
        }

        function spawnLauncher() {
            let lx = Math.floor(Math.random() * BOARD_SIZE);
            let ly = Math.floor(Math.random() * (BOARD_SIZE - 3)) + 3; 
            launchers.push({ x: lx, y: ly });
        }

        function drawHedgehog(cx, cy, radius, direction) {
            ctx.save();
            ctx.translate(cx, cy);
            if (direction === 'left') ctx.scale(-1, 1);

            ctx.fillStyle = "#6d4c41"; 
            ctx.beginPath();
            ctx.moveTo(-radius, 0);
            for (let i = 0; i < 6; i++) {
                let angle = Math.PI + (i * Math.PI / 5);
                let x1 = Math.cos(angle) * radius;
                let y1 = Math.sin(angle) * radius;
                let x2 = Math.cos(angle) * (radius * 1.3); 
                let y2 = Math.sin(angle) * (radius * 1.3);
                ctx.lineTo(x1, y1); ctx.lineTo(x2, y2);
            }
            ctx.fill();

            ctx.fillStyle = "#e0a96d"; 
            ctx.beginPath(); ctx.arc(0, 0, radius * 0.9, 0, Math.PI * 2); ctx.fill();

            ctx.beginPath();
            ctx.moveTo(radius * 0.5, -radius * 0.2);
            ctx.lineTo(radius * 1.2, radius * 0.2);
            ctx.lineTo(radius * 0.5, radius * 0.5);
            ctx.fill();

            ctx.fillStyle = "#000000";
            ctx.beginPath(); ctx.arc(radius * 1.2, radius * 0.2, 3, 0, Math.PI * 2); ctx.fill();
            ctx.beginPath(); ctx.arc(radius * 0.4, -radius * 0.1, 3, 0, Math.PI * 2); ctx.fill();

            ctx.restore();
        }

        function update() {
            if (isGameOver) return;
            if (isClear) { endingTimer++; return; }

            let baseSpeed = 0.08;
            let inPuddle = puddles.some(p => Math.hypot(player.x - p.x, player.y - p.y) < 0.6);
            let currentSpeed = inPuddle ? baseSpeed * 0.4 : baseSpeed;

            let dx = 0, dy = 0;
            if (keys['ArrowUp'] || touchDirs.up) dy = -currentSpeed;
            if (keys['ArrowDown'] || touchDirs.down) dy = currentSpeed;
            if (keys['ArrowLeft'] || touchDirs.left) { dx = -currentSpeed; player.dir = 'left'; }
            if (keys['ArrowRight'] || touchDirs.right) { dx = currentSpeed; player.dir = 'right'; }

            if (dx !== 0 || dy !== 0) {
                player.x += dx; player.y += dy;
                stamina -= 0.015;
                player.x = Math.max(0, Math.min(BOARD_SIZE - 1, player.x));
                player.y = Math.max(0, Math.min(BOARD_SIZE - 1, player.y));
            }

            poops.forEach(p => {
                p.x += p.vx; p.y += p.vy;
                if (p.x < 0 || p.x > BOARD_SIZE - 1) p.vx *= -1;
                if (p.y < 0 || p.y > BOARD_SIZE - 1) p.vy *= -1;
            });

            if (currentStage === 3) {
                boss.x += boss.vx;
                if (boss.x < 0 || boss.x > BOARD_SIZE - boss.width) boss.vx *= -1;

                if (launchers.length === 0) {
                    launcherSpawnTimer++;
                    if (launcherSpawnTimer > 100) { spawnLauncher(); launcherSpawnTimer = 0; }
                }

                for (let idx = needles.length - 1; idx >= 0; idx--) {
                    let n = needles[idx];
                    n.x += n.vx; n.y += n.vy;
                    if (n.x >= boss.x && n.x <= boss.x + boss.width && n.y >= boss.y && n.y <= boss.y + boss.height) {
                        boss.hp--;
                        const bossHpEl = document.getElementById('boss-hp');
                        if(bossHpEl) bossHpEl.innerText = boss.hp;
                        needles.splice(idx, 1);
                        if (boss.hp <= 0) {
                            startEnding();
                            return;
                        }
                        continue;
                    }
                    if (n.x < 0 || n.x > BOARD_SIZE || n.y < 0 || n.y > BOARD_SIZE) {
                        needles.splice(idx, 1);
                    }
                }
            }

            if (poops.some(p => Math.hypot(player.x - p.x, player.y - p.y) < 0.45)) {
                endGame("ゲームオーバー!うんちに衝突。");
                return;
            }

            if (currentStage !== 3 && Math.hypot(player.x - apple.x, player.y - apple.y) < 0.6) {
                score++;
                stamina += 6.0; 
                if (stamina > maxStamina) stamina = maxStamina; // 最大値を超えない処理
                if (score >= 5) { 
                    nextStage();
                } else {
                    spawnNewPoop(currentStage === 1 ? 0.04 : 0.05);
                    spawnApple();
                }
            }

            if (currentStage === 3) {
                for (let idx = launchers.length - 1; idx >= 0; idx--) {
                    let l = launchers[idx];
                    if (Math.hypot(player.x - l.x, player.y - l.y) < 0.6) {
                        launchers.splice(idx, 1); 
                        let bossCenterX = boss.x + boss.width / 2;
                        let bossCenterY = boss.y + boss.height / 2;
                        let angle = Math.atan2(bossCenterY - player.y, bossCenterX - player.x);
                        let needleSpeed = 0.16;
                        needles.push({ x: player.x, y: player.y, vx: Math.cos(angle) * needleSpeed, vy: Math.sin(angle) * needleSpeed });
                    }
                }
            }

            if (stamina <= 0) { stamina = 0; endGame("ゲームオーバー!スタミナ切れです。"); }
            
            updateStaminaGauge(); // 毎フレームスタミナバーを更新
        }

        function nextStage() {
            currentStage++;
            initStage();
        }

        function startEnding() {
            isClear = true; poops = []; launchers = []; needles = [];
            document.getElementById('controls').style.display = "none";
            document.getElementById('stage-title').innerText = "✨ EPILOGUE ✨";
        }

        function endGame(msg) {
            isGameOver = true;
            document.getElementById('message').innerText = msg;
            document.getElementById('message').style.color = "#ff4757";
            document.getElementById('btn-retry').style.display = "inline-block";
        }

        function draw() {
            const scoreEl = document.getElementById('score');
            if(scoreEl) scoreEl.innerText = score;

            ctx.clearRect(0, 0, canvas.width, canvas.height);

            if (!isClear) {
                ctx.strokeStyle = "#e8eef3"; ctx.lineWidth = 1;
                for (let i = 0; i <= BOARD_SIZE; i++) {
                    ctx.beginPath(); ctx.moveTo(i * CELL_SIZE, 0); ctx.lineTo(i * CELL_SIZE, canvas.height); ctx.stroke();
                    ctx.beginPath(); ctx.moveTo(0, i * CELL_SIZE); ctx.lineTo(canvas.width, i * CELL_SIZE); ctx.stroke();
                }

                ctx.textAlign = "center"; ctx.textBaseline = "middle";

                if (currentStage === 2) {
                    ctx.font = "34px Arial";
                    puddles.forEach(p => ctx.fillText('💧', p.x * CELL_SIZE + CELL_SIZE/2, p.y * CELL_SIZE + CELL_SIZE/2));
                }

                if (currentStage === 3) {
                    ctx.font = "34px Arial";
                    launchers.forEach(l => ctx.fillText('🌵', l.x * CELL_SIZE + CELL_SIZE/2, l.y * CELL_SIZE + CELL_SIZE/2));
                    ctx.font = "20px Arial";
                    needles.forEach(n => ctx.fillText('📍', n.x * CELL_SIZE + CELL_SIZE/2, n.y * CELL_SIZE + CELL_SIZE/2));
                    
                    // ボスをコブラ(🐍👑)に描き替え
                    ctx.font = "70px Arial";
                    ctx.fillText('🐍👑', (boss.x + boss.width/2) * CELL_SIZE, (boss.y + boss.height/2) * CELL_SIZE);
                }

                if (currentStage !== 3) {
                    ctx.font = "38px Arial";
                    if (apple.x !== undefined) ctx.fillText('🍎', apple.x * CELL_SIZE + CELL_SIZE/2, apple.y * CELL_SIZE + CELL_SIZE/2);
                }

                ctx.font = "36px Arial";
                poops.forEach(p => ctx.fillText('💩', p.x * CELL_SIZE + CELL_SIZE/2, p.y * CELL_SIZE + CELL_SIZE/2));

                let px = player.x * CELL_SIZE + CELL_SIZE/2;
                let py = player.y * CELL_SIZE + CELL_SIZE/2;
                drawHedgehog(px, py, 20, player.dir);

            } else {
                ctx.fillStyle = "#1e272e"; ctx.fillRect(0, 0, canvas.width, canvas.height);
                ctx.textAlign = "center"; ctx.fillStyle = "#ffffff";

                if (endingTimer < 120) {
                    ctx.font = "24px 'Helvetica Neue'";
                    ctx.fillText("激闘の末、大蛇コブラは逃げ出した!", canvas.width/2, 180);
                    ctx.font = "40px Arial"; ctx.fillText("🏃‍♂️💨🐍", canvas.width/2, 260);
                } else if (endingTimer < 260) {
                    ctx.font = "22px 'Helvetica Neue'";
                    ctx.fillText("森に平和が戻り、たくさんの", canvas.width/2, 160);
                    ctx.fillText("特大リンゴが実りました。", canvas.width/2, 200);
                    ctx.font = "45px Arial"; ctx.fillText("🍎🍏🍎🍏🍎", canvas.width/2, 280);
                } else {
                    ctx.fillStyle = "#ffd32a"; ctx.font = "bold 32px 'Helvetica Neue'";
                    ctx.fillText("✨ HAPPY END ✨", canvas.width/2, 100);
                    drawHedgehog(canvas.width/2, 180, 30, 'right');
                    ctx.fillStyle = "#ffffff"; ctx.font = "18px 'Helvetica Neue'";
                    ctx.fillText("こうしてハリネズミの満腹な旅は幕を閉じた。", canvas.width/2, 260);
                    ctx.fillText("遊んでくれてありがとう!", canvas.width/2, 300);
                    document.getElementById('btn-retry').style.display = "inline-block";
                    document.getElementById('btn-retry').innerText = "もう一度最初から遊ぶ";
                }
            }
        }

        function gameLoop() { update(); draw(); requestAnimationFrame(gameLoop); }

        window.addEventListener('keydown', (e) => {
            if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) e.preventDefault();
            keys[e.key] = true;
        });
        window.addEventListener('keyup', (e) => { keys[e.key] = false; });

        function setupButton(id, dirStr) {
            const btn = document.getElementById(id);
            const startEvent = (e) => { e.preventDefault(); if(!isGameOver && !isClear) touchDirs[dirStr] = true; };
            const endEvent = (e) => { e.preventDefault(); touchDirs[dirStr] = false; };
            btn.addEventListener('mousedown', startEvent); btn.addEventListener('mouseup', endEvent); btn.addEventListener('mouseleave', endEvent);
            btn.addEventListener('touchstart', startEvent); btn.addEventListener('touchend', endEvent);
        }
        setupButton('btn-up', 'up'); setupButton('btn-down', 'down'); setupButton('btn-left', 'left'); setupButton('btn-right', 'right');

        resetGame();
        gameLoop();
    </script>

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です