ai.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  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(this.char.moveIDs.map(id => Move.findOne({_id: id})))
  21. const { move } = await prompt({
  22. channel: this.team.channel,
  23. user: this.char,
  24. title: this.char.getName(this.battle.game.guild),
  25. description: 'Next move:',
  26. choices: moves.map(move => ({emote: move.emote, name: `**${move.name}** - ${move.description}`, move}))
  27. })
  28. // Target(s)
  29. let targetPromptDesc = `Use _${move.name}_ on:`
  30. if (move.target.numberMin === 1 && move.target.numberMax > 1) {
  31. targetPromptDesc += ` (pick up to ${move.target.numberMax})`
  32. } else if (move.target.numberMax > move.target.numberMin) {
  33. targetPromptDesc += ` (pick ${move.target.numberMin} - ${move.target.numberMax})`
  34. }
  35. let targetable
  36. switch (move.target.type) {
  37. // Only yourself.
  38. case 'self':
  39. targetable = [this.char]
  40. break
  41. // Everyone in your party but yourself.
  42. case 'party':
  43. targetable = this.team.party.members
  44. .filter(m => m.discordID !== this.char.discordID)
  45. break
  46. // Anyone in a different party (ie. hostile),
  47. case 'enemy':
  48. targetable = _.flatten(this.battle.teams
  49. .map(({ party }) => party)
  50. .filter(party => party._id !== this.team.party._id)
  51. .map(party => party.members))
  52. break
  53. // Anyone else in the battle.
  54. case 'any':
  55. targetable = this.battle.everyone
  56. .filter(m => m._id !== this.char._id)
  57. break
  58. }
  59. if (targetable.length === 1) {
  60. // Only one character can be targeted, so just choose them.
  61. return {move, targets: targetable}
  62. } else if (targetable.length === 0) {
  63. // No-one can be targeted, so ask for a different move choice.
  64. // TODO: handle this case better?
  65. const msg = await this.team.channel.send('No targets for that move!')
  66. const ret = await this.moveChoice()
  67. await msg.delete()
  68. return ret
  69. }
  70. // TODO: think of a better way to represent users
  71. const targetEmotes = ['😀', '😂', '😉', '😎', '😏', '😐', '😜']
  72. const targets = await prompt.multi({
  73. channel: this.team.channel,
  74. user: this.char,
  75. title: this.char.getName(this.battle.game.guild),
  76. description: targetPromptDesc,
  77. choices: targetable
  78. .map((char, i) => (
  79. {emote: targetEmotes[i], name: char.getName(this.battle.game.guild), char}
  80. )),
  81. chooseMin: move.target.numberMin,
  82. chooseMax: move.target.numberMax,
  83. })
  84. return {move, targets: targets.map(choice => choice.char)}
  85. }
  86. }
  87. module.exports = {DiscordAI}