merge: 기본 사령턴 목록과 분류를 Ref에 맞춤
This commit is contained in:
@@ -10,6 +10,7 @@ import type {
|
|||||||
GeneralTurnCommandKey,
|
GeneralTurnCommandKey,
|
||||||
MapDefinition,
|
MapDefinition,
|
||||||
Nation,
|
Nation,
|
||||||
|
NationTurnCommandKey,
|
||||||
NationTurnCommandSpec,
|
NationTurnCommandSpec,
|
||||||
RequirementKey,
|
RequirementKey,
|
||||||
StateView,
|
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']>([
|
const INPUT_REQUIREMENT_KINDS = new Set<RequirementKey['kind']>([
|
||||||
'destGeneral',
|
'destGeneral',
|
||||||
'destCity',
|
'destCity',
|
||||||
@@ -665,6 +714,26 @@ const projectRefGeneralCommandGroups = (entries: CommandEntry[]): CommandEntry[]
|
|||||||
)
|
)
|
||||||
.map(({ entry }) => entry);
|
.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: {
|
export const buildTurnCommandTable = async (options: {
|
||||||
worldState: WorldStateRow;
|
worldState: WorldStateRow;
|
||||||
general: GeneralRow;
|
general: GeneralRow;
|
||||||
@@ -697,7 +766,7 @@ export const buildTurnCommandTable = async (options: {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
general: buildGroups(projectRefGeneralCommandGroups(generalEntries), ctx, view),
|
general: buildGroups(projectRefGeneralCommandGroups(generalEntries), ctx, view),
|
||||||
nation: buildGroups(nationEntries, ctx, view),
|
nation: buildGroups(projectRefNationCommandGroups(nationEntries), ctx, view),
|
||||||
inputOptions: options.inputOptions ?? {
|
inputOptions: options.inputOptions ?? {
|
||||||
cities: [],
|
cities: [],
|
||||||
nations: [],
|
nations: [],
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ const buildNation = (): NationRow =>
|
|||||||
}) as unknown as NationRow;
|
}) as unknown as NationRow;
|
||||||
|
|
||||||
describe('buildTurnCommandTable', () => {
|
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({
|
const table = await buildTurnCommandTable({
|
||||||
worldState: buildWorldState(),
|
worldState: buildWorldState(),
|
||||||
general: buildGeneral(),
|
general: buildGeneral(),
|
||||||
@@ -140,6 +140,36 @@ describe('buildTurnCommandTable', () => {
|
|||||||
계략: ['che_선동', 'che_탈취', 'che_파괴', 'che_화계'],
|
계략: ['che_선동', 'che_탈취', 'che_파괴', 'che_화계'],
|
||||||
국가: ['che_증여', 'che_헌납', 'che_물자조달', 'che_하야', '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 () => {
|
it('projects the Ref availability boundaries for force move, retirement, and resignation', async () => {
|
||||||
|
|||||||
@@ -35,7 +35,32 @@ const GENERAL_REF_EDITOR_ACTIONS = [
|
|||||||
] as const;
|
] as const;
|
||||||
const GENERAL_REF_STRATEGY_ACTIONS = ['che_선동', 'che_탈취', 'che_파괴', 'che_화계'] 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 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', () => {
|
describe('default turn command profile AI coverage', () => {
|
||||||
it('loads every action selected directly by the general and nation AI', async () => {
|
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(
|
expect(profile.general.filter((action) => GENERAL_REF_STRATEGY_ACTION_SET.has(action))).toEqual(
|
||||||
GENERAL_REF_STRATEGY_ACTIONS
|
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,
|
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 = {
|
const generalContext = {
|
||||||
general: {
|
general: {
|
||||||
id: 1,
|
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') });
|
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 }) => {
|
test('enters general and nation command arguments and sends exact values', async ({ page }) => {
|
||||||
const requests = await install(page);
|
const requests = await install(page);
|
||||||
await page.setViewportSize({ width: 1200, height: 900 });
|
await page.setViewportSize({ width: 1200, height: 900 });
|
||||||
|
|||||||
@@ -46,21 +46,28 @@
|
|||||||
],
|
],
|
||||||
"nation": [
|
"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_수몰",
|
||||||
"che_급습"
|
"che_허보",
|
||||||
|
"che_의병모집",
|
||||||
|
"che_이호경식",
|
||||||
|
"che_급습",
|
||||||
|
"che_피장파장",
|
||||||
|
"che_국기변경",
|
||||||
|
"che_국호변경"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user