도시 대상 명령 입력에 Ref 거리별 도시 목록 복원

Ref ProcessCity의 `N칸 떨어진 도시:` 목록(CitiesBasedOnDistance)을 전체화면 인자 입력에
되살린다. 이동·출병 1칸, 강행·첩보·화계·탈취·파괴·선동 3칸을 map layout path의 BFS로
Ref searchDistance와 같은 순서로 나열하고, 거리별 magenta/orange/yellow 밑줄 도시를 누르면
대상 도시를 고른다. che 지도 94개 도시 결과를 Ref PHP 원문 실행 fixture와 비교하는 unit test와
1200/390px e2e를 추가한다.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Hide_DandClaude Opus 5.5 committed 2026-09-23 14:35:40 +00:00
1 parent b90dd4499d
commit 0a9c47eddf
5 files changed
+282 -2

No files matched your search

@@ -0,0 +1,61 @@
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import test from 'node:test';
import { COMMAND_CITY_DISTANCE_RANGE, citiesBasedOnDistance } from '../src/components/command/commandCityDistance.ts';
type MapDefinition = { cities: Array<{ id: number; connections: number[] }> };
const readJson = <T>(path: string): T => JSON.parse(readFileSync(new URL(path, import.meta.url), 'utf8')) as T;
// Ref `hwe/func.php` searchDistance와 `func_legacy.php` JSCitiesBasedOnDistance 원문을
// che 지도 CityConst로 실행한 결과(2026-09-23, 생성 절차는 report 참고)다.
const refDistances = readJson<Record<string, Record<string, number[]>>>(
'./fixtures/ref-cities-based-on-distance-che.json'
);
const cheMap = readJson<MapDefinition>('../../../resources/map/map_che.json');
const cityList = cheMap.cities.map((city) => ({ id: city.id, path: city.connections }));
void test('matches Ref JSCitiesBasedOnDistance order for every che city up to 3 distance', () => {
assert.equal(Object.keys(refDistances).length, cityList.length);
for (const city of cityList) {
const expected = refDistances[String(city.id)];
assert.ok(expected, `Ref fixture has city ${city.id}`);
const actual = citiesBasedOnDistance(city.id, cityList, 3);
assert.deepEqual(
Object.fromEntries(actual.map((group) => [String(group.distance), group.cityIds])),
expected,
`city ${city.id}`
);
assert.deepEqual(citiesBasedOnDistance(city.id, cityList, 1), [{ distance: 1, cityIds: expected['1'] }]);
}
});
void test('keeps empty distance rows like Ref when the graph ends early', () => {
const tiny = [
{ id: 1, path: [2] },
{ id: 2, path: [1] },
];
assert.deepEqual(citiesBasedOnDistance(1, tiny, 3), [
{ distance: 1, cityIds: [2] },
{ distance: 2, cityIds: [] },
{ distance: 3, cityIds: [] },
]);
assert.deepEqual(citiesBasedOnDistance(99, tiny, 1), [{ distance: 1, cityIds: [] }]);
});
void test('uses the Ref ProcessCity distance for each general city command', () => {
assert.deepEqual(COMMAND_CITY_DISTANCE_RANGE, {
che_이동: 1,
che_출병: 1,
che_강행: 3,
che_첩보: 3,
che_화계: 3,
che_탈취: 3,
che_파괴: 3,
che_선동: 3,
});
for (const nationCommand of ['che_천도', 'che_수몰', 'che_허보', 'che_초토화', 'che_백성동원', 'che_발령']) {
assert.equal(Object.hasOwn(COMMAND_CITY_DISTANCE_RANGE, nationCommand), false);
}
});
@@ -0,0 +1 @@
{"1":{"1":[9,18,70,78,80],"2":[36,77,19,81,3,23,35,2,74],"3":[37,8,17,38,39,71,72,42,65,10]},"2":{"1":[10,19,38,71,74,80],"2":[20,51,18,81,11,12,3,1],"3":[6,25,78,39,82,21,70,72,9]},"3":{"1":[70,71,72,74],"2":[1,23,35,2,10,42,19,80],"3":[9,18,78,65,17,38,20,51,73,81]},"4":{"1":[41,73,86],"2":[40,85,42,24,75],"3":[22,64,84,43,23,72,25,44]},"5":{"1":[26,27,87],"2":[44,45,46,47],"3":[75,76,88,28,48]},"6":{"1":[51,89],"2":[10,25,13,52,91],"3":[2,20,71,24,14,53,88,15,56]},"7":{"1":[31,82,83],"2":[57,92,93,11,56,21],"3":[67,15,33,34,62,12,20,38,91]},"8":{"1":[77,90],"2":[9,17,60,68],"3":[1,36,35,16,61,79]},"9":{"1":[1,36,77],"2":[18,70,78,80,37,8,17],"3":[19,81,3,23,35,2,74,39,79,90]},"10":{"1":[2,20,51,71],"2":[19,38,74,80,11,6,25,3],"3":[18,81,12,1,82,89,24,70,72]},"11":{"1":[12,20,38,82],"2":[21,39,10,2,81,7,56],"3":[83,37,51,71,19,74,80,18,31,15,91,92]},"12":{"1":[11,21,38,39],"2":[20,82,83,2,81,37],"3":[10,7,56,19,71,74,80,18,36,79]},"13":{"1":[14,53,88,89],"2":[15,30,54,45,6,52,91],"3":[56,58,92,55,49,26,27,51]},"14":{"1":[13,15,30,53,54],"2":[88,89,56,58,91,92,55,49],"3":[45,6,52,82,67,31,29,28,66]},"15":{"1":[14,56,58,91,92],"2":[13,30,53,54,82,55,67,52,89,31],"3":[88,49,7,11,29,57,59,6,93]},"16":{"1":[32,33,34,60,79],"2":[61,93,62,90,37],"3":[68,31,69,8,36,39]},"17":{"1":[35,77],"2":[23,70,8,9],"3":[42,65,1,3,90,36]},"18":{"1":[1,19,78,81],"2":[9,70,80,2,74,36,38,39],"3":[77,3,23,35,10,71,37,11,12,20]},"19":{"1":[2,18,74,80,81],"2":[10,38,71,1,78,3,39],"3":[20,51,11,12,9,Line truncated