// ==================== 全局变量 ====================
// 粒子数组
let floatingTextParticles = [];        // 背景浮动文字粒子数组
let candidateParticles = [];            // 候选粒子数组（可交互的粒子）
let rippleEffects = [];                 // 涟漪效果数组
let repelBursts = [];                   // 排斥爆发效果数组

// 游戏参数
let fontSize = 28;                      // 字体大小
let floatingCount = 100;                // 背景浮动文字数量
let candidateCount = 50;                 // 候选粒子数量
let redParticleCount = 8;               // 红色粒子（目标粒子）数量
let connectionDistance = 200;           // 连接距离阈值（像素）

// 游戏状态
let isCompleted = false;                // 游戏是否完成
let completionPhase = 'none';            // 完成阶段：'none'(未完成), 'arranging'(排列中), 'revealing'(揭示中), 'completed'(已完成)
let completionTimer = 0;                 // 完成动画计时器
let fadeOutAlpha = 255;                 // 淡出透明度（0-255）
let orderedRedParticles = [];           // 按顺序排列的红色粒子数组
let targetPositions = [];               // 目标位置数组（用于完成动画）
let targetSentence = "别过来我感觉害怕";  // 目标句子

// 波形交互状态
let calmProgress = 0;                    // 平静进度（0-1，影响文字的抖动和颜色）
let waveformPoints = [];                 // 波形点数组（用于绘制波形）
let waveformLength = 200;                // 波形长度（点数）
let waveformAmplitude = 30;              // 波形振幅（像素）
let waveformBaseY = 0;                   // 波形基准Y坐标
let mouseInteractionActive = false;       // 鼠标交互是否激活
let interactionRadius = 50;              // 交互影响半径
let waveformParticles = [];             // 波形粒子效果数组
let waveformResistance = 0.15;          // 波形抵抗力（0-1，越高越难抚平）
let lastMouseX = 0;                      // 上一帧鼠标X位置（用于检测拖动速度）
let lastMouseY = 0;                      // 上一帧鼠标Y位置
let mouseDragSpeed = 0;                  // 鼠标拖动速度

// 文字区域
let textAreaMargin = 100;                // 文字区域边距
let textAreaX = 0;                      // 文字区域X坐标
let textAreaY = 0;                      // 文字区域Y坐标
let textAreaWidth = 0;                  // 文字区域宽度
let textAreaHeight = 0;                 // 文字区域高度

// 字符集
let chineseChars = '的一是在不了有和人这中大为上个国我以要他时来用们生到作地于出就分对成会可主发年动同工也能下过子说产种面而方后多定行学法所民得经十三之进着等部度家电力里如水化高自二理起小物现实加量都两体制机当使点从业本去把性好应开它合还因由其些然前外天政四日那社义事平形相全表间样与关各重新线内数正心反你明看原又么利比或但质气第向道命此变条只没结解问意建月公无系军很情者最立代想已通并提直题党程展五果料象员革位入常文总次品式活设及管特件长求老头基资边流路级少图山统接知较将组见计别她手角期根论运农指几九区强放决西被干做必战先回则任取据处队南给色光门即保治北造百规热领七海口东导器压志世金增争济阶油思术极交受联什认六共权收证改清己美再采转更单风切打白教速花带安场身车例真务具万每目至达走积示议声报斗完类八离华名确才科张信马节话米整空元况今集温传土许步群广石记需段研界拉林律叫且究观越织装影算低持音众书布复容儿须际商非验连断深难近矿千周委素技备半办青省列习响约支般史感劳便团往酸历市克何除消构府称太准精值号率族维划选标写存候毛亲快效斯院查江型眼王按格养易置派层片始却专状育厂京识适属圆包火住调满县局照参红细引听该铁价严龙飞';
let chineseWords = ['不安', '紧张', '焦虑', '担忧', '烦躁', '恐慌', '恐惧', '绝望', '痛苦', '悲伤', '愤怒', '孤独', '无助', '迷茫', '困惑', '压抑', '沉重', '疲惫', '空虚', '失落'];

// 连接状态跟踪
let previousConnections = new Map();    // 上一帧的连接状态映射（用于检测连接变化）
let effectDisplays = [];                // 效果显示对象数组（用于传递效果）
let initializationComplete = false;     // 初始化是否完成
let initializationFrames = 0;           // 初始化帧数计数

// 词组模式
let phraseMode = true;                  // 是否启用词组模式
let phraseGroups = [];                  // 词组分组数组

// ==================== 初始化 ====================
function setup() {
  createCanvas(windowWidth, windowHeight);
  textAlign(CENTER, CENTER);
  
  textAreaX = textAreaMargin;
  textAreaY = textAreaMargin;
  textAreaWidth = width - textAreaMargin * 2;
  textAreaHeight = height - textAreaMargin * 2;
  
  for (let i = 0; i < floatingCount; i++) {
    floatingTextParticles.push(new FloatingText(
      random(textAreaX, textAreaX + textAreaWidth), 
      random(textAreaY, textAreaY + textAreaHeight)
    ));
  }
  for (let i = 0; i < candidateCount; i++) {
    candidateParticles.push(new CandidateParticle(
      random(textAreaX, textAreaX + textAreaWidth), 
      random(textAreaY, textAreaY + textAreaHeight)
    ));
  }
  
  selectRedParticles();
}

// ==================== 主循环 ====================
/**
 * 主绘制循环：更新和渲染所有游戏元素
 */
function draw() {
  background(20, 25, 35);

  // 初始化检查：等待一定帧数后再开始检测连接变化
  if (!initializationComplete) {
    initializationFrames++;
    if (initializationFrames >= 30) {
      initializationComplete = true;
    }
  }

  // 更新词组分组
  updatePhraseGroups();
  
  // 检测连接变化（只在初始化完成后进行）
  if (initializationComplete) {
    detectConnectionChanges();
  } else {
    // 初始化阶段：建立初始连接状态（不触发效果）
    if (initializationFrames >= 20) {
      let currentConnections = new Map();
      for (let i = 0; i < candidateParticles.length; i++) {
        for (let j = i + 1; j < candidateParticles.length; j++) {
          let distance = dist(candidateParticles[i].x, candidateParticles[i].y, 
                             candidateParticles[j].x, candidateParticles[j].y);
          if (distance < connectionDistance) {
            let connectionKey = `${min(i, j)}-${max(i, j)}`;
            currentConnections.set(connectionKey, {
              p1: candidateParticles[i], 
              p2: candidateParticles[j], 
              d: distance
            });
          }
        }
      }
      previousConnections = currentConnections;
    }
  }

  // 更新和显示背景浮动文字粒子
  for (let particle of floatingTextParticles) {
    particle.update();
    particle.display();
  }

  // 更新和显示候选粒子
  for (let particle of candidateParticles) {
    // 如果游戏完成且不是红色粒子，停止移动
    if (isCompleted && !particle.isRed && completionPhase !== 'none') {
      particle.vx = 0;
      particle.vy = 0;
    } else {
      particle.update();
      // 在正常游戏阶段应用涟漪和排斥效果
      if (completionPhase === 'none') {
        particle.applyRippleAndRepel();
      }
    }
    particle.display();
  }

  // 更新和显示涟漪效果
  for (let i = rippleEffects.length - 1; i >= 0; i--) {
    rippleEffects[i].update();
    rippleEffects[i].display();
    if (rippleEffects[i].alpha <= 0) rippleEffects.splice(i, 1);
  }

  // 更新和显示排斥爆发效果
  for (let i = repelBursts.length - 1; i >= 0; i--) {
    repelBursts[i].update();
    repelBursts[i].display();
    if (repelBursts[i].alpha <= 0) repelBursts.splice(i, 1);
  }
  
  // 更新和显示效果传递（只在未完成或排列阶段）
  if (completionPhase === 'none' || completionPhase === 'arranging') {
    for (let i = effectDisplays.length - 1; i >= 0; i--) {
      if (!effectDisplays[i].update()) {
        // 效果结束，清理并移除
        effectDisplays[i].cleanup();
        effectDisplays.splice(i, 1);
      } else {
        // 效果仍在进行，显示效果
        effectDisplays[i].display();
      }
    }
  } else {
    // 完成阶段：清理所有效果
    for (let i = effectDisplays.length - 1; i >= 0; i--) {
      effectDisplays[i].cleanup();
      effectDisplays.splice(i, 1);
    }
  }

  // 绘制连接线
  drawConnections();

  // 检测完成状态：所有红色粒子是否连接且与蓝色粒子分离
  let newCompleted = checkRedParticlesConnected();
  if (newCompleted && !isCompleted) {
    isCompleted = true;
    startCompletionSequence();
  }
  
  // 更新完成动画
  if (isCompleted) {
    updateCompletionAnimation();
  }
  
  // 绘制波形（在完成阶段）
  if (completionPhase === 'revealing' || completionPhase === 'completed') {
    drawWaveform();
  }
  
  // 显示波形交互提示和UI
  if (completionPhase === 'completed') {
    displayWaveformInteraction();
  }
}

