구현
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import type { GeneralTriggerState } from '@sammo-ts/logic/domain/entities.js';
|
||||
import type { Constraint, ConstraintContext } from '@sammo-ts/logic/constraints/types.js';
|
||||
import { mustBeNPC, reqGeneralGold, unknownOrDeny, existsDestCity } from '@sammo-ts/logic/constraints/presets.js';
|
||||
import { mustBeNPC, existsDestCity } from '@sammo-ts/logic/constraints/presets.js';
|
||||
import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition.js';
|
||||
import type {
|
||||
GeneralActionOutcome,
|
||||
@@ -104,7 +104,7 @@ export class ActionDefinition<
|
||||
return null;
|
||||
}
|
||||
|
||||
buildConstraints(ctx: ConstraintContext, args: NPCSelfArgs): Constraint[] {
|
||||
buildConstraints(_ctx: ConstraintContext, args: NPCSelfArgs): Constraint[] {
|
||||
const constraints = [mustBeNPC()];
|
||||
|
||||
if (args.optionText === '순간이동') {
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
nearCity,
|
||||
reqGeneralGold,
|
||||
reqGeneralRice,
|
||||
unknownOrDeny,
|
||||
|
||||
existsDestCity,
|
||||
} from '@sammo-ts/logic/constraints/presets.js';
|
||||
import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition.js';
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import type { City, General, GeneralTriggerState, Nation } from '@sammo-ts/logic/domain/entities.js';
|
||||
import type { City, GeneralTriggerState } from '@sammo-ts/logic/domain/entities.js';
|
||||
import type { Constraint, ConstraintContext } from '@sammo-ts/logic/constraints/types.js';
|
||||
import {
|
||||
allow,
|
||||
// allow,
|
||||
notBeNeutral,
|
||||
notWanderingNation,
|
||||
notCapital,
|
||||
readMetaNumberFromUnknown,
|
||||
unknownOrDeny,
|
||||
// unknownOrDeny,
|
||||
} from '@sammo-ts/logic/constraints/presets.js';
|
||||
import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition.js';
|
||||
import type {
|
||||
@@ -22,7 +22,7 @@ import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'
|
||||
import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js';
|
||||
import type { GeneralTurnCommandSpec } from './index.js';
|
||||
|
||||
export interface ReturnArgs {}
|
||||
export interface ReturnArgs { }
|
||||
|
||||
const ACTION_NAME = '귀환';
|
||||
const ACTION_KEY = 'che_귀환';
|
||||
@@ -100,7 +100,7 @@ export class ActionResolver<
|
||||
}
|
||||
|
||||
const josaRo = JosaUtil.pick(destCityName, '로');
|
||||
const date = 'XX:XX'; // Placeholder for date, actual date is handled by log format usually, but legacy includes it explicitly?
|
||||
// const date = 'XX:XX';
|
||||
// Legacy: <1>$date</>
|
||||
// LogFormat.MONTH handles date prefix usually. LogFormat.HM handles HH:MM.
|
||||
// Legacy output: "StartCity로 귀환했습니다. 10:00"
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { TurnCommandModule, TurnCommandSpecBase } from '@sammo-ts/logic/act
|
||||
export const GENERAL_TURN_COMMAND_KEYS = [
|
||||
'che_거병',
|
||||
'che_임관',
|
||||
'che_랜덤임관',
|
||||
// 'che_랜덤임관',
|
||||
'che_귀환',
|
||||
'che_등용수락',
|
||||
'che_건국',
|
||||
@@ -60,7 +60,7 @@ const defaultImporters: Record<GeneralTurnCommandKey, GeneralTurnCommandImporter
|
||||
che_거병: async () => import('./che_거병.js'),
|
||||
che_임관: async () => import('./che_임관.js'),
|
||||
che_등용수락: () => import('./che_등용수락.js'),
|
||||
che_랜덤임관: () => import('./che_랜덤임관.js'),
|
||||
// che_랜덤임관: () => import('./che_랜덤임관.js'),
|
||||
che_귀환: async () => import('./che_귀환.js'),
|
||||
che_건국: async () => import('./che_건국.js'),
|
||||
che_훈련: async () => import('./che_훈련.js'),
|
||||
|
||||
@@ -541,11 +541,11 @@ export const notCapital = (checkCurrentCity = false): Constraint => ({
|
||||
}
|
||||
}
|
||||
// Need nation to know capital
|
||||
reqs.push({ kind: 'nation', id: ctx.nationId });
|
||||
reqs.push({ kind: 'nation', id: ctx.nationId ?? 0 });
|
||||
return reqs;
|
||||
},
|
||||
test: (ctx, view) => {
|
||||
const nationReq: RequirementKey = { kind: 'nation', id: ctx.nationId };
|
||||
const nationReq: RequirementKey = { kind: 'nation', id: ctx.nationId ?? 0 };
|
||||
if (!view.has(nationReq)) return unknownOrDeny(ctx, [nationReq], '국가 정보가 없습니다.');
|
||||
const nation = view.get(nationReq) as Nation | null;
|
||||
if (!nation) return unknownOrDeny(ctx, [nationReq], '국가 정보가 없습니다.');
|
||||
|
||||
@@ -181,11 +181,11 @@ export const existsDestGeneral = (): Constraint => ({
|
||||
requires: (ctx) =>
|
||||
resolveDestGeneralId(ctx) !== undefined
|
||||
? [
|
||||
{
|
||||
kind: 'destGeneral',
|
||||
id: resolveDestGeneralId(ctx) ?? 0,
|
||||
},
|
||||
]
|
||||
{
|
||||
kind: 'destGeneral',
|
||||
id: resolveDestGeneralId(ctx) ?? 0,
|
||||
},
|
||||
]
|
||||
: [],
|
||||
test: (ctx, view) => {
|
||||
const destGeneralId = resolveDestGeneralId(ctx);
|
||||
@@ -292,8 +292,8 @@ export const mustBeNPC = (): Constraint => ({
|
||||
if (!general) {
|
||||
return unknownOrDeny(ctx, [req], '장수 정보가 없습니다.');
|
||||
}
|
||||
// Assuming npcState >= 2 means NPC. Need to verify exact logic if possible,
|
||||
// but typically 0=human, 1=?, 2=NPC.
|
||||
// Assuming npcState >= 2 means NPC. Need to verify exact logic if possible,
|
||||
// but typically 0=human, 1=?, 2=NPC.
|
||||
// Legacy: $general->getNPC() where 0:User, 1:Virtual User(unused?), 2:NPC ...
|
||||
if (general.npcState >= 2) {
|
||||
return allow();
|
||||
|
||||
@@ -10,7 +10,7 @@ export const getCityDistance = (map: MapDefinition, startCityId: number, endCity
|
||||
while (queue.length > 0) {
|
||||
const [currentId, dist] = queue.shift()!;
|
||||
|
||||
const cityDef = map.cities.find(c => c.id === currentId);
|
||||
const cityDef = map.cities.find((c) => c.id === currentId);
|
||||
if (!cityDef) continue;
|
||||
|
||||
for (const neighborId of cityDef.connections) {
|
||||
@@ -26,3 +26,32 @@ export const getCityDistance = (map: MapDefinition, startCityId: number, endCity
|
||||
|
||||
return Infinity;
|
||||
};
|
||||
|
||||
export const searchDistance = (map: MapDefinition, startCityId: number, range: number): Record<number, number> => {
|
||||
const result: Record<number, number> = {};
|
||||
const visited = new Set<number>();
|
||||
const queue: [number, number][] = [[startCityId, 0]];
|
||||
|
||||
visited.add(startCityId);
|
||||
result[startCityId] = 0;
|
||||
|
||||
while (queue.length > 0) {
|
||||
const [currentId, dist] = queue.shift()!;
|
||||
|
||||
if (dist >= range) continue;
|
||||
|
||||
const cityDef = map.cities.find((c) => c.id === currentId);
|
||||
if (!cityDef) continue;
|
||||
|
||||
for (const neighborId of cityDef.connections) {
|
||||
if (!visited.has(neighborId)) {
|
||||
visited.add(neighborId);
|
||||
const newDist = dist + 1;
|
||||
result[neighborId] = newDist;
|
||||
queue.push([neighborId, newDist]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -111,8 +111,8 @@ describe('che_NPC능동', () => {
|
||||
const runner = new TestGameRunner(world, 200, 1);
|
||||
|
||||
// Add NPC General manually to snapshot
|
||||
const cityId = MINIMAL_MAP.cities[0].id;
|
||||
const destCityId = MINIMAL_MAP.cities[1].id;
|
||||
const cityId = MINIMAL_MAP.cities[0]!.id;
|
||||
const destCityId = MINIMAL_MAP.cities[1]!.id;
|
||||
|
||||
const general: General = {
|
||||
id: 1,
|
||||
@@ -144,7 +144,7 @@ describe('che_NPC능동', () => {
|
||||
};
|
||||
world.snapshot.generals.push(general);
|
||||
|
||||
const outcome = await runner.runTurn([
|
||||
await runner.runTurn([
|
||||
{
|
||||
generalId: general.id,
|
||||
commandKey: 'che_NPC능동',
|
||||
@@ -166,8 +166,8 @@ describe('che_NPC능동', () => {
|
||||
});
|
||||
const world = new InMemoryWorld(bootstrapResult.snapshot);
|
||||
|
||||
const cityId = MINIMAL_MAP.cities[0].id;
|
||||
const destCityId = MINIMAL_MAP.cities[1].id;
|
||||
const cityId = MINIMAL_MAP.cities[0]!.id;
|
||||
const destCityId = MINIMAL_MAP.cities[1]!.id;
|
||||
|
||||
const general: General = {
|
||||
id: 1,
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { getCityDistance, searchDistance } from '@sammo-ts/logic/world/distance.js';
|
||||
import type { MapDefinition } from '@sammo-ts/logic/world/types.js';
|
||||
|
||||
describe('World Distance', () => {
|
||||
const mockMap: MapDefinition = {
|
||||
id: 'test_map',
|
||||
name: 'Test Map',
|
||||
cities: [
|
||||
{ id: 1, connections: [2, 3] },
|
||||
{ id: 2, connections: [1, 4] },
|
||||
{ id: 3, connections: [1, 5] },
|
||||
{ id: 4, connections: [2, 6] },
|
||||
{ id: 5, connections: [3, 7] },
|
||||
{ id: 6, connections: [4] },
|
||||
{ id: 7, connections: [5] },
|
||||
// Isolated city
|
||||
{ id: 8, connections: [] },
|
||||
] as any[],
|
||||
};
|
||||
|
||||
describe('getCityDistance', () => {
|
||||
it('should return 0 for same city', () => {
|
||||
expect(getCityDistance(mockMap, 1, 1)).to.equal(0);
|
||||
});
|
||||
|
||||
it('should return 1 for adjacent cities', () => {
|
||||
expect(getCityDistance(mockMap, 1, 2)).to.equal(1);
|
||||
expect(getCityDistance(mockMap, 1, 3)).to.equal(1);
|
||||
});
|
||||
|
||||
it('should return correct distance for distant cities', () => {
|
||||
expect(getCityDistance(mockMap, 1, 4)).to.equal(2); // 1-2-4
|
||||
expect(getCityDistance(mockMap, 1, 6)).to.equal(3); // 1-2-4-6
|
||||
});
|
||||
|
||||
it('should return Infinity for unreachable cities', () => {
|
||||
expect(getCityDistance(mockMap, 1, 8)).to.equal(Infinity);
|
||||
});
|
||||
});
|
||||
|
||||
describe('searchDistance', () => {
|
||||
it('should include start city with distance 0', () => {
|
||||
const result = searchDistance(mockMap, 1, 0);
|
||||
expect(result).to.deep.equal({ 1: 0 });
|
||||
});
|
||||
|
||||
it('should find cities within range 1', () => {
|
||||
const result = searchDistance(mockMap, 1, 1);
|
||||
expect(result).to.deep.equal({
|
||||
1: 0,
|
||||
2: 1,
|
||||
3: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('should find cities within range 2', () => {
|
||||
const result = searchDistance(mockMap, 1, 2);
|
||||
expect(result).to.deep.equal({
|
||||
1: 0,
|
||||
2: 1,
|
||||
3: 1,
|
||||
4: 2,
|
||||
5: 2,
|
||||
});
|
||||
});
|
||||
|
||||
it('should not include unreachable cities', () => {
|
||||
const result = searchDistance(mockMap, 1, 10);
|
||||
expect(result).not.to.have.property('8');
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user