refactor: tighten backend and logic boundaries
This commit is contained in:
@@ -236,30 +236,6 @@ export const remainCityCapacity = (key: keyof City, label: string): Constraint =
|
||||
},
|
||||
});
|
||||
|
||||
export const remainCityCapacityByMax = (key: keyof City, maxKey: keyof City, label: string): Constraint => ({
|
||||
name: 'remainCityCapacityByMax',
|
||||
requires: (ctx) => (ctx.cityId !== undefined ? [{ kind: 'city', id: ctx.cityId }] : []),
|
||||
test: (ctx, view) => {
|
||||
const city = readCity(view, ctx.cityId);
|
||||
if (!city) {
|
||||
if (ctx.cityId === undefined) {
|
||||
return unknownOrDeny(ctx, [], '도시 정보가 없습니다.');
|
||||
}
|
||||
const req: RequirementKey = { kind: 'city', id: ctx.cityId };
|
||||
return unknownOrDeny(ctx, [req], '도시 정보가 없습니다.');
|
||||
}
|
||||
const current = city[key];
|
||||
const max = city[maxKey];
|
||||
if (typeof current !== 'number' || typeof max !== 'number') {
|
||||
return unknownOrDeny(ctx, [], '도시 정보가 없습니다.');
|
||||
}
|
||||
if (current < max) {
|
||||
return allow();
|
||||
}
|
||||
return { kind: 'deny', reason: `${label}이 충분합니다.` };
|
||||
},
|
||||
});
|
||||
|
||||
export const reqCityCapacity = (key: keyof City, label: string, required: number | string): Constraint => ({
|
||||
name: 'reqCityCapacity',
|
||||
requires: (ctx) => (ctx.cityId !== undefined ? [{ kind: 'city', id: ctx.cityId }] : []),
|
||||
@@ -506,7 +482,7 @@ export const hasRouteWithEnemy = (): Constraint => ({
|
||||
},
|
||||
});
|
||||
|
||||
export const beNeutralCity = (): Constraint => ({
|
||||
export const neutralCity = (): Constraint => ({
|
||||
name: 'beNeutralCity',
|
||||
requires: (ctx) => (ctx.cityId !== undefined ? [{ kind: 'city', id: ctx.cityId }] : []),
|
||||
test: (ctx, view) => {
|
||||
@@ -534,8 +510,6 @@ export const beNeutralCity = (): Constraint => ({
|
||||
},
|
||||
});
|
||||
|
||||
export const neutralCity = (): Constraint => beNeutralCity();
|
||||
|
||||
export const constructableCity = (): Constraint => ({
|
||||
name: 'constructableCity',
|
||||
requires: (ctx) => (ctx.cityId !== undefined ? [{ kind: 'city', id: ctx.cityId }] : []),
|
||||
@@ -558,25 +532,6 @@ export const constructableCity = (): Constraint => ({
|
||||
},
|
||||
});
|
||||
|
||||
export const reqCityLevel = (levels: number[]): Constraint => ({
|
||||
name: 'reqCityLevel',
|
||||
requires: (ctx) => (ctx.cityId !== undefined ? [{ kind: 'city', id: ctx.cityId }] : []),
|
||||
test: (ctx, view) => {
|
||||
const city = readCity(view, ctx.cityId);
|
||||
if (!city) {
|
||||
if (ctx.cityId === undefined) {
|
||||
return unknownOrDeny(ctx, [], '도시 정보가 없습니다.');
|
||||
}
|
||||
const req: RequirementKey = { kind: 'city', id: ctx.cityId };
|
||||
return unknownOrDeny(ctx, [req], '도시 정보가 없습니다.');
|
||||
}
|
||||
if (levels.includes(city.level)) {
|
||||
return allow();
|
||||
}
|
||||
return { kind: 'deny', reason: '규모가 맞지 않습니다.' };
|
||||
},
|
||||
});
|
||||
|
||||
export const nearCity = (maxDistance: number): Constraint => ({
|
||||
name: 'nearCity',
|
||||
requires: (ctx) => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Constraint, ConstraintContext, ConstraintResult, RequirementKey, StateView } from './types.js';
|
||||
import type { Constraint, ConstraintContext, ConstraintResult, StateView } from './types.js';
|
||||
|
||||
export const evaluateConstraints = (
|
||||
constraints: Constraint[],
|
||||
@@ -24,14 +24,6 @@ export const evaluateConstraints = (
|
||||
return { kind: 'allow' };
|
||||
};
|
||||
|
||||
export const collectRequirements = (constraints: Constraint[], ctx: ConstraintContext): RequirementKey[] => {
|
||||
const keys: RequirementKey[] = [];
|
||||
for (const constraint of constraints) {
|
||||
keys.push(...constraint.requires(ctx));
|
||||
}
|
||||
return keys;
|
||||
};
|
||||
|
||||
export interface ActionWithConstraints {
|
||||
buildConstraints(ctx: ConstraintContext, args: unknown): Constraint[];
|
||||
}
|
||||
|
||||
@@ -89,25 +89,6 @@ export const beChief = (): Constraint => ({
|
||||
},
|
||||
});
|
||||
|
||||
export const beMonarch = (): Constraint => ({
|
||||
name: 'beMonarch',
|
||||
requires: (ctx) => [{ kind: 'general', id: ctx.actorId }],
|
||||
test: (ctx, view) => {
|
||||
const req: RequirementKey = { kind: 'general', id: ctx.actorId };
|
||||
if (!view.has(req)) {
|
||||
return unknownOrDeny(ctx, [req], '장수 정보가 없습니다.');
|
||||
}
|
||||
const general = view.get(req) as General | null;
|
||||
if (!general) {
|
||||
return unknownOrDeny(ctx, [req], '장수 정보가 없습니다.');
|
||||
}
|
||||
if (general.officerLevel === 12) {
|
||||
return allow();
|
||||
}
|
||||
return { kind: 'deny', reason: '군주가 아닙니다.' };
|
||||
},
|
||||
});
|
||||
|
||||
export const beLord = (): Constraint => ({
|
||||
name: 'beLord',
|
||||
requires: (ctx) => [{ kind: 'general', id: ctx.actorId }],
|
||||
@@ -508,47 +489,6 @@ export const existsDestGeneral = (): Constraint => ({
|
||||
},
|
||||
});
|
||||
|
||||
export const destGeneralInDestNation = (): Constraint => ({
|
||||
name: 'destGeneralInDestNation',
|
||||
requires: (ctx) => {
|
||||
const reqs: RequirementKey[] = [];
|
||||
const destGeneralId = resolveDestGeneralId(ctx);
|
||||
if (destGeneralId !== undefined) {
|
||||
reqs.push({ kind: 'destGeneral', id: destGeneralId });
|
||||
}
|
||||
const destNationId = resolveDestNationId(ctx);
|
||||
if (destNationId !== undefined) {
|
||||
reqs.push({ kind: 'destNation', id: destNationId });
|
||||
}
|
||||
return reqs;
|
||||
},
|
||||
test: (ctx, view) => {
|
||||
const destGeneral = readDestGeneral(ctx, view);
|
||||
if (!destGeneral) {
|
||||
const destGeneralId = resolveDestGeneralId(ctx);
|
||||
if (destGeneralId === undefined) {
|
||||
return unknownOrDeny(ctx, [], '장수 정보가 없습니다.');
|
||||
}
|
||||
const req: RequirementKey = {
|
||||
kind: 'destGeneral',
|
||||
id: destGeneralId,
|
||||
};
|
||||
return unknownOrDeny(ctx, [req], '장수 정보가 없습니다.');
|
||||
}
|
||||
const destNationId = resolveDestNationId(ctx);
|
||||
if (destNationId === undefined) {
|
||||
return unknownOrDeny(ctx, [], '국가 정보가 없습니다.');
|
||||
}
|
||||
if (destGeneral.nationId !== destNationId) {
|
||||
return {
|
||||
kind: 'deny',
|
||||
reason: '제의 장수가 국가 소속이 아닙니다.',
|
||||
};
|
||||
}
|
||||
return allow();
|
||||
},
|
||||
});
|
||||
|
||||
export const friendlyDestGeneral = (): Constraint => ({
|
||||
name: 'friendlyDestGeneral',
|
||||
requires: (ctx) => {
|
||||
@@ -603,29 +543,6 @@ export const mustBeNPC = (): Constraint => ({
|
||||
},
|
||||
});
|
||||
|
||||
export const notSameDestNation = (): Constraint => ({
|
||||
name: 'notSameDestNation',
|
||||
requires: (ctx) => {
|
||||
const reqs: RequirementKey[] = [];
|
||||
const destNationId = resolveDestNationId(ctx);
|
||||
if (destNationId !== undefined) {
|
||||
reqs.push({ kind: 'destNation', id: destNationId });
|
||||
}
|
||||
return reqs;
|
||||
},
|
||||
test: (ctx, _view) => {
|
||||
const destNationId = resolveDestNationId(ctx);
|
||||
if (destNationId === undefined) {
|
||||
return unknownOrDeny(ctx, [], '목표 국가가 없습니다.');
|
||||
}
|
||||
|
||||
if (ctx.nationId === destNationId) {
|
||||
return { kind: 'deny', reason: '이미 소속된 국가입니다.' };
|
||||
}
|
||||
return allow();
|
||||
},
|
||||
});
|
||||
|
||||
export const notLord = (): Constraint => ({
|
||||
name: 'notLord',
|
||||
requires: (ctx) => [{ kind: 'general', id: ctx.actorId }],
|
||||
@@ -640,18 +557,3 @@ export const notLord = (): Constraint => ({
|
||||
return { kind: 'deny', reason: '군주는 불가능합니다.' };
|
||||
},
|
||||
});
|
||||
|
||||
export const notChief = (): Constraint => ({
|
||||
name: 'notChief',
|
||||
requires: (ctx) => [{ kind: 'general', id: ctx.actorId }],
|
||||
test: (ctx, view) => {
|
||||
const req: RequirementKey = { kind: 'general', id: ctx.actorId };
|
||||
const general = view.get(req) as General | null;
|
||||
if (!general) return unknownOrDeny(ctx, [req], '장수 정보가 없습니다.');
|
||||
|
||||
if (general.officerLevel <= 4) {
|
||||
return allow();
|
||||
}
|
||||
return { kind: 'deny', reason: '수뇌입니다.' };
|
||||
},
|
||||
});
|
||||
|
||||
@@ -7,9 +7,6 @@ export const denyWithReason = (reason: string): Constraint => ({
|
||||
test: () => ({ kind: 'deny', reason }),
|
||||
});
|
||||
|
||||
// TODO: 점진 이전을 위해 유지. 신규 코드에서는 denyWithReason을 사용한다.
|
||||
export const alwaysFail = denyWithReason;
|
||||
|
||||
export const notOpeningPart = (relYear: number, openingPartYear: number): Constraint => ({
|
||||
name: 'notOpeningPart',
|
||||
requires: () => [],
|
||||
|
||||
@@ -48,25 +48,6 @@ export const notWanderingNation = (): Constraint => ({
|
||||
},
|
||||
});
|
||||
|
||||
export const beWanderingNation = (): Constraint => ({
|
||||
name: 'beWanderingNation',
|
||||
requires: (ctx) => (ctx.nationId !== undefined ? [{ kind: 'nation', id: ctx.nationId }] : []),
|
||||
test: (ctx, view) => {
|
||||
const nation = readNation(view, ctx.nationId);
|
||||
if (!nation) {
|
||||
if (ctx.nationId === undefined) {
|
||||
return unknownOrDeny(ctx, [], '국가 정보가 없습니다.');
|
||||
}
|
||||
const req: RequirementKey = { kind: 'nation', id: ctx.nationId };
|
||||
return unknownOrDeny(ctx, [req], '국가 정보가 없습니다.');
|
||||
}
|
||||
if (nation.level === 0) {
|
||||
return allow();
|
||||
}
|
||||
return { kind: 'deny', reason: '방랑군이 아닙니다.' };
|
||||
},
|
||||
});
|
||||
|
||||
export const wanderingNation = (): Constraint => ({
|
||||
name: 'wanderingNation',
|
||||
requires: (ctx) => (ctx.nationId !== undefined ? [{ kind: 'nation', id: ctx.nationId }] : []),
|
||||
|
||||
Reference in New Issue
Block a user