// ==================== 连接检测 ====================
/**
 * 检测粒子之间的连接变化，并在红色粒子和蓝色粒子新建立连接时触发效果传递
 * 此函数会更新每个粒子的connections数组，记录真正连接的粒子
 */
function detectConnectionChanges() {
  let currentConnections = new Map();
  
  // 遍历所有候选粒子对，检测连接关系
  for (let i = 0; i < candidateParticles.length; i++) {
    let particle1 = candidateParticles[i];
    particle1.connections = [];  // 重置连接数组
    
    for (let j = i + 1; j < candidateParticles.length; j++) {
      let particle2 = candidateParticles[j];
      let distance = dist(particle1.x, particle1.y, particle2.x, particle2.y);
      
      // 如果距离小于连接阈值，则建立连接
      if (distance < connectionDistance) {
        let connectionKey = `${min(i, j)}-${max(i, j)}`;
        currentConnections.set(connectionKey, {
          p1: particle1, 
          p2: particle2, 
          d: distance
        });
        
        // 在双方的connections数组中记录连接关系
        particle1.connections.push(particle2);
        particle2.connections.push(particle1);
      }
    }
  }
  
  // 检测新建立的连接（红色粒子与蓝色粒子之间的连接）
  if (initializationComplete && previousConnections.size > 0) {
    for (let [connectionKey, connection] of currentConnections) {
      // 如果这是一个新建立的连接
      if (!previousConnections.has(connectionKey)) {
        let particle1 = connection.p1;
        let particle2 = connection.p2;
        
        // 检查是否是红色粒子与蓝色粒子的新连接
        // 条件：一个是红色粒子（原始红色），另一个是蓝色粒子
        let isRedToBlueConnection = 
          (particle1.isRed && !particle2.isRed && !particle2.isOrange && 
           !particle1.isStatic && !particle1.hasEffect && particle1.originalIsRed === true) ||
          (!particle1.isRed && !particle1.isOrange && particle2.isRed && 
           !particle2.isStatic && !particle2.hasEffect && particle2.originalIsRed === true);
        
        if (isRedToBlueConnection) {
          let redParticle = particle1.isRed ? particle1 : particle2;
          let blueParticle = particle1.isRed ? particle2 : particle1;
          
          // 确保蓝色粒子可以接收效果
          if (!blueParticle.isStatic && !blueParticle.hasEffect && !blueParticle.isOrange) {
            triggerEffectPropagation(redParticle, blueParticle);
          }
        }
      }
    }
  }
  
  // 更新上一帧的连接状态
  previousConnections = currentConnections;
}

/**
 * 触发效果传递（从红色粒子传递到蓝色粒子）
 * @param {CandidateParticle} redParticle - 红色粒子（效果源）
 * @param {CandidateParticle} blueParticle - 蓝色粒子（效果目标）
 */
function triggerEffectPropagation(redParticle, blueParticle) {
  if (blueParticle) {
    effectDisplays.push(new EffectDisplay(redParticle, blueParticle));
  }
}

// ==================== 绘制连接线 ====================
function drawConnections() {
  // 蓝色粒子之间的连线
  if (completionPhase === 'none' || completionPhase === 'arranging') {
    strokeWeight(1.2);
    for (let i = 0; i < candidateParticles.length; i++) {
      for (let j = i + 1; j < candidateParticles.length; j++) {
        if (candidateParticles[i].isRed && candidateParticles[j].isRed) continue;
        
        let d = dist(candidateParticles[i].x, candidateParticles[i].y, candidateParticles[j].x, candidateParticles[j].y);
        if (d < connectionDistance) {
          let alpha = map(d, 0, connectionDistance, 180, 0) * (fadeOutAlpha / 255);
          stroke(100, 200, 255, alpha);
          line(candidateParticles[i].x, candidateParticles[i].y, candidateParticles[j].x, candidateParticles[j].y);
        }
      }
    }
  }

  // 红色粒子之间的连线
  if (completionPhase === 'none' || completionPhase === 'arranging') {
    let redParticles = candidateParticles.filter(p => p.isRed);
    strokeWeight(2.5);
    for (let i = 0; i < redParticles.length; i++) {
      for (let j = i + 1; j < redParticles.length; j++) {
        let d = dist(redParticles[i].x, redParticles[i].y, redParticles[j].x, redParticles[j].y);
        if (d < connectionDistance) {
          let alpha = map(d, 0, connectionDistance, 255, 100);
          stroke(255, 100, 100, alpha);
          line(redParticles[i].x, redParticles[i].y, redParticles[j].x, redParticles[j].y);
        }
      }
    }
  }
}

// ==================== 完成序列 ====================
function startCompletionSequence() {
  completionPhase = 'arranging';
  completionTimer = 0;
  fadeOutAlpha = 255;
  
  // 重置波形和平静进度
  calmProgress = 0;
  waveformPoints = [];
  
  orderedRedParticles = getOrderedRedParticles();
  let displayLength = min(orderedRedParticles.length, targetSentence.length);
  
  let totalWidth = displayLength * (fontSize + 20);
  let startX = (width - totalWidth) / 2 + fontSize / 2;
  let centerY = height / 2;
  
  targetPositions = [];
  for (let i = 0; i < displayLength; i++) {
    targetPositions.push({
      x: startX + i * (fontSize + 20),
      y: centerY
    });
  }
  
  if (orderedRedParticles.length > displayLength) {
    orderedRedParticles = orderedRedParticles.slice(0, displayLength);
  }
  
  effectDisplays = [];
  
  for (let p of candidateParticles) {
    if (!p.isRed) {
      p.vx = 0;
      p.vy = 0;
      if (p.isOrange) {
        p.hasEffect = false;
        p.changeSpeedMultiplier = 1;
        p.vibrationOffsetX = 0;
        p.vibrationOffsetY = 0;
      }
    }
  }
}

function getOrderedRedParticles() {
  let redParticles = candidateParticles.filter(p => p.isRed);
  if (redParticles.length === 0) return [];
  if (redParticles.length === 1) return redParticles;
  
  let startIdx = 0;
  let minX = redParticles[0].x;
  for (let i = 1; i < redParticles.length; i++) {
    if (redParticles[i].x < minX) {
      minX = redParticles[i].x;
      startIdx = i;
    }
  }
  
  let ordered = [];
  let visited = new Set();
  let stack = [startIdx];
  visited.add(startIdx);
  
  while (stack.length > 0) {
    let currentIdx = stack.pop();
    ordered.push(redParticles[currentIdx]);
    
    for (let i = 0; i < redParticles.length; i++) {
      if (!visited.has(i)) {
        let d = dist(redParticles[currentIdx].x, redParticles[currentIdx].y,
                     redParticles[i].x, redParticles[i].y);
        if (d < connectionDistance) {
          visited.add(i);
          stack.push(i);
        }
      }
    }
  }
  
  return ordered;
}

/**
 * 更新完成动画：包括文字揭示、抖动效果和波形交互
 */
