重生之我解锁了德州登神系统
by RocketPanda331647 lines62.9 KB
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>重生之我解锁了德州登神系统</title>
<style>
/* ===== 全局样式 ===== */
* { box-sizing: border-box; user-select: none; }
body {
font-family: 'Segoe UI', Roboto, system-ui, sans-serif;
background: #1a1a2e;
color: #eee;
margin: 0;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 12px;
}
.game-container {
max-width: 580px;
width: 100%;
background: linear-gradient(145deg, #16213e, #0f3460);
border-radius: 36px;
padding: 16px 16px 30px;
box-shadow: 0 20px 40px rgba(0,0,0,0.6);
border: 1px solid #2a3a6a;
}
.game-title {
text-align: center;
font-size: 20px;
font-weight: 700;
color: #f7d44a;
text-shadow: 0 0 20px #f7d44a44;
margin-bottom: 6px;
letter-spacing: 1px;
}
.tab-nav {
display: flex;
gap: 6px;
margin-bottom: 14px;
border-bottom: 1px solid #2a3a6a;
padding-bottom: 8px;
}
.tab-nav .tab-btn {
flex: 1;
background: transparent;
border: none;
color: #99aabb;
padding: 8px 0;
font-size: 15px;
font-weight: 600;
cursor: pointer;
transition: 0.2s;
border-radius: 30px;
background: #1a2a4a;
}
.tab-nav .tab-btn.active {
background: #2a5a8a;
color: white;
box-shadow: 0 0 12px #2a5a8a88;
}
.tab-nav .tab-btn:hover { background: #2a4a6a; }
.page {
display: none;
}
.page.active {
display: block;
}
/* ===== 共用组件 ===== */
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 6px 12px;
border-bottom: 1px solid #3a4a7a;
flex-wrap: wrap;
gap: 8px;
}
.gold-display {
font-size: 24px;
font-weight: bold;
color: #f7d44a;
text-shadow: 0 0 10px #f7d44a55;
}
.controls-header {
display: flex;
gap: 10px;
align-items: center;
}
.btn {
background: #2a3a6a;
border: none;
color: white;
padding: 8px 16px;
border-radius: 30px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: 0.2s;
box-shadow: 0 4px 0 #0f1a3a;
touch-action: manipulation;
}
.btn:active { transform: translateY(3px); box-shadow: 0 1px 0 #0f1a3a; }
.btn-primary { background: #e94560; box-shadow: 0 4px 0 #8a1a2a; }
.btn-success { background: #2ecc71; box-shadow: 0 4px 0 #1a8a4a; }
.btn-gold { background: #f39c12; box-shadow: 0 4px 0 #a76f0a; color: #1a1a2e; }
.btn:disabled { opacity: 0.5; transform: translateY(3px); box-shadow: 0 1px 0 #0f1a3a; pointer-events: none; }
.hand-area {
display: flex;
align-items: center;
gap: 12px;
padding: 6px 12px;
background: #0a1a2a;
border-radius: 16px;
margin-bottom: 10px;
font-size: 14px;
color: #aab;
}
.hand-area .hand-label { font-weight: 600; color: #f7d44a; }
.hand-cards { display: flex; gap: 8px; }
.hand-cards .hand-cell {
width: 48px; height: 64px; background: #1e2f4a; border-radius: 8px;
display: flex; flex-direction: column; align-items: center; justify-content: center;
font-weight: 700; font-size: 18px; color: #fff;
box-shadow: inset 0 -3px 0 #0a1a2a; text-shadow: 0 1px 2px black;
}
.hand-cards .hand-cell .rank { font-size: 20px; line-height: 1; }
.hand-cards .hand-cell .suit { font-size: 16px; line-height: 1; }
.hand-cards .hand-cell .suit-red { color: #e74c3c; }
.hand-cards .hand-cell .suit-black { color: #bdc3c7; }
.hand-cards .hand-cell .suit-joker { color: #f1c40f; }
.grid-wrapper {
margin: 8px 0; background: #0a1a2a; border-radius: 20px; padding: 10px;
box-shadow: inset 0 0 20px #00000066; display: flex; justify-content: center;
}
.grid {
display: grid; grid-template-columns: repeat(5, 1fr); gap: 6px;
width: 100%; max-width: 360px; aspect-ratio: 1 / 1;
}
.cell {
background: #1e2f4a; border-radius: 10px; display: flex; flex-direction: column;
align-items: center; justify-content: center; font-size: 18px; font-weight: 700;
color: #fff; box-shadow: inset 0 -3px 0 #0a1a2a; aspect-ratio: 1 / 1;
padding: 2px; text-shadow: 0 1px 2px black; transition: background 0.1s;
}
.cell .rank { font-size: 22px; line-height: 1; }
.cell .suit { font-size: 18px; line-height: 1; }
.suit-red { color: #e74c3c; }
.suit-black { color: #bdc3c7; }
.suit-joker { color: #f1c40f; }
.score-detail {
margin: 8px 0; background: #0a1a2a; border-radius: 16px; padding: 8px 10px;
max-height: 150px; overflow-y: auto; border: 1px solid #2a3a6a; font-size: 12px;
}
.score-detail .line-item {
display: flex; justify-content: space-between; padding: 2px 4px;
border-bottom: 1px solid #1a2a4a;
}
.score-detail .line-item:last-child { border-bottom: none; }
.score-detail .line-name { color: #aab; }
.score-detail .line-score { color: #f7d44a; font-weight: 600; }
.info-bar {
display: flex; justify-content: space-between; align-items: center;
padding: 8px 4px; font-size: 14px; flex-wrap: wrap; gap: 6px;
}
.score-display { font-size: 20px; font-weight: bold; color: #2ecc71; }
.action-area {
display: flex; gap: 12px; justify-content: center; margin: 12px 0 8px;
flex-wrap: wrap;
}
.talent-section {
margin-top: 20px; background: #0a1a2a; border-radius: 20px; padding: 12px 10px;
border: 1px solid #2a3a6a;
}
.talent-tabs {
display: flex; gap: 6px; margin-bottom: 10px; flex-wrap: wrap;
}
.tab-btn-sub {
flex: 1; min-width: 60px; background: #1a2a4a; border: none; color: #aab;
padding: 6px 0; border-radius: 30px; font-size: 12px; font-weight: 600;
cursor: pointer; transition: 0.2s;
}
.tab-btn-sub.active { background: #2a5a8a; color: white; box-shadow: 0 0 10px #2a5a8a88; }
.talent-panel { display: none; flex-direction: column; gap: 6px; }
.talent-panel.active { display: flex; }
.talent-item {
display: flex; justify-content: space-between; align-items: center;
background: #121e32; padding: 6px 12px; border-radius: 30px;
border-left: 4px solid #f39c12; font-size: 13px;
}
.talent-item .info { display: flex; flex-direction: column; flex:1; }
.talent-item .name { font-weight: 600; font-size: 14px; }
.talent-item .desc { font-size: 10px; color: #99aabb; }
.talent-item .level { font-size: 12px; color: #f7d44a; margin-right: 8px; }
.talent-item .cost { font-size: 12px; color: #e67e22; margin-right: 6px; }
.talent-item .status { font-size: 11px; color: #e74c3c; margin-right: 6px; }
.talent-item .upgrade-btn { padding: 2px 12px; font-size: 12px; }
.footer-note { text-align: center; font-size: 11px; color: #667; margin-top: 12px; }
.hidden { display: none !important; }
/* ===== 锦标赛样式 ===== */
.tournament-stats {
display: flex; justify-content: space-around; background: #0a1a2a;
border-radius: 16px; padding: 8px; margin-bottom: 12px;
}
.tournament-stats .stat { text-align: center; }
.tournament-stats .stat .label { font-size: 12px; color: #99aabb; }
.tournament-stats .stat .value { font-size: 18px; font-weight: bold; color: #f7d44a; }
.tournament-list {
display: flex; flex-direction: column; gap: 8px; max-height: 400px;
overflow-y: auto; padding-right: 4px;
}
.tournament-item {
background: #121e32; border-radius: 16px; padding: 10px 14px;
display: flex; justify-content: space-between; align-items: center;
border-left: 4px solid #f39c12;
}
.tournament-item .info .name { font-weight: 600; font-size: 15px; }
.tournament-item .info .detail { font-size: 12px; color: #99aabb; }
.tournament-item .info .reward { font-size: 12px; color: #f7d44a; }
.tournament-item .btn { padding: 4px 16px; font-size: 13px; }
.match-detail {
background: #0a1a2a; border-radius: 16px; padding: 12px;
margin-bottom: 12px;
}
.match-detail .match-info { display: flex; flex-wrap: wrap; gap: 12px; font-size: 14px; }
.match-detail .match-info .item { background: #1a2a4a; padding: 4px 12px; border-radius: 30px; }
.match-detail .match-log {
background: #0a0a1a; border-radius: 12px; padding: 8px; max-height: 200px;
overflow-y: auto; font-size: 12px; font-family: monospace; margin: 8px 0;
border: 1px solid #2a3a6a;
}
.match-detail .match-log .log-line { color: #aab; padding: 2px 0; border-bottom: 1px solid #1a2a4a; }
.match-actions {
display: flex; gap: 8px; justify-content: center; flex-wrap: wrap;
margin-top: 8px;
}
.match-actions .btn { min-width: 80px; }
@media (max-width: 480px) {
.cell .rank { font-size: 18px; }
.cell .suit { font-size: 14px; }
.hand-cards .hand-cell { width: 40px; height: 54px; font-size: 16px; }
.gold-display { font-size: 20px; }
.btn { padding: 6px 12px; font-size: 12px; }
.score-detail { max-height: 120px; font-size: 11px; }
.game-title { font-size: 16px; }
.tournament-item { flex-wrap: wrap; }
.tournament-item .info { flex: 1; }
}
</style>
</head>
<body>
<div class="game-container" id="app">
<div class="game-title">♠ 重生之我解锁了德州登神系统 ♥</div>
<div class="tab-nav">
<button class="tab-btn active" data-page="adventure">冒险</button>
<button class="tab-btn" data-page="cash">现金桌</button>
<button class="tab-btn" data-page="tournament">锦标赛</button>
</div>
<!-- ===== 冒险页面 ===== -->
<div class="page active" id="page-adventure">
<div class="header">
<div class="gold-display">$ <span id="goldDisplay">0</span></div>
<div class="controls-header">
<button class="btn hidden" id="autoToggle">⏸️ 自动</button>
<button class="btn" id="resetBtn" style="background:#c0392b;box-shadow:0 4px 0 #782a1a;">↺ 重置</button>
</div>
</div>
<div class="hand-area">
<span class="hand-label">🃏 手牌</span>
<div class="hand-cards" id="handCards">
<div class="hand-cell" id="hand0"><span style="color:#666;">?</span></div>
<div class="hand-cell" id="hand1"><span style="color:#666;">?</span></div>
</div>
<span style="margin-left:auto;font-size:12px;color:#99aabb;" id="skillCounter">未解锁</span>
</div>
<div class="grid-wrapper">
<div class="grid" id="gridContainer"></div>
</div>
<div class="score-detail" id="scoreDetail">
<div style="color:#667;text-align:center;">拉杆后显示得分详情</div>
</div>
<div class="info-bar">
<span>次数: <strong id="spinCount">0</strong></span>
<span class="score-display">+<span id="lastScore">0</span> $</span>
<span>每秒: <strong id="perSecond">0</strong> $</span>
</div>
<div class="action-area">
<button class="btn btn-primary" id="spinBtn">🎰 拉杆</button>
</div>
<div class="talent-section">
<div class="talent-tabs">
<button class="tab-btn-sub active" data-tab="tab0">增强</button>
<button class="tab-btn-sub" data-tab="tab1">技能</button>
<button class="tab-btn-sub" data-tab="tab2">理论</button>
</div>
<div class="talent-panel active" id="tab0"></div>
<div class="talent-panel" id="tab1"></div>
<div class="talent-panel" id="tab2"></div>
</div>
<div class="footer-note">数据自动保存 · 离线挂机收益</div>
</div>
<!-- ===== 现金桌 ===== -->
<div class="page" id="page-cash">
<div style="text-align:center;padding:60px 20px;color:#aab;">
<h2 style="color:#f7d44a;">💰 现金桌</h2>
<p style="font-size:16px;margin-top:20px;">即将开放,敬请期待...</p>
</div>
</div>
<!-- ===== 锦标赛 ===== -->
<div class="page" id="page-tournament">
<div id="tournamentContent"></div>
</div>
</div>
<script>
// ============================================================
// 🎮 完整游戏逻辑(冒险 + 锦标赛)
// ============================================================
// ---------- 常量 ----------
const SUIT_SYMBOLS = { 'spade': '♠', 'heart': '♥', 'diamond': '♦', 'club': '♣' };
const ALL_RANKS = ['2','3','4','5','6','7','8','9','10','J','Q','K','A'];
const ALL_SUITS = ['spade','heart','diamond','club'];
const HAND_TYPE_NAMES = {
'FIVE_OF_A_KIND_FLUSH': '同花五条',
'FOUR_OF_A_KIND_FLUSH': '同花四条',
'ROYAL_FLUSH': '皇家同花顺',
'STRAIGHT_FLUSH': '同花顺',
'FOUR_OF_A_KIND': '四条',
'FULL_HOUSE': '葫芦',
'FLUSH': '同花',
'STRAIGHT': '顺子',
'THREE_OF_A_KIND': '三条',
'TWO_PAIR': '两对',
'ONE_PAIR': '一对',
'HIGH_CARD': '高牌',
};
const CONFIG = {
HAND_MULTIPLIERS: {
FIVE_OF_A_KIND_FLUSH: 1000000,
FOUR_OF_A_KIND_FLUSH: 100000,
ROYAL_FLUSH: 50000,
STRAIGHT_FLUSH: 10000,
FOUR_OF_A_KIND: 1000,
FULL_HOUSE: 200,
FLUSH: 30,
STRAIGHT: 20,
THREE_OF_A_KIND: 10,
TWO_PAIR: 3,
ONE_PAIR: 1,
HIGH_CARD: 0,
},
SKILL_BASE_TRIGGER: 30,
SKILL_REDUCTION_PER_LEVEL: 4,
AUTO_INTERVAL: 9500,
OFFLINE_DECAY: 0.8,
ANIMATION_DURATION: 9000,
COLUMN_STOP_TIMES: [2.5, 4.5, 6.0, 7.5, 8.8],
};
// ---------- 移牌链 ----------
const SUITS = ['diamond', 'club', 'heart', 'spade'];
const SUIT_NAMES = { diamond:'方块', club:'梅花', heart:'红桃', spade:'黑桃' };
const RANKS = ['2','3','4','5','6'];
const REMOVE_SEQUENCE = [];
for (let rank of RANKS) {
for (let suit of SUITS) {
REMOVE_SEQUENCE.push({
id: `remove_${rank}_${suit}`,
name: `移除 ${SUIT_NAMES[suit]}${rank}`,
rank: rank,
suit: suit,
cost: 100 + REMOVE_SEQUENCE.length * 100,
});
}
}
// ---------- 锦标赛数据 ----------
const TOURNAMENTS = [
{ id: 'daily', name: '俱乐部日赛', buyin: 210, minPlayers: 60, maxPlayers: 120, skillPoints: 1 },
{ id: 'weekly', name: '俱乐部周赛', buyin: 630, minPlayers: 200, maxPlayers: 350, skillPoints: 2 },
{ id: 'monthly', name: '俱乐部月赛', buyin: 1440, minPlayers: 400, maxPlayers: 600, skillPoints: 4 },
{ id: 'national_side', name: '国内巡回赛边赛', buyin: 4400, minPlayers: 300, maxPlayers: 1000, skillPoints: 10 },
{ id: 'national_main', name: '国内巡回赛主赛', buyin: 10000, minPlayers: 1800, maxPlayers: 3500, skillPoints: 20 },
{ id: 'wsop_side', name: 'WSOP边赛', buyin: 20000, minPlayers: 1000, maxPlayers: 4500, skillPoints: 200 },
{ id: 'wsop_main', name: 'WSOP主赛', buyin: 100000, minPlayers: 8000, maxPlayers: 20000, skillPoints: 1000 },
{ id: 'wsop_million', name: 'WSOP百万豪客赛', buyin: 10000000, minPlayers: 18, maxPlayers: 27, skillPoints: 0 },
];
// ---------- 全局状态 ----------
let state = {
gold: 100,
spinCount: 0,
lastScore: 0,
talents: {
removeProgress: 0,
smallJoker: 0,
bigJoker: 0,
boostAce: 0,
lineUnlock: 0,
autoSpin: 0,
skillUnlock: 0,
theory: 0,
},
handsSinceLastSkill: 0,
autoEnabled: false,
scoreHistory: [],
lastSaveTime: Date.now(),
handCards: [],
tournament: {
practicalExp: 1,
skillPoints: 0,
currentMatch: null,
}
};
// ---------- 辅助函数 ----------
function rankToValue(rank) {
if (rank === 'A') return 14;
if (rank === 'K') return 13;
if (rank === 'Q') return 12;
if (rank === 'J') return 11;
if (rank === '10') return 10;
if (rank === '9') return 9;
if (rank === '8') return 8;
if (rank === '7') return 7;
if (rank === '6') return 6;
if (rank === '5') return 5;
if (rank === '4') return 4;
if (rank === '3') return 3;
if (rank === '2') return 2;
return 0;
}
function createDeck() {
const deck = [];
for (let rank of ALL_RANKS) {
for (let suit of ALL_SUITS) {
deck.push({ rank, suit, isJoker: false });
}
}
return deck;
}
function shuffle(arr) {
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr;
}
// ---------- 德州扑克牌型判断(支持大小王) ----------
function getHandType5(cards) {
// 处理王:将王替换为所有可能的牌,取最高分
const nonJokers = cards.filter(c => !c.isJoker);
const jokers = cards.filter(c => c.isJoker);
if (jokers.length === 0) {
return evaluateNormalHand(cards);
}
let best = null;
const possibleRanks = ALL_RANKS;
const possibleSuits = ALL_SUITS;
function generateReplacements(index, current) {
if (index === jokers.length) {
const result = evaluateNormalHand(current);
if (!best || result.score > best.score) best = result;
return;
}
for (let rank of possibleRanks) {
for (let suit of possibleSuits) {
const dup = current.some(c => c.rank === rank && c.suit === suit && !c.isJoker);
if (dup) continue;
const newCard = { rank, suit, isJoker: false };
generateReplacements(index + 1, [...current, newCard]);
}
}
}
generateReplacements(0, nonJokers);
return best || { score: 0 };
}
function evaluateNormalHand(cards) {
const values = cards.map(c => rankToValue(c.rank)).sort((a,b)=>a-b);
const suits = cards.map(c => c.suit);
const isFlush = suits.every(s => s === suits[0]);
const isStraight = (() => {
if (values[0] === 2 && values[1] === 3 && values[2] === 4 && values[3] === 5 && values[4] === 14) return true;
for (let i=1; i<5; i++) if (values[i] !== values[i-1]+1) return false;
return true;
})();
const rankCount = {};
for (let v of values) rankCount[v] = (rankCount[v]||0)+1;
const counts = Object.values(rankCount);
const hasFour = counts.includes(4);
const hasThree = counts.includes(3);
const pairs = counts.filter(c => c===2).length;
if (isFlush && isStraight && values[0]===10 && values[4]===14) return { score: 900 };
if (isFlush && isStraight) return { score: 800 };
if (hasFour) return { score: 700 };
if (hasThree && pairs===1) return { score: 600 };
if (isFlush) return { score: 500 };
if (isStraight) return { score: 400 };
if (hasThree) return { score: 300 };
if (pairs===2) return { score: 200 };
if (pairs===1) return { score: 100 };
return { score: 0 };
}
function getHandType7(cards) {
let best = null;
const indices = cards.map((_, i) => i);
for (let i = 0; i < indices.length; i++) {
for (let j = i+1; j < indices.length; j++) {
const five = cards.filter((_, idx) => idx !== i && idx !== j);
const hand = getHandType5(five);
if (!best || hand.score > best.score) best = hand;
}
}
return best || { score: 0 };
}
// ---------- 牌技值 ----------
function getTheoryValue() {
const lv = state.talents.theory || 0;
return lv * 10;
}
function getSkillPower() {
return getTheoryValue() * (state.tournament.practicalExp || 1);
}
// ---------- 冒险核心逻辑 ----------
const $ = id => document.getElementById(id);
const goldDisplay = $('goldDisplay');
const gridContainer = $('gridContainer');
const spinCountEl = $('spinCount');
const lastScoreEl = $('lastScore');
const perSecondEl = $('perSecond');
const spinBtn = $('spinBtn');
const autoToggle = $('autoToggle');
const resetBtn = $('resetBtn');
const skillCounter = $('skillCounter');
const scoreDetail = $('scoreDetail');
const hand0 = $('hand0');
const hand1 = $('hand1');
function getCurrentDeck() {
let deck = createDeck();
const removedCount = state.talents.removeProgress || 0;
for (let i = 0; i < removedCount; i++) {
const t = REMOVE_SEQUENCE[i];
deck = deck.filter(c => !(c.rank === t.rank && c.suit === t.suit && !c.isJoker));
}
if (state.talents.smallJoker >= 1) {
deck.push({ rank: '小王', suit: 'joker', isJoker: true, jokerType: 'small' });
}
if (state.talents.bigJoker >= 1) {
deck.push({ rank: '大王', suit: 'joker', isJoker: true, jokerType: 'big' });
}
return deck;
}
function drawHandAndBoard() {
const deck = getCurrentDeck();
if (deck.length < 27) {
const extra = createDeck();
while (deck.length < 27) {
const card = extra[Math.floor(Math.random() * extra.length)];
if (!deck.some(c => c.rank === card.rank && c.suit === card.suit && c.isJoker === card.isJoker)) {
deck.push({ ...card });
}
}
}
const shuffled = shuffle([...deck]);
const hand = shuffled.slice(0, 2);
const board = shuffled.slice(2, 27);
return { hand, board };
}
function renderHand(hand) {
const renderOne = (element, card) => {
element.innerHTML = '';
if (!card) {
element.innerHTML = '<span style="color:#666;">?</span>';
return;
}
if (card.isJoker) {
const rankSpan = document.createElement('span');
rankSpan.className = 'rank suit-joker';
rankSpan.textContent = '🃏';
const suitSpan = document.createElement('span');
suitSpan.className = 'suit suit-joker';
suitSpan.textContent = card.rank === '小王' ? '小' : '大';
element.appendChild(rankSpan);
element.appendChild(suitSpan);
} else {
const rankSpan = document.createElement('span');
rankSpan.className = 'rank';
rankSpan.textContent = card.rank;
const suitSpan = document.createElement('span');
suitSpan.className = 'suit ' + (card.suit === 'spade' || card.suit === 'club' ? 'suit-black' : 'suit-red');
suitSpan.textContent = SUIT_SYMBOLS[card.suit] || '';
element.appendChild(rankSpan);
element.appendChild(suitSpan);
}
};
renderOne(hand0, hand[0]);
renderOne(hand1, hand[1]);
}
function renderGrid(cards) {
gridContainer.innerHTML = '';
for (let r = 0; r < 5; r++) {
for (let c = 0; c < 5; c++) {
const card = cards[r*5 + c];
const cell = document.createElement('div');
cell.className = 'cell';
if (card.isJoker) {
const rankSpan = document.createElement('span');
rankSpan.className = 'rank suit-joker';
rankSpan.textContent = '🃏';
const suitSpan = document.createElement('span');
suitSpan.className = 'suit suit-joker';
suitSpan.textContent = card.rank === '小王' ? '小' : '大';
cell.appendChild(rankSpan);
cell.appendChild(suitSpan);
} else {
const rankSpan = document.createElement('span');
rankSpan.className = 'rank';
rankSpan.textContent = card.rank;
const suitSpan = document.createElement('span');
suitSpan.className = 'suit ' + (card.suit === 'spade' || card.suit === 'club' ? 'suit-black' : 'suit-red');
suitSpan.textContent = SUIT_SYMBOLS[card.suit] || '';
cell.appendChild(rankSpan);
cell.appendChild(suitSpan);
}
gridContainer.appendChild(cell);
}
}
}
function getAllLines(cells) {
const lines = [];
for (let r = 0; r < 5; r++) lines.push(cells.slice(r*5, r*5+5));
for (let c = 0; c < 5; c++) {
const col = [];
for (let r = 0; r < 5; r++) col.push(cells[r*5 + c]);
lines.push(col);
}
const diag1 = [], diag2 = [];
for (let i = 0; i < 5; i++) {
diag1.push(cells[i*5 + i]);
diag2.push(cells[i*5 + (4-i)]);
}
lines.push(diag1, diag2);
return lines;
}
function getUnlockedLines(cells) {
const all = getAllLines(cells);
const lineUnlock = state.talents.lineUnlock || 0;
const unlockedIndices = [0,1,2,3,4];
const colsUnlocked = Math.min(lineUnlock, 5);
for (let i = 0; i < colsUnlocked; i++) unlockedIndices.push(5 + i);
if (lineUnlock >= 6) unlockedIndices.push(10, 11);
return unlockedIndices.map(idx => all[idx]);
}
function calculateLineScore(lineCards, handCards) {
const combined = lineCards.concat(handCards);
const hand = getHandType7(combined);
let score = hand.score;
const aceCount = combined.filter(c => c.rank === 'A' && !c.isJoker).length;
const boostLevel = state.talents.boostAce || 0;
if (boostLevel > 0 && aceCount > 0) {
const bonus = 0.10 * boostLevel * aceCount;
score = Math.floor(score * (1 + bonus));
}
return { type: (score > 0) ? Object.keys(CONFIG.HAND_MULTIPLIERS).find(k => CONFIG.HAND_MULTIPLIERS[k] === score) : 'HIGH_CARD', score };
}
function updateScoreDetail(lineResults) {
scoreDetail.innerHTML = '';
if (!lineResults || lineResults.length === 0) {
scoreDetail.innerHTML = '<div style="color:#667;text-align:center;">拉杆后显示得分详情</div>';
return;
}
const filtered = lineResults.filter(item => item.score > 0);
if (filtered.length === 0) {
scoreDetail.innerHTML = '<div style="color:#667;text-align:center;">本局无得分线</div>';
return;
}
for (let item of filtered) {
const div = document.createElement('div');
div.className = 'line-item';
const nameSpan = document.createElement('span');
nameSpan.className = 'line-name';
nameSpan.textContent = item.name;
const scoreSpan = document.createElement('span');
scoreSpan.className = 'line-score';
const typeName = HAND_TYPE_NAMES[item.type] || item.type;
scoreSpan.textContent = `${typeName} +${item.score}`;
div.appendChild(nameSpan);
div.appendChild(scoreSpan);
scoreDetail.appendChild(div);
}
}
let isAnimating = false;
let animFrameId = null;
function performSpin() {
if (isAnimating) return;
try {
// 黑桃领域触发检测
let useSkill = false;
const skillLevel = state.talents.skillUnlock || 0;
if (skillLevel > 0) {
state.handsSinceLastSkill = (state.handsSinceLastSkill || 0) + 1;
const threshold = CONFIG.SKILL_BASE_TRIGGER - skillLevel * CONFIG.SKILL_REDUCTION_PER_LEVEL;
if (state.handsSinceLastSkill >= threshold) {
useSkill = true;
state.handsSinceLastSkill = 0;
}
}
const { hand, board } = drawHandAndBoard();
state.handCards = hand;
renderHand(hand);
let finalCards = board;
if (useSkill) {
finalCards = finalCards.map(c => ({ ...c, suit: 'spade' }));
}
const allLines = getAllLines(finalCards);
const lineUnlock = state.talents.lineUnlock || 0;
const unlockedIndices = [0,1,2,3,4];
for (let i = 0; i < Math.min(lineUnlock, 5); i++) unlockedIndices.push(5+i);
if (lineUnlock >= 6) unlockedIndices.push(10, 11);
const unlockedLines = unlockedIndices.map(idx => allLines[idx]);
const lineNames = ['第1行','第2行','第3行','第4行','第5行','第1列','第2列','第3列','第4列','第5列','主对角线','副对角线'];
const lineResults = [];
let totalScore = 0;
for (let i = 0; i < unlockedLines.length; i++) {
const idx = unlockedIndices[i];
const result = calculateLineScore(unlockedLines[i], hand);
lineResults.push({ name: lineNames[idx], type: result.type, score: result.score });
totalScore += result.score;
}
// 动画开始
isAnimating = true;
spinBtn.disabled = true;
resetBtn.disabled = true;
const finalCardsByCol = [];
for (let c = 0; c < 5; c++) {
const colCards = [];
for (let r = 0; r < 5; r++) {
colCards.push(finalCards[r*5 + c]);
}
finalCardsByCol.push(colCards);
}
const fullDeck = getCurrentDeck();
const cells = gridContainer.children;
const duration = CONFIG.ANIMATION_DURATION;
const stopTimes = CONFIG.COLUMN_STOP_TIMES.map(t => t * 1000);
const startTime = performance.now();
let columnStopped = [false, false, false, false, false];
function renderColumnRandom(colIndex) {
for (let r = 0; r < 5; r++) {
const idx = r*5 + colIndex;
const randomCard = fullDeck[Math.floor(Math.random() * fullDeck.length)];
const cell = cells[idx];
cell.innerHTML = '';
if (randomCard.isJoker) {
const rankSpan = document.createElement('span');
rankSpan.className = 'rank suit-joker';
rankSpan.textContent = '🃏';
const suitSpan = document.createElement('span');
suitSpan.className = 'suit suit-joker';
suitSpan.textContent = randomCard.rank === '小王' ? '小' : '大';
cell.appendChild(rankSpan);
cell.appendChild(suitSpan);
} else {
const rankSpan = document.createElement('span');
rankSpan.className = 'rank';
rankSpan.textContent = randomCard.rank;
const suitSpan = document.createElement('span');
suitSpan.className = 'suit ' + (randomCard.suit === 'spade' || randomCard.suit === 'club' ? 'suit-black' : 'suit-red');
suitSpan.textContent = SUIT_SYMBOLS[randomCard.suit] || '';
cell.appendChild(rankSpan);
cell.appendChild(suitSpan);
}
}
}
function renderColumnFinal(colIndex) {
const colCards = finalCardsByCol[colIndex];
for (let r = 0; r < 5; r++) {
const idx = r*5 + colIndex;
const card = colCards[r];
const cell = cells[idx];
cell.innerHTML = '';
if (card.isJoker) {
const rankSpan = document.createElement('span');
rankSpan.className = 'rank suit-joker';
rankSpan.textContent = '🃏';
const suitSpan = document.createElement('span');
suitSpan.className = 'suit suit-joker';
suitSpan.textContent = card.rank === '小王' ? '小' : '大';
cell.appendChild(rankSpan);
cell.appendChild(suitSpan);
} else {
const rankSpan = document.createElement('span');
rankSpan.className = 'rank';
rankSpan.textContent = card.rank;
const suitSpan = document.createElement('span');
suitSpan.className = 'suit ' + (card.suit === 'spade' || card.suit === 'club' ? 'suit-black' : 'suit-red');
suitSpan.textContent = SUIT_SYMBOLS[card.suit] || '';
cell.appendChild(rankSpan);
cell.appendChild(suitSpan);
}
}
}
function animate(timestamp) {
const elapsed = timestamp - startTime;
for (let c = 0; c < 5; c++) {
if (!columnStopped[c] && elapsed >= stopTimes[c]) {
columnStopped[c] = true;
renderColumnFinal(c);
}
}
for (let c = 0; c < 5; c++) {
if (columnStopped[c]) continue;
const timeToStop = stopTimes[c] - elapsed;
let prob = 1;
if (timeToStop < 1000 && timeToStop > 0) {
prob = timeToStop / 1000;
} else if (timeToStop <= 0) {
prob = 0;
}
if (Math.random() < prob) {
renderColumnRandom(c);
}
}
if (columnStopped.every(v => v === true)) {
for (let c = 0; c < 5; c++) renderColumnFinal(c);
state.gold += totalScore;
state.lastScore = totalScore;
state.spinCount += 1;
state.scoreHistory.push(totalScore);
if (state.scoreHistory.length > 20) state.scoreHistory.shift();
updateScoreDetail(lineResults);
updateUI();
saveGame();
isAnimating = false;
spinBtn.disabled = false;
resetBtn.disabled = false;
return;
}
animFrameId = requestAnimationFrame(animate);
}
animFrameId = requestAnimationFrame(animate);
} catch (e) {
console.error('拉杆出错:', e);
isAnimating = false;
spinBtn.disabled = false;
resetBtn.disabled = false;
alert('拉杆出现错误,请查看控制台信息');
}
}
// ---------- 更新UI ----------
function updateUI() {
goldDisplay.textContent = state.gold;
spinCountEl.textContent = state.spinCount;
lastScoreEl.textContent = state.lastScore;
const recent = state.scoreHistory.slice(-10);
const avg = recent.length ? Math.round(recent.reduce((a,b)=>a+b,0) / recent.length) : 0;
perSecondEl.textContent = avg;
const autoUnlocked = state.talents.autoSpin >= 1;
if (autoUnlocked) {
autoToggle.classList.remove('hidden');
autoToggle.textContent = state.autoEnabled ? '⏸️ 自动' : '▶️ 自动';
} else {
autoToggle.classList.add('hidden');
if (state.autoEnabled) {
state.autoEnabled = false;
stopAutoSpin();
}
}
const skillLevel = state.talents.skillUnlock || 0;
if (skillLevel === 0) {
skillCounter.textContent = '未解锁';
} else {
const threshold = CONFIG.SKILL_BASE_TRIGGER - skillLevel * CONFIG.SKILL_REDUCTION_PER_LEVEL;
const current = state.handsSinceLastSkill || 0;
skillCounter.textContent = `触发: ${current}/${threshold}`;
}
updateTalentUI();
}
// ---------- 天赋系统 ----------
const THEORY_TALENT = {
id: 'theory',
label: '理论',
getCost: () => {
const lv = state.talents.theory || 0;
if (lv >= 5) return Infinity;
return [500, 1000, 2000, 4000, 8000][lv];
},
maxLevel: 5,
getLevel: () => state.talents.theory || 0,
getName: () => {
const lv = state.talents.theory || 0;
if (lv >= 5) return '理论·满级';
return `理论 Lv${lv+1}`;
},
getDesc: () => {
const lv = state.talents.theory || 0;
if (lv >= 5) return '理论值 +50 (已满)';
return `理论值 +${(lv+1)*10} (当前 ${lv*10})`;
},
prereq: () => true
};
const ENHANCE_TALENTS = [
{ id: 'removeProgress', label: '移牌', getCost: () => {
const p = state.talents.removeProgress || 0;
if (p >= REMOVE_SEQUENCE.length) return Infinity;
return REMOVE_SEQUENCE[p].cost;
}, maxLevel: REMOVE_SEQUENCE.length, getLevel: () => state.talents.removeProgress || 0,
getName: () => {
const p = state.talents.removeProgress || 0;
if (p >= REMOVE_SEQUENCE.length) return '移除完成';
const t = REMOVE_SEQUENCE[p];
return `移除 ${SUIT_NAMES[t.suit]}${t.rank}`;
},
getDesc: () => {
const p = state.talents.removeProgress || 0;
if (p >= REMOVE_SEQUENCE.length) return '已移除所有2~6';
const t = REMOVE_SEQUENCE[p];
return `从牌库中移除 ${SUIT_NAMES[t.suit]}${t.rank}`;
},
prereq: () => true
},
{ id: 'smallJoker', label: '小王', getCost: () => state.talents.smallJoker >= 1 ? Infinity : 1000,
maxLevel: 1, getLevel: () => state.talents.smallJoker || 0,
getName: () => '增加小王', getDesc: () => '牌库增加1张万能小王',
prereq: () => true
},
{ id: 'bigJoker', label: '大王', getCost: () => state.talents.bigJoker >= 1 ? Infinity : 10000,
maxLevel: 1, getLevel: () => state.talents.bigJoker || 0,
getName: () => '增加大王', getDesc: () => '牌库增加1张万能大王 (需小王≥1)',
prereq: () => (state.talents.smallJoker || 0) >= 1
},
{ id: 'boostAce', label: '倍率A', getCost: () => {
const lv = state.talents.boostAce || 0;
if (lv >= 5) return Infinity;
return [300, 600, 1200, 2400, 4800][lv];
}, maxLevel: 5, getLevel: () => state.talents.boostAce || 0,
getName: () => '倍率A', getDesc: () => '每张A使总得分 +10%',
prereq: () => true
},
{ id: 'lineUnlock', label: '列/对角线', getCost: () => {
const lv = state.talents.lineUnlock || 0;
if (lv >= 6) return Infinity;
return [100, 200, 300, 400, 500, 1000][lv];
}, maxLevel: 6, getLevel: () => state.talents.lineUnlock || 0,
getName: () => {
const lv = state.talents.lineUnlock || 0;
if (lv >= 6) return '全部解锁';
return ['解锁第1列', '解锁第2列', '解锁第3列', '解锁第4列', '解锁第5列', '解锁两条对角线'][lv];
},
getDesc: () => {
const lv = state.talents.lineUnlock || 0;
if (lv >= 6) return '所有列和对角线已解锁';
return ['第1列参与计分', '第2列参与计分', '第3列参与计分', '第4列参与计分', '第5列参与计分', '两条对角线参与计分'][lv];
},
prereq: () => true
},
{ id: 'autoSpin', label: '自动拉杆', getCost: () => state.talents.autoSpin >= 1 ? Infinity : 100,
maxLevel: 1, getLevel: () => state.talents.autoSpin || 0,
getName: () => '自动拉杆', getDesc: () => '解锁自动旋转',
prereq: () => true
},
];
const SKILL_TALENTS = [
{ id: 'skillUnlock', label: '黑桃领域', getCost: () => {
const lv = state.talents.skillUnlock || 0;
if (lv >= 4) return Infinity;
return [3000, 6000, 12000, 24000][lv];
}, maxLevel: 4, getLevel: () => state.talents.skillUnlock || 0,
getName: () => {
const lv = state.talents.skillUnlock || 0;
if (lv >= 4) return '黑桃领域·满级';
return `黑桃领域 Lv${lv+1}`;
},
getDesc: () => {
const lv = state.talents.skillUnlock || 0;
if (lv >= 4) return '触发间隔 14手';
const trigger = CONFIG.SKILL_BASE_TRIGGER - (lv+1) * CONFIG.SKILL_REDUCTION_PER_LEVEL;
return `每${trigger}手牌自动触发 (当前Lv${lv+1})`;
},
prereq: () => true
},
];
function createTalentItem(talent) {
const div = document.createElement('div');
div.className = 'talent-item';
div.dataset.id = talent.id;
const info = document.createElement('div');
info.className = 'info';
info.innerHTML = `<span class="name">${talent.getName()}</span><span class="desc">${talent.getDesc()}</span>`;
const right = document.createElement('div');
right.style.display = 'flex';
right.style.alignItems = 'center';
right.style.gap = '6px';
const levelSpan = document.createElement('span');
levelSpan.className = 'level';
levelSpan.textContent = `Lv.${talent.getLevel()}`;
const costSpan = document.createElement('span');
costSpan.className = 'cost';
const statusSpan = document.createElement('span');
statusSpan.className = 'status';
const btn = document.createElement('button');
btn.className = 'btn btn-gold upgrade-btn';
btn.textContent = '升级';
btn.dataset.id = talent.id;
right.appendChild(levelSpan);
right.appendChild(costSpan);
right.appendChild(statusSpan);
right.appendChild(btn);
div.appendChild(info);
div.appendChild(right);
return div;
}
function buildTalentPanels() {
const tab0 = document.getElementById('tab0');
tab0.innerHTML = '';
for (let t of ENHANCE_TALENTS) tab0.appendChild(createTalentItem(t));
const tab1 = document.getElementById('tab1');
tab1.innerHTML = '';
for (let t of SKILL_TALENTS) tab1.appendChild(createTalentItem(t));
const tab2 = document.getElementById('tab2');
tab2.innerHTML = '';
tab2.appendChild(createTalentItem(THEORY_TALENT));
document.querySelectorAll('.upgrade-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const id = e.target.dataset.id;
upgradeTalent(id);
});
});
}
function updateTalentUI() {
document.querySelectorAll('.talent-item').forEach(item => {
const id = item.dataset.id;
let talent = null;
if (id === 'theory') talent = THEORY_TALENT;
else {
talent = ENHANCE_TALENTS.find(t => t.id === id) || SKILL_TALENTS.find(t => t.id === id);
}
if (!talent) return;
const level = talent.getLevel();
const maxLevel = talent.maxLevel;
const cost = talent.getCost();
const prereqOk = talent.prereq();
const nameSpan = item.querySelector('.name');
const descSpan = item.querySelector('.desc');
const levelSpan = item.querySelector('.level');
const costSpan = item.querySelector('.cost');
const statusSpan = item.querySelector('.status');
const btn = item.querySelector('.upgrade-btn');
nameSpan.textContent = talent.getName();
descSpan.textContent = talent.getDesc();
levelSpan.textContent = `Lv.${level}`;
if (level >= maxLevel || cost === Infinity) {
costSpan.textContent = '已满';
btn.disabled = true;
btn.textContent = '满级';
statusSpan.textContent = '';
} else {
costSpan.textContent = `$${cost}`;
if (!prereqOk) {
btn.disabled = true;
btn.textContent = '前置不足';
statusSpan.textContent = '🔒';
} else if (state.gold < cost) {
btn.disabled = true;
btn.textContent = '金币不足';
statusSpan.textContent = '💰';
} else {
btn.disabled = false;
btn.textContent = '升级';
statusSpan.textContent = '✅';
}
}
});
}
function upgradeTalent(id) {
let talent = null;
if (id === 'theory') talent = THEORY_TALENT;
else {
talent = ENHANCE_TALENTS.find(t => t.id === id) || SKILL_TALENTS.find(t => t.id === id);
}
if (!talent) return;
const level = talent.getLevel();
const maxLevel = talent.maxLevel;
if (level >= maxLevel) return;
if (!talent.prereq()) {
alert('前置条件未满足!');
return;
}
const cost = talent.getCost();
if (cost === Infinity) {
alert('已满级');
return;
}
if (state.gold < cost) {
alert(`金币不足!需要 $${cost}`);
return;
}
state.gold -= cost;
// 升级
if (id === 'removeProgress') {
state.talents.removeProgress = (state.talents.removeProgress || 0) + 1;
} else if (id === 'smallJoker') {
state.talents.smallJoker = 1;
} else if (id === 'bigJoker') {
state.talents.bigJoker = 1;
} else if (id === 'boostAce') {
state.talents.boostAce = (state.talents.boostAce || 0) + 1;
} else if (id === 'lineUnlock') {
state.talents.lineUnlock = (state.talents.lineUnlock || 0) + 1;
} else if (id === 'autoSpin') {
state.talents.autoSpin = 1;
} else if (id === 'skillUnlock') {
state.talents.skillUnlock = (state.talents.skillUnlock || 0) + 1;
state.handsSinceLastSkill = 0;
} else if (id === 'theory') {
state.talents.theory = (state.talents.theory || 0) + 1;
}
saveGame();
updateUI();
}
// ---------- 自动拉杆 ----------
let autoTimer = null;
function startAutoSpin() {
if (autoTimer) clearInterval(autoTimer);
if (!state.autoEnabled || state.talents.autoSpin < 1) return;
autoTimer = setInterval(() => {
if (!isAnimating) {
performSpin();
}
}, CONFIG.AUTO_INTERVAL);
}
function stopAutoSpin() {
if (autoTimer) { clearInterval(autoTimer); autoTimer = null; }
}
function toggleAuto() {
if (state.talents.autoSpin < 1) {
alert('请先在天赋中解锁「自动拉杆」');
return;
}
state.autoEnabled = !state.autoEnabled;
if (state.autoEnabled) startAutoSpin();
else stopAutoSpin();
saveGame();
updateUI();
}
// ---------- 锦标赛逻辑 ----------
function renderTournament() {
const container = document.getElementById('tournamentContent');
const theory = getTheoryValue();
const exp = state.tournament.practicalExp || 1;
const skillPower = theory * exp;
const skillPoints = state.tournament.skillPoints || 0;
if (state.tournament.currentMatch) {
renderMatchDetail(container);
return;
}
container.innerHTML = `
<div class="tournament-stats">
<div class="stat"><div class="label">牌技值</div><div class="value">${skillPower}</div></div>
<div class="stat"><div class="label">理论</div><div class="value">${theory}</div></div>
<div class="stat"><div class="label">实战经验</div><div class="value">${exp}</div></div>
<div class="stat"><div class="label">技能点</div><div class="value">${skillPoints}</div></div>
</div>
<div style="margin-bottom:10px;font-size:14px;color:#aab;">选择比赛报名:</div>
<div class="tournament-list" id="tournamentList">
${TOURNAMENTS.map(t => `
<div class="tournament-item" data-id="${t.id}">
<div class="info">
<div class="name">${t.name}</div>
<div class="detail">报名费 $${t.buyin} · 人数 ${t.minPlayers}-${t.maxPlayers}</div>
<div class="reward">🏆 冠军奖励 ${t.skillPoints} 技能点</div>
</div>
<button class="btn btn-primary join-btn" data-id="${t.id}">报名</button>
</div>
`).join('')}
</div>
`;
container.querySelectorAll('.join-btn').forEach(btn => {
btn.addEventListener('click', function() {
const id = this.dataset.id;
joinTournament(id);
});
});
}
function joinTournament(id) {
const tournament = TOURNAMENTS.find(t => t.id === id);
if (!tournament) return;
if (state.gold < tournament.buyin) {
alert(`金币不足!需要 $${tournament.buyin}`);
return;
}
state.gold -= tournament.buyin;
const players = Math.floor(Math.random() * (tournament.maxPlayers - tournament.minPlayers + 1)) + tournament.minPlayers;
const match = {
id: tournament.id,
name: tournament.name,
buyin: tournament.buyin,
totalPlayers: players,
remainingPlayers: players,
skillPointsReward: tournament.skillPoints,
status: 'registered',
blinds: 20,
level: 1,
myChips: players * 1000,
avgChips: players * 1000,
logs: [],
finished: false,
result: null,
isManual: false,
waitingForAction: false,
actionResolve: null,
intervalId: null,
opponents: [],
};
state.tournament.currentMatch = match;
renderTournament();
}
function renderMatchDetail(container) {
const match = state.tournament.currentMatch;
if (!match) return renderTournament();
const logsHtml = match.logs.slice(-30).map(log => `<div class="log-line">${log}</div>`).join('');
container.innerHTML = `
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;">
<span style="font-size:18px;font-weight:bold;">${match.name}</span>
<button class="btn" id="exitMatchBtn" style="background:#c0392b;box-shadow:0 4px 0 #782a1a;">退出</button>
</div>
<div class="match-detail">
<div class="match-info">
<span class="item">报名费 $${match.buyin}</span>
<span class="item">参赛人数 ${match.totalPlayers}</span>
<span class="item">剩余 ${match.remainingPlayers}</span>
<span class="item">盲注级别 ${match.level}</span>
<span class="item">盲注 ${match.blinds}/${match.blinds*2}</span>
<span class="item">我的筹码 $${match.myChips}</span>
<span class="item">平均筹码 $${match.avgChips}</span>
</div>
${match.status === 'registered' ? `
<div style="margin:12px 0;text-align:center;">
<button class="btn btn-primary" id="startMatchBtn">开始比赛</button>
</div>
` : `
<div class="match-log" id="matchLog">
${logsHtml}
</div>
${match.waitingForAction ? `
<div class="match-actions" id="actionButtons">
<button class="btn btn-success action-btn" data-action="fold">弃牌</button>
<button class="btn btn-primary action-btn" data-action="call">跟注</button>
<button class="btn btn-gold action-btn" data-action="raise">加注</button>
</div>
` : ''}
${match.status === 'finished' ? `
<div style="margin-top:10px;padding:10px;background:#1a2a4a;border-radius:12px;text-align:center;">
<div>🏆 比赛结束!</div>
<div>名次: ${match.result.rank}</div>
<div>奖金: $${match.result.prize}</div>
<div>获得技能点: ${match.result.skillPoints}</div>
<button class="btn btn-primary" id="claimPrizeBtn">领取奖励</button>
</div>
` : ''}
`}
</div>
`;
// 绑定事件
const exitBtn = document.getElementById('exitMatchBtn');
if (exitBtn) exitBtn.addEventListener('click', () => {
if (match.status === 'running' || match.status === 'registered') {
if (!confirm('退出将放弃当前比赛,确定吗?')) return;
}
if (match.intervalId) clearInterval(match.intervalId);
state.tournament.currentMatch = null;
renderTournament();
});
const startBtn = document.getElementById('startMatchBtn');
if (startBtn) {
startBtn.addEventListener('click', () => {
const mode = confirm('选择比赛模式:\n点击“确定”为自动模式,点击“取消”为手动模式');
match.isManual = !mode;
match.status = 'running';
startMatch(match);
renderTournament();
});
}
const claimBtn = document.getElementById('claimPrizeBtn');
if (claimBtn) {
claimBtn.addEventListener('click', () => {
const prize = match.result.prize;
const sp = match.result.skillPoints;
state.gold += prize;
state.tournament.skillPoints = (state.tournament.skillPoints || 0) + sp;
state.tournament.practicalExp = (state.tournament.practicalExp || 1) + 1;
if (match.intervalId) clearInterval(match.intervalId);
state.tournament.currentMatch = null;
saveGame();
renderTournament();
});
}
document.querySelectorAll('.action-btn').forEach(btn => {
btn.addEventListener('click', function() {
const action = this.dataset.action;
if (match.waitingForAction && match.actionResolve) {
match.actionResolve(action);
match.waitingForAction = false;
match.actionResolve = null;
setTimeout(() => simulateHand(match), 300);
}
});
});
}
function startMatch(match) {
match.opponents = [];
for (let i = 0; i < match.totalPlayers - 1; i++) {
match.opponents.push({ chips: match.totalPlayers * 1000, active: true });
}
match.myChips = match.totalPlayers * 1000;
match.avgChips = match.totalPlayers * 1000;
match.logs = [`比赛开始!共 ${match.totalPlayers} 名选手`];
match.level = 1;
match.blinds = 20;
if (!match.isManual) {
match.intervalId = setInterval(() => {
if (match.status !== 'running') return;
simulateHand(match);
}, 1500);
} else {
simulateHand(match);
}
}
function simulateHand(match) {
if (match.status === 'finished') return;
const activePlayers = match.opponents.filter(o => o.active).length + 1;
if (activePlayers <= 1) {
finishMatch(match, 1);
return;
}
match.remainingPlayers = activePlayers;
match.level = Math.floor(match.logs.length / 10) + 1;
match.blinds = 20 + (match.level - 1) * 10;
const deck = createDeck();
shuffle(deck);
match.hand = deck.slice(0, 2);
const community = deck.slice(2, 7);
match.board = community;
const playerHand = getHandType7(match.hand.concat(community));
const playerScore = playerHand.score;
const theory = getTheoryValue();
const exp = state.tournament.practicalExp || 1;
const skillPower = theory * exp;
let winRate = Math.min(playerScore / 1000, 0.8);
winRate += skillPower / 10000;
winRate = Math.min(winRate, 0.95);
const isWin = Math.random() < winRate;
const pot = match.blinds * 2 + Math.floor(Math.random() * 200);
if (isWin) {
match.myChips += pot;
match.logs.push(`✅ 赢下底池 $${pot},当前筹码 $${match.myChips}`);
} else {
match.myChips -= pot;
if (match.myChips < 0) match.myChips = 0;
match.logs.push(`❌ 输掉底池 $${pot},当前筹码 $${match.myChips}`);
}
for (let opp of match.opponents) {
if (!opp.active) continue;
if (Math.random() < 0.05) {
opp.active = false;
match.logs.push(`💀 一名选手被淘汰`);
}
}
if (match.myChips <= 0) {
match.myChips = 0;
finishMatch(match, activePlayers);
return;
}
if (match.isManual) {
match.waitingForAction = true;
const actionPromise = new Promise((resolve) => {
match.actionResolve = resolve;
});
renderTournament();
return;
}
renderTournament();
}
function finishMatch(match, rank) {
match.status = 'finished';
if (match.intervalId) clearInterval(match.intervalId);
const totalPrize = match.buyin * match.totalPlayers;
const winners = Math.floor(match.totalPlayers * 0.15);
let prize = 0;
if (rank <= winners) {
const factor = Math.max(0.02, 0.20 - (rank - 1) * 0.01);
prize = Math.floor(totalPrize * factor);
if (prize < match.buyin * 2) prize = match.buyin * 2;
} else {
prize = 0;
}
let skillPoints = 0;
if (rank === 1) {
skillPoints = match.skillPointsReward;
} else if (rank <= 3) {
skillPoints = Math.floor(match.skillPointsReward * 0.2);
} else if (rank <= 10) {
skillPoints = Math.floor(match.skillPointsReward * 0.05);
}
match.result = { rank, prize, skillPoints };
match.logs.push(`🏁 比赛结束!名次 ${rank},奖金 $${prize},技能点 ${skillPoints}`);
renderTournament();
}
// ---------- 存档 ----------
function saveGame() {
const data = {
gold: state.gold,
spinCount: state.spinCount,
lastScore: state.lastScore,
talents: state.talents,
handsSinceLastSkill: state.handsSinceLastSkill || 0,
autoEnabled: state.autoEnabled,
scoreHistory: state.scoreHistory,
lastSaveTime: Date.now(),
tournament: {
practicalExp: state.tournament.practicalExp || 1,
skillPoints: state.tournament.skillPoints || 0,
}
};
localStorage.setItem('pokerSlotData', JSON.stringify(data));
}
function loadGame() {
const raw = localStorage.getItem('pokerSlotData');
if (!raw) return false;
try {
const data = JSON.parse(raw);
state.gold = data.gold ?? 100;
state.spinCount = data.spinCount ?? 0;
state.lastScore = data.lastScore ?? 0;
state.talents = data.talents || {};
state.handsSinceLastSkill = data.handsSinceLastSkill || 0;
state.autoEnabled = data.autoEnabled || false;
state.scoreHistory = data.scoreHistory || [];
state.tournament.practicalExp = data.tournament?.practicalExp || 1;
state.tournament.skillPoints = data.tournament?.skillPoints || 0;
const defaultTalents = {
removeProgress: 0, smallJoker: 0, bigJoker: 0, boostAce: 0,
lineUnlock: 0, autoSpin: 0, skillUnlock: 0, theory: 0,
};
for (let key in defaultTalents) {
if (!(key in state.talents)) state.talents[key] = defaultTalents[key];
}
if (state.talents.autoSpin < 1) state.autoEnabled = false;
return true;
} catch(e) { return false; }
}
// ---------- 重置 ----------
function resetGame() {
if (!confirm('重置所有数据(包括金币和天赋)?')) return;
stopAutoSpin();
if (animFrameId) cancelAnimationFrame(animFrameId);
isAnimating = false;
state.gold = 100;
state.spinCount = 0;
state.lastScore = 0;
state.talents = {
removeProgress: 0, smallJoker: 0, bigJoker: 0, boostAce: 0,
lineUnlock: 0, autoSpin: 0, skillUnlock: 0, theory: 0,
};
state.handsSinceLastSkill = 0;
state.autoEnabled = false;
state.scoreHistory = [];
state.lastSaveTime = Date.now();
state.tournament.practicalExp = 1;
state.tournament.skillPoints = 0;
state.tournament.currentMatch = null;
localStorage.removeItem('pokerSlotData');
const initial = drawHandAndBoard();
renderHand(initial.hand);
renderGrid(initial.board);
updateScoreDetail([]);
updateUI();
if (autoTimer) clearInterval(autoTimer);
renderTournament();
saveGame();
}
// ---------- 初始化 ----------
function initGame() {
loadGame();
// 构建天赋面板
buildTalentPanels();
// 初始牌局
const initial = drawHandAndBoard();
renderHand(initial.hand);
renderGrid(initial.board);
updateScoreDetail([]);
updateUI();
// 事件绑定
spinBtn.addEventListener('click', performSpin);
autoToggle.addEventListener('click', toggleAuto);
resetBtn.addEventListener('click', resetGame);
// 天赋子标签切换
document.querySelectorAll('.talent-tabs .tab-btn-sub').forEach(tab => {
tab.addEventListener('click', function() {
document.querySelectorAll('.talent-tabs .tab-btn-sub').forEach(t => t.classList.remove('active'));
this.classList.add('active');
document.querySelectorAll('.talent-panel').forEach(p => p.classList.remove('active'));
document.getElementById(this.dataset.tab).classList.add('active');
});
});
// 自动开启(若存档中有)
if (state.talents.autoSpin >= 1 && state.autoEnabled) {
startAutoSpin();
}
// 渲染锦标赛
renderTournament();
// 主标签切换
document.querySelectorAll('.tab-nav .tab-btn').forEach(btn => {
btn.addEventListener('click', function() {
document.querySelectorAll('.tab-nav .tab-btn').forEach(b => b.classList.remove('active'));
this.classList.add('active');
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
const pageId = this.dataset.page;
document.getElementById('page-' + pageId).classList.add('active');
if (pageId === 'tournament') renderTournament();
if (pageId === 'adventure') updateUI();
});
});
// 自动保存
setInterval(saveGame, 30000);
window.addEventListener('beforeunload', saveGame);
}
// 启动
window.onload = initGame;
</script>
</body>
</html>Game Source: 重生之我解锁了德州登神系统
Creator: RocketPanda33
Libraries: none
Complexity: complex (1647 lines, 62.9 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: game-rocketpanda33" to link back to the original. Then publish at arcadelab.ai/publish.