165 lines
4.8 KiB
TypeScript
165 lines
4.8 KiB
TypeScript
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),
|
||
};
|
||
}
|