function updateCompletionAnimation() {
  completionTimer++;
  
  // 更新波形交互
  if (completionPhase === 'revealing' || completionPhase === 'completed') {
    updateWaveform();
  }
  
  if (completionPhase === 'arranging') {
    let allArrived = true;
    for (let i = 0; i < orderedRedParticles.length; i++) {
      let p = orderedRedParticles[i];
      let target = targetPositions[i];
      
      p.x = lerp(p.x, target.x, 0.1);
      p.y = lerp(p.y, target.y, 0.1);
      
      if (dist(p.x, p.y, target.x, target.y) > 1) {
        allArrived = false;
      }
    }
    
    fadeOutAlpha = max(0, fadeOutAlpha - 5);
    
    if (allArrived && completionTimer > 30) {
      completionPhase = 'revealing';
      completionTimer = 0;
      
      // 初始化波形（如果还没有初始化）
      if (waveformPoints.length === 0) {
        initializeWaveform();
      }
      
      // 初始化抖动和漂浮效果
      for (let p of orderedRedParticles) {
        p.changeTimer = 5;
        p.changeSpeedMultiplier = 10;
        p.isStatic = false;
        // 初始化抖动参数
        p.anxietyShakePhase = random(TWO_PI);
        p.anxietyShakeIntensity = random(2, 4);
        p.anxietyFloatPhase = random(TWO_PI);
        p.anxietyFloatSpeed = random(0.02, 0.04);
      }
    }
  } else if (completionPhase === 'revealing') {
    let revealDelay = 15;
    let currentIndex = floor(completionTimer / revealDelay);
    
    for (let i = 0; i < orderedRedParticles.length; i++) {
      let p = orderedRedParticles[i];
      
      if (i < currentIndex) {
        if (i < targetSentence.length) {
          p.char = targetSentence.charAt(i);
        }
        p.isStatic = false;  // 允许抖动
        p.changeSpeedMultiplier = 1;
        p.phraseGroup = null;
        // 更新抖动和漂浮
        updateAnxietyEffect(p);
      } else if (i === currentIndex) {
        p.changeTimer = max(0, p.changeTimer - 1);
        p.changeSpeedMultiplier = 20;
        p.isStatic = false;
        p.phraseGroup = null;
        
        if (completionTimer % revealDelay >= revealDelay - 5) {
          if (i < targetSentence.length) {
            p.char = targetSentence.charAt(i);
          }
          // 初始化抖动参数
          if (p.anxietyShakePhase === undefined) {
            p.anxietyShakePhase = random(TWO_PI);
            p.anxietyShakeIntensity = random(2, 4);
            p.anxietyFloatPhase = random(TWO_PI);
            p.anxietyFloatSpeed = random(0.02, 0.04);
          }
        } else {
          if (p.changeTimer <= 0) {
            p.char = chineseChars.charAt(floor(random(chineseChars.length)));
            p.changeTimer = 2;
          }
        }
      } else {
        p.changeTimer = max(0, p.changeTimer - 1);
        p.changeSpeedMultiplier = 20;
        p.isStatic = false;
        p.phraseGroup = null;
        
        if (p.changeTimer <= 0) {
          p.char = chineseChars.charAt(floor(random(chineseChars.length)));
          p.changeTimer = 2;
        }
      }
    }
    
    if (currentIndex >= orderedRedParticles.length) {
      completionPhase = 'completed';
      for (let i = 0; i < orderedRedParticles.length && i < targetSentence.length; i++) {
        orderedRedParticles[i].char = targetSentence.charAt(i);
        orderedRedParticles[i].isStatic = false;  // 允许抖动
        orderedRedParticles[i].changeSpeedMultiplier = 1;
        orderedRedParticles[i].phraseGroup = null;
        // 确保有抖动参数
        if (orderedRedParticles[i].anxietyShakePhase === undefined) {
          orderedRedParticles[i].anxietyShakePhase = random(TWO_PI);
          orderedRedParticles[i].anxietyShakeIntensity = random(2, 4);
          orderedRedParticles[i].anxietyFloatPhase = random(TWO_PI);
          orderedRedParticles[i].anxietyFloatSpeed = random(0.02, 0.04);
        }
      }
    }
  } else if (completionPhase === 'completed') {
    // 在完成阶段，更新所有文字的抖动和漂浮效果
    for (let p of orderedRedParticles) {
      updateAnxietyEffect(p);
    }
    
    // 检查所有文字是否都已消散完成
    let allDissolved = true;
    for (let p of orderedRedParticles) {
      let dissolveProgress = p.dissolveProgress !== undefined ? p.dissolveProgress : 0;
      if (dissolveProgress < 1) {
        allDissolved = false;
        break;
      }
    }
    
    // 如果所有文字都已消散，触发游戏结束
    if (allDissolved && orderedRedParticles.length > 0) {
      // 可以在这里添加结束动画或回调
      // 例如：延迟一段时间后重置游戏或显示结束画面
      if (completionTimer > 60) { // 等待1秒（假设60fps）后重置
        resetGame();
      }
    }
  }
}

/**
 * 更新焦虑效果（抖动和漂浮）- 根据对应波形点的抚平程度
 * @param {CandidateParticle} particle - 要更新的粒子
 */
function updateAnxietyEffect(particle) {
  if (particle.anxietyShakePhase === undefined) {
    particle.anxietyShakePhase = random(TWO_PI);
    particle.anxietyShakeIntensity = random(2, 4);
    particle.anxietyFloatPhase = random(TWO_PI);
    particle.anxietyFloatSpeed = random(0.02, 0.04);
  }
  
  // 获取对应波形点的抚平程度（如果没有关联，使用整体平静进度）
  let localCalmProgress = particle.calmProgress !== undefined ? particle.calmProgress : calmProgress;
  
  // 根据对应波形点的抚平程度减少抖动强度（0 = 完全抖动，1 = 完全平静）
  let shakeIntensity = particle.anxietyShakeIntensity * (1 - localCalmProgress);
  let floatAmplitude = 3 * (1 - localCalmProgress);
  
  // 如果抚平程度高，停止抖动更新
  if (localCalmProgress < 0.9) {
    // 更新抖动相位
    particle.anxietyShakePhase += 0.3;
    particle.anxietyFloatPhase += particle.anxietyFloatSpeed;
  }
  
  // 计算抖动偏移（随机方向的小幅度抖动）
  particle.anxietyShakeX = cos(particle.anxietyShakePhase) * shakeIntensity + 
                           sin(particle.anxietyShakePhase * 1.7) * shakeIntensity * 0.5;
  particle.anxietyShakeY = sin(particle.anxietyShakePhase * 1.3) * shakeIntensity + 
                           cos(particle.anxietyShakePhase * 0.9) * shakeIntensity * 0.5;
  
  // 计算漂浮偏移（缓慢的上下浮动）
  particle.anxietyFloatY = sin(particle.anxietyFloatPhase) * floatAmplitude;
}

/**
 * 初始化波形数据（与文字位置对应）
 */
function initializeWaveform() {
  waveformPoints = [];
  waveformParticles = [];
  
  // 确保orderedRedParticles已经初始化
  if (orderedRedParticles.length === 0) return;
  
  // 波形显示在文字位置（与文字重叠）
  waveformBaseY = height / 2;  // 与文字中心对齐
  
  // 为每个文字粒子创建一个对应的波形点
  for (let i = 0; i < orderedRedParticles.length; i++) {
    let particle = orderedRedParticles[i];
    let x = particle.x;
    
    // 异常波形：使用Perlin噪声生成真实的噪波效果
    // 使用多个频率的噪声叠加，产生更自然的噪波
    let noiseScale1 = 0.05;  // 低频噪声（大范围变化）
    let noiseScale2 = 0.15;  // 中频噪声
    let noiseScale3 = 0.4;   // 高频噪声（细节变化）
    
    // 使用p5.js的noise函数生成平滑的噪声值
    let noiseValue1 = noise(x * noiseScale1, frameCount * 0.01);
    let noiseValue2 = noise(x * noiseScale2, frameCount * 0.02 + 100);
    let noiseValue3 = noise(x * noiseScale3, frameCount * 0.03 + 200);
    
    // 将噪声值从[0,1]映射到[-1,1]，并叠加多层
    noiseValue1 = map(noiseValue1, 0, 1, -1, 1);
    noiseValue2 = map(noiseValue2, 0, 1, -1, 1) * 0.6;
    noiseValue3 = map(noiseValue3, 0, 1, -1, 1) * 0.3;
    
    // 叠加多层噪声，产生更复杂的噪波
    let combinedNoise = noiseValue1 + noiseValue2 + noiseValue3;
    combinedNoise = constrain(combinedNoise, -1.5, 1.5);
    
    // 添加一些随机性，使每个点的噪波特征不同
    let noiseAmplitude = random(0.7, 1.0);
    let noiseOffset = random(-0.2, 0.2);
    let initialY = waveformBaseY + (combinedNoise + noiseOffset) * noiseAmplitude * waveformAmplitude;
    
    // 抚平后的目标位置：规律的波形（正弦波）
    let targetWavePhase = i * 0.3;  // 波形相位
    let targetWaveAmplitude = waveformAmplitude * 0.3;  // 规律波形的振幅（较小）
    let targetY = waveformBaseY + sin(targetWavePhase) * targetWaveAmplitude;
    
    // 计算每个点的异常程度（距离规律波形的距离）
    let anomalyAmount = abs(initialY - targetY) / waveformAmplitude;
    
    waveformPoints.push({
      x: x,
      y: initialY,                    // 初始Y坐标（噪波）
      targetY: targetY,               // 目标位置（规律的波形）
      smoothedY: initialY,            // 平滑后的Y坐标
      noiseOffset: noiseOffset,       // 噪声偏移（用于动态噪波）
      noiseScale1: noiseScale1,      // 噪声缩放1
      noiseScale2: noiseScale2,      // 噪声缩放2
      noiseScale3: noiseScale3,      // 噪声缩放3
      noiseAmplitude: noiseAmplitude, // 噪声振幅
      noiseTime: random(0, 1000),     // 噪声时间偏移（使每个点有不同的时间相位）
      resistance: 0.1 + anomalyAmount * 0.25,  // 抵抗力（异常程度越高，抵抗力越强）
      calmAmount: 0,                  // 当前被抚平的程度（0-1）
      isBeingCalmed: false,           // 是否正在被抚平
      calmTimer: 0,                   // 抚平计时器
      particle: particle,              // 关联的文字粒子
      particleIndex: i                // 粒子索引
    });
    
    // 在文字粒子上添加波形关联
    particle.waveformPoint = waveformPoints[waveformPoints.length - 1];
    particle.calmProgress = 0;        // 文字粒子的平静进度
    particle.dissolveProgress = 0;     // 文字粒子的消散进度
    particle.isCalmed = false;        // 是否已被抚平
  }
  
  // 初始化鼠标位置
  lastMouseX = mouseX;
  lastMouseY = mouseY;
}

