mirror of
https://github.com/sern-handler/website
synced 2026-06-06 01:16:47 +00:00
more progres
This commit is contained in:
@@ -3,3 +3,103 @@ title: Logic
|
||||
sidebar_position: 5
|
||||
---
|
||||
|
||||
## Game rules
|
||||
- Two players max.
|
||||
- Each player is assigned **X** or **O**
|
||||
- The first person to get 3 in a row of their letter wins.
|
||||
|
||||
Get the option selected, adding this to the top of your **execute** function:
|
||||
```js
|
||||
const opponent = ctx.options.getUser("opponent");
|
||||
```
|
||||
:::warning
|
||||
Since we are in a Slash command, the above is valid. If we were to interoperate between Text Commands and Slash Commands,
|
||||
we'd have to check if the current [Context](https://sern.dev/docs/api/classes/Context) is the correct type. In our current setup,
|
||||
this won't happen since we did not set our command type to **Both**.
|
||||
:::
|
||||
|
||||
We need to ensure we're not playing against **an invalid user**. Invalid users are those that
|
||||
- are bots **or**
|
||||
- ourselves.
|
||||
|
||||
Create helper function **isInvalidUser** which returns a boolean satisfying the conditions above.
|
||||
|
||||
```js
|
||||
const isInvalidUser = (user) => {
|
||||
return user.id === "182326315813306368" || user.bot;
|
||||
}
|
||||
```
|
||||
:::tip
|
||||
You can easily access anyone's user id by [enabling dev mode](https://beebom.com/how-enable-disable-developer-mode-discord).
|
||||
:::
|
||||
|
||||
Cool, now let's call it, preventing invalid users from being opponents. Put the code snippet before any of the UI logic:
|
||||
```js title="tictactoe, execute function"
|
||||
if(isInvalidUser(opponent)) {
|
||||
return;
|
||||
}
|
||||
```
|
||||
We **return**, or **stop** execution of the function if the condition `isInvalidUser` is true.
|
||||
:::info
|
||||
A step further would be extracting `isInvalidUser` into a separate plugin, if multiple commands share this same logic.
|
||||
:::
|
||||
Before we go any further, let's test. Start up the bot again.
|
||||
- Try running `/tictactoe` with a bot.
|
||||
- What happens?
|
||||
- It may be beneficial to give the user some feedback instead of a **return**.
|
||||
- Try running `/tictactoe` with yourself
|
||||
- What happens?
|
||||
- It may be beneficial to give the user some feedback instead of a **return**.
|
||||
- Try running `/tictactoe` in a DM channel with the bot itself.
|
||||
- What happens?
|
||||
|
||||
Great! notice how in a DM channel, the command is entirely **useless**.
|
||||
Let's use our **channelType** plugin to ensure this doesn't happen.
|
||||
- Add the **plugins** field to your commandModule, adding the channelType plugin.
|
||||
```js
|
||||
plugins: [channelType( )]
|
||||
```
|
||||
- Call it with the right parameters.
|
||||
- All plugins come with documentation in the same file
|
||||
- In our case, we need to supply an array of ChannelTypes, and an optional message.
|
||||
```js
|
||||
plugins: [channelType([ChannelType.GuildText], "You cannot execute this command here.")]
|
||||
```
|
||||
:::info
|
||||
You can publish commands to specific guilds using special configuration.
|
||||
For more information, view the docs [here](https://sern.dev/docs/cli/publish).
|
||||
For educational and simplicity purposes, I'm using a plugin here.
|
||||
:::
|
||||
|
||||
Great! When we rerunning these test conditions, it should catch and respond to these conditions.
|
||||
|
||||
|
||||
## Collectors
|
||||
We know how to display components, but what about responding to them?
|
||||
Discord.js gives us the Collector API, which at its core, listens to incoming interactions from discord.js. It essentially comes in two flavors.
|
||||
- **await**
|
||||
- **callback based**
|
||||
|
||||
**Await** is [easy](https://www.infoq.com/presentations/Simple-Made-Easy/), but it comes at the cost of only collecting `one` at a time. Also,
|
||||
it is blocking on other commands attempting to be run; When we await a component,
|
||||
All other commands will be waiting as well. So, we'll be using callback based, lazily waiting. Let's create a collector to the message sent by the bot. How do we get that message?
|
||||
Luckily `ctx.reply` returns the [Message](https://discord.js.org/#/docs/discord.js/main/class/Message) sent. Let's capture that in a variable.
|
||||
|
||||
- Capture the result of ctx.reply in a variable called **resultMessage**.
|
||||
```js
|
||||
const resultMessage = await ctx.reply({ message, components: grid });
|
||||
```
|
||||
- Create a collector from the message's property `.createMessageComponentCollector()`
|
||||
- Accept only interactions of type **Button** (we are making a tictactoe game of solely buttons),
|
||||
- Make sure to import **ComponentType** from discord.js
|
||||
- Give it a time limit of 60_000 milliseconds aka 1 minute.
|
||||
```js
|
||||
// verbosity!! //ignore this
|
||||
const collector = responseMessage.createMessageComponentCollector({
|
||||
componentType: ComponentType.Button,
|
||||
time: 60_000
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -15,6 +15,10 @@ sern has a bunch of premade plugins thanks to our awesome community, which you c
|
||||
|
||||
- Run: `sern plugins`, selecting the **channelType** plugin
|
||||
- Thank the creator(s) of the plugin. (Thank you)
|
||||
- Import your plugin at the top of the file like this
|
||||
```js
|
||||
import { channelType } from '../plugins/channelType.js'
|
||||
```
|
||||
|
||||
Once you download the `channelType` plugin, You may continue.
|
||||
|
||||
@@ -26,12 +30,13 @@ Lets take an aside to show how to install plugins.
|
||||
- Thank the creator(s) of the plugin. (Thank you)
|
||||
|
||||
```js
|
||||
import { commandModule, CommandType } from '@sern/handler'
|
||||
import { channelType } from '../plugins/channelType.js'
|
||||
|
||||
export default commandModule({
|
||||
type: CommandType.Slash,
|
||||
description: "post very nsfw stuff",
|
||||
//WE CALL THE PLUGIN IN THE PLUGINS FIELD.
|
||||
description: "haliburt",
|
||||
// here:
|
||||
plugins: [channelType([ChannelType.GuildText], 'This cannot be used here')]
|
||||
})
|
||||
|
||||
|
||||
@@ -52,29 +52,67 @@ TODO!!! PICTURE HERE
|
||||
How do we represent this in code? In discord.js, the library we're using, we can represent this via their builder api. Let's create some utility functions before diving straight in.
|
||||
|
||||
```js title="tictactoe.js"
|
||||
const createButton = (customId, label) => {
|
||||
return ButtonBuilder.from({
|
||||
customId,
|
||||
const EMPTY = "\0"; // A little trick to show buttons with no 'label'
|
||||
const button = (custom_id, label=EMPTY) => {
|
||||
return new ButtonBuilder({
|
||||
custom_id,
|
||||
label,
|
||||
style: ButtonStyle.Secondary
|
||||
});
|
||||
}
|
||||
```
|
||||
The name is pretty self explanatory.
|
||||
|
||||
In our **execute** function, let's build the Action Rows.
|
||||
```js
|
||||
const message = "Tic Tac Toe";
|
||||
const rows = [];
|
||||
for(let i = 0; i < 3; i++) {
|
||||
rows.push(new ActionRowBuilder([
|
||||
createButton(i),
|
||||
createButton(i+1),
|
||||
createButton(i+2),
|
||||
]));
|
||||
const row = (...elements) => {
|
||||
return new ActionRowBuilder().addComponents(elements);
|
||||
}
|
||||
```
|
||||
> Question: Does this satisfy the rules stated above?
|
||||
The names for these are pretty self explanatory. **EMPTY** is a placeholder for labels in a button.
|
||||
|
||||
> Answer: Yes! We have 3 Action Rows in total, with 3 Buttons in each.
|
||||
In our **execute** function, let's build the Action Rows.
|
||||
```js title="tictactoe.js, in the execute body"
|
||||
const message = "Tic Tac Toe";
|
||||
|
||||
//Our TicTacToe grid. consists of 3 rows, and in each row, 3 buttons.
|
||||
//Each button has a unique 'customId' of a number.
|
||||
const grid = [row(button('0'), button('1'), button('2')),
|
||||
row(button('3'), button('4'), button('5')),
|
||||
row(button('6'), button('7'), button('8'))];
|
||||
|
||||
|
||||
await ctx.reply("Pwease wait. dis command in pwogwess"); // 👻
|
||||
```
|
||||
> Question: Does this grid satisfy the rules stated above?
|
||||
|
||||
> Answer: Yes! We have 3 Action Rows in total, with 3 Buttons in each. Less than 25 Components!
|
||||
|
||||
Let's try displaying this to Discord. We're not just displaying plain text anymore, we need to change ctx.reply.
|
||||
|
||||
- Change how we call `ctx.reply`. Give it an object instead:
|
||||
```js
|
||||
// await ctx.reply("Pwease wait. dis command in pwogwess"); // 👻
|
||||
|
||||
await ctx.reply({ content: message, components: grid });
|
||||
```
|
||||
One more thing, we need to play against someone, so we'll need to update our commandModule.
|
||||
- Add the import **ApplicationCommandOptionType** from discord.js
|
||||
- Add a new **option** of type **User** to the **options** field of your commandModule.
|
||||
This will display on the user's end as an option to choose from.
|
||||
```js
|
||||
options: [
|
||||
{
|
||||
name: "opponent",
|
||||
description: "Opponent you would like to play with",
|
||||
type: ApplicationCommandOptionType.User,
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
```
|
||||
- Run `npm run commands:publish` once more to update it.
|
||||
|
||||
:::tip
|
||||
Anytime we add a new slash command or its options, its a good idea to run `npm run commands:publish`
|
||||
:::
|
||||
|
||||
Run your bot.
|
||||
- Congrats! You got something to display.
|
||||
Notice how when you try to click, it will say something like `interaction failed to respond`.
|
||||
**Next chapter**, let's wire everything up.
|
||||
|
||||
Reference in New Issue
Block a user