情绪泡泡龙——ABC消消乐大作战
by LaserPirate421256 lines49.2 KB
<!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;
--text-light: #7a6a5a;
--accent: #e8a87c;
--golden: #f2c94c;
--golden-light: #f9e4a0;
--golden-dark: #d4a830;
--gray-bubble: #c4b5d4;
--gray-bubble-dark: #a895bc;
--gray-emotion: #d4b5b5;
--gray-emotion-dark: #bc9595;
--positive-b: #f5d78c;
--positive-b-dark: #e8c35e;
--positive-c: #f9e4a0;
--positive-c-dark: #f0d060;
--danger: #e07b7b;
--success: #6bbf6b;
--info: #6b9fbf;
--shadow: 0 4px 15px rgba(0, 0, 0, 0.08);
--shadow-lg: 0 8px 30px rgba(0, 0, 0, 0.12);
--radius: 18px;
--radius-sm: 10px;
--font: 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Noto Sans SC', sans-serif;
}
* {
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%);
background-attachment: fixed;
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;
-webkit-touch-callout: none;
}
.game-container {
width: 100%;
max-width: 520px;
background: var(--card-bg);
border-radius: 24px;
box-shadow: var(--shadow-lg);
padding: 14px 16px 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: 8px;
padding: 6px 8px;
background: #fffefb;
border-radius: var(--radius-sm);
box-shadow: var(--shadow);
margin-top: 2px;
}
.header-item {
display: flex;
align-items: center;
gap: 5px;
font-size: 0.9rem;
font-weight: 600;
color: var(--text);
white-space: nowrap;
}
.header-item .icon {
font-size: 1.3rem;
}
.header-item .value {
color: #d4843a;
font-weight: 700;
font-size: 1rem;
min-width: 28px;
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: var(--radius-sm);
padding: 10px 14px;
text-align: center;
position: relative;
transition: all 0.3s;
}
.event-label {
font-size: 0.75rem;
color: #b8957a;
text-transform: uppercase;
letter-spacing: 2px;
margin-bottom: 3px;
}
.event-text {
font-size: 1.05rem;
font-weight: 700;
color: #5a3e2b;
line-height: 1.4;
}
.event-badge {
display: inline-block;
background: #f9e4a0;
color: #8b6914;
font-size: 0.7rem;
padding: 3px 10px;
border-radius: 20px;
font-weight: 600;
letter-spacing: 1px;
}
/* 泡泡网格 */
.bubble-grid {
display: grid;
grid-template-columns: repeat(6, 1fr);
grid-template-rows: repeat(5, 1fr);
gap: 7px;
padding: 6px;
background: #fefcf9;
border-radius: var(--radius);
box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.04);
min-height: 280px;
position: relative;
}
@media (max-width: 400px) {
.bubble-grid {
gap: 5px;
padding: 4px;
}
.bubble {
font-size: 0.65rem !important;
padding: 4px 2px !important;
min-height: 48px !important;
}
.game-container {
padding: 10px 8px 12px;
}
.header-item {
font-size: 0.75rem;
}
.event-text {
font-size: 0.9rem;
}
}
@media (min-width: 401px) and (max-width: 480px) {
.bubble {
font-size: 0.7rem !important;
padding: 5px 3px !important;
min-height: 52px !important;
}
}
/* 泡泡样式 */
.bubble {
display: flex;
align-items: center;
justify-content: center;
text-align: center;
border-radius: 50%;
cursor: pointer;
font-weight: 600;
font-size: 0.72rem;
line-height: 1.25;
padding: 6px 4px;
transition: transform 0.15s ease, box-shadow 0.15s ease, background 0.3s;
position: relative;
word-break: break-all;
min-height: 56px;
aspect-ratio: 1 / 1;
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.1), inset 0 2px 0 rgba(255, 255, 255, 0.5);
letter-spacing: 0.3px;
color: #3a2a1a;
}
.bubble:active {
transform: scale(0.9);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
}
.bubble.selected {
transform: scale(1.08);
box-shadow: 0 0 0 5px rgba(100, 160, 220, 0.5), 0 6px 20px rgba(0, 0, 0, 0.2) !important;
z-index: 5;
animation: gentle-bounce 0.5s ease-in-out infinite;
}
@keyframes gentle-bounce {
0%,
100% {
transform: scale(1.08);
}
50% {
transform: scale(1.14);
}
}
.bubble.hint-glow {
animation: hint-pulse 0.7s ease-in-out infinite !important;
box-shadow: 0 0 0 6px rgba(255, 180, 50, 0.7), 0 6px 22px rgba(255, 150, 30, 0.4) !important;
z-index: 4;
}
@keyframes hint-pulse {
0%,
100% {
box-shadow: 0 0 0 6px rgba(255, 180, 50, 0.7), 0 6px 22px rgba(255, 150, 30, 0.4);
}
50% {
box-shadow: 0 0 0 12px rgba(255, 180, 50, 0.2), 0 8px 28px rgba(255, 150, 30, 0.6);
}
}
/* 灰色泡泡 - 不合理信念 */
.bubble.belief-negative {
background: linear-gradient(145deg, #d5c8e8, #bfaed6);
box-shadow: 0 3px 8px rgba(150, 120, 170, 0.3), inset 0 2px 0 rgba(255, 255, 255, 0.4);
}
/* 灰色泡泡 - 负性情绪 */
.bubble.emotion-negative {
background: linear-gradient(145deg, #e4cdcd, #d4b5b5);
box-shadow: 0 3px 8px rgba(170, 130, 130, 0.3), inset 0 2px 0 rgba(255, 255, 255, 0.4);
}
/* 金色泡泡 - 合理信念 */
.bubble.belief-positive {
background: linear-gradient(145deg, #f8df90, #f0c94c);
box-shadow: 0 3px 10px rgba(200, 150, 40, 0.35), inset 0 2px 0 rgba(255, 255, 255, 0.5);
color: #5a3a0a;
}
/* 金色泡泡 - 正性情绪 */
.bubble.emotion-positive {
background: linear-gradient(145deg, #fceba8, #f7d96a);
box-shadow: 0 3px 10px rgba(200, 150, 30, 0.35), inset 0 2px 0 rgba(255, 255, 255, 0.5);
color: #5a3a0a;
}
/* 动画:爆破 */
.bubble.bursting {
animation: burst-out 0.5s ease-out forwards;
}
@keyframes burst-out {
0% {
transform: scale(1);
opacity: 1;
filter: blur(0);
}
30% {
transform: scale(1.3);
opacity: 0.7;
filter: blur(1px);
}
100% {
transform: scale(0);
opacity: 0;
filter: blur(4px);
}
}
/* 动画:收集金光 */
.bubble.collecting {
animation: collect-gold 0.6s ease-out forwards;
}
@keyframes collect-gold {
0% {
transform: scale(1);
opacity: 1;
box-shadow: 0 0 0 0 rgba(255, 200, 50, 0.8);
}
50% {
transform: scale(1.4);
opacity: 0.9;
box-shadow: 0 0 30px 20px rgba(255, 200, 50, 0.6);
}
100% {
transform: scale(0);
opacity: 0;
box-shadow: 0 0 60px 40px rgba(255, 200, 50, 0);
}
}
/* 动画:掉落 */
.bubble.dropping {
animation: drop-in 0.55s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}
@keyframes drop-in {
0% {
transform: translateY(-80px) scale(0.3);
opacity: 0;
}
60% {
transform: translateY(8px) scale(1.06);
opacity: 1;
}
80% {
transform: translateY(-3px) scale(0.97);
}
100% {
transform: translateY(0) scale(1);
opacity: 1;
}
}
/* 抖动 */
.bubble.shaking {
animation: shake 0.45s ease-in-out;
}
@keyframes shake {
0%,
100% {
transform: translateX(0);
}
15% {
transform: translateX(-8px);
}
30% {
transform: translateX(8px);
}
45% {
transform: translateX(-6px);
}
60% {
transform: translateX(6px);
}
75% {
transform: translateX(-3px);
}
90% {
transform: translateX(3px);
}
}
/* 浮动提示文字 */
.floating-tip {
position: fixed;
pointer-events: none;
z-index: 100;
font-weight: 700;
font-size: 0.95rem;
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 ease-out 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);
}
70% {
opacity: 0.9;
transform: translateY(-60px) scale(1);
}
100% {
opacity: 0;
transform: translateY(-90px) 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);
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
}
.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 ease;
}
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.modal {
background: #fffefb;
border-radius: 24px;
padding: 24px 20px;
max-width: 440px;
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.5rem;
color: #5a3e2b;
margin-bottom: 8px;
}
.modal .title-badge {
display: inline-block;
font-size: 1.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;
}
.modal .score-display {
font-size: 3rem;
font-weight: 900;
color: #d4843a;
margin: 6px 0;
}
.modal .stars-display {
font-size: 2rem;
letter-spacing: 4px;
margin: 4px 0 10px;
}
.modal .quotes-list {
text-align: left;
background: #fefcf8;
border-radius: 14px;
padding: 14px;
margin: 10px 0;
border: 1px solid #f0e8d8;
}
.modal .quotes-list p {
padding: 6px 0;
border-bottom: 1px dotted #e8d8c4;
font-size: 0.85rem;
color: #5a4a3a;
line-height: 1.5;
}
.modal .quotes-list p:last-child {
border-bottom: none;
}
.modal .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);
letter-spacing: 0.5px;
transition: all 0.2s;
}
.modal .btn-close:active {
transform: scale(0.94);
}
/* 响应式 */
@media (min-width: 481px) {
.bubble {
font-size: 0.78rem;
min-height: 62px;
}
.bubble-grid {
gap: 8px;
}
}
</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>
<!-- 泡泡网格 -->
<div class="bubble-grid" id="bubbleGrid"></div>
<!-- 按钮行 -->
<div class="btn-row">
<button class="btn btn-hint" id="btnHint" title="每关限用1次">💡 提示</button>
<button class="btn btn-reset" id="btnReset">🔄 重置本关</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: '委屈😭、愤怒😡',
beliefPositive: '方案有取舍很正常,不代表我不好。',
emotionPositive: '平静😊、有动力💪',
encouragement: '太棒了!你的想法很有价值,不被采纳只是方案选择不同而已✨',
}, {
id: 1,
eventA: '手机被老师暂时保管了。',
beliefNegative: '老师故意针对我,让我丢脸!',
emotionNegative: '暴怒😡、想顶撞',
beliefPositive: '规则对大家都一样,老师是在帮我。',
emotionPositive: '释然😌、专心听课',
encouragement: '没错!规则面前人人平等,专注听课才是对自己负责💪',
}, {
id: 2,
eventA: '好朋友突然不回我消息。',
beliefNegative: '他肯定讨厌我了。',
emotionNegative: '焦虑😰、难过😢',
beliefPositive: '他可能在忙,我可以等等再问。',
emotionPositive: '理解🤗、耐心',
encouragement: '理解万岁!朋友可能只是暂时忙碌,耐心是友谊的试金石🤗',
}, {
id: 3,
eventA: '考试没考好。',
beliefNegative: '我太笨了,永远学不好。',
emotionNegative: '沮丧😞、想放弃',
beliefPositive: '这次没考好说明有提升空间。',
emotionPositive: '反思🤔、制定计划✍️',
encouragement: '成长型思维!考试是检验学习的机会,不是定义能力的标签✍️',
}, {
id: 4,
eventA: '被同学当众开了玩笑。',
beliefNegative: '他在羞辱我,所有人都笑我。',
emotionNegative: '羞耻😳、愤怒😡',
beliefPositive: '他只是开玩笑,没有恶意。',
emotionPositive: '一笑了之😄、大度',
encouragement: '大度是一种力量!一笑了之,你的心胸比玩笑更大😄',
}];
// 全局金句
const globalQuotes = [
'💡 想法一变,心情就亮了!',
'🌈 事件无法改变,但信念可以选择。',
'🧠 做情绪的主人,从转念开始。',
'🫧 灰色泡泡不可怕,转念就能变金色!',
'🎨 你的想法,决定你的心情颜色。',
'🌟 换个角度看世界,世界也会温柔待你。',
];
// ==================== 游戏状态 ====================
let currentLevel = 0; // 0-4
let score = 0;
let stars = 0;
let timeLeft = 90;
let timerInterval = null;
let hintUsedThisLevel = false;
let selectedBubble = null;
let isProcessing = false;
let collectedGoldenPairs = 0; // 当前关卡已收集的金色配对数量
let goldenPairsNeeded = 1; // 当前关卡需要收集的金色配对数量(初始1对,转化后可能增加)
let currentEventGoldenBubbles = []; // 追踪当前事件的金色泡泡ID
let allCollectedEncouragements = []; // 收集到的所有鼓励语
// ==================== DOM引用 ====================
const bubbleGrid = document.getElementById('bubbleGrid');
const eventText = document.getElementById('eventText');
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');
// ==================== 泡泡数据结构 ====================
// 每个泡泡: { id, eventId, type:'belief'|'emotion', chain:'negative'|'positive', text, htmlId }
let allBubbles = [];
function generateBubbleId() {
return 'bub_' + Date.now() + '_' + Math.random().toString(36).substr(2, 6);
}
function createBubbleData(eventId, type, chain, text) {
return {
id: generateBubbleId(),
eventId: eventId,
type: type,
chain: chain,
text: text,
};
}
// ==================== 构建当前关卡的泡泡池 ====================
function buildBubblePoolForLevel(levelIndex) {
const pool = [];
const currentEvent = eventsData[levelIndex];
// 当前事件的4个泡泡(必须出现)
pool.push(createBubbleData(levelIndex, 'belief', 'negative', currentEvent.beliefNegative));
pool.push(createBubbleData(levelIndex, 'emotion', 'negative', currentEvent.emotionNegative));
pool.push(createBubbleData(levelIndex, 'belief', 'positive', currentEvent.beliefPositive));
pool.push(createBubbleData(levelIndex, 'emotion', 'positive', currentEvent.emotionPositive));
// 从其他事件中随机选取填充泡泡
const otherEvents = eventsData.filter((_, i) => i !== levelIndex);
const fillerPool = [];
otherEvents.forEach(ev => {
fillerPool.push(createBubbleData(ev.id, 'belief', 'negative', ev.beliefNegative));
fillerPool.push(createBubbleData(ev.id, 'emotion', 'negative', ev.emotionNegative));
fillerPool.push(createBubbleData(ev.id, 'belief', 'positive', ev.beliefPositive));
fillerPool.push(createBubbleData(ev.id, 'emotion', 'positive', ev.emotionPositive));
});
// 随机打乱填充池
shuffleArray(fillerPool);
// 需要填充到30个(5×6网格)
const needed = 30 - pool.length;
for (let i = 0; i < needed; i++) {
pool.push({ ...fillerPool[i % fillerPool.length], id: generateBubbleId() });
}
// 打乱整个池
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.forEach((bubble, index) => {
const el = document.createElement('div');
el.className = 'bubble';
el.setAttribute('data-index', index);
el.setAttribute('data-id', bubble.id);
el.textContent = bubble.text;
// 根据类型和链设置样式类
if (bubble.chain === 'negative') {
if (bubble.type === 'belief') {
el.classList.add('belief-negative');
} else {
el.classList.add('emotion-negative');
}
} else {
if (bubble.type === 'belief') {
el.classList.add('belief-positive');
} else {
el.classList.add('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;
});
// 追踪当前事件的金色泡泡
updateGoldenBubbleTracking();
}
function updateGoldenBubbleTracking() {
currentEventGoldenBubbles = allBubbles
.filter(b => b.eventId === currentLevel && b.chain === 'positive')
.map(b => b.id);
// 计算需要收集的金色配对数量(= 金色信念数量,因为每个金色信念需要一个金色情绪配对)
const goldenBeliefs = allBubbles.filter(b => b.eventId === currentLevel && b.chain === 'positive' && b
.type === 'belief').length;
goldenPairsNeeded = goldenBeliefs;
collectedGoldenPairs = 0;
}
function refreshBubbleElements() {
const els = bubbleGrid.querySelectorAll('.bubble');
els.forEach(el => {
const index = parseInt(el.getAttribute('data-index'));
if (index < allBubbles.length) {
allBubbles[index].htmlEl = el;
allBubbles[index].gridIndex = index;
}
});
}
// ==================== 泡泡点击处理 ====================
function onBubbleClick(index, el) {
if (isProcessing) return;
if (el.classList.contains('bursting') || el.classList.contains('collecting') || el.classList
.contains('dropping')) return;
if (!el.parentNode) return; // 已被移除
const bubble = allBubbles[index];
if (!bubble) return;
// 如果已经选中了这个泡泡,取消选中
if (selectedBubble && selectedBubble.id === bubble.id) {
deselectBubble();
return;
}
// 如果没有已选中的泡泡,选中当前泡泡
if (!selectedBubble) {
selectBubble(bubble, el);
return;
}
// 已有选中的泡泡,进行配对判断
const firstBubble = selectedBubble;
const firstEl = firstBubble.htmlEl;
const secondBubble = bubble;
const secondEl = el;
// 检查是否是有效配对:同一事件、一个信念一个情绪、同一条链
if (firstBubble.eventId === secondBubble.eventId &&
firstBubble.type !== secondBubble.type &&
firstBubble.chain === secondBubble.chain) {
// 有效配对!
isProcessing = true;
deselectBubbleSilent();
if (firstBubble.chain === 'negative') {
// 灰色配对 → 爆破转化
handleNegativePair(firstBubble, secondBubble, firstEl, secondEl);
} else {
// 金色配对 → 收集得分
handlePositivePair(firstBubble, secondBubble, firstEl, secondEl);
}
} else {
// 无效配对 → 抖动
handleInvalidPair(firstEl, secondEl);
deselectBubbleSilent();
}
}
function selectBubble(bubble, el) {
deselectBubbleSilent();
selectedBubble = bubble;
el.classList.add('selected');
}
function deselectBubble() {
if (selectedBubble && selectedBubble.htmlEl) {
selectedBubble.htmlEl.classList.remove('selected');
}
selectedBubble = null;
}
function deselectBubbleSilent() {
if (selectedBubble && selectedBubble.htmlEl) {
selectedBubble.htmlEl.classList.remove('selected');
}
selectedBubble = null;
}
// ==================== 配对处理 ====================
function handleNegativePair(bub1, bub2, el1, el2) {
// 爆破动画
el1.classList.add('bursting');
el2.classList.add('bursting');
// 弹出提示
const rect1 = el1.getBoundingClientRect();
const rect2 = el2.getBoundingClientRect();
const midX = (rect1.left + rect1.right + rect2.left + rect2.right) / 4;
const midY = (rect1.top + rect1.bottom + rect2.top + rect2.bottom) / 4;
showFloatingTip(midX, midY, '💡 试试换个想法,心情会不一样哦!');
// 获取当前事件的正面泡泡文本
const currentEvent = eventsData[currentLevel];
const newBeliefText = currentEvent.beliefPositive;
const newEmotionText = currentEvent.emotionPositive;
// 延迟移除旧泡泡并添加新金色泡泡
setTimeout(() => {
// 移除旧泡泡
removeBubbleById(bub1.id);
removeBubbleById(bub2.id);
// 创建新的金色泡泡
const newBeliefBubble = createBubbleData(currentLevel, 'belief', 'positive', newBeliefText);
const newEmotionBubble = createBubbleData(currentLevel, 'emotion', 'positive', newEmotionText);
// 插入到网格中(替换被移除的位置)
// 找到空位或使用原来的位置
const emptyIndices = [];
allBubbles.forEach((b, i) => {
if (b === null || b === undefined) emptyIndices.push(i);
});
// 清理null
allBubbles = allBubbles.filter(b => b !== null && b !== undefined);
// 添加新泡泡
allBubbles.push(newBeliefBubble);
allBubbles.push(newEmotionBubble);
// 如果总数超过30,移除一些非当前事件的干扰泡泡
while (allBubbles.length > 30) {
const removeIndex = allBubbles.findIndex(b => b.eventId !== currentLevel && b.chain ===
'negative');
if (removeIndex >= 0) {
allBubbles.splice(removeIndex, 1);
} else {
const ri = allBubbles.findIndex(b => b.eventId !== currentLevel);
if (ri >= 0) allBubbles.splice(ri, 1);
else break;
}
}
// 重新渲染
renderGrid();
// 给新泡泡添加掉落动画
setTimeout(() => {
const newBubs = allBubbles.filter(b => b.eventId === currentLevel && b.chain ===
'positive');
newBubs.forEach(b => {
if (b.htmlEl && !b.htmlEl.classList.contains('dropping') && !b.htmlEl.classList
.contains('collecting')) {
// 检查是否是新加入的(没有旧位置)
b.htmlEl.classList.add('dropping');
}
});
updateGoldenBubbleTracking();
checkLevelComplete();
}, 80);
isProcessing = false;
}, 500);
}
function handlePositivePair(bub1, bub2, el1, el2) {
// 收集动画
el1.classList.add('collecting');
el2.classList.add('collecting');
// 弹出鼓励语
const currentEvent = eventsData[currentLevel];
const rect1 = el1.getBoundingClientRect();
const rect2 = el2.getBoundingClientRect();
const midX = (rect1.left + rect1.right + rect2.left + rect2.right) / 4;
const midY = (rect1.top + rect1.bottom + rect2.top + rect2.bottom) / 4;
showFloatingTip(midX, midY, currentEvent.encouragement);
// 加分
score += 10;
stars += 1;
collectedGoldenPairs += 1;
// 收集鼓励语
if (!allCollectedEncouragements.includes(currentEvent.encouragement)) {
allCollectedEncouragements.push(currentEvent.encouragement);
}
updateUI();
// 延迟移除
setTimeout(() => {
removeBubbleById(bub1.id);
removeBubbleById(bub2.id);
allBubbles = allBubbles.filter(b => b !== null && b !== undefined);
renderGrid();
updateGoldenBubbleTracking();
checkLevelComplete();
isProcessing = false;
}, 550);
}
function handleInvalidPair(el1, el2) {
el1.classList.add('shaking');
el2.classList.add('shaking');
setTimeout(() => {
el1.classList.remove('shaking');
el2.classList.remove('shaking');
}, 450);
}
function removeBubbleById(id) {
const index = allBubbles.findIndex(b => b && b.id === id);
if (index >= 0) {
allBubbles[index] = null;
}
}
// ==================== 浮动提示 ====================
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.parentNode.removeChild(tip);
}, 2600);
}
// ==================== 检查关卡完成 ====================
function checkLevelComplete() {
// 更新追踪
updateGoldenBubbleTracking();
// 检查当前事件是否还有金色泡泡
const remainingGolden = allBubbles.filter(b => b && b.eventId === currentLevel && b.chain === 'positive');
if (remainingGolden.length === 0 && collectedGoldenPairs >= goldenPairsNeeded) {
// 当前关卡完成
clearInterval(timerInterval);
if (currentLevel < 4) {
// 进入下一关
setTimeout(() => {
currentLevel++;
startLevel(currentLevel);
}, 800);
} else {
// 全部通关
setTimeout(() => {
showFinalModal();
}, 600);
}
}
}
// ==================== 计时器 ====================
function startTimer() {
clearInterval(timerInterval);
timeLeft = 90;
updateTimerDisplay();
timerInterval = setInterval(() => {
timeLeft--;
updateTimerDisplay();
if (timeLeft <= 0) {
clearInterval(timerInterval);
handleTimeUp();
}
}, 1000);
}
function updateTimerDisplay() {
timerDisplay.textContent = timeLeft;
if (timeLeft <= 15) {
timerDisplay.classList.add('timer-warning');
} else {
timerDisplay.classList.remove('timer-warning');
}
}
function handleTimeUp() {
isProcessing = true;
clearInterval(timerInterval);
// 显示提示并重置当前关卡
const gridRect = bubbleGrid.getBoundingClientRect();
const cx = gridRect.left + gridRect.width / 2;
const cy = gridRect.top + gridRect.height / 2;
showFloatingTip(cx, cy, '⏰ 时间到!重新挑战本关吧~');
setTimeout(() => {
isProcessing = false;
startLevel(currentLevel);
}, 1500);
}
// ==================== 开始关卡 ====================
function startLevel(levelIndex) {
currentLevel = levelIndex;
clearInterval(timerInterval);
isProcessing = false;
selectedBubble = null;
hintUsedThisLevel = false;
collectedGoldenPairs = 0;
goldenPairsNeeded = 1;
btnHint.disabled = false;
btnHint.textContent = '💡 提示';
const currentEvent = eventsData[levelIndex];
eventText.textContent = currentEvent.eventA;
levelDisplay.textContent = (levelIndex + 1) + '/5';
updateUI();
allBubbles = buildBubblePoolForLevel(levelIndex);
renderGrid();
updateGoldenBubbleTracking();
updateTimerDisplay();
timerDisplay.classList.remove('timer-warning');
startTimer();
// 滚动到游戏区域
document.getElementById('gameContainer').scrollIntoView({ behavior: 'smooth' });
}
function updateUI() {
scoreDisplay.textContent = score;
starDisplay.textContent = stars;
levelDisplay.textContent = (currentLevel + 1) + '/5';
}
// ==================== 提示按钮 ====================
btnHint.addEventListener('click', () => {
if (hintUsedThisLevel || isProcessing) return;
hintUsedThisLevel = true;
btnHint.disabled = true;
btnHint.textContent = '💡 已使用';
// 找到当前事件的一个金色配对并高亮
const goldenBelief = allBubbles.find(b => b && b.eventId === currentLevel && b.chain === 'positive' &&
b.type === 'belief');
const goldenEmotion = allBubbles.find(b => b && b.eventId === currentLevel && b.chain === 'positive' &&
b.type === 'emotion');
if (goldenBelief && goldenBelief.htmlEl) {
goldenBelief.htmlEl.classList.add('hint-glow');
}
if (goldenEmotion && goldenEmotion.htmlEl) {
goldenEmotion.htmlEl.classList.add('hint-glow');
}
// 3秒后取消高亮
setTimeout(() => {
if (goldenBelief && goldenBelief.htmlEl) goldenBelief.htmlEl.classList.remove('hint-glow');
if (goldenEmotion && goldenEmotion.htmlEl) goldenEmotion.htmlEl.classList.remove('hint-glow');
}, 3000);
});
// ==================== 重置按钮 ====================
btnReset.addEventListener('click', () => {
if (isProcessing) return;
clearInterval(timerInterval);
isProcessing = false;
selectedBubble = null;
hintUsedThisLevel = false;
collectedGoldenPairs = 0;
goldenPairsNeeded = 1;
btnHint.disabled = false;
btnHint.textContent = '💡 提示';
allBubbles = buildBubblePoolForLevel(currentLevel);
renderGrid();
updateGoldenBubbleTracking();
timeLeft = 90;
updateTimerDisplay();
timerDisplay.classList.remove('timer-warning');
updateUI();
startTimer();
});
// ==================== 通关结算 ====================
function showFinalModal() {
clearInterval(timerInterval);
isProcessing = true;
let titleBadgeClass = '';
let titleName = '';
if (score >= 80) {
titleBadgeClass = 'badge-diamond';
titleName = '💎 钻石心灵大师';
} else if (score >= 50) {
titleBadgeClass = 'badge-gold';
titleName = '🥇 黄金炼心师';
} else {
titleBadgeClass = 'badge-bronze';
titleName = '🥉 青铜净化师';
}
// 收集所有鼓励语和全局金句
const allQuotes = [...allCollectedEncouragements, ...globalQuotes];
// 去重
const uniqueQuotes = [...new Set(allQuotes)];
modalContent.innerHTML = `
<h2>🎉 恭喜通关!</h2>
<div class="title-badge ${titleBadgeClass}">${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 class="quotes-list">
<strong style="color:#5a3e2b;">📝 你的心灵金句合集:</strong>
${uniqueQuotes.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;
timeLeft = 90;
collectedGoldenPairs = 0;
goldenPairsNeeded = 1;
allCollectedEncouragements = [];
isProcessing = false;
selectedBubble = null;
hintUsedThisLevel = false;
btnHint.disabled = false;
btnHint.textContent = '💡 提示';
clearInterval(timerInterval);
updateUI();
updateTimerDisplay();
timerDisplay.classList.remove('timer-warning');
allBubbles = buildBubblePoolForLevel(0);
renderGrid();
updateGoldenBubbleTracking();
startTimer();
eventText.textContent = eventsData[0].eventA;
document.getElementById('gameContainer').scrollIntoView({ behavior: 'smooth' });
}
// 点击弹窗遮罩关闭
modalOverlay.addEventListener('click', function(e) {
if (e.target === modalOverlay) {
modalOverlay.style.display = 'none';
resetGame();
}
});
// ==================== 初始化游戏 ====================
function initGame() {
currentLevel = 0;
score = 0;
stars = 0;
allCollectedEncouragements = [];
allBubbles = buildBubblePoolForLevel(0);
eventText.textContent = eventsData[0].eventA;
updateUI();
renderGrid();
updateGoldenBubbleTracking();
startTimer();
}
// 启动
initGame();
console.log('🫧 情绪泡泡龙——ABC消消乐大作战 已就绪!');
console.log('📋 知识点:事件A只是间接原因,信念B才是情绪C的直接原因');
console.log('🎮 玩法:点击两个同色系泡泡配对,灰色=不合理信念+负性情绪(爆破转化),金色=合理信念+正性情绪(收集得分)');
console.log('💡 每关限用1次提示,共5关,90秒/关,加油!');
})();
</script>
</body>
</html>Game Source: 情绪泡泡龙——ABC消消乐大作战
Creator: LaserPirate42
Libraries: none
Complexity: complex (1256 lines, 49.2 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" to link back to the original. Then publish at arcadelab.ai/publish.