트리거 시스템 및 랜덤 생성기 인터페이스 추가: 기본 구조 정의
This commit is contained in:
@@ -1,2 +1,4 @@
|
|||||||
export * from './domain/entities';
|
export * from './domain/entities';
|
||||||
|
export * from './ports/rng';
|
||||||
export * from './ports/world';
|
export * from './ports/world';
|
||||||
|
export * from './triggers';
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export interface RandomGenerator {
|
||||||
|
nextFloat(): number;
|
||||||
|
nextBool(probability: number): boolean;
|
||||||
|
nextInt(minInclusive: number, maxExclusive: number): number;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export * from './core';
|
||||||
|
export * from './general';
|
||||||
|
export * from './general-action';
|
||||||
Reference in New Issue
Block a user