A bot to play games with you in Slack
1.2K
A slack bot that plays games with you!
This bot was originally written as part of a 24 hour hackathon: HackUpsate vii.
The game bot can play the following games:
In this game, the bot will "hide" in a random room they are present in. Players then need to join the room (if they are not already in it) and say the bot's name. If they guess right before anyone else they win the round.
@botname play Hide and SeekIn this game, the bot will pick a random number between 1 and 100. Users then try to guess the game and the bot will give them hints based on how close they are.
@botname play Hot or ColdIn this game, the bot will pick a random letter. Users then try to come up with the longest word that starts with that letter. The user that picks the longest word first wins!
Note: An API key from dictionaryapi.com is needed for work lookup. You will need to set the DICTIONARY_KEY environmental variable with your key
@botname play Longest WordIn this game, the bot will generate a random simple math problem. The first player to answer the problem correctly wins the round.
@botname play MathGames have the option of collecting and displaying statistics on games such as tracking winners and records. Stats can be persisted to a database using various available drivers.
To get help from the bot, simply type @botname help.
To get help about a specific game, type @botname help <game name>
To get a list of games, type @botname play
To play a game, type @botname play <game name>
To get stats on a game, type @botname stats <game name>
npm install./config.default.js to ./config.js and fill it outnpm startThe bot can persist stats to various locations. By default stats will be persisted to memory.
The Memory drive will persist the data in the apps memory. This means the stats will be lost if the app is restarted.
Config None.
The config file should be filled out as follows:
Build an image using docker build -t your_image:tag
Official Image shaunburdick/gamebot
You can set the configuration of the bot by using environment variables. ENVIRONMENT_VARIABLE=Default Value
Set them using the -e flag while running docker:
docker run -it \
-e SLACK_TOKEN=xobo-blarty-blar-blar \
shaunburdick/gamebot:latest
npm test to see the tests fail.npm test to see if the tests pass. Repeat steps 2-4 until done.The bot was created in a way that makes adding your own games pretty easy. Most of the communication with Slack is abstracted away from you, so all you have to worry about is the game logic and communicating with the player. To create a new game, first create a new directory in lib/games/ and add an index.js
mkdir lib/games/my-new-game/ && touch lib/games/my-new-game/index.js.
Each game is it's own class, the bot interface is expecting the following methods to be present:
The constructor is the method that is called when the game class is instantiated. This method is a good spot to initialize your game (set up variables, state, etc). The bot passes in three variables, map which contains a map of Slack ID's to users and channels. This can be used later to look up a users information. The map uses .get(<some-id>) to lookup user/channel details. bot is all the information about the bot that instantiated the class such as the bot's name. It also exposes a message method which can be used to push a message from your game out to Slack. And lastly, the logger is an instance of a Winston wrapper that allows you to output debugging and logging info.
/**
* Class constructor
* @param {object} map Map of slack channels and user ids to info
* @param {object} bot Information about the bot
* @param {Logger} logger Instance of logger utility
*/
constructor(map, bot, logger) {
...
}
Next up is the static help method, when a player types @gamebot help <my-new-game-name> the bot will run this method and reply with the help text. This method takes one argument, bot, which is the information about the bot that is calling it. Because the help method is static, the class hasn't been instantiated yet and the bot information wouldn't be available without passing it in here.
/**
* Static method to return help message that explains how to play the game
* @param {object} bot Slack bot information
* @return {Promise} A promise that resolves with a formatted help string
*/
static help(bot) {
const promise = new Promise((resolve) => {
resolve('My help text');
});
return promise;
}
On to the start method, this is what kicks off your game lifecycle. This is called when a player types @gamebot play <my-new-game-name>. The method takes an argument of channel which is information about the channel in which the game was started from. Here's where you'd update the state of your app. Two important things in this method are this.game.end and this.game.error. The method (and most others) return promises that the bot uses to manage the state of a game. When the game is finished, the start method should resolve it's promise using this.game.end which tells the bot that the game is over and tears down the instantiated class. The promise should resolve with any stats you want to store. You can use this.game.error at any time your game is running and it experiences an error.
/**
* Start game lifecycle
* @param {string} channel The channel ID that started the game
* @return {Promise} A promise that will resolve when the game is finished with stats
*/
start(channel) {
const promise = new Promise((resolve, reject) => {
this.game.end = resolve;
this.game.error = reject;
...
});
}
Next up is the heart of your game, the handleMessage. This method is essentially your game loop. When a message comes in to the bot from Slack, it dispatches it to any instantiated games. Your game has to decide if it should respond to those messages. The method takes a single argument of message which contains the message object received from Slack. You can use things like message.type or message.event and the message.text to determine if your game needs to respond. This method must return an Promise, if you need to send a message back you can pass text into your resolve and the bot will send a message to Slack.
/**
* Handle incoming message
* @param {object} message Message object from Slack RTM api
* @param {object} user User information
* @param {object} channel Channel information
* @return {Promise} A promise that resolves with an appropriate response
*/
handleMessage(message) {
...
}
The updateStats() function will be called at the end of every game. This allows your game to update any global/channel stats with new values from the last play. The function gets the current stats, the game history and the last play. It expects you to return the new stats object which will be saved over the previous stats. This function keeps you from having to build stats from the history every time.
/**
* Updates the stats for the Game
* @param {object} stats The previous stats object
* @param {object[]} history The game's stored history
* @param {object} lastPlay The last play through
* @return {Promise} A promise that resolves with the new stats object to be stored
*/
updateStats(stats, history, lastPlay) {
...
}
The formatStats() function is called when a user asks for stats from your game. The function will be passed the latest stored stats object, what channel the call originated, and a new lookup object so you can map ids to users/channels. The bot expects your game to return a formatted string it can then send to slack as your representation of stats.
/**
* Format the stats into a message
*
* @param {object} stats the stats to format
* @param {string} channel the stats to format
* @param {Map} lookup A lookup map if needed
* @return {string} the formated message
*/
static formatStats(stats, channel) {
...
}
And the last bit you need to add to the index.js is an export so the bot can include your game and configs. Note that the config.name is what users will have to type to play. The config.unique.global is used to deterine if there should only ever be one instance of your game running Slack wide. If set to false, your game can be running in multiple channels.
module.exports = {
config: {
name: '<my-new-game-name>',
unique: {
global: true,
},
},
Game: <my-new-class-name>,
};
Content type
Image
Digest
Size
36.6 MB
Last updated
over 10 years ago
docker pull shaunburdick/gamebot