UNPKG

utilitai

Version:

Utility AI library built with TypeScript

191 lines (154 loc) 4.28 kB
import './style.css' /** UTILITY AI */ class UtilityAI<TContext> { updatePeriod: number; #rootReasoner: Reasoner<TContext>; constructor(context: TContext, reasoner: Reasoner<TContext>, updatePeriod = 1) { this.updatePeriod = updatePeriod; this.#rootReasoner = reasoner; } } /** * REASONERS - TOP LEVEL "ACTION" * * select CONSIDERATION from a list of CONSIDERATIONS * * * first score * * * highest score */ interface Reasoner<TContext> { considerations: Consideration<TContext>[], selectBestConsideration: (context: TContext) => Consideration<TContext> | null; } /** * CONSIDERATIONS - SUB * * contains a list of APPRAISALS and ACTIONS * * calculates the score which represents numerically the utility of its ACTION */ interface Consideration<TContext> { // score: number; getScore: (context: TContext) => number; action?: Action<TContext>; appraisals?: Appraisal<TContext>[]; } /** * Score appraisals by accumulating their scores */ class AveragedConsideration<TContext> implements Consideration<TContext> { action?: Action<TContext>; appraisals?: Appraisal<TContext>[]; constructor(action: Action<TContext>, appraisals: Appraisal<TContext>[]) { this.action = action; this.appraisals = appraisals; } getScore(context: TContext) { if (!this.appraisals || this.appraisals?.length === 0) return 0; let totalScore = 0; this.appraisals?.forEach(appraisal => { const score = appraisal.getScore(context); totalScore += score; }) return totalScore / this.appraisals.length; } } /** * Score child appraisals until child scores below threshold */ class ThresholdConsideration<TContext, TPersonality> implements Consideration<TContext> { threshold: number; action?: Action<TContext>; appraisals?: Appraisal<TContext>[]; constructor(threshold: number, action: Action<TContext>, appraisals: Appraisal<TContext>[]) { this.action = action; this.appraisals = appraisals; this.threshold = threshold; } getScore = (context: TContext) => { let sum = 0; this.appraisals?.forEach(appraisal => { const score = appraisal.getScore(context); if (score < this.threshold) return sum; sum += score; }) return sum; } } /** * APPRAISALS * * the calculated Utility * * * https://www.desmos.com/calculator */ interface Appraisal<TContext> { weight: number getScore: (context: TContext) => number; } /** * ACTIONS */ interface Action<TContext> { name: string; execute: (context?: TContext) => void; } /** * AGENT * * context * * personality * * currentAction */ interface Agent<TContext> { name: string; currentAction: string | null; context: TContext } type ContextualUnitData = { distance: number, health: number, power: number } // implement game state / context type Context = { health: number, attackRange: number, moveSpeed: number, allies: ContextualUnitData[], enemies: ContextualUnitData[], } // build actions class FleeAction implements Action<Context> { name = "Flee"; execute(context?: Context): void { // fleeing } } /** * appraisal should have a weight, getScore, and */ const enemiesNearby = () => { /** calculate value of avoiding enemies */ // define what AI considers "close" // get the number of enemies within "close" range and LoS // return number based on "fearfulness" } const healthRemaining = () => { // define the flee threshold // get the remaining health (percentage) // return calculation of remaining health * flee threshold } const alliesNearby = () => { /** calculate value of retreating to allies */ // check for a "cluster" of allies within "range" // } class EatAction implements Action<Context> { name = "Eat"; execute(context?: Context): void { // eating } } class DrinkAction implements Action<Context> { name = "Drink"; execute(context?: Context): void { // drinking } } // build appraisals // place action / appraisal combinations into considerations // place considerations into reasoner