Back to discovery
賽博方塊消除
Game · 繁體中文

賽博方塊消除

經典10x20下落方塊消除。配備落點幽靈預覽(Ghost Piece)、下一塊預告、快速硬降(Hard Drop)與多行同時消除連擊。 🎮 線上即點即玩:https://media.huangguodrama.ai/motion/games/cyber-block-drop/500dc2331e981765/index.html

Game

Continue With Interactive Roleplay & Novel Adaptation

Keep exploring the world of 賽博方塊消除: chat with AI characters in interactive roleplay, read full web novel adaptations, or binge multi-episode vertical dramas across the Huangguo network.

Watch and read details

Available now
4 available images
Reading language
简体中文

Content record updated .

Availability describes the media currently listed here. A preview is not a complete series; more episodes or a release schedule are not promised.

Read & Watch 賽博方塊消除 Online — FAQ

Explore 賽博方塊消除 online in your browser on Reelshorts. 4 available images.

Where can I read or watch 賽博方塊消除 online?
You can explore 賽博方塊消除 directly in your browser on Reelshorts (https://reelshorts.app/title/1004717). Current availability: 4 available images.
What other collections are available on Reelshorts?
Browse Readable Novels, Short Dramas to Watch Online, Alpha King & Fated Mate Wolf Pack Dramas, Revenge Heiress & Betrayed Wife Comeback, Secret Billionaire Baby & Hidden Identity, Best Free DramaBox & ShortMax Alternatives, Top Chinese Short Dramas with English Subtitles, and Visual Stories in the Huangguo catalog.
简体中文

游戏说明与在线入口

【赛博方块消除】

经典10x20下落方块消除。配备落点幽灵预览(Ghost Piece)、下一块预告、快速硬降(Hard Drop)与多行同时消除连击。

■ 在线畅玩地址:https://media.huangguodrama.ai/motion/games/cyber-block-drop/500dc2331e981765/index.html

■ 操控方式:支持触屏点击/滑动与桌面端鼠标/键盘操控,内置中英双语切换、Web Audio 合成音效与最高分自动存档。

■ 开源许可:MIT License (Huangguo Arcade Studio)

HTML5 独立可玩源码包 (index.html)

<!doctype html>

<html lang="zh-CN">

<head>

<meta charset="utf-8">

<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">

<title>赛博方块消除 · Cyber Block Drop</title>

<style>

:root {

--bg: #0b0713;

--panel: #150e24;

--accent: #22d3ee;

--accent2: #f43f5e;

--text: #f8f4fc;

--muted: rgba(248, 244, 252, 0.65);

}

* { box-sizing: border-box; user-select: none; -webkit-user-select: none; }

html, body {

margin: 0; padding: 0; width: 100%; height: 100%;

background: radial-gradient(circle at 50% 0%, #24133d 0%, var(--bg) 75%);

color: var(--text);

font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Noto Sans SC", sans-serif;

overflow-x: hidden;

}

button { font: inherit; color: inherit; cursor: pointer; }

.shell {

max-width: 780px; margin: 0 auto; min-height: 100svh;

display: flex; flex-direction: column; padding: 12px 14px; gap: 10px;

}

.topbar {

display: flex; align-items: center; justify-content: space-between; gap: 8px;

padding: 10px 14px; border-radius: 14px;

background: rgba(21, 14, 36, 0.85);

border: 1px solid rgba(255, 255, 255, 0.12);

}

.brand { display: flex; align-items: center; gap: 10px; min-width: 0; }

.badge {

padding: 3px 9px; border-radius: 999px; font-size: 11px; font-weight: 800;

background: linear-gradient(90deg, var(--accent), var(--accent2)); color: #12091c;

}

.brand h1 { margin: 0; font-size: 16px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.stats { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

.pill {

padding: 4px 10px; border-radius: 999px; font-size: 12px;

background: rgba(255, 255, 255, 0.06); border: 1px solid rgba(255, 255, 255, 0.12);

}

.tbtn {

padding: 5px 10px; border-radius: 999px; font-size: 12px;

background: rgba(255, 255, 255, 0.08); border: 1px solid rgba(255, 255, 255, 0.16);

}

.banner {

padding: 8px 14px; border-radius: 10px; font-size: 13px; text-align: center;

background: rgba(255, 255, 255, 0.05); color: var(--accent2);

border: 1px solid rgba(255, 255, 255, 0.08);

}

.stage {

flex: 1; display: flex; align-items: center; justify-content: center;

background: rgba(15, 10, 28, 0.78); border-radius: 18px; padding: 14px;

border: 1px solid rgba(255, 255, 255, 0.12);

box-shadow: inset 0 0 40px rgba(0, 0, 0, 0.55);

}

.table-felt { width: 100%; text-align: center; display: flex; flex-direction: column; gap: 12px; }

.hand-zone { display: flex; flex-direction: column; gap: 6px; align-items: center; }

.zone-label { font-size: 12px; color: var(--muted); letter-spacing: 0.06em; }

.cards-row { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; min-height: 68px; }

.pcard {

width: 50px; height: 68px; border-radius: 9px; background: #fcfaf7; color: #181224;

display: flex; flex-direction: column; justify-content: space-between; padding: 5px 6px;

font-weight: 800; box-shadow: 0 4px 12px rgba(0,0,0,.45); border: 1px solid #ddd;

}

.pcard.red { color: #e11d48; }

.pcard.back {

background: linear-gradient(135deg, var(--accent), #2e1065); color: #fff;

display: grid; place-items: center; font-size: 24px; border-color: rgba(255,255,255,.3);

}

.pcard span { font-size: 20px; align-self: center; }

.btn-bar { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; }

.gbtn {

padding: 9px 15px; border-radius: 11px; font-weight: 700; font-size: 13px;

background: rgba(255, 255, 255, 0.08); border: 1px solid rgba(255, 255, 255, 0.18);

transition: transform 0.1s, background 0.15s;

}

.gbtn:hover { background: rgba(255, 255, 255, 0.15); }

.gbtn:active { transform: scale(0.96); }

.gbtn.primary {

background: linear-gradient(90deg, var(--accent), var(--accent2));

color: #13091f; border-color: transparent;

}

.gbtn.accent {

border-color: var(--accent2); color: var(--accent2);

}

</style>

</head>

<body>

<div class="shell">

<header class="topbar">

<div class="brand">

<span class="badge">BLOCK DROP</span>

<h1 id="titleText">赛博方块消除</h1>

</div>

<div class="stats">

<span class="pill">SCORE <b id="scoreVal">0</b></span>

<span class="pill">BEST <b id="bestVal">0</b></span>

<button class="tbtn" id="langBtn">中/EN</button>

<button class="tbtn" id="muteBtn">🔊</button>

<button class="tbtn" id="resetBtn">重开</button>

</div>

</header>

<div class="banner" id="statusBanner">经典10x20下落方块消除。配备落点幽灵预览(Ghost Piece)、下一块预告、快速硬降(Hard Drop)与多行同时消除连击。</div>

<main class="stage" id="gameStage"></main>

</div>

<script>

const GAME_ID = "cyber-block-drop";

const ZH_TITLE = "赛博方块消除";

const EN_TITLE = "Cyber Block Drop";

// Shared Web Audio synth & helpers

let audioCtx = null, muted = false, lang = (navigator.language || 'zh').startsWith('zh') ? 'zh' : 'en';

function sfx(freq=440, dur=0.08, type='sine', vol=0.12) {

if (muted) return;

try {

if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)();

if (audioCtx.state === 'suspended') audioCtx.resume();

const o = audioCtx.createOscillator(), g = audioCtx.createGain();

o.type = type; o.frequency.setValueAtTime(freq, audioCtx.currentTime);

g.gain.setValueAtTime(vol, audioCtx.currentTime);

g.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + dur);

o.connect(g); g.connect(audioCtx.destination);

o.start(); o.stop(audioCtx.currentTime + dur);

} catch {}

}

function chordWin() { sfx(523,0.1,'triangle'); setTimeout(()=>sfx(659,0.1,'triangle'),80); setTimeout(()=>sfx(784,0.18,'triangle'),160); }

function chordLose() { sfx(300,0.14,'sawtooth'); setTimeout(()=>sfx(220,0.22,'sawtooth'),120); }

const scoreEl = document.getElementById('scoreVal');

const bestEl = document.getElementById('bestVal');

const statusEl = document.getElementById('statusBanner');

const stageEl = document.getElementById('gameStage');

const BEST_KEY = 'hg_best_' + GAME_ID;

let score = 0, best = Number(localStorage.getItem(BEST_KEY) || 0);

bestEl.textContent = best;

function setScore(v) {

score = Math.max(0, Math.round(v));

scoreEl.textContent = score;

if (score > best) { best = score; bestEl.textContent = best; try { localStorage.setItem(BEST_KEY, String(best)); } catch {} }

}

function addScore(d) { setScore(score + d); }

function msg(zhText, enText) { statusEl.textContent = lang === 'zh' ? zhText : enText; }

function t(zhText, enText) { return lang === 'zh' ? zhText : enText; }

document.getElementById('muteBtn').onclick = () => {

muted = !muted;

document.getElementById('muteBtn').textContent = muted ? '🔇' : '🔊';

};

document.getElementById('langBtn').onclick = () => {

lang = lang === 'zh' ? 'en' : 'zh';

document.getElementById('titleText').textContent = lang === 'zh' ? ZH_TITLE : EN_TITLE;

document.getElementById('resetBtn').textContent = t('重开', 'Reset');

if (window.onLangChange) window.onLangChange();

};

const SUITS = ['♠','♥','♦','♣'];

const RANKS = ['A','2','3','4','5','6','7','8','9','10','J','Q','K'];

function makeDeck() {

const d = [];

for (const s of SUITS) for (let i=0;i<RANKS.length;i++) d.push({s, r:RANKS[i], v:i+1, red:s==='♥'||s==='♦'});

for (let i=d.length-1;i>0;i--) { const j=Math.floor(Math.random()*(i+1)); [d[i],d[j]]=[d[j],d[i]]; }

return d;

}

function cardHTML(c, hidden=false) {

if (hidden) return `<div class="pcard back"><span>♛</span></div>`;

return `<div class="pcard ${c.red?'red':''}"><b>${c.r}${c.s}</b><span>${c.s}</span></div>`;

}

const ENGINES = {

"neon-blackjack": function() {

let bank = 1000, bet = 50, deck = [], player = [], dealer = [], state = 'bet';

setScore(bank);

function handVal(h) {

let sum = 0, aces = 0;

for (const c of h) { if (c.v===1) { aces++; sum+=11; } else sum += Math.min(10, c.v); }

while (sum > 21 && aces > 0) { sum -= 10; aces--; }

return sum;

}

function render() {

const pv = handVal(player), dv = state==='play' ? handVal(dealer.slice(0,1)) : handVal(dealer);

stageEl.innerHTML = `

<div class="table-felt">

<div class="hand-zone">

<div class="zone-label">${t('荷官手牌','Dealer')} (${state==='play' ? dv+'+?' : dv})</div>

<div class="cards-row">${dealer.map((c,i)=>cardHTML(c, state==='play' && i===1)).join('')}</div>

</div>

<div class="hand-zone">

<div class="zone-label">${t('你的手牌','Your Hand')} (${pv}) · ${t('底池','Bet')} $${bet}</div>

<div class="cards-row">${player.map(c=>cardHTML(c)).join('')}</div>

</div>

<div class="btn-bar">

${state==='play' ? `

<button class="gbtn primary" id="bjHit">${t('要牌 Hit','Hit')}</button>

<button class="gbtn" id="bjStand">${t('停牌 Stand','Stand')}</button>

${player.length===2 && bank>=bet ? `<button class="gbtn accent" id="bjDouble">${t('双倍 Double','Double')}</button>` : ''}

` : `

<button class="gbtn" data-chip="25">+$25</button>

<button class="gbtn" data-chip="100">+$100</button>

<button class="gbtn" id="bjClear">${t('重置注额','Min Bet')}</button>

<button class="gbtn primary" id="bjDeal">${t('发牌 Deal','Deal Hand')}</button>

`}

</div>

</div>`;

if (state==='play') {

document.getElementById('bjHit').onclick = () => {

sfx(520,0.06); player.push(deck.pop());

if (handVal(player) > 21) endRound(-bet, t('爆牌!庄家胜','Bust! Dealer wins'));

else render();

};

document.getElementById('bjStand').onclick = stand;

const db = document.getElementById('bjDouble');

if (db) db.onclick = () => { bank -= bet; bet *= 2; player.push(deck.pop()); if (handVal(player)>21) endRound(0, t('双倍爆牌!','Double Bust!'), true); else stand(true); };

} else {

stageEl.querySelectorAll('[data-chip]').forEach(b => b.onclick = () => { sfx(600,0.05); bet = Math.min(bank, bet + Number(b.dataset.chip)); render(); });

document.getElementById('bjClear').onclick = () => { bet = Math.min(bank, 50); render(); };

document.getElementById('bjDeal').onclick = deal;

}

}

function deal() {

if (bank < 10) bank = 500;

bet = Math.min(bank, Math.max(25, bet));

bank -= bet;

deck = makeDeck();

player = [deck.pop(), deck.pop()];

dealer = [deck.pop(), deck.pop()];

state = 'play'; sfx(440,0.08);

msg('请选择要牌或停牌', 'Choose Hit, Stand, or Double Down');

if (handVal(player) === 21) stand(true);

else render();

}

function stand(doubled=false) {

state = 'done';

while (handVal(dealer) < 17) dealer.push(deck.pop());

const pv = handVal(player), dv = handVal(dealer);

if (pv === 21 && player.length === 2 && dv !== 21) { bank += Math.round(bet * 2.5); chordWin(); msg('Blackjack 21点!赢得 1.5 倍彩金','Blackjack! 3:2 Payout!'); }

else if (dv > 21 || pv > dv) { bank += bet * 2; chordWin(); msg(`你赢了!(${pv} vs ${dv})`, `You win! (${pv} vs ${dv})`); }

else if (pv === dv) { bank += bet; sfx(400,0.1); msg(`平局 Push (${pv})`, `Push tie (${pv})`); }

else { chordLose(); msg(`庄家胜 (${dv} vs ${pv})`, `Dealer wins (${dv} vs ${pv})`); }

if (doubled) bet = Math.max(25, Math.floor(bet/2));

setScore(bank); render();

}

function endRound(delta, text, alreadyDeducted=false) {

state = 'done'; chordLose(); setScore(bank); msg(text, text); render();

}

deal();

},

"velvet-roulette": function() {

const REDS = new Set([1,3,5,7,9,12,14,16,18,19,21,23,25,27,30,32,34,36]);

let bank = 1000, betType = 'red', betNum = 7, stake = 50, lastNum = null, spinning = false;

setScore(bank);

function render() {

const col = lastNum===null ? '#888' : lastNum===0 ? '#10b981' : REDS.has(lastNum) ? '#e11d48' : '#1e293b';

stageEl.innerHTML = `

<div class="table-felt">

<div style="display:flex;align-items:center;justify-content:center;gap:22px;flex-wrap:wrap;">

<div id="wheelPocket" style="width:118px;height:118px;border-radius:50%;border:5px solid var(--accent);display:grid;place-items:center;font-size:38px;font-weight:900;background:${col};box-shadow:0 0 28px rgba(245,166,35,.35);">

${lastNum === null ? '◎' : lastNum}

</div>

<div style="text-align:left;max-width:260px;">

<div style="font-size:14px;color:var(--muted);">${t('当前筹码','Bankroll')}: <b style="color:#fff">$${bank}</b> · ${t('单注','Stake')} $${stake}</div>

<div style="margin-top:6px;font-size:15px;">${t('押注项','Selected Bet')}: <b style="color:var(--accent2)">${betType.toUpperCase()}${betType==='number'?' #'+betNum:''}</b></div>

</div>

</div>

<div class="btn-bar" style="margin-top:8px;">

<button class="gbtn ${betType==='red'?'primary':''}" data-bt="red">${t('红 Red (1:1)','Red (1:1)')}</button>

<button class="gbtn ${betType==='black'?'primary':''}" data-bt="black">${t('黑 Black (1:1)','Black (1:1)')}</button>

<button class="gbtn ${betType==='odd'?'primary':''}" data-bt="odd">${t('奇数 Odd (1:1)','Odd (1:1)')}</button>

<button class="gbtn ${betType==='even'?'primary':''}" data-bt="even">${t('偶数 Even (1:1)','Even (1:1)')}</button>

<button class="gbtn ${betType==='1st12'?'primary':''}" data-bt="1st12">1–12 (2:1)</button>

<button class="gbtn ${betType==='2nd12'?'primary':''}" data-bt="2nd12">13–24 (2:1)</button>

<button class="gbtn ${betType==='3rd12'?'primary':''}" data-bt="3rd12">25–36 (2:1)</button>

<button class="gbtn ${betType==='number'?'primary':''}" data-bt="number">${t('幸运号','Lucky')} #${betNum} (35:1)</button>

</div>

<div class="btn-bar">

<button class="gbtn" id="rlPick">${t('换号 (0-36)','Pick # (0-36)')}</button>

<button class="gbtn" id="rlStake">${t('切换注额','Bet')} $${stake}</button>

<button class="gbtn accent" id="rlSpin" ${spinning?'disabled':''}>${t('转动轮盘 Spin','Spin Wheel')}</button>

</div>

</div>`;

stageEl.querySelectorAll('[data-bt]').forEach(b => b.onclick = () => { sfx(500,0.05); betType = b.dataset.bt; render(); });

document.getElementById('rlPick').onclick = () => { betNum = (betNum + 1) % 37; betType = 'number'; sfx(580,0.05); render(); };

document.getElementById('rlStake').onclick = () => { stake = stake===50 ? 100 : stake===100 ? 250 : 50; render(); };

document.getElementById('rlSpin').onclick = spin;

}

function spin() {

if (spinning) return;

if (bank < stake) bank = 500;

bank -= stake; spinning = true;

let ticks = 18;

const timer = setInterval(() => {

lastNum = Math.floor(Math.random() * 37);

sfx(320 + ticks*14, 0.04);

render();

if (--ticks <= 0) {

clearInterval(timer); spinning = false;

let mult = 0;

if (betType==='red' && REDS.has(lastNum)) mult = 2;

else if (betType==='black' && lastNum>0 && !REDS.has(lastNum)) mult = 2;

else if (betType==='odd' && lastNum>0 && lastNum%2===1) mult = 2;

else if (betType==='even' && lastNum>0 && lastNum%2===0) mult = 2;

else if (betType==='1st12' && lastNum>=1 && lastNum<=12) mult = 3;

else if (betType==='2nd12' && lastNum>=13 && lastNum<=24) mult = 3;

else if (betType==='3rd12' && lastNum>=25 && lastNum<=36) mult = 3;

else if (betType==='number' && lastNum===betNum) mult = 36;

if (mult > 0) {

const win = stake * mult; bank += win; chordWin();

msg(`开出 ${lastNum} 号!赢得 $${win}`, `Ball landed on ${lastNum}! Won $${win}!`);

} else {

chordLose();

msg(`开出 ${lastNum} 号,未命中`, `Ball landed on ${lastNum}. Try again!`);

}

setScore(bank); render();

}

}, 65);

}

msg('选择押注区域并转动轮盘', 'Select your bet and spin the wheel');

render();

},

"midnight-slots": function() {

const SYMS = ['🍒','🍋','🍇','🔔','💋','💎','7️⃣'];

const PAY = {'🍒':3,'🍋':4,'🍇':6,'🔔':10,'💋':15,'💎':25,'7️⃣':50};

let credits = 1000, bet = 25, freeSpins = 0, grid = [['7️⃣','💎','7️⃣'],['💋','7️⃣','💋'],['🍒','🔔','🍒']], spinning = false;

setScore(credits);

function render() {

stageEl.innerHTML = `

<div class="table-felt">

<div style="display:flex;gap:14px;justify-content:center;font-size:14px;color:var(--muted);">

<span>${t('筹码','Credits')}: <b style="color:#fff">$${credits}</b></span>

<span>${t('单线注','Bet')}: <b style="color:var(--accent2)">$${bet}</b></span>

${freeSpins>0 ? `<span style="color:#4ade80;font-weight:700;">🎁 Free Spins: ${freeSpins}</span>` : ''}

</div>

<div style="display:grid;grid-template-columns:repeat(3,86px);gap:10px;justify-content:center;margin:10px auto;padding:14px;background:rgba(0,0,0,.45);border-radius:18px;border:2px solid var(--accent);">

${grid.flat().map(s => `<div style="height:72px;display:grid;place-items:center;font-size:38px;background:rgba(255,255,255,.06);border-radius:12px;">${s}</div>`).join('')}

</div>

<div class="btn-bar">

<button class="gbtn" id="slBet">${t('切换注额','Bet')} $${bet}</button>

<button class="gbtn primary" id="slSpin" ${spinning?'disabled':''}>${freeSpins>0 ? t('免费旋转!','FREE SPIN!') : t('拉杆旋转 SPIN','SPIN REELS')}</button>

</div>

</div>`;

document.getElementById('slBet').onclick = () => { bet = bet===25 ? 50 : bet===50 ? 100 : 25; render(); };

document.getElementById('slSpin').onclick = spin;

}

function spin() {

if (spinning) return;

if (freeSpins > 0) freeSpins--;

else { if (credits < bet) credits = 500; credits -= bet; }

spinning = true;

let steps = 10;

const timer = setInterval(() => {

grid = Array.from({length:3}, () => Array.from({length:3}, () => SYMS[Math.floor(Math.random()*SYMS.length)]));

sfx(380 + steps*25, 0.04, 'triangle');

render();

if (--steps <= 0) {

clearInterval(timer); spinning = false;

const lines = [

[grid[0][0],grid[0][1],grid[0][2]],

[grid[1][0],grid[1][1],grid[1][2]],

[grid[2][0],grid[2][1],grid[2][2]],

[grid[0][0],grid[1][1],grid[2][2]],

[grid[2][0],grid[1][1],grid[0][2]],

];

let totalWin = 0;

for (const ln of lines) {

const nonWild = ln.filter(x => x !== '💎');

if (nonWild.length === 0) totalWin += bet * PAY['💎'];

else if (nonWild.every(x => x === nonWild[0])) totalWin += bet * PAY[nonWild[0]];

else if (ln[0] === ln[1] || ln[1] === ln[2]) totalWin += Math.round(bet * 0.8);

}

const kisses = grid.flat().filter(x => x === '💋').length;

if (kisses >= 3) { freeSpins += 3; totalWin += bet * 3; }

if (totalWin > 0) {

credits += totalWin; chordWin();

msg(`中奖!赢得 $${totalWin}${kisses>=3?' + 3次免费旋转':''}`, `JACKPOT! Won $${totalWin}${kisses>=3?' + 3 Free Spins':''}`);

} else {

msg('差一点点!再来一局', 'So close! Spin again');

}

setScore(credits); render();

}

}, 65);

}

msg('💎百搭任意图标,3个💋触发免费旋转!', '💎 is Wild! 3+ 💋 awards Free Spins!');

render();

},

"texas-holdem-duel": function() {

let chips = 1000, pot = 0, stage = 0, deck = [], my = [], ai = [], comm = [], over = false;

setScore(chips);

function rankScore(cards) {

const counts = {};

for (const c of cards) counts[c.v] = (counts[c.v]||0) + 1;

const pairs = Object.entries(counts).map(([v,c])=>({v:Number(v)===1?14:Number(v),c})).sort((a,b)=>b.c-a.c||b.v-a.v);

return pairs[0].c * 100 + pairs[0].v + (pairs[1]?.c||0)*15 + (pairs[1]?.v||0)*0.1;

}

function startHand() {

if (chips < 50) chips = 800;

deck = makeDeck(); my = [deck.pop(), deck.pop()]; ai = [deck.pop(), deck.pop()];

comm = []; stage = 0; over = false; chips -= 50; pot = 100;

setScore(chips); sfx(460,0.07);

msg('翻牌前 Pre-Flop:选择过牌跟注或加注', 'Pre-Flop: Check/Call or Raise');

render();

}

function advance(raiseAmt) {

if (raiseAmt > 0 && chips >= raiseAmt) { chips -= raiseAmt; pot += raiseAmt * 2; sfx(620,0.07); }

else sfx(440,0.06);

stage++;

if (stage === 1) comm.push(deck.pop(), deck.pop(), deck.pop());

else if (stage === 2 || stage === 3) comm.push(deck.pop());

else {

over = true;

const ms = rankScore([...my, ...comm]), as = rankScore([...ai, ...comm]);

if (ms >= as) { chips += pot; chordWin(); msg(`摊牌获胜!赢得底池 $${pot}`, `Showdown Win! Collected $${pot}`); }

else { chordLose(); msg(`对手牌力更强,失去底池 $${pot}`, `Opponent wins showdown ($${pot})`); }

}

setScore(chips); render();

}

function render() {

stageEl.innerHTML = `

<div class="table-felt">

<div class="hand-zone">

<div class="zone-label">${t('对手手牌','Rival')} · ${t('底池 Pot','Pot')} $${pot}</div>

<div class="cards-row">${ai.map(c=>cardHTML(c, !over)).join('')}</div>

</div>

<div class="hand-zone">

<div class="zone-label">${t('公共牌 Community','Community Cards')} (${comm.length}/5)</div>

<div class="cards-row">${comm.length ? comm.map(c=>cardHTML(c)).join('') : '<span style="color:var(--muted)">Waiting for Flop...</span>'}</div>

</div>

<div class="hand-zone">

<div class="zone-label">${t('你的底牌','Your Hole Cards')} · $${chips}</div>

<div class="cards-row">${my.map(c=>cardHTML(c)).join('')}</div>

</div>

<div class="btn-bar">

${!over ? `

<button class="gbtn" id="thCheck">${t('过牌/跟注 Check','Check / Call')}</button>

<button class="gbtn primary" id="thRaise">${t('加注 +$50','Raise +$50')}</button>

<button class="gbtn accent" id="thAll">${t('全下 All-In','All-In +$150')}</button>

` : `<button class="gbtn primary" id="thNext">${t('下一局 Next Hand','Deal Next Hand')}</button>`}

</div>

</div>`;

if (!over) {

document.getElementById('thCheck').onclick = () => advance(0);

document.getElementById('thRaise').onclick = () => advance(Math.min(chips, 50));

document.getElementById('thAll').onclick = () => advance(Math.min(chips, 150));

} else document.getElementById('thNext').onclick = startHand;

}

window.resetGame = startHand;

startHand();

},

"crystal-mahjong-solitaire": function() {

const ICONS = ['🀇','🀈','🀉','🀙','🀚','🀛','🀐','🀑','🀒','🀄','🀅','🀆'];

let tiles = [], sel = null;

function init() {

const pool = [];

for (let i=0;i<18;i++) { const sym = ICONS[i % ICONS.length]; pool.push(sym, sym); }

for (let i=pool.length-1;i>0;i--) { const j=Math.floor(Math.random()*(i+1)); [pool[i],pool[j]]=[pool[j],pool[i]]; }

tiles = [];

let idx = 0;

for (let r=0;r<4;r++) for (let c=0;c<6;c++) tiles.push({id:idx, r, c, z:0, sym:pool[idx++], gone:false});

for (let r=1;r<3;r++) for (let c=1;c<5;c++) tiles.push({id:idx, r, c, z:1, sym:pool[idx++], gone:false});

for (let r=1;r<3;r++) for (let c=2;c<4;c++) tiles.push({id:idx, r, c, z:2, sym:pool[idx++], gone:false});

sel = null;

msg('配对消除顶层或左右无遮挡的麻将牌', 'Match free uncovered Mahjong tiles');

render();

}

function isFree(tile) {

if (tile.gone) return false;

const onTop = tiles.some(o => !o.gone && o.z === tile.z + 1 && Math.abs(o.r - tile.r) <= 0 && Math.abs(o.c - tile.c) <= 0);

if (onTop) return false;

const leftBlocked = tiles.some(o => !o.gone && o.z === tile.z && o.r === tile.r && o.c === tile.c - 1);

const rightBlocked = tiles.some(o => !o.gone && o.z === tile.z && o.r === tile.r && o.c === tile.c + 1);

return !(leftBlocked && rightBlocked);

}

function shuffleRemaining() {

const rem = tiles.filter(x => !x.gone);

const syms = rem.map(x => x.sym).sort(() => Math.random() - 0.5);

rem.forEach((x, i) => x.sym = syms[i]);

sel = null; sfx(500,0.08); render();

}

function render() {

const rem = tiles.filter(x => !x.gone).length;

stageEl.innerHTML = `

<div class="table-felt">

<div style="position:relative;width:360px;height:260px;margin:0 auto;">

${tiles.filter(x=>!x.gone).sort((a,b)=>a.z-b.z).map(t => {

const free = isFree(t), picked = sel && sel.id === t.id;

const left = 18 + t.c * 54 - t.z * 5, top = 16 + t.r * 58 - t.z * 6;

return `<button data-tid="${t.id}" style="position:absolute;left:${left}px;top:${top}px;width:48px;height:54px;border-radius:10px;border:2px solid ${picked?'var(--accent2)':free?'rgba(255,255,255,.45)':'rgba(255,255,255,.12)'};background:${picked?'#3b1d5e':t.z===2?'#251b3d':t.z===1?'#1c1530':'#130e22'};font-size:28px;opacity:${free?1:0.52};box-shadow:0 ${4+t.z*3}px 12px rgba(0,0,0,.6);">${t.sym}</button>`;

}).join('')}

</div>

<div class="btn-bar">

<span style="color:var(--muted);font-size:13px;">${t('剩余牌数','Tiles Left')}: ${rem}</span>

<button class="gbtn" id="mjShuf">${t('洗牌重排 Shuffle','Shuffle Tiles')}</button>

</div>

</div>`;

document.getElementById('mjShuf').onclick = shuffleRemaining;

stageEl.querySelectorAll('[data-tid]').forEach(b => b.onclick = () => {

const tile = tiles.find(x => x.id === Number(b.dataset.tid));

if (!isFree(tile)) { sfx(220,0.06,'sawtooth'); return; }

if (!sel || sel.id === tile.id) { sel = tile; sfx(520,0.05); render(); return; }

if (sel.sym === tile.sym) {

sel.gone = tile.gone = true; sel = null; addScore(50); sfx(720,0.09);

if (tiles.every(x => x.gone)) { chordWin(); addScore(300); msg('恭喜清空全部水晶麻将!','All Mahjong tiles cleared!'); setTimeout(init, 1200); }

} else { sel = tile; sfx(300,0.07); }

render();

});

}

window.resetGame = init;

init();

},

"silk-memory-match": function() {

const EMOJIS = ['💋','🍷','🌹','💎','🎭','🔥','🌙','🦋','🍒','👑'];

let level = 1, cards = [], open = [], streak = 0, lock = false;

function init() {

const pairs = Math.min(10, 5 + level);

const deck = EMOJIS.slice(0, pairs).flatMap((s, i) => [{id:i*2, s, done:false}, {id:i*2+1, s, done:false}]);

deck.sort(() => Math.random() - 0.5);

cards = deck; open = []; streak = 0; lock = false;

msg(`第 ${level} 关:翻开两张相同图案配对`, `Stage ${level}: Match all pairs`);

render();

}

function render() {

const cols = cards.length <= 12 ? 4 : 5;

stageEl.innerHTML = `

<div class="table-felt">

<div style="display:grid;grid-template-columns:repeat(${cols},64px);gap:10px;justify-content:center;margin:8px auto;">

${cards.map((c, idx) => {

const show = c.done || open.includes(idx);

return `<button data-idx="${idx}" style="height:68px;border-radius:12px;border:2px solid ${c.done?'var(--accent2)':show?'var(--accent)':'rgba(255,255,255,.18)'};background:${show?'rgba(236,72,153,.22)':'rgba(255,255,255,.05)'};font-size:28px;">${show ? c.s : '✦'}</button>`;

}).join('')}

</div>

</div>`;

stageEl.querySelectorAll('[data-idx]').forEach(b => b.onclick = () => {

if (lock) return;

const idx = Number(b.dataset.idx);

if (cards[idx].done || open.includes(idx)) return;

open.push(idx); sfx(540,0.05); render();

if (open.length === 2) {

lock = true;

const [a, bIdx] = open;

if (cards[a].s === cards[bIdx].s) {

cards[a].done = cards[bIdx].done = true; open = []; lock = false; streak++;

addScore(30 * streak); sfx(760,0.09);

if (cards.every(c => c.done)) { chordWin(); level++; setTimeout(init, 700); }

else render();

} else {

streak = 0;

setTimeout(() => { open = []; lock = false; render(); }, 550);

}

}

});

}

window.resetGame = () => { level = 1; setScore(0); init(); };

init();

},

"neon-brick-breaker": function() {

stageEl.innerHTML = `<canvas id="cv" width="480" height="340" style="width:100%;max-width:480px;background:#090612;border-radius:14px;border:1px solid rgba(255,255,255,.15);touch-action:none;"></canvas>`;

const cv = document.getElementById('cv'), ctx = cv.getContext('2d');

let px = 190, pw = 92, bx = 240, by = 290, vx = 3.4, vy = -3.6, bricks = [], anim;

function resetBricks() {

bricks = [];

const colors = ['#f43f5e','#ec4899','#a855f7','#06b6d4','#eab308'];

for (let r=0;r<5;r++) for (let c=0;c<8;c++) bricks.push({x:18+c*56, y:28+r*24, w:48, h:16, hp:r===0?2:1, col:colors[r]});

bx = 240; by = 290; vx = 3.4 * (Math.random()>.5?1:-1); vy = -3.6;

}

resetBricks();

msg('左右移动鼠标或手指反弹光球击碎砖块', 'Move mouse or touch to bounce the ball');

const move = clientX => {

const rect = cv.getBoundingClientRect();

px = Math.max(8, Math.min(cv.width - pw - 8, ((clientX - rect.left) / rect.width) * cv.width - pw/2));

};

cv.onmousemove = e => move(e.clientX);

cv.ontouchmove = e => { e.preventDefault(); move(e.touches[0].clientX); };

function loop() {

ctx.clearRect(0,0,cv.width,cv.height);

bx += vx; by += vy;

if (bx < 8 || bx > cv.width - 8) { vx = -vx; sfx(420,0.03); }

if (by < 8) { vy = -vy; sfx(460,0.03); }

if (by > 306 && by < 322 && bx >= px && bx <= px + pw && vy > 0) {

vy = -Math.abs(vy); vx = ((bx - (px + pw/2)) / (pw/2)) * 4.5; sfx(580,0.05);

}

if (by > cv.height) { bx = px + pw/2; by = 295; vy = -3.6; chordLose(); }

for (const b of bricks) {

if (b.hp <= 0) continue;

if (bx > b.x && bx < b.x + b.w && by > b.y && by < b.y + b.h) {

b.hp--; vy = -vy; addScore(25); sfx(680,0.04); break;

}

}

if (bricks.every(b => b.hp <= 0)) { chordWin(); addScore(200); resetBricks(); }

for (const b of bricks) {

if (b.hp <= 0) continue;

ctx.fillStyle = b.col; ctx.beginPath(); ctx.roundRect(b.x, b.y, b.w, b.h, 5); ctx.fill();

}

ctx.fillStyle = '#22d3ee'; ctx.beginPath(); ctx.roundRect(px, 312, pw, 12, 6); ctx.fill();

ctx.fillStyle = '#fff'; ctx.beginPath(); ctx.arc(bx, by, 7, 0, Math.PI*2); ctx.fill();

anim = requestAnimationFrame(loop);

}

window.resetGame = () => { cancelAnimationFrame(anim); setScore(0); resetBricks(); loop(); };

loop();

},

"velvet-bubble-shooter": function() {

stageEl.innerHTML = `<canvas id="cv" width="420" height="350" style="width:100%;max-width:420px;background:#0b0714;border-radius:14px;border:1px solid rgba(255,255,255,.15);cursor:crosshair;"></canvas>`;

const cv = document.getElementById('cv'), ctx = cv.getContext('2d');

const COLS = ['#f43f5e','#38bdf8','#facc15','#4ade80','#c084fc'];

const R = 16, ROWS = 9, C = 11;

let grid = [], angle = -Math.PI/2, shot = null, curCol = COLS[0];

function init() {

grid = Array.from({length:ROWS}, (_, r) => Array.from({length:C}, () => r < 4 ? COLS[Math.floor(Math.random()*COLS.length)] : null));

curCol = COLS[Math.floor(Math.random()*COLS.length)]; shot = null;

msg('点击画布朝目标方向发射同色泡泡(3个以上消除)', 'Click or tap to shoot bubbles (3+ same color pops)');

}

function cellPos(r, c) { return { x: 26 + c * 34 + (r%2)*17, y: 24 + r * 30 }; }

function neighbors(r, c) {

const odd = r % 2 === 1;

const d = [[0,-1],[0,1],[-1,odd?0:-1],[-1,odd?1:0],[1,odd?0:-1],[1,odd?1:0]];

return d.map(([dr,dc])=>[r+dr,c+dc]).filter(([nr,nc])=>nr>=0&&nr<ROWS&&nc>=0&&nc<C);

}

function popCluster(sr, sc, col) {

const q = [[sr,sc]], seen = new Set([sr+','+sc]), cluster = [[sr,sc]];

while (q.length) {

const [r,c] = q.shift();

for (const [nr,nc] of neighbors(r,c)) {

const k = nr+','+nc;

if (!seen.has(k) && grid[nr][nc] === col) { seen.add(k); q.push([nr,nc]); cluster.push([nr,nc]); }

}

}

if (cluster.length >= 3) {

cluster.forEach(([r,c]) => grid[r][c] = null);

addScore(cluster.length * 30); sfx(740,0.08);

if (grid.flat().every(x => !x)) { chordWin(); addScore(300); init(); }

}

}

cv.onmousemove = e => {

const rect = cv.getBoundingClientRect();

const x = ((e.clientX - rect.left)/rect.width)*cv.width, y = ((e.clientY - rect.top)/rect.height)*cv.height;

angle = Math.max(-2.85, Math.min(-0.3, Math.atan2(y - 325, x - 210)));

};

cv.onclick = e => {

cv.onmousemove(e);

if (!shot) { shot = {x:210, y:325, vx:Math.cos(angle)*8, vy:Math.sin(angle)*8, col:curCol}; curCol = COLS[Math.floor(Math.random()*COLS.length)]; sfx(500,0.05); }

};

function loop() {

ctx.clearRect(0,0,cv.width,cv.height);

if (shot) {

shot.x += shot.vx; shot.y += shot.vy;

if (shot.x < R || shot.x > cv.width - R) shot.vx = -shot.vx;

let hit = shot.y < 26;

for (let r=0;r<ROWS && !hit;r++) for (let c=0;c<C;c++) if (grid[r][c]) {

const p = cellPos(r,c);

if (Math.hypot(shot.x - p.x, shot.y - p.y) < R * 1.85) { hit = true; break; }

}

if (hit) {

let best = null, bestD = 1e9;

for (let r=0;r<ROWS;r++) for (let c=0;c<C;c++) if (!grid[r][c]) {

const p = cellPos(r,c), d = Math.hypot(shot.x - p.x, shot.y - p.y);

if (d < bestD) { bestD = d; best = [r,c]; }

}

if (best) { grid[best[0]][best[1]] = shot.col; popCluster(best[0], best[1], shot.col); }

shot = null;

}

}

for (let r=0;r<ROWS;r++) for (let c=0;c<C;c++) if (grid[r][c]) {

const p = cellPos(r,c);

ctx.fillStyle = grid[r][c]; ctx.beginPath(); ctx.arc(p.x, p.y, R-1, 0, Math.PI*2); ctx.fill();

}

ctx.strokeStyle = 'rgba(255,255,255,0.35)'; ctx.setLineDash([5,5]);

ctx.beginPath(); ctx.moveTo(210,325); ctx.lineTo(210+Math.cos(angle)*90, 325+Math.sin(angle)*90); ctx.stroke(); ctx.setLineDash([]);

ctx.fillStyle = shot ? shot.col : curCol;

ctx.beginPath(); ctx.arc(shot ? shot.x : 210, shot ? shot.y : 325, R, 0, Math.PI*2); ctx.fill();

requestAnimationFrame(loop);

}

window.resetGame = init;

init(); loop();

},

"gem-crush-fever": function() {

const GEMS = ['💎','🔮','❤️','⚡','🍀','🌙'];

let N = 7, board = [], sel = null;

function init() {

board = Array.from({length:N}, () => Array.from({length:N}, () => GEMS[Math.floor(Math.random()*GEMS.length)]));

resolveMatches(false);

msg('点击相邻两颗宝石交换位置,凑成3颗以上连线消除', 'Tap two adjacent gems to swap and match 3+');

render();

}

function resolveMatches(scoreIt=true) {

let any = false;

for (let loop=0;loop<8;loop++) {

const mark = Array.from({length:N}, () => Array(N).fill(false));

for (let r=0;r<N;r++) for (let c=0;c<N-2;c++) if (board[r][c] && board[r][c]===board[r][c+1] && board[r][c]===board[r][c+2]) mark[r][c]=mark[r][c+1]=mark[r][c+2]=true;

for (let c=0;c<N;c++) for (let r=0;r<N-2;r++) if (board[r][c] && board[r][c]===board[r+1][c] && board[r][c]===board[r+2][c]) mark[r][c]=mark[r+1][c]=mark[r+2][c]=true;

let cnt = 0;

for (let r=0;r<N;r++) for (let c=0;c<N;c++) if (mark[r][c]) { board[r][c] = null; cnt++; }

if (!cnt) break;

any = true;

if (scoreIt) addScore(cnt * 20);

for (let c=0;c<N;c++) {

const col = [];

for (let r=N-1;r>=0;r--) if (board[r][c]) col.push(board[r][c]);

while (col.length < N) col.push(GEMS[Math.floor(Math.random()*GEMS.length)]);

for (let r=N-1,i=0;r>=0;r--,i++) board[r][c] = col[i];

}

}

return any;

}

function render() {

stageEl.innerHTML = `

<div class="table-felt">

<div style="display:grid;grid-template-columns:repeat(${N},44px);gap:6px;justify-content:center;">

${board.map((row, r) => row.map((g, c) => {

const picked = sel && sel[0]===r && sel[1]===c;

return `<button data-rc="${r},${c}" style="height:44px;border-radius:10px;border:2px solid ${picked?'var(--accent2)':'rgba(255,255,255,.14)'};background:${picked?'rgba(168,85,247,.35)':'rgba(255,255,255,.05)'};font-size:22px;">${g}</button>`;

}).join('')).join('')}

</div>

</div>`;

stageEl.querySelectorAll('[data-rc]').forEach(b => b.onclick = () => {

const [r,c] = b.dataset.rc.split(',').map(Number);

if (!sel) { sel = [r,c]; sfx(500,0.04); render(); return; }

const [sr,sc] = sel; sel = null;

if (Math.abs(sr-r) + Math.abs(sc-c) === 1) {

[board[sr][sc], board[r][c]] = [board[r][c], board[sr][sc]];

if (resolveMatches(true)) chordWin();

else { [board[sr][sc], board[r][c]] = [board[r][c], board[sr][sc]]; sfx(240,0.07,'sawtooth'); }

}

render();

});

}

window.resetGame = init;

init();

},

"cyber-block-drop": function() {

stageEl.innerHTML = `

<div style="display:flex;gap:14px;align-items:center;justify-content:center;flex-wrap:wrap;">

<canvas id="cv" width="220" height="352" style="background:#080510;border-radius:12px;border:2px solid var(--accent);"></canvas>

<div style="display:grid;grid-template-columns:repeat(2,78px);gap:8px;">

<button class="gbtn" id="tbL">◀</button>

<button class="gbtn" id="tbR">▶</button>

<button class="gbtn primary" id="tbRot">↻ ${t('旋转','Rot')}</button>

<button class="gbtn accent" id="tbDrop">⏬ ${t('落底','Drop')}</button>

</div>

</div>`;

const cv = document.getElementById('cv'), ctx = cv.getContext('2d');

const W = 10, H = 16, S = 22;

const SHAPES = [

{m:[[1,1,1,1]], c:'#22d3ee'},

{m:[[1,1],[1,1]], c:'#facc15'},

{m:[[0,1,0],[1,1,1]], c:'#c084fc'},

{m:[[1,0,0],[1,1,1]], c:'#fb923c'},

{m:[[0,1,1],[1,1,0]], c:'#4ade80'},

{m:[[1,1,0],[0,1,1]], c:'#f43f5e'},

];

let grid = Array.from({length:H}, () => Array(W).fill(null)), cur, timer;

function spawn() {

const p = SHAPES[Math.floor(Math.random()*SHAPES.length)];

cur = {m:p.m.map(r=>[...r]), c:p.c, x:3, y:0};

if (collide(cur.x, cur.y, cur.m)) { grid = Array.from({length:H}, () => Array(W).fill(null)); chordLose(); }

}

function collide(nx, ny, mat) {

for (let r=0;r<mat.length;r++) for (let c=0;c<mat[0].length;c++) if (mat[r][c]) {

const x = nx + c, y = ny + r;

if (x < 0 || x >= W || y >= H || (y >= 0 && grid[y][x])) return true;

}

return false;

}

function lock() {

for (let r=0;r<cur.m.length;r++) for (let c=0;c<cur.m[0].length;c++) if (cur.m[r][c] && cur.y+r>=0) grid[cur.y+r][cur.x+c] = cur.c;

let cleared = 0;

for (let r=H-1;r>=0;r--) if (grid[r].every(Boolean)) { grid.splice(r,1); grid.unshift(Array(W).fill(null)); cleared++; r++; }

if (cleared) { addScore(cleared * cleared * 100); chordWin(); }

spawn();

}

function draw() {

ctx.clearRect(0,0,cv.width,cv.height);

for (let r=0;r<H;r++) for (let c=0;c<W;c++) if (grid[r][c]) { ctx.fillStyle = grid[r][c]; ctx.fillRect(c*S+1,r*S+1,S-2,S-2); }

for (let r=0;r<cur.m.length;r++) for (let c=0;c<cur.m[0].length;c++) if (cur.m[r][c]) { ctx.fillStyle = cur.c; ctx.fillRect((cur.x+c)*S+1,(cur.y+r)*S+1,S-2,S-2); }

}

function step() { if (!collide(cur.x, cur.y+1, cur.m)) cur.y++; else lock(); draw(); }

const rot = () => { const m = cur.m[0].map((_,i)=>cur.m.map(r=>r[i]).reverse()); if (!collide(cur.x,cur.y,m)) { cur.m=m; sfx(540,0.04); draw(); } };

document.getElementById('tbL').onclick = () => { if (!collide(cur.x-1,cur.y,cur.m)) { cur.x--; sfx(420,0.03); draw(); } };

document.getElementById('tbR').onclick = () => { if (!collide(cur.x+1,cur.y,cur.m)) { cur.x++; sfx(420,0.03); draw(); } };

document.getElementById('tbRot').onclick = rot;

document.getElementById('tbDrop').onclick = () => { while (!collide(cur.x,cur.y+1,cur.m)) cur.y++; lock(); sfx(640,0.06); draw(); };

window.onkeydown = e => {

if (e.key==='ArrowLeft') document.getElementById('tbL').click();

if (e.key==='ArrowRight') document.getElementById('tbR').click();

if (e.key==='ArrowUp') rot();

if (e.key==='ArrowDown') step();

if (e.key===' ') document.getElementById('tbDrop').click();

};

msg('方向键或右侧按钮控制方块旋转与落底', 'Use Arrow keys or buttons to rotate and drop blocks');

spawn(); draw();

timer = setInterval(step, 520);

window.resetGame = () => { clearInterval(timer); grid = Array.from({length:H}, () => Array(W).fill(null)); spawn(); draw(); timer = setInterval(step, 520); };

},

"serpent-neon-run": function() {

stageEl.innerHTML = `

<div style="display:flex;gap:14px;align-items:center;justify-content:center;flex-wrap:wrap;">

<canvas id="cv" width="320" height="320" style="background:#070b12;border-radius:14px;border:2px solid var(--accent);"></canvas>

<div style="display:grid;grid-template-columns:repeat(3,48px);gap:6px;">

<div></div><button class="gbtn" data-d="0,-1">▲</button><div></div>

<button class="gbtn" data-d="-1,0">◀</button><button class="gbtn" data-d="0,1">▼</button><button class="gbtn" data-d="1,0">▶</button>

</div>

</div>`;

const cv = document.getElementById('cv'), ctx = cv.getContext('2d');

const N = 16, S = 20;

let snake = [{x:8,y:8},{x:7,y:8},{x:6,y:8}], dir = {x:1,y:0}, food = {x:12,y:8}, timer;

function placeFood() { food = {x:Math.floor(Math.random()*N), y:Math.floor(Math.random()*N)}; }

function step() {

const head = {x:(snake[0].x + dir.x + N)%N, y:(snake[0].y + dir.y + N)%N};

if (snake.some(p => p.x===head.x && p.y===head.y)) { chordLose(); snake = [{x:8,y:8},{x:7,y:8},{x:6,y:8}]; dir={x:1,y:0}; return; }

snake.unshift(head);

if (head.x===food.x && head.y===food.y) { addScore(35); sfx(700,0.06); placeFood(); }

else snake.pop();

ctx.clearRect(0,0,cv.width,cv.height);

ctx.fillStyle = '#f43f5e'; ctx.beginPath(); ctx.arc(food.x*S+10, food.y*S+10, 8, 0, Math.PI*2); ctx.fill();

snake.forEach((p, i) => { ctx.fillStyle = i===0 ? '#4ade80' : '#22d3ee'; ctx.fillRect(p.x*S+2, p.y*S+2, S-4, S-4); });

}

stageEl.querySelectorAll('[data-d]').forEach(b => b.onclick = () => {

const [dx,dy] = b.dataset.d.split(',').map(Number);

if (dx !== -dir.x || dy !== -dir.y) dir = {x:dx,y:dy};

});

window.onkeydown = e => {

const m = {ArrowUp:[0,-1],ArrowDown:[0,1],ArrowLeft:[-1,0],ArrowRight:[1,0]}[e.key];

if (m && (m[0]!==-dir.x || m[1]!==-dir.y)) dir = {x:m[0],y:m[1]};

};

msg('吃掉霓虹果实不断变长,避免撞到自身', 'Eat glowing orbs to grow without hitting your tail');

timer = setInterval(step, 135);

window.resetGame = () => { clearInterval(timer); snake = [{x:8,y:8},{x:7,y:8},{x:6,y:8}]; setScore(0); timer = setInterval(step, 135); };

},

"flappy-cupid-wings": function() {

stageEl.innerHTML = `<canvas id="cv" width="440" height="320" style="width:100%;max-width:440px;background:linear-gradient(#120824,#261038);border-radius:14px;border:1px solid rgba(255,255,255,.18);cursor:pointer;"></canvas>`;

const cv = document.getElementById('cv'), ctx = cv.getContext('2d');

let y = 150, vy = 0, pipes = [{x:420, gapY:140, passed:false}], anim;

const flap = () => { vy = -5.2; sfx(560,0.05); };

cv.onclick = flap;

window.onkeydown = e => { if (e.key === ' ') flap(); };

msg('点击画面或按空格键扇动翅膀穿越光柱', 'Tap screen or press Space to flap through neon pillars');

function loop() {

ctx.clearRect(0,0,cv.width,cv.height);

vy += 0.26; y += vy;

if (pipes[pipes.length-1].x < 240) pipes.push({x:450, gapY:65 + Math.random()*170, passed:false});

for (const p of pipes) {

p.x -= 2.5;

ctx.fillStyle = '#fb7185';

ctx.fillRect(p.x, 0, 42, p.gapY - 52);

ctx.fillRect(p.x, p.gapY + 52, 42, cv.height);

if (!p.passed && p.x + 42 < 90) { p.passed = true; addScore(25); sfx(740,0.06); }

if (90 > p.x - 10 && 90 < p.x + 52 && (y < p.gapY - 44 || y > p.gapY + 44)) { y = 150; vy = 0; pipes = [{x:420, gapY:150, passed:false}]; chordLose(); }

}

if (y < 0 || y > cv.height) { y = 150; vy = 0; chordLose(); }

pipes = pipes.filter(p => p.x > -60);

ctx.font = '26px serif'; ctx.fillText('💘', 76, y + 8);

anim = requestAnimationFrame(loop);

}

window.resetGame = () => { cancelAnimationFrame(anim); y = 150; vy = 0; pipes = [{x:420, gapY:140, passed:false}]; loop(); };

loop();

},

"minesweeper-noir": function() {

const N = 9, MINES = 10;

let grid = [], flagMode = false, first = true, over = false;

function init() {

grid = Array.from({length:N}, (_,r) => Array.from({length:N}, (_,c) => ({r,c,m:false,open:false,flag:false,n:0})));

first = true; over = false;

msg('点击翻开安全格,切换插旗标记地雷', 'Reveal safe tiles or toggle Flag mode to mark mines');

render();

}

function plant(sr, sc) {

let left = MINES;

while (left > 0) {

const r = Math.floor(Math.random()*N), c = Math.floor(Math.random()*N);

if (!grid[r][c].m && (Math.abs(r-sr)>1 || Math.abs(c-sc)>1)) { grid[r][c].m = true; left--; }

}

for (let r=0;r<N;r++) for (let c=0;c<N;c++) {

let cnt = 0;

for (let dr=-1;dr<=1;dr++) for (let dc=-1;dc<=1;dc++) if (grid[r+dr]?.[c+dc]?.m) cnt++;

grid[r][c].n = cnt;

}

}

function flood(r, c) {

const cell = grid[r]?.[c];

if (!cell || cell.open || cell.flag) return;

cell.open = true; addScore(10);

if (cell.n === 0 && !cell.m) for (let dr=-1;dr<=1;dr++) for (let dc=-1;dc<=1;dc++) flood(r+dr, c+dc);

}

function render() {

stageEl.innerHTML = `

<div class="table-felt">

<div class="btn-bar" style="margin-bottom:8px;">

<button class="gbtn ${flagMode?'primary':''}" id="msFlag">${flagMode ? t('🚩 插旗模式 ON','🚩 Flag Mode ON') : t('🔍 翻开模式 Dig','🔍 Dig Mode')}</button>

</div>

<div style="display:grid;grid-template-columns:repeat(${N},34px);gap:4px;justify-content:center;">

${grid.flat().map(cell => `<button data-rc="${cell.r},${cell.c}" style="height:34px;border-radius:7px;border:1px solid rgba(255,255,255,.15);background:${cell.open?'rgba(255,255,255,.12)':'#1a1326'};font-weight:700;color:${cell.m?'#f43f5e':'#38bdf8'};">${cell.open ? (cell.m ? '💣' : (cell.n || '')) : (cell.flag ? '🚩' : '')}</button>`).join('')}

</div>

</div>`;

document.getElementById('msFlag').onclick = () => { flagMode = !flagMode; render(); };

stageEl.querySelectorAll('[data-rc]').forEach(b => {

b.oncontextmenu = e => { e.preventDefault(); const [r,c]=b.dataset.rc.split(',').map(Number); grid[r][c].flag=!grid[r][c].flag; render(); };

b.onclick = () => {

if (over) return;

const [r,c] = b.dataset.rc.split(',').map(Number);

const cell = grid[r][c];

if (flagMode) { if (!cell.open) cell.flag = !cell.flag; sfx(500,0.04); render(); return; }

if (cell.flag) return;

if (first) { first = false; plant(r,c); }

if (cell.m) { over = true; grid.flat().forEach(x => { if (x.m) x.open = true; }); chordLose(); msg('踩中地雷!点击重开再来一局','BOOM! Hit a mine!'); }

else {

flood(r,c); sfx(560,0.04);

if (grid.flat().filter(x => !x.m).every(x => x.open)) { over = true; chordWin(); addScore(200); msg('完美排雷通关!','All mines cleared!'); }

}

render();

};

});

}

window.resetGame = init;

init();

},

"sudoku-after-dark": function() {

const SOL = [

[5,3,4,6,7,8,9,1,2],[6,7,2,1,9,5,3,4,8],[1,9,8,3,4,2,5,6,7],

[8,5,9,7,6,1,4,2,3],[4,2,6,8,5,3,7,9,1],[7,1,3,9,2,4,8,5,6],

[9,6,1,5,3,7,2,8,4],[2,8,7,4,1,9,6,3,5],[3,4,5,2,8,6,1,7,9]

];

let board = [], fixed = [], sel = [0,0];

function init() {

const shift = Math.floor(Math.random()*9);

board = SOL.map(r => r.map(v => ((v + shift - 1) % 9) + 1));

fixed = board.map(r => r.map(() => Math.random() < 0.56));

for (let r=0;r<9;r++) for (let c=0;c<9;c++) if (!fixed[r][c]) board[r][c] = 0;

msg('选中空格填入1-9,使每行每列每宫不重复', 'Select a cell and enter 1-9 without repeats');

render();

}

function render() {

stageEl.innerHTML = `

<div class="table-felt">

<div style="display:grid;grid-template-columns:repeat(9,33px);gap:2px;justify-content:center;margin-bottom:10px;">

${board.map((row,r) => row.map((v,c) => {

const isSel = sel[0]===r && sel[1]===c;

return `<button data-rc="${r},${c}" style="height:33px;border-radius:5px;border:1px solid ${isSel?'var(--accent2)':'rgba(255,255,255,.15)'};background:${fixed[r][c]?'rgba(255,255,255,.12)':'#140f24'};color:${fixed[r][c]?'#fff':'var(--accent)'};font-weight:700;">${v||''}</button>`;

}).join('')).join('')}

</div>

<div class="btn-bar">

${[1,2,3,4,5,6,7,8,9].map(n => `<button class="gbtn" data-n="${n}">${n}</button>`).join('')}

</div>

</div>`;

stageEl.querySelectorAll('[data-rc]').forEach(b => b.onclick = () => { sel = b.dataset.rc.split(',').map(Number); render(); });

stageEl.querySelectorAll('[data-n]').forEach(b => b.onclick = () => {

const [r,c] = sel;

if (fixed[r][c]) return;

board[r][c] = Number(b.dataset.n); addScore(15); sfx(600,0.04);

if (board.flat().every(x => x > 0)) { chordWin(); addScore(300); msg('数独解谜完成!','Sudoku Solved!'); }

render();

});

}

window.resetGame = init;

init();

},

"sliding-portrait-15": function() {

let tiles = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,0], moves = 0;

function shuffle() {

tiles = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,0];

for (let i=0;i<120;i++) {

const z = tiles.indexOf(0), r = Math.floor(z/4), c = z%4, nbs = [];

if (r>0) nbs.push(z-4); if (r<3) nbs.push(z+4); if (c>0) nbs.push(z-1); if (c<3) nbs.push(z+1);

const pick = nbs[Math.floor(Math.random()*nbs.length)];

[tiles[z], tiles[pick]] = [tiles[pick], tiles[z]];

}

moves = 0; msg('点击空格旁边的滑块,按 1-15 顺序复原棋盘', 'Slide tiles into order 1–15'); render();

}

function render() {

stageEl.innerHTML = `

<div class="table-felt">

<div style="display:grid;grid-template-columns:repeat(4,62px);gap:8px;justify-content:center;">

${tiles.map((v, i) => v === 0 ? `<div style="height:62px;border-radius:12px;background:rgba(0,0,0,.35);"></div>` : `<button data-i="${i}" style="height:62px;border-radius:12px;border:1px solid rgba(255,255,255,.22);background:linear-gradient(135deg,#2e1646,#1a102b);font-size:20px;font-weight:800;color:${v===i+1?'#4ade80':'#fff'};">${v}</button>`).join('')}

</div>

<div style="margin-top:8px;color:var(--muted);font-size:13px;">${t('步数 Moves','Moves')}: ${moves}</div>

</div>`;

stageEl.querySelectorAll('[data-i]').forEach(b => b.onclick = () => {

const i = Number(b.dataset.i), z = tiles.indexOf(0);

if (Math.abs(Math.floor(i/4)-Math.floor(z/4)) + Math.abs((i%4)-(z%4)) === 1) {

[tiles[i], tiles[z]] = [tiles[z], tiles[i]]; moves++; sfx(520,0.04);

if (tiles.slice(0,15).every((v,idx)=>v===idx+1)) { chordWin(); addScore(500); msg('恭喜复原15滑块拼图!','15-Puzzle Solved!'); }

render();

}

});

}

window.resetGame = shuffle;

shuffle();

},

"reversi-royal": function() {

let b = [];

function init() {

b = Array.from({length:8}, () => Array(8).fill(0));

b[3][3]=2; b[3][4]=1; b[4][3]=1; b[4][4]=2;

msg('你执黑先行(⚫),夹击翻转白子', 'You play Black (⚫). Outflank White discs!');

render();

}

function flips(r, c, p) {

if (b[r][c] !== 0) return [];

const opp = 3 - p, res = [];

for (let dr=-1;dr<=1;dr++) for (let dc=-1;dc<=1;dc++) if (dr||dc) {

let nr=r+dr, nc=c+dc, line=[];

while (nr>=0&&nr<8&&nc>=0&&nc<8&&b[nr][nc]===opp) { line.push([nr,nc]); nr+=dr; nc+=dc; }

if (line.length && nr>=0&&nr<8&&nc>=0&&nc<8&&b[nr][nc]===p) res.push(...line);

}

return res;

}

function render() {

const black = b.flat().filter(x=>x===1).length, white = b.flat().filter(x=>x===2).length;

stageEl.innerHTML = `

<div class="table-felt">

<div style="margin-bottom:6px;font-size:14px;">⚫ ${black} : ⚪ ${white}</div>

<div style="display:grid;grid-template-columns:repeat(8,36px);gap:3px;justify-content:center;">

${b.map((row,r)=>row.map((v,c)=>{

const valid = flips(r,c,1).length > 0;

return `<button data-rc="${r},${c}" style="height:36px;border-radius:7px;background:#0d3b2e;border:1px solid ${valid?'#34d399':'rgba(255,255,255,.12)'};font-size:20px;">${v===1?'⚫':v===2?'⚪':valid?'·':''}</button>`;

}).join('')).join('')}

</div>

</div>`;

stageEl.querySelectorAll('[data-rc]').forEach(btn => btn.onclick = () => {

const [r,c] = btn.dataset.rc.split(',').map(Number);

const f = flips(r,c,1);

if (!f.length) return;

b[r][c] = 1; f.forEach(([fr,fc])=>b[fr][fc]=1); addScore(f.length*15); sfx(580,0.05);

setTimeout(() => {

const moves = [];

for (let ar=0;ar<8;ar++) for (let ac=0;ac<8;ac++) { const af=flips(ar,ac,2); if (af.length) moves.push({ar,ac,af}); }

if (moves.length) {

moves.sort((x,y)=>y.af.length-x.af.length);

const m = moves[0]; b[m.ar][m.ac]=2; m.af.forEach(([fr,fc])=>b[fr][fc]=2);

}

render();

}, 240);

render();

});

}

window.resetGame = init;

init();

},

"gomoku-velvet": function() {

const N = 11;

let b = [], over = false;

function init() {

b = Array.from({length:N}, () => Array(N).fill(0)); over = false;

msg('你执黑子(●),在棋盘上连成五子即获胜', 'Connect 5 stones in a row to win');

render();

}

function checkWin(p) {

const dirs = [[0,1],[1,0],[1,1],[1,-1]];

for (let r=0;r<N;r++) for (let c=0;c<N;c++) if (b[r][c]===p) {

for (const [dr,dc] of dirs) {

let cnt = 0;

for (let k=0;k<5;k++) if (b[r+dr*k]?.[c+dc*k]===p) cnt++;

if (cnt===5) return true;

}

}

return false;

}

function aiMove() {

let best = [5,5], bestS = -1;

for (let r=0;r<N;r++) for (let c=0;c<N;c++) if (b[r][c]===0) {

let sc = (5 - Math.hypot(r-5, c-5));

for (const [dr,dc] of [[0,1],[1,0],[1,1],[1,-1]]) {

for (const p of [1,2]) {

let run = 0;

for (let k=1;k<=4;k++) if (b[r+dr*k]?.[c+dc*k]===p) run++; else break;

for (let k=1;k<=4;k++) if (b[r-dr*k]?.[c-dc*k]===p) run++; else break;

if (run>=4) sc += p===2 ? 10000 : 8000;

else if (run===3) sc += p===2 ? 900 : 750;

else if (run===2) sc += 120;

}

}

if (sc > bestS) { bestS = sc; best = [r,c]; }

}

b[best[0]][best[1]] = 2;

if (checkWin(2)) { over = true; chordLose(); msg('AI 五子连珠获胜!','AI connected 5 in a row!'); }

}

function render() {

stageEl.innerHTML = `

<div class="table-felt">

<div style="display:grid;grid-template-columns:repeat(${N},30px);gap:2px;justify-content:center;">

${b.map((row,r)=>row.map((v,c)=>`<button data-rc="${r},${c}" style="height:30px;background:#2b1d12;border:1px solid rgba(255,255,255,.15);border-radius:4px;font-size:18px;">${v===1?'⚫':v===2?'⚪':''}</button>`).join('')).join('')}

</div>

</div>`;

stageEl.querySelectorAll('[data-rc]').forEach(btn => btn.onclick = () => {

if (over) return;

const [r,c] = btn.dataset.rc.split(',').map(Number);

if (b[r][c] !== 0) return;

b[r][c] = 1; sfx(540,0.04);

if (checkWin(1)) { over = true; chordWin(); addScore(250); msg('五子连珠!你赢了!','Five in a row! You win!'); render(); return; }

aiMove(); render();

});

}

window.resetGame = init;

init();

},

"checkers-lounge": function() {

let b = [], sel = null;

function init() {

b = Array.from({length:8}, (_,r) => Array.from({length:8}, (_,c) => (r+c)%2===1 ? (r<3 ? 2 : r>4 ? 1 : 0) : 0));

sel = null;

msg('点击红棋(🔴)斜向推进或跳过黑棋吃子', 'Select your Red piece (🔴) to advance or jump');

render();

}

function render() {

stageEl.innerHTML = `

<div class="table-felt">

<div style="display:grid;grid-template-columns:repeat(8,36px);gap:2px;justify-content:center;">

${b.map((row,r)=>row.map((v,c)=>{

const dark = (r+c)%2===1, picked = sel && sel[0]===r && sel[1]===c;

return `<button data-rc="${r},${c}" style="height:36px;background:${picked?'#7c2d12':dark?'#1e1b2e':'#312e48'};border:1px solid rgba(255,255,255,.1);font-size:20px;">${v===1?'🔴':v===3?'👑':v===2?'⚫':''}</button>`;

}).join('')).join('')}

</div>

</div>`;

stageEl.querySelectorAll('[data-rc]').forEach(btn => btn.onclick = () => {

const [r,c] = btn.dataset.rc.split(',').map(Number);

if (b[r][c]===1 || b[r][c]===3) { sel = [r,c]; sfx(500,0.04); render(); return; }

if (!sel || b[r][c]!==0) return;

const [sr,sc] = sel, p = b[sr][sc];

const dr = r - sr, dc = c - sc;

if ((p===1 && dr===-1 && Math.abs(dc)===1) || (p===3 && Math.abs(dr)===1 && Math.abs(dc)===1)) {

b[r][c] = r===0 ? 3 : p; b[sr][sc] = 0; sel = null; sfx(580,0.05); aiStep(); render();

} else if (Math.abs(dr)===2 && Math.abs(dc)===2 && b[(sr+r)/2][(sc+c)/2]===2) {

b[r][c] = r===0 ? 3 : p; b[sr][sc] = 0; b[(sr+r)/2][(sc+c)/2] = 0; sel = null; addScore(40); chordWin(); aiStep(); render();

}

});

}

function aiStep() {

for (let r=0;r<7;r++) for (let c=0;c<8;c++) if (b[r][c]===2) {

for (const dc of [-1,1]) if (b[r+1]?.[c+dc]===0) { b[r+1][c+dc]=2; b[r][c]=0; return; }

}

}

window.resetGame = init;

init();

},

"solitaire-klondike-noir": function() {

let cols = [], stock = [], waste = [], found = { '♠':0, '♥':0, '♦':0, '♣':0 };

function init() {

const d = makeDeck();

cols = Array.from({length:7}, (_,i) => d.splice(0, i+1));

stock = d; waste = [stock.pop()]; found = { '♠':0, '♥':0, '♦':0, '♣':0 };

msg('点击牌列顶牌自动移至基座(A→K)或红黑交替接龙', 'Tap any bottom card to auto-move to Foundation or Columns');

render();

}

function tryMove(card, fromArr) {

if (card.v === found[card.s] + 1) { found[card.s]++; fromArr.pop(); addScore(25); sfx(680,0.06); return true; }

for (const col of cols) {

if (col === fromArr) continue;

const top = col[col.length-1];

if ((!top && card.v===13) || (top && top.red !== card.red && top.v === card.v + 1)) {

col.push(fromArr.pop()); addScore(10); sfx(520,0.05); return true;

}

}

return false;

}

function render() {

stageEl.innerHTML = `

<div class="table-felt">

<div style="display:flex;justify-content:space-between;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:10px;">

<div style="display:flex;gap:8px;align-items:center;">

<button class="gbtn primary" id="solDraw">${t('发牌 Stock','Draw')} (${stock.length})</button>

<div id="solWaste" style="cursor:pointer;">${waste.length ? cardHTML(waste[waste.length-1]) : ''}</div>

</div>

<div style="display:flex;gap:8px;">

${SUITS.map(s=>`<div style="padding:8px 12px;border-radius:10px;border:1px solid rgba(255,255,255,.2);background:rgba(0,0,0,.3);">${s} ${found[s] ? RANKS[found[s]-1] : '-'}</div>`).join('')}

</div>

</div>

<div style="display:grid;grid-template-columns:repeat(7,1fr);gap:6px;">

${cols.map((col, ci) => `<div data-col="${ci}" style="min-height:130px;background:rgba(255,255,255,.03);border-radius:10px;padding:4px;cursor:pointer;display:flex;flex-direction:column;gap:4px;align-items:center;">${col.map(c=>cardHTML(c)).join('')}</div>`).join('')}

</div>

</div>`;

document.getElementById('solDraw').onclick = () => {

if (!stock.length) { stock = waste.reverse(); waste = []; }

if (stock.length) waste.push(stock.pop());

sfx(480,0.04); render();

};

const wEl = document.getElementById('solWaste');

if (wEl) wEl.onclick = () => { if (waste.length && tryMove(waste[waste.length-1], waste)) render(); };

stageEl.querySelectorAll('[data-col]').forEach(el => el.onclick = () => {

const col = cols[Number(el.dataset.col)];

if (col.length && tryMove(col[col.length-1], col)) render();

});

}

window.resetGame = init;

init();

},

"dice-yacht-club": function() {

let dice = [1,2,3,4,5], held = [false,false,false,false,false], rolls = 3, round = 1;

function roll() {

if (rolls <= 0) return;

dice = dice.map((v, i) => held[i] ? v : 1 + Math.floor(Math.random()*6));

rolls--; sfx(540,0.06,'triangle'); render();

}

function calcCats() {

const cnt = [0,0,0,0,0,0,0]; dice.forEach(d => cnt[d]++);

const sum = dice.reduce((a,b)=>a+b,0);

const maxC = Math.max(...cnt);

const yacht = maxC===5 ? 50 : 0;

const four = maxC>=4 ? sum : 0;

const full = (cnt.includes(3) && cnt.includes(2)) || maxC===5 ? 25 : 0;

return [

{name:t('全选总和 Choice','Choice Sum'), pts:sum},

{name:t('四条 Four of a Kind','4 of a Kind'), pts:four},

{name:t('葫芦 Full House','Full House (25)'), pts:full},

{name:t('快艇 Yacht (50)','Yacht 5-Kind (50)'), pts:yacht},

];

}

function render() {

const cats = calcCats();

stageEl.innerHTML = `

<div class="table-felt">

<div style="font-size:14px;color:var(--muted);">${t('第','Round')} ${round} ${t('局 · 剩余投掷','· Rolls Left')}: <b style="color:#fff">${rolls}</b></div>

<div style="display:flex;gap:10px;justify-content:center;margin:12px 0;">

${dice.map((d,i)=>`<button data-di="${i}" style="width:56px;height:56px;border-radius:14px;border:2px solid ${held[i]?'var(--accent2)':'rgba(255,255,255,.22)'};background:${held[i]?'rgba(167,139,250,.3)':'#171126'};font-size:24px;font-weight:800;">🎲${d}<div style="font-size:10px;color:var(--accent2)">${held[i]?t('锁定','HOLD'):''}</div></button>`).join('')}

</div>

<div class="btn-bar">

<button class="gbtn primary" id="ydRoll" ${rolls<=0?'disabled':''}>${t('摇骰子 Roll Dice','Roll Dice')} (${rolls})</button>

</div>

<div class="btn-bar" style="margin-top:8px;">

${cats.map(c => `<button class="gbtn" data-pts="${c.pts}">${c.name}: +${c.pts}</button>`).join('')}

</div>

</div>`;

document.getElementById('ydRoll').onclick = roll;

stageEl.querySelectorAll('[data-di]').forEach(b => b.onclick = () => { const i=Number(b.dataset.di); held[i]=!held[i]; sfx(480,0.04); render(); });

stageEl.querySelectorAll('[data-pts]').forEach(b => b.onclick = () => {

const pts = Number(b.dataset.pts); addScore(pts);

if (pts >= 25) chordWin(); else sfx(620,0.06);

round++; rolls = 3; held = [false,false,false,false,false]; roll();

});

}

msg('每局可摇3次骰子,点击骰子锁定保留,选择计分项结算', 'Roll up to 3 times, click dice to Hold, then score');

window.resetGame = () => { round = 1; rolls = 3; held = [false,false,false,false,false]; setScore(0); roll(); };

roll();

}

};

window.onLangChange = () => { if (ENGINES[GAME_ID]) ENGINES[GAME_ID](); };

document.getElementById('resetBtn').onclick = () => { if (window.resetGame) window.resetGame(); else ENGINES[GAME_ID](); };

ENGINES[GAME_ID]();

</script>

</body>

</html>

Continue the experience

This format opens in Huangguo’s original viewer.

Open original viewer ↗