🔬 AI発明ギャラリー syunsuke ミズダコ
🔒 サンドボックス内で実行中 ⛶ 全画面で遊ぶ
HTML / CSS / JS
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>ミズダコの一生 - Realistic Survival</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <style>
        body {
            margin: 0;
            overflow: hidden;
            background-color: #001a33;
            font-family: 'Helvetica Neue', Arial, sans-serif;
            touch-action: none;
            user-select: none;
        }
        #game-canvas {
            display: block;
        }
        .ui-layer {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            pointer-events: none;
            display: flex;
            flex-direction: column;
            justify-content: space-between;
            padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
            color: white;
            text-shadow: 2px 2px 8px rgba(0,0,0,0.9);
        }
        .interactive {
            pointer-events: auto;
        }
        .progress-bar {
            height: 12px;
            background: rgba(255,255,255,0.2);
            border-radius: 6px;
            overflow: hidden;
            width: 140px;
            border: 1px solid rgba(255,255,255,0.3);
        }
        @media (min-width: 768px) {
            .progress-bar { width: 240px; height: 16px; }
        }
        .progress-fill {
            height: 100%;
            background: linear-gradient(90deg, #4ade80, #22c55e);
            width: 0%;
            transition: width 0.4s ease-out;
        }
        #joystick-base {
            position: absolute;
            bottom: 60px;
            left: 60px;
            width: 110px;
            height: 110px;
            background: rgba(255, 255, 255, 0.05);
            border: 2px solid rgba(255, 255, 255, 0.15);
            border-radius: 50%;
            display: none;
            pointer-events: none;
            z-index: 100;
        }
        #joystick-knob {
            position: absolute;
            width: 44px;
            height: 44px;
            background: rgba(255, 255, 255, 0.4);
            backdrop-filter: blur(4px);
            border-radius: 50%;
            transform: translate(-50%, -50%);
            pointer-events: none;
        }
    </style>