/**
 * 更新波形：根据平静进度平滑波形，并处理鼠标交互（带抵抗力和愈合机制）
 */
function updateWaveform() {
  if (waveformPoints.length === 0) {
    initializeWaveform();
  }
  
  // 计算鼠标拖动速度
  mouseDragSpeed = dist(mouseX, mouseY, lastMouseX, lastMouseY);
  lastMouseX = mouseX;
  lastMouseY = mouseY;
  
  // 更新粒子效果
  for (let i = waveformParticles.length - 1; i >= 0; i--) {
    let p = waveformParticles[i];
    p.x += p.vx;
    p.y += p.vy;
    p.vy += 0.1; // 重力
    p.alpha -= 3;
    p.size *= 0.98;
    
    if (p.alpha <= 0 || p.size < 0.5) {
      waveformParticles.splice(i, 1);
    }
  }
  
  // 更新每个波形点
  for (let point of waveformPoints) {
    // 同步更新波形点的X位置（跟随文字位置）
    if (point.particle) {
      point.x = point.particle.x;
      // 更新规律波形的目标位置（跟随文字位置）
      let targetWavePhase = point.particleIndex * 0.3;
      let targetWaveAmplitude = waveformAmplitude * 0.3;
      point.targetY = waveformBaseY + sin(targetWavePhase) * targetWaveAmplitude;
    }
    
    // 重置状态
    point.isBeingCalmed = false;
    
    // 如果未抚平，添加动态噪波效果（表现异常波形的"生命力"）
    if (point.calmAmount < 0.9) {
      // 使用Perlin噪声生成动态噪波
      let currentTime = frameCount * 0.01 + point.noiseTime * 0.001;
      
      // 生成多层噪声
      let noiseValue1 = noise(point.x * point.noiseScale1, currentTime);
      let noiseValue2 = noise(point.x * point.noiseScale2, currentTime * 1.5 + 100);
      let noiseValue3 = noise(point.x * point.noiseScale3, currentTime * 2 + 200);
      
      // 映射并叠加
      noiseValue1 = map(noiseValue1, 0, 1, -1, 1);
      noiseValue2 = map(noiseValue2, 0, 1, -1, 1) * 0.6;
      noiseValue3 = map(noiseValue3, 0, 1, -1, 1) * 0.3;
      
      let dynamicNoise = noiseValue1 + noiseValue2 + noiseValue3 + point.noiseOffset;
      dynamicNoise = constrain(dynamicNoise, -1.5, 1.5);
      
      // 根据抚平程度减少噪波强度（抚平程度越高，噪波越弱）
      let noiseIntensity = (1 - point.calmAmount) * waveformAmplitude * point.noiseAmplitude;
      point.smoothedY = point.y + dynamicNoise * noiseIntensity;
    }
    
    // 处理鼠标交互：如果鼠标在附近，尝试抚平该区域的波形
    if (mouseIsPressed && mouseButton === LEFT) {
      let distToMouse = dist(mouseX, mouseY, point.x, point.smoothedY);
      if (distToMouse < interactionRadius) {
        // 计算影响强度（距离越近影响越大，拖动速度越快影响越大）
        let distanceInfluence = map(distToMouse, 0, interactionRadius, 1, 0);
        let speedBonus = min(1, mouseDragSpeed / 5); // 拖动速度加成
        let influence = distanceInfluence * (0.5 + speedBonus * 0.5);
        
        // 标记为正在被抚平
        point.isBeingCalmed = true;
        point.calmTimer++;
        
        // 需要持续按住才能抚平（抵抗机制）
        // 抵抗力越高，需要按住的时间越长
        let requiredTime = 30 + point.resistance * 60; // 需要按住30-90帧
        let calmProgressLocal = min(1, point.calmTimer / requiredTime);
        
        // 应用抚平效果（考虑抵抗力）
        let calmStrength = influence * (1 - point.resistance) * 0.15;
        
        // 计算目标位置（从噪波位置向规律波形位置过渡）
        let targetY = lerp(point.y, point.targetY, calmProgressLocal);
        point.smoothedY = lerp(point.smoothedY, targetY, calmStrength);
        
        // 更新抚平程度（一旦抚平，就不会恢复）
        if (calmProgressLocal > point.calmAmount) {
          point.calmAmount = calmProgressLocal;
        }
        
        // 如果完全抚平，标记为已抚平
        if (point.calmAmount >= 0.95) {
          point.calmAmount = 1;
          point.smoothedY = point.targetY;  // 强制设置为目标位置
        }
        
        // 如果成功抚平了一点，生成粒子效果
        if (calmProgressLocal > 0.3 && random() > 0.7) {
          for (let j = 0; j < 2; j++) {
            waveformParticles.push({
              x: point.x,
              y: point.smoothedY,
              vx: random(-1, 1),
              vy: random(-2, -0.5),
              alpha: 200,
              size: random(2, 4),
              color: [150, 200, 255]
            });
          }
        }
        
        // 增加整体平静进度（需要持续交互）
        if (calmProgressLocal > 0.5) {
          calmProgress = min(1, calmProgress + influence * 0.0005);
        }
      } else {
        // 不在交互范围内，保持当前状态（不重置）
      }
    }
    
    // 关键：如果calmAmount接近1，强制让波形显示为规律波形
    if (point.calmAmount > 0.8) {
      // 高度抚平的点，强制向目标位置（规律波形）靠拢
      let forceStrength = map(point.calmAmount, 0.8, 1, 0.1, 0.5);
      point.smoothedY = lerp(point.smoothedY, point.targetY, forceStrength);
    }
    
    // 如果完全抚平，直接设置为目标位置（规律波形）
    if (point.calmAmount >= 1) {
      point.smoothedY = point.targetY;
    }
    
    // 限制波形点不会完全超出范围（抚平的点限制更严格）
    let maxDeviation = waveformAmplitude * (1 - calmProgress * 0.5) * (1 - point.calmAmount * 0.8);
    point.smoothedY = constrain(point.smoothedY, 
                                 waveformBaseY - maxDeviation, 
                                 waveformBaseY + maxDeviation);
    
    // 同步更新对应文字粒子的平静进度
    if (point.particle) {
      let oldCalmProgress = point.particle.calmProgress || 0;
      point.particle.calmProgress = point.calmAmount;
      
      // 如果完全抚平（calmAmount >= 0.95），标记为已稳定
      if (point.calmAmount >= 0.95 && !point.particle.isCalmed) {
        point.particle.isCalmed = true;
        // 停止字符变化和抖动
        point.particle.isStatic = true;
        point.particle.changeSpeedMultiplier = 0;
        // 初始化消散进度（从0开始，等待波形完全稳定）
        point.particle.dissolveProgress = 0;
        point.particle.stableTimer = 0; // 稳定计时器
      }
      
      // 如果已抚平，检查波形是否完全稳定（接近目标位置）
      if (point.particle.isCalmed) {
        // 检查波形是否稳定（距离目标位置很近）
        let distanceToTarget = abs(point.smoothedY - point.targetY);
        let stabilityThreshold = waveformAmplitude * 0.05; // 稳定阈值
        
        if (distanceToTarget < stabilityThreshold) {
          // 波形已稳定，开始增加稳定计时器
          if (point.particle.stableTimer === undefined) {
            point.particle.stableTimer = 0;
          }
          point.particle.stableTimer++;
          
          // 稳定一段时间后（例如30帧），开始消散
          if (point.particle.stableTimer > 30 && point.particle.dissolveProgress < 1) {
            // 消散速度：根据波形稳定程度决定
            let dissolveSpeed = 0.02 + (point.calmAmount - 0.95) * 0.15; // 0.02到0.07之间
            point.particle.dissolveProgress = min(1, point.particle.dissolveProgress + dissolveSpeed);
          }
        } else {
          // 波形还未完全稳定，重置稳定计时器
          point.particle.stableTimer = 0;
        }
      }
    }
  }
  
  // 如果平静进度达到1，波形完全平滑
  if (calmProgress >= 1) {
    for (let point of waveformPoints) {
      point.smoothedY = lerp(point.smoothedY, point.targetY, 0.05);
      point.calmAmount = 1;
    }
  }
}

