How to build a discord game bot!

In the previous posts we learned about how to “Make” a discord bot.
Today, we are going to learn how to actually code a game and make discord game bot.

Contents:
Basic Setup
Start Coding!
How to use it

Basic Setup
Follow this page to actually start coding in python but pretty much the outline is:

  1. Download Python IDE.
  2. Run the installer to install Thonny on your computer.
  3. Go to: File > New. Then save the file with . py extension. …
  4. Write Python code in the file and save it. Running Python using Thonny IDE.
  5. Then Go to Run > Run current script or simply click F5 to run it.

So lets Start Coding!
Make sure when you start coding, the file location is at the same place as you downloaded the bots.
The first 3 lines of the code is called imports

import discord
from discord.ext import commands
import random

It is used to import the modules from another context in this piece of code that you are writing.

Now let’s look at the next lines.

client = commands.Bot(command_prefix="!")

player1 = ""
player2 = ""
turn = ""
gameOver = True

board = []

First you are defining the command line via “!”
This means that to use the commands (bot) that you have created, you must type “!” first.
Then you will define the player right away using the “@” and the usernames of the players
So the simple command to start the game would look like this:

!tictactoe @yourusername @otherplayer

Since the game has just started the board will be empty which leads to “[ ]” which stands for empty array.
The game state which is determining if the game has ended or not will set to True.

Now let’s go to the next line.

winningConditions = [
    [0, 1, 2],
    [3, 4, 5],
    [6, 7, 8],
    [0, 3, 6],
    [1, 4, 7],
    [2, 5, 8],
    [0, 4, 8],
    [2, 4, 6]
]

So, what we have done is we made all possible combinations that you can do to win the game of tic tac toe.

These will look like this when we play the game.
image

Now since we know how the board will look like + the conditions of winning + who we are playing with. Let’s set up some rules before we even place blocks. It wouldn’t make sense to place to Xs in a row or 3 O to win the game right away.

@client.command()
async def tictactoe(ctx, p1: discord.Member, p2: discord.Member):
    global count
    global player1
    global player2
    global turn
    global gameOver

    if gameOver:
        global board
        board = [":white_large_square:", ":white_large_square:", ":white_large_square:",
                 ":white_large_square:", ":white_large_square:", ":white_large_square:",
                 ":white_large_square:", ":white_large_square:", ":white_large_square:"]
        turn = ""
        gameOver = False
        count = 0

        player1 = p1
        player2 = p2

So this commands define you things.
It defines:

  1. The command name (tictactoe)
  2. The 2 following players (discord.Memeber) and their names for each (p1 and p2)

After doing so,it sets what we call “global variables” meaning the code can go back to them to see what is going on with them or if anything changed.

After we actually “Draw” the board. Since Discord actually don’t allow us to draw we are going to use emojis instead.
So the white_large_square is just a white block emoji, and setting up in a 3x3 method allows to print out:
image
Which kind of look like a board!

Then we set the globals to:

turn = “”
gameOver = False
count = 0
player1 = p1
player2 = p2

which indicates whos turn it is, if the game has ended, the number of moves (count), and the player’s name.

Now here are some more complicated code:

line = ""
        for x in range(len(board)):
            if x == 2 or x == 5 or x == 8:
                line += " " + board[x]
                await ctx.send(line)
                line = ""
            else:
                line += " " + board[x]

        num = random.randint(1, 2)
        if num == 1:
            turn = player1
            await ctx.send("It is <@" + str(player1.id) + ">'s turn.")
        elif num == 2:
            turn = player2
            await ctx.send("It is <@" + str(player2.id) + ">'s turn.")
    else:
        await ctx.send("A game is already in progress! Finish it before starting a new one.")

so first block of code “The For loop” goes over the lines of the blocks and just sends the line back to the discord channel so we can view them (ctx.send).

The second block is more complicated than you think.
First we pick a number between 1 and 2. If it is number 1, it is player 1’s turn, if it is 2, then it is player 2’s turn.

Then, we tell the user that it is their turn to play.

However, if someone intercepts our play we tell them

A game is already in progress! Finish it before starting a new one.

Alright let’s move on to the actual commands!

