|
|
|
@@ -1,7 +1,8 @@ |
|
|
|
import { Creature } from './creature' |
|
|
|
import { Encounter } from './combat' |
|
|
|
import { LogEntry } from './interface' |
|
|
|
import { Encounter, Action } from './combat' |
|
|
|
import { LogEntry, nilLog } from './interface' |
|
|
|
import { ReleaseAction, TransferAction, PassAction } from './combat/actions' |
|
|
|
import { NoPassDecider, NoReleaseDecider, ChanceDecider } from './ai/deciders' |
|
|
|
|
|
|
|
export interface AI { |
|
|
|
name: string; |
|
|
|
@@ -15,6 +16,62 @@ export class NoAI implements AI { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 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. |
|
|
|
*/ |
|
|
|
@@ -33,21 +90,15 @@ export class RandomAI implements AI { |
|
|
|
/** |
|
|
|
* The VoreAI tries to eat opponents, but only if the odds are good enough |
|
|
|
*/ |
|
|
|
export class VoreAI extends RandomAI { |
|
|
|
name = "Vore AI" |
|
|
|
decide (actor: Creature, encounter: Encounter): LogEntry { |
|
|
|
const actions = encounter.combatants.flatMap(enemy => actor.validActions(enemy).map(action => ({ |
|
|
|
target: enemy, |
|
|
|
action: action |
|
|
|
}))) |
|
|
|
const voreActions = actions.filter(action => actor.otherContainers.concat(actor.containers).some(container => container.actions.includes(action.action) || action instanceof TransferAction)) |
|
|
|
const aggressiveActions = voreActions.filter(action => !(action.action instanceof ReleaseAction) && !(action.action instanceof PassAction)) |
|
|
|
const likelyActions = aggressiveActions.filter(action => action.action.odds(actor, action.target) > 0.4) |
|
|
|
likelyActions.forEach(action => console.log(action.action.odds(actor, action.target))) |
|
|
|
const chosen = likelyActions[Math.floor(Math.random() * likelyActions.length)] |
|
|
|
if (chosen === undefined) { |
|
|
|
return super.decide(actor, encounter) |
|
|
|
} |
|
|
|
return chosen.action.try(actor, chosen.target) |
|
|
|
export class VoreAI extends DeciderAI { |
|
|
|
constructor () { |
|
|
|
super ( |
|
|
|
"Vore AI", |
|
|
|
[ |
|
|
|
new NoPassDecider(), |
|
|
|
new NoReleaseDecider(), |
|
|
|
new ChanceDecider() |
|
|
|
] |
|
|
|
) |
|
|
|
} |
|
|
|
} |