트리거 시스템 및 랜덤 생성기 인터페이스 추가: 기본 구조 정의

This commit is contained in:
2025-12-27 04:36:38 +00:00
parent b42d87a51b
commit 206aa64c64
6 changed files with 343 additions and 0 deletions
+2
View File
@@ -1,2 +1,4 @@
export * from './domain/entities';
export * from './ports/rng';
export * from './ports/world';
export * from './triggers';
+5
View File
@@ -0,0 +1,5 @@
export interface RandomGenerator {
nextFloat(): number;
nextBool(probability: number): boolean;
nextInt(minInclusive: number, maxExclusive: number): number;
}
+78
View File
@@ -0,0 +1,78 @@
export const TriggerPriority = {
Min: 0,
Begin: 10000,
Pre: 20000,
Body: 30000,
Post: 40000,
Final: 50000,
} as const;
export type TriggerPriority = (typeof TriggerPriority)[keyof typeof TriggerPriority];
export interface Trigger<TContext, TEnv = Record<string, unknown>, TArg = unknown> {
readonly priority: number;
readonly uniqueId: string;
action(context: TContext, env: TEnv, arg?: TArg): TEnv;
}
export class TriggerCaller<TContext, TEnv = Record<string, unknown>, TArg = unknown> {
private readonly triggersByPriority = new Map<number, Map<string, Trigger<TContext, TEnv, TArg>>>();
constructor(...triggers: Array<Trigger<TContext, TEnv, TArg>>) {
if (triggers.length === 0) {
return;
}
for (const trigger of triggers) {
this.append(trigger);
}
}
isEmpty(): boolean {
return this.triggersByPriority.size === 0;
}
append(trigger: Trigger<TContext, TEnv, TArg>): this {
const priority = trigger.priority;
const uniqueId = trigger.uniqueId;
const bucket = this.triggersByPriority.get(priority) ?? new Map();
bucket.set(uniqueId, trigger);
this.triggersByPriority.set(priority, bucket);
return this;
}
merge(other: TriggerCaller<TContext, TEnv, TArg> | null | undefined): this {
if (!other) {
return this;
}
for (const [priority, otherBucket] of other.triggersByPriority.entries()) {
const bucket = this.triggersByPriority.get(priority) ?? new Map();
for (const [uniqueId, trigger] of otherBucket.entries()) {
bucket.set(uniqueId, trigger);
}
this.triggersByPriority.set(priority, bucket);
}
return this;
}
fire(context: TContext, env: TEnv, arg?: TArg): TEnv {
let currentEnv = env;
const priorities = Array.from(this.triggersByPriority.keys()).sort((a, b) => a - b);
for (const priority of priorities) {
const bucket = this.triggersByPriority.get(priority);
if (!bucket) {
continue;
}
for (const trigger of bucket.values()) {
currentEnv = trigger.action(context, currentEnv, arg);
}
}
return currentEnv;
}
}
@@ -0,0 +1,175 @@
import type { GeneralTriggerState } from '../domain/entities';
import { type GeneralActionContext, GeneralTriggerCaller } from './general';
export interface GeneralActionModule<TriggerState extends GeneralTriggerState = GeneralTriggerState> {
getName?(): string;
getInfo?(): string;
getPreTurnExecuteTriggerList?(
context: GeneralActionContext<TriggerState>
): GeneralTriggerCaller<TriggerState> | null;
onCalcDomestic?(
context: GeneralActionContext<TriggerState>,
turnType: string,
varType: string,
value: number,
aux?: unknown
): number;
onCalcStat?(
context: GeneralActionContext<TriggerState>,
statName: string,
value: number,
aux?: unknown
): number;
onCalcOpposeStat?(
context: GeneralActionContext<TriggerState>,
statName: string,
value: number,
aux?: unknown
): number;
onCalcStrategic?(
context: GeneralActionContext<TriggerState>,
turnType: string,
varType: string,
value: number
): number;
onCalcNationalIncome?(
context: GeneralActionContext<TriggerState>,
type: string,
amount: number
): number;
onArbitraryAction?(
context: GeneralActionContext<TriggerState>,
actionType: string,
phase?: string | null,
aux?: Record<string, unknown> | null
): Record<string, unknown> | null;
}
export class GeneralActionPipeline<TriggerState extends GeneralTriggerState = GeneralTriggerState> {
private readonly modules: GeneralActionModule<TriggerState>[];
constructor(modules: Array<GeneralActionModule<TriggerState> | null | undefined>) {
this.modules = modules.filter(Boolean) as GeneralActionModule<TriggerState>[];
}
getPreTurnExecuteTriggerList(
context: GeneralActionContext<TriggerState>
): GeneralTriggerCaller<TriggerState> {
const triggerCaller = new GeneralTriggerCaller<TriggerState>();
for (const module of this.modules) {
const triggers = module.getPreTurnExecuteTriggerList?.(context);
if (triggers) {
triggerCaller.merge(triggers);
}
}
return triggerCaller;
}
onCalcDomestic(
context: GeneralActionContext<TriggerState>,
turnType: string,
varType: string,
value: number,
aux?: unknown
): number {
let current = value;
for (const module of this.modules) {
if (!module.onCalcDomestic) {
continue;
}
current = module.onCalcDomestic(context, turnType, varType, current, aux);
}
return current;
}
onCalcStat(
context: GeneralActionContext<TriggerState>,
statName: string,
value: number,
aux?: unknown
): number {
let current = value;
for (const module of this.modules) {
if (!module.onCalcStat) {
continue;
}
current = module.onCalcStat(context, statName, current, aux);
}
return current;
}
onCalcOpposeStat(
context: GeneralActionContext<TriggerState>,
statName: string,
value: number,
aux?: unknown
): number {
let current = value;
for (const module of this.modules) {
if (!module.onCalcOpposeStat) {
continue;
}
current = module.onCalcOpposeStat(context, statName, current, aux);
}
return current;
}
onCalcStrategic(
context: GeneralActionContext<TriggerState>,
turnType: string,
varType: string,
value: number
): number {
let current = value;
for (const module of this.modules) {
if (!module.onCalcStrategic) {
continue;
}
current = module.onCalcStrategic(context, turnType, varType, current);
}
return current;
}
onCalcNationalIncome(
context: GeneralActionContext<TriggerState>,
type: string,
amount: number
): number {
let current = amount;
for (const module of this.modules) {
if (!module.onCalcNationalIncome) {
continue;
}
current = module.onCalcNationalIncome(context, type, current);
}
return current;
}
onArbitraryAction(
context: GeneralActionContext<TriggerState>,
actionType: string,
phase?: string | null,
aux?: Record<string, unknown> | null
): Record<string, unknown> | null {
let current = aux ?? null;
for (const module of this.modules) {
if (!module.onArbitraryAction) {
continue;
}
const result = module.onArbitraryAction(context, actionType, phase, current);
if (result !== undefined) {
current = result;
}
}
return current;
}
}
+80
View File
@@ -0,0 +1,80 @@
import type { General, GeneralTriggerState } from '../domain/entities';
import type { RandomGenerator } from '../ports/rng';
import type { WorldStateRepository } from '../ports/world';
import { TriggerCaller, type Trigger } from './core';
export interface GeneralActionLogSink {
push(message: string): void;
}
export interface GeneralSkillActivation {
has(key: string): boolean;
activate(...keys: string[]): void;
}
export const createGeneralSkillActivation = <
TriggerState extends GeneralTriggerState
>(
general: General<TriggerState>
): GeneralSkillActivation => ({
has: (key: string) => Boolean(general.triggerState.flags[key]),
activate: (...keys: string[]) => {
for (const key of keys) {
general.triggerState.flags[key] = true;
}
},
});
export interface GeneralActionContext<TriggerState extends GeneralTriggerState = GeneralTriggerState> {
general: General<TriggerState>;
world?: WorldStateRepository;
log?: GeneralActionLogSink;
rng?: RandomGenerator;
}
export interface GeneralTriggerContext<TriggerState extends GeneralTriggerState = GeneralTriggerState>
extends GeneralActionContext<TriggerState> {
rng: RandomGenerator;
skill: GeneralSkillActivation;
}
export const createGeneralTriggerContext = <
TriggerState extends GeneralTriggerState
>(
context: GeneralActionContext<TriggerState> & { rng: RandomGenerator }
): GeneralTriggerContext<TriggerState> => ({
...context,
skill: createGeneralSkillActivation(context.general),
});
export type GeneralTrigger<
TriggerState extends GeneralTriggerState = GeneralTriggerState,
Env extends Record<string, unknown> = Record<string, unknown>,
Arg = unknown
> = Trigger<GeneralTriggerContext<TriggerState>, Env, Arg>;
export class GeneralTriggerCaller<
TriggerState extends GeneralTriggerState = GeneralTriggerState,
Env extends Record<string, unknown> = Record<string, unknown>,
Arg = unknown
> extends TriggerCaller<GeneralTriggerContext<TriggerState>, Env, Arg> {}
export abstract class BaseGeneralTrigger<
TriggerState extends GeneralTriggerState = GeneralTriggerState,
Env extends Record<string, unknown> = Record<string, unknown>,
Arg = unknown
> implements GeneralTrigger<TriggerState, Env, Arg> {
public abstract readonly priority: number;
protected constructor(protected readonly general: General<TriggerState>) {}
get uniqueId(): string {
return `${this.priority}_${this.constructor.name}_${this.general.id}`;
}
abstract action(
context: GeneralTriggerContext<TriggerState>,
env: Env,
arg?: Arg
): Env;
}
+3
View File
@@ -0,0 +1,3 @@
export * from './core';
export * from './general';
export * from './general-action';