`@client.command()
async def place(ctx, pos: int):
global turn
global player1
global player2
global board
global count
global gameOver

if not gameOver:
    mark = ""
    if turn == ctx.author:
        if turn == player1:
            mark = ":regional_indicator_x:"
        elif turn == player2:
            mark = ":o2:"
        if 0 < pos < 10 and board[pos - 1] == ":white_large_square:" :
            board[pos - 1] = mark
            count += 1

            line = ""
            for x in range(len(board)):
                if x == 2 or x == 5 or x == 8:
                    line += " " + board[x]
                    await ctx.send(line)
                    line = ""
                else:
                    line += " " + board[x]

            checkWinner(winningConditions, mark)
            print(count)
            if gameOver == True:
                await ctx.send(mark + " wins!")
            elif count >= 9:
                gameOver = True
                await ctx.send("It's a tie!")

            # switch turns
            if turn == player1:
                turn = player2
            elif turn == player2:
                turn = player1
        else:
            await ctx.send("Be sure to choose an integer between 1 and 9 (inclusive) and an unmarked tile.")
    else:
        await ctx.send("It is not your turn.")
else:
    await ctx.send("Please start a new game using the !tictactoe command.")`

Whoa whoa, this is long, let’s break it down a little.

Let’s look at the first block:

@client.command()
async def place(ctx, pos: int):
  ... #means skipping the global 
    if not gameOver:
        mark = ""
        if turn == ctx.author:
            if turn == player1:
                mark = ":regional_indicator_x:"
            elif turn == player2:
                mark = ":o2:"
            if 0 < pos < 10 and board[pos - 1] == ":white_large_square:" :
                board[pos - 1] = mark
                count += 1

                line = ""
                for x in range(len(board)):
                    if x == 2 or x == 5 or x == 8:
                        line += " " + board[x]
                        await ctx.send(line)
                        line = ""
                    else:
                        line += " " + board[x]

So, let’s take a look at the command.
It will be “!place X” where x stands for an integer value between 0 ~ 8 which makes up our board.
So, doing that we first have to assume the game has not ended yet which is where the “if not gameOver” comes in.

Then, we will have a mark which is an empty string that will take our input. So that way the code is able to keep track of what users have been placing for each player.

So if the turn is equal to our player then we will take inputs, between 0 ~ 10 that will allow us to replace the white_large_square with a big o2 emoji (which you will see later on)

Then the count (the number of total moves) goes up by 1.

Then we will send the user back our new board with the user’s input.

image

Now we before we go to next block we actually gotta take a look at a function that checks if the player has won or not. What if the player wins after place a block? well, this is what our function is for. If the person did not win, we just skip it!

def checkWinner(winningConditions, mark):
    global gameOver
    for condition in winningConditions:
        if board[condition[0]] == mark and board[condition[1]] == mark and board[condition[2]] == mark:
            gameOver = True

So here is our winning condition, so remember at the start how we defined every winning condition? well this is what it is used for. So we take a look at mark and if the user’s input matches anyone of the condition then the game over statement is true since the user has won!

Then we proceed to the next block.

checkWinner(winningConditions, mark)
                print(count)
                if gameOver == True:
                    await ctx.send(mark + " wins!")
                elif count >= 9:
                    gameOver = True
                    await ctx.send("It's a tie!")

                # switch turns
                if turn == player1:
                    turn = player2
                elif turn == player2:
                    turn = player1
            else:
                await ctx.send("Be sure to choose an integer between 1 and 9 (inclusive) and an unmarked tile.")
        else:
            await ctx.send("It is not your turn.")
    else:
        await ctx.send("Please start a new game using the !tictactoe command.")

So after the checking if the winner has won or not. if won, we give the player’s name + who won, if it is a tie then we give it a tie! (To determine if it is a tie or not, we look at the number of total moves that were made, the max number is 9 in tictactoe)

If none of these happen then all we do change the turn of the players.

As for the errors:
if the user has inputted a number that is outside from our range we send:

Be sure to choose an integer between 1 and 9 (inclusive) and an unmarked tile.

If it is wrong turn, then we say:

It is not your turn.

If it has ended, then we say:

lease start a new game using the !tictactoe command.

Well how are these Errors actually determined?

@tictactoe.error
async def tictactoe_error(ctx, error):
    print(error)
    if isinstance(error, commands.MissingRequiredArgument):
        await ctx.send("Please mention 2 players for this command.")
    elif isinstance(error, commands.BadArgument):
        await ctx.send("Please make sure to mention/ping players (ie. <@688534433879556134>).")

@place.error
async def place_error(ctx, error):
    if isinstance(error, commands.MissingRequiredArgument):
        await ctx.send("Please enter a position you would like to mark.")
    elif isinstance(error, commands.BadArgument):
        await ctx.send("Please make sure to enter an integer.")