/**
 * 绘制波形（带粒子效果和动态颜色）
 */
function drawWaveform() {
  if (waveformPoints.length === 0) return;
  
  // 绘制粒子效果
  for (let p of waveformParticles) {
    fill(p.color[0], p.color[1], p.color[2], p.alpha);
    noStroke();
    ellipse(p.x, p.y, p.size);
    
    // 添加光晕效果
    fill(p.color[0], p.color[1], p.color[2], p.alpha * 0.3);
    ellipse(p.x, p.y, p.size * 2);
  }
  
  // 绘制波形线（根据抚平程度改变颜色：红色=异常，蓝色=正常）
  noFill();
  strokeWeight(2.5);
  
  beginShape();
  for (let i = 0; i < waveformPoints.length; i++) {
    let point = waveformPoints[i];
    
    // 同步更新波形点的X位置（跟随文字位置）
    if (point.particle) {
      point.x = point.particle.x;
    }
    
    // 获取对应文字的消散进度
    let particleDissolve = point.particle && point.particle.dissolveProgress !== undefined ? 
                          point.particle.dissolveProgress : 0;
    
    // 根据抚平程度混合颜色：红色(255,100,100) -> 蓝色(150,200,255)
    let redValue = lerp(255, 150, point.calmAmount);
    let greenValue = lerp(100, 200, point.calmAmount);
    let blueValue = lerp(100, 255, point.calmAmount);
    let alphaValue = 200 * (1 - calmProgress * 0.3) * (1 - particleDissolve);
    
    // 如果文字完全消散，不绘制该点
    if (particleDissolve < 1) {
      stroke(redValue, greenValue, blueValue, alphaValue);
      vertex(point.x, point.smoothedY);
    }
  }
  endShape();
  
  // 绘制波形点（显示抵抗力和抚平进度）
  for (let i = 0; i < waveformPoints.length; i += 3) {
    let point = waveformPoints[i];
    
    // 获取对应文字的消散进度
    let particleDissolve = point.particle && point.particle.dissolveProgress !== undefined ? 
                          point.particle.dissolveProgress : 0;
    
    // 如果文字完全消散，不绘制
    if (particleDissolve >= 1) continue;
    
    // 如果正在被抚平，显示进度
    if (point.isBeingCalmed && point.calmTimer > 0) {
      let requiredTime = 30 + point.resistance * 60;
      let progress = min(1, point.calmTimer / requiredTime);
      
      // 绘制进度环
      push();
      translate(point.x, point.smoothedY);
      noFill();
      stroke(150, 200, 255, 150 * (1 - particleDissolve));
      strokeWeight(1);
      arc(0, 0, 8, 8, -HALF_PI, -HALF_PI + TWO_PI * progress);
      pop();
    }
    
    // 绘制波形点（根据抚平程度和消散进度）
    let pointAlpha = 150 * (1 - point.calmAmount) * (1 - calmProgress * 0.5) * (1 - particleDissolve);
    if (pointAlpha > 10) {
      let redValue = lerp(255, 150, point.calmAmount);
      let greenValue = lerp(100, 200, point.calmAmount);
      let blueValue = lerp(100, 255, point.calmAmount);
      fill(redValue, greenValue, blueValue, pointAlpha);
      noStroke();
      ellipse(point.x, point.smoothedY, 3);
    }
  }
  
  // 绘制交互反馈（如果鼠标按下）
  if (mouseIsPressed && mouseButton === LEFT) {
    // 内圈（交互范围）
    fill(150, 200, 255, 30);
    noStroke();
    ellipse(mouseX, mouseY, interactionRadius * 2);
    
    // 外圈（视觉反馈）
    stroke(150, 200, 255, 150);
    strokeWeight(2);
    noFill();
    ellipse(mouseX, mouseY, interactionRadius * 2);
    
    // 根据拖动速度显示额外反馈
    if (mouseDragSpeed > 2) {
      stroke(150, 200, 255, 100);
      strokeWeight(1);
      noFill();
      ellipse(mouseX, mouseY, interactionRadius * 2.5);
    }
  }
  
  // 绘制基准线（目标线）
  if (calmProgress < 0.9) {
    stroke(150, 200, 255, 50 * (1 - calmProgress));
    strokeWeight(1);
    line(width * 0.2, waveformBaseY, width * 0.8, waveformBaseY);
  }
}

// ==================== 游戏逻辑 ====================
function updatePhraseGroups() {
  phraseGroups = [];
  let visited = new Set();
  
  for (let i = 0; i < candidateParticles.length; i++) {
    if (visited.has(i)) continue;
    
    let group = [];
    let stack = [i];
    visited.add(i);
    
    while (stack.length > 0) {
      let currentIdx = stack.pop();
      let current = candidateParticles[currentIdx];
      group.push(current);
      
      for (let j = 0; j < candidateParticles.length; j++) {
        if (j === currentIdx || visited.has(j)) continue;
        let d = dist(current.x, current.y, candidateParticles[j].x, candidateParticles[j].y);
        if (d < connectionDistance) {
          visited.add(j);
          stack.push(j);
        }
      }
    }
    
    if (group.length >= 2) {
      phraseGroups.push(group);
      for (let p of group) {
        p.phraseGroup = group;
        p.phraseIndex = group.indexOf(p);
      }
    }
  }
}

