ai.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. const Move = require('./move')
  2. const discord = require('discord.js')
  3. const _ = require('lodash')
  4. const prompt = require('../util/prompt')
  5. // The base class! Each method is called to request a decision from the AI.
  6. // Instanced on a battle-by-battle, per-character basis.
  7. class AI {
  8. constructor(char, battle, team) {
  9. this.char = char
  10. this.battle = battle
  11. this.team = team
  12. }
  13. // Returns { move: Move, targets: [Character] } to perform next.
  14. async moveChoice(self, battle) {}
  15. }
  16. // For player-controlled characters. Interfaces with Discord.
  17. class DiscordAI extends AI {
  18. async moveChoice() {
  19. // Move
  20. const moves = await Promise.all(
  21. this.char.moveIDs.map(id => Move.findOne({_id: id}))
  22. )
  23. const {move} = await prompt({
  24. channel: this.team.channel,
  25. user: this.char,
  26. title: this.char.getName(this.battle.game.guild),
  27. description: 'Next move:',
  28. choices: moves.map(move => ({
  29. emote: move.emote,
  30. name: `**${move.name}** - ${move.description}`,
  31. move,
  32. })),
  33. })
  34. // Target(s)
  35. let targetPromptDesc = `Use _${move.name}_ on:`
  36. if (move.target.numberMin === 1 && move.target.numberMax > 1) {
  37. targetPromptDesc += ` (pick up to ${move.target.numberMax})`
  38. } else if (move.target.numberMax > move.target.numberMin) {
  39. targetPromptDesc += ` (pick ${move.target.numberMin} - ${
  40. move.target.numberMax
  41. })`
  42. }
  43. let targetable
  44. switch (move.target.type) {
  45. // Only yourself.
  46. case 'self':
  47. targetable = [this.char]
  48. break
  49. // Everyone in your party but yourself.
  50. case 'party':
  51. targetable = this.team.party.members.filter(
  52. m => m.discordID !== this.char.discordID
  53. )
  54. break
  55. // Anyone in a different party (ie. hostile),
  56. case 'enemy':
  57. targetable = _.flatten(
  58. this.battle.teams
  59. .map(({party}) => party)
  60. .filter(party => party._id !== this.team.party._id)
  61. .map(party => party.members)
  62. )
  63. break
  64. // Anyone else in the battle.
  65. case 'any':
  66. targetable = this.battle.everyone.filter(m => m._id !== this.char._id)
  67. break
  68. }
  69. /*if (targetable.length === 1) {
  70. // Only one character can be targeted, so just choose them.
  71. return {move, targets: targetable}
  72. } else */ if (
  73. targetable.length === 0
  74. ) {
  75. // No-one can be targeted, so ask for a different move choice.
  76. // TODO: handle this case better?
  77. const msg = await this.team.channel.send('No targets for that move!')
  78. const ret = await this.moveChoice()
  79. await msg.delete()
  80. return ret
  81. }
  82. // TODO: add a back button
  83. const targets = await prompt.multi({
  84. channel: this.team.channel,
  85. user: this.char,
  86. title: this.char.getName(this.battle.game.guild),
  87. description: targetPromptDesc,
  88. choices: await Promise.all(
  89. targetable.map(async (char, i) => ({
  90. emote: await char.getEmote(this.battle.game),
  91. name: char.getName(this.battle.game.guild),
  92. char,
  93. }))
  94. ),
  95. chooseMin: move.target.numberMin,
  96. chooseMax: move.target.numberMax,
  97. })
  98. return {move, targets: targets.map(choice => choice.char)}
  99. }
  100. }
  101. module.exports = {DiscordAI}