feat(combat): 몬스터 랜덤 구성·랜덤 행동·덱 오염(AddCard)·map01 로스터 (P14-3)
- 구성 랜덤화: BuildMonsters를 그룹별 수집 후 노드 타입별 추첨 (일반 1~3 / 엘리트 1+일반0~2 / 보스 1, MAX_MONSTERS=4 내) - 행동 랜덤화: EnemyActStep 순차(round-robin) → 정의된 intent 중 math.random 선택 (스폰 시 시작 intent도 랜덤, sim-balance.mjs 미러 동기화) - StS2식 덱 오염: AddCard intent 신규 — 저주 카드(Wound/Burn)를 버린 더미에 추가 · Wound=사용불가 사석, Burn=사용불가+턴종료 피해2(EndPlayerTurn 처리) · PlayCard unplayable 가드, CardPool class 필터로 보상/상점 자동 제외 · luaIntentsArray(card/count)·luaCardsTable(unplayable/curse/endTurnDamage) 직렬화 - map01 인카운터: 일반 5종(주황/초록/빨강달팽이/파랑/돼지) + 엘리트1 + 보스1, 우측 포메이션 · enemies.json red_snail/stump 신규, blue_mushroom/mushmom에 AddCard intent · gen-map-encounters 레이아웃 맵별 분기 + 풀 인덱싱 일반화 - 막 배율 0.6→0.45(5막 기준 완화). sim 테스트 35/35 통과(신규 3 포함). 산출물 재생성 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -74,7 +74,7 @@ export function loadData() {
|
||||
// 이며, Lua에 대응 AI가 없다(동기화 대상은 데미지/방어/의도/승패 규칙이지 플레이어 선택이 아님).
|
||||
// 손패에서 낼 카드 인덱스(-1=종료). 파워 우선(지속 가치) → 공격 → 스킬.
|
||||
export function chooseAction(hand, cards, energy) {
|
||||
const entries = hand.map((id, i) => ({ id, i })).filter((x) => cards[x.id].cost <= energy);
|
||||
const entries = hand.map((id, i) => ({ id, i })).filter((x) => cards[x.id] && cards[x.id].cost <= energy && !cards[x.id].unplayable);
|
||||
const powers = entries.filter((x) => cards[x.id].kind === 'Power');
|
||||
const attacks = entries.filter((x) => cards[x.id].kind === 'Attack');
|
||||
const skills = entries.filter((x) => cards[x.id].kind === 'Skill');
|
||||
@@ -202,7 +202,12 @@ export function simulateCombat(data, rng, stats) {
|
||||
if (c.draw) draw(c.draw);
|
||||
if (aliveList().length === 0) return { win: true, turns, playerHpRemaining: pHp };
|
||||
}
|
||||
// 화상(endTurnDamage) — 손패에 있으면 턴 종료 시 피해 (Lua EndPlayerTurn 동기화)
|
||||
let burn = 0;
|
||||
for (const hid of hand) { const hc = cards[hid]; if (hc && hc.endTurnDamage) burn += hc.endTurnDamage; }
|
||||
if (burn > 0) { pHp -= burn; if (pHp < 0) pHp = 0; }
|
||||
discard.push(...hand); hand = [];
|
||||
if (pHp <= 0) return { win: false, turns, playerHpRemaining: 0 };
|
||||
// 플레이어 디버프 감소 — Lua EndPlayerTurn 동기화 (적 행동 전)
|
||||
if (pWeak > 0) pWeak--;
|
||||
if (pVuln > 0) pVuln--;
|
||||
@@ -215,7 +220,8 @@ export function simulateCombat(data, rng, stats) {
|
||||
if (m.hp <= 0) { m.hp = 0; m.alive = false; continue; }
|
||||
}
|
||||
m.block = 0; // 매 턴 초기화 (이전 턴 블록 미이월)
|
||||
const it = m.intents[m.intentIdx];
|
||||
// 정의된 intent 중 랜덤 선택 (Lua EnemyActStep 동기화 — 순차→랜덤)
|
||||
const it = m.intents.length ? m.intents[Math.floor(rng() * m.intents.length)] : null;
|
||||
if (it) {
|
||||
if (it.kind === 'Attack') {
|
||||
const atk = calcAttack(it.value, m.str, m.weak, pVuln);
|
||||
@@ -224,9 +230,12 @@ export function simulateCombat(data, rng, stats) {
|
||||
else if (it.kind === 'Debuff') {
|
||||
if (it.effect === 'weak') pWeak += it.value;
|
||||
else if (it.effect === 'vuln') pVuln += it.value;
|
||||
} else if (it.kind === 'AddCard') {
|
||||
// StS2식 덱 오염 — 저주 카드를 버린 더미에 추가 (Lua 동기화)
|
||||
const cnt = it.count || 1;
|
||||
for (let k = 0; k < cnt; k++) discard.push(it.card);
|
||||
}
|
||||
}
|
||||
m.intentIdx = (m.intentIdx + 1) % m.intents.length;
|
||||
// 적 디버프 감소 — Lua EnemyActStep 동기화 (자기 행동 후)
|
||||
if (m.weak > 0) m.weak--;
|
||||
if (m.vuln > 0) m.vuln--;
|
||||
|
||||
Reference in New Issue
Block a user