function selectRedParticles() {
  let selected = [];
  let minDistance = min(textAreaWidth, textAreaHeight) / (redParticleCount * 0.8);
  
  let shuffled = [...candidateParticles];
  for (let i = shuffled.length - 1; i > 0; i--) {
    let j = floor(random(i + 1));
    [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
  }
  
  for (let particle of shuffled) {
    if (selected.length >= redParticleCount) break;
    
    let tooClose = false;
    for (let selectedP of selected) {
      if (dist(particle.x, particle.y, selectedP.x, selectedP.y) < minDistance) {
        tooClose = true;
        break;
      }
    }
    
    if (!tooClose || selected.length === 0) {
      particle.isRed = true;
      particle.originalIsRed = true;
      selected.push(particle);
    }
  }
  
  let remaining = redParticleCount - selected.length;
  for (let i = 0; i < remaining && i < shuffled.length; i++) {
    if (!shuffled[i].isRed) {
      shuffled[i].isRed = true;
      shuffled[i].originalIsRed = true;
      selected.push(shuffled[i]);
    }
  }
}

function checkRedParticlesConnected() {
  let redParticles = candidateParticles.filter(p => p.isRed);
  if (redParticles.length === 0) return false;
  if (redParticles.length === 1) return true;
  
  let visited = new Set();
  let stack = [0];
  visited.add(0);
  
  while (stack.length > 0) {
    let current = stack.pop();
    for (let i = 0; i < redParticles.length; i++) {
      if (!visited.has(i)) {
        let d = dist(redParticles[current].x, redParticles[current].y, 
                     redParticles[i].x, redParticles[i].y);
        if (d < connectionDistance) {
          visited.add(i);
          stack.push(i);
        }
      }
    }
  }
  
  let allRedConnected = visited.size === redParticles.length;
  if (!allRedConnected) return false;
  
  for (let redP of redParticles) {
    for (let p of candidateParticles) {
      if (p.isRed) continue;
      
      let d = dist(redP.x, redP.y, p.x, p.y);
      if (d < connectionDistance) {
        return false;
      }
    }
  }
  
  return true;
}

function resetGame() {
  isCompleted = false;
  completionPhase = 'none';
  completionTimer = 0;
  fadeOutAlpha = 255;
  orderedRedParticles = [];
  targetPositions = [];
  effectDisplays = [];
  previousConnections = new Map();
  initializationComplete = false;
  initializationFrames = 0;
  
  // 重置波形交互状态
  calmProgress = 0;
  waveformPoints = [];
  waveformParticles = [];
  mouseInteractionActive = false;
  lastMouseX = 0;
  lastMouseY = 0;
  mouseDragSpeed = 0;
  
  for (let p of candidateParticles) {
    p.isRed = false;
    p.isOrange = false;
    p.hasEffect = false;
    p.isStatic = false;
    p.originalIsRed = false;
    p.changeSpeedMultiplier = 1;
    p.vibrationOffsetX = 0;
    p.vibrationOffsetY = 0;
    p.vx = random(-0.5, 0.5);
    p.vy = random(-0.5, 0.5);
    p.x = random(textAreaX, textAreaX + textAreaWidth);
    p.y = random(textAreaY, textAreaY + textAreaHeight);
    p.alpha = p.baseAlpha + p.prob * 105;
    p.phraseGroup = null;
    p.phraseIndex = 0;
    
    // 重置波形相关状态
    p.calmProgress = undefined;
    p.dissolveProgress = undefined;
    p.isCalmed = false;
    p.waveformPoint = undefined;
    p.stableTimer = undefined;
    
    // 重置抖动和漂浮参数
    p.anxietyShakePhase = undefined;
    p.anxietyShakeIntensity = undefined;
    p.anxietyFloatPhase = undefined;
    p.anxietyFloatSpeed = undefined;
    p.anxietyShakeX = undefined;
    p.anxietyShakeY = undefined;
    p.anxietyFloatY = undefined;
  }
  
  selectRedParticles();
  rippleEffects = [];
  repelBursts = [];
}

// ==================== UI ====================
/**
 * 显示波形交互提示和UI
 */
function displayWaveformInteraction() {
  // 显示提示文字
  let hintY = height / 2 + 180;
  fill(200, 220, 255, 180);
  textSize(16);
  textAlign(CENTER, CENTER);
  
  // 检查是否有文字正在消散
  let hasDissolving = false;
  for (let p of orderedRedParticles) {
    if (p.dissolveProgress !== undefined && p.dissolveProgress > 0 && p.dissolveProgress < 1) {
      hasDissolving = true;
      break;
    }
  }
  
  // 检查是否所有文字都已消散
  let allDissolved = true;
  for (let p of orderedRedParticles) {
    if (p.dissolveProgress === undefined || p.dissolveProgress < 1) {
      allDissolved = false;
      break;
    }
  }
  
  if (allDissolved) {
    fill(150, 255, 150, 200);
    text("所有文字已消散，情绪已完全平静", width / 2, hintY);
  } else if (hasDissolving) {
    fill(150, 220, 255, 200);
    text("波形已稳定，文字正在消散...", width / 2, hintY);
  } else if (calmProgress < 1) {
    if (mouseIsPressed && mouseButton === LEFT) {
      text("持续按住并拖动，抚平波形（波形稳定后文字会消散）", width / 2, hintY);
    } else {
      text("按住鼠标左键持续拖动，抚平异常波形（波形稳定后文字会消散）", width / 2, hintY);
    }
    
    // 显示平静进度条
    let progressBarX = width / 2;
    let progressBarY = hintY + 25;
    let progressBarWidth = 200;
    let progressBarHeight = 8;
    
    // 背景
    fill(50, 50, 70, 150);
    rectMode(CENTER);
    rect(progressBarX, progressBarY, progressBarWidth, progressBarHeight, 4);
    
    // 进度
    fill(150, 200, 255, 200);
    rectMode(CORNER);
    rect(progressBarX - progressBarWidth / 2, progressBarY - progressBarHeight / 2, 
         progressBarWidth * calmProgress, progressBarHeight, 4);
  } else {
    fill(150, 255, 150, 200);
    text("异常波形已抚平，等待文字消散...", width / 2, hintY);
  }
}

function displayNextButton() {
  // 不再显示下一步按钮，游戏会在所有文字消散后自动重置
  // 保留函数以防其他地方调用
}

// ==================== 事件处理 ====================
/**
 * 鼠标按下事件
 */
function mousePressed() {
  if (completionPhase === 'completed') {
    // 在完成阶段，鼠标交互用于抚平波形（在updateWaveform中处理）
    // 游戏会在所有文字消散后自动重置，不需要手动点击按钮
  }
  
  if (isCompleted && completionPhase !== 'completed') {
    return;
  }
  
  // 正常游戏阶段的交互
  if (mouseButton === LEFT) {
    repelBursts.push(new RepelBurst(mouseX, mouseY));
  } else if (mouseButton === RIGHT) {
    rippleEffects.push(new Ripple(mouseX, mouseY));
  }
}

/**
 * 键盘按下事件
 */
function keyPressed() {
  // 切换词组模式
  if (key === 'p' || key === 'P') {
    phraseMode = !phraseMode;
    for (let p of candidateParticles) {
      p.phraseGroup = null;
      p.phraseIndex = 0;
    }
  }
}

// ==================== 类定义 ====================
/**
 * 效果显示类：管理从红色粒子到蓝色粒子的效果传递和显示
 * 效果会沿着连接线传播，形成连锁反应
 */
class EffectDisplay {
  /**
   * 构造函数：初始化效果传递
   * @param {CandidateParticle} redParticle - 红色粒子（效果源）
   * @param {CandidateParticle} startBlueParticle - 起始蓝色粒子（第一个接收效果的粒子）
   */
  constructor(redParticle, startBlueParticle) {
    this.redParticle = redParticle;              // 红色粒子（效果源）
    this.startBlueParticle = startBlueParticle;  // 起始蓝色粒子
    this.lifeTimer = 20;                          // 效果持续时间（帧数）
    
    this.propagatedParticles = [];                // 已传播的粒子数组（包含传播信息）
    this.propagationQueue = [];                   // 传播队列（待传播的粒子）
    this.propagationDelay = 3;                    // 传播延迟（帧数）
    this.propagationCounter = 0;                  // 传播计数器
    this.maxPropagationSteps = 8;                 // 最大传播步数
    this.currentPropagationStep = 0;               // 当前传播步数
    
    // 从红色粒子开始，找到第一个连接的蓝色粒子并应用效果
    let connectedBlueParticles = this.findConnectedBlues(this.redParticle);
    if (connectedBlueParticles.length > 0 && this.currentPropagationStep < this.maxPropagationSteps) {
      // 随机选择一个连接的蓝色粒子
      let targetParticle = connectedBlueParticles[floor(random(connectedBlueParticles.length))];
      this.applyEffectToParticle(targetParticle);
      
      // 记录传播信息
      this.propagatedParticles.push({
        particle: targetParticle,
        source: this.redParticle,
        step: 1,
        pulseProgress: 0  // 脉冲动画进度（0-1）
      });
      
      // 加入传播队列，用于下一步传播
      this.propagationQueue.push(targetParticle);
      this.currentPropagationStep = 1;
    }
  }
  
  /**
   * 对粒子应用效果（将其变为橙色并添加动画效果）
   * @param {CandidateParticle} particle - 要应用效果的粒子
   */
  applyEffectToParticle(particle) {
    // 如果粒子已经有效果，则跳过
    if (particle.hasEffect) return;
    // 如果是原始红色粒子，则跳过
    if (particle.isRed && particle.originalIsRed === true) return;
    
    // 标记粒子已有效果
    particle.hasEffect = true;
    
    // 保存原始状态
    if (particle.originalIsRed === undefined) {
      particle.originalIsRed = particle.isRed;
    }
    particle.originalChangeTimer = particle.changeTimer;
    particle.originalX = particle.x;
    particle.originalY = particle.y;
    
    // 设置效果属性
    particle.effectTimer = this.lifeTimer;
    particle.isOrange = true;              // 变为橙色
    particle.isRed = false;                 // 不再是红色
    particle.changeSpeedMultiplier = 3;     // 字符变化速度加快
    particle.vibrationOffsetX = 0;         // 振动偏移X
    particle.vibrationOffsetY = 0;          // 振动偏移Y
    particle.vibrationPhase = random(TWO_PI);  // 振动相位（随机）
  }
  
  /**
   * 查找与指定粒子真正连接的蓝色粒子（通过连接线连接）
   * @param {CandidateParticle} particle - 源粒子
   * @returns {Array} 连接的蓝色粒子数组
   */
  findConnectedBlues(particle) {
    let connected = [];
    let usedParticles = new Set();
    
    // 记录已使用的粒子（红色粒子和已传播的粒子）
    usedParticles.add(this.redParticle);
    for (let prop of this.propagatedParticles) {
      usedParticles.add(prop.particle);
    }
    
    // 只检查粒子connections数组中真正连接的粒子
    // 这样可以确保只在有连接线的粒子之间传递效果
    for (let connectedParticle of particle.connections) {
      // 检查条件：
      // 1. 粒子还没有效果
      // 2. 粒子不在已使用列表中
      // 3. 粒子不是红色
      // 4. 粒子不是橙色（已受影响的粒子）
      if (!connectedParticle.hasEffect && 
          !usedParticles.has(connectedParticle) && 
          !connectedParticle.isRed && 
          !connectedParticle.isOrange) {
        connected.push(connectedParticle);
      }
    }
    
    return connected;
  }
  
  /**
   * 更新效果传播和动画
   * @returns {boolean} 效果是否仍然有效
   */
  update() {
    this.lifeTimer--;
    this.propagationCounter++;
    
    // 每隔一定帧数进行一次传播
    if (this.propagationCounter >= this.propagationDelay && 
        this.currentPropagationStep < this.maxPropagationSteps) {
      this.propagationCounter = 0;
      
      let nextPropagationQueue = [];
      
      // 从当前传播队列中的每个粒子继续传播
      for (let sourceParticle of this.propagationQueue) {
        if (this.currentPropagationStep >= this.maxPropagationSteps) break;
        
        // 找到与源粒子连接的蓝色粒子（只查找真正有连接线的粒子）
        let connectedBlueParticles = this.findConnectedBlues(sourceParticle);
        
        if (connectedBlueParticles.length > 0) {
          // 随机选择一个连接的蓝色粒子
          let targetParticle = connectedBlueParticles[floor(random(connectedBlueParticles.length))];
          this.applyEffectToParticle(targetParticle);
          
          // 更新传播步数
          this.currentPropagationStep++;
          
          // 记录传播信息
          this.propagatedParticles.push({
            particle: targetParticle,
            source: sourceParticle,
            step: this.currentPropagationStep,
            pulseProgress: 0
          });
          
          // 如果还有传播步数，加入下一轮传播队列
          if (this.currentPropagationStep < this.maxPropagationSteps) {
            nextPropagationQueue.push(targetParticle);
          }
        }
      }
      
      // 更新传播队列
      this.propagationQueue = nextPropagationQueue.length > 0 ? nextPropagationQueue : [];
    }
    
    // 更新所有已传播粒子的效果动画
    for (let propagationInfo of this.propagatedParticles) {
      this.updateParticleEffectAnimation(propagationInfo.particle);
      // 更新脉冲动画进度
      propagationInfo.pulseProgress += 0.15;
      if (propagationInfo.pulseProgress > 1) {
        propagationInfo.pulseProgress = 1;
      }
    }
    
    // 返回效果是否仍然有效
    return this.lifeTimer > 0;
  }
  
  /**
   * 更新粒子的效果动画（振动、透明度、缩放、发光）
   * @param {CandidateParticle} particle - 要更新动画的粒子
   */
  updateParticleEffectAnimation(particle) {
    if (!particle.hasEffect) return;
    
    particle.effectTimer--;
    
    // 振动效果：粒子位置轻微振动
    let vibrationIntensity = 2;
    particle.vibrationPhase += 0.4;
    particle.vibrationOffsetX = cos(particle.vibrationPhase) * vibrationIntensity;
    particle.vibrationOffsetY = sin(particle.vibrationPhase * 1.3) * vibrationIntensity;
    
    // 透明度动画：周期性变化
    let alphaPhase = (frameCount * 0.1 + particle.vibrationPhase) % (TWO_PI);
    particle.effectAlpha = map(sin(alphaPhase), -1, 1, 150, 255);
    
    // 缩放动画：周期性缩放
    let scalePhase = (frameCount * 0.15 + particle.vibrationPhase) % (TWO_PI);
    particle.effectScale = map(sin(scalePhase), -1, 1, 0.95, 1.05);
    
    // 发光效果：根据透明度计算发光强度
    particle.effectGlow = map(particle.effectAlpha, 150, 255, 15, 25);
  }
  
  /**
   * 显示效果传播的视觉效果（连接线、脉冲、箭头）
   */
  display() {
    strokeWeight(2);
    
    // 遍历所有已传播的粒子，绘制传播效果
    for (let propagationInfo of this.propagatedParticles) {
      if (propagationInfo.source) {
        // 计算透明度：根据传播步数和剩余时间
        let stepAlpha = map(propagationInfo.step, 1, this.maxPropagationSteps, 255, 100);
        let timeAlpha = map(this.lifeTimer, 0, 20, 0, 255);
        let alpha = min(stepAlpha, timeAlpha);
        
        // 绘制连接线（从源粒子到目标粒子）
        stroke(255, 150, 50, alpha * 0.3);
        line(propagationInfo.source.x, propagationInfo.source.y, 
             propagationInfo.particle.x, propagationInfo.particle.y);
        
        // 绘制脉冲动画（沿着连接线移动的光点）
        if (propagationInfo.pulseProgress < 1) {
          let pulseX = lerp(propagationInfo.source.x, propagationInfo.particle.x, 
                           propagationInfo.pulseProgress);
          let pulseY = lerp(propagationInfo.source.y, propagationInfo.particle.y, 
                           propagationInfo.pulseProgress);
          
          let pulseSize = map(propagationInfo.pulseProgress, 0, 1, 3, 8);
          let pulseAlpha = map(propagationInfo.pulseProgress, 0, 1, 255, 0);
          
          // 绘制脉冲光点
          noStroke();
          fill(255, 200, 100, pulseAlpha);
          ellipse(pulseX, pulseY, pulseSize);
          
          // 绘制脉冲光晕
          fill(255, 180, 80, pulseAlpha * 0.5);
          ellipse(pulseX, pulseY, pulseSize * 2);
        }
        
        // 绘制箭头（在连接线中间，表示传播方向）
        if (propagationInfo.pulseProgress > 0.5 && propagationInfo.pulseProgress < 1) {
          let arrowX = lerp(propagationInfo.source.x, propagationInfo.particle.x, 0.5);
          let arrowY = lerp(propagationInfo.source.y, propagationInfo.particle.y, 0.5);
          let angle = atan2(propagationInfo.particle.y - propagationInfo.source.y, 
                           propagationInfo.particle.x - propagationInfo.source.x);
          
          push();
          translate(arrowX, arrowY);
          rotate(angle);
          stroke(255, 200, 100, alpha * 0.8);
          strokeWeight(1.5);
          fill(255, 200, 100, alpha * 0.8);
          triangle(0, 0, -8, -4, -8, 4);
          pop();
        }
      }
    }
  }
  
  /**
   * 清理效果：移除所有粒子的效果
   */
  cleanup() {
    for (let propagationInfo of this.propagatedParticles) {
      this.removeEffectFromParticle(propagationInfo.particle);
    }
  }
  
  /**
   * 从粒子移除效果，恢复原始状态
   * @param {CandidateParticle} particle - 要移除效果的粒子
   */
  removeEffectFromParticle(particle) {
    if (!particle.hasEffect) return;
    
    // 移除效果标记
    particle.hasEffect = false;
    
    // 恢复颜色状态
    if (particle.isOrange) {
      particle.isOrange = false;
      particle.isRed = false;
    }
    
    // 恢复属性
    particle.changeSpeedMultiplier = 1;
    particle.vibrationOffsetX = 0;
    particle.vibrationOffsetY = 0;
    
    // 恢复原始计时器
    if (particle.originalChangeTimer !== undefined) {
      particle.changeTimer = particle.originalChangeTimer;
    }
  }
}

class FloatingText {
  constructor(x, y) {
    this.x = x;
    this.y = y;
    this.vx = random(-0.3, 0.3);
    this.vy = random(-0.3, 0.3);
    this.char = this.randomChar();
    this.alpha = random(10, 80);
    this.size = random(fontSize - 10, fontSize + 2);
    this.changeTimer = int(random(30, 80));
  }
  
  randomChar() {
    return chineseChars.charAt(floor(random(chineseChars.length)));
  }
  
  update() {
    this.x += this.vx;
    this.y += this.vy;
    
    if (this.x < textAreaX || this.x > textAreaX + textAreaWidth) {
      this.vx *= -1;
      this.x = constrain(this.x, textAreaX, textAreaX + textAreaWidth);
    }
    if (this.y < textAreaY || this.y > textAreaY + textAreaHeight) {
      this.vy *= -1;
      this.y = constrain(this.y, textAreaY, textAreaY + textAreaHeight);
    }
    
    this.changeTimer--;
    if (this.changeTimer <= 0) {
      this.char = this.randomChar();
      this.changeTimer = int(random(30, 80));
    }
  }
  
  display() {
    let displayAlpha = this.alpha;
    if (isCompleted && completionPhase !== 'none') {
      displayAlpha = this.alpha * (fadeOutAlpha / 255);
    }
    
    noStroke();
    fill(180, 200, 255, displayAlpha);
    textSize(this.size);
    text(this.char, this.x, this.y);
  }
}

class CandidateParticle {
  constructor(x, y) {
    this.x = x;
    this.y = y;
    this.vx = random(-0.5, 0.5);
    this.vy = random(-0.5, 0.5);
    this.char = this.randomChar();
    this.baseAlpha = 150;
    this.size = fontSize;
    this.prob = random(0.1, 1.0);
    this.isRed = false;
    this.isOrange = false;
    this.changeTimer = int(random(30, 80));
    this.phraseGroup = null;
    this.phraseIndex = 0;
    this.isTemporaryRed = false;
    this.tempRedTimer = 0;
    this.connections = [];
    this.isStatic = false;
    this.originalIsRed = false;
    this.hasEffect = false;
    this.changeSpeedMultiplier = 1;
    this.vibrationOffsetX = 0;
    this.vibrationOffsetY = 0;
    this.vibrationPhase = 0;
  }

  randomChar() {
    return chineseChars.charAt(floor(random(chineseChars.length)));
  }
  
  randomWord() {
    return chineseWords[floor(random(chineseWords.length))];
  }

  update() {
    this.x += this.vx;
    this.y += this.vy;

    if (this.x < textAreaX || this.x > textAreaX + textAreaWidth) {
      this.vx *= -1;
      this.x = constrain(this.x, textAreaX, textAreaX + textAreaWidth);
    }
    if (this.y < textAreaY || this.y > textAreaY + textAreaHeight) {
      this.vy *= -1;
      this.y = constrain(this.y, textAreaY, textAreaY + textAreaHeight);
    }

    this.alpha = this.baseAlpha + this.prob * 105;
    this.displaySize = this.size * (0.5 + this.prob);

    this.vx *= 0.95;
    this.vy *= 0.95;
    
    if (!this.isStatic && !this.isCalmed) {
      // 获取对应波形点的抚平程度（如果没有关联，使用整体平静进度）
      let localCalmProgress = this.calmProgress !== undefined ? this.calmProgress : calmProgress;
      
      // 如果抚平程度高，停止字符变化
      if (localCalmProgress < 0.7) {
        let changeSpeed = this.changeSpeedMultiplier || 1;
        // 根据抚平程度减慢变化速度
        changeSpeed *= (1 - localCalmProgress);
        this.changeTimer -= changeSpeed;
        
        if (this.changeTimer <= 0) {
          if (phraseMode && this.phraseGroup) {
            let word = this.randomWord();
            for (let i = 0; i < this.phraseGroup.length; i++) {
              if (!this.phraseGroup[i].isStatic && !this.phraseGroup[i].isCalmed) {
                let groupCalmProgress = this.phraseGroup[i].calmProgress !== undefined ? 
                                       this.phraseGroup[i].calmProgress : calmProgress;
                if (groupCalmProgress < 0.7) {
                  if (i < word.length) {
                    this.phraseGroup[i].char = word.charAt(i);
                  } else {
                    this.phraseGroup[i].char = this.phraseGroup[i].randomChar();
                  }
                  this.phraseGroup[i].changeTimer = int(random(30, 80));
                }
              }
            }
          } else {
            this.char = this.randomChar();
            this.changeTimer = int(random(30, 80));
          }
        }
      }
    }
    
    if (this.hasEffect) {
      if (this.changeSpeedMultiplier === undefined) {
        this.changeSpeedMultiplier = 1;
      }
      if (this.vibrationOffsetX === undefined) {
        this.vibrationOffsetX = 0;
        this.vibrationOffsetY = 0;
        this.vibrationPhase = random(TWO_PI);
      }
    }
    
    if (this.isTemporaryRed) {
      this.tempRedTimer--;
      if (this.tempRedTimer <= 0) {
        this.isTemporaryRed = false;
      }
    }
  }

  applyRippleAndRepel() {
    for (let other of candidateParticles) {
      if (other === this) continue;
      let d = dist(this.x, this.y, other.x, other.y);
      let minDist = (this.displaySize + other.displaySize) * 0.4;
      if (d < minDist && d > 0) {
        let angle = atan2(this.y - other.y, this.x - other.x);
        let force = map(d, 0, minDist, 1.5, 0);
        this.vx += cos(angle) * force * 0.1;
        this.vy += sin(angle) * force * 0.1;
      }
    }

    for (let ripple of rippleEffects) {
      let rd = dist(ripple.x, ripple.y, this.x, this.y);
      if (rd < ripple.r + 60) {
        let angle = atan2(ripple.y - this.y, ripple.x - this.x);
        let force = map(ripple.r + 60 - rd, 0, ripple.r + 60, 0, 0.3);
        this.vx += cos(angle) * force;
        this.vy += sin(angle) * force;
      }
    }

    for (let repel of repelBursts) {
      let d = dist(repel.x, repel.y, this.x, this.y);
      if (d < repel.r) {
        let angle = atan2(this.y - repel.y, this.x - repel.x);
        let force = map(repel.r - d, 0, repel.r, 0, 1.2);
        this.vx += cos(angle) * force;
        this.vy += sin(angle) * force;
      }
    }
  }

  display() {
    noStroke();
    
    // 计算显示位置：基础位置 + 效果振动 + 焦虑抖动 + 焦虑漂浮
    let displayX = this.x + (this.vibrationOffsetX || 0);
    let displayY = this.y + (this.vibrationOffsetY || 0);
    
    // 获取对应波形点的抚平程度（如果没有关联，使用整体平静进度）
    let localCalmProgress = this.calmProgress !== undefined ? this.calmProgress : calmProgress;
    let dissolveProgress = this.dissolveProgress !== undefined ? this.dissolveProgress : 0;
    
    // 如果是红色粒子且在完成阶段，应用抖动和漂浮效果（根据抚平程度减少）
    if (this.isRed && (completionPhase === 'revealing' || completionPhase === 'completed')) {
      if (this.anxietyShakeX !== undefined && this.anxietyShakeY !== undefined) {
        // 根据抚平程度减少抖动
        displayX += (this.anxietyShakeX || 0) * (1 - localCalmProgress);
        displayY += ((this.anxietyShakeY || 0) + (this.anxietyFloatY || 0)) * (1 - localCalmProgress);
      }
    }
    
    let displaySize = this.displaySize;
    if (this.hasEffect && this.effectScale !== undefined) {
      displaySize = this.displaySize * this.effectScale;
    }
    
    let glowBlur = 12 * this.prob;
    if (this.hasEffect && this.effectGlow !== undefined) {
      glowBlur = this.effectGlow;
    }
    drawingContext.shadowBlur = glowBlur;
    
    let displayAlpha = this.alpha;
    if (this.hasEffect && this.effectAlpha !== undefined) {
      displayAlpha = this.effectAlpha;
    }
    
    // 根据消散进度减少透明度
    displayAlpha *= (1 - dissolveProgress);
    
    // 根据平静进度调整红色粒子的颜色（逐渐变淡）
    if (this.isRed) {
      if (completionPhase === 'revealing' || completionPhase === 'completed') {
        // 在完成阶段，根据对应波形点的抚平程度调整颜色
        let redValue = map(localCalmProgress, 0, 1, 255, 200);
        let greenValue = map(localCalmProgress, 0, 1, 100, 150);
        let blueValue = map(localCalmProgress, 0, 1, 100, 150);
        drawingContext.shadowColor = `rgba(${redValue},${greenValue},${blueValue},${this.prob * (1 - localCalmProgress * 0.5) * (1 - dissolveProgress)})`;
        fill(redValue, greenValue, blueValue, displayAlpha);
      } else {
        // 正常游戏阶段，显示正常的红色
        drawingContext.shadowColor = `rgba(255,100,100,${this.prob})`;
        fill(255, 100, 100, displayAlpha);
      }
    } else if (this.isOrange) {
      if (isCompleted && completionPhase !== 'none') {
        displayAlpha = displayAlpha * (fadeOutAlpha / 255);
      }
      drawingContext.shadowColor = `rgba(255,150,50,${this.prob * (fadeOutAlpha / 255)})`;
      fill(255, 150, 50, displayAlpha);
    } else if (this.isTemporaryRed) {
      drawingContext.shadowColor = `rgba(255,150,150,${this.prob})`;
      fill(255, 150, 150, displayAlpha);
    } else {
      if (isCompleted && completionPhase !== 'none') {
        displayAlpha = displayAlpha * (fadeOutAlpha / 255);
      }
      drawingContext.shadowColor = `rgba(50,200,255,${this.prob * (fadeOutAlpha / 255)})`;
      fill(50, 200, 255, displayAlpha);
    }
    
    if (this.hasEffect && this.changeTimer !== undefined && this.changeTimer > 0 && this.changeTimer < 5) {
      let highlightAlpha = map(this.changeTimer, 0, 5, 255, displayAlpha);
      if (this.isOrange) {
        fill(255, 180, 80, highlightAlpha);
      } else if (this.isRed) {
        fill(255, 150, 150, highlightAlpha);
      } else {
        fill(50, 200, 255, highlightAlpha);
      }
    }
    
    textSize(displaySize);
    text(this.char, displayX, displayY);
    drawingContext.shadowBlur = 0;
  }
}

class Ripple {
  constructor(x, y) {
    this.x = x;
    this.y = y;
    this.r = 10;
    this.alpha = 200;
  }
  
  update() {
    this.r += 3;
    this.alpha -= 4;
  }
  
  display() {
    noFill();
    stroke(100, 200, 255, this.alpha);
    strokeWeight(2);
    ellipse(this.x, this.y, this.r * 2);
  }
}

class RepelBurst {
  constructor(x, y) {
    this.x = x;
    this.y = y;
    this.r = 10;
    this.alpha = 180;
  }
  
  update() {
    this.r += 6;
    this.alpha -= 5;
  }
  
  display() {
    noFill();
    stroke(255, 150, 100, this.alpha);
    strokeWeight(2);
    ellipse(this.x, this.y, this.r * 2);
  }
}
