refactor: simplify buildCombatState and fix formatting in encounter

system

Refactor `buildCombatState` to derive encounter data directly from
`runState` instead of requiring it as an argument. Also apply
consistent 2-space indentation and formatting to the encounter
lifecycle module.
This commit is contained in:
hypercross 2026-04-19 15:42:47 +08:00
parent 89d96d838b
commit 8142fbfa60
1 changed files with 151 additions and 139 deletions

View File

@ -1,9 +1,19 @@
import type { PointCrawlMap } from '../map/types';
import type { CombatState, EnemyEntity, PlayerEntity, EffectTable } from '../combat/types';
import type { EncounterData, EnemyData, EffectData, IntentData } from '../types';
import type { RunState } from './types';
import { generateDeckFromInventory } from '../deck/factory';
import { ReadonlyRNG } from '@/utils/rng';
import type { PointCrawlMap } from "../map/types";
import type {
CombatState,
EnemyEntity,
PlayerEntity,
EffectTable,
} from "../combat/types";
import type {
EncounterData,
EnemyData,
EffectData,
IntentData,
} from "../types";
import type { RunState } from "./types";
import { generateDeckFromInventory } from "../deck/factory";
import { ReadonlyRNG } from "@/utils/rng";
// -- Encounter assignment to nodes --
@ -14,7 +24,7 @@ import { ReadonlyRNG } from '@/utils/rng';
export function assignEncounterToNode(
map: PointCrawlMap,
nodeId: string,
encounter: EncounterData
encounter: EncounterData,
): void {
const node = map.nodes.get(nodeId);
if (!node) {
@ -30,12 +40,12 @@ export function assignEncounterToNode(
export function assignEncountersFromPool(
map: PointCrawlMap,
encounterPool: EncounterData[],
rng: ReadonlyRNG
rng: ReadonlyRNG,
): void {
if (encounterPool.length === 0) return;
for (const node of map.nodes.values()) {
if (node.type === 'start' || node.type === 'end') continue;
if (node.type === "start" || node.type === "end") continue;
if (node.encounter) continue;
const assigned = encounterPool[rng.nextInt(encounterPool.length)];
@ -50,10 +60,10 @@ export function assignEncountersFromPool(
export function assignAllEncounters(
map: PointCrawlMap,
encounterIndex: Map<string, EncounterData[]>,
rng: ReadonlyRNG
rng: ReadonlyRNG,
): void {
for (const node of map.nodes.values()) {
if (node.type === 'start' || node.type === 'end') continue;
if (node.type === "start" || node.type === "end") continue;
if (node.encounter) continue;
const encounterType = node.type;
@ -72,10 +82,11 @@ export function assignAllEncounters(
* - Creates PlayerEntity with energy (3), deck from inventory, and HP from run state
* - Sets initial phase to 'playerTurn', turn 1
*/
export function buildCombatState(
encounter: EncounterData,
runState: RunState,
): CombatState {
export function buildCombatState(runState: RunState): CombatState {
const encounter = getCurrentEncounterData(runState);
if (!encounter)
throw new Error(`No encounter found for node ${runState.currentNodeId}`);
const enemies = createEnemyEntities(encounter);
const deck = generateDeckFromInventory(runState.inventory);
const player = createPlayerEntity(runState, deck);
@ -84,7 +95,7 @@ export function buildCombatState(
enemies,
player,
inventory: runState.inventory,
phase: 'playerTurn',
phase: "playerTurn",
turnNumber: 1,
result: null,
loot: [],
@ -95,9 +106,7 @@ export function buildCombatState(
* Creates EnemyEntity instances from encounter enemy definitions.
* Each enemy gets: HP from encounter tuple, initial buffs from encounter, intents from enemy definition.
*/
export function createEnemyEntities(
encounter: EncounterData,
): EnemyEntity[] {
export function createEnemyEntities(encounter: EncounterData): EnemyEntity[] {
const enemies: EnemyEntity[] = [];
let instanceCounter = 0;
@ -126,9 +135,7 @@ export function createEnemyEntities(
/**
* Builds a map of intent ID -> IntentData for an enemy.
*/
function buildIntentMap(
enemy: EnemyData,
): Record<string, IntentData> {
function buildIntentMap(enemy: EnemyData): Record<string, IntentData> {
const intents: Record<string, IntentData> = {};
for (const intent of enemy.intents) {
intents[intent.id] = intent;
@ -165,7 +172,10 @@ function buildEffectTable(buffs: readonly [EffectData, number][]): EffectTable {
/**
* Creates a PlayerEntity from the run state and deck.
*/
function createPlayerEntity(runState: RunState, deck: ReturnType<typeof generateDeckFromInventory>): PlayerEntity {
function createPlayerEntity(
runState: RunState,
deck: ReturnType<typeof generateDeckFromInventory>,
): PlayerEntity {
return {
id: "player",
hp: runState.player.currentHp,
@ -184,7 +194,9 @@ function createPlayerEntity(runState: RunState, deck: ReturnType<typeof generate
/**
* Gets the encounter data for the current node.
*/
export function getCurrentEncounterData(runState: RunState): EncounterData | undefined {
export function getCurrentEncounterData(
runState: RunState,
): EncounterData | undefined {
const node = runState.map.nodes.get(runState.currentNodeId);
return node?.encounter;
}
@ -207,7 +219,7 @@ export function startEncounter(runState: RunState): CombatState | null {
return null;
}
return buildCombatState(encounter, runState);
return buildCombatState(runState);
}
/**
@ -217,10 +229,10 @@ export function startEncounter(runState: RunState): CombatState | null {
*/
export function resolveCombatEncounter(
runState: RunState,
combatState: CombatState
combatState: CombatState,
): { success: true } | { success: false; reason: string } {
if (runState.currentEncounter.resolved) {
return { success: false, reason: '该遭遇已解决' };
return { success: false, reason: "该遭遇已解决" };
}
// Apply HP from combat state back to run state
@ -228,7 +240,7 @@ export function resolveCombatEncounter(
// Apply loot
for (const loot of combatState.loot) {
if (loot.type === 'gold') {
if (loot.type === "gold") {
runState.player.gold += loot.amount;
}
// Item rewards are handled by the caller via addItem()