Pokémon TRPS in Node (TypeScript)

Switching to TypeScript

Hello once again everyone! Last post I finished the final Dovahzul bot, therefore this week I will be starting an entirely new bot.  Pokémon-TRPS became a more ambitious bot than I had expected, so there is much information to share. Rather than sticking with JavaScript, I made the executive decision to switch to TypeScript. Since TypeScript is a superset to JavaScript, I figured the change would not be too significant. There are three main reasons for this change:

Reason 1: Another project I am working on uses TypeScript, thus it would make more sense for me to continue only learning TypeScript to not cause further confusion by learning both at the same time. 

Reason 2: TypeScript can be written in a more object-oriented style further differentiating itself from python. 

Reason 3: Since significantly less resources exist for writing discord bots in TypeScript, I believe this blog will now provide more useful information. 

Thanks to TypeScript, a class I created called "Bot" now extends all discord.js client functionality. 

Designing Pokémon-TRPS 

As mentioned in a previous post, Pokémon-TRPS is a more sophisticated form of Rock Paper Scissors. Instead of having three choices, the player chooses between 17 different types. Types are strong, weak, or immune against other types. All types properties are shown in the type chart below. The green squares indicate that the attacking type is strong against the defending type. For Pokémon-TRPS, I will only consider these green strengths. The player that selects a type strong against another player type wins, much like rock-paper-scissors.  

Type chart from https://pokemondb.net/type

Compared to Dovahzul's one command to translate, Pokémon-TRPS has five unique commands.

  • play - create a new game with another player on the server
  • playcpu - create a new game with the bot
  • type - show type information (strength, weakness, immunes, reaction emoji) 
  • types - show all possible types
  • games - show list of active games

Project Pipeline Changes

In light of TypeScript, some changes occurred to the project structure and general dependencies. Luckily discord.js automatically includes TypeScript tpyings, therefore no additional typing modules are required. The typings are yet another plus to chose Node over Python and Java. The main change was the inclusion of a type script config file and additional compiler options. All the code for this bot is available on my GitHub. (See link in results section) The property "experimentalDecorators" needs to be true for my implementation of the command design pattern.

tsconfig.json

Besides TypeScript, another dependency I installed was ts-node. This dependency let TypeScript compile and run with a single npm script all within nodemon

nodemon --exec ts-node src/App.ts -e ts

Command Design Pattern

Given the larger amount of Commands, I decided to utilize the command design pattern.  

CommandManager.ts

All the commands are loaded from a command directory, and have the Command decoration provided by the CommandManager. This decoration tells TypeScript to add the command field to the given 
command and attaches its implementation of execute.

Types.ts


After all the commands are imported into some data structure like a discord.js Collection, the correct one can executed within handleCommand. This method gets called on the typical messageCreate event like the Dovahzul bots. Instead of using a large switch statement, the command functionality is thoroughly mapped out using an interface. Now commands can be added or removed from the bot dynamically. 

lines 77-91, Bot.ts

Type Mapping  

lines 198 - 204, Game.ts

Given 17 Pokémon types with each having their own sets of strengths and discord reactions, I included a few helper methods in the Game class to retrieve type data. I stored the type data as a JSON string originally, but later I switched to a regular JavaScript object. Either way would work, however I decided to save myself from parsing JSON.

lines 310 - 329, Data.ts

I mapped a Pokémon Type data object containing the information for each type to a string representation. The mapping is essential for quickly retrieving the data for the correct type. Otherwise, the bot needs to search for the data in a more algorithmic manner.

lines 303 - 308, Data.ts

One additional type, "missing", was added in the event a user or the bot attempts to reference a type that does not exist. 


Instanced Games

So how can multiple games persist simultaneously across the same Discord text channel?  Or let alone entirely different servers? Well besides most methods being asynchronous, I had to create a list to store all the active games. Each Game also has a randomly generated unique id for differentiation. 

lines 106 - 111, Bot.ts


lines 27 - 41, Game.ts


The "play" and "playcpu" commands both call the createGame method and trigger the start of the Game. The game takes a few seconds to initialize since as reaction calls need to be made separately in order to preserve order. The message which the game takes place in will become edited after each update. Using edited messages rather than creating new messages reduces channel spam and optimizes gameplay speed.  Each Game instance stores an objects for each user, as well as multiple other fields for determining the status of the game. When the game is over, the finished field becomes true and the Game becomes removable from the Bot's active games list.  We do not want to support infinite game instances because of memory limitations. Using a package called cron, inactive games may be purged every other minute. 

Reactions

Now with instanced games working, the final piece of the puzzle is reaction collection. Bot's need to create a "reaction request" to send to Discord's API, and unfortunately this often causes massive slowdowns. Embedded Discord buttons may have been more performant. Reactions are pushed onto the message using the client's react method.  

lines 157 - 166, Game.ts

Before creating a reaction collector, I declared a filter function which filters out reactions from users not participating in the game. 

lines 54 - 58, Game.ts

Discord.js provides a createReactionCollector method for Message objects which enables the tracking of reactions. The filter function as well as other information such as time may be supplied to the creation method. When the message reaches the time limit from the creation of the collector, the collector's "end" event fires. The collector's "collect" event triggers after any added reaction that successfully passes the filter. The "end" event may also be triggered by the collector's  stop method, which I actually utilized for ending the game prior to the time limit.

lines 63 - 81, Game.ts

Once both users selected reactions, the onReactionCollectorEnd method gets executed as a callback for the collector's "end" event. In onReactionCollectorEnd the types are compared and the game declares a winner or a tie.

Results

The results for this bot were astounding! Shoutout to Michael Chiro for testing the play command and overall game functionality. The Game takes around 10 seconds to initialize which is rather annoying, however everything is fully functional. When there are multiple games loading at the same time the wait becomes pretty unbearable.  The reaction successfully disappears before the opposing player sees what was selected. Therefore, there is no incentive to go second as you will have no intellectual advantage. Below are some screenshots of the bot in action! 












GitHub Link for Pokemon-TRPS-Node

Comments

  1. Nice to see you using typescript, I honestly don't see it often so its a nice change. Also, out of most of the blogs I've seen in our class you are one of the ones who put in the most work so good job. I can barely understand all the types weaknesses/strengths when playing the actual games so I can't say I'm necessarily stoked for a game focused only on that... but maybe it would be good practice!

    ReplyDelete

Post a Comment