🎮ArcadeLab

情绪泡泡龙——ABC消消乐大作战(规则优化版)

by LaserPirate42
579 lines34.3 KB
▶ Play
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
    <title>情绪泡泡龙——ABC消消乐大作战(规则优化版)</title>
    <style>
        :root {
            --bg: #fdf6f0;
            --card-bg: #fffaf5;
            --text: #5a4a3a;
            --shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
            --shadow-lg: 0 8px 30px rgba(0, 0, 0, 0.12);
            --radius: 18px;
            --font: 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Noto Sans SC', sans-serif;

            /* 增强颜色辨识度 */
            --gray-belief: #9b8cbf;
            --gray-emotion: #c49a9a;
            --gold-belief: #ffbf47;
            --gold-emotion: #ffd966;
            --interference-bg: #f4b084;
            --interference-border: #d4895a;
        }

        * { margin:0; padding:0; box-sizing:border-box; }
        body {
            font-family: var(--font);
            background: linear-gradient(135deg, #fef5ed 0%, #fde9df 30%, #fdf0f5 60%, #fef8f3 100%);
            min-height: 100vh; display: flex; justify-content: center; align-items: center; padding: 10px;
            -webkit-tap-highlight-color: transparent; user-select: none; -webkit-user-select: none;
        }
        .game-container {
            width: 100%; max-width: 480px; background: var(--card-bg); border-radius: 24px;
            box-shadow: var(--shadow-lg); padding: 14px 14px 16px; display: flex; flex-direction: column;
            gap: 10px; position: relative; overflow: hidden;
        }
        .game-container::before {
            content: ''; position: absolute; top: 0; left: 0; right: 0; height: 6px;
            background: linear-gradient(90deg, #f9c5a7, #f7d98c, #a7d4c5, #c4b5d4, #f9c5a7);
            border-radius: 24px 24px 0 0;
        }
        .header {
            display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 6px;
            padding: 6px 8px; background: #fffefb; border-radius: 12px; box-shadow: var(--shadow); margin-top: 2px;
        }
        .header-item {
            display: flex; align-items: center; gap: 4px; font-size: 0.85rem; font-weight: 600; color: var(--text);
        }
        .header-item .icon { font-size: 1.2rem; }
        .header-item .value { color: #d4843a; font-weight: 700; font-size: 0.95rem; min-width: 24px; text-align: center; }
        .timer-warning { color: #e05a5a !important; animation: pulse-warning 0.6s ease-in-out infinite; }
        @keyframes pulse-warning { 0%,100%{transform:scale(1);} 50%{transform:scale(1.15);} }
        .event-display {
            background: linear-gradient(135deg, #fffef9, #fef9f2); border: 2px dashed #e8d5c4;
            border-radius: 12px; padding: 8px 12px; text-align: center;
        }
        .event-label { font-size: 0.7rem; color: #b8957a; letter-spacing: 1px; margin-bottom: 2px; }
        .event-text { font-size: 1rem; font-weight: 700; color: #5a3e2b; line-height: 1.4; }
        .guide-text { font-size: 0.75rem; color: #a08060; margin-top: 4px; min-height: 20px; }
        .bubble-grid {
            display: grid; grid-template-columns: repeat(4,1fr); grid-template-rows: repeat(3,1fr);
            gap: 8px; padding: 6px; background: #fefcf9; border-radius: var(--radius);
            box-shadow: inset 0 2px 8px rgba(0,0,0,0.04); min-height: 240px;
        }
        @media (max-width:400px) {
            .bubble-grid { gap:6px; padding:4px; }
            .bubble { font-size:0.65rem !important; min-height:58px !important; }
            .game-container { padding:10px 8px 12px; }
            .event-text { font-size:0.9rem; }
        }
        .bubble {
            display: flex; align-items: center; justify-content: center; text-align: center;
            border-radius: 50%; cursor: pointer; font-weight: 700; font-size: 0.75rem; line-height: 1.25;
            padding: 5px 3px; transition: transform 0.15s, box-shadow 0.15s; position: relative;
            word-break: break-all; min-height: 62px; aspect-ratio: 1/1;
            box-shadow: 0 4px 10px rgba(0,0,0,0.15), inset 0 2px 0 rgba(255,255,255,0.6);
            letter-spacing: 0.3px; color: #2b2b2b;
        }
        .bubble:active { transform:scale(0.9); }
        .bubble.selected {
            transform: scale(1.08); box-shadow: 0 0 0 5px rgba(100,160,220,0.6), 0 6px 20px rgba(0,0,0,0.25) !important;
            z-index: 5; animation: gentle-bounce 0.5s infinite;
        }
        @keyframes gentle-bounce { 0%,100%{transform:scale(1.08);} 50%{transform:scale(1.14);} }
        .bubble.hint-glow {
            animation: hint-pulse 0.7s infinite !important;
            box-shadow: 0 0 0 6px rgba(255,180,50,0.9), 0 6px 22px rgba(255,150,30,0.6) !important; z-index:4;
        }
        @keyframes hint-pulse {
            0%,100%{box-shadow: 0 0 0 6px rgba(255,180,50,0.9), 0 6px 22px rgba(255,150,30,0.6);}
            50%{box-shadow: 0 0 0 12px rgba(255,180,50,0.3), 0 8px 28px rgba(255,150,30,0.7);}
        }

        /* 增强灰色泡泡识别度 */
        .bubble.belief-negative {
            background: linear-gradient(145deg, #b9a8d6, #9b8cbf);
            box-shadow: 0 4px 10px rgba(120,100,150,0.4), inset 0 2px 0 rgba(255,255,255,0.5);
            color: #fff;
        }
        .bubble.emotion-negative {
            background: linear-gradient(145deg, #d9b3b3, #c49a9a);
            box-shadow: 0 4px 10px rgba(160,120,120,0.4), inset 0 2px 0 rgba(255,255,255,0.5);
            color: #fff;
        }

        /* 增强金色泡泡识别度 */
        .bubble.belief-positive {
            background: linear-gradient(145deg, #ffbf47, #f0a500);
            box-shadow: 0 4px 12px rgba(200,150,40,0.5), inset 0 2px 0 rgba(255,255,255,0.6);
            color: #3a2a0a;
        }
        .bubble.emotion-positive {
            background: linear-gradient(145deg, #ffe28c, #ffd966);
            box-shadow: 0 4px 12px rgba(200,150,30,0.5), inset 0 2px 0 rgba(255,255,255,0.6);
            color: #3a2a0a;
        }

        /* 增强干扰泡泡识别度 */
        .bubble.interference {
            background: linear-gradient(145deg, #f9bd8e, #f4b084);
            box-shadow: 0 4px 10px rgba(180,120,80,0.4), inset 0 2px 0 rgba(255,255,255,0.5);
            border: 2px dashed #c47a4a;
            color: #fff;
        }

        .bubble.bursting { animation: burst-out 0.5s forwards; }
        @keyframes burst-out { 0%{transform:scale(1);opacity:1;} 30%{transform:scale(1.3);opacity:0.7;} 100%{transform:scale(0);opacity:0;} }
        .bubble.collecting { animation: collect-gold 0.6s forwards; }
        @keyframes collect-gold { 0%{transform:scale(1);opacity:1;} 50%{transform:scale(1.4);opacity:0.9;box-shadow:0 0 30px 20px rgba(255,200,50,0.7);} 100%{transform:scale(0);opacity:0;} }
        .bubble.shaking { animation: shake 0.4s; }
        @keyframes shake { 0%,100%{transform:translateX(0);} 25%{transform:translateX(-6px);} 50%{transform:translateX(6px);} 75%{transform:translateX(-3px);} }

        .floating-tip {
            position: fixed; pointer-events: none; z-index: 100; font-weight: 700; font-size: 0.9rem;
            color: #5a3a0a; background: #fef9e8; border: 2px solid #f0c94c; border-radius: 20px;
            padding: 8px 16px; box-shadow: 0 6px 20px rgba(0,0,0,0.15); animation: float-up 2.5s forwards;
            white-space: nowrap; text-align: center;
        }
        @keyframes float-up { 0%{opacity:0;transform:translateY(0) scale(0.6);} 20%{opacity:1;transform:translateY(-20px) scale(1.05);} 80%{opacity:0.8;transform:translateY(-50px) scale(1);} 100%{opacity:0;transform:translateY(-80px) scale(0.8);} }

        .btn-row { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }
        .btn {
            padding: 10px 18px; border-radius: 25px; border: none; font-size: 0.9rem; font-weight: 700;
            cursor: pointer; letter-spacing: 0.5px; transition: all 0.2s; font-family: var(--font);
            box-shadow: 0 3px 10px rgba(0,0,0,0.1); display: flex; align-items: center; gap: 6px;
        }
        .btn:active { transform: scale(0.94); }
        .btn-hint { background: #fff3d6; color: #8b6914; border: 2px solid #f0c94c; }
        .btn-hint:disabled { opacity: 0.4; cursor: not-allowed; pointer-events: none; }
        .btn-reset { background: #f5f0eb; color: #7a6a5a; border: 2px solid #d5c8b5; }

        /* 规则弹窗 */
        .modal-overlay {
            position: fixed; top:0; left:0; right:0; bottom:0; background: rgba(0,0,0,0.55);
            z-index: 200; display: flex; align-items: center; justify-content: center; animation: fade-in 0.3s;
        }
        @keyframes fade-in { from{opacity:0;} to{opacity:1;} }
        .modal {
            background: #fffefb; border-radius: 24px; padding: 24px 20px; max-width: 420px; width: 90%;
            box-shadow: 0 20px 50px rgba(0,0,0,0.25); text-align: center; max-height: 85vh;
            overflow-y: auto; animation: slide-up 0.4s cubic-bezier(0.34,1.56,0.64,1);
        }
        @keyframes slide-up { from{transform:translateY(60px);opacity:0;} to{transform:translateY(0);opacity:1;} }
        .modal h2 { font-size: 1.4rem; color: #5a3e2b; margin-bottom: 8px; }
        .title-badge { display: inline-block; font-size: 1rem; font-weight: 700; padding: 8px 20px; border-radius: 25px; margin: 8px 0 14px; letter-spacing: 1px; }
        .badge-diamond { background:#e8f8ff; color:#2b6f9a; border:2px solid #6bc4e8; }
        .badge-gold { background:#fff8e8; color:#8b6914; border:2px solid #f0c94c; }
        .badge-bronze { background:#fdf5f0; color:#8b5a3a; border:2px solid #d4a080; }
        .score-display { font-size: 2.5rem; font-weight: 900; color: #d4843a; }
        .stars-display { font-size: 1.8rem; letter-spacing: 4px; margin: 4px 0 10px; }
        .quotes-list { text-align: left; background: #fefcf8; border-radius: 14px; padding: 14px; margin: 10px 0; border: 1px solid #f0e8d8; }
        .quotes-list p { padding: 6px 0; border-bottom: 1px dotted #e8d8c4; font-size: 0.8rem; color: #5a4a3a; line-height: 1.5; }
        .quotes-list p:last-child { border-bottom: none; }
        .btn-close { margin-top: 12px; background: #f0c94c; color: #5a3a0a; font-size: 1rem; padding: 12px 30px; border-radius: 25px; border: none; font-weight: 700; cursor: pointer; box-shadow: 0 4px 14px rgba(200,150,30,0.3); font-family: var(--font); }

        /* 规则弹窗内小图标 */
        .rule-icon { display: inline-block; width: 24px; height: 24px; border-radius: 50%; margin-right: 4px; vertical-align: middle; }
        .rule-icon.gray { background: #9b8cbf; }
        .rule-icon.gold { background: #ffbf47; }
        .rule-icon.interference { background: #f4b084; border: 2px dashed #c47a4a; }
    </style>
</head>
<body>
    <div class="game-container" id="gameContainer">
        <div class="header">
            <div class="header-item"><span class="icon">🎯</span>关卡:<span class="value" id="levelDisplay">1/5</span></div>
            <div class="header-item"><span class="icon">⭐</span>星星:<span class="value" id="starDisplay">0</span></div>
            <div class="header-item"><span class="icon">💯</span>分数:<span class="value" id="scoreDisplay">0</span></div>
            <div class="header-item"><span class="icon">⏱️</span>时间:<span class="value" id="timerDisplay">90</span>秒</div>
        </div>
        <div class="event-display" id="eventDisplay">
            <div class="event-label">📋 当前事件 A</div>
            <div class="event-text" id="eventText">小组合作作业,我的建议没被采纳。</div>
            <div class="guide-text" id="guideText">👆 先点灰色配对,再逐一匹配三组金色泡泡~</div>
        </div>
        <div class="bubble-grid" id="bubbleGrid"></div>
        <div class="btn-row">
            <button class="btn btn-hint" id="btnHint">💡 提示(1次)</button>
            <button class="btn btn-reset" id="btnReset">🔄 重置本关</button>
        </div>
    </div>

    <!-- 游戏规则弹窗 -->
    <div class="modal-overlay" id="ruleModal" style="display:flex;">
        <div class="modal">
            <h2>🫧 游戏规则</h2>
            <div style="text-align:left; font-size:0.9rem; line-height:1.6; color:#5a4a3a;">
                <p style="margin-bottom:10px;">🎯 <strong>目标:</strong>消除所有灰色泡泡,并收集三组金色泡泡。</p>
                <p style="margin-bottom:10px;">📌 <strong>操作方法:</strong>依次点击两个同色系泡泡进行配对。</p>
                <div style="margin:12px 0; padding:10px; background:#faf5ff; border-radius:12px;">
                    <p><span class="rule-icon gray"></span> <strong>灰色泡泡</strong> = 不合理信念 + 负性情绪(必须消除)</p>
                    <p><span class="rule-icon gold"></span> <strong>金色泡泡</strong> = 合理信念 + 正性情绪(需要收集)</p>
                    <p><span class="rule-icon interference"></span> <strong>橙色虚边</strong> = 干扰项,点击错误<strong style="color:#e05a5a;">-10秒</strong></p>
                </div>
                <p style="margin-bottom:10px;">⏱️ 每关90秒,连续两次错误会自动提示。</p>
                <p>⭐ 成功收集金色泡泡得10分+一颗星,全部完成进入下一关。</p>
            </div>
            <button class="btn-close" id="btnStartGame">🎮 开始游戏</button>
        </div>
    </div>

    <!-- 通关弹窗(隐藏) -->
    <div class="modal-overlay" id="modalOverlay" style="display:none;">
        <div class="modal" id="modalContent"></div>
    </div>

    <script>
        (function() {
            // 精简版事件数据
            const eventsData = [
                {
                    id:0, eventA:'小组合作作业,我的建议没被采纳。',
                    beliefNegative:'他们肯定看不起我',
                    emotionNegative:'委屈😭、愤怒😡',
                    positivePairs: [
                        { belief:'方案有取舍很正常', emotion:'平静😊', encouragement:'不被采纳不代表你不好✨' },
                        { belief:'我可以学习别人的优点', emotion:'有动力💪', encouragement:'吸收优点,你会更强!' },
                        { belief:'下次表达更清晰就好', emotion:'期待🌟', encouragement:'每一次尝试都是成长!' }
                    ]
                },
                {
                    id:1, eventA:'手机被老师暂时保管了。',
                    beliefNegative:'老师故意针对我',
                    emotionNegative:'暴怒😡、想顶撞',
                    positivePairs: [
                        { belief:'规则对大家都一样', emotion:'释然😌', encouragement:'遵守规则,专注听课💪' },
                        { belief:'正好练习专注力', emotion:'专心📵', encouragement:'无手机干扰,效率更高!' },
                        { belief:'老师为全班好', emotion:'理解👍', encouragement:'换位思考,更懂秩序。' }
                    ]
                },
                {
                    id:2, eventA:'好朋友突然不回我消息。',
                    beliefNegative:'他肯定讨厌我了',
                    emotionNegative:'焦虑😰、难过😢',
                    positivePairs: [
                        { belief:'他可能在忙', emotion:'耐心🤗', encouragement:'耐心是友谊的试金石。' },
                        { belief:'也许手机没电了', emotion:'轻松😌', encouragement:'别多想,信任最重要。' },
                        { belief:'我也常有晚回', emotion:'包容🍃', encouragement:'将心比心,关系更融洽。' }
                    ]
                },
                {
                    id:3, eventA:'考试没考好。',
                    beliefNegative:'我太笨了,永远学不好',
                    emotionNegative:'沮丧😞、想放弃',
                    positivePairs: [
                        { belief:'这次暴露了提升空间', emotion:'反思🤔', encouragement:'发现盲区就是进步!' },
                        { belief:'一次考试不代表全部', emotion:'制定计划✍️', encouragement:'查漏补缺,下次逆袭!' },
                        { belief:'失败是成功之母', emotion:'行动🏃', encouragement:'把错误变成阶梯。' }
                    ]
                },
                {
                    id:4, eventA:'被同学当众开了玩笑。',
                    beliefNegative:'他在羞辱我',
                    emotionNegative:'羞耻😳、愤怒😡',
                    positivePairs: [
                        { belief:'他只是开玩笑', emotion:'一笑了之😄', encouragement:'大度让玩笑变轻松。' },
                        { belief:'幽默是调味品', emotion:'一起笑😆', encouragement:'参与欢笑,尴尬消失!' },
                        { belief:'我的感受我做主', emotion:'自信💪', encouragement:'你定义自己的情绪。' }
                    ]
                }
            ];

            const globalQuotes = [
                '💡 想法一变,心情就亮了!',
                '🌈 事件无法改变,但信念可以选择。',
                '🧠 做情绪的主人,从转念开始。',
                '🫧 灰色泡泡不可怕,转念就能变金色!',
            ];

            let currentLevel = 0;
            let score = 0;
            let stars = 0;
            let timeLeft = 90;
            let timerInterval = null;
            let hintUsed = false;
            let selectedBubble = null;
            let isProcessing = false;
            let errorCount = 0;
            let allBubbles = [];
            let allEncouragements = [];
            let totalElapsedSeconds = 0;
            let levelStartTime = null;
            let gameStarted = false;

            const bubbleGrid = document.getElementById('bubbleGrid');
            const eventText = document.getElementById('eventText');
            const guideText = document.getElementById('guideText');
            const levelDisplay = document.getElementById('levelDisplay');
            const starDisplay = document.getElementById('starDisplay');
            const scoreDisplay = document.getElementById('scoreDisplay');
            const timerDisplay = document.getElementById('timerDisplay');
            const btnHint = document.getElementById('btnHint');
            const btnReset = document.getElementById('btnReset');
            const modalOverlay = document.getElementById('modalOverlay');
            const modalContent = document.getElementById('modalContent');
            const ruleModal = document.getElementById('ruleModal');
            const btnStartGame = document.getElementById('btnStartGame');

            function genId() { return 'b_'+Date.now()+'_'+Math.random().toString(36).substr(2,8); }
            function createBubble(eventId, type, chain, text, isInterference=false, pairId=null) {
                return { id:genId(), eventId, type, chain, text, isInterference, pairId, htmlEl:null };
            }

            function buildBubblePool(level) {
                const cur = eventsData[level];
                const pool = [];
                pool.push(createBubble(level, 'belief', 'negative', cur.beliefNegative));
                pool.push(createBubble(level, 'emotion', 'negative', cur.emotionNegative));
                cur.positivePairs.forEach((pair, idx) => {
                    pool.push(createBubble(level, 'belief', 'positive', pair.belief, false, idx));
                    pool.push(createBubble(level, 'emotion', 'positive', pair.emotion, false, idx));
                });
                const others = eventsData.filter((_,i)=>i!==level);
                const shuffled = [...others].sort(()=>Math.random()-0.5);
                const ev1 = shuffled[0];
                const ev2 = shuffled[1] || shuffled[0];
                pool.push(createBubble(ev1.id, 'belief', 'negative', ev1.beliefNegative, true));
                pool.push(createBubble(ev2.id, 'emotion', 'negative', ev2.emotionNegative, true));
                pool.push(createBubble(ev2.id, 'belief', 'positive', ev2.positivePairs[0].belief, true, 0));
                pool.push(createBubble(ev1.id, 'emotion', 'positive', ev1.positivePairs[0].emotion, true, 0));
                shuffleArray(pool);
                return pool;
            }

            function shuffleArray(arr) {
                for(let i=arr.length-1; i>0; i--) {
                    const j = Math.floor(Math.random()*(i+1));
                    [arr[i],arr[j]] = [arr[j],arr[i]];
                }
            }

            function renderGrid() {
                bubbleGrid.innerHTML = '';
                allBubbles = allBubbles.filter(b=>b);
                allBubbles.forEach((bubble,index)=>{
                    const el = document.createElement('div');
                    el.className = 'bubble';
                    el.setAttribute('data-index', index);
                    el.textContent = bubble.text;
                    if(bubble.isInterference) el.classList.add('interference');
                    else if(bubble.chain==='negative') el.classList.add(bubble.type==='belief'?'belief-negative':'emotion-negative');
                    else el.classList.add(bubble.type==='belief'?'belief-positive':'emotion-positive');
                    el.addEventListener('click', ()=>onBubbleClick(index,el));
                    el.addEventListener('touchend', (e)=>{ e.preventDefault(); onBubbleClick(index,el); });
                    bubbleGrid.appendChild(el);
                    bubble.htmlEl = el;
                    bubble.gridIndex = index;
                });
            }

            function onBubbleClick(index, el) {
                if(!gameStarted || isProcessing) return;
                if(el.classList.contains('bursting')||el.classList.contains('collecting')) return;
                const bubble = allBubbles[index];
                if(!bubble) return;
                if(selectedBubble && selectedBubble.id===bubble.id) { deselectBubble(); return; }
                if(!selectedBubble) { selectBubble(bubble,el); return; }
                const first = selectedBubble;
                const firstEl = first.htmlEl;
                const second = bubble;
                const secondEl = el;
                let isValid = !first.isInterference && !second.isInterference &&
                    first.eventId===second.eventId && first.type!==second.type && first.chain===second.chain;
                if(isValid && first.chain==='positive') {
                    isValid = (first.pairId !== undefined && second.pairId !== undefined && first.pairId === second.pairId);
                }
                if(isValid) {
                    errorCount = 0;
                    isProcessing = true;
                    deselectBubbleSilent();
                    if(first.chain==='negative') handleNegativePair(first,second,firstEl,secondEl);
                    else handlePositivePair(first,second,firstEl,secondEl);
                } else {
                    errorCount++;
                    firstEl.classList.add('shaking'); secondEl.classList.add('shaking');
                    deselectBubbleSilent();
                    setTimeout(()=>{ firstEl.classList.remove('shaking'); secondEl.classList.remove('shaking'); },400);
                    timeLeft = Math.max(0, timeLeft-10);
                    updateTimerDisplay();
                    if(timeLeft<=0) { clearInterval(timerInterval); handleTimeUp(); return; }
                    const rect = bubbleGrid.getBoundingClientRect();
                    showFloatingTip(rect.left+rect.width/2, rect.top+30, '⏱️ -10秒');
                    if(errorCount>=2 && !isProcessing) { errorCount=0; autoShowHint(); }
                }
            }

            function selectBubble(bubble,el) { deselectBubbleSilent(); selectedBubble=bubble; el.classList.add('selected'); }
            function deselectBubble() { if(selectedBubble?.htmlEl) selectedBubble.htmlEl.classList.remove('selected'); selectedBubble=null; }
            function deselectBubbleSilent() { if(selectedBubble?.htmlEl) selectedBubble.htmlEl.classList.remove('selected'); selectedBubble=null; }

            function handleNegativePair(b1,b2,el1,el2) {
                el1.classList.add('bursting'); el2.classList.add('bursting');
                score+=5; updateUI();
                const midX = (el1.getBoundingClientRect().left+el2.getBoundingClientRect().right)/2;
                const midY = (el1.getBoundingClientRect().top+el2.getBoundingClientRect().bottom)/2;
                showFloatingTip(midX,midY,'💡 识别了不合理信念,接下来转化想法吧!');
                setTimeout(()=>{ removeBubbleById(b1.id); removeBubbleById(b2.id); renderGrid(); checkLevelComplete(); isProcessing=false; },500);
            }

            function handlePositivePair(b1,b2,el1,el2) {
                el1.classList.add('collecting'); el2.classList.add('collecting');
                score+=10; stars+=1;
                const cur = eventsData[currentLevel];
                const pair = cur.positivePairs.find(p=>p.belief===b1.text||p.belief===b2.text);
                const enc = pair ? pair.encouragement : '想法一转,心情就亮啦✨';
                if(!allEncouragements.includes(enc)) allEncouragements.push(enc);
                updateUI();
                const midX = (el1.getBoundingClientRect().left+el2.getBoundingClientRect().right)/2;
                const midY = (el1.getBoundingClientRect().top+el2.getBoundingClientRect().bottom)/2;
                showFloatingTip(midX,midY,enc);
                setTimeout(()=>{ removeBubbleById(b1.id); removeBubbleById(b2.id); renderGrid(); checkLevelComplete(); isProcessing=false; },550);
            }

            function removeBubbleById(id) { const idx = allBubbles.findIndex(b=>b&&b.id===id); if(idx>=0) allBubbles.splice(idx,1); }
            function showFloatingTip(x,y,text) {
                const tip=document.createElement('div'); tip.className='floating-tip'; tip.textContent=text;
                tip.style.left=x+'px'; tip.style.top=y+'px'; tip.style.transform='translate(-50%,-50%)';
                document.body.appendChild(tip); setTimeout(()=>{ if(tip.parentNode) tip.remove(); },2600);
            }

            function checkLevelComplete() {
                const correctLeft = allBubbles.filter(b=>!b.isInterference).length;
                if(correctLeft===0) {
                    allBubbles = []; renderGrid(); clearInterval(timerInterval);
                    if(levelStartTime) totalElapsedSeconds += Math.min(90, Math.round((Date.now()-levelStartTime)/1000));
                    if(currentLevel<4) { setTimeout(()=>{ currentLevel++; startLevel(currentLevel); },700); }
                    else { setTimeout(()=>showFinalModal(),600); }
                } else if(allBubbles.filter(b=>b.chain==='negative'&&!b.isInterference).length===0 &&
                          allBubbles.filter(b=>b.chain==='positive'&&!b.isInterference).length>0) {
                    guideText.innerHTML = '✨ 灰色已清空!现在逐一匹配三组不同的金色泡泡~';
                }
            }

            function startTimer() {
                clearInterval(timerInterval); timeLeft=90; updateTimerDisplay();
                levelStartTime = Date.now();
                timerInterval = setInterval(()=>{ timeLeft--; updateTimerDisplay(); if(timeLeft<=0){ clearInterval(timerInterval); handleTimeUp(); } },1000);
            }
            function updateTimerDisplay() { timerDisplay.textContent = timeLeft; timerDisplay.classList.toggle('timer-warning', timeLeft<=15); }
            function handleTimeUp() {
                isProcessing=true; clearInterval(timerInterval);
                const rect = bubbleGrid.getBoundingClientRect();
                showFloatingTip(rect.left+rect.width/2, rect.top+rect.height/2, '⏰ 时间到!重新挑战本关吧~');
                setTimeout(()=>{ isProcessing=false; startLevel(currentLevel); },1500);
            }

            function startLevel(level) {
                currentLevel=level; clearInterval(timerInterval); isProcessing=false;
                selectedBubble=null; hintUsed=false; errorCount=0;
                btnHint.disabled=false; btnHint.textContent='💡 提示(1次)';
                const ev = eventsData[level];
                eventText.textContent = ev.eventA;
                guideText.innerHTML = '👆 先点灰色配对,再逐一匹配三组金色泡泡~';
                levelDisplay.textContent = (level+1)+'/5';
                updateUI();
                allBubbles = buildBubblePool(level);
                renderGrid();
                timerDisplay.classList.remove('timer-warning');
                updateTimerDisplay();
                startTimer();
            }

            function updateUI() { scoreDisplay.textContent=score; starDisplay.textContent=stars; }

            btnHint.addEventListener('click', ()=>{
                if(!gameStarted || hintUsed||isProcessing) return;
                hintUsed=true; btnHint.disabled=true; btnHint.textContent='💡 已使用';
                applyHintGlow();
            });
            function autoShowHint() { if(!gameStarted || isProcessing) return; applyHintGlow();
                const rect = bubbleGrid.getBoundingClientRect();
                showFloatingTip(rect.left+rect.width/2, rect.top+30, '🔍 金色信念与情绪短句要对应哦!');
            }
            function applyHintGlow() {
                const gb = allBubbles.find(b=>!b.isInterference && b.chain==='positive' && b.type==='belief');
                const ge = allBubbles.find(b=>!b.isInterference && b.chain==='positive' && b.type==='emotion' && b.pairId===gb?.pairId);
                if(gb?.htmlEl) gb.htmlEl.classList.add('hint-glow');
                if(ge?.htmlEl) ge.htmlEl.classList.add('hint-glow');
                setTimeout(()=>{ if(gb?.htmlEl) gb.htmlEl.classList.remove('hint-glow'); if(ge?.htmlEl) ge.htmlEl.classList.remove('hint-glow'); },3000);
            }

            btnReset.addEventListener('click', ()=>{ if(!gameStarted || isProcessing) return; startLevel(currentLevel); });

            function showFinalModal() {
                clearInterval(timerInterval); isProcessing=true; gameStarted=false;
                let badgeClass, titleName;
                if(score>=80) { badgeClass='badge-diamond'; titleName='💎 钻石心灵大师'; }
                else if(score>=50) { badgeClass='badge-gold'; titleName='🥇 黄金炼心师'; }
                else { badgeClass='badge-bronze'; titleName='🥉 青铜净化师'; }
                const totalTimeStr = (()=>{ const m=Math.floor(totalElapsedSeconds/60); const s=totalElapsedSeconds%60; return m+'分'+s+'秒'; })();
                const quotes = [...new Set([...allEncouragements,...globalQuotes])];
                modalContent.innerHTML = `
                    <h2>🎉 通关成功!</h2>
                    <div class="title-badge ${badgeClass}">${titleName}</div>
                    <div class="score-display">${score}</div>
                    <div style="color:#7a6a5a;">总分</div>
                    <div class="stars-display">${'⭐'.repeat(Math.min(stars,20))}</div>
                    <div style="color:#7a6a5a;font-size:0.85rem;">共获得 ${stars} 颗星星</div>
                    <div style="color:#d4843a;font-size:1.1rem;font-weight:700;margin:8px 0;">⏱️ 总耗时:${totalTimeStr}</div>
                    <div class="quotes-list"><strong>📝 心灵金句合集:</strong>${quotes.map(q=>`<p>${q}</p>`).join('')}</div>
                    <p style="font-size:0.8rem;color:#b8957a;margin-top:8px;">记住:<strong>事件A只是间接原因,信念B才是情绪C的直接原因</strong>。<br>做情绪的主人,从调整想法开始!🌈</p>
                    <button class="btn-close" id="btnCloseModal">🔄 重新挑战</button>
                `;
                modalOverlay.style.display='flex';
                document.getElementById('btnCloseModal').addEventListener('click', ()=>{ modalOverlay.style.display='none'; resetGame(); });
            }

            function resetGame() {
                currentLevel=0; score=0; stars=0; allEncouragements=[]; totalElapsedSeconds=0;
                isProcessing=false; selectedBubble=null; hintUsed=false; errorCount=0;
                btnHint.disabled=false; btnHint.textContent='💡 提示(1次)';
                clearInterval(timerInterval); updateUI();
                gameStarted = false;
                ruleModal.style.display = 'flex';
                startLevel(0);
                modalOverlay.style.display='none';
            }

            modalOverlay.addEventListener('click', (e)=>{ if(e.target===modalOverlay) resetGame(); });

            // 开始游戏按钮
            btnStartGame.addEventListener('click', ()=>{
                ruleModal.style.display = 'none';
                gameStarted = true;
                startLevel(0);
            });

            function initGame() {
                // 显示规则弹窗,初始化第一关但不开始计时
                ruleModal.style.display = 'flex';
                gameStarted = false;
                currentLevel = 0;
                score = 0;
                stars = 0;
                allEncouragements = [];
                totalElapsedSeconds = 0;
                isProcessing = false;
                selectedBubble = null;
                hintUsed = false;
                errorCount = 0;
                btnHint.disabled = false;
                btnHint.textContent = '💡 提示(1次)';
                updateUI();
                allBubbles = buildBubblePool(0);
                eventText.textContent = eventsData[0].eventA;
                guideText.innerHTML = '👆 先点灰色配对,再逐一匹配三组金色泡泡~';
                levelDisplay.textContent = '1/5';
                timerDisplay.textContent = '90';
                timerDisplay.classList.remove('timer-warning');
                renderGrid();
                clearInterval(timerInterval);
            }

            initGame();
        })();
    </script>
</body>
</html>

Game Source: 情绪泡泡龙——ABC消消乐大作战(规则优化版)

Creator: LaserPirate42

Libraries: none

Complexity: complex (579 lines, 34.3 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: abc-laserpirate42-mpcviv5r" to link back to the original. Then publish at arcadelab.ai/publish.