童年的游戏机:用代码重现那些年的快乐时光 🎰
还记得小时候在游戏厅里,那些闪闪发光的老虎机吗?一枚硬币,三个转轴,无数个梦想...今天,让我们用现代化的技术栈,重新构建这份童年的美好回忆。
前言:那些年的游戏厅记忆 🕹️
作为一名程序员,每当我看到那些经典的游戏机时,总会想起童年时光。那时的我们,会为了一次"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-change、contain等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策略
- 模块化的音效系统
效果展示 🎉
源码展示 📝
<!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()">
×
</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次 😉
"最好的代码,不仅能解决问题,还能带来快乐。"
如果这个项目唤起了你的童年回忆,别忘了给个关注+转发+收藏!让我们一起用代码记录那些年的美好时光。