A modular, scalable and internationalized game bot that enables multiple games to be played through a unified core, supporting new games and interaction channels via simple interface implementations without altering the core logic.
Here's Lowdie (pronounced low as in "low temperature" + dee as in the letter D):
It can play rock-paper-scissors, tic-tac-toe and blackjack.
There are two main abstractions on the project: modules and interaction channels.
Every functionality of the bot is represented by a module, which has no internal state. The bot itself is a module, as well as its games and additional functionality (such as retry).
flowchart TD
subgraph Bot["Bot Module"]
subgraph RetryTTT["Retry Module"]
TicTacToe["Tic-Tac-Toe Module"]
end
subgraph RetryRPS["Retry Module"]
RockPaperScissors["Rock-Paper-Scissors Module"]
end
subgraph RetryRPS["Retry Module"]
Blackjack["Blackjack Module"]
end
end
A module is driven by states and events, just like a bloc.
A state can be anything the implementer chooses, but it must obey three rules:
- It must be an object
- It must have a
stringproperty namedtype - It must have a final state where by
typeis equal to "done"
An event has no specific rules, so it can be anything (although it is recommended for it to also be an object
with a string property named type, so that you can do exhaustiveness checking when implementing a module).
A module implementer must be able to answer three questions:
- How to obtain the module's initial state?
- Given a state
sand an evente, what should be the module's next state? - Given a state
s, what events should be available for the user to emit?
The last question can be answered by instantiating an action. There are two types of actions:
- The select action has a list of events that the user can choose from (multiple choice). For example, when selecting a move in a rock-paper-scissors game, the user can only choose three options ("rock", "paper" or "scissors").
- The input action parses a text sent by the user into an event (open ended). For example, when selecting a position on a Battleship game, the user would typically input the column + row as a text rather than choosing from a long list.
When implementing a new game, there are three places you can put your implementation:
- domain/entities: declares the base models the game uses
- domain/services: declares the game logic based on the declared entities
- application/use-cases/modules: declares the game module based on the declared logic
For example, the rock-paper-scissors module declares the previous implementations as follows:
- domain/entities: a
Movetype as"rock" | "paper" | "scissors" - domain/services: a
evaluateGame(botMove: Move, userMove: Move): GameResultfunction, which tells who would win in a match - application/use-cases/modules: a module with states
waitingForUseranddone, and eventsuserChose
Unlike the game modules, whose implementations are self-contained, the wrapping modules' implementation depends on another module's implementation.
There are currenlty two wrapping modules: the bot module (which receives a list of modules for the user to choose) and the retry module (which receives a single module).
For example, the retry module answers the previous questions as follows:
- Its initial state is
activeand contain the wrapped module's initial state - Given
- an
activestate and an evente, if the wrapped module's next state wheneis applied is adonestate, the next state should bewaiting. Otherwise, it should be kept asactive - a
waitingstate and acontinueevent, the next state should beactiveand containing the wrapped module's initial state (the retry feature) - a
waitingstate and abreakevent, the next state should bedone
- an
- Given
- an
activestate, the action should be the same as the wrapped module's action for its current state - a
waitingstate, the action should beselectwith two events:continueandbreak
- an
TBA
It's required to have Node installed in your machine.
- Clone the repository:
git clone https://github.com/ezgrs/lowdie
cd lowdie- Install the dependencies:
npm installRun the cli script to interact with the bot and follow the on-screen instructions.
npm run cliInteract with the bot by sending /start to @lowdiebot.
- Create a new bot by sending
/startto @botfather:
> /newbot
Alright, a new bot. How are we going to call it? Please choose a name for your bot.
> Lowdie
Good. Now let's choose a username for your bot. It must end in `bot`. Like this, for example: TetrisBot or tetris_bot.
> lowdiebot
Done! Congratulations on your new bot. You will find it at t.me/lowdiebot. You can now add a description, about section and profile picture for your bot, see /help for a list of commands. By the way, when you've finished creating your cool bot, ping our Bot Support if you want a better username for it. Just make sure the bot is fully operational before you do this.
Use this token to access the HTTP API:
9999999999:ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
Keep your token secure and store it safely, it can be used by anyone to control your bot.
For a description of the Bot API, see this page: https://core.telegram.org/bots/api
-
Save the token as an environment variable named
TELEGRAM_BOT_TOKEN(or declare it on a .env on the project's root). -
Run the
telegramscript to keep the bot active:
npm run telegramTake a look at the cloud.md file for more info.
