From 3ec7e66948ea0ff2240fe3595b85b1524339d0bf Mon Sep 17 00:00:00 2001 From: Hide_D Date: Sat, 10 Jan 2026 13:16:43 +0000 Subject: [PATCH] =?UTF-8?q?constraint=20=EC=8B=A4=ED=8C=A8=20=ED=9B=84=20?= =?UTF-8?q?=EC=8B=A4=ED=8C=A8=20=EA=B2=80=EC=82=AC=20=EA=B2=B0=EA=B3=BC=20?= =?UTF-8?q?=ED=95=AD=EB=AA=A9=EC=9D=84=20=EC=A7=81=EC=A0=91=20=EB=B0=98?= =?UTF-8?q?=ED=99=98.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/turn/reservedTurnHandler.ts | 6 +- packages/logic/src/constraints/city.ts | 60 +++++++++---------- packages/logic/src/constraints/diplomacy.ts | 12 ++-- packages/logic/src/constraints/evaluate.ts | 6 ++ packages/logic/src/constraints/general.ts | 32 +++++----- packages/logic/src/constraints/misc.ts | 6 +- packages/logic/src/constraints/nation.ts | 28 ++++----- packages/logic/src/constraints/troop.ts | 4 +- packages/logic/src/constraints/types.ts | 2 +- 9 files changed, 82 insertions(+), 74 deletions(-) diff --git a/app/game-engine/src/turn/reservedTurnHandler.ts b/app/game-engine/src/turn/reservedTurnHandler.ts index 27dd6c9..97f4cc1 100644 --- a/app/game-engine/src/turn/reservedTurnHandler.ts +++ b/app/game-engine/src/turn/reservedTurnHandler.ts @@ -340,11 +340,12 @@ const buildConstraintContext = ( mode: 'full', }); -const createActionLog = (message: string): LogEntryDraft => ({ +const createActionLog = (message: string, meta?: Record): LogEntryDraft => ({ scope: LogScope.GENERAL, category: LogCategory.ACTION, format: LogFormat.MONTH, text: message, + meta, }); const resolveDefinition = ( @@ -505,7 +506,8 @@ export const createReservedTurnHandler = async (options: { actionArgs = definition.parseArgs({}) ?? {}; actionKey = definition.key; const reason = result.kind === 'deny' ? result.reason : '조건을 확인할 수 없습니다.'; - logs.push(createActionLog(reason)); + const meta = result.kind === 'deny' ? { constraintName: result.constraintName } : undefined; + logs.push(createActionLog(reason, meta)); } const seedBase = buildSeedBase(context.world); diff --git a/packages/logic/src/constraints/city.ts b/packages/logic/src/constraints/city.ts index c545fcc..0dca27e 100644 --- a/packages/logic/src/constraints/city.ts +++ b/packages/logic/src/constraints/city.ts @@ -14,7 +14,7 @@ import { import type { Constraint, RequirementKey } from './types.js'; export const occupiedCity = (options: { allowNeutral?: boolean } = {}): Constraint => ({ - name: 'OccupiedCity', + name: 'occupiedCity', requires: (ctx) => { const reqs: RequirementKey[] = [{ kind: 'general', id: ctx.actorId }]; if (ctx.cityId !== undefined) { @@ -51,7 +51,7 @@ export const occupiedCity = (options: { allowNeutral?: boolean } = {}): Constrai }); export const occupiedDestCity = (): Constraint => ({ - name: 'OccupiedDestCity', + name: 'occupiedDestCity', requires: (ctx) => { const reqs: RequirementKey[] = [{ kind: 'general', id: ctx.actorId }]; const destCityId = resolveDestCityId(ctx); @@ -90,7 +90,7 @@ export const occupiedDestCity = (): Constraint => ({ }); export const suppliedCity = (): Constraint => ({ - name: 'SuppliedCity', + name: 'suppliedCity', requires: (ctx) => (ctx.cityId !== undefined ? [{ kind: 'city', id: ctx.cityId }] : []), test: (ctx, view) => { const city = readCity(view, ctx.cityId); @@ -109,15 +109,15 @@ export const suppliedCity = (): Constraint => ({ }); export const suppliedDestCity = (): Constraint => ({ - name: 'SuppliedDestCity', + name: 'suppliedDestCity', requires: (ctx) => resolveDestCityId(ctx) !== undefined ? [ - { - kind: 'destCity', - id: resolveDestCityId(ctx) ?? 0, - }, - ] + { + kind: 'destCity', + id: resolveDestCityId(ctx) ?? 0, + }, + ] : [], test: (ctx, view) => { const destCity = readDestCity(ctx, view); @@ -140,7 +140,7 @@ export const suppliedDestCity = (): Constraint => ({ }); export const remainCityCapacity = (key: keyof City, label: string): Constraint => ({ - name: 'RemainCityCapacity', + name: 'remainCityCapacity', requires: (ctx) => (ctx.cityId !== undefined ? [{ kind: 'city', id: ctx.cityId }] : []), test: (ctx, view) => { const city = readCity(view, ctx.cityId); @@ -165,7 +165,7 @@ export const remainCityCapacity = (key: keyof City, label: string): Constraint = }); export const remainCityCapacityByMax = (key: keyof City, maxKey: keyof City, label: string): Constraint => ({ - name: 'RemainCityCapacityByMax', + name: 'remainCityCapacityByMax', requires: (ctx) => (ctx.cityId !== undefined ? [{ kind: 'city', id: ctx.cityId }] : []), test: (ctx, view) => { const city = readCity(view, ctx.cityId); @@ -189,7 +189,7 @@ export const remainCityCapacityByMax = (key: keyof City, maxKey: keyof City, lab }); export const reqCityCapacity = (key: keyof City, label: string, required: number | string): Constraint => ({ - name: 'ReqCityCapacity', + name: 'reqCityCapacity', requires: (ctx) => (ctx.cityId !== undefined ? [{ kind: 'city', id: ctx.cityId }] : []), test: (ctx, view) => { const city = readCity(view, ctx.cityId); @@ -222,7 +222,7 @@ export const reqCityCapacity = (key: keyof City, label: string, required: number }); export const reqCityTrust = (minTrust: number): Constraint => ({ - name: 'ReqCityTrust', + name: 'reqCityTrust', requires: (ctx) => (ctx.cityId !== undefined ? [{ kind: 'city', id: ctx.cityId }] : []), test: (ctx, view) => { const city = readCity(view, ctx.cityId); @@ -245,15 +245,15 @@ export const reqCityTrust = (minTrust: number): Constraint => ({ }); export const existsDestCity = (): Constraint => ({ - name: 'ExistsDestCity', + name: 'existsDestCity', requires: (ctx) => resolveDestCityId(ctx) !== undefined ? [ - { - kind: 'destCity', - id: resolveDestCityId(ctx) ?? 0, - }, - ] + { + kind: 'destCity', + id: resolveDestCityId(ctx) ?? 0, + }, + ] : [], test: (ctx, view) => { const destCityId = resolveDestCityId(ctx); @@ -273,7 +273,7 @@ export const existsDestCity = (): Constraint => ({ }); export const notOccupiedDestCity = (): Constraint => ({ - name: 'NotOccupiedDestCity', + name: 'notOccupiedDestCity', requires: (ctx) => { const reqs: RequirementKey[] = [{ kind: 'general', id: ctx.actorId }]; const destCityId = resolveDestCityId(ctx); @@ -311,15 +311,15 @@ export const notOccupiedDestCity = (): Constraint => ({ }); export const notNeutralDestCity = (): Constraint => ({ - name: 'NotNeutralDestCity', + name: 'notNeutralDestCity', requires: (ctx) => resolveDestCityId(ctx) !== undefined ? [ - { - kind: 'destCity', - id: resolveDestCityId(ctx) ?? 0, - }, - ] + { + kind: 'destCity', + id: resolveDestCityId(ctx) ?? 0, + }, + ] : [], test: (ctx, view) => { const destCity = readDestCity(ctx, view); @@ -342,7 +342,7 @@ export const notNeutralDestCity = (): Constraint => ({ }); export const notSameDestCity = (): Constraint => ({ - name: 'NotSameDestCity', + name: 'notSameDestCity', requires: (ctx) => { const reqs: RequirementKey[] = [{ kind: 'general', id: ctx.actorId }]; const destCityId = resolveDestCityId(ctx); @@ -369,7 +369,7 @@ export const notSameDestCity = (): Constraint => ({ }); export const hasRouteWithEnemy = (): Constraint => ({ - name: 'HasRouteWithEnemy', + name: 'hasRouteWithEnemy', requires: (ctx) => { const reqs: RequirementKey[] = [{ kind: 'general', id: ctx.actorId }]; const destCityId = resolveDestCityId(ctx); @@ -435,7 +435,7 @@ export const hasRouteWithEnemy = (): Constraint => ({ }); export const beNeutralCity = (): Constraint => ({ - name: 'BeNeutralCity', + name: 'beNeutralCity', requires: (ctx) => (ctx.cityId !== undefined ? [{ kind: 'city', id: ctx.cityId }] : []), test: (ctx, view) => { const city = readCity(view, ctx.cityId); @@ -463,7 +463,7 @@ export const beNeutralCity = (): Constraint => ({ }); export const reqCityLevel = (levels: number[]): Constraint => ({ - name: 'ReqCityLevel', + name: 'reqCityLevel', requires: (ctx) => (ctx.cityId !== undefined ? [{ kind: 'city', id: ctx.cityId }] : []), test: (ctx, view) => { const city = readCity(view, ctx.cityId); diff --git a/packages/logic/src/constraints/diplomacy.ts b/packages/logic/src/constraints/diplomacy.ts index 8230a72..b66b7ac 100644 --- a/packages/logic/src/constraints/diplomacy.ts +++ b/packages/logic/src/constraints/diplomacy.ts @@ -32,8 +32,8 @@ const readDiplomacyEntry = ( typeof record.state === 'number' ? record.state : typeof record.stateCode === 'number' - ? record.stateCode - : null; + ? record.stateCode + : null; const term = typeof record.term === 'number' ? record.term : null; return { state, term }; } @@ -41,7 +41,7 @@ const readDiplomacyEntry = ( }; export const disallowDiplomacyBetweenStatus = (disallowList: Record): Constraint => ({ - name: 'DisallowDiplomacyBetweenStatus', + name: 'disallowDiplomacyBetweenStatus', requires: (ctx) => { const reqs: RequirementKey[] = []; if (ctx.nationId !== undefined) { @@ -93,7 +93,7 @@ export const disallowDiplomacyBetweenStatus = (disallowList: Record ({ - name: 'AllowDiplomacyBetweenStatus', + name: 'allowDiplomacyBetweenStatus', requires: (ctx) => { const reqs: RequirementKey[] = []; if (ctx.nationId !== undefined) { @@ -144,7 +144,7 @@ export const allowDiplomacyBetweenStatus = (allowList: number[], reason: string) }); export const allowDiplomacyWithTerm = (requiredState: number, minTerm: number, reason: string): Constraint => ({ - name: 'AllowDiplomacyWithTerm', + name: 'allowDiplomacyWithTerm', requires: (ctx) => { const reqs: RequirementKey[] = []; if (ctx.nationId !== undefined) { @@ -195,7 +195,7 @@ export const allowDiplomacyWithTerm = (requiredState: number, minTerm: number, r }); export const allowDiplomacyStatus = (allowList: number[], reason: string): Constraint => ({ - name: 'AllowDiplomacyStatus', + name: 'allowDiplomacyStatus', requires: (ctx) => { const reqs: RequirementKey[] = [{ kind: 'general', id: ctx.actorId }]; if (ctx.nationId !== undefined) { diff --git a/packages/logic/src/constraints/evaluate.ts b/packages/logic/src/constraints/evaluate.ts index 8ed17c3..4ad620f 100644 --- a/packages/logic/src/constraints/evaluate.ts +++ b/packages/logic/src/constraints/evaluate.ts @@ -11,6 +11,12 @@ export const evaluateConstraints = ( return { kind: 'unknown', missing }; } const result = constraint.test(ctx, view); + if (result.kind === 'deny') { + return { + ...result, + constraintName: result.constraintName ?? constraint.name, + }; + } if (result.kind !== 'allow') { return result; } diff --git a/packages/logic/src/constraints/general.ts b/packages/logic/src/constraints/general.ts index 81747ef..2d89abb 100644 --- a/packages/logic/src/constraints/general.ts +++ b/packages/logic/src/constraints/general.ts @@ -3,7 +3,7 @@ import { allow, readDestGeneral, resolveDestGeneralId, resolveDestNationId, unkn import type { Constraint, ConstraintContext, RequirementKey, StateView } from './types.js'; export const notBeNeutral = (): Constraint => ({ - name: 'NotBeNeutral', + name: 'notBeNeutral', requires: (ctx) => [{ kind: 'general', id: ctx.actorId }], test: (ctx, view) => { const req: RequirementKey = { kind: 'general', id: ctx.actorId }; @@ -22,7 +22,7 @@ export const notBeNeutral = (): Constraint => ({ }); export const beNeutral = (): Constraint => ({ - name: 'BeNeutral', + name: 'beNeutral', requires: (ctx) => [{ kind: 'general', id: ctx.actorId }], test: (ctx, view) => { const req: RequirementKey = { kind: 'general', id: ctx.actorId }; @@ -41,7 +41,7 @@ export const beNeutral = (): Constraint => ({ }); export const beChief = (): Constraint => ({ - name: 'BeChief', + name: 'beChief', requires: (ctx) => [{ kind: 'general', id: ctx.actorId }], test: (ctx, view) => { const req: RequirementKey = { kind: 'general', id: ctx.actorId }; @@ -60,7 +60,7 @@ export const beChief = (): Constraint => ({ }); export const beMonarch = (): Constraint => ({ - name: 'BeMonarch', + name: 'beMonarch', requires: (ctx) => [{ kind: 'general', id: ctx.actorId }], test: (ctx, view) => { const req: RequirementKey = { kind: 'general', id: ctx.actorId }; @@ -82,7 +82,7 @@ export const reqGeneralGold = ( getRequiredGold: (ctx: ConstraintContext, view: StateView) => number, requirements: RequirementKey[] = [] ): Constraint => ({ - name: 'ReqGeneralGold', + name: 'reqGeneralGold', requires: (ctx) => [{ kind: 'general', id: ctx.actorId }, ...requirements], test: (ctx, view) => { const generalReq: RequirementKey = { kind: 'general', id: ctx.actorId }; @@ -106,7 +106,7 @@ export const reqGeneralRice = ( getRequiredRice: (ctx: ConstraintContext, view: StateView) => number, requirements: RequirementKey[] = [] ): Constraint => ({ - name: 'ReqGeneralRice', + name: 'reqGeneralRice', requires: (ctx) => [{ kind: 'general', id: ctx.actorId }, ...requirements], test: (ctx, view) => { const generalReq: RequirementKey = { kind: 'general', id: ctx.actorId }; @@ -127,7 +127,7 @@ export const reqGeneralRice = ( }); export const reqGeneralCrew = (): Constraint => ({ - name: 'ReqGeneralCrew', + name: 'reqGeneralCrew', requires: (ctx) => [{ kind: 'general', id: ctx.actorId }], test: (ctx, view) => { const generalReq: RequirementKey = { kind: 'general', id: ctx.actorId }; @@ -149,7 +149,7 @@ export const reqGeneralCrewMargin = ( getCrewTypeId: (ctx: ConstraintContext, view: StateView) => number | null, requirements: RequirementKey[] = [] ): Constraint => ({ - name: 'ReqGeneralCrewMargin', + name: 'reqGeneralCrewMargin', requires: (ctx) => [{ kind: 'general', id: ctx.actorId }, ...requirements], test: (ctx, view) => { const generalReq: RequirementKey = { kind: 'general', id: ctx.actorId }; @@ -177,15 +177,15 @@ export const reqGeneralCrewMargin = ( }); export const existsDestGeneral = (): Constraint => ({ - name: 'ExistsDestGeneral', + name: 'existsDestGeneral', requires: (ctx) => resolveDestGeneralId(ctx) !== undefined ? [ - { - kind: 'destGeneral', - id: resolveDestGeneralId(ctx) ?? 0, - }, - ] + { + kind: 'destGeneral', + id: resolveDestGeneralId(ctx) ?? 0, + }, + ] : [], test: (ctx, view) => { const destGeneralId = resolveDestGeneralId(ctx); @@ -205,7 +205,7 @@ export const existsDestGeneral = (): Constraint => ({ }); export const destGeneralInDestNation = (): Constraint => ({ - name: 'DestGeneralInDestNation', + name: 'destGeneralInDestNation', requires: (ctx) => { const reqs: RequirementKey[] = []; const destGeneralId = resolveDestGeneralId(ctx); @@ -246,7 +246,7 @@ export const destGeneralInDestNation = (): Constraint => ({ }); export const friendlyDestGeneral = (): Constraint => ({ - name: 'FriendlyDestGeneral', + name: 'friendlyDestGeneral', requires: (ctx) => { const reqs: RequirementKey[] = [{ kind: 'general', id: ctx.actorId }]; const destGeneralId = resolveDestGeneralId(ctx); diff --git a/packages/logic/src/constraints/misc.ts b/packages/logic/src/constraints/misc.ts index 30298aa..3fb82da 100644 --- a/packages/logic/src/constraints/misc.ts +++ b/packages/logic/src/constraints/misc.ts @@ -2,13 +2,13 @@ import { allow } from './helpers.js'; import type { Constraint } from './types.js'; export const alwaysFail = (reason: string): Constraint => ({ - name: 'AlwaysFail', + name: 'alwaysFail', requires: () => [], test: () => ({ kind: 'deny', reason }), }); export const notOpeningPart = (relYear: number, openingPartYear: number): Constraint => ({ - name: 'NotOpeningPart', + name: 'notOpeningPart', requires: () => [], test: (_ctx) => { if (relYear >= openingPartYear) { @@ -19,7 +19,7 @@ export const notOpeningPart = (relYear: number, openingPartYear: number): Constr }); export const beOpeningPart = (): Constraint => ({ - name: 'BeOpeningPart', + name: 'beOpeningPart', requires: () => [ { kind: 'env', key: 'year' }, { kind: 'env', key: 'openingPartYear' }, diff --git a/packages/logic/src/constraints/nation.ts b/packages/logic/src/constraints/nation.ts index d4bda8a..3e3ec56 100644 --- a/packages/logic/src/constraints/nation.ts +++ b/packages/logic/src/constraints/nation.ts @@ -3,7 +3,7 @@ import { allow, readGeneral, readMetaNumber, readNation, resolveDestNationId, un import type { Constraint, ConstraintContext, RequirementKey, StateView } from './types.js'; export const notWanderingNation = (): Constraint => ({ - name: 'NotWanderingNation', + name: 'notWanderingNation', requires: (ctx) => (ctx.nationId !== undefined ? [{ kind: 'nation', id: ctx.nationId }] : []), test: (ctx, view) => { const nation = readNation(view, ctx.nationId); @@ -22,7 +22,7 @@ export const notWanderingNation = (): Constraint => ({ }); export const beWanderingNation = (): Constraint => ({ - name: 'BeWanderingNation', + name: 'beWanderingNation', requires: (ctx) => (ctx.nationId !== undefined ? [{ kind: 'nation', id: ctx.nationId }] : []), test: (ctx, view) => { const nation = readNation(view, ctx.nationId); @@ -41,7 +41,7 @@ export const beWanderingNation = (): Constraint => ({ }); export const availableStrategicCommand = (allowTurnCnt = 0): Constraint => ({ - name: 'AvailableStrategicCommand', + name: 'availableStrategicCommand', requires: (ctx) => { const reqs: RequirementKey[] = [{ kind: 'general', id: ctx.actorId }]; if (ctx.nationId !== undefined) { @@ -85,7 +85,7 @@ export const reqNationGold = ( getRequiredGold: (ctx: ConstraintContext, view: StateView) => number, requirements: RequirementKey[] = [] ): Constraint => ({ - name: 'ReqNationGold', + name: 'reqNationGold', requires: (ctx) => { const reqs: RequirementKey[] = [...requirements]; if (ctx.nationId !== undefined) { @@ -119,7 +119,7 @@ export const reqNationRice = ( getRequiredRice: (ctx: ConstraintContext, view: StateView) => number, requirements: RequirementKey[] = [] ): Constraint => ({ - name: 'ReqNationRice', + name: 'reqNationRice', requires: (ctx) => { const reqs: RequirementKey[] = [...requirements]; if (ctx.nationId !== undefined) { @@ -150,15 +150,15 @@ export const reqNationRice = ( }); export const existsDestNation = (): Constraint => ({ - name: 'ExistsDestNation', + name: 'existsDestNation', requires: (ctx) => resolveDestNationId(ctx) !== undefined ? [ - { - kind: 'destNation', - id: resolveDestNationId(ctx) ?? 0, - }, - ] + { + kind: 'destNation', + id: resolveDestNationId(ctx) ?? 0, + }, + ] : [], test: (ctx, view) => { const destNationId = resolveDestNationId(ctx); @@ -178,7 +178,7 @@ export const existsDestNation = (): Constraint => ({ }); export const differentDestNation = (): Constraint => ({ - name: 'DifferentDestNation', + name: 'differentDestNation', requires: (ctx) => { const reqs: RequirementKey[] = []; if (ctx.nationId !== undefined) { @@ -217,7 +217,7 @@ export const differentDestNation = (): Constraint => ({ }); export const reqNationGeneralCount = (min: number): Constraint => ({ - name: 'ReqNationGeneralCount', + name: 'reqNationGeneralCount', requires: (ctx) => { const reqs: RequirementKey[] = [{ kind: 'generalList' }]; if (ctx.nationId !== undefined) { @@ -259,7 +259,7 @@ export const reqNationGeneralCount = (min: number): Constraint => ({ }); export const checkNationNameDuplicate = (name: string): Constraint => ({ - name: 'CheckNationNameDuplicate', + name: 'checkNationNameDuplicate', requires: () => [{ kind: 'nationList' }], test: (ctx, view) => { const nations = view.get({ kind: 'nationList' }) as Nation[] | null; diff --git a/packages/logic/src/constraints/troop.ts b/packages/logic/src/constraints/troop.ts index f07e502..63edf00 100644 --- a/packages/logic/src/constraints/troop.ts +++ b/packages/logic/src/constraints/troop.ts @@ -3,7 +3,7 @@ import { allow, unknownOrDeny } from './helpers.js'; import type { Constraint, RequirementKey } from './types.js'; export const mustBeTroopLeader = (): Constraint => ({ - name: 'MustBeTroopLeader', + name: 'mustBeTroopLeader', requires: (ctx) => [{ kind: 'general', id: ctx.actorId }], test: (ctx, view) => { const generalReq: RequirementKey = { kind: 'general', id: ctx.actorId }; @@ -22,7 +22,7 @@ export const mustBeTroopLeader = (): Constraint => ({ }); export const reqTroopMembers = (): Constraint => ({ - name: 'ReqTroopMembers', + name: 'reqTroopMembers', requires: (ctx) => [{ kind: 'general', id: ctx.actorId }, { kind: 'generalList' }], test: (ctx, view) => { const generalReq: RequirementKey = { kind: 'general', id: ctx.actorId }; diff --git a/packages/logic/src/constraints/types.ts b/packages/logic/src/constraints/types.ts index c5032fe..f7a1785 100644 --- a/packages/logic/src/constraints/types.ts +++ b/packages/logic/src/constraints/types.ts @@ -1,6 +1,6 @@ export type ConstraintResult = | { kind: 'allow' } - | { kind: 'deny'; reason: string; code?: string } + | { kind: 'deny'; reason: string; code?: string; constraintName?: string } | { kind: 'unknown'; missing: RequirementKey[] }; export type RequirementKey =