import { HANDS, jokerDef } from '../content/catalog'; import { evaluate } from './poker'; import { random } from './random'; import type { Card, Run, Score, Trace } from './types'; export const debuffed = (run: Run, c: Card) => run.blind === 2 && ((run.bosses[run.ante - 1] === 0 && c.suit === 0) || (run.bosses[run.ante - 1] === 1 && c.suit === 1)); export function scoreHand(run: Run, played: Card[]): Score { const e = evaluate(played), h = HANDS[e.hand], lv = run.levels[e.hand] - 1; let chips: number = h[1] + h[3] * lv, mult: number = h[2] + h[4] * lv; if (run.blind === 2 && run.bosses[run.ante - 1] === 5) { chips = Math.ceil(chips / 2); mult = Math.ceil(mult / 2); } const trace: Trace[] = [{ source: e.name, label: `${e.name} · Lv.${lv + 1}`, chips, mult }]; const add = ( source: string, label: string, c = 0, m = 0, x = 1, cardId?: number, jokerUid?: number, ) => { chips += c; mult = (mult + m) * x; trace.push({ source, label, chips, mult, cardId, jokerUid }); }; run.counts[e.hand]++; for (const j of run.jokers) if (j.id === 'growth') j.value += 2; e.scoring.forEach((c, index) => { if (debuffed(run, c)) { trace.push({ source: '失效', label: 'Boss 封印', chips, mult, cardId: c.id }); return; } const times = 1 + (c.seal ? 1 : 0) + (index === 0 ? run.jokers.filter((j) => j.id === 'echo').length : 0); for (let t = 0; t < times; t++) { add( t ? '再次触发' : '扑克牌', `+${c.rank === 14 ? 11 : Math.min(c.rank, 10)} 筹码`, c.rank === 14 ? 11 : Math.min(c.rank, 10), 0, 1, c.id, ); if (c.enh === 'bonus') add('奖励牌', '+30 筹码', 30, 0, 1, c.id); if (c.enh === 'mult') add('多倍牌', '+4 倍率', 0, 4, 1, c.id); if (c.enh === 'glass') add('玻璃牌', '×2 倍率', 0, 0, 2, c.id); for (const j of run.jokers) { let cb = 0, mb = 0; if (j.id === 'hearts' && c.suit === 0) mb = 4; if (j.id === 'spades' && c.suit === 1) cb = 35; if (j.id === 'clubs' && c.suit === 3) mb = 4; if (j.id === 'diamonds' && c.suit === 2) { run.money++; add(jokerDef(j.id).name, '+$1', 0, 0, 1, c.id, j.uid); } if (j.id === 'faces' && c.rank >= 11 && c.rank <= 13) mb = 5; if (j.id === 'aces' && c.rank === 14) { cb = 30; mb = 4; } if (j.id === 'even' && [2, 4, 6, 8, 10].includes(c.rank)) mb = 4; if (j.id === 'odd' && [3, 5, 7, 9, 14].includes(c.rank)) cb = 35; if (cb || mb) add( jokerDef(j.id).name, [cb ? `+${cb} 筹码` : '', mb ? `+${mb} 倍率` : ''].filter(Boolean).join(' · '), cb, mb, 1, c.id, j.uid, ); } } }); const selected = new Set(played.map((c) => c.id)); for (const id of run.hand) { const c = run.cards.find((c) => c.id === id)!; if (selected.has(id) || debuffed(run, c)) continue; for (let t = 0; t < (c.seal ? 2 : 1); t++) { if (c.enh === 'steel') add('钢铁牌', '×1.5 倍率', 0, 0, 1.5, c.id); for (const j of run.jokers) if (j.id === 'moon' && c.rank === 12) add('月后', '+13 倍率', 0, 13, 1, c.id, j.uid); } } for (const j of run.jokers) { let cb = 0, mb = 0, x = 1; switch (j.id) { case 'smile': mb = 4; break; case 'pair': if (e.pair) mb = 8; break; case 'triple': if (e.triple) mb = 20; break; case 'flush': if (e.flush) mb = 18; break; case 'straight': if (e.straight) cb = 100; break; case 'chips': cb = 80; break; case 'double': if (e.pair) x = 2; break; case 'minimal': if (played.length <= 3) x = 2; break; case 'growth': mb = j.value; break; case 'scholar': mb = run.counts[e.hand] * 3; break; case 'bank': mb = Math.floor(run.money / 5) * 4; break; case 'last': if (run.hands === 1) x = 3; break; case 'wanderer': x = 1 + run.skips * 0.35; break; } if (cb || mb || x !== 1) add( jokerDef(j.id).name, cb ? `+${cb} 筹码` : mb ? `+${mb} 倍率` : `×${Number(x.toFixed(2))} 倍率`, cb, mb, x, undefined, j.uid, ); } for (const c of e.scoring) if (c.enh === 'glass' && !debuffed(run, c) && random(run, 'effect') < 0.25) { run.cards = run.cards.filter((v) => v.id !== c.id); add('玻璃破碎', '永久移除', 0, 0, 1, c.id); } return { hand: e.hand, name: e.name, chips, mult, total: Math.floor(chips * mult), trace, played: structuredClone(played), }; }