more progres

This commit is contained in:
Jacob Nguyen
2024-01-12 17:23:43 -06:00
parent e9cf39984d
commit 8b72bfd07e
3 changed files with 162 additions and 19 deletions

View File

@@ -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
});
```

View File

@@ -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')]
})

View File

@@ -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.