🔬 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">
    <title>High-Visibility Lugia Maze</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <style>
        @import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');

        body {
            margin: 0;
            padding: 0;
            background-color: #87ceeb;
            color: white;
            font-family: 'Press Start 2P', cursive;
            overflow: hidden;
            touch-action: none;
        }

        #game-canvas {
            width: 100vw;
            height: 100vh;
            display: block;
        }

        .ui-layer {
            position: fixed;
            pointer-events: none;
            z-index: 10;
            width: 100%;
            height: 100%;
        }

        .interactive {
            pointer-events: auto;
        }

        #loading-screen {
            background: #ffffff;
            z-index: 100;
            color: #003366;
        }

        .d-pad-btn {
            background: rgba(0, 51, 102, 0.6);
            backdrop-filter: blur(8px);
            border: 3px solid #ffffff;
            width: 70px;
            height: 70px;
            display: flex;
            align-items: center;
            justify-content: center;
            border-radius: 50%;
            font-size: 24px;
            color: #ffffff;
            box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
        }

        .d-pad-btn:active {
            background: rgba(0, 51, 102, 0.9);
            transform: scale(0.9);
        }

        .hud-panel {
            background: rgba(0, 51, 102, 0.85);
            border: 3px solid #ffffff;
            color: #ffffff;
            border-radius: 12px;
        }

        @keyframes title-pulse {
            0%, 100% { color: #003366; text-shadow: 0 0 5px #ffffff; }
            50% { color: #ffffff; text-shadow: 0 0 15px #003366; }
        }

        .title-anim {
            animation: title-pulse 2s ease-in-out infinite;
        }
    </style>
</head>
<body>

    <!-- Loading Screen -->
    <div id="loading-screen" class="fixed inset-0 flex flex-col items-center justify-center">
        <div class="mb-8 text-2xl font-bold title-anim">POKEMON MAZE</div>
        <div id="loading-text" class="mb-4 text-[10px] tracking-widest uppercase">Visualizing Ocean...</div>
        <div class="w-64 h-3 bg-gray-200 rounded-full overflow-hidden">
            <div id="loading-bar" class="w-0 h-full bg-blue-600 transition-all duration-300"></div>
        </div>
    </div>

    <!-- Start Screen -->
    <div id="start-screen" class="fixed inset-0 flex flex-col items-center justify-center bg-blue-900 bg-opacity-80 z-50 text-center px-4 hidden">
        <div class="bg-white p-8 rounded-3xl shadow-2xl max-w-lg w-full border-8 border-blue-400">
            <h1 class="text-xl md:text-3xl mb-8 text-blue-900 tracking-tighter">LUGIA SEARCH</h1>
            <p class="text-[10px] mb-4 text-blue-600">CHOOSE YOUR PARTNER</p>
            <div class="mb-8 flex space-x-4 overflow-x-auto p-4 interactive bg-blue-50 rounded-xl" id="character-selector"></div>
            <button id="start-btn" class="interactive bg-yellow-400 hover:bg-yellow-300 text-blue-900 px-10 py-4 rounded-full text-lg font-bold shadow-lg transition-all transform active:scale-95 border-b-4 border-yellow-600">
                START GAME
            </button>
        </div>
    </div>

    <!-- Game UI Layer -->
    <div id="game-ui" class="ui-layer hidden p-6 flex flex-col justify-between">
        <div class="flex justify-between items-start">
            <div class="hud-panel p-4 shadow-xl">
                <div class="text-[8px] opacity-80 mb-1">AREA</div>
                <div id="level-val" class="text-2xl font-bold">01</div>
            </div>
            <div class="hud-panel p-4 shadow-xl text-right">
                <div class="text-[8px] opacity-80 mb-1">TIME</div>
                <div id="timer-val" class="text-2xl font-bold">00:00</div>
            </div>
        </div>

        <!-- Mobile Controls -->
        <div class="flex justify-center mb-10 md:hidden">
            <div class="grid grid-cols-3 gap-4 interactive">
                <div></div>
                <button class="d-pad-btn" id="btn-up">↑</button>
                <div></div>
                <button class="d-pad-btn" id="btn-left">←</button>
                <button class="d-pad-btn" id="btn-down">↓</button>
                <button class="d-pad-btn" id="btn-right">→</button>
            </div>
        </div>
    </div>

    <!-- Victory Overlay -->
    <div id="victory-screen" class="fixed inset-0 bg-blue-900 bg-opacity-95 flex flex-col items-center justify-center hidden z-50">
        <h2 class="text-3xl text-white mb-8">LUGIA FOUND!</h2>
        <div id="next-poke-preview" class="relative w-64 h-64 mb-10">
            <div class="absolute inset-0 bg-white opacity-20 blur-3xl rounded-full"></div>
            <div id="victory-sprite-container" class="relative z-10 w-full h-full flex items-center justify-center bg-white bg-opacity-10 rounded-full border-4 border-white shadow-2xl"></div>
        </div>
        <button id="next-btn" class="bg-yellow-400 text-blue-900 px-12 py-5 rounded-full text-xl font-bold interactive shadow-2xl hover:bg-white transition-all">
            NEXT STAGE
        </button>
    </div>

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

    <script>
        const API_URL = 'https://pokeapi.co/api/v2/pokemon/';
        const LUGIA_ID = 249;
        const GRID_SIZE_START = 11;
        const MAX_GRID_SIZE = 21;

        let state = {
            level: 1,
            gridSize: GRID_SIZE_START,
            maze: [],
            playerPos: { x: 1, y: 1 },
            goalPos: { x: 0, y: 0 },
            playerSpriteUrl: '',
            goalSpriteUrl: '',
            characters: [],
            timer: 0,
            timerInterval: null,
            isGameActive: false
        };

        let scene, camera, renderer, playerGroup, goalGroup, mazeGroup;
        let ambientLight, sunLight, hemiLight;
        let moveTarget = new THREE.Vector3(1, 0.5, 1);
        let currentPos = new THREE.Vector3(1, 0.5, 1);
        let keys = {};
        const textureLoader = new THREE.TextureLoader();

        async function init() {
            initThree();
            try {
                updateLoading(20, "Calling Starters...");
                const starterIds = [1, 4, 7, 25, 133, 152, 155, 158];
                const promises = starterIds.map(id => fetch(API_URL + id).then(res => res.json()));
                state.characters = await Promise.all(promises);
                
                const lugiaData = await fetch(API_URL + LUGIA_ID).then(res => res.json());
                state.goalSpriteUrl = lugiaData.sprites.other['official-artwork'].front_default || lugiaData.sprites.front_default;

                updateLoading(70, "Calibrating Visibility...");
                renderCharacterSelector();
                
                updateLoading(100, "Ready!");
                setTimeout(() => {
                    document.getElementById('loading-screen').classList.add('hidden');
                    document.getElementById('start-screen').classList.remove('hidden');
                }, 800);
            } catch (err) {
                console.error("Initialization error:", err);
                document.getElementById('loading-text').innerText = "CONNECTION ERROR";
            }
        }

        function initThree() {
            scene = new THREE.Scene();
            scene.background = new THREE.Color(0xadd8e6); // Light Sky Blue for contrast
            scene.fog = new THREE.Fog(0xadd8e6, 5, 40);

            camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
            renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('game-canvas'), antialias: true });
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
            renderer.shadowMap.enabled = true;

            // Balanced Lighting for High Visibility
            ambientLight = new THREE.AmbientLight(0xffffff, 0.7);
            scene.add(ambientLight);

            hemiLight = new THREE.HemisphereLight(0xffffff, 0x003366, 0.5);
            scene.add(hemiLight);

            sunLight = new THREE.DirectionalLight(0xffffff, 0.8);
            sunLight.position.set(10, 20, 10);
            sunLight.castShadow = true;
            sunLight.shadow.camera.left = -20;
            sunLight.shadow.camera.right = 20;
            sunLight.shadow.camera.top = 20;
            sunLight.shadow.camera.bottom = -20;
            scene.add(sunLight);

            window.addEventListener('resize', onWindowResize);
            animate();
        }

        function onWindowResize() {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        }

        function updateLoading(progress, text) {
            document.getElementById('loading-bar').style.width = progress + '%';
            document.getElementById('loading-text').innerText = text;
        }

        function renderCharacterSelector() {
            const container = document.getElementById('character-selector');
            state.characters.forEach((poke, index) => {
                const div = document.createElement('div');
                div.className = `cursor-pointer p-2 border-4 ${index === 0 ? 'border-blue-600' : 'border-transparent'} hover:border-blue-300 bg-white rounded-2xl flex-shrink-0 transition-all transform hover:scale-110 shadow-md`;
                div.innerHTML = `<img src="${poke.sprites.front_default}" class="w-16 h-16" style="image-rendering:pixelated">`;
                div.onclick = () => {
                    document.querySelectorAll('#character-selector div').forEach(d => d.classList.replace('border-blue-600', 'border-transparent'));
                    div.classList.replace('border-transparent', 'border-blue-600');
                    state.playerSpriteUrl = poke.sprites.front_default;
                };
                container.appendChild(div);
                if(index === 0) state.playerSpriteUrl = poke.sprites.front_default;
            });
        }

        function generateMaze(size) {
            const maze = Array(size).fill().map(() => Array(size).fill(1));
            function walk(x, y) {
                maze[y][x] = 0;
                const dirs = [[0,-2],[0,2],[-2,0],[2,0]].sort(() => Math.random() - 0.5);
                for (let [dx, dy] of dirs) {
                    const nx = x+dx, ny = y+dy;
                    if (nx>0 && nx<size-1 && ny>0 && ny<size-1 && maze[ny][nx] === 1) {
                        maze[y+dy/2][x+dx/2] = 0;
                        walk(nx, ny);
                    }
                }
            }
            walk(1, 1);
            return maze;
        }

        async function setupLevel() {
            state.isGameActive = false;
            if (mazeGroup) scene.remove(mazeGroup);
            mazeGroup = new THREE.Group();

            state.maze = generateMaze(state.gridSize);
            state.playerPos = { x: 1, y: 1 };
            state.goalPos = { x: state.gridSize - 2, y: state.gridSize - 2 };
            state.maze[state.goalPos.y][state.goalPos.x] = 0;

            // High Contrast Walls
            const wallMat = new THREE.MeshStandardMaterial({ color: 0x003366, roughness: 0.5, metalness: 0.1 });
            const wallGeo = new THREE.BoxGeometry(1, 1.5, 1);

            // High Contrast Floor (Sand Color)
            const floorGeo = new THREE.PlaneGeometry(state.gridSize * 3, state.gridSize * 3);
            const floorMat = new THREE.MeshStandardMaterial({ color: 0xd2b48c }); // Tan/Sand color
            const floorMesh = new THREE.Mesh(floorGeo, floorMat);
            floorMesh.rotation.x = -Math.PI / 2;
            floorMesh.position.set(state.gridSize/2, 0, state.gridSize/2);
            floorMesh.receiveShadow = true;
            mazeGroup.add(floorMesh);

            for (let y = 0; y < state.gridSize; y++) {
                for (let x = 0; x < state.gridSize; x++) {
                    if (state.maze[y][x] === 1) {
                        const wall = new THREE.Mesh(wallGeo, wallMat);
                        wall.position.set(x, 0.75, y);
                        wall.castShadow = true;
                        wall.receiveShadow = true;
                        mazeGroup.add(wall);
                    }
                }
            }

            // Player Group
            playerGroup = new THREE.Group();
            const pSprite = createPokemonSprite(state.playerSpriteUrl);
            playerGroup.add(pSprite);
            playerGroup.position.set(1, 0.5, 1);
            mazeGroup.add(playerGroup);

            // Goal Group
            goalGroup = new THREE.Group();
            const gSprite = createPokemonSprite(state.goalSpriteUrl, true);
            goalGroup.add(gSprite);
            goalGroup.position.set(state.goalPos.x, 0.8, state.goalPos.y);
            mazeGroup.add(goalGroup);

            scene.add(mazeGroup);

            currentPos.set(1, 0.5, 1);
            moveTarget.copy(currentPos);
            
            document.getElementById('level-val').innerText = state.level.toString().padStart(2, '0');
            startTimer();
            state.isGameActive = true;
        }

        function createPokemonSprite(url, isGoal = false) {
            const texture = textureLoader.load(url);
            texture.magFilter = THREE.NearestFilter;
            // alphaTest solves the transparent sorting issue in 3D
            const mat = new THREE.SpriteMaterial({ map: texture, transparent: true, alphaTest: 0.5 });
            const sprite = new THREE.Sprite(mat);
            sprite.scale.set(1.5, 1.5, 1);
            if (isGoal) sprite.scale.set(4, 4, 1);
            return sprite;
        }

        function animate() {
            requestAnimationFrame(animate);
            const time = Date.now() * 0.001;

            if (state.isGameActive && playerGroup) {
                handleInput();
                
                currentPos.lerp(moveTarget, 0.15);
                playerGroup.position.copy(currentPos);

                // Clear Visibility Camera
                const offset = new THREE.Vector3(0, 7, 5);
                const camPos = currentPos.clone().add(offset);
                camera.position.lerp(camPos, 0.1);
                camera.lookAt(currentPos.x, 0, currentPos.z - 2);

                if (goalGroup) {
                    goalGroup.position.y = 1.0 + Math.sin(time * 2.5) * 0.5;
                }

                if (currentPos.distanceTo(new THREE.Vector3(state.goalPos.x, 0.5, state.goalPos.y)) < 1.0) {
                    state.isGameActive = false;
                    winLevel();
                }
            }

            renderer.render(scene, camera);
        }

        function handleInput() {
            if (currentPos.distanceTo(moveTarget) > 0.05) return;

            let dx = 0, dz = 0;
            const isUp = keys['arrowup'] || keys['w'] || keys['btn-up'];
            const isDown = keys['arrowdown'] || keys['s'] || keys['btn-down'];
            const isLeft = keys['arrowleft'] || keys['a'] || keys['btn-left'];
            const isRight = keys['arrowright'] || keys['d'] || keys['btn-right'];

            if (isUp) dz = -1;
            else if (isDown) dz = 1;
            else if (isLeft) dx = -1;
            else if (isRight) dx = 1;

            if (dx !== 0 || dz !== 0) {
                const nx = state.playerPos.x + dx;
                const nz = state.playerPos.y + dz;
                if (nx >= 0 && nx < state.gridSize && nz >= 0 && nz < state.gridSize && state.maze[nz][nx] === 0) {
                    state.playerPos.x = nx;
                    state.playerPos.y = nz;
                    moveTarget.set(nx, 0.5, nz);
                }
            }
        }

        function winLevel() {
            clearInterval(state.timerInterval);
            const overlay = document.getElementById('victory-screen');
            const container = document.getElementById('victory-sprite-container');
            container.innerHTML = `<img src="${state.goalSpriteUrl}" class="w-52 h-52">`;
            overlay.classList.remove('hidden');
        }

        async function nextLevel() {
            state.level++;
            if (state.level % 2 === 0 && state.gridSize < MAX_GRID_SIZE) state.gridSize += 2;
            document.getElementById('victory-screen').classList.add('hidden');
            await setupLevel();
        }

        function startTimer() {
            state.timer = 0;
            clearInterval(state.timerInterval);
            state.timerInterval = setInterval(() => {
                state.timer++;
                const mins = Math.floor(state.timer / 60).toString().padStart(2, '0');
                const secs = (state.timer % 60).toString().padStart(2, '0');
                document.getElementById('timer-val').innerText = `${mins}:${secs}`;
            }, 1000);
        }

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

        ['up', 'down', 'left', 'right'].forEach(dir => {
            const btn = document.getElementById(`btn-${dir}`);
            btn.addEventListener('pointerdown', (e) => { e.preventDefault(); keys[`btn-${dir}`] = true; });
            btn.addEventListener('pointerup', () => keys[`btn-${dir}`] = false);
            btn.addEventListener('pointerleave', () => keys[`btn-${dir}`] = false);
        });

        document.getElementById('start-btn').onclick = async () => {
            document.getElementById('start-screen').classList.add('hidden');
            document.getElementById('game-ui').classList.remove('hidden');
            await setupLevel();
        };

        document.getElementById('next-btn').onclick = nextLevel;

        window.onload = init;
    </script>
</body>
</html>

ポケモン迷路

ポケモンでぼうけん!

🤖 使用したAI
Gemini
👁 29 回閲覧
📅 投稿:2026年4月18日 🔄 更新:2026年8月13日
応援スタンプを押してみよう!