簡単な対戦ゲーム「MagicalMaze」



【更新履歴】

 ・2026/5/18 バージョン1.0公開。
 ・2026/5/18 バージョン1.1公開。
 ・2026/5/18 バージョン1.2公開。
 ・2026/5/18 バージョン1.3公開。(マルチプレイモード追加)

画像
シングルプレイモード( 対CPU )
画像
マルチプレイモード( 対人間 )

・ダウンロードされる方はこちら。↓

【操作説明】


・方向キーで移動する。
・Aキーで取得した魔法を発動、もう一度押すと発射する。
・制限時間内に、より多くのスコアを得たほうが勝ち。
・あるいは、相手のHPを、先にゼロにしたほうが勝ち。
・魔法が当たった障害物が同じ属性なら破壊される。
・同じ属性の魔法を続けて取得すると、攻撃範囲が広くなる。(最大3回)
・相手の攻撃が当たっても、同じ魔法を持っていたらノーダメージ。
・または、同じ属性の障害物の跡に乗っている時は、
 1/2の確率でノーダメージ。

【マルチプレイ時の操作】

・プレイヤー2は、テンキーで移動。スペースキーで魔法発動。

・このゲームで遊ぶには、いつものテスト用ブラウザが必要です。↓

・ソースコードはこちら。↓

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Magical Maze</title>
    <style>
        body {
            margin: 0;
            padding: 0;
            background-color: #111;
            color: #fff;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            overflow: hidden;
        }
        #gameContainer {
            position: relative;
            box-shadow: 0 0 20px rgba(0, 0, 0, 0.8);
        }
        canvas {
            display: block;
            background-color: #222;
        }
    </style>
