fix(sim): 빈 인카운터 즉시 승리·타겟 타이브레이크 결정성·주석·draw/empty 테스트

This commit is contained in:
2026-06-10 00:57:53 +09:00
parent 4ef3d1811d
commit de23829439
2 changed files with 22 additions and 2 deletions

View File

@@ -52,6 +52,8 @@ export function loadData() {
return { cards: cardsData.cards, starterDeck: cardsData.starterDeck, monsters };
}
// 주의: 인게임은 플레이어가 카드를 직접 선택한다. 이 chooseAction은 밸런스 추정용 자동 플레이 휴리스틱일 뿐
// 이며, Lua에 대응 AI가 없다(동기화 대상은 데미지/방어/의도/승패 규칙이지 플레이어 선택이 아님).
// 손패에서 낼 카드 인덱스(-1=종료). 공격 우선, 없으면 스킬.
export function chooseAction(hand, cards, energy) {
const entries = hand.map((id, i) => ({ id, i })).filter((x) => cards[x.id].cost <= energy);
@@ -70,7 +72,7 @@ export function chooseTarget(aliveMonsters, plannedDamage) {
const eff = (m) => m.hp + m.block;
const killable = aliveMonsters.filter((m) => eff(m) <= plannedDamage);
const pool = killable.length ? killable : aliveMonsters;
return pool.slice().sort((a, b) => eff(a) - eff(b))[0];
return pool.slice().sort((a, b) => eff(a) - eff(b) || pool.indexOf(a) - pool.indexOf(b))[0];
}
function bump(s, cost, dmg, blk) {
@@ -83,6 +85,7 @@ function bump(s, cost, dmg, blk) {
// 반환: { win, turns, playerHpRemaining, draw? }
export function simulateCombat(data, rng, stats) {
const { cards, starterDeck, monsters } = data;
if (monsters.length === 0) return { win: true, turns: 0, playerHpRemaining: PLAYER_HP };
let drawPile = shuffle(starterDeck, rng);
let discard = [];
let hand = [];
@@ -128,7 +131,7 @@ export function simulateCombat(data, rng, stats) {
discard.push(...hand); hand = [];
for (const m of mob) {
if (!m.alive) continue;
m.block = 0;
m.block = 0; // 매 턴 초기화 (이전 턴 블록 미이월)
const it = m.intents[m.intentIdx];
if (it) {
if (it.kind === 'Attack') { const r = applyDamage(pHp, pBlock, it.value); pHp = r.hp; pBlock = r.block; }