🎮ArcadeLab

猫武士斗蛐蛐 - 日神 VS 冬青叶

by TurboKoala83
590 lines23.9 KB
▶ Play
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>猫武士斗蛐蛐 - 日神 VS 冬青叶</title>
    <style>
        * { box-sizing: border-box; user-select: none; }
        body {
            margin: 0; padding: 0; width: 100vw; height: 100vh;
            background: #111; color: #eee; font-family: sans-serif;
            overflow: hidden; display: flex; justify-content: center; align-items: center;
        }
        .screen {
            width: 100%; height: 100%; position: absolute;
            display: flex; flex-direction: column; justify-content: center; align-items: center;
            transition: opacity 0.3s ease;
        }
        .hidden { opacity: 0; pointer-events: none; z-index: -1; }

        /* 开始页面 */
        #screen-start { background: #1a1a1a; }
        .start-btn {
            padding: 15px 50px; font-size: 24px; font-weight: bold;
            color: #fff; background: #e74c3c; border: none; border-radius: 8px;
            box-shadow: 0 4px 15px rgba(231, 76, 60, 0.4); cursor: pointer;
            transition: transform 0.1s;
        }
        .start-btn:active { transform: scale(0.95); }

        /* 选人页面 */
        #screen-select h2 { margin-bottom: 20px; color: #f1c40f; font-size: 20px; }
        .hero-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px; width: 80%; max-width: 300px; }
        .hero-card {
            background: #333; border: 2px solid #555; border-radius: 10px;
            padding: 15px; text-align: center; cursor: pointer; transition: all 0.2s;
        }
        .hero-card.selected { border-color: #f1c40f; background: #444; box-shadow: 0 0 15px #f1c40f; }
        .hero-card .emoji { font-size: 40px; display: block; margin-bottom: 8px; }
        .hero-card .name { font-size: 16px; font-weight: bold; }

        /* 决斗场 */
        #screen-arena { justify-content: flex-start; padding-top: 50px; }
        .arena-bg {
            position: absolute; top: 0; left: 0; width: 100%; height: 100%;
            background-size: 25px 25px;
            background-image: 
                linear-gradient(to right, #222 1px, transparent 1px),
                linear-gradient(to bottom, #222 1px, transparent 1px);
            z-index: -1;
        }
        .arena-box {
            width: 320px; height: 480px;
            border: 4px solid #e74c3c; background: rgba(20, 20, 20, 0.8);
            border-radius: 5px; position: relative; overflow: hidden;
        }
        .title-arena { position: absolute; top: 15px; color: #aaa; font-size: 14px; }

        /* 战斗单位 */
        .cat-fighter {
            position: absolute; font-size: 40px; width: 50px; height: 50px;
            display: flex; justify-content: center; align-items: center;
            will-change: left, top; z-index: 2;
        }
        
        /* 伤害飘字与状态特效 */
        .damage-text {
            position: absolute; color: #ff4d4d; font-weight: bold; font-size: 20px;
            pointer-events: none; animation: floatUp 0.8s ease-out forwards; z-index: 10;
        }
        @keyframes floatUp {
            0% { transform: translateY(0); opacity: 1; }
            100% { transform: translateY(-40px); opacity: 0; }
        }
        
        .buff-icon {
            position: absolute; font-size: 14px; top: -10px; right: -10px;
            animation: pop 0.3s ease-out; z-index: 5;
        }
        @keyframes pop { 0% { transform: scale(0); } 80% { transform: scale(1.2); } 100% { transform: scale(1); } }

        .dialogue-bubble {
            position: absolute; background: #ffd700; color: #000; 
            padding: 4px 8px; border-radius: 10px; font-size: 12px; font-weight: bold;
            white-space: nowrap; top: -40px; left: 50%; transform: translateX(-50%);
            animation: bubbleFade 1.5s ease-out forwards; z-index: 20; pointer-events: none;
            box-shadow: 0 2px 10px rgba(255, 215, 0, 0.5);
        }
        @keyframes bubbleFade {
            0% { opacity: 0; transform: translateX(-50%) translateY(10px); }
            20% { opacity: 1; transform: translateX(-50%) translateY(0); }
            80% { opacity: 1; }
            100% { opacity: 0; transform: translateX(-50%) translateY(-20px); }
        }

        /* 冬青叶的特殊视觉 */
        .hole-entry, .hole-exit {
            position: absolute; width: 40px; height: 40px; border-radius: 50%;
            display: flex; justify-content: center; align-items: center;
            font-size: 24px; color: #fff; z-index: 1;
        }
        .hole-entry { border: 2px dashed #000; background: rgba(0,0,0,0.5); }
        .hole-exit { border: 2px dashed #666; background: rgba(50,50,50,0.5); }
    </style>
</head>
<body>

    <div id="screen-start" class="screen">
        <button class="start-btn" onclick="goToSelect()">开始游戏</button>
    </div>

    <div id="screen-select" class="screen hidden">
        <h2>请选择两位武士</h2>
        <div class="hero-grid" id="heroGrid"></div>
    </div>

    <div id="screen-arena" class="screen hidden">
        <div class="arena-bg"></div>
        <div class="title-arena">⚔️ 物理决斗场 - 机制实装版</div>
        <div class="arena-box" id="arenaBox"></div>
    </div>

    <script>
        // ================= 1. 英雄数据定义 =================
        const ALL_HEROES = [
            { 
                id: 1, name: "日神", emoji: "😏", color: "#ffd700",
                hp: 900, maxHp: 900, speed: 130, range: 45, attack: 67, 
                type: "mage"
            },
            { 
                id: 2, name: "冬青叶", emoji: "🐈‍⬛", color: "#2e8b57",
                hp: 1500, maxHp: 1500, speed: 150, range: 45, attack: 45, 
                type: "assassin"
            }
        ];

        let selectedHeroes = [];

        // ================= 2. 场景切换逻辑 =================
        function switchScreen(fromId, toId) {
            document.getElementById(fromId).classList.add('hidden');
            document.getElementById(toId).classList.remove('hidden');
        }

        function goToSelect() {
            switchScreen('screen-start', 'screen-select');
            renderHeroList();
        }

        function renderHeroList() {
            const grid = document.getElementById('heroGrid');
            grid.innerHTML = '';
            selectedHeroes = [];

            ALL_HEROES.forEach(hero => {
                const card = document.createElement('div');
                card.className = 'hero-card';
                card.innerHTML = `<span class="emoji">${hero.emoji}</span><span class="name">${hero.name}</span>`;
                card.onclick = () => selectHero(hero, card);
                grid.appendChild(card);
            });
        }

        function selectHero(hero, cardElement) {
            const index = selectedHeroes.findIndex(h => h.id === hero.id);
            if (index !== -1) {
                selectedHeroes.splice(index, 1);
                cardElement.classList.remove('selected');
            } else {
                if (selectedHeroes.length < 2) {
                    selectedHeroes.push(hero);
                    cardElement.classList.add('selected');
                }
            }
            if (selectedHeroes.length === 2) {
                setTimeout(() => startArena(selectedHeroes[0], selectedHeroes[1]), 500);
            }
        }

        // ================= 3. 游戏主循环与状态系统 =================
        let gameLoopId = null;
        let fighters = []; // 场上选手
        let lastTime = 0;
        
        // 格子换算:1格 ≈ 30像素
        const GRID_SIZE = 30; 

        function startArena(hero1, hero2) {
            switchScreen('screen-select', 'screen-arena');
            const box = document.getElementById('arenaBox');
            box.innerHTML = ''; 

            // 初始化实例数据
            fighters = [
                createFighterInstance(hero1, 30, 30, 1.5, 1.2),
                createFighterInstance(hero2, 200, 350, -1.5, -1.2)
            ];

            fighters.forEach(f => {
                const el = document.createElement('div');
                el.className = 'cat-fighter';
                el.id = 'fighter-' + f.uid;
                el.innerText = f.emoji;
                el.style.color = f.color;
                el.style.left = f.x + 'px';
                el.style.top = f.y + 'px';
                box.appendChild(el);
                f.el = el;
            });

            lastTime = performance.now();
            if (gameLoopId) cancelAnimationFrame(gameLoopId);
            gameLoopId = requestAnimationFrame(gameLoop);
        }

        function createFighterInstance(data, x, y, vx, vy) {
            return {
                uid: data.id,
                name: data.name, emoji: data.emoji, color: data.color,
                maxHp: data.maxHp, hp: data.hp,
                speed: data.speed, baseSpeed: data.speed,
                range: data.range, baseAttack: data.attack,
                x: x, y: y, vx: vx, vy: vy,
                // 状态控制
                isInvincible: false,
                state: "normal", // normal, digging, striking, retreating
                stateTimer: 0,
                attackCd: 0,
                skillCd: 6, // 日神技能冷却/冬青叶刷洞冷却
                // Buff系统
                selfDoubtTimer: 0, // 自我怀疑
                confusionTimer: 0,  // 困惑
                // 冬青叶专属
                tunnelEntry: null,
                tunnelExit: null
            };
        }

        // ================= 4. 核心逻辑更新 =================
        function gameLoop(currentTime) {
            const dt = (currentTime - lastTime) / 1000; // 转秒
            lastTime = currentTime;

            updatePhysics(dt);
            updateCombatAndSkills(dt);

            gameLoopId = requestAnimationFrame(gameLoop);
        }

        function updatePhysics(dt) {
            const box = document.getElementById('arenaBox');
            const maxWidth = box.clientWidth;
            const maxHeight = box.clientHeight;
            const catSize = 50;

            fighters.forEach(f => {
                // 根据状态决定移动
                if (f.state === "digging" || f.state === "retreating") {
                    // 无敌状态,停止物理移动,由技能逻辑控制
                    return;
                }

                // 计算最终速度(受困惑Buff影响)
                let currentSpeed = f.baseSpeed;
                if (f.confusionTimer > 0) {
                    currentSpeed *= 0.5; // 移速减半
                }

                // 根据速度改变位置
                f.x += f.vx * (currentSpeed / f.baseSpeed) * 60 * dt; 
                f.y += f.vy * (currentSpeed / f.baseSpeed) * 60 * dt; 

                // 边界碰撞检测(红框物理)
                if (f.x <= 0) { f.x = 0; f.vx = Math.abs(f.vx); } 
                if (f.x >= maxWidth - catSize) { f.x = maxWidth - catSize; f.vx = -Math.abs(f.vx); } 
                if (f.y <= 0) { f.y = 0; f.vy = Math.abs(f.vy); } 
                if (f.y >= maxHeight - catSize) { f.y = maxHeight - catSize; f.vy = -Math.abs(f.vy); } 

                // 更新DOM位置
                if (f.el) {
                    f.el.style.left = f.x + 'px';
                    f.el.style.top = f.y + 'px';
                }
            });
        }

        // ================= 5. 战斗与技能系统 =================
        function updateCombatAndSkills(dt) {
            const box = document.getElementById('arenaBox');
            const f1 = fighters[0];
            const f2 = fighters[1];
            if (!f1 || !f2) return;

            // 递减 Buff 计时器
            [f1, f2].forEach(f => {
                if (f.selfDoubtTimer > 0) f.selfDoubtTimer -= dt;
                if (f.confusionTimer > 0) f.confusionTimer -= dt;
                if (f.attackCd > 0) f.attackCd -= dt;
                if (f.skillCd > 0) f.skillCd -= dt;
            });

            // 物理距离计算(像素转格)
            const dx = (f2.x - f1.x);
            const dy = (f2.y - f1.y);
            const dist = Math.hypot(dx, dy);
            const gridDist = dist / GRID_SIZE;

            // ---------------- 日神 AI (f1) ----------------
            if (f1.state === "normal") {
                // 机制2:逻辑闭环(隔空挂Buff,每帧判定,10%概率,代码里每0.5秒判定一次)
                if (Math.random() < 0.02 && f2.confusionTimer <= 0) { // 概率稍作调整避免过于频繁
                    f2.confusionTimer = 30; // 30秒困惑
                    showBuffIcon(f2, "❓");
                    showLog(`【日神】隔空触发逻辑闭环!${f2.name} 陷入困惑!`);
                }

                // 机制1:灵魂嘴炮(技能冷却好了就发射)
                if (f1.skillCd <= 0) {
                    f1.skillCd = 6 + Math.random() * 2; // 6-8秒冷却
                    fireProjectile(f1, f2, box);
                }

                // 普攻(1.5格近战)
                if (gridDist <= 1.5 && f1.attackCd <= 0) {
                    f1.attackCd = 1.0; // 日神攻速1秒
                    performAttack(f1, f2, f1.baseAttack);
                } else if (gridDist > 1.5) {
                    // 追击
                    f1.x += (dx / dist) * f1.speed * dt * 2; // 临时加速追击
                    f1.y += (dy / dist) * f1.speed * dt * 2;
                }
            }

            // ---------------- 冬青叶 AI (f2) ----------------
            if (f2.state === "normal") {
                // 机制1:地道奇袭(冷却好了就触发)
                if (f2.skillCd <= 0) {
                    startTunnelStrike(f2, f1, box);
                    f2.skillCd = 8; // 8秒冷却
                } 
                // 常规追击(冷却期间)
                else {
                    if (gridDist <= 1.5 && f2.attackCd <= 0) {
                        f2.attackCd = 1.2; // 冬青叶攻速1.2秒
                        performAttack(f2, f1, f2.baseAttack);
                    } else if (gridDist > 1.5) {
                        // 绕后迂回走位(模拟一点切线方向)
                        let angle = Math.atan2(dy, dx) + Math.PI / 4; // 偏转45度
                        f2.x += Math.cos(angle) * f2.speed * dt * 1.5;
                        f2.y += Math.sin(angle) * f2.speed * dt * 1.5;
                    }
                }
            }

            // 统一处理冬青叶的状态机
            if (f2.state !== "normal") {
                updateTunnelStateMachine(f2, f1, box, dt);
            }
        }

        // ---------------- 核心机制函数 ----------------

        // 伤害计算(包含Debuff乘法叠加)
        function calculateDamage(attacker, baseDmg) {
            let dmg = baseDmg;
            if (attacker.selfDoubtTimer > 0) dmg *= 0.8;  // 自我怀疑 -20%
            if (attacker.confusionTimer > 0) dmg *= 0.6;  // 困惑 -40%
            return Math.round(dmg);
        }

        // 执行普通攻击
        function performAttack(attacker, defender, baseDmg) {
            const finalDmg = calculateDamage(attacker, baseDmg);
            defender.hp = Math.max(0, defender.hp - finalDmg);
            
            // 受击反馈
            showDamage(defender.el, finalDmg);
            checkDeath(defender);
        }

        // 日神:灵魂嘴炮(发射金色弹幕)
        function fireProjectile(attacker, defender, box) {
            // 弹出气泡台词
            const quotes = [
                "这样倒不像是明确的选择。",
                "哦,你那样做可真的是愚蠢透了,我有个更明智的办法。"
            ];
            const quote = quotes[Math.floor(Math.random() * quotes.length)];
            showDialogueBubble(attacker.el, quote);

            // 生成弹幕视觉
            const proj = document.createElement('div');
            proj.innerText = "📜";
            proj.style.position = "absolute";
            proj.style.fontSize = "20px";
            proj.style.left = (attacker.x + 15) + 'px';
            proj.style.top = (attacker.y + 15) + 'px';
            proj.style.transition = "all 0.3s linear"; // 快速飞向敌人
            proj.style.zIndex = "5";
            box.appendChild(proj);

            // 下一帧移动到敌人位置
            requestAnimationFrame(() => {
                proj.style.left = (defender.x + 15) + 'px';
                proj.style.top = (defender.y + 15) + 'px';
            });

            // 命中判定(简化为延迟结算)
            setTimeout(() => {
                if (!proj.parentNode) return;
                proj.remove();
                
                // 命中伤害
                defender.hp = Math.max(0, defender.hp - 11);
                showDamage(defender.el, 11);
                
                // 挂上"自我怀疑" Debuff
                defender.selfDoubtTimer = 20; // 20秒
                showBuffIcon(defender.el, "🤔");
                showLog(`【日神】灵魂嘴炮命中!造成11点伤害,${defender.name} 被施加【自我怀疑】`);
                
                checkDeath(defender);
            }, 350);
        }

        // 冬青叶:地道奇袭三阶段状态机
        function startTunnelStrike(attacker, defender, box) {
            // 阶段1:刷新入口(在敌方附近 3-5格)
            const angle = Math.random() * Math.PI * 2;
            const distance = (3 + Math.random() * 2) * GRID_SIZE; // 3-5格
            const entryX = defender.x + Math.cos(angle) * distance;
            const entryY = defender.y + Math.sin(angle) * distance;

            // 边界限制
            const entryXClamped = Math.max(20, Math.min(box.clientWidth - 60, entryX));
            const entryYClamped = Math.max(20, Math.min(box.clientHeight - 60, entryY));

            const hole = document.createElement('div');
            hole.className = 'hole-entry';
            hole.innerText = '⬇️';
            hole.style.left = entryXClamped + 'px';
            hole.style.top = entryYClamped + 'px';
            box.appendChild(hole);
            attacker.tunnelEntry = hole;
            attacker.entryX = entryXClamped;
            attacker.entryY = entryYClamped;

            // 切换状态:开始钻地
            attacker.state = "digging";
            attacker.stateTimer = 0.3; // 0.3秒无敌
            attacker.isInvincible = true;
            attacker.el.style.opacity = "0.4"; // 半透明
            
            // 记录位置回去
            attacker.vx = 0; attacker.vy = 0;
            
            showLog(`【冬青叶】释放地道奇袭!钻入地道...`);
        }

        function updateTunnelStateMachine(f, defender, box, dt) {
            if (f.stateTimer > 0) {
                f.stateTimer -= dt;
                return;
            }

            if (f.state === "digging") {
                // 阶段2:钻出(刷新出口,在敌人身旁 2-3格)
                if (f.tunnelEntry) f.tunnelEntry.remove(); // 移除入口
                
                const angle = Math.random() * Math.PI * 2;
                const distance = (2 + Math.random() * 1) * GRID_SIZE; // 2-3格
                let exitX = defender.x + Math.cos(angle) * distance;
                let exitY = defender.y + Math.sin(angle) * distance;

                // 边界限制
                exitX = Math.max(0, Math.min(box.clientWidth - 50, exitX));
                exitY = Math.max(0, Math.min(box.clientHeight - 50, exitY));

                const exitHole = document.createElement('div');
                exitHole.className = 'hole-exit';
                exitHole.innerText = '⬆️';
                exitHole.style.left = exitX + 'px';
                exitHole.style.top = exitY + 'px';
                box.appendChild(exitHole);
                f.tunnelExit = exitHole;

                // 移动本体到出口
                f.x = exitX; f.y = exitY;
                f.el.style.left = f.x + 'px';
                f.el.style.top = f.y + 'px';
                f.el.style.opacity = "1";
                
                f.state = "striking";
                f.stateTimer = 0.2; // 0.2秒打完三连击
                showLog(`【冬青叶】从出口钻出!发动三连击!`);
            } 
            else if (f.state === "striking") {
                // 阶段3:三连击(0.2秒内打完,总伤害390)
                // 判定敌人是否在1.5格范围内
                const dx = defender.x - f.x;
                const dy = defender.y - f.y;
                const dist = Math.hypot(dx, dy) / GRID_SIZE;
                
                if (dist <= 1.5) {
                    for (let i = 0; i < 3; i++) {
                        setTimeout(() => {
                            if (defender.hp <= 0) return;
                            const finalDmg = calculateDamage(f, 130);
                            defender.hp = Math.max(0, defender.hp - finalDmg);
                            showDamage(defender.el, finalDmg);
                            checkDeath(defender);
                        }, i * 60); // 每隔60毫秒打一下
                    }
                    showLog(`【冬青叶】三连击命中!造成巨额伤害!`);
                } else {
                    showLog(`【冬青叶】三连击落空!敌人已位移。`);
                }

                f.state = "retreating";
                f.stateTimer = 0.3; // 0.3秒撤回无敌
                f.isInvincible = true;
                f.el.style.opacity = "0.4"; // 再次半透明
            } 
            else if (f.state === "retreating") {
                // 阶段4:撤回
                if (f.tunnelExit) f.tunnelExit.remove(); // 移除出口
                
                f.x = f.entryX; f.y = f.entryY;
                f.el.style.left = f.x + 'px';
                f.el.style.top = f.y + 'px';

                // 结束,恢复正常
                f.state = "normal";
                f.isInvincible = false;
                f.el.style.opacity = "1";
                f.vx = (Math.random() > 0.5 ? 1 : -1) * 1.5;
                f.vy = (Math.random() > 0.5 ? 1 : -1) * 1.2;
                showLog(`【冬青叶】安全撤回。`);
            }
        }

        // ---------------- 视觉与辅助函数 ----------------
        function showDamage(el, dmg) {
            const dmgEl = document.createElement('div');
            dmgEl.className = 'damage-text';
            dmgEl.innerText = `-${dmg}`;
            dmgEl.style.left = `${Math.random() * 50 + 10}%`;
            dmgEl.style.top = `20%`;
            el.appendChild(dmgEl);
            setTimeout(() => dmgEl.remove(), 800);
        }

        function showBuffIcon(el, icon) {
            const iconEl = document.createElement('div');
            iconEl.className = 'buff-icon';
            iconEl.innerText = icon;
            el.appendChild(iconEl);
            // 持续一段时间后消失(为了视觉不堆积,设置比Buff时间短一点)
            setTimeout(() => iconEl.remove(), 3000);
        }

        function showDialogueBubble(el, text) {
            const bubble = document.createElement('div');
            bubble.className = 'dialogue-bubble';
            bubble.innerText = text;
            el.appendChild(bubble);
            setTimeout(() => bubble.remove(), 1500);
        }

        function showLog(text) {
            console.log(text); // 暂时输出在控制台,下一版可以做个UI日志栏
        }

        let isGameOver = false;
        function checkDeath(fighter) {
            if (fighter.hp <= 0 && !isGameOver) {
                isGameOver = true;
                fighter.hp = 0;
                cancelAnimationFrame(gameLoopId);
                setTimeout(() => {
                    alert(`💀 ${fighter.name} 倒下了!战斗结束!`);
                    // 简单刷新回选人界面(你可以按需改回开始页面)
                    location.reload(); 
                }, 500);
            }
        }
    </script>
</body>
</html>

Game Source: 猫武士斗蛐蛐 - 日神 VS 冬青叶

Creator: TurboKoala83

Libraries: none

Complexity: complex (590 lines, 23.9 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: vs-turbokoala83" to link back to the original. Then publish at arcadelab.ai/publish.