트리거 시스템 및 랜덤 생성기 인터페이스 추가: 기본 구조 정의
This commit is contained in:
@@ -1,2 +1,4 @@
|
||||
export * from './domain/entities';
|
||||
export * from './ports/rng';
|
||||
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