first commit

This commit is contained in:
richarjiang
2026-09-21 14:45:24 +08:00
commit 12380c715c
34 changed files with 6609 additions and 0 deletions

164
src/core/scoring.ts Normal file
View File

@@ -0,0 +1,164 @@
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),
};
}