utilitai
Version:
Utility AI library built with TypeScript
191 lines (154 loc) • 4.28 kB
text/typescript
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