Here is how it works.
If the user forgets to mention a player or puts down their username without the @
then, these are Missing Required Argument and Bad Argument.
So we return:

Please mention 2 players for this command.
or
Please make sure to mention/ping players (ie. <@688534433879556134>).

If it is placement error then we return:

Please enter a position you would like to mark.
or
Please make sure to enter an integer.

and once that is finshed:
we write

client.run("ABCDEFGHIJKLMNOP1234567890")

What goes in the “” should be your token that we have talked about earlier.
This will “Run” the bots with the commands we have wrote above.

After doing so the bot will become active and you can start playing!


import discord
from discord.ext import commands
import random

client = commands.Bot(command_prefix="!")

player1 = ""
player2 = ""
turn = ""
gameOver = True

board = []

winningConditions = [
    [0, 1, 2],
    [3, 4, 5],
    [6, 7, 8],
    [0, 3, 6],
    [1, 4, 7],
    [2, 5, 8],
    [0, 4, 8],
    [2, 4, 6]
]

@client.command()
async def tictactoe(ctx, p1: discord.Member, p2: discord.Member):
    global count
    global player1
    global player2
    global turn
    global gameOver

    if gameOver:
        global board
        board = [":white_large_square:", ":white_large_square:", ":white_large_square:",
                 ":white_large_square:", ":white_large_square:", ":white_large_square:",
                 ":white_large_square:", ":white_large_square:", ":white_large_square:"]
        turn = ""
        gameOver = False
        count = 0

        player1 = p1
        player2 = p2

        # 보드 만들기
        line = ""
        for x in range(len(board)):
            if x == 2 or x == 5 or x == 8:
                line += " " + board[x]
                await ctx.send(line)
                line = ""
            else:
                line += " " + board[x]

        # 누가 먼저 하는지 결정
        num = random.randint(1, 2)
        if num == 1:
            turn = player1
            await ctx.send("It is <@" + str(player1.id) + ">'s turn.")
        elif num == 2:
            turn = player2
            await ctx.send("It is <@" + str(player2.id) + ">'s turn.")
    else:
        await ctx.send("A game is already in progress! Finish it before starting a new one.")

@client.command()
async def place(ctx, pos: int):
    global turn
    global player1
    global player2
    global board
    global count
    global gameOver

    if not gameOver:
        mark = ""
        if turn == ctx.author:
            if turn == player1:
                mark = ":regional_indicator_x:"
            elif turn == player2:
                mark = ":o2:"
            if 0 < pos < 10 and board[pos - 1] == ":white_large_square:" :
                board[pos - 1] = mark
                count += 1

                # 보드 업데이트
                line = ""
                for x in range(len(board)):
                    if x == 2 or x == 5 or x == 8:
                        line += " " + board[x]
                        await ctx.send(line)
                        line = ""
                    else:
                        line += " " + board[x]

                checkWinner(winningConditions, mark)
                print(count)
                if gameOver == True:
                    await ctx.send(mark + " wins!")
                elif count >= 9:
                    gameOver = True
                    await ctx.send("It's a tie!")

                # switch turns
                if turn == player1:
                    turn = player2
                elif turn == player2:
                    turn = player1
            else:
                await ctx.send("Be sure to choose an integer between 1 and 9 (inclusive) and an unmarked tile.")
        else:
            await ctx.send("It is not your turn.")
    else:
        await ctx.send("Please start a new game using the !tictactoe command.")


def checkWinner(winningConditions, mark):
    global gameOver
    for condition in winningConditions:
        if board[condition[0]] == mark and board[condition[1]] == mark and board[condition[2]] == mark:
            gameOver = True

@tictactoe.error
async def tictactoe_error(ctx, error):
    print(error)
    if isinstance(error, commands.MissingRequiredArgument):
        await ctx.send("Please mention 2 players for this command.")
    elif isinstance(error, commands.BadArgument):
        await ctx.send("Please make sure to mention/ping players (ie. <@688534433879556134>).")

@place.error
async def place_error(ctx, error):
    if isinstance(error, commands.MissingRequiredArgument):
        await ctx.send("Please enter a position you would like to mark.")
    elif isinstance(error, commands.BadArgument):
        await ctx.send("Please make sure to enter an integer.")

client.run("")

And there we have it!
We have our own discord game bot~

2 Likes