Feast 2.0!
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

107 lines
2.7 KiB

  1. import { Creature } from './creature'
  2. import { Encounter, Action, Damage, StatusEffect } from './combat'
  3. import { LogEntry, nilLog } from './interface'
  4. import { ReleaseAction, TransferAction, PassAction } from './combat/actions'
  5. import { NoPassDecider, NoReleaseDecider, ChanceDecider, ConsequenceDecider, ConsequenceFunctionDecider, NoSurrenderDecider } from './ai/deciders'
  6. import { SurrenderEffect } from './combat/effects'
  7. import { StatusConsequence, DamageConsequence } from './combat/consequences'
  8. export interface AI {
  9. name: string;
  10. decide (actor: Creature, encounter: Encounter): LogEntry;
  11. }
  12. export class NoAI implements AI {
  13. name = "No AI"
  14. decide (actor: Creature, encounter: Encounter): LogEntry {
  15. throw new Error("This AI cannot be used.")
  16. }
  17. }
  18. /**
  19. * A Decider determines how favorable an action is to perform.
  20. */
  21. export interface Decider {
  22. decide: (encounter: Encounter, user: Creature, target: Creature, action: Action) => number;
  23. }
  24. export class DeciderAI implements AI {
  25. constructor (public name: string, private deciders: Decider[]) {
  26. }
  27. decide (actor: Creature, encounter: Encounter): LogEntry {
  28. const options = encounter.combatants.flatMap(enemy => actor.validActions(enemy).map(action => ({
  29. target: enemy,
  30. action: action,
  31. weight: 1
  32. })))
  33. console.log(options)
  34. this.deciders.forEach(
  35. decider => options.forEach(
  36. option => { option.weight *= decider.decide(encounter, actor, option.target, option.action) }
  37. )
  38. )
  39. let total = options.reduce(
  40. (sum: number, option) => sum + option.weight,
  41. 0
  42. )
  43. total *= Math.random()
  44. console.log(total)
  45. const chosen = options.find(
  46. option => {
  47. if (total < option.weight) {
  48. return true
  49. } else {
  50. total -= option.weight
  51. return false
  52. }
  53. }
  54. )
  55. if (chosen !== undefined) {
  56. return chosen.action.try(actor, chosen.target)
  57. }
  58. // should never reach this point!
  59. throw new Error("Couldn't pick an action")
  60. }
  61. }
  62. /**
  63. * The RandomAI is **COMPLETELY** random. Good luck.
  64. */
  65. export class RandomAI implements AI {
  66. name = "Random AI"
  67. decide (actor: Creature, encounter: Encounter): LogEntry {
  68. const actions = encounter.combatants.flatMap(enemy => actor.validActions(enemy).map(action => ({
  69. target: enemy,
  70. action: action
  71. })))
  72. const chosen = actions[Math.floor(Math.random() * actions.length)]
  73. return chosen.action.try(actor, chosen.target)
  74. }
  75. }
  76. /**
  77. * The VoreAI tries to eat opponents, but only if the odds are good enough
  78. */
  79. export class VoreAI extends DeciderAI {
  80. constructor () {
  81. super(
  82. "Vore AI",
  83. [
  84. new NoReleaseDecider(),
  85. new ChanceDecider(),
  86. new NoSurrenderDecider()
  87. ]
  88. )
  89. }
  90. }