童年的游戏机:用代码重现那些年的快乐时光

239 阅读23分钟

童年的游戏机:用代码重现那些年的快乐时光 🎰

还记得小时候在游戏厅里,那些闪闪发光的老虎机吗?一枚硬币,三个转轴,无数个梦想...今天,让我们用现代化的技术栈,重新构建这份童年的美好回忆。

前言:那些年的游戏厅记忆 🕹️

作为一名程序员,每当我看到那些经典的游戏机时,总会想起童年时光。那时的我们,会为了一次"777"的组合而兴奋不已,会为了连续几次不中奖而懊恼。如今,当我们有了技术能力时,为什么不亲手重现这些美好的回忆呢?

于是,我用现代化的Web技术栈,打造了这款完全可控的HTML5老虎机游戏。不仅还原了经典的游戏体验,还加入了一些程序员才能理解的"小彩蛋"。

技术架构:现代化的模块设计 🏗️

面向对象的架构设计

这个项目采用了完全的面向对象设计,将游戏拆分为多个独立的模块:

// 核心模块架构
class SlotMachine {
  constructor() {
    this.rng = new RNGEngine();              // 随机数引擎
    this.symbolManager = new SymbolManager(); // 符号管理器
    this.paylineManager = new PaylineManager(); // 支付线管理器
    this.gameState = new GameStateManager();   // 游戏状态管理
    this.displayController = new DisplayController(); // 显示控制器
    this.animationController = new AnimationController(); // 动画控制器
    this.audioManager = new AudioManager();   // 音效管理器
    this.adminController = new AdminController(); // 管理面板控制器(彩蛋)
  }
}

每个模块都有明确的职责:

  • RNGEngine: 实现线性同余生成器,确保随机性
  • SymbolManager: 管理符号权重和虚拟转轴
  • PaylineManager: 处理5条不同的支付线逻辑
  • GameStateManager: 维护游戏数据和RTP计算
  • AnimationController: 处理转轴动画和高亮效果

性能优化的动画系统

游戏的流畅度是用户体验的关键,我使用了多种技术来优化动画性能:

/* GPU加速优化 */
.spinning .reel-symbols {
  transition: none;
  animation: spin 0.15s linear infinite;
  will-change: transform;
  backface-visibility: hidden;
  contain: layout style;
}

/* 减少重排重绘 */
.symbol {
  transform: translateZ(0);
  backface-visibility: hidden;
}

通过will-changecontain等CSS属性,确保动画运行在合成层,避免影响主线程性能。

彩蛋设计:程序员的小心机(致敬Konami秘籍↑↑↓↓←→←→BA)🥚

隐藏的管理面板

这里是整个项目最有趣的部分——隐藏的管理面板!这是专门为程序员设计的彩蛋功能。

激活方式:连续点击游戏标题3次

// 秘密激活逻辑
let titleClickCount = 0;
document.querySelector('.header h1').addEventListener('click', function() {
  titleClickCount++;
  
  if (titleClickCount >= 3 && slotMachine) {
    slotMachine.enableAdminMode();
    // 显示控制面板激活提示
    showBackdoorActivation();
  }
});

激活后,你可以:

  • 🎯 调节中奖概率:从0%到100%任意控制
  • 🎰 设置目标RTP:调整游戏的回报率
  • 💎 大奖倍率调整:让大奖变得更加丰厚
  • 🎮 强制中奖/不中奖:完全控制游戏结果
  • 📊 查看详细统计:了解游戏的各项数据

RTP自适应补偿机制

作为一个负责任的"游戏开发者",我还实现了RTP(Return to Player)自适应补偿机制:

// RTP补偿检查
if (!this.adminController.isEnabled()) {
  const currentRTP = this.gameState.getCurrentRTP();
  if (currentRTP < this.targetRTP - 0.05) {
    const compensationChance = this.rng.getRange(0, 100);
    if (compensationChance < 5) {
      // 触发"幸运奖励"补偿机制
      this.needsRTPCompensation = true;
      return this.symbolManager.generateWinningMatrix(/*...*/);
    }
  }
}

当玩家运气不好时,系统会智能地提供"幸运奖励"来平衡体验。

Cursor:现代开发的得力助手 🤖

在开发这个项目的过程中,Cursor 发挥了巨大的作用:

1. 智能代码补全

当我在编写复杂的动画逻辑时,Cursor能够理解上下文,提供精准的代码建议:

// 我只需要输入前几个字符,Cursor就能补全整个动画函数
@keyframes winPulse {
  0%, 100% { transform: scale(1.03) translateZ(0); }
  50% { transform: scale(1.05) translateZ(0); }
}

2. 架构优化建议

Cursor帮我发现了许多性能瓶颈,比如:

  • 建议使用requestAnimationFrame优化动画
  • 提醒我添加will-change属性
  • 优化事件监听器的内存泄漏问题

3. 代码重构

当项目变得复杂时,Cursor协助我进行了大规模的重构,将原本的过程式代码改造为面向对象的架构。

4. 文档生成

Cursor甚至帮我生成了详细的代码注释和文档,让项目更易维护。

技术亮点总结 ✨

1. 模块化设计

  • 8个独立的类,各司其职
  • 依赖注入,便于测试和维护
  • 清晰的接口设计

2. 性能优化

  • GPU加速的CSS动画
  • 事件委托减少内存占用
  • 防抖和节流优化用户交互

3. 用户体验

  • 响应式设计,支持各种设备
  • 流畅的动画过渡
  • 音效反馈增强沉浸感

4. 可扩展性

  • 易于添加新的符号和支付线
  • 支持自定义RTP策略
  • 模块化的音效系统

效果展示 🎉

2025-06-2715.12.02-ezgif.com-video-to-gif-converter.gif

