import { Creature } from './creature' import { Encounter, Action, Damage, StatusEffect } from './combat' import { LogEntry, nilLog } from './interface' import { ReleaseAction, TransferAction, PassAction } from './combat/actions' import { NoPassDecider, NoReleaseDecider, ChanceDecider, ConsequenceDecider, ConsequenceFunctionDecider, NoSurrenderDecider } from './ai/deciders' import { SurrenderEffect } from './combat/effects' import { StatusConsequence, DamageConsequence } from './combat/consequences' export interface AI { name: string; decide (actor: Creature, encounter: Encounter): LogEntry; } export class NoAI implements AI { name = "No AI" decide (actor: Creature, encounter: Encounter): LogEntry { throw new Error("This AI cannot be used.") } } /** * A Decider determines how favorable an action is to perform. */ export interface Decider { decide: (encounter: Encounter, user: Creature, target: Creature, action: Action) => number; } export class DeciderAI implements AI { constructor (public name: string, private deciders: Decider[]) { } decide (actor: Creature, encounter: Encounter): LogEntry { const options = encounter.combatants.flatMap(enemy => actor.validActions(enemy).map(action => ({ target: enemy, action: action, weight: 1 }))) console.log(options) this.deciders.forEach( decider => options.forEach( option => { option.weight *= decider.decide(encounter, actor, option.target, option.action) } ) ) let total = options.reduce( (sum: number, option) => sum + option.weight, 0 ) total *= Math.random() console.log(total) const chosen = options.find( option => { if (total < option.weight) { return true } else { total -= option.weight return false } } ) if (chosen !== undefined) { return chosen.action.try(actor, chosen.target) } // should never reach this point! throw new Error("Couldn't pick an action") } } /** * The RandomAI is **COMPLETELY** random. Good luck. */ export class RandomAI implements AI { name = "Random AI" decide (actor: Creature, encounter: Encounter): LogEntry { const actions = encounter.combatants.flatMap(enemy => actor.validActions(enemy).map(action => ({ target: enemy, action: action }))) const chosen = actions[Math.floor(Math.random() * actions.length)] return chosen.action.try(actor, chosen.target) } } /** * The VoreAI tries to eat opponents, but only if the odds are good enough */ export class VoreAI extends DeciderAI { constructor () { super( "Vore AI", [ new NoReleaseDecider(), new ChanceDecider(), new NoSurrenderDecider() ] ) } }