fix(turn): 기본 사령턴 목록과 분류를 Ref에 맞춤
Ref 사령부 조작기의 24개 공개 명령을 기본 프로필에 모두 포함하고 카테고리와 내부 순서를 그대로 투영한다.\n\n35개 국가 명령 차등 실행과 조건·로그 비교, 실제 Chromium 목록 검사를 추가한다.
This commit is contained in:
@@ -10,6 +10,7 @@ import type {
|
||||
GeneralTurnCommandKey,
|
||||
MapDefinition,
|
||||
Nation,
|
||||
NationTurnCommandKey,
|
||||
NationTurnCommandSpec,
|
||||
RequirementKey,
|
||||
StateView,
|
||||
@@ -150,6 +151,54 @@ const REF_GENERAL_COMMAND_POSITION = new Map<string, { category: string; index:
|
||||
)
|
||||
);
|
||||
|
||||
const REF_NATION_COMMAND_GROUPS = [
|
||||
{
|
||||
category: '휴식',
|
||||
commands: ['휴식'],
|
||||
},
|
||||
{
|
||||
category: '인사',
|
||||
commands: ['che_발령', 'che_포상', 'che_몰수', 'che_부대탈퇴지시'],
|
||||
},
|
||||
{
|
||||
category: '외교',
|
||||
commands: ['che_물자원조', 'che_불가침제의', 'che_선전포고', 'che_종전제의', 'che_불가침파기제의'],
|
||||
},
|
||||
{
|
||||
category: '특수',
|
||||
commands: ['che_초토화', 'che_천도', 'che_증축', 'che_감축'],
|
||||
},
|
||||
{
|
||||
category: '전략',
|
||||
commands: [
|
||||
'che_필사즉생',
|
||||
'che_백성동원',
|
||||
'che_수몰',
|
||||
'che_허보',
|
||||
'che_의병모집',
|
||||
'che_이호경식',
|
||||
'che_급습',
|
||||
'che_피장파장',
|
||||
],
|
||||
},
|
||||
{
|
||||
category: '기타',
|
||||
commands: ['che_국기변경', 'che_국호변경'],
|
||||
},
|
||||
] as const satisfies ReadonlyArray<{
|
||||
category: string;
|
||||
commands: ReadonlyArray<NationTurnCommandKey>;
|
||||
}>;
|
||||
|
||||
const REF_NATION_CATEGORY_ORDER = new Map<string, number>(
|
||||
REF_NATION_COMMAND_GROUPS.map(({ category }, index) => [category, index] as const)
|
||||
);
|
||||
const REF_NATION_COMMAND_POSITION = new Map<string, { category: string; index: number }>(
|
||||
REF_NATION_COMMAND_GROUPS.flatMap(({ category, commands }) =>
|
||||
commands.map((command, index) => [command, { category, index }] as const)
|
||||
)
|
||||
);
|
||||
|
||||
const INPUT_REQUIREMENT_KINDS = new Set<RequirementKey['kind']>([
|
||||
'destGeneral',
|
||||
'destCity',
|
||||
@@ -665,6 +714,26 @@ const projectRefGeneralCommandGroups = (entries: CommandEntry[]): CommandEntry[]
|
||||
)
|
||||
.map(({ entry }) => entry);
|
||||
|
||||
const projectRefNationCommandGroups = (entries: CommandEntry[]): CommandEntry[] =>
|
||||
entries
|
||||
.map((entry, profileIndex) => {
|
||||
const refPosition = REF_NATION_COMMAND_POSITION.get(entry.definition.key as NationTurnCommandKey);
|
||||
return {
|
||||
entry: refPosition ? { ...entry, category: refPosition.category } : entry,
|
||||
categoryIndex:
|
||||
REF_NATION_CATEGORY_ORDER.get(refPosition?.category ?? entry.category) ?? Number.MAX_SAFE_INTEGER,
|
||||
commandIndex: refPosition?.index ?? profileIndex,
|
||||
profileIndex,
|
||||
};
|
||||
})
|
||||
.sort(
|
||||
(left, right) =>
|
||||
left.categoryIndex - right.categoryIndex ||
|
||||
left.commandIndex - right.commandIndex ||
|
||||
left.profileIndex - right.profileIndex
|
||||
)
|
||||
.map(({ entry }) => entry);
|
||||
|
||||
export const buildTurnCommandTable = async (options: {
|
||||
worldState: WorldStateRow;
|
||||
general: GeneralRow;
|
||||
@@ -697,7 +766,7 @@ export const buildTurnCommandTable = async (options: {
|
||||
|
||||
return {
|
||||
general: buildGroups(projectRefGeneralCommandGroups(generalEntries), ctx, view),
|
||||
nation: buildGroups(nationEntries, ctx, view),
|
||||
nation: buildGroups(projectRefNationCommandGroups(nationEntries), ctx, view),
|
||||
inputOptions: options.inputOptions ?? {
|
||||
cities: [],
|
||||
nations: [],
|
||||
|
||||
@@ -100,7 +100,7 @@ const buildNation = (): NationRow =>
|
||||
}) as unknown as NationRow;
|
||||
|
||||
describe('buildTurnCommandTable', () => {
|
||||
it('projects the main reserved-turn categories and command order from Ref', async () => {
|
||||
it('projects the general and chief reserved-turn categories and command order from Ref', async () => {
|
||||
const table = await buildTurnCommandTable({
|
||||
worldState: buildWorldState(),
|
||||
general: buildGeneral(),
|
||||
@@ -140,6 +140,36 @@ describe('buildTurnCommandTable', () => {
|
||||
계략: ['che_선동', 'che_탈취', 'che_파괴', 'che_화계'],
|
||||
국가: ['che_증여', 'che_헌납', 'che_물자조달', 'che_하야', 'che_거병', 'che_건국', 'che_선양', 'che_해산'],
|
||||
});
|
||||
expect(table.nation.map(({ category }) => category)).toEqual(['휴식', '인사', '외교', '특수', '전략', '기타']);
|
||||
expect(
|
||||
Object.fromEntries(table.nation.map(({ category, values }) => [category, values.map(({ key }) => key)]))
|
||||
).toEqual({
|
||||
휴식: ['휴식'],
|
||||
인사: ['che_발령', 'che_포상', 'che_몰수', 'che_부대탈퇴지시'],
|
||||
외교: ['che_물자원조', 'che_불가침제의', 'che_선전포고', 'che_종전제의', 'che_불가침파기제의'],
|
||||
특수: ['che_초토화', 'che_천도', 'che_증축', 'che_감축'],
|
||||
전략: [
|
||||
'che_필사즉생',
|
||||
'che_백성동원',
|
||||
'che_수몰',
|
||||
'che_허보',
|
||||
'che_의병모집',
|
||||
'che_이호경식',
|
||||
'che_급습',
|
||||
'che_피장파장',
|
||||
],
|
||||
기타: ['che_국기변경', 'che_국호변경'],
|
||||
});
|
||||
expect(
|
||||
Object.fromEntries(table.nation.map(({ category, values }) => [category, values.map(({ name }) => name)]))
|
||||
).toEqual({
|
||||
휴식: ['휴식'],
|
||||
인사: ['발령', '포상', '몰수', '부대 탈퇴 지시'],
|
||||
외교: ['원조', '불가침 제의', '선전포고', '종전 제의', '불가침 파기 제의'],
|
||||
특수: ['초토화', '천도', '증축', '감축'],
|
||||
전략: ['필사즉생', '백성동원', '수몰', '허보', '의병모집', '이호경식', '급습', '피장파장'],
|
||||
기타: ['국기변경', '국호변경'],
|
||||
});
|
||||
});
|
||||
|
||||
it('projects the Ref availability boundaries for force move, retirement, and resignation', async () => {
|
||||
|
||||
@@ -35,7 +35,32 @@ const GENERAL_REF_EDITOR_ACTIONS = [
|
||||
] as const;
|
||||
const GENERAL_REF_STRATEGY_ACTIONS = ['che_선동', 'che_탈취', 'che_파괴', 'che_화계'] as const;
|
||||
const GENERAL_REF_STRATEGY_ACTION_SET = new Set<string>(GENERAL_REF_STRATEGY_ACTIONS);
|
||||
const NATION_REF_EDITOR_ACTIONS = ['che_포상', 'che_발령', 'che_증축', 'che_필사즉생'] as const;
|
||||
const NATION_REF_EDITOR_ACTIONS = [
|
||||
'휴식',
|
||||
'che_발령',
|
||||
'che_포상',
|
||||
'che_몰수',
|
||||
'che_부대탈퇴지시',
|
||||
'che_물자원조',
|
||||
'che_불가침제의',
|
||||
'che_선전포고',
|
||||
'che_종전제의',
|
||||
'che_불가침파기제의',
|
||||
'che_초토화',
|
||||
'che_천도',
|
||||
'che_증축',
|
||||
'che_감축',
|
||||
'che_필사즉생',
|
||||
'che_백성동원',
|
||||
'che_수몰',
|
||||
'che_허보',
|
||||
'che_의병모집',
|
||||
'che_이호경식',
|
||||
'che_급습',
|
||||
'che_피장파장',
|
||||
'che_국기변경',
|
||||
'che_국호변경',
|
||||
] as const;
|
||||
|
||||
describe('default turn command profile AI coverage', () => {
|
||||
it('loads every action selected directly by the general and nation AI', async () => {
|
||||
@@ -52,6 +77,6 @@ describe('default turn command profile AI coverage', () => {
|
||||
expect(profile.general.filter((action) => GENERAL_REF_STRATEGY_ACTION_SET.has(action))).toEqual(
|
||||
GENERAL_REF_STRATEGY_ACTIONS
|
||||
);
|
||||
expect(profile.nation).toEqual(expect.arrayContaining([...NATION_REF_EDITOR_ACTIONS]));
|
||||
expect(profile.nation).toEqual(NATION_REF_EDITOR_ACTIONS);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -231,6 +231,66 @@ const commandTable = {
|
||||
],
|
||||
inputOptions,
|
||||
};
|
||||
const buildSimpleCommand = (key: string, name: string) => ({
|
||||
key,
|
||||
name,
|
||||
reqArg: false,
|
||||
possible: true,
|
||||
status: 'available',
|
||||
inputFields: [],
|
||||
});
|
||||
const refChiefCommandTable = {
|
||||
general: [],
|
||||
nation: [
|
||||
{ category: '휴식', values: [buildSimpleCommand('휴식', '휴식')] },
|
||||
{
|
||||
category: '인사',
|
||||
values: [
|
||||
buildSimpleCommand('che_발령', '발령'),
|
||||
buildSimpleCommand('che_포상', '포상'),
|
||||
buildSimpleCommand('che_몰수', '몰수'),
|
||||
buildSimpleCommand('che_부대탈퇴지시', '부대 탈퇴 지시'),
|
||||
],
|
||||
},
|
||||
{
|
||||
category: '외교',
|
||||
values: [
|
||||
buildSimpleCommand('che_물자원조', '원조'),
|
||||
buildSimpleCommand('che_불가침제의', '불가침 제의'),
|
||||
buildSimpleCommand('che_선전포고', '선전포고'),
|
||||
buildSimpleCommand('che_종전제의', '종전 제의'),
|
||||
buildSimpleCommand('che_불가침파기제의', '불가침 파기 제의'),
|
||||
],
|
||||
},
|
||||
{
|
||||
category: '특수',
|
||||
values: [
|
||||
buildSimpleCommand('che_초토화', '초토화'),
|
||||
buildSimpleCommand('che_천도', '천도'),
|
||||
buildSimpleCommand('che_증축', '증축'),
|
||||
buildSimpleCommand('che_감축', '감축'),
|
||||
],
|
||||
},
|
||||
{
|
||||
category: '전략',
|
||||
values: [
|
||||
buildSimpleCommand('che_필사즉생', '필사즉생'),
|
||||
buildSimpleCommand('che_백성동원', '백성동원'),
|
||||
buildSimpleCommand('che_수몰', '수몰'),
|
||||
buildSimpleCommand('che_허보', '허보'),
|
||||
buildSimpleCommand('che_의병모집', '의병모집'),
|
||||
buildSimpleCommand('che_이호경식', '이호경식'),
|
||||
buildSimpleCommand('che_급습', '급습'),
|
||||
buildSimpleCommand('che_피장파장', '피장파장'),
|
||||
],
|
||||
},
|
||||
{
|
||||
category: '기타',
|
||||
values: [buildSimpleCommand('che_국기변경', '국기변경'), buildSimpleCommand('che_국호변경', '국호변경')],
|
||||
},
|
||||
],
|
||||
inputOptions,
|
||||
};
|
||||
const generalContext = {
|
||||
general: {
|
||||
id: 1,
|
||||
@@ -618,6 +678,51 @@ test('reserves force move, retirement, and resignation from the user command pic
|
||||
await picker.screenshot({ path: test.info().outputPath('special-user-commands-mobile-500.png') });
|
||||
});
|
||||
|
||||
test('shows every Ref chief command in the exact category and command order', async ({ page }) => {
|
||||
await install(page, false, refChiefCommandTable);
|
||||
await page.setViewportSize({ width: 1200, height: 900 });
|
||||
await page.goto('/che/chief-center');
|
||||
|
||||
const editor = page.locator('[data-command-scope="nation"]');
|
||||
await editor.getByRole('button', { name: '1턴 명령 입력', exact: true }).click();
|
||||
const picker = page.getByTestId('command-picker');
|
||||
const expected = {
|
||||
휴식: ['휴식'],
|
||||
인사: ['발령', '포상', '몰수', '부대 탈퇴 지시'],
|
||||
외교: ['원조', '불가침 제의', '선전포고', '종전 제의', '불가침 파기 제의'],
|
||||
특수: ['초토화', '천도', '증축', '감축'],
|
||||
전략: ['필사즉생', '백성동원', '수몰', '허보', '의병모집', '이호경식', '급습', '피장파장'],
|
||||
기타: ['국기변경', '국호변경'],
|
||||
} as const;
|
||||
|
||||
await expect(picker.locator('.category-btn')).toHaveText(Object.keys(expected));
|
||||
for (const [category, commands] of Object.entries(expected)) {
|
||||
await picker.locator('.category-btn').filter({ hasText: category }).click();
|
||||
await expect(picker.locator('.command-grid .command-item')).toHaveText([...commands]);
|
||||
}
|
||||
const rename = picker.getByRole('button', { name: '국호변경', exact: true });
|
||||
await rename.hover();
|
||||
await rename.focus();
|
||||
await expect(rename).toBeFocused();
|
||||
await picker.screenshot({ path: test.info().outputPath('ref-chief-command-list-desktop-1200.png') });
|
||||
|
||||
await picker.getByRole('button', { name: '명령 입력 닫기', exact: true }).click();
|
||||
await page.setViewportSize({ width: 500, height: 900 });
|
||||
const mobileEditor = page.locator('[data-command-scope="nation"]:visible');
|
||||
await mobileEditor.getByRole('button', { name: '1턴 명령 입력', exact: true }).click();
|
||||
const mobilePicker = page.locator('[data-testid="command-picker"]:visible');
|
||||
await expect(mobilePicker.locator('.category-btn')).toHaveText(Object.keys(expected));
|
||||
const mobileGeometry = await mobilePicker.evaluate((element) => ({
|
||||
width: element.getBoundingClientRect().width,
|
||||
horizontalOverflow: element.scrollWidth - element.clientWidth,
|
||||
categoryColumns: getComputedStyle(element.querySelector<HTMLElement>('.category-list')!).gridTemplateColumns,
|
||||
}));
|
||||
expect(mobileGeometry.width).toBeLessThanOrEqual(500);
|
||||
expect(mobileGeometry.horizontalOverflow).toBeLessThanOrEqual(0);
|
||||
expect(mobileGeometry.categoryColumns.split(' ')).toHaveLength(3);
|
||||
await mobilePicker.screenshot({ path: test.info().outputPath('ref-chief-command-list-mobile-500.png') });
|
||||
});
|
||||
|
||||
test('enters general and nation command arguments and sends exact values', async ({ page }) => {
|
||||
const requests = await install(page);
|
||||
await page.setViewportSize({ width: 1200, height: 900 });
|
||||
|
||||
@@ -46,21 +46,28 @@
|
||||
],
|
||||
"nation": [
|
||||
"휴식",
|
||||
"che_발령",
|
||||
"che_포상",
|
||||
"che_몰수",
|
||||
"che_부대탈퇴지시",
|
||||
"che_발령",
|
||||
"che_물자원조",
|
||||
"che_불가침제의",
|
||||
"che_선전포고",
|
||||
"che_종전제의",
|
||||
"che_불가침파기제의",
|
||||
"che_초토화",
|
||||
"che_천도",
|
||||
"che_증축",
|
||||
"che_선전포고",
|
||||
"che_불가침제의",
|
||||
"che_불가침파기제의",
|
||||
"che_의병모집",
|
||||
"che_허보",
|
||||
"che_감축",
|
||||
"che_필사즉생",
|
||||
"che_백성동원",
|
||||
"che_이호경식",
|
||||
"che_수몰",
|
||||
"che_급습"
|
||||
"che_허보",
|
||||
"che_의병모집",
|
||||
"che_이호경식",
|
||||
"che_급습",
|
||||
"che_피장파장",
|
||||
"che_국기변경",
|
||||
"che_국호변경"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user