微件:GGLScratchGame:修订间差异
来自Limbo Wiki Mirror
无编辑摘要 标签:(旧)WikiEditor |
无编辑摘要 标签:(旧)WikiEditor |
||
| 第1行: | 第1行: | ||
< | <!DOCTYPE html> | ||
= | <html> | ||
<head> | |||
<meta charset="UTF-8"> | |||
<style> | |||
.scratch-lottery-container { | |||
font-family: 'Arial', sans-serif; | |||
max-width: 1100px; | |||
margin: 20px auto; | |||
background: #f5f5f5; | |||
border-radius: 8px; | |||
padding: 20px; | |||
box-shadow: 0 2px 8px rgba(0,0,0,0.1); | |||
} | |||
.scratch-lottery-header { | |||
text-align: center; | |||
margin-bottom: 20px; | |||
} | |||
.scratch-lottery-header h2 { | |||
margin: 0 0 10px 0; | |||
color: #333; | |||
font-size: 24px; | |||
} | |||
.scratch-lottery-status { | |||
display: flex; | |||
justify-content: space-around; | |||
margin: 15px 0; | |||
font-size: 14px; | |||
color: #666; | |||
} | |||
.status-item { | |||
text-align: center; | |||
} | |||
.status-label { | |||
font-weight: bold; | |||
color: #333; | |||
display: block; | |||
font-size: 12px; | |||
margin-bottom: 4px; | |||
} | |||
.status-value { | |||
font-size: 20px; | |||
/* | color: #ff6b35; | ||
. | } | ||
/* 彩票主容器 */ | |||
.lottery-ticket-wrapper { | |||
position: relative; | |||
} | margin: 20px auto; | ||
display: inline-block; | |||
width: 100%; | |||
max-width: 540px; | |||
background: white; | |||
border-radius: 4px; | |||
overflow: hidden; | |||
box-shadow: 0 4px 12px rgba(0,0,0,0.15); | |||
} | |||
.lottery-bg-img { | |||
display: block; | |||
width: 100%; | |||
height: auto; | |||
background: #fff; | |||
} | |||
/* 刮奖区域容器(黄色块位置) */ | |||
.scratchable-area { | |||
position: absolute; | |||
left: 10.6%; | |||
top: 23.6%; | |||
width: 78.6%; | |||
height: 53.4%; | |||
background: transparent; | |||
} | |||
/* 30 个格子网格 */ | |||
.tile-grid { | |||
display: grid; | |||
grid-template-columns: repeat(5, 1fr); | |||
grid-template-rows: repeat(6, 1fr); | |||
gap: 0; | |||
width: 100%; | |||
height: 100%; | |||
padding: 0; | |||
} | |||
/* 单个格子 */ | |||
.tile { | |||
position: relative; | |||
aspect-ratio: 1; | |||
background: #000; | |||
cursor: pointer; | |||
border: 1px solid rgba(255,255,255,0.2); | |||
overflow: hidden; | |||
transition: all 0.2s ease; | |||
} | |||
.tile:hover:not(.revealed) { | |||
background: #1a1a1a; | |||
box-shadow: inset 0 0 8px rgba(255,255,255,0.3); | |||
} | |||
.tile-cover { | |||
position: absolute; | |||
inset: 0; | |||
display: flex; | |||
align-items: center; | |||
justify-content: center; | |||
background: #000; | |||
font-size: 24px; | |||
font-weight: bold; | |||
color: #666; | |||
opacity: 1; | |||
transition: opacity 0.3s ease; | |||
pointer-events: none; | |||
} | |||
.tile-cover img { | |||
max-width: 100%; | |||
max-height: 100%; | |||
object-fit: contain; | |||
} | |||
.tile.revealed .tile-cover { | |||
opacity: 0; | |||
pointer-events: none; | |||
} | |||
.tile-reveal { | |||
position: absolute; | |||
inset: 0; | |||
display: flex; | |||
align-items: center; | |||
justify-content: center; | |||
background: #ffd700; | |||
opacity: 0; | |||
transition: opacity 0.3s ease; | |||
pointer-events: none; | |||
} | |||
.tile-reveal img { | |||
max-width: 100%; | |||
max-height: 100%; | |||
object-fit: contain; | |||
} | |||
.tile.revealed .tile-reveal { | |||
opacity: 1; | |||
} | |||
.tile-text { | |||
position: absolute; | |||
inset: 0; | |||
display: flex; | |||
align-items: center; | |||
justify-content: center; | |||
font-size: 12px; | |||
color: #333; | |||
font-weight: bold; | |||
text-align: center; | |||
padding: 4px; | |||
word-break: break-word; | |||
} | |||
/* 气泡文本 */ | |||
.bubble { | |||
position: fixed; | |||
background: white; | |||
border: 2px solid #333; | |||
border-radius: 8px; | |||
padding: 12px 16px; | |||
font-size: 13px; | |||
color: #333; | |||
max-width: 200px; | |||
word-wrap: break-word; | |||
z-index: 1000; | |||
box-shadow: 0 4px 12px rgba(0,0,0,0.2); | |||
animation: bubbleAppear 0.3s ease; | |||
pointer-events: none; | |||
} | |||
.bubble::before { | |||
content: ''; | |||
position: absolute; | |||
bottom: -8px; | |||
left: 20px; | |||
width: 0; | |||
height: 0; | |||
border-left: 8px solid transparent; | |||
border-right: 8px solid transparent; | |||
border-top: 8px solid #333; | |||
} | |||
.bubble::after { | |||
content: ''; | |||
position: absolute; | |||
bottom: -4px; | |||
left: 22px; | |||
width: 0; | |||
height: 0; | |||
border-left: 6px solid transparent; | |||
border-right: 6px solid transparent; | |||
border-top: 6px solid white; | |||
} | |||
.bubble.top { | |||
bottom: auto; | |||
} | |||
.bubble.top::before { | |||
bottom: auto; | |||
top: -8px; | |||
border-top: none; | |||
border-bottom: 8px solid #333; | |||
} | |||
.bubble.top::after { | |||
bottom: auto; | |||
top: -4px; | |||
border-top: none; | |||
border-bottom: 6px solid white; | |||
} | |||
@keyframes bubbleAppear { | |||
from { | |||
opacity: 0; | |||
transform: scale(0.8); | |||
} | |||
to { | |||
opacity: 1; | |||
transform: scale(1); | |||
} | |||
} | |||
/* 吉祥物按钮 + 扫描按钮容器 */ | |||
.controls-row { | |||
display: flex; | |||
gap: 10px; | |||
margin-top: 20px; | |||
width: 100%; | |||
} | |||
.mascot-button, | |||
.scan-button { | |||
flex: 1; | |||
padding: 16px 20px; | |||
font-size: 14px; | |||
font-weight: bold; | |||
border: none; | |||
border-radius: 6px; | |||
cursor: pointer; | |||
display: flex; | |||
align-items: center; | |||
justify-content: center; | |||
gap: 12px; | |||
transition: all 0.3s ease; | |||
} | |||
.mascot-button { | |||
background: #fff3e0; | |||
color: #333; | |||
border: 2px solid #ffb74d; | |||
} | |||
.mascot-button:hover { | |||
background: #ffe0b2; | |||
transform: translateY(-2px); | |||
box-shadow: 0 4px 12px rgba(255, 183, 77, 0.3); | |||
} | |||
.mascot-icon { | |||
font-size: 24px; | |||
min-width: 28px; | |||
} | |||
.mascot-text { | |||
text-align: left; | |||
flex: 1; | |||
} | |||
.scan-button { | |||
background: #e0e0e0; | |||
color: #666; | |||
border: 2px solid #999; | |||
} | |||
.scan-button:hover { | |||
background: #d0d0d0; | |||
transform: translateY(-2px); | |||
} | |||
.scan-button.active { | |||
background: linear-gradient(135deg, #ff6b35 0%, #ff8c42 100%); | |||
color: white; | |||
border-color: #ff6b35; | |||
box-shadow: 0 4px 16px rgba(255, 107, 53, 0.4); | |||
animation: pulse 1.5s infinite; | |||
} | |||
.scan-button.active:hover { | |||
box-shadow: 0 6px 20px rgba(255, 107, 53, 0.6); | |||
} | |||
@keyframes pulse { | |||
0%, 100% { | |||
transform: scale(1); | |||
} | |||
50% { | |||
transform: scale(1.02); | |||
} | |||
} | |||
/* 彩票申领按钮 */ | |||
.draw-button { | |||
display: block; | |||
margin: 20px auto; | |||
padding: 12px 32px; | |||
font-size: 16px; | |||
font-weight: bold; | |||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |||
color: white; | |||
border: none; | |||
border-radius: 6px; | |||
cursor: pointer; | |||
transition: all 0.3s ease; | |||
} | |||
.draw-button:hover { | |||
transform: translateY(-2px); | |||
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4); | |||
} | |||
.draw-button:disabled { | |||
opacity: 0.6; | |||
cursor: not-allowed; | |||
} | |||
/* 无彩票提示 */ | |||
.no-tickets-message { | |||
text-align: center; | |||
padding: 40px 20px; | |||
color: #999; | |||
font-size: 14px; | |||
} | |||
/* 倒数计时器 */ | |||
.countdown { | |||
display: inline-block; | |||
font-size: 14px; | |||
color: #ff6b35; | |||
font-weight: bold; | |||
margin-left: 10px; | |||
} | |||
/* 反色滤镜 (第11张后) */ | |||
.invert-filter { | |||
filter: invert(1) hue-rotate(180deg); | |||
} | |||
. | /* 调试模式 */ | ||
.debug-panel { | |||
position: fixed; | |||
bottom: 20px; | |||
right: 20px; | |||
} | background: rgba(0,0,0,0.85); | ||
color: #0f0; | |||
padding: 12px 16px; | |||
border-radius: 4px; | |||
font-family: 'Courier New', monospace; | |||
font-size: 12px; | |||
z-index: 999; | |||
max-height: 200px; | |||
overflow-y: auto; | |||
display: none; | |||
} | |||
.debug-panel.active { | |||
. | display: block; | ||
} | |||
} | |||
.debug-line { | |||
. | margin: 4px 0; | ||
} | |||
} | |||
/* | /* 响应式设计 */ | ||
@media (max-width: 600px) { | |||
.scratch-lottery-container { | |||
padding: 12px; | |||
} | |||
} | |||
.lottery-ticket-wrapper { | |||
. | max-width: 100%; | ||
} | |||
} | |||
.controls-row { | |||
.controls- | flex-direction: column; | ||
} | |||
} | |||
.scratch-lottery-status { | |||
. | flex-direction: column; | ||
gap: 10px; | |||
} | |||
} | |||
} | |||
} | |||
/* | /* 整页反色效果 (第11张) */ | ||
. | .page-invert-crash { | ||
position: fixed; | |||
top: 0; | |||
left: 0; | |||
width: 100%; | |||
height: 100%; | |||
background: #000; | |||
color: #ff0000; | |||
display: flex; | |||
align-items: center; | |||
justify-content: center; | |||
font-size: 32px; | |||
font-weight: bold; | |||
font-family: 'Courier New', monospace; | |||
z-index: 2000; | |||
animation: crashFlash 0.1s infinite; | |||
. | pointer-events: none; | ||
} | |||
@keyframes crashFlash { | |||
0%, 100% { opacity: 1; } | |||
50% { opacity: 0.8; } | |||
} | |||
} | |||
/* 吉祥物消失效果 */ | |||
.mascot-hidden .mascot-button { | |||
opacity: 0; | |||
< | pointer-events: none; | ||
} | |||
</style> | |||
</head> | |||
<body> | |||
<div class="scratch-lottery-container" id="scratchLotteryRoot"> | |||
< | <div class="scratch-lottery-header"> | ||
<h2>🎰 刮刮乐抽奖</h2> | |||
<div class="scratch-lottery-status"> | |||
<div class="status-item"> | |||
<span class="status-label">剩余彩票</span> | |||
< | <span class="status-value" id="ticketCount">3</span> | ||
</div> | |||
<div class="status-item"> | |||
<span class="status-label">当前票号</span> | |||
<span class="status-value" id="currentTicketId">-</span> | |||
</div> | |||
<div class="status-item"> | |||
<span class="status-label">已刮格子</span> | |||
<span class="status-value" id="revealedCount">0/30</span> | |||
</div> | |||
</div> | </div> | ||
</div> | |||
<!-- 主彩票区域 --> | |||
<div id="ticketContainer" style="text-align: center;"> | |||
<button class="draw-button" id="drawButton">来一张彩票</button> | |||
<div class="no-tickets-message" id="noTicketsMsg" style="display: none;"> | |||
没有更多彩票了!所有剧情已解锁。 | |||
</div> | </div> | ||
</div> | </div> | ||
<!-- | <!-- 控制按钮 --> | ||
<div class="controls- | <div class="controls-row" id="controlsRow" style="display: none;"> | ||
<div class="mascot- | <div class="mascot-button" id="mascotButton"> | ||
< | <span class="mascot-icon" id="mascotIcon">🎲</span> | ||
<div class="mascot- | <div class="mascot-text" id="mascotText">点击格子开始</div> | ||
</div> | </div> | ||
<button id=" | <button class="scan-button" id="scanButton" style="display: none;"> | ||
扫描结果 | |||
</button> | |||
</div> | </div> | ||
<!-- | <!-- 调试面板 --> | ||
<div | <div class="debug-panel" id="debugPanel"></div> | ||
</div> | </div> | ||
<script> | <script> | ||
(function() { | // ==================== 配置与数据 ==================== | ||
const CONFIG = { | |||
bg: 'lottery-bg.png', | |||
area: { x: 114, y: 450, width: 846, height: 1020 }, | |||
cols: 5, | |||
rows: 6, | |||
tileSize: 169.2, | |||
coverSize: 559, | |||
scale: 0.3027 | |||
}; | |||
const STATES = { | |||
1: { type: 'normal', text: '', emoji: '😐' }, | |||
2: { type: 'emoji', text: '(◕‿◕) 彩票扫描中…', emoji: '😊' }, | |||
3: { type: 'emoji', text: '(≧◡≦) 太棒了!', emoji: '🎉' }, | |||
4: { type: 'emoji', text: '(•̀ᴗ•́) 思考中…', emoji: '🤔' }, | |||
5: { type: 'emoji', text: '(⊙_⊙) 等等,好像不对', emoji: '😲' }, | |||
6: { type: 'meta', text: '你看见了什么?', emoji: '👻' }, | |||
7: { type: 'glitch', text: '…▒▒▒▒▒', emoji: '❌' }, | |||
8: { type: 'text', text: '哦嗯嘿哈… 别挠我痒痒', emoji: '😅' }, | |||
9: { type: 'text', text: '你是谁?', emoji: '❓' }, | |||
10: { type: 'text', text: '停下来。', emoji: '🛑' }, | |||
11: { type: 'text', text: '平安喜乐。你知道的太多了。', emoji: '⚫' } | |||
}; | |||
// 11张预设票序数据 (每张 30 格的状态) | |||
const TICKETS = [ | |||
{ | |||
id: 1, | |||
outcomes: [2,1,1,3,1, 1,2,1,1,1, 1,1,2,1,1, 1,1,1,2,1, 1,1,1,1,1, 1,1,1,1,1], | |||
notes: '第1张:介绍规则,吉祥物出场', | |||
extraTickets: 0, | |||
storyTrigger: 'intro' | |||
}, | |||
{ | |||
id: 2, | |||
outcomes: [3,1,1,1,3, 1,1,3,1,1, 3,1,1,1,1, 1,1,1,1,1, 1,1,1,1,1, 1,1,1,1,1], | |||
notes: '第2张:连中四次,额外发放 2 张', | |||
extraTickets: 2, | |||
storyTrigger: 'bonus' | |||
}, | |||
{ | |||
id: 3, | |||
outcomes: [1,2,1,1,1, 1,1,2,1,1, 1,1,1,1,2, 1,1,1,1,1, 1,1,1,1,1, 1,1,1,1,1], | |||
notes: '第3张:中两次 data', | |||
extraTickets: 0, | |||
storyTrigger: 'combo' | |||
}, | |||
{ | |||
id: 4, | |||
outcomes: [1,1,1,1,1, 1,1,1,1,1, 1,1,1,1,1, 1,1,1,1,1, 1,1,1,1,1, 1,1,1,1,3], | |||
notes: '第4张:本来没中,颜文字改了一个字后中了', | |||
extraTickets: 0, | |||
storyTrigger: 'plot_twist' | |||
}, | |||
{ | |||
id: 5, | |||
outcomes: [2,1,1,1,1, 1,1,1,1,1, 1,1,1,1,1, 1,1,1,1,1, 1,1,1,1,1, 1,1,1,1,1], | |||
notes: '第5张:中 data', | |||
extraTickets: 0, | |||
storyTrigger: 'normal' | |||
}, | |||
{ | |||
id: 6, | |||
outcomes: [1,1,1,1,1, 1,1,1,1,1, 1,1,1,1,1, 1,1,1,1,1, 1,1,1,1,1, 1,1,1,1,1], | |||
notes: '第6张:什么都没有(空白)', | |||
extraTickets: 0, | |||
storyTrigger: 'empty' | |||
}, | |||
{ | |||
id: 7, | |||
outcomes: [1,1,1,1,1, 1,1,1,1,1, 1,1,1,1,1, 1,1,1,1,1, 1,1,1,3,1, 1,1,1,1,1], | |||
notes: '第7张:中了一张', | |||
extraTickets: 0, | |||
storyTrigger: 'single' | |||
}, | |||
{ | |||
id: 8, | |||
outcomes: [2,1,1,1,1, 1,1,1,1,1, 1,1,1,1,1, 1,1,1,1,1, 1,1,1,3,1, 1,1,1,3,1], | |||
notes: '第8张:中 data,并中两张', | |||
extraTickets: 0, | |||
storyTrigger: 'double' | |||
}, | |||
{ | |||
id: 9, | |||
outcomes: [2,2,2,2,2, 2,2,2,2,2, 2,2,2,2,2, 2,2,2,2,2, 2,2,2,2,2, 2,2,2,2,2], | |||
notes: '第9张:全页 data', | |||
extraTickets: 0, | |||
storyTrigger: 'full_page' | |||
}, | |||
{ | |||
id: 10, | |||
outcomes: [10,10,10,10,10, 10,10,10,10,10, 10,10,10,10,10, 10,10,10,10,10, 10,10,10,10,10, 10,10,10,10,10], | |||
notes: '第10张:全页停下来,扫描按钮高亮', | |||
extraTickets: 0, | |||
storyTrigger: 'scan_ready' | |||
}, | |||
{ | |||
id: 11, | |||
outcomes: [11,11,11,11,11, 11,11,11,11,11, 11,11,11,11,11, 11,11,11,11,11, 11,11,11,11,11, 11,11,11,11,11], | |||
notes: '第11张:反色滤镜,伪闪退跳转', | |||
extraTickets: 0, | |||
storyTrigger: 'final' | |||
} | |||
]; | |||
// ==================== 游戏状态机 ==================== | |||
let gameState = { | |||
playerTickets: 3, | |||
currentTicketIndex: -1, | |||
currentTicket: null, | |||
revealedTiles: new Set(), | |||
isTicketOpen: false, | |||
isFinal: false, | |||
debugMode: false | |||
}; | |||
// ==================== DOM 元素缓存 ==================== | |||
const elements = { | |||
root: null, | |||
ticketContainer: null, | |||
drawButton: null, | |||
noTicketsMsg: null, | |||
controlsRow: null, | |||
mascotButton: null, | |||
mascotIcon: null, | |||
mascotText: null, | |||
scanButton: null, | |||
ticketCount: null, | |||
currentTicketId: null, | |||
revealedCount: null, | |||
debugPanel: null | |||
}; | |||
// ==================== 初始化 ==================== | |||
function init() { | |||
cacheElements(); | |||
attachEventListeners(); | |||
updateUI(); | |||
logDebug('Game initialized'); | |||
} | |||
function cacheElements() { | |||
elements.root = document.getElementById('scratchLotteryRoot'); | |||
elements.ticketContainer = document.getElementById('ticketContainer'); | |||
elements.drawButton = document.getElementById('drawButton'); | |||
elements.noTicketsMsg = document.getElementById('noTicketsMsg'); | |||
elements.controlsRow = document.getElementById('controlsRow'); | |||
elements.mascotButton = document.getElementById('mascotButton'); | |||
elements.mascotIcon = document.getElementById('mascotIcon'); | |||
elements.mascotText = document.getElementById('mascotText'); | |||
elements.scanButton = document.getElementById('scanButton'); | |||
elements.ticketCount = document.getElementById('ticketCount'); | |||
elements.currentTicketId = document.getElementById('currentTicketId'); | |||
elements.revealedCount = document.getElementById('revealedCount'); | |||
elements.debugPanel = document.getElementById('debugPanel'); | |||
} | |||
function attachEventListeners() { | |||
elements.drawButton.addEventListener('click', drawTicket); | |||
elements.mascotButton.addEventListener('click', mascotButtonClick); | |||
elements.scanButton.addEventListener('click', scanButtonClick); | |||
// | // 键盘快捷键 | ||
document.addEventListener('keydown', handleKeyboard); | |||
} | |||
// ==================== 核心游戏函数 ==================== | |||
if ( | function drawTicket() { | ||
if (gameState.playerTickets <= 0) { | |||
showMessage('没有更多彩票了!'); | |||
return; | |||
} | |||
gameState.currentTicketIndex++; | |||
if (gameState.currentTicketIndex >= TICKETS.length) { | |||
showMessage('所有剧情已完成!'); | |||
return; | |||
} | } | ||
gameState.currentTicket = TICKETS[gameState.currentTicketIndex]; | |||
gameState.playerTickets--; | |||
gameState.revealedTiles.clear(); | |||
gameState.isTicketOpen = true; | |||
renderTicket(); | |||
updateUI(); | |||
logDebug(`Ticket ${gameState.currentTicket.id} opened`); | |||
function | // 第10张后高亮扫描按钮 | ||
if (gameState.currentTicketIndex === 9) { | |||
elements.scanButton.classList.add('active'); | |||
} | |||
} | |||
function renderTicket() { | |||
const ticket = gameState.currentTicket; | |||
// 清空容器 | |||
elements.ticketContainer.innerHTML = ''; | |||
// 创建彩票HTML | |||
const ticketHTML = ` | |||
<div class="lottery-ticket-wrapper"> | |||
<canvas id="lotteryCanvas" style="display: block; width: 100%; border-radius: 4px;"></canvas> | |||
<div class="scratchable-area"> | |||
<div class="tile-grid" id="tileGrid"></div> | |||
</div> | |||
</div> | |||
`; | |||
elements.ticketContainer.insertAdjacentHTML('beforeend', ticketHTML); | |||
const canvas = document.getElementById('lotteryCanvas'); | |||
const ctx = canvas.getContext('2d'); | |||
// 设置 canvas 尺寸(宽高比按照背景图) | |||
const containerWidth = elements.ticketContainer.offsetWidth - 20; | |||
const aspectRatio = 1075 / 1911; | |||
canvas.width = containerWidth; | |||
canvas.height = containerWidth / aspectRatio; | |||
// 绘制背景色(灰白色作为背景) | |||
ctx.fillStyle = '#f0f0f0'; | |||
ctx.fillRect(0, 0, canvas.width, canvas.height); | |||
// 绘制黄色刮奖区域 | |||
const areaLeft = (CONFIG.area.x / 1075) * canvas.width; | |||
const areaTop = (CONFIG.area.y / 1911) * canvas.height; | |||
const areaWidth = (CONFIG.area.width / 1075) * canvas.width; | |||
const areaHeight = (CONFIG.area.height / 1911) * canvas.height; | |||
ctx.fillStyle = '#ffd700'; | |||
ctx.fillRect(areaLeft, areaTop, areaWidth, areaHeight); | |||
// 绘制黑色分割线 | |||
ctx.strokeStyle = '#ccc'; | |||
ctx.lineWidth = 2; | |||
ctx.strokeRect(areaLeft, areaTop, areaWidth, areaHeight); | |||
// 创建30个格子 | |||
const tileGrid = document.getElementById('tileGrid'); | |||
const tileSize = areaWidth / CONFIG.cols; | |||
for (let i = 0; i < 30; i++) { | |||
const state = ticket.outcomes[i]; | |||
const tile = createTile(i, state, tileSize); | |||
tileGrid.appendChild(tile); | |||
} | } | ||
elements.controlsRow.style.display = 'flex'; | |||
updateMascotMessage(); | |||
} | |||
function createTile(index, stateNum, tileSize) { | |||
const tile = document.createElement('div'); | |||
tile.className = 'tile'; | |||
tile.dataset.index = index; | |||
tile.setAttribute('aria-label', `格子 ${index + 1}`); | |||
tile.tabIndex = 0; | |||
const stateInfo = STATES[stateNum] || STATES[1]; | |||
// 覆盖层 (黑色) | |||
const cover = document.createElement('div'); | |||
cover.className = 'tile-cover'; | |||
cover.textContent = '🔲'; | |||
tile.appendChild(cover); | |||
// 揭示层 (黄色) | |||
const reveal = document.createElement('div'); | |||
reveal.className = 'tile-reveal'; | |||
if (stateInfo.type === 'emoji') { | |||
reveal.textContent = stateInfo.emoji; | |||
} else if (stateInfo.type === 'normal') { | |||
reveal.textContent = '✨'; | |||
} else if (stateInfo.type === 'text') { | |||
const textDiv = document.createElement('div'); | |||
textDiv.style.fontSize = '11px'; | |||
textDiv.style.wordBreak = 'break-word'; | |||
textDiv.textContent = stateInfo.text.substring(0, 8); | |||
reveal.appendChild(textDiv); | |||
} else if (stateInfo.type === 'glitch') { | |||
reveal.textContent = '▒▒'; | |||
reveal.style.color = '#333'; | |||
} else if (stateInfo.type === 'meta') { | |||
reveal.textContent = '?'; | |||
reveal.style.fontSize = '32px'; | |||
} | } | ||
tile.appendChild(reveal); | |||
// 点击事件 | |||
tile.addEventListener('click', () => revealTile(index, stateNum)); | |||
tile.addEventListener('keydown', (e) => { | |||
if (e.key === 'Enter' || e.key === ' ') { | |||
e.preventDefault(); | |||
revealTile(index, stateNum); | |||
} | |||
}); | |||
return tile; | |||
} | |||
function revealTile(index, stateNum) { | |||
if (gameState.revealedTiles.has(index)) return; | |||
gameState.revealedTiles.add(index); | |||
const tiles = document.querySelectorAll('.tile'); | |||
tiles[index].classList.add('revealed'); | |||
// 显示气泡 | |||
const stateInfo = STATES[stateNum] || STATES[1]; | |||
if (stateInfo.text) { | |||
showBubble(index, stateInfo.text); | |||
} | |||
// 触发状态效果 | |||
handleStateEffect(stateNum); | |||
// 更新UI | |||
updateUI(); | |||
// 检查是否完成 | |||
checkTicketCompletion(); | |||
logDebug(`Tile ${index} revealed: state ${stateNum}`); | |||
} | |||
function showBubble(tileIndex, text) { | |||
// 获取格子位置 | |||
const tiles = document.querySelectorAll('.tile'); | |||
const tile = tiles[tileIndex]; | |||
const rect = tile.getBoundingClientRect(); | |||
const bubble = document.createElement('div'); | |||
bubble.className = 'bubble'; | |||
bubble.textContent = text; | |||
document.body.appendChild(bubble); | |||
// 自动定位(避免超出视口) | |||
bubble.style.left = (rect.left + rect.width / 2 - 100) + 'px'; | |||
bubble.style.top = (rect.top - 60) + 'px'; | |||
// 检查是否超出视口 | |||
const bubbleRect = bubble.getBoundingClientRect(); | |||
if (bubbleRect.top < 0) { | |||
bubble.classList.add('top'); | |||
bubble.style.top = (rect.bottom + 20) + 'px'; | |||
} | |||
// 3秒后移除 | |||
setTimeout(() => bubble.remove(), 3000); | |||
} | |||
function handleStateEffect(stateNum) { | |||
switch (stateNum) { | |||
case 2: // 播报 | |||
updateMascotMessage(); | |||
break; | |||
case 3: // 欢呼 | |||
elements.mascotIcon.textContent = '🎉'; | |||
break; | |||
case 5: // 惊讶 | |||
elements.mascotIcon.textContent = '😲'; | |||
break; | |||
case 6: // 消失 | |||
elements.mascotButton.style.opacity = '0.3'; | |||
break; | |||
case 11: // 最终 | |||
triggerFinalSequence(); | |||
break; | |||
} | |||
} | |||
function checkTicketCompletion() { | |||
if (gameState.revealedTiles.size === 30) { | |||
const ticket = gameState.currentTicket; | |||
logDebug(`Ticket ${ticket.id} completed - Story: ${ticket.storyTrigger}`); | |||
// 额外发放彩票 | |||
if (ticket.extraTickets > 0) { | |||
gameState.playerTickets += ticket.extraTickets; | |||
showMessage(`✨ 额外获得 ${ticket.extraTickets} 张彩票!`); | |||
} | } | ||
// 延迟后显示下一张按钮 | |||
setTimeout(() => { | |||
updateUI(); | |||
}, 1000); | |||
} | |||
} | |||
function triggerFinalSequence() { | |||
if (gameState.isFinal) return; | |||
gameState.isFinal = true; | |||
// 创建反色闪退效果 | |||
const crash = document.createElement('div'); | |||
crash.className = 'page-invert-crash'; | |||
crash.textContent = '扫描异常…'; | |||
document.body.appendChild(crash); | |||
// 1秒后跳转到词条页面 | |||
setTimeout(() => { | |||
// 这里可以替换为实际的跳转逻辑 | |||
showMessage('剧情结束!所有秘密已解锁。'); | |||
crash.remove(); | |||
}, 2000); | |||
} | |||
function scanButtonClick() { | |||
if (gameState.currentTicketIndex !== 9) { | |||
showMessage('尚未解锁此功能'); | |||
return; | |||
} | |||
logDebug('Scan button clicked - Releasing ticket 11'); | |||
gameState.playerTickets = 1; // 发放最后一张票 | |||
updateUI(); | |||
} | |||
function mascotButtonClick() { | |||
showMessage('吉祥物说:' + (elements.mascotText.textContent || '点击格子看看吧!')); | |||
} | |||
function updateMascotMessage() { | |||
if (gameState.currentTicket) { | |||
const storyText = { | |||
intro: '欢迎来到刮刮乐!点击格子开始吧!', | |||
bonus: '哇,连中了!你很幸运呢!', | |||
combo: '组合效果!再来一张?', | |||
plot_twist: '等等... 这个结果不对劲', | |||
normal: '继续加油!', | |||
empty: '哎呀,什么都没有...', | |||
single: '再接再厉!', | |||
double: '双倍幸运!', | |||
full_page: '全中了!!!', | |||
scan_ready: '...', | |||
final: '你看到太多了...' | |||
}; | |||
elements.mascotText.textContent = storyText[gameState.currentTicket.storyTrigger] || '...'; | |||
elements.mascotIcon.textContent = STATES[2].emoji; | |||
} | |||
} | |||
function updateUI() { | |||
elements.ticketCount.textContent = gameState.playerTickets; | |||
elements.currentTicketId.textContent = gameState.currentTicket | |||
? gameState.currentTicket.id | |||
: '-'; | |||
elements.revealedCount.textContent = | |||
`${gameState.revealedTiles.size}/30`; | |||
// 控制按钮显示 | |||
if (gameState.isTicketOpen) { | |||
elements.drawButton.style.display = 'none'; | |||
elements.noTicketsMsg.style.display = 'none'; | |||
} else { | |||
elements.drawButton.style.display = 'block'; | |||
elements.drawButton.textContent = gameState.playerTickets > 0 | |||
? '来一张彩票' | |||
: '没有更多彩票了'; | |||
elements.drawButton.disabled = gameState.playerTickets <= 0; | |||
elements.noTicketsMsg.style.display = gameState.playerTickets <= 0 ? 'block' : 'none'; | |||
} | |||
} | |||
function handleKeyboard(e) { | |||
if (e.ctrlKey && e.key === 'd') { | |||
gameState.debugMode = !gameState.debugMode; | |||
elements.debugPanel.classList.toggle('active'); | |||
logDebug('Debug mode toggled: ' + gameState.debugMode); | |||
} | } | ||
} | |||
function showMessage(msg) { | |||
alert(msg); | |||
} | |||
function logDebug(msg) { | |||
if (gameState.debugMode) { | |||
const line = document.createElement('div'); | |||
line.className = 'debug-line'; | |||
line.textContent = '[' + new Date().toLocaleTimeString() + '] ' + msg; | |||
elements.debugPanel.appendChild(line); | |||
elements.debugPanel.scrollTop = elements.debugPanel.scrollHeight; | |||
} | |||
console.log('[ScratchLottery]', msg); | |||
} | |||
// ==================== 入口 ==================== | |||
window.addEventListener('DOMContentLoaded', init); | |||
// 如果在现有 DOM 中,立即初始化 | |||
} | if (document.readyState !== 'loading') { | ||
init(); | |||
} | |||
</script> | </script> | ||
</ | |||
</body> | |||
</html> | |||
2026年2月1日 (日) 15:56的版本
<!DOCTYPE html>
🎰 刮刮乐抽奖
剩余彩票
3
当前票号
-
已刮格子
0/30