</head>
<body>
    <div id="gameContainer">
        <canvas id="gameCanvas" width="800" height="680" tabindex="1"></canvas>
    </div>
    <script>
        /**
         * Magical Maze - Main Game Logic (Integrated)
         */

        const canvas = document.getElementById('gameCanvas');
        const ctx = canvas.getContext('2d');
        canvas.focus();

        // --- Game Constants ---
        const TS = 40; 
        const COLS = 20;
        const ROWS = 13;
        const TIME_LIMIT = 180;
        const MAX_STAGES = 8;
        const UI_HEIGHT = 160; 

        // --- Asset Management ---
        const ASSETS = { images: {}, audio: {} };
        const AUDIO_CTX = new (window.AudioContext || window.webkitAudioContext)();
        let currentBgmAudio = null;

        function loadImg(folder, name) {
            const path = `${folder}/${name}.png`;
            if (!ASSETS.images[path]) {
                const img = new Image();
                img.src = path;
                ASSETS.images[path] = { img, loaded: false, error: false };
                img.onload = () => ASSETS.images[path].loaded = true;
                img.onerror = () => ASSETS.images[path].error = true;
            }
        }

        function playBGM(folder, name) {
            if (currentBgmAudio) {
                currentBgmAudio.pause();
                currentBgmAudio = null;
            }
            const path = `${folder}/${name}.mp3`;
            if (!ASSETS.audio[path]) {
                const audio = new Audio(path);
                audio.loop = true;
                audio.volume = 0.4;
                ASSETS.audio[path] = { audio: audio, loaded: false, error: false };
                audio.oncanplaythrough = () => { ASSETS.audio[path].loaded = true; };
                audio.onerror = () => { ASSETS.audio[path].error = true; };
            }
            if (!ASSETS.audio[path].error) {
                currentBgmAudio = ASSETS.audio[path].audio;
                currentBgmAudio.currentTime = 0;
                currentBgmAudio.play().catch(() => {});
            }
        }

        function playSound(folder, name, type = 'beep') {
            const path = `${folder}/${name}.mp3`;
            if (ASSETS.audio[path] && ASSETS.audio[path].loaded && !ASSETS.audio[path].error) {
                const clone = ASSETS.audio[path].audio.cloneNode();
                clone.volume = 0.6;
                clone.play().catch(() => playFallbackSound(type));
                return;
            }
            if (!ASSETS.audio[path]) {
                const audio = new Audio(path);
                ASSETS.audio[path] = { audio: audio, loaded: false, error: false };
                audio.oncanplaythrough = () => { ASSETS.audio[path].loaded = true; };
                audio.onerror = () => {
                    ASSETS.audio[path].error = true;
                    playFallbackSound(type);
                };
                audio.volume = 0.6;
                audio.play().catch(() => {
                    ASSETS.audio[path].error = true;
                    playFallbackSound(type);
                });
                return;
            }
            if (ASSETS.audio[path].error) {
                playFallbackSound(type);
            }
        }

        function playFallbackSound(type) {
            if (AUDIO_CTX.state === 'suspended') {
                AUDIO_CTX.resume().catch(() => {});
            }
            const gain = AUDIO_CTX.createGain();
            gain.connect(AUDIO_CTX.destination);

            if (type === 'break') {
                const bufferSize = Math.floor(AUDIO_CTX.sampleRate * 0.2);
                const buffer = AUDIO_CTX.createBuffer(1, bufferSize, AUDIO_CTX.sampleRate);
                const data = buffer.getChannelData(0);
                for (let i = 0; i < bufferSize; i++) data[i] = Math.random() * 2 - 1;
                const noise = AUDIO_CTX.createBufferSource();
                noise.buffer = buffer;
                const filter = AUDIO_CTX.createBiquadFilter();
                filter.type = 'lowpass'; filter.frequency.value = 800;
                noise.connect(filter); filter.connect(gain);
                gain.gain.setValueAtTime(0.3, AUDIO_CTX.currentTime);
                gain.gain.exponentialRampToValueAtTime(0.01, AUDIO_CTX.currentTime + 0.2);
                noise.start();
                return;
            }

            const osc = AUDIO_CTX.createOscillator();
            osc.connect(gain);

            if (type === 'magic_cast') {
                osc.type = 'sine'; osc.frequency.setValueAtTime(400, AUDIO_CTX.currentTime);
                osc.frequency.exponentialRampToValueAtTime(800, AUDIO_CTX.currentTime + 0.1);
                gain.gain.setValueAtTime(0.1, AUDIO_CTX.currentTime);
            } else if (type === 'damage') {
                osc.type = 'sawtooth'; osc.frequency.setValueAtTime(150, AUDIO_CTX.currentTime);
                osc.frequency.exponentialRampToValueAtTime(50, AUDIO_CTX.currentTime + 0.2);
                gain.gain.setValueAtTime(0.1, AUDIO_CTX.currentTime);
            } else if (type === 'get_food' || type === 'heal' || type === 'speedup') {
                osc.type = 'square';
                let freq = type === 'heal' ? 1000 : (type === 'speedup' ? 1200 : 800);
                osc.frequency.setValueAtTime(freq, AUDIO_CTX.currentTime);
                osc.frequency.setValueAtTime(freq + 400, AUDIO_CTX.currentTime + 0.05);
                gain.gain.setValueAtTime(0.05, AUDIO_CTX.currentTime);
            } else if (type === 'move') {
                osc.type = 'triangle'; osc.frequency.setValueAtTime(300, AUDIO_CTX.currentTime);
                gain.gain.setValueAtTime(0.02, AUDIO_CTX.currentTime);
                osc.start(); osc.stop(AUDIO_CTX.currentTime + 0.05); return;
            } else if (type === 'dont_move') {
                osc.type = 'square'; osc.frequency.setValueAtTime(100, AUDIO_CTX.currentTime);
                gain.gain.setValueAtTime(0.05, AUDIO_CTX.currentTime);
                osc.start(); osc.stop(AUDIO_CTX.currentTime + 0.1); return;
            } else if (type === 'pageup') {
                osc.type = 'sine'; osc.frequency.setValueAtTime(880, AUDIO_CTX.currentTime);
                gain.gain.setValueAtTime(0.1, AUDIO_CTX.currentTime);
            } else {
                osc.type = 'triangle'; osc.frequency.setValueAtTime(440, AUDIO_CTX.currentTime);
                gain.gain.setValueAtTime(0.05, AUDIO_CTX.currentTime);
            }

            osc.start(); osc.stop(AUDIO_CTX.currentTime + 0.2);
        }

        // --- Pre-load Assets ---
        const dirs = ['left', 'right', 'up', 'down'];
        dirs.forEach(d => { loadImg('Player', d); loadImg('Enemy', d); });
        loadImg('Player', 'Player'); loadImg('Enemy', 'Enemy');
        loadImg('Road', 'default');
        
        ['title', 'stage_start', 'stage_clear', 'game_clear', 'game_over'].forEach(s => {
            loadImg('Screen', s);
        });

        const VALID_ITEM_PATHS = { food: [], heal: [], speed: [] };

        function loadItemImages() {
            const types = ['food', 'heal', 'speed'];
            types.forEach(type => {
                const folder = type === 'food' ? 'Food' : type === 'heal' ? 'Heal' : 'SpeedUp';
                const namesToCheck = [type, type.charAt(0).toUpperCase() + type.slice(1)]; 
                if (type === 'speed') { namesToCheck.push('speedup', 'SpeedUp'); }

                for (let i = 1; i <= 20; i++) {
                    namesToCheck.push(`${i}`);
                    namesToCheck.push(`${type}${i}`);
                    namesToCheck.push(`${type.charAt(0).toUpperCase() + type.slice(1)}${i}`);
                }

                namesToCheck.forEach(name => {
                    const path = `${folder}/${name}.png`;
                    if (!ASSETS.images[path]) {
                        const img = new Image();
                        img.src = path;
                        ASSETS.images[path] = { img, loaded: false, error: false };
                        img.onload = () => {
                            ASSETS.images[path].loaded = true;
                            if (!VALID_ITEM_PATHS[type].includes(path)) {
                                VALID_ITEM_PATHS[type].push(path);
                            }
                        };
                        img.onerror = () => { ASSETS.images[path].error = true; };
                    }
                });
            });
        }
        loadItemImages();

        // --- Input Handling ---
        const keys = {};
        document.addEventListener('keydown', e => {
            if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', ' '].includes(e.key)) {
                e.preventDefault();
            }
            keys[e.key] = true;
            if (e.key === 'Escape') togglePause();
            if (gameState === 'TITLE') {
                if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
                    mode = mode === 1 ? 2 : 1;
                    playSound('Sound', 'move', 'move');
                }
            }
        });

        document.addEventListener('keyup', e => {
            keys[e.key] = false;
            if (gameState !== 'PLAY' && gameState !== 'PAUSE') {
                if (gameState === 'TITLE' && (e.key === 'ArrowUp' || e.key === 'ArrowDown' || e.key.toLowerCase() === 'm')) {
                    return;
                }
                handleScreenTransition();
            }
        });

        canvas.addEventListener('mouseup', () => {
            if (gameState !== 'PLAY' && gameState !== 'PAUSE') {
                handleScreenTransition();
            }
        });

        // --- State Variables ---
        let gameState = 'STARTUP';
        let mode = 1;
        let stage = 1;
        let timeLeft = TIME_LIMIT;
        let lastTime = 0;
        let map = [];
        let items = [];
        let players = [];
        let particles = [];
        let meteors = [];
        let fallingItems = [];

        // 新規追加: 勝点とステージ勝者記録
        let p1Wins = 0;
        let p2Wins = 0;
        let lastStageWinner = 0;

        const MAGIC_TYPES = [
            { name: 'water', color: 'cyan' },
            { name: 'fire', color: 'red' },
            { name: 'earth', color: 'saddlebrown' },
            { name: 'ice', color: 'blue' },
            { name: 'wind', color: 'green' },
            { name: 'thunder', color: 'yellow' }
        ];
        const HURDLE_TYPES = [...MAGIC_TYPES];

        // --- Classes ---
        class FallingItem {
            constructor(x, y, type, data) {
                this.targetX = x; this.targetY = y;
                this.pixelX = x * TS + TS / 2; this.pixelY = y * TS + TS / 2;
                this.currentY = -TS; this.speed = 400;
                this.type = type; this.data = data; this.hit = false;
            }
            update(dt) {
                if (this.hit) return;
                this.currentY += this.speed * dt;
                if (this.currentY >= this.pixelY) {
                    this.currentY = this.pixelY; this.hit = true;
                    items.push({ x: this.targetX, y: this.targetY, type: this.type, magicData: this.data, rndIdx: Math.floor(Math.random() * 10000) });
                }
            }
            draw(ctx) {
                if (this.hit) return;
                if (this.type === 'magic') {
                    const path = `Magic/${this.data.name}.png`;
                    if (ASSETS.images[path] && ASSETS.images[path].loaded) {
                        ctx.drawImage(ASSETS.images[path].img, this.pixelX - TS/2, this.currentY - TS/2, TS, TS);
                    } else {
                        ctx.beginPath(); ctx.arc(this.pixelX, this.currentY, TS/4, 0, Math.PI*2);
                        ctx.fillStyle = this.data.color; ctx.fill(); ctx.closePath();
                    }
                }
            }
        }

        class Particle {
            constructor(x, y, color) {
                this.x = x; this.y = y; this.color = color;
                const angle = Math.random() * Math.PI * 2;
                const speed = Math.random() * 150 + 50; 
                this.vx = Math.cos(angle) * speed; this.vy = Math.sin(angle) * speed;
                this.life = 0.5 + Math.random() * 0.3; this.maxLife = this.life;
                this.size = Math.random() * 6 + 3;
            }
            update(dt) {
                this.vy += 400 * dt; 
                this.x += this.vx * dt; this.y += this.vy * dt;
                this.life -= dt;
            }
            draw(ctx) {
                const alpha = Math.max(0, this.life / this.maxLife);
                ctx.globalAlpha = alpha; ctx.fillStyle = this.color;
                ctx.fillRect(this.x - this.size/2, this.y - this.size/2, this.size, this.size);
                ctx.globalAlpha = 1.0;
            }
        }

        class Meteor {
            constructor(ax, ay, magic, owner) {
                this.targetX = ax; this.targetY = ay;
                this.pixelX = ax * TS + TS / 2; this.pixelY = ay * TS + TS / 2;
                this.currentX = this.pixelX; this.currentY = -TS;
                this.speed = 900 + Math.random() * 200;
                this.magic = magic; this.owner = owner; this.hit = false;
            }
            update(dt) {
                if (this.hit) return;
                this.currentY += this.speed * dt;
                if (this.currentY >= this.pixelY) {
                    this.currentY = this.pixelY; this.hit = true;
                    handleMeteorHit(this);
                }
            }
            draw(ctx) {
                if (this.hit) return;
                const path = `Magic/${this.magic.name}.png`;
                if (ASSETS.images[path] && ASSETS.images[path].loaded) {
                    ctx.drawImage(ASSETS.images[path].img, this.currentX - TS/2, this.currentY - TS/2, TS, TS);
                } else {
                    ctx.beginPath(); ctx.arc(this.currentX, this.currentY, TS/4, 0, Math.PI*2);
                    ctx.fillStyle = this.magic.color; ctx.fill(); ctx.closePath();
                }
                ctx.beginPath(); ctx.moveTo(this.currentX, this.currentY - TS/4);
                ctx.lineTo(this.currentX, this.currentY - TS*1.5);
                ctx.strokeStyle = this.magic.color; ctx.lineWidth = 4;
                ctx.stroke(); ctx.closePath();
            }
        }

        class Entity {
            constructor(x, y, isPlayer1) {
                this.x = x; this.y = y; this.targetX = x; this.targetY = y;
                this.isPlayer1 = isPlayer1;
                this.hp = 100; this.maxHp = 100; this.score = 0;
                this.magic = null; this.magicLevel = 1; 
                this.lifting = false; this.attackArea = [];
                this.steps = 0; this.speedTimer = 0; this.hesitateTimer = 0;
                this.moving = false; this.moveTimer = 0; this.dir = 'down';
            }
            update(dt) {
                if (this.speedTimer > 0) this.speedTimer -= dt;
                if (this.moving) {
                    this.moveTimer += dt;
                    let speedThreshold = this.speedTimer > 0 ? 0.1 : 0.2; 
                    if (this.moveTimer >= speedThreshold) {
                        this.x = this.targetX; this.y = this.targetY;
                        this.moving = false; this.moveTimer = 0; this.steps++;
                        checkItemPickup(this);
                    }
                }
            }
            move(dx, dy) {
                if (this.moving) return; 
                const nx = this.x + dx; const ny = this.y + dy;
                if (dx > 0) this.dir = 'right'; if (dx < 0) this.dir = 'left';
                if (dy > 0) this.dir = 'down'; if (dy < 0) this.dir = 'up';

                if (nx >= 0 && nx < COLS && ny >= 0 && ny < ROWS) {
                    const cell = map[ny][nx];
                    if (cell.type === 'road' || cell.type === 'start') {
                        const other = players.find(p => p !== this && p.targetX === nx && p.targetY === ny);
                        if (!other) {
                            this.targetX = nx; this.targetY = ny;
                            this.moving = true;
                            playSound('Sound', 'move', 'move');
                        } else playSound('Sound', 'dont_move', 'dont_move');
                    } else playSound('Sound', 'dont_move', 'dont_move');
                } else playSound('Sound', 'dont_move', 'dont_move');
            }
            draw(ctx) {
                let drawX = this.x * TS; let drawY = this.y * TS; let jumpOffset = 0;
                if (this.moving) {
                    let speedThreshold = this.speedTimer > 0 ? 0.1 : 0.2;
                    const progress = this.moveTimer / speedThreshold;
                    drawX += (this.targetX - this.x) * TS * progress;
                    drawY += (this.targetY - this.y) * TS * progress;
                    jumpOffset = Math.sin(progress * Math.PI) * 15;
                }
                const folder = this.isPlayer1 ? 'Player' : 'Enemy';
                const color = this.isPlayer1 ? (mode === 2 ? 'blue' : 'cyan') : (mode === 2 ? 'red' : 'purple');
                const dirPath = `${folder}/${this.dir}.png`; const defPath = `${folder}/${folder}.png`;
                
                let imgToDraw = null;
                if (ASSETS.images[dirPath] && ASSETS.images[dirPath].loaded) imgToDraw = ASSETS.images[dirPath].img;
                else if (ASSETS.images[defPath] && ASSETS.images[defPath].loaded) imgToDraw = ASSETS.images[defPath].img;

                drawY -= jumpOffset;
                if (imgToDraw) ctx.drawImage(imgToDraw, drawX, drawY, TS, TS);
                else {
                    ctx.beginPath(); ctx.arc(drawX + TS/2, drawY + TS/2, TS/2.2, 0, Math.PI*2);
                    ctx.fillStyle = color; ctx.fill(); ctx.closePath();
                }

                if (this.lifting && this.magic) {
                    ctx.beginPath(); ctx.arc(drawX + TS/2, drawY - 15, TS/5, 0, Math.PI*2);
                    ctx.fillStyle = this.magic.color; ctx.fill(); ctx.closePath();
                }
            }
        }

        // --- Map & Logic ---
        function initGame() {
            map = []; items = []; particles = []; meteors = []; fallingItems = [];
            players = [new Entity(0, 0, true), new Entity(COLS - 1, ROWS - 1, false)];
            timeLeft = TIME_LIMIT;

            for (let y = 0; y < ROWS; y++) {
                let row = [];
                for (let x = 0; x < COLS; x++) row.push({ type: 'road', name: 'normal', color: 'lightgreen' });
                map.push(row);
            }

            for (let y = 0; y < ROWS; y++) {
                for (let x = 0; x < COLS; x++) {
                    if (Math.random() < 0.25) {
                        const h = HURDLE_TYPES[Math.floor(Math.random() * HURDLE_TYPES.length)];
                        map[y][x] = { type: 'hurdle', name: h.name, color: h.color };
                        loadImg('Hurdle', h.name);
                    }
                }
            }

            map[0][0] = { type: 'start', name: 'normal', color: 'lightgreen' };
            map[0][1] = { type: 'road', name: 'normal', color: 'lightgreen' };
            map[1][0] = { type: 'road', name: 'normal', color: 'lightgreen' };
            map[ROWS-1][COLS-1] = { type: 'start', name: 'normal', color: 'lightgreen' };
            map[ROWS-1][COLS-2] = { type: 'road', name: 'normal', color: 'lightgreen' };
            map[ROWS-2][COLS-1] = { type: 'road', name: 'normal', color: 'lightgreen' };

            let cx = 0, cy = 0;
            while (cx < COLS - 1 || cy < ROWS - 1) {
                if (!(cx === 0 && cy === 0)) map[cy][cx] = { type: 'road', name: 'normal', color: 'lightgreen' };
                if (cx === COLS - 1) cy++; else if (cy === ROWS - 1) cx++;
                else Math.random() < 0.5 ? cx++ : cy++;
            }

            spawnItems();
        }

        function spawnItems() {
            items = [];
            for(let i=0; i<4; i++) spawnRandomItem('food');
            for(let i=0; i<3; i++) spawnRandomItem('magic');
            for(let i=0; i<2; i++) spawnRandomItem('heal');
            for(let i=0; i<2; i++) spawnRandomItem('speed');
        }

        function spawnRandomItem(type) {
            let rx, ry, attempts = 0;
            do {
                rx = Math.floor(Math.random() * COLS); ry = Math.floor(Math.random() * ROWS);
                attempts++; if (attempts > 500) break;
            } while (map[ry][rx].type === 'hurdle' || items.some(i => i.x === rx && i.y === ry) || map[ry][rx].type === 'start');

            if (attempts > 500) return;

            if (['food', 'heal', 'speed'].includes(type)) {
                items.push({ x: rx, y: ry, type: type, rndIdx: Math.floor(Math.random() * 10000) });
            } else if (type === 'magic') {
                const m = MAGIC_TYPES[Math.floor(Math.random() * MAGIC_TYPES.length)];
                items.push({ x: rx, y: ry, type: 'magic', magicData: m, rndIdx: Math.floor(Math.random() * 10000) });
                loadImg('Magic', m.name);
            }
        }

        function spawnSpecificMagic(magicData) {
            if (items.some(i => i.type === 'magic' && i.magicData.name === magicData.name)) return;
            let rx, ry, attempts = 0;
            do {
                rx = Math.floor(Math.random() * COLS); ry = Math.floor(Math.random() * ROWS);
                attempts++; if (attempts > 500) break;
            } while (map[ry][rx].type === 'hurdle' || items.some(i => i.x === rx && i.y === ry) || map[ry][rx].type === 'start');
            if (attempts <= 500) {
                items.push({ x: rx, y: ry, type: 'magic', magicData: magicData, rndIdx: Math.floor(Math.random() * 10000) });
                loadImg('Magic', magicData.name);
            }
        }

        function checkItemPickup(player) {
            const idx = items.findIndex(i => i.x === player.x && i.y === player.y);
            if (idx !== -1) {
                const item = items[idx];
                if (item.type === 'food') {
                    player.score += 100 + (player.steps * 10); player.steps = 0;
                    playSound('Sound', 'get_food', 'get_food');
                    items.splice(idx, 1); spawnRandomItem('food');
                } else if (item.type === 'magic') {
                    if (player.magic && player.magic.name === item.magicData.name) {
                        player.magicLevel = Math.min(3, player.magicLevel + 1);
                    } else {
                        player.magic = item.magicData;
                        player.magicLevel = 1;
                    }
                    playSound('Sound', 'magic_get', 'get_food');
                    items.splice(idx, 1); spawnSpecificMagic(item.magicData); spawnRandomItem('magic');
                } else if (item.type === 'heal') {
                    player.hp = Math.min(player.maxHp, player.hp + 30);
                    playSound('Sound', 'heal', 'heal');
                    items.splice(idx, 1); spawnRandomItem('heal');
                } else if (item.type === 'speed') {
                    player.speedTimer = 10; 
                    playSound('Sound', 'speedup', 'speedup');
                    items.splice(idx, 1); spawnRandomItem('speed');
                }
            }
        }

        function generateAttackArea(level) {
            const area = [];
            for (let dy = -level; dy <= level; dy++) {
                for (let dx = -level; dx <= level; dx++) {
                    if (Math.abs(dx) + Math.abs(dy) <= level && Math.random() < 0.7) area.push({ dx: dx, dy: dy });
                }
            }
            return area;
        }

        function executeMagic(player) {
            playSound('Sound', 'magic_cast', 'magic_cast'); 
            const magic = player.magic;
            player.attackArea.forEach(cell => {
                const ax = player.targetX + cell.dx; const ay = player.targetY + cell.dy;
                if (ax >= 0 && ax < COLS && ay >= 0 && ay < ROWS) meteors.push(new Meteor(ax, ay, magic, player));
            });
            player.magic = null; player.magicLevel = 1; player.lifting = false; player.attackArea = [];
        }

        function setStageClear() {
            if (stage >= MAX_STAGES) {
                gameState = 'GAME_CLEAR'; playBGM('ScreenBgm', 'game_clear');
            } else {
                gameState = 'STAGE_CLEAR'; playBGM('ScreenBgm', 'stage_clear');
            }
        }

        function setGameOver() {
            gameState = 'GAME_OVER'; playBGM('ScreenBgm', 'game_over');
        }

        function handleMeteorHit(m) {
            const ax = m.targetX; const ay = m.targetY;
            const magic = m.magic; const player = m.owner;

            for (let i = 0; i < 15; i++) particles.push(new Particle(ax * TS + TS/2, ay * TS + TS/2, magic.color));
            
            let mapCell = map[ay][ax];
            let hitSomething = false;
            
            if (mapCell.type === 'hurdle') {
                hitSomething = true;
                let isDestructible = (mapCell.name === 'water' && magic.name === 'ice') || mapCell.name === magic.name;
                if (isDestructible) {
                    map[ay][ax] = { type: 'road', name: magic.name, color: magic.color };
                    loadImg('Road', magic.name);
                    playSound('Sound', 'break', 'break');
                    for (let i = 0; i < 15; i++) particles.push(new Particle(ax * TS + TS/2, ay * TS + TS/2, mapCell.color));
                } else playSound('Sound', 'damage', 'damage'); 
            }
            
            players.forEach(target => {
                const tx1 = Math.round(target.x); const ty1 = Math.round(target.y);
                const tx2 = target.targetX; const ty2 = target.targetY;

                if (target !== player && ((tx1 === ax && ty1 === ay) || (tx2 === ax && ty2 === ay))) {
                    hitSomething = true;
                    let tCell1 = map[ty1][tx1]; let tCell2 = map[ty2][tx2];
                    let onSameAttribute = (tCell1.type === 'road' && tCell1.name === magic.name) || 
                                          (tCell2.type === 'road' && tCell2.name === magic.name);

                    if (onSameAttribute && Math.random() < 0.5) {
                        // Dodge
                    } else if (!target.magic || target.magic.name !== magic.name) {
                        target.hp -= 30;
                        playSound('Sound', 'damage', 'damage');
                        for (let i = 0; i < 15; i++) {
                            const pColor = target.isPlayer1 ? (mode === 2 ? 'blue' : 'cyan') : (mode === 2 ? 'red' : 'purple');
                            particles.push(new Particle(ax * TS + TS/2, ay * TS + TS/2, pColor));
                        }
                        
                        if (target.hp <= 0 && gameState === 'PLAY') {
                            if (target.isPlayer1) {
                                lastStageWinner = 2; p2Wins++;
                            } else {
                                lastStageWinner = 1; p1Wins++;
                            }
                            setStageClear();
                        }
                    }
                }
            });

            if(!hitSomething) playSound('Sound', 'damage', 'damage');
        }

        function findPath(startX, startY, targetType, targetEntity) {
            let queue = [{x: startX, y: startY, path: []}];
            let visited = Array.from({length: ROWS}, () => Array(COLS).fill(false));
            visited[startY][startX] = true;

            while(queue.length > 0) {
                let curr = queue.shift();
                let found = false;
                if (['magic', 'food', 'heal', 'speed'].includes(targetType)) {
                    if (items.some(i => i.type === targetType && i.x === curr.x && i.y === curr.y)) found = true;
                } else if (targetType === 'player') {
                    if (curr.x === targetEntity.targetX && curr.y === targetEntity.targetY) found = true;
                }

                if (found) return curr.path.length > 0 ? curr.path[0] : null;

                const dirs = [{dx: 0, dy: -1}, {dx: 0, dy: 1}, {dx: -1, dy: 0}, {dx: 1, dy: 0}];
                for (let d of dirs) {
                    let nx = curr.x + d.dx; let ny = curr.y + d.dy;
                    if (nx >= 0 && nx < COLS && ny >= 0 && ny < ROWS) {
                        if (!visited[ny][nx] && (map[ny][nx].type === 'road' || map[ny][nx].type === 'start')) {
                            visited[ny][nx] = true;
                            queue.push({x: nx, y: ny, path: [...curr.path, d]});
                        }
                    }
                }
            }
            return null;
        }

        function checkTrapped(player) {
            if (player.moving) return;
            const dirs = [{dx: 0, dy: -1}, {dx: 0, dy: 1}, {dx: -1, dy: 0}, {dx: 1, dy: 0}];
            let trapped = true; let adjacentHurdles = [];
            
            for (let d of dirs) {
                let nx = player.targetX + d.dx; let ny = player.targetY + d.dy;
                if (nx >= 0 && nx < COLS && ny >= 0 && ny < ROWS) {
                    let cell = map[ny][nx];
                    if (cell.type !== 'hurdle') { trapped = false; break; } 
                    else adjacentHurdles.push(cell);
                }
            }

            if (trapped && adjacentHurdles.length > 0) {
                if (player.magic) {
                    let canBreak = adjacentHurdles.some(h => (h.name === 'water' && player.magic.name === 'ice') || h.name === player.magic.name);
                    if (canBreak) return; 
                }
                if (items.some(i => i.x === player.targetX && i.y === player.targetY && i.type === 'magic')) return;
                if (fallingItems.some(f => f.targetX === player.targetX && f.targetY === player.targetY)) return;

                let targetHurdle = adjacentHurdles[Math.floor(Math.random() * adjacentHurdles.length)];
                let requiredMagicName = targetHurdle.name === 'water' ? 'ice' : targetHurdle.name;
                let mData = MAGIC_TYPES.find(m => m.name === requiredMagicName);
                
                if (mData) {
                    fallingItems.push(new FallingItem(player.targetX, player.targetY, 'magic', mData));
                    loadImg('Magic', mData.name);
                }
            }
        }

        // --- Main Loop ---
        function update(dt) {
            if (gameState === 'STARTUP') {
                gameState = 'TITLE'; playBGM('ScreenBgm', 'title'); return;
            }
            if (gameState !== 'PLAY') return;

            timeLeft -= dt;
            if (timeLeft <= 0) {
                if (gameState === 'PLAY') {
                    if (players[0].score > players[1].score) { lastStageWinner = 1; p1Wins++; }
                    else if (players[1].score > players[0].score) { lastStageWinner = 2; p2Wins++; }
                    else { lastStageWinner = 0; }
                    
                    setStageClear();
                }
                return;
            }

            let p1 = players[0];
            if (keys['ArrowUp']) p1.move(0, -1);
            else if (keys['ArrowDown']) p1.move(0, 1);
            else if (keys['ArrowLeft']) p1.move(-1, 0);
            else if (keys['ArrowRight']) p1.move(1, 0);

            if (keys['a'] || keys['A']) {
                if (!keys['a_handled']) {
                    if (!p1.lifting && p1.magic) {
                        p1.lifting = true; p1.attackArea = generateAttackArea(p1.magicLevel);
                        playSound('Sound', 'magic_cast', 'magic_cast');
                    } else if (p1.lifting) executeMagic(p1);
                    keys['a_handled'] = true;
                }
            } else keys['a_handled'] = false;

            let p2 = players[1];
            if (mode === 2) {
                // Numpad movement + 4,8,2,6 fallback
                if (keys['8'] || keys['Numpad8']) p2.move(0, -1);
                else if (keys['2'] || keys['Numpad2']) p2.move(0, 1);
                else if (keys['4'] || keys['Numpad4']) p2.move(-1, 0);
                else if (keys['6'] || keys['Numpad6']) p2.move(1, 0);

                if (keys[' '] || keys['Spacebar']) {
                    if (!keys['space_handled']) {
                        if (!p2.lifting && p2.magic) {
                            p2.lifting = true; p2.attackArea = generateAttackArea(p2.magicLevel);
                            playSound('Sound', 'magic_cast', 'magic_cast');
                        } else if (p2.lifting) executeMagic(p2);
                        keys['space_handled'] = true;
                    }
                } else keys['space_handled'] = false;
            } else {
                if (p2.lifting) {
                    if (p2.hesitateTimer > 0) p2.hesitateTimer -= dt;
                    else executeMagic(p2);
                } else if (!p2.moving && Math.random() < 0.15 + (stage * 0.05)) {
                    let actionTaken = false;
                    if (p2.magic && !p2.lifting) {
                        let tempArea = generateAttackArea(p2.magicLevel);
                        let inRange = tempArea.some(cell => {
                            let ax = p2.targetX + cell.dx; let ay = p2.targetY + cell.dy;
                            return (ax === p1.targetX && ay === p1.targetY) || (ax === p1.x && ay === p1.y);
                        });
                        if (inRange) {
                            p2.lifting = true; p2.attackArea = tempArea;
                            playSound('Sound', 'magic_cast', 'magic_cast');
                            if (Math.random() < 0.5) p2.hesitateTimer = 1.0 + Math.random();
                            else p2.hesitateTimer = 0;
                            actionTaken = true;
                        }
                    }
                    if (!actionTaken && !p2.lifting) {
                        let nextMove = null;
                        if (!p2.magic) nextMove = findPath(p2.targetX, p2.targetY, 'magic', null);
                        if (!nextMove && p2.hp < 70) nextMove = findPath(p2.targetX, p2.targetY, 'heal', null);
                        if (!nextMove) nextMove = findPath(p2.targetX, p2.targetY, 'food', null);
                        if (!nextMove) nextMove = findPath(p2.targetX, p2.targetY, 'player', p1);

                        if (nextMove) p2.move(nextMove.dx, nextMove.dy);
                        else {
                            const dirs = [{dx: 0, dy: -1}, {dx: 0, dy: 1}, {dx: -1, dy: 0}, {dx: 1, dy: 0}];
                            let rm = dirs[Math.floor(Math.random()*dirs.length)];
                            p2.move(rm.dx, rm.dy);
                        }
                    }
                }
            }

            players.forEach(p => p.update(dt));
            players.forEach(p => checkTrapped(p));
            meteors.forEach(m => m.update(dt));
            meteors = meteors.filter(m => !m.hit); 
            fallingItems.forEach(f => f.update(dt));
            fallingItems = fallingItems.filter(f => !f.hit);
            particles.forEach(p => p.update(dt));
            particles = particles.filter(p => p.life > 0);
        }

        function draw() {
            ctx.fillStyle = '#000'; ctx.fillRect(0, 0, canvas.width, canvas.height);

            if (gameState === 'TITLE') {
                const path = `Screen/title.png`;
                if (ASSETS.images[path] && ASSETS.images[path].loaded) {
                    ctx.drawImage(ASSETS.images[path].img, 0, 0, canvas.width, canvas.height);
                } else {
                    ctx.fillStyle = '#111'; ctx.fillRect(0, 0, canvas.width, canvas.height);
                    ctx.fillStyle = '#fff'; ctx.textAlign = 'center';
                    ctx.font = '50px sans-serif'; ctx.fillText('Magical Maze', 400, 250);
                }
                
                ctx.fillStyle = 'rgba(0,0,0,0.8)'; ctx.strokeStyle = '#fff'; ctx.lineWidth = 2;
                ctx.fillRect(150, 400, 500, 120); ctx.strokeRect(150, 400, 500, 120);
                
                ctx.fillStyle = '#fff'; ctx.textAlign = 'left'; ctx.font = '24px sans-serif';
                ctx.fillText("Select Mode:", 170, 435);
                ctx.fillText(mode === 1 ? "▶ SINGLE MODE (CPU対戦)" : "   SINGLE MODE (CPU対戦)", 190, 470);
                ctx.fillText(mode === 2 ? "▶ MULTI MODE (人間同士で対戦)" : "   MULTI MODE (人間同士で対戦)", 190, 500);
                return;
            }

            if (gameState === 'STAGE_START' || gameState === 'STAGE_CLEAR' || gameState === 'GAME_OVER' || gameState === 'GAME_CLEAR') {
                let bgName = gameState === 'STAGE_START' ? 'stage_start' : 
                             gameState === 'STAGE_CLEAR' ? 'stage_clear' :
                             gameState === 'GAME_CLEAR' ? 'game_clear' : 'game_over';

                const path = `Screen/${bgName}.png`;
                if (ASSETS.images[path] && ASSETS.images[path].loaded) {
                    ctx.drawImage(ASSETS.images[path].img, 0, 0, canvas.width, canvas.height);
                    ctx.fillStyle = 'rgba(0,0,0,0.6)'; ctx.fillRect(0, 0, canvas.width, canvas.height);
                } else {
                    ctx.fillStyle = '#111'; ctx.fillRect(0, 0, canvas.width, canvas.height);
                }

                ctx.fillStyle = '#fff'; ctx.textAlign = 'center'; 
                
                if (gameState === 'GAME_CLEAR') {
                    ctx.font = 'bold 40px sans-serif';
                    let winnerTxt = "MATCH DRAW!";
                    if (p1Wins > p2Wins) winnerTxt = mode === 2 ? "P1 WINS THE MATCH!" : "YOU WIN THE MATCH!";
                    else if (p2Wins > p1Wins) winnerTxt = (mode === 1 ? "CPU" : "P2") + " WINS THE MATCH!";
                    
                    ctx.fillStyle = p1Wins > p2Wins ? '#00e5ff' : (p2Wins > p1Wins ? '#ff00ff' : '#ffeb3b');
                    ctx.fillText(winnerTxt, canvas.width / 2, canvas.height / 2 - 40);

                    ctx.font = 'bold 30px sans-serif';
                    ctx.fillStyle = '#fff';
                    ctx.fillText(`FINAL SCORE: P1 [ ${p1Wins} ] - [ ${p2Wins} ] ${mode === 1 ? 'CPU' : 'P2'}`, canvas.width / 2, canvas.height / 2 + 20);
                    
                    ctx.font = '20px sans-serif';
                    ctx.fillStyle = '#aaa';
                    ctx.fillText('Press Any Key...', canvas.width / 2, canvas.height / 2 + 80);
                } else {
                    ctx.font = 'bold 40px sans-serif';
                    let txt = gameState === 'STAGE_START' ? `Stage ${stage} Start` :
                              gameState === 'STAGE_CLEAR' ? `Stage ${stage} Clear!` :
                              `Game Over`;
                    ctx.fillText(txt, canvas.width / 2, canvas.height / 2 - 20);

                    if (gameState === 'STAGE_CLEAR') {
                        ctx.font = 'bold 24px sans-serif';
                        ctx.fillStyle = '#00e5ff';
                        let stgWinner = "DRAW!";
                        if (lastStageWinner === 1) stgWinner = "P1 WINS THE STAGE!";
                        else if (lastStageWinner === 2) {
                            stgWinner = (mode === 1 ? "CPU" : "P2") + " WINS THE STAGE!";
                            ctx.fillStyle = '#ff00ff';
                        } else {
                            ctx.fillStyle = '#ffeb3b';
                        }
                        ctx.fillText(stgWinner, canvas.width / 2, canvas.height / 2 + 30);
                        ctx.fillStyle = '#fff';
                    }

                    ctx.font = '20px sans-serif'; 
                    ctx.fillStyle = '#aaa';
                    ctx.fillText('Press Any Key...', canvas.width / 2, canvas.height / 2 + (gameState === 'STAGE_CLEAR' ? 80 : 40));
                }
                return;
            }

            for (let y = 0; y < ROWS; y++) {
                for (let x = 0; x < COLS; x++) {
                    const cell = map[y][x];
                    if (cell.type === 'road' || cell.type === 'start') {
                        if (cell.name === 'normal') {
                            const path = 'Road/default.png';
                            if (ASSETS.images[path] && ASSETS.images[path].loaded) {
                                ctx.drawImage(ASSETS.images[path].img, x*TS, y*TS, TS, TS);
                            } else {
                                ctx.fillStyle = 'lightgreen'; ctx.fillRect(x*TS, y*TS, TS, TS);
                            }
                        } else {
                            const path = `Road/${cell.name}.png`;
                            if (ASSETS.images[path] && ASSETS.images[path].loaded) {
                                ctx.drawImage(ASSETS.images[path].img, x*TS, y*TS, TS, TS);
                            } else {
                                ctx.fillStyle = 'lightgreen'; ctx.fillRect(x*TS, y*TS, TS, TS);
                            }
                        }
                    } else if (cell.type === 'hurdle') {
                        const path = `Hurdle/${cell.name}.png`;
                        if (ASSETS.images[path] && ASSETS.images[path].loaded) {
                            ctx.drawImage(ASSETS.images[path].img, x*TS, y*TS, TS, TS);
                            ctx.lineWidth = 2; ctx.strokeStyle = cell.color;
                            ctx.strokeRect(x * TS + 1, y * TS + 1, TS - 2, TS - 2);
                        } else {
                            ctx.fillStyle = cell.color; ctx.fillRect(x*TS, y*TS, TS, TS);
                        }
                    }
                }
            }

            items.forEach(item => {
                if (['food', 'heal', 'speed'].includes(item.type)) {
                    const validPaths = VALID_ITEM_PATHS[item.type];
                    if (validPaths.length > 0) {
                        const path = validPaths[item.rndIdx % validPaths.length];
                        ctx.drawImage(ASSETS.images[path].img, item.x * TS, item.y * TS, TS, TS);
                    } else {
                        if (item.type === 'food') {
                            ctx.fillStyle = 'yellow'; ctx.fillRect(item.x * TS + TS/4, item.y * TS + TS/4, TS/2, TS/2);
                        } else if (item.type === 'heal') {
                            ctx.fillStyle = 'yellowgreen'; ctx.fillRect(item.x * TS + TS/3, item.y * TS + TS/3, TS/3, TS/3);
                        } else {
                            ctx.fillStyle = 'cyan'; ctx.fillRect(item.x * TS + TS/3, item.y * TS + TS/3, TS/3, TS/3);
                        }
                    }
                } else if (item.type === 'magic') {
                    const path = `Magic/${item.magicData.name}.png`;
                    if (ASSETS.images[path] && ASSETS.images[path].loaded) {
                        ctx.drawImage(ASSETS.images[path].img, item.x * TS, item.y * TS, TS, TS);
                    } else {
                        ctx.beginPath(); ctx.arc(item.x * TS + TS/2, item.y * TS + TS/2, TS/4, 0, Math.PI*2);
                        ctx.fillStyle = item.magicData.color; ctx.fill(); ctx.closePath();
                    }
                }
            });

            fallingItems.forEach(f => f.draw(ctx));

            players.forEach(p => {
                if (p.lifting && p.attackArea.length > 0 && p.magic) {
                    p.attackArea.forEach(cell => {
                        const ax = p.targetX + cell.dx; const ay = p.targetY + cell.dy;
                        if(ax >= 0 && ax < COLS && ay >= 0 && ay < ROWS) {
                            let mapCell = map[ay][ax];
                            let isDestructible = mapCell.type === 'hurdle' && 
                                ((mapCell.name === 'water' && p.magic.name === 'ice') || mapCell.name === p.magic.name);

                            if (isDestructible) {
                                ctx.setLineDash([]); 
                                ctx.globalAlpha = 0.5 + 0.5 * Math.sin(performance.now() / 150); 
                                ctx.strokeStyle = 'white'; 
                            } else {
                                ctx.setLineDash([5, 5]); 
                                ctx.lineDashOffset = -performance.now() / 50; 
                                ctx.globalAlpha = 1.0;
                                ctx.strokeStyle = p.magic.color; 
                            }
                            ctx.lineWidth = 3;
                            ctx.strokeRect(ax * TS + 2, ay * TS + 2, TS - 4, TS - 4);
                        }
                    });
                    ctx.globalAlpha = 1.0; ctx.setLineDash([]);
                }
            });

            players.forEach(p => p.draw(ctx));
            meteors.forEach(m => m.draw(ctx));
            particles.forEach(p => p.draw(ctx));

            // --- UI 領域の描画 ---
            const uiY = ROWS * TS;
            ctx.fillStyle = '#333'; ctx.fillRect(0, uiY, canvas.width, UI_HEIGHT);
            
            ctx.font = '24px sans-serif'; 
            ctx.fillStyle = '#fff';
            
            // P1
            ctx.textAlign = 'left';
            ctx.fillText(`P1 Score: ${players[0].score}   ★ Win: ${p1Wins}`, 20, uiY + 30);
            drawHpBar(20, uiY + 40, players[0].hp, players[0].maxHp, 'cyan', 'navy');
            if (players[0].magic) {
                const m = players[0].magic;
                const path = `Magic/${m.name}.png`;
                if (ASSETS.images[path] && ASSETS.images[path].loaded) {
                    ctx.drawImage(ASSETS.images[path].img, 20, uiY + 80, 40, 40);
                } else {
                    ctx.beginPath(); ctx.arc(40, uiY + 100, 15, 0, Math.PI*2);
                    ctx.fillStyle = m.color; ctx.fill(); ctx.closePath();
                }
                ctx.fillText(`: ${m.name.toUpperCase()} (Lv.${players[0].magicLevel})`, 70, uiY + 110);
            }

            // P2
            ctx.textAlign = 'right';
            ctx.fillText(`★ Win: ${p2Wins}   ${mode === 1 ? 'CPU' : 'P2'} Score: ${players[1].score}`, canvas.width - 20, uiY + 30);
            drawHpBar(canvas.width - 270, uiY + 40, players[1].hp, players[1].maxHp, 'pink', 'saddlebrown');
            if (players[1].magic) {
                const m = players[1].magic;
                ctx.fillText(`(Lv.${players[1].magicLevel}) ${m.name.toUpperCase()} :`, canvas.width - 70, uiY + 110);
                const path = `Magic/${m.name}.png`;
                if (ASSETS.images[path] && ASSETS.images[path].loaded) {
                    ctx.drawImage(ASSETS.images[path].img, canvas.width - 60, uiY + 80, 40, 40);
                } else {
                    ctx.beginPath(); ctx.arc(canvas.width - 40, uiY + 100, 15, 0, Math.PI*2);
                    ctx.fillStyle = m.color; ctx.fill(); ctx.closePath();
                }
            }

            // 中央タイマーとステージ
            ctx.textAlign = 'center'; ctx.fillStyle = '#fff'; ctx.font = '30px sans-serif';
            ctx.fillText(`Stage: ${stage} / ${MAX_STAGES}`, canvas.width / 2, uiY + 50);
            const min = Math.floor(timeLeft / 60); const sec = Math.floor(timeLeft % 60).toString().padStart(2, '0');
            ctx.fillText(`Time: ${min}:${sec}`, canvas.width / 2, uiY + 100);

            if (gameState === 'PAUSE') {
                ctx.fillStyle = 'rgba(0,0,0,0.5)'; ctx.fillRect(0, 0, canvas.width, canvas.height);
                ctx.fillStyle = '#fff'; ctx.textAlign = 'center'; ctx.font = '50px sans-serif';
                ctx.fillText('PAUSE', 400, 300);
            }
        }

        function drawHpBar(x, y, hp, maxHp, color, bgColor) {
            const width = 250; const height = 25;
            ctx.fillStyle = bgColor; ctx.fillRect(x, y, width, height);
            ctx.fillStyle = color;
            const hpWidth = Math.max(0, (hp / maxHp) * width);
            ctx.fillRect(x, y, hpWidth, height);
            ctx.strokeStyle = '#fff'; ctx.lineWidth = 2; ctx.strokeRect(x, y, width, height);
        }

        function handleScreenTransition() {
            playSound('Sound', 'pageup', 'pageup'); 

            if (gameState === 'TITLE') {
                stage = 1; gameState = 'STAGE_START';
                p1Wins = 0; p2Wins = 0; lastStageWinner = 0; // スコア・勝敗リセット
                playBGM('ScreenBgm', 'stage_state'); 
            } else if (gameState === 'STAGE_START') {
                initGame(); gameState = 'PLAY';
                playBGM('StageBgm', 'bgm' + (Math.floor(Math.random() * 3) + 1));
            } else if (gameState === 'STAGE_CLEAR') {
                stage++; gameState = 'STAGE_START'; playBGM('ScreenBgm', 'stage_state');
            } else if (gameState === 'GAME_OVER') {
                gameState = 'STAGE_START'; playBGM('ScreenBgm', 'stage_state');
            } else if (gameState === 'GAME_CLEAR') {
                gameState = 'TITLE'; playBGM('ScreenBgm', 'title');
            }
        }

        function togglePause() {
            if (gameState === 'PLAY') gameState = 'PAUSE';
            else if (gameState === 'PAUSE') gameState = 'PLAY';
        }

        function loop(timestamp) {
            if (!lastTime) lastTime = timestamp;
            const dt = (timestamp - lastTime) / 1000;
            lastTime = timestamp;

            update(dt);
            draw();

            requestAnimationFrame(loop);
        }

        requestAnimationFrame(loop);
    </script>
</body>
</html>

いいなと思ったら応援しよう!

ピックアップされています

気楽に遊べるゲーム

  • 23本

コメント

コメントするには、 ログイン または 会員登録 をお願いします。
簡単な対戦ゲーム「MagicalMaze」|古井和雄
word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word

mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1