</head>
<body>

    <canvas id="game-canvas"></canvas>

    <div id="joystick-base">
        <div id="joystick-knob"></div>
    </div>

    <!-- UI Layers -->
    <div class="ui-layer" id="ui-start">
        <div class="flex-1 flex flex-col items-center justify-center text-center interactive bg-black/50 backdrop-blur-xl rounded-2xl m-6 p-8 shadow-2xl">
            <h1 class="text-5xl sm:text-7xl font-black mb-6 text-transparent bg-clip-text bg-gradient-to-b from-cyan-300 to-blue-600">ミズダコの一生</h1>
            <p class="text-xl sm:text-2xl mb-8 max-w-xl text-cyan-100 font-medium">
                北の冷たい海で育まれる、<br>巨大な生命のドラマを体験してください。
            </p>
            <button id="start-btn" class="bg-gradient-to-r from-cyan-600 to-blue-700 hover:from-cyan-500 hover:to-blue-600 text-white font-black py-4 px-12 rounded-full text-2xl transition-all transform active:scale-90 shadow-2xl border-b-4 border-blue-900">
                生命の旅を始める
            </button>
        </div>
    </div>

    <div class="ui-layer hidden" id="ui-game">
        <div class="flex justify-between items-start p-6">
            <div class="bg-black/30 backdrop-blur-md p-3 rounded-xl border border-white/10">
                <div class="text-[10px] font-black uppercase tracking-widest text-cyan-400">生命の形態</div>
                <div id="stage-name" class="text-xl sm:text-2xl font-black">幼体</div>
                <div id="mimicry-status" class="text-[12px] font-bold text-yellow-400 opacity-0 transition-all duration-500">MIMICRY: 環境と同化中</div>
            </div>
            <div class="text-right bg-black/30 backdrop-blur-md p-3 rounded-xl border border-white/10">
                <div class="text-[10px] font-black uppercase tracking-widest text-green-400">成長度</div>
                <div class="progress-bar mt-1">
                    <div id="growth-fill" class="progress-fill"></div>
                </div>
            </div>
        </div>
        
        <div id="message-area" class="text-center text-2xl sm:text-3xl font-black py-4 px-8 opacity-0 transition-opacity duration-700 pointer-events-none drop-shadow-lg">
            メッセージ
        </div>

        <div class="flex justify-between items-end p-6">
            <div class="bg-black/40 p-3 sm:p-5 rounded-2xl backdrop-blur-md border border-white/10 shadow-xl">
                <div class="text-[10px] text-red-300 font-black uppercase tracking-tighter">生存エネルギー</div>
                <div class="progress-bar h-3 sm:h-4 mt-1 border-red-900/30">
                    <div id="health-fill" class="progress-fill bg-gradient-to-r from-red-600 to-red-400" style="width: 100%"></div>
                </div>
            </div>
        </div>
    </div>

    <div class="ui-layer hidden" id="ui-gameover">
        <div class="flex-1 flex flex-col items-center justify-center text-center interactive bg-black/80 backdrop-blur-2xl p-8">
            <h2 class="text-5xl font-black mb-4 text-red-500">旅の終焉</h2>
            <p id="death-reason" class="text-2xl mb-12 text-gray-300">自然の摂理に還りました。</p>
            <button id="retry-btn" class="bg-white text-black font-black py-4 px-12 rounded-full text-2xl active:scale-95 transition-transform">
                輪廻転生
            </button>
        </div>
    </div>

    <div class="ui-layer hidden" id="ui-win">
        <div class="flex-1 flex flex-col items-center justify-center text-center interactive bg-black/90 backdrop-blur-3xl p-6 sm:p-12 overflow-y-auto">
            <h2 class="text-5xl sm:text-7xl font-black mb-6 text-pink-500 drop-shadow-2xl">命の継承</h2>
            <p class="text-2xl sm:text-3xl mb-6 font-bold text-pink-200">偉大なるミズダコとして</p>
            <p class="text-lg sm:text-xl mb-12 max-w-3xl leading-relaxed text-gray-200">
                あなたは数え切れないほどの困難を乗り越え、命のバトンを次世代へと渡しました。<br>
                冷たい海底で卵を守り抜き、あなたの体はやがて海の一部となり、また新しい命を育みます。
            </p>
            <button id="restart-btn" class="bg-pink-600 text-white font-black py-5 px-14 rounded-full text-2xl active:scale-95 shadow-2xl transition-all">
                新しいミズダコとして目覚める
            </button>
        </div>
    </div>

    <script>
        const canvas = document.getElementById('game-canvas');
        const ctx = canvas.getContext('2d');
        const joyBase = document.getElementById('joystick-base');
        const joyKnob = document.getElementById('joystick-knob');
        
        const UI = {
            start: document.getElementById('ui-start'),
            game: document.getElementById('ui-game'),
            gameover: document.getElementById('ui-gameover'),
            win: document.getElementById('ui-win'),
            growthFill: document.getElementById('growth-fill'),
            healthFill: document.getElementById('health-fill'),
            stageName: document.getElementById('stage-name'),
            message: document.getElementById('message-area'),
            mimicryStatus: document.getElementById('mimicry-status'),
            deathReason: document.getElementById('death-reason')
        };

        const STAGES = [
            { name: "幼体 (Paralarva)", color: "rgba(230, 245, 255, 0.2)", minSize: 18, maxSize: 28, threshold: 120, habitat: 'pelagic' },
            { name: "亜成体 (Juvenile)", color: "rgb(160, 60, 45)", minSize: 40, maxSize: 65, threshold: 400, habitat: 'benthic' },
            { name: "成体 (Adult)", color: "rgb(140, 35, 20)", minSize: 75, maxSize: 110, threshold: 1000, habitat: 'benthic' },
            { name: "伝説の巨体 (Giant)", color: "rgb(110, 20, 10)", minSize: 120, maxSize: 180, threshold: 2000, habitat: 'benthic' }
        ];

        let gameState = 'START';
        let currentStageIdx = 0;
        let score = 0;
        let health = 100;
        let frameCount = 0;
        let camera = { x: 0, y: 0 };
        let worldSize = { width: 6000, height: 6000 };

        let hatchProgress = 0;
        let eggShake = 0;

        let entities = [];
        let marineSnow = [];
        let rocks = []; 
        let skinBumps = []; // 皮膚のテクスチャ用
        let partnerSpawned = false;

        const player = {
            x: 3000, y: 3000,
            size: 20,
            vx: 0, vy: 0,
            rotation: 0,
            arms: [],
            mimicryAmount: 0 
        };

        const input = { isMoving: false, joyX: 0, joyY: 0, touchId: null };
        const keys = {};

        window.addEventListener('keydown', e => keys[e.code] = true);
        window.addEventListener('keyup', e => keys[e.code] = false);

        const handleTouchStart = (e) => {
            if (gameState === 'HATCHING') { hatchProgress += 18; eggShake = 20; return; }
            if (gameState !== 'PLAYING') return;
            const touch = e.touches[0];
            input.touchId = touch.identifier;
            input.isMoving = true;
            joyBase.style.display = 'block';
            joyBase.style.left = `${touch.clientX - 55}px`;
            joyBase.style.top = `${touch.clientY - 55}px`;
            updateJoystick(touch.clientX, touch.clientY);
        };

        const handleTouchMove = (e) => {
            if (gameState !== 'PLAYING') return;
            e.preventDefault();
            for (let touch of e.changedTouches) {
                if (touch.identifier === input.touchId) { updateJoystick(touch.clientX, touch.clientY); }
            }
        };

        const handleTouchEnd = (e) => {
            for (let touch of e.changedTouches) {
                if (touch.identifier === input.touchId) {
                    input.isMoving = false;
                    input.joyX = 0; input.joyY = 0;
                    joyBase.style.display = 'none';
                }
            }
        };

        canvas.addEventListener('touchstart', handleTouchStart);
        canvas.addEventListener('touchmove', handleTouchMove, { passive: false });
        canvas.addEventListener('touchend', handleTouchEnd);
        canvas.addEventListener('mousedown', (e) => {
            if (gameState === 'HATCHING') { hatchProgress += 18; eggShake = 20; }
        });

        function updateJoystick(tx, ty) {
            const bx = parseInt(joyBase.style.left) + 55;
            const by = parseInt(joyBase.style.top) + 55;
            let dx = tx - bx;
            let dy = ty - by;
            const dist = Math.sqrt(dx*dx + dy*dy);
            const maxDist = 55;
            if (dist > maxDist) { dx = (dx / dist) * maxDist; dy = (dy / dist) * maxDist; }
            joyKnob.style.left = `${55 + dx}px`;
            joyKnob.style.top = `${55 + dy}px`;
            input.joyX = dx / maxDist;
            input.joyY = dy / maxDist;
        }

        function init() {
            resize();
            window.addEventListener('resize', resize);
            
            player.arms = Array.from({ length: 8 }, (_, i) => ({
                angle: (Math.PI * 2 / 8) * i,
                segments: Array.from({ length: 10 }, () => ({ x: player.x, y: player.y }))
            }));

            // 海の背景要素生成
            for (let i = 0; i < 250; i++) {
                marineSnow.push({ x: Math.random()*worldSize.width, y: Math.random()*worldSize.height, size: Math.random()*2+1, speed: 0.15+Math.random()*0.4, alpha: 0.1+Math.random()*0.4 });
            }

            for (let i = 0; i < 160; i++) {
                rocks.push({ 
                    x: Math.random()*worldSize.width, 
                    y: Math.random()*worldSize.height, 
                    size: 100+Math.random()*250, 
                    rotation: Math.random()*Math.PI*2,
                    color: `rgb(${25+Math.random()*15}, ${25+Math.random()*15}, ${20+Math.random()*10})` 
                });
            }

            // 皮膚のブツブツ用パーティクル
            for(let i=0; i<30; i++){
                skinBumps.push({ x: Math.random(), y: Math.random(), size: 0.1+Math.random()*0.2 });
            }

            requestAnimationFrame(gameLoop);
        }

        function resize() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; }

        function spawnEntity() {
            if (entities.length > 100) return;
            const isEnemy = Math.random() > 0.8;
            let type, color, size, name;
            if (!isEnemy) {
                if (currentStageIdx === 0) { type = 'plankton'; color = '#fff'; size = 3; }
                else if (currentStageIdx === 1) { type = 'shrimp'; color = '#ff9966'; size = 12; }
                else { type = 'crab'; color = '#ff4444'; size = 22; }
            } else {
                if (currentStageIdx === 0) { type = 'smallFish'; color = '#5555ff'; size = 38; name = "小魚"; }
                else if (currentStageIdx === 1) { type = 'bigFish'; color = '#3333aa'; size = 80; name = "大型魚"; }
                else { type = 'shark'; color = '#222'; size = 280; name = "サメ"; }
            }
            const angle = Math.random()*Math.PI*2;
            const dist = Math.max(canvas.width, canvas.height)*1.1;
            entities.push({ x: player.x+Math.cos(angle)*dist, y: player.y+Math.sin(angle)*dist, vx: (Math.random()-0.5)*2.5, vy: (Math.random()-0.5)*2.5, size, color, type, name, isEnemy, pulse: 0 });
        }

        function update() {
            if (gameState === 'HATCHING') {
                eggShake *= 0.88;
                if (hatchProgress >= 100) { gameState = 'PLAYING'; showMessage("誕生!透明な幼体として一生が始まります", "text-cyan-200"); }
                return;
            }
            if (gameState !== 'PLAYING') return;

            const stage = STAGES[currentStageIdx];
            const currentGrowthRate = score / stage.threshold;

            if (currentStageIdx >= 2 && currentGrowthRate > 0.85 && !partnerSpawned) {
                partnerSpawned = true;
                entities.push({
                    x: player.x+1000, y: player.y+1000, size: player.size*0.9, color: "#ff66aa", type: 'partner', name: "パートナー", isEnemy: false
                });
                showMessage("繁殖の準備が整いました。パートナーを探してください。", "text-pink-300");
            }

            let moveX = input.joyX, moveY = input.joyY;
            if (keys['KeyW'] || keys['ArrowUp']) moveY = -1;
            if (keys['KeyS'] || keys['ArrowDown']) moveY = 1;
            if (keys['KeyA'] || keys['ArrowLeft']) moveX = -1;
            if (keys['KeyD'] || keys['ArrowRight']) moveX = 1;

            if (moveX !== 0 || moveY !== 0) {
                const mag = Math.sqrt(moveX*moveX + moveY*moveY);
                player.vx += (moveX / mag) * 0.5;
                player.vy += (moveY / mag) * 0.5;
                player.rotation = Math.atan2(moveY, moveX);
            }

            player.vx *= 0.95; player.vy *= 0.95;
            player.x += player.vx; player.y += player.vy;

            const currentSpeed = Math.sqrt(player.vx*player.vx + player.vy*player.vy);
            if (currentSpeed < 1.2) player.mimicryAmount = Math.min(1, player.mimicryAmount + 0.015);
            else player.mimicryAmount = Math.max(0, player.mimicryAmount - 0.08);
            UI.mimicryStatus.style.opacity = player.mimicryAmount > 0.7 ? "1" : "0";

            player.x = Math.max(0, Math.min(worldSize.width, player.x));
            player.y = Math.max(0, Math.min(worldSize.height, player.y));
            camera.x = player.x - canvas.width / 2; camera.y = player.y - canvas.height / 2;

            player.arms.forEach((arm, i) => {
                const baseAngle = player.rotation + arm.angle;
                let px = player.x, py = player.y;
                arm.segments.forEach((seg, j) => {
                    const targetX = px - Math.cos(baseAngle + Math.sin(frameCount*0.12 + i + j*0.35)*0.25) * (player.size * 0.5);
                    const targetY = py - Math.sin(baseAngle + Math.sin(frameCount*0.12 + i + j*0.35)*0.25) * (player.size * 0.5);
                    seg.x += (targetX - seg.x) * 0.45; seg.y += (targetY - seg.y) * 0.45;
                    px = seg.x; py = seg.y;
                });
            });

            for (let i = entities.length - 1; i >= 0; i--) {
                const e = entities[i];
                e.x += e.vx; e.y += e.vy;
                const dist = Math.sqrt((e.x-player.x)**2 + (e.y-player.y)**2);
                if (dist < player.size + e.size) {
                    if (e.type === 'partner') { gameWin(); return; }
                    const canEat = (currentStageIdx >= 1 && currentGrowthRate > 0.4 && e.type === 'smallFish');
                    if (e.isEnemy && !canEat) {
                        if (player.mimicryAmount < 0.75) {
                            health -= 25; showMessage(`${e.name}に攻撃された!`, "text-red-400");
                            entities.splice(i, 1);
                            if (health <= 0) gameOver();
                        }
                    } else {
                        score += e.size * 5; health = Math.min(100, health + 12);
                        entities.splice(i, 1); checkGrowth();
                    }
                } else if (dist > 4000) entities.splice(i, 1);
            }

            marineSnow.forEach(s => { s.y += s.speed; if (s.y > worldSize.height) s.y = 0; });
            if (frameCount % 35 === 0) spawnEntity();
            UI.healthFill.style.width = `${health}%`;
            frameCount++;
        }

        function checkGrowth() {
            const stage = STAGES[currentStageIdx];
            const progress = score / stage.threshold;
            player.size = stage.minSize + (stage.maxSize - stage.minSize) * Math.min(1, progress);
            UI.growthFill.style.width = `${Math.min(100, progress * 100)}%`;
            if (score >= stage.threshold && STAGES[currentStageIdx + 1]) {
                currentStageIdx++; score = 0; UI.stageName.innerText = STAGES[currentStageIdx].name;
                partnerSpawned = false;
                showMessage("成長!体が変化しました。", "text-yellow-300");
            }
        }

        function draw() {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            const stage = STAGES[currentStageIdx];
            const isBenthic = stage.habitat === 'benthic';
            
            // 背景描画
            const grad = ctx.createLinearGradient(0, 0, 0, canvas.height);
            if (isBenthic) {
                grad.addColorStop(0, "#2c4a5c"); grad.addColorStop(1, "#0a151b");
            } else {
                grad.addColorStop(0, "#0066cc"); grad.addColorStop(1, "#002b55");
            }
            ctx.fillStyle = grad;
            ctx.fillRect(0, 0, canvas.width, canvas.height);

            // 岩場の描画
            if (isBenthic) {
                rocks.forEach(r => {
                    const rx = r.x - camera.x, ry = r.y - camera.y;
                    if (rx > -r.size && rx < canvas.width + r.size && ry > -r.size && ry < canvas.height + r.size) {
                        ctx.fillStyle = r.color;
                        ctx.beginPath();
                        ctx.ellipse(rx, ry, r.size, r.size*0.7, r.rotation, 0, Math.PI*2);
                        ctx.fill();
                        // 岩のハイライト
                        ctx.strokeStyle = "rgba(255,255,255,0.05)";
                        ctx.stroke();
                    }
                });
            }

            // マリンスノー
            ctx.fillStyle = "rgba(255,255,255,0.25)";
            marineSnow.forEach(s => { 
                const sx = s.x - camera.x, sy = s.y - camera.y;
                if (sx > 0 && sx < canvas.width && sy > 0 && sy < canvas.height) {
                    ctx.beginPath(); ctx.arc(sx, sy, s.size, 0, Math.PI*2); ctx.fill(); 
                }
            });

            if (gameState === 'HATCHING') {
                const ex = canvas.width / 2 + (Math.random()-0.5)*eggShake;
                const ey = canvas.height / 2 + (Math.random()-0.5)*eggShake;
                // 卵の影
                ctx.fillStyle = "rgba(0,0,0,0.4)";
                ctx.beginPath(); ctx.ellipse(ex+10, ey+10, 65, 85, 0, 0, Math.PI*2); ctx.fill();
                // 卵本体
                ctx.fillStyle = `rgba(255, 255, 250, ${0.9 - hatchProgress/150})`;
                ctx.beginPath(); ctx.ellipse(ex, ey, 60, 80, 0, 0, Math.PI*2); ctx.fill();
                // ヒビ
                if(hatchProgress > 30) {
                    ctx.strokeStyle = "rgba(0,0,0,0.2)"; ctx.lineWidth = 2;
                    ctx.beginPath(); ctx.moveTo(ex-30, ey-40); ctx.lineTo(ex+10, ey); ctx.lineTo(ex-10, ey+40); ctx.stroke();
                }
                // 中の影
                ctx.fillStyle = "rgba(100, 150, 200, 0.3)";
                ctx.beginPath(); ctx.arc(ex, ey + 15, 25 + hatchProgress/6, 0, Math.PI*2); ctx.fill();
                ctx.fillStyle = "white"; ctx.font = "bold 24px sans-serif"; ctx.textAlign="center";
                ctx.fillText("連打して卵から生まれる", canvas.width/2, canvas.height/2 + 150);
                return;
            }

            const px = player.x - camera.x, py = player.y - camera.y;
            
            // 擬態と皮膚カラーの計算
            let baseColor = stage.color;
            if (currentStageIdx > 0) {
                // 亜成体以降の岩場への擬態
                const rockRGB = [44, 74, 92]; // 海底カラー
                const octopusRGB = stage.color.match(/\d+/g).map(Number);
                const r = octopusRGB[0] + (rockRGB[0] - octopusRGB[0]) * player.mimicryAmount;
                const g = octopusRGB[1] + (rockRGB[1] - octopusRGB[1]) * player.mimicryAmount;
                const b = octopusRGB[2] + (targetB = rockRGB[2] - octopusRGB[2]) * player.mimicryAmount;
                baseColor = `rgb(${Math.floor(r)}, ${Math.floor(g)}, ${Math.floor(octopusRGB[2] + (rockRGB[2]-octopusRGB[2])*player.mimicryAmount)})`;
            } else {
                // 幼体:擬態中はより透明に
                baseColor = `rgba(230, 245, 255, ${0.3 * (1 - player.mimicryAmount * 0.9)})`;
            }

            // 腕と吸盤の描画
            player.arms.forEach((arm, i) => {
                ctx.strokeStyle = baseColor;
                ctx.lineWidth = currentStageIdx === 0 ? 3 : player.size * 0.7;
                ctx.beginPath(); ctx.moveTo(px, py);
                arm.segments.forEach(seg => ctx.lineTo(seg.x - camera.x, seg.y - camera.y));
                ctx.stroke();

                // 吸盤の描画(亜成体以降)
                if (currentStageIdx > 0) {
                    arm.segments.forEach((seg, idx) => {
                        if (idx % 2 === 0 && idx > 0) {
                            ctx.fillStyle = `rgba(255, 255, 255, ${0.3 * (1 - player.mimicryAmount * 0.5)})`;
                            ctx.beginPath(); ctx.arc(seg.x - camera.x, seg.y - camera.y, player.size * 0.18, 0, Math.PI*2); ctx.fill();
                        }
                    });
                }
            });

            // 本体(外套膜)の描画
            ctx.save(); ctx.translate(px, py); ctx.rotate(player.rotation);
            ctx.fillStyle = baseColor;
            ctx.beginPath(); ctx.ellipse(0, 0, player.size, player.size * 0.9, 0, 0, Math.PI * 2); ctx.fill();
            
            if (currentStageIdx === 0) {
                // 幼体のディテール:透ける内臓
                ctx.fillStyle = `rgba(150, 100, 50, ${0.7 * (1 - player.mimicryAmount)})`;
                ctx.beginPath(); ctx.ellipse(-player.size*0.2, 0, player.size*0.5, player.size*0.4, 0, 0, Math.PI*2); ctx.fill();
                // 大きな目
                const eyeA = 1 - player.mimicryAmount * 0.8;
                ctx.fillStyle = `rgba(0,0,0,${eyeA})`;
                ctx.beginPath(); ctx.arc(player.size*0.5, -player.size*0.55, player.size*0.45, 0, Math.PI*2); ctx.fill();
                ctx.beginPath(); ctx.arc(player.size*0.5, player.size*0.55, player.size*0.45, 0, Math.PI*2); ctx.fill();
                // 目のハイライト
                ctx.fillStyle = `rgba(255,255,255,${eyeA*0.8})`;
                ctx.beginPath(); ctx.arc(player.size*0.65, -player.size*0.65, 3, 0, Math.PI*2); ctx.fill();
                ctx.beginPath(); ctx.arc(player.size*0.65, player.size*0.65, 3, 0, Math.PI*2); ctx.fill();
            } else {
                // 皮膚のブツブツテクスチャ
                ctx.fillStyle = `rgba(0,0,0,${0.2 * (1 - player.mimicryAmount)})`;
                skinBumps.forEach(b => {
                    ctx.beginPath(); ctx.arc((b.x-0.5)*player.size*1.5, (b.y-0.5)*player.size*1.5, player.size*b.size, 0, Math.PI*2); ctx.fill();
                });
                // 成体の目
                const eyeA = 1 - player.mimicryAmount * 0.4;
                ctx.fillStyle = `rgba(255,255,255,${eyeA})`;
                ctx.beginPath(); ctx.arc(player.size*0.45, -player.size*0.45, player.size*0.35, 0, Math.PI*2); ctx.fill();
                ctx.beginPath(); ctx.arc(player.size*0.45, player.size*0.45, player.size*0.35, 0, Math.PI*2); ctx.fill();
                ctx.fillStyle = `rgba(0,0,0,${eyeA})`;
                ctx.beginPath(); ctx.arc(player.size*0.55, -player.size*0.45, player.size*0.18, 0, Math.PI*2); ctx.fill();
                ctx.beginPath(); ctx.arc(player.size*0.55, player.size*0.45, player.size*0.18, 0, Math.PI*2); ctx.fill();
            }
            ctx.restore();

            // 他の生物
            entities.forEach(e => {
                const ex = e.x - camera.x, ey = e.y - camera.y;
                if (ex < -300 || ex > canvas.width + 300 || ey < -300 || ey > canvas.height + 300) return;
                
                ctx.fillStyle = e.color;
                if (e.isEnemy) {
                    ctx.save(); ctx.translate(ex, ey); ctx.rotate(Math.atan2(e.vy, e.vx));
                    ctx.beginPath(); ctx.ellipse(0, 0, e.size, e.size*0.42, 0, 0, Math.PI*2); ctx.fill();
                    // ヒレ
                    ctx.beginPath(); ctx.moveTo(-e.size*0.8, 0); ctx.lineTo(-e.size*1.7, -e.size*0.65); ctx.lineTo(-e.size*1.7, e.size*0.65); ctx.closePath(); ctx.fill();
                    ctx.restore();
                } else if (e.type === 'partner') {
                    ctx.beginPath(); ctx.arc(ex, ey, e.size, 0, Math.PI*2); ctx.fill();
                    ctx.fillStyle = "#ff66aa"; ctx.font = "bold 24px Arial"; ctx.fillText("♥", ex-12, ey-e.size-15);
                } else {
                    ctx.beginPath(); ctx.arc(ex, ey, e.size, 0, Math.PI*2); ctx.fill();
                }
            });
        }

        function showMessage(txt, colorClass) {
            UI.message.innerText = txt; UI.message.className = `text-center font-black py-3 opacity-100 transition-opacity ${colorClass}`;
            setTimeout(() => UI.message.style.opacity = "0", 4000);
        }

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

        function startGame() {
            gameState = 'HATCHING'; currentStageIdx = 0; score = 0; health = 100; entities = [];
            hatchProgress = 0; partnerSpawned = false;
            player.x = 3000; player.y = 3000; player.mimicryAmount = 0;
            UI.start.classList.add('hidden'); UI.gameover.classList.add('hidden'); UI.win.classList.add('hidden');
            UI.game.classList.remove('hidden');
        }

        function gameOver() { gameState = 'GAMEOVER'; UI.game.classList.add('hidden'); UI.gameover.classList.remove('hidden'); }
        function gameWin() { gameState = 'WIN'; UI.game.classList.add('hidden'); UI.win.classList.remove('hidden'); }

        document.getElementById('start-btn').onclick = startGame;
        document.getElementById('retry-btn').onclick = startGame;
        document.getElementById('restart-btn').onclick = startGame;

        init();
    </script>
</body>
</html>

ミズダコ

ミズダコの一生を体感できます! 可愛い幼体から、大きくなっていきます!

🤖 使用したAI
Gemini
💡 工夫したこと・プロンプトのポイント
擬態できるようにしました!
👁 33 回閲覧
📅 投稿:2026年4月11日 🔄 更新:2026年8月14日
応援スタンプを押してみよう!