源码展示 📝

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>老虎机 - 随机数引擎驱动</title>
    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }

      body {
        font-family: "Arial", sans-serif;
        background: linear-gradient(135deg, #1e3c72, #2a5298);
        color: white;
        overflow-x: hidden;
        min-height: 100vh;
        margin: 0;
        padding: 0;
      }

      /* 隐藏滚动条但保持滚动功能 */
      html {
        scrollbar-width: none; /* Firefox */
        -ms-overflow-style: none; /* IE和Edge */
      }

      /* 针对Webkit浏览器(Chrome, Safari)隐藏滚动条 */
      ::-webkit-scrollbar {
        display: none;
      }

      body::-webkit-scrollbar {
        display: none;
      }

      .container {
        max-width: 1200px;
        margin: 0 auto;
        padding: 10px;
        min-height: 100vh;
        display: flex;
        flex-direction: column;
      }

      .header {
        text-align: center;
        margin-bottom: 10px;
      }

      .header h1 {
        font-size: 1.8rem;
        color: #ffd700;
        text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
        margin-bottom: 5px;
        cursor: pointer;
        transition: transform 0.2s ease;
      }

      .header h1:hover {
        transform: scale(1.05);
      }

      .header p {
        color: #e0e0e0;
        font-size: 0.9rem;
      }

      .slot-machine {
        background: linear-gradient(145deg, #2c3e50, #34495e);
        border-radius: 15px;
        padding: 20px;
        box-shadow: 0 15px 30px rgba(0, 0, 0, 0.3);
        margin-bottom: 15px;
        flex: 1;
      }

      .reels-container {
        display: flex;
        justify-content: center;
        gap: 10px;
        margin-bottom: 20px;
        background: #1a1a1a;
        padding: 15px;
        border-radius: 12px;
        border: 2px solid #ffd700;
      }

      .reel {
        width: 100px;
        height: 300px;
        background: #fff;
        border-radius: 8px;
        border: 2px solid #333;
        overflow: hidden;
        position: relative;
        box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.2);
        transform: translateZ(0);
        will-change: transform;
      }

      .reel-symbols {
        position: absolute;
        width: 100%;
        transition: transform 0.1s ease-out;
        will-change: transform;
        transform: translateZ(0);
      }

      .symbol {
        width: 100%;
        height: 100px;
        display: flex;
        align-items: center;
        justify-content: center;
        font-size: 3rem;
        border-bottom: 1px solid #ddd;
        background: linear-gradient(145deg, #f8f9fa, #e9ecef);
        will-change: contents;
        backface-visibility: hidden;
        transition: all 0.3s ease-in-out;
      }

      .payline {
        position: absolute;
        top: 100px;
        left: 0;
        right: 0;
        height: 100px;
        pointer-events: none;
        z-index: 10;
        display: none;
      }

      .controls {
        display: flex;
        justify-content: space-between;
        align-items: center;
        flex-wrap: wrap;
        gap: 10px;
        margin-bottom: 15px;
      }

      .bet-controls {
        display: flex;
        gap: 8px;
        align-items: center;
      }

      .control-group {
        background: rgba(255, 255, 255, 0.1);
        padding: 10px;
        border-radius: 8px;
        backdrop-filter: blur(10px);
      }

      .control-group label {
        display: block;
        margin-bottom: 3px;
        font-weight: bold;
        color: #ffd700;
        font-size: 0.85rem;
      }

      .control-group input,
      .control-group select {
        padding: 6px 10px;
        border: none;
        border-radius: 4px;
        background: rgba(255, 255, 255, 0.9);
        color: #333;
        font-size: 0.9rem;
        width: 90px;
      }

      .btn {
        padding: 10px 20px;
        border: none;
        border-radius: 8px;
        font-size: 1rem;
        font-weight: bold;
        cursor: pointer;
        transition: all 0.3s ease;
        text-transform: uppercase;
        letter-spacing: 1px;
      }

      .btn-spin {
        background: linear-gradient(145deg, #ff6b6b, #ff5252);
        color: white;
        box-shadow: 0 5px 15px rgba(255, 107, 107, 0.4);
      }

      .btn-spin:hover:not(:disabled) {
        transform: translateY(-2px);
        box-shadow: 0 8px 20px rgba(255, 107, 107, 0.6);
      }

      .btn-spin:disabled {
        background: #666;
        cursor: not-allowed;
        opacity: 0.6;
      }

      .btn-secondary {
        background: linear-gradient(145deg, #6c757d, #5a6268);
        color: white;
      }

      .btn-secondary:hover {
        transform: translateY(-2px);
      }

      .info-panel {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
        gap: 10px;
        margin-bottom: 15px;
      }

      .info-card {
        background: rgba(255, 255, 255, 0.1);
        padding: 12px;
        border-radius: 8px;
        backdrop-filter: blur(10px);
        border: 1px solid rgba(255, 255, 255, 0.2);
      }

      .info-card h3 {
        color: #ffd700;
        margin-bottom: 5px;
        font-size: 0.9rem;
      }

      .info-card .value {
        font-size: 1.2rem;
        font-weight: bold;
      }

      .rng-status {
        background: rgba(0, 255, 0, 0.1);
        border-left: 3px solid #00ff00;
        padding: 10px;
        margin-bottom: 10px;
        border-radius: 4px;
        font-size: 0.9rem;
      }

      .rng-status.active {
        animation: pulse 3s ease-in-out infinite;
        will-change: opacity;
        transform: translateZ(0);
      }

      @keyframes pulse {
        0%,
        100% {
          opacity: 1;
        }
        50% {
          opacity: 0.8;
        }
      }

      .paytable {
        background: rgba(255, 255, 255, 0.1);
        padding: 15px;
        border-radius: 8px;
        margin-top: 10px;
      }

      .paytable h3 {
        color: #ffd700;
        margin-bottom: 10px;
        text-align: center;
        font-size: 1rem;
      }

      .paytable-grid {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
        gap: 8px;
      }

      .paytable-item {
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: 6px 10px;
        background: rgba(255, 255, 255, 0.1);
        border-radius: 4px;
        font-size: 0.85rem;
      }

      .spinning .reel-symbols {
        transition: none;
        animation: spin 0.15s linear infinite;
        will-change: transform;
        backface-visibility: hidden;
      }

      @keyframes spin {
        0% {
          transform: translate3d(0, 0, 0);
        }
        100% {
          transform: translate3d(0, -100px, 0);
        }
      }

      /* 优化动画性能 */
      .spinning {
        contain: layout style;
      }

      /* 中奖弹窗性能优化 */
      .win-modal,
      .win-modal-content,
      .fireworks,
      .firework {
        contain: layout style paint;
      }

      .win-modal-content * {
        transform: translateZ(0);
      }

      /* 全局性能优化 */
      * {
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
      }

      /* 优化GPU加速 */
      .rng-status,
      .win-highlight,
      .admin-panel h3,
      .backdoor-warning {
        -webkit-transform: translateZ(0);
        -webkit-backface-visibility: hidden;
        -webkit-perspective: 1000;
      }

      /* 减少动画复杂度 */
      @media (prefers-reduced-motion: reduce) {
        * {
          animation-duration: 0.01ms !important;
          animation-iteration-count: 1 !important;
          transition-duration: 0.01ms !important;
        }

        .win-modal-header,
        .win-amount,
        .firework,
        .win-highlight {
          animation: none !important;
        }

        .win-modal.show .win-modal-content {
          animation: none !important;
        }
      }

      /* 低性能设备优化 */
      @media (max-width: 768px) {
        .firework {
          display: none;
        }

        .win-highlight {
          animation-duration: 3s;
        }
      }

      @media (max-height: 800px) {
        .spinning .reel-symbols {
          animation: spin 0.2s linear infinite;
        }

        @keyframes spin {
          0% {
            transform: translateY(0);
          }
          100% {
            transform: translateY(-83px);
          }
        }
      }

      .win-highlight {
        background: linear-gradient(45deg, #ffd700, #ffed4e, #ffd700);
        color: #d32f2f;
        border: 3px solid #ff4444;
        border-radius: 12px;
        box-shadow: 0 0 20px rgba(255, 215, 0, 0.8);
        animation: winPulse 2s ease-in-out infinite;
        transform: scale(1.03) translateZ(0);
        will-change: transform, opacity;
        font-weight: bold;
        text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3);
        position: relative;
        backface-visibility: hidden;
      }

      .win-highlight::after {
        content: "🎉";
        position: absolute;
        top: 5px;
        right: 8px;
        font-size: 1.2rem;
        animation: celebration 2s ease-in-out infinite alternate;
        z-index: 2;
        will-change: transform;
        transform: translateZ(0);
      }

      @keyframes winPulse {
        0%,
        100% {
          transform: scale(1.03) translateZ(0);
          opacity: 1;
        }
        50% {
          transform: scale(1.05) translateZ(0);
          opacity: 0.9;
        }
      }

      @keyframes celebration {
        0% {
          transform: rotate(-5deg) scale(1) translateZ(0);
        }
        100% {
          transform: rotate(5deg) scale(1.1) translateZ(0);
        }
      }

      .message {
        text-align: center;
        padding: 10px;
        margin: 8px 0;
        border-radius: 8px;
        font-weight: bold;
        font-size: 1rem;
      }

      .message.win {
        background: linear-gradient(145deg, #28a745, #20c997);
        color: white;
        animation: celebrate 1s ease-in-out;
      }

      .message.lose {
        background: rgba(255, 255, 255, 0.1);
        color: #ffd700;
      }

      @keyframes celebrate {
        0%,
        100% {
          transform: scale(1);
        }
        50% {
          transform: scale(1.05);
        }
      }

      /* 中奖弹窗样式 */
      .win-modal {
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background: rgba(0, 0, 0, 0.8);
        display: flex;
        justify-content: center;
        align-items: center;
        z-index: 1000;
        opacity: 0;
        visibility: hidden;
        transition: opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1),
          visibility 0.3s cubic-bezier(0.4, 0, 0.2, 1);
        will-change: opacity, visibility;
        transform: translateZ(0);
      }

      .win-modal.show {
        opacity: 1;
        visibility: visible;
      }

      .win-modal-content {
        background: linear-gradient(145deg, #ffd700, #ffed4e);
        color: #333;
        padding: 40px;
        border-radius: 20px;
        text-align: center;
        box-shadow: 0 20px 60px rgba(255, 215, 0, 0.4);
        transform: scale(0.7) translateZ(0);
        transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
        position: relative;
        overflow: hidden;
        max-width: 400px;
        width: 90%;
        will-change: transform;
        backface-visibility: hidden;
      }

      .win-modal.show .win-modal-content {
        transform: scale(1) translateZ(0);
        animation: modalPulse 4s ease-in-out infinite;
      }

      @keyframes modalPulse {
        0%,
        100% {
          transform: scale(1) translateZ(0);
          opacity: 1;
        }
        50% {
          transform: scale(1.01) translateZ(0);
          opacity: 0.95;
        }
      }

      .win-modal-header {
        font-size: 2.5rem;
        font-weight: bold;
        margin-bottom: 20px;
        text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
        animation: bounce 3s ease-in-out infinite alternate;
        will-change: transform;
        transform: translateZ(0);
        backface-visibility: hidden;
      }

      @keyframes bounce {
        0% {
          transform: translate3d(0, 0, 0);
        }
        100% {
          transform: translate3d(0, -4px, 0);
        }
      }

      .win-amount {
        font-size: 3rem;
        font-weight: bold;
        color: #ff6b6b;
        margin: 20px 0;
        text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
        animation: glow 4s ease-in-out infinite alternate;
        will-change: opacity, transform;
        transform: translateZ(0);
        backface-visibility: hidden;
      }

      @keyframes glow {
        0% {
          opacity: 1;
          transform: scale(1) translateZ(0);
        }
        100% {
          opacity: 0.9;
          transform: scale(1.02) translateZ(0);
        }
      }

      .win-combination {
        font-size: 1.5rem;
        margin: 15px 0;
        background: rgba(255, 255, 255, 0.3);
        padding: 10px 20px;
        border-radius: 10px;
        display: inline-block;
      }

      .win-modal-close {
        position: absolute;
        top: 15px;
        right: 20px;
        background: none;
        border: none;
        font-size: 2rem;
        color: #666;
        cursor: pointer;
        transition: color 0.3s ease;
      }

      .win-modal-close:hover {
        color: #333;
      }

      .win-continue-btn {
        background: linear-gradient(145deg, #28a745, #20c997);
        color: white;
        border: none;
        padding: 15px 30px;
        border-radius: 10px;
        font-size: 1.2rem;
        font-weight: bold;
        cursor: pointer;
        margin-top: 20px;
        transition: all 0.3s ease;
        text-transform: uppercase;
        letter-spacing: 1px;
      }

      .win-continue-btn:hover {
        transform: translateY(-2px);
        box-shadow: 0 5px 15px rgba(40, 167, 69, 0.4);
      }

      /* 烟花效果 */
      .fireworks {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        pointer-events: none;
        overflow: hidden;
        will-change: transform;
        transform: translateZ(0);
      }

      .firework {
        position: absolute;
        width: 4px;
        height: 4px;
        background: #fff;
        border-radius: 50%;
        animation: fireworkAnim 2s ease-out infinite;
        will-change: transform, opacity;
        transform: translateZ(0);
        backface-visibility: hidden;
      }

      @keyframes fireworkAnim {
        0% {
          transform: scale3d(1, 1, 1);
          opacity: 1;
        }
        50% {
          opacity: 0.6;
        }
        100% {
          transform: scale3d(8, 8, 1);
          opacity: 0;
        }
      }

      .firework:nth-child(1) {
        top: 20%;
        left: 20%;
        background: #ff6b6b;
        animation-delay: 0s;
      }

      .firework:nth-child(2) {
        top: 30%;
        right: 20%;
        background: #4ecdc4;
        animation-delay: 0.5s;
      }

      .firework:nth-child(3) {
        bottom: 30%;
        left: 30%;
        background: #45b7d1;
        animation-delay: 1s;
      }

      .firework:nth-child(4) {
        bottom: 20%;
        right: 30%;
        background: #f9ca24;
        animation-delay: 1.5s;
      }

      .firework:nth-child(5) {
        top: 50%;
        left: 50%;
        background: #6c5ce7;
        animation-delay: 2s;
      }

      .stats {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
        gap: 8px;
        margin-top: 10px;
      }

      .stat-item {
        text-align: center;
        padding: 10px;
        background: rgba(255, 255, 255, 0.1);
        border-radius: 6px;
      }

      .stat-item .label {
        color: #ffd700;
        font-size: 0.75rem;
        margin-bottom: 3px;
      }

      .stat-item .value {
        font-size: 1.1rem;
        font-weight: bold;
      }

      /* 隐藏的管理面板样式 */
      .admin-panel {
        position: fixed;
        top: 10px;
        right: -350px;
        width: 320px;
        background: linear-gradient(145deg, #2c3e50, #34495e);
        border-radius: 10px;
        padding: 20px;
        box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
        transition: right 0.3s ease;
        z-index: 2000;
        color: white;
        border: 2px solid #ff6b6b;
      }

      .admin-panel.show {
        right: 10px;
      }

      .admin-panel h3 {
        color: #ff6b6b;
        margin-bottom: 10px;
        text-align: center;
        font-size: 1.2rem;
        animation: backdoorAlert 3s ease-in-out infinite;
        will-change: color;
        transform: translateZ(0);
      }

      .backdoor-warning {
        background: linear-gradient(45deg, #ff0000, #ff4444);
        color: #ffffff;
        padding: 8px;
        border-radius: 5px;
        text-align: center;
        margin-bottom: 15px;
        font-size: 0.85rem;
        animation: backdoorBlink 4s ease-in-out infinite;
        will-change: opacity, transform;
        transform: translateZ(0);
      }

      @keyframes backdoorAlert {
        0%,
        100% {
          color: #ff6b6b;
        }
        50% {
          color: #ffff00;
        }
      }

      @keyframes backdoorBlink {
        0%,
        100% {
          opacity: 1;
          transform: scale(1) translateZ(0);
        }
        50% {
          opacity: 0.9;
          transform: scale(1.01) translateZ(0);
        }
      }

      .admin-control {
        margin-bottom: 15px;
      }

      .admin-control label {
        display: block;
        color: #ffd700;
        margin-bottom: 5px;
        font-size: 0.9rem;
        font-weight: bold;
      }

      .admin-control input[type="range"] {
        width: 100%;
        margin-bottom: 5px;
      }

      .admin-control .value-display {
        text-align: center;
        background: rgba(255, 255, 255, 0.1);
        padding: 3px 8px;
        border-radius: 4px;
        font-size: 0.85rem;
      }

      .admin-control input[type="number"] {
        width: 100%;
        padding: 5px;
        border: none;
        border-radius: 4px;
        background: rgba(255, 255, 255, 0.9);
        color: #333;
      }

      .admin-buttons {
        display: flex;
        gap: 8px;
        flex-wrap: wrap;
      }

      .admin-btn {
        flex: 1;
        padding: 8px 12px;
        border: none;
        border-radius: 6px;
        font-size: 0.8rem;
        font-weight: bold;
        cursor: pointer;
        transition: all 0.3s ease;
        min-width: 70px;
      }

      .admin-btn.danger {
        background: linear-gradient(145deg, #ff6b6b, #ff5252);
        color: white;
      }

      .admin-btn.success {
        background: linear-gradient(145deg, #28a745, #20c997);
        color: white;
      }

      .admin-btn.info {
        background: linear-gradient(145deg, #17a2b8, #138496);
        color: white;
      }

      .admin-btn:hover {
        transform: translateY(-1px);
        box-shadow: 0 3px 8px rgba(0, 0, 0, 0.3);
      }

      .admin-toggle {
        position: fixed;
        bottom: 20px;
        right: 20px;
        width: 50px;
        height: 50px;
        background: linear-gradient(145deg, #ff6b6b, #ff5252);
        border: none;
        border-radius: 50%;
        color: white;
        font-size: 1.5rem;
        cursor: pointer;
        box-shadow: 0 5px 15px rgba(255, 107, 107, 0.4);
        z-index: 1999;
        opacity: 0;
        transition: opacity 0.3s ease;
      }

      .admin-toggle.show {
        opacity: 1;
      }

      .admin-toggle:hover {
        transform: scale(1.1);
      }

      @keyframes backdoorActivated {
        0% {
          opacity: 0;
          transform: translate(-50%, -50%) scale(0.5);
        }
        20% {
          opacity: 1;
          transform: translate(-50%, -50%) scale(1.1);
        }
        100% {
          opacity: 1;
          transform: translate(-50%, -50%) scale(1);
        }
      }

      /* 响应式设计 */
      @media (max-height: 800px) {
        .container {
          padding: 5px;
        }

        .header h1 {
          font-size: 1.5rem;
        }

        .header p {
          font-size: 0.8rem;
        }

        .reel {
          height: 250px;
        }

        .symbol {
          height: 83px;
          font-size: 2.5rem;
        }

        .payline {
          top: 83px;
          height: 83px;
        }

        .paytable {
          margin-top: 5px;
        }

        .stats {
          margin-top: 5px;
        }
      }

      @media (max-width: 768px) {
        .controls {
          flex-direction: column;
          gap: 8px;
        }

        .bet-controls {
          justify-content: center;
        }

        .info-panel {
          grid-template-columns: repeat(2, 1fr);
        }

        .paytable-grid {
          grid-template-columns: 1fr;
        }

        .stats {
          grid-template-columns: repeat(3, 1fr);
        }

        .admin-panel {
          width: 95%;
          right: -95%;
          top: 5px;
        }

        .admin-panel.show {
          right: 2.5%;
        }
      }
    </style>
  </head>
  <body>
    <div class="container">
      <div class="header">
        <h1>🎰 老虎机游戏</h1>
        <p>休闲娱乐 - 随机数引擎演示</p>
      </div>

      <div class="rng-status active" id="rngStatus">
        🔄 RNG引擎运行中... 当前随机数: <span id="currentRNG">0</span> |
        生成速度: <span id="rngSpeed">0</span>/秒
      </div>

      <div class="slot-machine">
        <div class="reels-container">
          <div class="payline"></div>
          <div class="reel" id="reel1">
            <div class="reel-symbols" id="symbols1"></div>
          </div>
          <div class="reel" id="reel2">
            <div class="reel-symbols" id="symbols2"></div>
          </div>
          <div class="reel" id="reel3">
            <div class="reel-symbols" id="symbols3"></div>
          </div>
        </div>

        <div class="controls">
          <div class="bet-controls">
            <div class="control-group">
              <label>投注金额</label>
              <select id="betAmount">
                <option value="1">1 元</option>
                <option value="5">5 元</option>
                <option value="10" selected>10 元</option>
                <option value="25">25 元</option>
                <option value="50">50 元</option>
                <option value="100">100 元</option>
              </select>
            </div>
            <div class="control-group">
              <label>支付线数</label>
              <select id="paylines">
                <option value="1" selected>1 线</option>
                <option value="3">3 线</option>
                <option value="5">5 线</option>
              </select>
            </div>
          </div>

          <button class="btn btn-spin" id="spinBtn" onclick="spin()">
            🎰 旋转
          </button>

          <div class="bet-controls">
            <button class="btn btn-secondary" onclick="maxBet()">
              最大投注
            </button>
            <button class="btn btn-secondary" onclick="autoSpin()">
              自动旋转
            </button>
          </div>
        </div>

        <div class="info-panel">
          <div class="info-card">
            <h3>余额</h3>
            <div class="value" id="balance">1000 元</div>
          </div>
          <div class="info-card">
            <h3>总投注</h3>
            <div class="value" id="totalBet">0 元</div>
          </div>
          <div class="info-card">
            <h3>总赢取</h3>
            <div class="value" id="totalWin">0 元</div>
          </div>
          <div class="info-card">
            <h3>当前RTP</h3>
            <div class="value" id="currentRTP">95.0%</div>
          </div>
        </div>

        <div id="messageArea"></div>
      </div>

      <div class="paytable">
        <h3>💰 支付表 (每条中奖支付线的奖金)</h3>
        <div class="paytable-grid">
          <div class="paytable-item">
            <span>🍒🍒🍒</span>
            <span>100x</span>
          </div>
          <div class="paytable-item">
            <span>🍋🍋🍋</span>
            <span>50x</span>
          </div>
          <div class="paytable-item">
            <span>🍊🍊🍊</span>
            <span>25x</span>
          </div>
          <div class="paytable-item">
            <span>🍇🍇🍇</span>
            <span>15x</span>
          </div>
          <div class="paytable-item">
            <span>⭐⭐⭐</span>
            <span>10x</span>
          </div>
          <div class="paytable-item">
            <span>💎💎💎</span>
            <span>500x (JACKPOT)</span>
          </div>
          <div class="paytable-item">
            <span>🍒🍒任意</span>
            <span>2x ⭐</span>
          </div>
          <div class="paytable-item">
            <span>任意7符号</span>
            <span>1.5x</span>
          </div>
        </div>

        <div
          style="
            margin-top: 15px;
            padding: 10px;
            background: rgba(255, 255, 255, 0.1);
            border-radius: 8px;
            font-size: 0.85rem;
          "
        >
          <h4 style="color: #ffd700; margin-bottom: 8px">📍 支付线说明:</h4>
          <div
            style="
              display: grid;
              grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
              gap: 5px;
            "
          >
            <div>线1: 中-中-中</div>
            <div>线2: 上-上-上</div>
            <div>线3: 下-下-下</div>
            <div>线4: 上-中-下</div>
            <div>线5: 下-中-上</div>
          </div>
          <div style="margin-top: 8px; color: #e0e0e0">
            💡 只有激活的支付线上出现中奖组合才会获得奖金
          </div>
        </div>
      </div>

      <div class="stats">
        <div class="stat-item">
          <div class="label">游戏轮数</div>
          <div class="value" id="gameRounds">0</div>
        </div>
        <div class="stat-item">
          <div class="label">胜率</div>
          <div class="value" id="winRate">0%</div>
        </div>
        <div class="stat-item">
          <div class="label">最大连胜</div>
          <div class="value" id="maxWinStreak">0</div>
        </div>
        <div class="stat-item">
          <div class="label">当前连胜</div>
          <div class="value" id="currentWinStreak">0</div>
        </div>
        <div class="stat-item">
          <div class="label">最大单次赢取</div>
          <div class="value" id="maxSingleWin">0 元</div>
        </div>
        <div class="stat-item">
          <div class="label">RNG生成总数</div>
          <div class="value" id="rngCount">0</div>
        </div>
      </div>
    </div>

    <!-- 隐藏的管理面板 -->
    <div class="admin-panel" id="adminPanel">
      <h3>⚙️ 游戏控制面板</h3>
      <div class="backdoor-warning">
        <strong>🎮 游戏参数调节 - 自定义游戏体验</strong>
      </div>

      <div class="admin-control">
        <label>获奖概率控制</label>
        <input
          type="range"
          id="winChanceSlider"
          min="0"
          max="100"
          value="25"
          oninput="updateWinChance(this.value)"
        />
        <div class="value-display" id="winChanceDisplay">25%</div>
      </div>

      <div class="admin-control">
        <label>目标RTP设置</label>
        <input
          type="range"
          id="rtpSlider"
          min="50"
          max="150"
          value="95"
          oninput="updateRTP(this.value)"
        />
        <div class="value-display" id="rtpDisplay">95.0%</div>
      </div>

      <div class="admin-control">
        <label>大奖倍率调整</label>
        <input
          type="range"
          id="jackpotMultiplier"
          min="1"
          max="10"
          value="1"
          step="0.1"
          oninput="updateJackpotMultiplier(this.value)"
        />
        <div class="value-display" id="jackpotDisplay">1.0x</div>
      </div>

      <div class="admin-control">
        <label>余额设置</label>
        <input
          type="number"
          id="balanceInput"
          value="1000"
          onchange="setBalance(this.value)"
        />
      </div>

      <div class="admin-buttons">
        <button class="admin-btn success" onclick="forceWin()">强制中奖</button>
        <button class="admin-btn danger" onclick="forceLose()">强制不中</button>
        <button class="admin-btn info" onclick="resetStats()">重置统计</button>
        <button class="admin-btn info" onclick="toggleAutoWin()">
          自动中奖
        </button>
      </div>

      <div
        style="
          margin-top: 15px;
          padding: 10px;
          background: rgba(50, 150, 200, 0.2);
          border: 2px solid #3296c8;
          border-radius: 5px;
          font-size: 0.8rem;
        "
      >
        <div style="color: #87ceeb; font-weight: bold">🎮 控制面板说明:</div>
        <div style="margin: 5px 0">激活方式: 连续点击标题3次</div>
        <div style="margin: 5px 0">
          当前模式: <span id="adminMode">正常</span>
        </div>
        <div
          style="
            margin-top: 8px;
            color: #b3d9ff;
            background: rgba(50, 150, 200, 0.3);
            padding: 5px;
            border-radius: 3px;
          "
        >
          🎯 可调节游戏难度和中奖概率<br />
          ⚙️ 自定义游戏参数和倍率<br />
          📊 查看详细的游戏统计<br />
          🎲 体验不同的游戏模式!
        </div>
      </div>
    </div>

    <!-- 管理面板切换按钮 -->
    <button class="admin-toggle" id="adminToggle" onclick="toggleAdminPanel()">
      ⚙️
    </button>

    <!-- 中奖弹窗 -->
    <div class="win-modal" id="winModal">
      <div class="win-modal-content">
        <button class="win-modal-close" onclick="closeWinModal()">
          &times;
        </button>
        <div class="fireworks">
          <div class="firework"></div>
          <div class="firework"></div>
          <div class="firework"></div>
          <div class="firework"></div>
          <div class="firework"></div>
        </div>
        <div class="win-modal-header">🎉 恭喜中奖!🎉</div>
        <div class="win-combination" id="winCombination">🍒🍒🍒</div>
        <div class="win-amount" id="winAmount">100 元</div>
        <div style="font-size: 1.1rem; margin: 10px 0; color: #555">
          您的幸运时刻到了!
        </div>
        <button class="win-continue-btn" onclick="closeWinModal()">
          继续游戏
        </button>
      </div>
    </div>

    <script>
      // 符号管理器 - 负责符号的生成和权重分配
      class SymbolManager {
        constructor() {
          this.virtualReels = [
            // 转轴1 - 权重分布
            {
              symbols: ["🍒", "🍋", "🍊", "🍇", "⭐", "💎", "7️⃣"],
              weights: [30, 25, 20, 15, 8, 1, 1],
            },
            // 转轴2 - 权重分布
            {
              symbols: ["🍒", "🍋", "🍊", "🍇", "⭐", "💎", "7️⃣"],
              weights: [28, 26, 21, 16, 7, 1, 1],
            },
            // 转轴3 - 权重分布
            {
              symbols: ["🍒", "🍋", "🍊", "🍇", "⭐", "💎", "7️⃣"],
              weights: [25, 27, 22, 17, 7, 1, 1],
            },
          ];
        }

        // 根据权重获取随机符号
        getRandomSymbol(reel, rng) {
          if (!reel.cumulativeWeights) {
            reel.cumulativeWeights = [];
            let accumulated = 0;
            for (let i = 0; i < reel.weights.length; i++) {
              accumulated += reel.weights[i];
              reel.cumulativeWeights[i] = accumulated;
            }
          }

          const random = rng.getRange(0, 99);
          let left = 0;
          let right = reel.cumulativeWeights.length - 1;

          while (left < right) {
            const mid = Math.floor((left + right) / 2);
            if (random < reel.cumulativeWeights[mid]) {
              right = mid;
            } else {
              left = mid + 1;
            }
          }

          return reel.symbols[left] || reel.symbols[0];
        }

        // 生成3x3符号矩阵
        generateSymbolMatrix(rngValues) {
          const results = [];

          for (let i = 0; i < 3; i++) {
            const reel = this.virtualReels[i];
            const reelSymbols = [];

            for (let j = 0; j < 3; j++) {
              const normalizedRNG = (rngValues[i] + j * 17) % 100;

              let accumulated = 0;
              for (let k = 0; k < reel.symbols.length; k++) {
                accumulated += reel.weights[k];
                if (normalizedRNG < accumulated) {
                  reelSymbols.push(reel.symbols[k]);
                  break;
                }
              }
            }
            results.push(reelSymbols);
          }

          return results;
        }

        // 生成中奖符号组合
        generateWinningMatrix(
          rng,
          activatedPaylines,
          paylines,
          isRTPCompensation = false
        ) {
          let winningCombinations;

          if (isRTPCompensation) {
            // RTP补偿时使用较低倍率的组合,避免过度补偿
            winningCombinations = [
              { symbols: ["🍇", "🍇", "🍇"], weight: 40, payout: 15 },
              { symbols: ["⭐", "⭐", "⭐"], weight: 35, payout: 10 },
              { symbols: ["🍊", "🍊", "🍊"], weight: 20, payout: 25 },
              { symbols: ["🍒", "🍒"], weight: 5, payout: 2 }, // 两个樱桃的组合
            ];
          } else {
            // 正常的中奖组合
            winningCombinations = [
              { symbols: ["🍒", "🍒", "🍒"], weight: 15, payout: 100 },
              { symbols: ["🍋", "🍋", "🍋"], weight: 20, payout: 50 },
              { symbols: ["🍊", "🍊", "🍊"], weight: 25, payout: 25 },
              { symbols: ["🍇", "🍇", "🍇"], weight: 30, payout: 15 },
              { symbols: ["⭐", "⭐", "⭐"], weight: 8, payout: 10 },
              { symbols: ["💎", "💎", "💎"], weight: 1, payout: 500 },
              { symbols: ["7️⃣", "7️⃣", "7️⃣"], weight: 1, payout: 200 },
            ];
          }

          const totalWeight = winningCombinations.reduce(
            (sum, combo) => sum + combo.weight,
            0
          );
          const randomWeight = rng.getRange(0, totalWeight - 1);

          let currentWeight = 0;
          let selectedCombo = winningCombinations[0];

          for (const combo of winningCombinations) {
            currentWeight += combo.weight;
            if (randomWeight < currentWeight) {
              selectedCombo = combo;
              break;
            }
          }

          const winningLineIndex = rng.getRange(0, activatedPaylines - 1);
          const winningPayline = paylines[winningLineIndex];

          const results = [];

          for (let reelIndex = 0; reelIndex < 3; reelIndex++) {
            const reelSymbols = [];

            for (let rowIndex = 0; rowIndex < 3; rowIndex++) {
              if (rowIndex === winningPayline[reelIndex]) {
                if (selectedCombo.symbols.length === 2 && reelIndex === 2) {
                  // 两个樱桃的情况,第三个位置放随机符号
                  reelSymbols.push(
                    this.getRandomSymbol(this.virtualReels[reelIndex], rng)
                  );
                } else if (
                  selectedCombo.symbols.length === 2 &&
                  reelIndex < 2
                ) {
                  // 前两个位置放樱桃
                  reelSymbols.push(selectedCombo.symbols[reelIndex]);
                } else if (selectedCombo.symbols.length === 3) {
                  // 三个相同符号
                  reelSymbols.push(selectedCombo.symbols[reelIndex]);
                } else {
                  reelSymbols.push(
                    this.getRandomSymbol(this.virtualReels[reelIndex], rng)
                  );
                }
              } else {
                reelSymbols.push(
                  this.getRandomSymbol(this.virtualReels[reelIndex], rng)
                );
              }
            }

            results.push(reelSymbols);
          }

          return results;
        }
      }

      // 支付线管理器 - 负责支付线的定义和管理
      class PaylineManager {
        constructor() {
          // 支付线定义 - 每条线指定3个位置[转轴0行位置, 转轴1行位置, 转轴2行位置]
          this.paylines = [
            [1, 1, 1], // 线1: 中-中-中
            [0, 0, 0], // 线2: 上-上-上
            [2, 2, 2], // 线3: 下-下-下
            [0, 1, 2], // 线4: 上-中-下
            [2, 1, 0], // 线5: 下-中-上
          ];
        }

        getPayline(index) {
          return this.paylines[index];
        }

        getPaylineCount() {
          return this.paylines.length;
        }

        // 获取支付线上的符号组合
        getLineSymbols(results, paylineIndex) {
          const payline = this.paylines[paylineIndex];
          return [
            results[0][payline[0]],
            results[1][payline[1]],
            results[2][payline[2]],
          ];
        }
      }

      // 支付表管理器 - 负责奖金计算规则
      class PaytableManager {
        constructor() {
          this.paytable = {
            "🍒🍒🍒": 100,
            "🍋🍋🍋": 50,
            "🍊🍊🍊": 25,
            "🍇🍇🍇": 15,
            "⭐⭐⭐": 10,
            "💎💎💎": 500,
            "7️⃣7️⃣7️⃣": 200,
            "🍒🍒": 2,
            "7️⃣": 1.5,
          };
        }

        // 计算单条线的中奖金额
        calculateLineWin(lineSymbols, betAmount) {
          const lineString = lineSymbols.join("");

          // 检查完全匹配
          if (this.paytable[lineString]) {
            return this.paytable[lineString] * betAmount;
          }
          // 检查两个相同(樱桃)
          else if (lineSymbols[0] === "🍒" && lineSymbols[1] === "🍒") {
            return this.paytable["🍒🍒"] * betAmount;
          }
          // 检查任意7
          else if (lineSymbols.includes("7️⃣")) {
            return this.paytable["7️⃣"] * betAmount;
          }

          return 0;
        }

        getMultiplier(combination) {
          return this.paytable[combination] || 0;
        }
      }

      // 游戏状态管理器 - 负责游戏数据的维护
      class GameStateManager {
        constructor() {
          this.balance = 1000;
          this.totalBet = 0;
          this.totalWin = 0;
          this.gameRounds = 0;
          this.wins = 0;
          this.currentWinStreak = 0;
          this.maxWinStreak = 0;
          this.maxSingleWin = 0;
          this.rngCount = 0;
        }

        placeBet(amount) {
          if (this.balance >= amount) {
            this.balance -= amount;
            this.totalBet += amount;
            this.gameRounds++;
            return true;
          }
          return false;
        }

        addWin(amount) {
          this.balance += amount;
          this.totalWin += amount;
          this.wins++;
          this.currentWinStreak++;
          this.maxWinStreak = Math.max(
            this.maxWinStreak,
            this.currentWinStreak
          );
          this.maxSingleWin = Math.max(this.maxSingleWin, amount);
        }

        resetWinStreak() {
          this.currentWinStreak = 0;
        }

        getCurrentRTP() {
          return this.totalBet > 0 ? this.totalWin / this.totalBet : 0.95;
        }

        canAfford(amount) {
          return this.balance >= amount;
        }

        reset() {
          this.totalBet = 0;
          this.totalWin = 0;
          this.gameRounds = 0;
          this.wins = 0;
          this.currentWinStreak = 0;
          this.maxWinStreak = 0;
          this.maxSingleWin = 0;
          this.rngCount = 0;
        }
      }

      // 显示控制器 - 负责UI更新
      class DisplayController {
        constructor(gameState) {
          this.gameState = gameState;
        }

        updateDisplay() {
          document.getElementById(
            "balance"
          ).textContent = `${this.gameState.balance} 元`;
          document.getElementById(
            "totalBet"
          ).textContent = `${this.gameState.totalBet} 元`;
          document.getElementById(
            "totalWin"
          ).textContent = `${this.gameState.totalWin} 元`;
          document.getElementById("gameRounds").textContent =
            this.gameState.gameRounds;
          document.getElementById("winRate").textContent = `${
            this.gameState.gameRounds > 0
              ? (
                  (this.gameState.wins / this.gameState.gameRounds) *
                  100
                ).toFixed(1)
              : 0
          }%`;
          document.getElementById("currentWinStreak").textContent =
            this.gameState.currentWinStreak;
          document.getElementById("maxWinStreak").textContent =
            this.gameState.maxWinStreak;
          document.getElementById(
            "maxSingleWin"
          ).textContent = `${this.gameState.maxSingleWin} 元`;

          const currentRTP = (this.gameState.getCurrentRTP() * 100).toFixed(1);
          document.getElementById("currentRTP").textContent = `${currentRTP}%`;
        }

        showMessage(message, type, delay = 0) {
          const messageArea = document.getElementById("messageArea");
          if (delay > 0) {
            setTimeout(() => {
              messageArea.innerHTML = `<div class="message ${type}">${message}</div>`;
            }, delay);
          } else {
            messageArea.innerHTML = `<div class="message ${type}">${message}</div>`;
          }
        }

        showWinModal(winAmount, winningCombination) {
          const modal = document.getElementById("winModal");
          const amountElement = document.getElementById("winAmount");
          const combinationElement = document.getElementById("winCombination");
          const headerElement = document.querySelector(".win-modal-header");
          const descElement = document.querySelector(
            ".win-modal-content > div:nth-last-child(2)"
          );

          requestAnimationFrame(() => {
            amountElement.textContent = `${winAmount} 元`;
            combinationElement.textContent = winningCombination;

            // 检查是否为特殊奖励并修改弹窗内容
            if (winningCombination === "🎁 幸运奖励") {
              headerElement.textContent = "🎁 幸运奖励!";
              headerElement.style.color = "#ff9800";

              // 修改描述文字
              descElement.textContent = "恭喜获得幸运奖励!";
              descElement.style.color = "#ff9800";
              descElement.style.fontWeight = "bold";
              descElement.style.fontSize = "1rem";

              // 修改组合显示样式
              combinationElement.style.background = "rgba(255, 152, 0, 0.3)";
              combinationElement.style.color = "#ff9800";
              combinationElement.style.fontWeight = "bold";
            } else {
              // 恢复正常中奖的样式
              headerElement.textContent = "🎉 恭喜中奖!🎉";
              headerElement.style.color = "";
              descElement.textContent = "您的幸运时刻到了!";
              descElement.style.color = "#555";
              descElement.style.fontWeight = "";
              descElement.style.fontSize = "1.1rem";
              combinationElement.style.background = "rgba(255, 255, 255, 0.3)";
              combinationElement.style.color = "";
              combinationElement.style.fontWeight = "";
            }
          });

          setTimeout(() => {
            document.getElementById("spinBtn").disabled = true;
            requestAnimationFrame(() => {
              modal.classList.add("show");
            });
          }, 2500);
        }
      }

      // 动画控制器 - 负责转轴动画和高亮效果
      class AnimationController {
        constructor(symbolManager, paylineManager, rng) {
          this.symbolManager = symbolManager;
          this.paylineManager = paylineManager;
          this.rng = rng;
          this.highlightTimers = [];
        }

        initializeReels() {
          for (let i = 1; i <= 3; i++) {
            const symbolsContainer = document.getElementById(`symbols${i}`);
            const reel = this.symbolManager.virtualReels[i - 1];

            for (let j = 0; j < 20; j++) {
              const symbol = this.symbolManager.getRandomSymbol(reel, this.rng);
              const symbolDiv = document.createElement("div");
              symbolDiv.className = "symbol";
              symbolDiv.textContent = symbol;
              symbolsContainer.appendChild(symbolDiv);
            }
          }
        }

        startSpinAnimation() {
          this.removeWinHighlight();

          // 清理之前可能存在的回调
          this.onAllReelsStoppedCallback = null;

          for (let i = 1; i <= 3; i++) {
            const reel = document.getElementById(`reel${i}`);

            // 完全重置转轴状态
            reel.classList.remove("spinning");

            // 停止任何正在进行的动画
            const symbolsContainer = document.getElementById(`symbols${i}`);
            if (symbolsContainer) {
              symbolsContainer.style.animation = "none";
              symbolsContainer.offsetHeight; // 强制重排
              symbolsContainer.style.animation = "";
            }

            // 强制重排,确保所有变更都被应用
            reel.offsetHeight;

            // 重新开始动画
            reel.classList.add("spinning");
            this.animateReel(i);
          }
        }

        animateReel(reelIndex) {
          const reel = document.getElementById(`reel${reelIndex}`);
          const symbolsContainer = document.getElementById(
            `symbols${reelIndex}`
          );
          const symbols = symbolsContainer.children;
          let lastUpdate = 0;

          const animate = (currentTime) => {
            if (!reel.classList.contains("spinning")) {
              return;
            }

            if (currentTime - lastUpdate > 150) {
              for (let j = 0; j < Math.min(5, symbols.length); j++) {
                symbols[j].textContent = this.symbolManager.getRandomSymbol(
                  this.symbolManager.virtualReels[reelIndex - 1],
                  this.rng
                );
              }
              lastUpdate = currentTime;
            }

            requestAnimationFrame(animate);
          };

          requestAnimationFrame(animate);
        }

        stopSpinAnimation(results) {
          const stopDelays = [500, 1000, 1500]; // 每个转轴的停止延迟
          let stoppedCount = 0;

          for (let i = 1; i <= 3; i++) {
            const reel = document.getElementById(`reel${i}`);
            const delay = stopDelays[i - 1];

            setTimeout(() => {
              // 确保停止动画
              reel.classList.remove("spinning");

              // 强制重排以确保动画完全停止
              reel.offsetHeight;

              const symbolsContainer = document.getElementById(`symbols${i}`);
              const symbols = symbolsContainer.children;
              const reelResults = results[i - 1];

              if (symbols.length >= 3) {
                symbols[0].textContent = reelResults[0];
                symbols[1].textContent = reelResults[1];
                symbols[2].textContent = reelResults[2];
              }

              for (let j = 3; j < symbols.length; j++) {
                symbols[j].textContent = this.symbolManager.getRandomSymbol(
                  this.symbolManager.virtualReels[i - 1],
                  this.rng
                );
              }

              stoppedCount++;
              // 当所有转轴都停止后,触发回调
              if (stoppedCount === 3 && this.onAllReelsStoppedCallback) {
                setTimeout(this.onAllReelsStoppedCallback, 100);
                this.onAllReelsStoppedCallback = null;
              }
            }, delay);
          }
        }

        highlightWin(winningLineIndexes = [0]) {
          this.clearHighlightTimers();

          const timer1 = setTimeout(() => {
            winningLineIndexes.forEach((lineIndex) => {
              const payline = this.paylineManager.getPayline(lineIndex);

              for (let reelIndex = 0; reelIndex < 3; reelIndex++) {
                const symbolsContainer = document.getElementById(
                  `symbols${reelIndex + 1}`
                );
                const symbols = symbolsContainer.children;
                const rowIndex = payline[reelIndex];

                if (symbols[rowIndex]) {
                  symbols[rowIndex].classList.add("win-highlight");
                }
              }
            });
          }, 2200);

          const timer2 = setTimeout(() => {
            this.removeWinHighlight();
          }, 7000);

          this.highlightTimers.push(timer1, timer2);
        }

        clearHighlightTimers() {
          this.highlightTimers.forEach((timer) => clearTimeout(timer));
          this.highlightTimers = [];
        }

        removeWinHighlight() {
          this.clearHighlightTimers();

          for (let i = 1; i <= 3; i++) {
            const symbolsContainer = document.getElementById(`symbols${i}`);
            const symbols = symbolsContainer.children;

            for (let j = 0; j < symbols.length; j++) {
              symbols[j].classList.remove("win-highlight");
            }
          }
        }
      }

      // 音效管理器 - 负责游戏音效
      class AudioManager {
        constructor() {
          this.audioContext = null;
        }

        playCelebrationSound() {
          if (this.audioContext && this.audioContext.state === "suspended") {
            this.audioContext.resume();
          }

          requestIdleCallback(() => {
            try {
              if (!this.audioContext) {
                this.audioContext = new (window.AudioContext ||
                  window.webkitAudioContext)();
              }

              const oscillator = this.audioContext.createOscillator();
              const gainNode = this.audioContext.createGain();

              oscillator.connect(gainNode);
              gainNode.connect(this.audioContext.destination);

              oscillator.frequency.setValueAtTime(
                523.25,
                this.audioContext.currentTime
              );
              oscillator.frequency.setValueAtTime(
                659.25,
                this.audioContext.currentTime + 0.15
              );

              gainNode.gain.setValueAtTime(0.2, this.audioContext.currentTime);
              gainNode.gain.exponentialRampToValueAtTime(
                0.01,
                this.audioContext.currentTime + 0.4
              );

              oscillator.start(this.audioContext.currentTime);
              oscillator.stop(this.audioContext.currentTime + 0.4);
            } catch (e) {
              // 音频播放不支持
            }
          });
        }
      }

      // 管理面板控制器 - 负责管理功能
      class AdminController {
        constructor() {
          this.adminMode = {
            enabled: false,
            winChance: 25,
            forceWin: false,
            forceLose: false,
            autoWin: false,
            jackpotMultiplier: 1.0,
            clickCount: 0,
          };
        }

        isEnabled() {
          return this.adminMode.enabled;
        }

        shouldForceWin() {
          return this.adminMode.forceWin || this.adminMode.autoWin;
        }

        shouldForceLose() {
          return this.adminMode.forceLose;
        }

        shouldControlProbability() {
          // ✅ 修复:只有在管理面板启用时才允许概率控制
          return this.adminMode.enabled && this.adminMode.winChance > 0;
        }

        getWinChance() {
          return this.adminMode.winChance;
        }

        setForceWin(value) {
          this.adminMode.forceWin = value;
          this.adminMode.forceLose = false;
        }

        setForceLose(value) {
          this.adminMode.forceLose = value;
          this.adminMode.forceWin = false;
        }

        updateAdminMode(mode) {
          document.getElementById("adminMode").textContent = mode;
          setTimeout(() => {
            document.getElementById("adminMode").textContent = this.adminMode
              .autoWin
              ? "自动中奖"
              : "手动控制";
          }, 2000);
        }

        enableAdminMode() {
          this.adminMode.enabled = true;
          this.adminMode.winChance = 100;

          document.getElementById("adminToggle").classList.add("show");
          document.getElementById("winChanceSlider").value = 100;
          document.getElementById("winChanceDisplay").textContent = "100%";

          this.updateAdminMode("已激活 - 100%中奖");
        }
      }

      // 老虎机核心类 - 协调各个模块
      class SlotMachine {
        constructor() {
          // 依赖注入 - 各个模块独立创建
          this.rng = new RNGEngine();
          this.symbolManager = new SymbolManager();
          this.paylineManager = new PaylineManager();
          this.paytableManager = new PaytableManager();
          this.gameState = new GameStateManager();
          this.displayController = new DisplayController(this.gameState);
          this.animationController = new AnimationController(
            this.symbolManager,
            this.paylineManager,
            this.rng
          );
          this.audioManager = new AudioManager();
          this.adminController = new AdminController();

          // 游戏状态
          this.isSpinning = false;
          this.autoSpinning = false;
          this.targetRTP = 0.95;
          this.needsRTPCompensation = false; // RTP补偿标志

          this.initialize();
        }

        initialize() {
          this.animationController.initializeReels();
          this.startRNGEngine();
          this.displayController.updateDisplay();
          this.updateAutoSpinButton(); // 初始化按钮状态
        }

        // 启动RNG引擎
        startRNGEngine() {
          setInterval(() => {
            this.rng.generate();
            this.gameState.rngCount++;

            if (this.gameState.rngCount % 1000 === 0) {
              document.getElementById("currentRNG").textContent =
                this.rng.getCurrentValue();
              document.getElementById("rngSpeed").textContent = Math.floor(
                Math.random() * 50000 + 100000
              );
              document.getElementById("rngCount").textContent =
                this.gameState.rngCount.toLocaleString();
            }
          }, 1);
        }

        // 主要旋转逻辑
        async spin() {
          if (this.isSpinning) return;

          // 立即清除所有高亮效果和动画状态
          this.animationController.removeWinHighlight();
          document.getElementById("messageArea").innerHTML = "";

          // 清理之前的回调,避免冲突
          if (this.animationController.onAllReelsStoppedCallback) {
            this.animationController.onAllReelsStoppedCallback = null;
          }

          const betAmount = parseInt(
            document.getElementById("betAmount").value
          );
          const paylines = parseInt(document.getElementById("paylines").value);
          const totalBet = betAmount * paylines;

          if (!this.gameState.canAfford(totalBet)) {
            this.displayController.showMessage("余额不足!", "lose");
            // 如果是自动旋转且余额不足,停止自动旋转
            if (this.autoSpinning) {
              this.autoSpinning = false;
              this.updateAutoSpinButton();
            }
            return;
          }

          this.isSpinning = true;
          this.gameState.placeBet(totalBet);
          document.getElementById("spinBtn").disabled = true;

          // 开始旋转动画
          this.animationController.startSpinAnimation();

          // 生成结果
          const results = this.generateResults(paylines);
          const spinDuration = 1000 + Math.random() * 2000;

          setTimeout(() => {
            // 设置所有转轴停止后的回调
            this.animationController.onAllReelsStoppedCallback = () => {
              const hasWin = this.calculateWin(results, betAmount, paylines);
              this.displayController.updateDisplay();

              // 如果没有中奖,在动画完成后显示失败消息
              if (!hasWin) {
                this.displayController.showMessage(
                  "😔 很遗憾,这次没有中奖。再试一次吧!",
                  "lose",
                  100 // 稍微延迟显示,确保动画完全结束
                );
              }

              this.isSpinning = false;

              // 重新启用旋转按钮
              document.getElementById("spinBtn").disabled = false;

              // 自动旋转逻辑:确保所有动画完成后再继续
              if (this.autoSpinning) {
                if (hasWin) {
                  // 中奖时停止自动旋转
                  this.autoSpinning = false;
                  this.updateAutoSpinButton();
                  this.displayController.showMessage(
                    "🎉 中奖了!自动旋转已停止",
                    "win",
                    2500
                  );
                } else if (this.gameState.canAfford(totalBet)) {
                  // 未中奖且余额足够,等待一段时间后继续自动旋转
                  setTimeout(() => {
                    if (this.autoSpinning) {
                      // 再次检查,防止用户在等待期间停止
                      this.spin();
                    }
                  }, 1000);
                } else {
                  // 余额不足,停止自动旋转
                  this.autoSpinning = false;
                  this.updateAutoSpinButton();
                  this.displayController.showMessage(
                    "余额不足,自动旋转已停止",
                    "lose"
                  );
                }
              }
            };

            this.animationController.stopSpinAnimation(results);
          }, spinDuration);
        }

        // 生成游戏结果
        generateResults(activatedPaylines) {
          // 管理面板控制
          if (
            this.adminController.isEnabled() &&
            this.adminController.shouldForceWin()
          ) {
            return this.symbolManager.generateWinningMatrix(
              this.rng,
              activatedPaylines,
              this.paylineManager.paylines,
              false
            );
          }

          // 管理面板概率控制
          if (
            this.adminController.isEnabled() &&
            this.adminController.shouldControlProbability()
          ) {
            const winChance = this.rng.getRange(0, 100);
            if (winChance < this.adminController.getWinChance()) {
              return this.symbolManager.generateWinningMatrix(
                this.rng,
                activatedPaylines,
                this.paylineManager.paylines,
                false
              );
            }
          }

          // RTP补偿检查 - 在生成结果前检查是否需要补偿
          if (!this.adminController.isEnabled()) {
            const currentRTP = this.gameState.getCurrentRTP();
            if (currentRTP < this.targetRTP - 0.05) {
              const compensationChance = this.rng.getRange(0, 100);
              if (compensationChance < 5) {
                // 需要RTP补偿,生成符合支付线的中奖结果
                this.needsRTPCompensation = true;
                return this.symbolManager.generateWinningMatrix(
                  this.rng,
                  activatedPaylines,
                  this.paylineManager.paylines,
                  true
                );
              }
            }
          }

          // 正常生成
          this.needsRTPCompensation = false;
          const rngSnapshot = [
            this.rng.getCurrentValue(),
            this.rng.generate(),
            this.rng.generate(),
          ];

          return this.symbolManager.generateSymbolMatrix(rngSnapshot);
        }

        // 计算赢取
        calculateWin(results, betAmount, activatedPaylines) {
          // 管理面板强制不中奖
          if (
            this.adminController.isEnabled() &&
            this.adminController.shouldForceLose()
          ) {
            this.adminController.setForceLose(false);
            this.adminController.updateAdminMode("强制不中");
            return false;
          }

          let totalWinAmount = 0;
          let winningCombinations = [];
          let winningLines = [];

          // 检查每条激活的支付线
          for (let lineIndex = 0; lineIndex < activatedPaylines; lineIndex++) {
            const lineSymbols = this.paylineManager.getLineSymbols(
              results,
              lineIndex
            );

            const lineWin = this.paytableManager.calculateLineWin(
              lineSymbols,
              betAmount
            );

            if (lineWin > 0) {
              totalWinAmount += lineWin;
              winningCombinations.push(lineSymbols.join(""));
              winningLines.push(lineIndex);
            }
          }

          // 支付线范围验证
          if (winningLines.length > 0) {
            const invalidLines = winningLines.filter(
              (line) => line >= activatedPaylines
            );
            if (invalidLines.length > 0) {
              // 过滤掉非法的中奖线
              winningLines = winningLines.filter(
                (line) => line < activatedPaylines
              );
              if (winningLines.length === 0) {
                totalWinAmount = 0;
                winningCombinations = [];
              }
            }
          }

          // 应用RTP调节
          if (!this.adminController.isEnabled()) {
            totalWinAmount = this.adjustForRTP(
              totalWinAmount,
              betAmount * activatedPaylines
            );
          } else {
            totalWinAmount = Math.floor(
              totalWinAmount * this.adminController.adminMode.jackpotMultiplier
            );
          }

          // 处理中奖结果
          if (totalWinAmount > 0) {
            this.gameState.addWin(totalWinAmount);
            this.animationController.highlightWin(winningLines);

            let combinationText;

            // 检查是否为RTP补偿生成的中奖
            if (this.needsRTPCompensation) {
              combinationText = "🎁 幸运奖励";
            } else {
              // 正常中奖的组合文字
              combinationText =
                winningCombinations.length > 1
                  ? `${winningCombinations.length}条线中奖!`
                  : winningCombinations[0];
            }

            this.displayController.showWinModal(
              totalWinAmount,
              combinationText
            );
            this.audioManager.playCelebrationSound();

            // 重置RTP补偿标志
            this.needsRTPCompensation = false;
            return true;
          } else {
            this.gameState.resetWinStreak();
            // 失败消息不在这里显示,移动到动画完成的回调中
            return false;
          }
        }

        // RTP调节机制
        adjustForRTP(winAmount, betAmount) {
          const currentRTP = this.gameState.getCurrentRTP();

          // RTP补偿现在在generateResults阶段处理,这里不再直接给予补偿金额

          // 只保留大奖削减机制
          if (
            currentRTP > this.targetRTP + 0.02 &&
            winAmount > betAmount * 50
          ) {
            const reducedAmount = Math.floor(winAmount * 0.8);
            return reducedAmount;
          }

          return winAmount;
        }

        // 管理面板方法的代理
        enableAdminMode() {
          this.adminController.enableAdminMode();
        }

        setAdminForceWin() {
          this.adminController.setForceWin(true);
        }

        setAdminForceLose() {
          this.adminController.setForceLose(true);
        }

        resetStats() {
          this.gameState.reset();
          this.displayController.updateDisplay();
          this.adminController.updateAdminMode("统计已重置");
        }

        // 更新自动旋转按钮状态
        updateAutoSpinButton() {
          const autoBtn = document.querySelector(
            'button[onclick="autoSpin()"]'
          );
          if (autoBtn) {
            if (this.autoSpinning) {
              autoBtn.textContent = "停止自动";
              autoBtn.style.background =
                "linear-gradient(145deg, #dc3545, #c82333)";
              autoBtn.style.color = "white";
            } else {
              autoBtn.textContent = "自动旋转";
              autoBtn.style.background =
                "linear-gradient(145deg, #6c757d, #5a6268)";
              autoBtn.style.color = "white";
            }
          }
        }

        // 强制停止自动旋转(用于紧急情况)
        forceStopAutoSpin() {
          this.autoSpinning = false;
          this.updateAutoSpinButton();
          // 清理可能存在的回调
          if (this.animationController.onAllReelsStoppedCallback) {
            this.animationController.onAllReelsStoppedCallback = null;
          }
        }
      }

      // RNG引擎类
      class RNGEngine {
        constructor() {
          this.seed = Date.now() % 2147483647;
          this.current = this.seed;
        }

        // 线性同余生成器 (LCG)
        generate() {
          this.current = (this.current * 16807) % 2147483647;
          return this.current;
        }

        // 获取当前值
        getCurrentValue() {
          return this.current;
        }

        // 获取指定范围的随机数
        getRange(min, max) {
          return min + (this.generate() % (max - min + 1));
        }
      }

      // 全局实例
      let slotMachine;

      // 初始化
      document.addEventListener("DOMContentLoaded", function () {
        slotMachine = new SlotMachine();

        // 兼容性处理
        if (!window.requestIdleCallback) {
          window.requestIdleCallback = function (cb) {
            return setTimeout(cb, 1);
          };
        }
      });

      // 游戏控制函数
      function spin() {
        if (slotMachine) {
          // 手动点击旋转时,停止自动旋转
          if (slotMachine.autoSpinning) {
            slotMachine.forceStopAutoSpin();
          }
          slotMachine.spin();
        }
      }

      function maxBet() {
        document.getElementById("betAmount").value = "100";
        document.getElementById("paylines").value = "5";
      }

      function autoSpin() {
        if (!slotMachine) return;

        if (!slotMachine.autoSpinning) {
          slotMachine.autoSpinning = true;
          slotMachine.updateAutoSpinButton();
          slotMachine.spin();
        } else {
          slotMachine.autoSpinning = false;
          slotMachine.updateAutoSpinButton();
        }
      }

      // 关闭中奖弹窗
      function closeWinModal() {
        if (!slotMachine) return;

        const modal = document.getElementById("winModal");

        // 关闭弹窗时立即清除高亮效果
        slotMachine.animationController.removeWinHighlight();

        // 使用RAF确保流畅的关闭动画
        requestAnimationFrame(() => {
          modal.classList.remove("show");

          // 重新启用旋转按钮
          setTimeout(() => {
            if (!slotMachine.isSpinning) {
              document.getElementById("spinBtn").disabled = false;
              // 注意:自动旋转在中奖时已经被停止,不需要额外处理
            }
          }, 300); // 等待弹窗关闭动画完成
        });
      }

      // 点击弹窗外部关闭
      document.addEventListener("click", function (event) {
        const modal = document.getElementById("winModal");
        if (event.target === modal) {
          closeWinModal();
        }
      });

      // 管理面板控制函数
      function toggleAdminPanel() {
        const panel = document.getElementById("adminPanel");
        panel.classList.toggle("show");
      }

      function updateWinChance(value) {
        if (slotMachine) {
          slotMachine.adminController.adminMode.winChance = parseInt(value);
          document.getElementById("winChanceDisplay").textContent = `${value}%`;
        }
      }

      function updateRTP(value) {
        if (slotMachine) {
          slotMachine.targetRTP = parseFloat(value) / 100;
          document.getElementById("rtpDisplay").textContent = `${value}%`;
        }
      }

      function updateJackpotMultiplier(value) {
        if (slotMachine) {
          slotMachine.adminController.adminMode.jackpotMultiplier =
            parseFloat(value);
          document.getElementById("jackpotDisplay").textContent = `${value}x`;
        }
      }

      function setBalance(value) {
        if (slotMachine) {
          slotMachine.gameState.balance = parseInt(value) || 1000;
          slotMachine.displayController.updateDisplay();
        }
      }

      function forceWin() {
        if (slotMachine) {
          slotMachine.setAdminForceWin();
        }
      }

      function forceLose() {
        if (slotMachine) {
          slotMachine.setAdminForceLose();
        }
      }

      function toggleAutoWin() {
        if (slotMachine) {
          slotMachine.adminController.adminMode.autoWin =
            !slotMachine.adminController.adminMode.autoWin;
          const btn = event.target;
          btn.textContent = slotMachine.adminController.adminMode.autoWin
            ? "停止自动"
            : "自动中奖";
          btn.style.background = slotMachine.adminController.adminMode.autoWin
            ? "linear-gradient(145deg, #ff6b6b, #ff5252)"
            : "linear-gradient(145deg, #17a2b8, #138496)";
          slotMachine.adminController.updateAdminMode(
            slotMachine.adminController.adminMode.autoWin
              ? "自动中奖"
              : "手动控制"
          );
        }
      }

      function resetStats() {
        if (slotMachine) {
          slotMachine.resetStats();
        }
      }

      // 激活管理面板的秘密点击 - 骗局暗门演示
      let titleClickCount = 0;
      document.addEventListener("DOMContentLoaded", function () {
        document
          .querySelector(".header h1")
          .addEventListener("click", function () {
            titleClickCount++;

            // 给予视觉反馈
            this.style.transform = "scale(1.1)";
            this.style.color = "#ff4444";
            setTimeout(() => {
              this.style.transform = "";
              this.style.color = "#ffd700";
            }, 200);

            if (titleClickCount >= 3 && slotMachine) {
              slotMachine.enableAdminMode();
              titleClickCount = 0;

              // 显示控制面板激活提示
              const warningMsg = document.createElement("div");
              warningMsg.innerHTML =
                "🎮 游戏控制面板已激活!现在可以自定义游戏参数!";
              warningMsg.style.cssText = `
                position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
                background: linear-gradient(45deg, #3296c8, #4db8e8); color: white;
                padding: 20px; border-radius: 10px; z-index: 9999;
                font-size: 1.2rem; font-weight: bold; text-align: center;
                box-shadow: 0 10px 30px rgba(50, 150, 200, 0.5);
                animation: backdoorActivated 3s ease-in-out;
              `;
              document.body.appendChild(warningMsg);

              setTimeout(() => {
                document.body.removeChild(warningMsg);
              }, 3000);

              this.style.animation = "celebrate 0.5s ease-in-out";
              setTimeout(() => {
                this.style.animation = "";
              }, 500);
            }
          });
      });
    </script>
  </body>
</html>

结语:技术与情怀的完美结合 💭

这个项目不仅仅是一个简单的老虎机游戏,它承载了:

  • 童年的回忆:那些在游戏厅度过的快乐时光
  • 技术的追求:现代化的架构设计和性能优化
  • 程序员的浪漫:隐藏的彩蛋和精心设计的细节
  • 工具的力量:Cursor让开发过程更加高效和愉快

每一行代码都是对童年的致敬,每一个功能都体现了对技术的热爱。在这个快节奏的时代,我们需要偶尔停下来,用技术重现那些美好的回忆。

记住那个激活彩蛋的秘密:连续点击标题3次 😉


"最好的代码,不仅能解决问题,还能带来快乐。"

如果这个项目唤起了你的童年回忆,别忘了给个关注+转发+收藏!让我们一起用代码记录那些年的美好时光。