Automated Yugioh Deckbuilding in Python with OpenAI's GPT-3 and Twilio SMS

September 23, 2020
Written by
Sam Agnew
Twilion

Copy of Generic Blog Header 1(1).png

Coming up with a deck in any trading card game is often very difficult, and takes a lot of thought and experimentation. What if we could just have a computer do it for us?

OpenAI's new GPT-3 (Generative Pre-trained Transformer 3) model was trained on a massive corpus of text making it incredibly powerful. This can be used to generate something similar to pretty much any text found on the internet, including in our case Yugioh deck lists.

Let's walk through how to create a text-message powered bot to generate Yugioh deck lists in Python using Twilio Programmable Messaging and OpenAI's API for GPT-3.

EXODIA THE FORBIDDEN ONE

 

Before moving on, you'll need the following:

wget http://download.redis.io/releases/redis-5.0.5.tar.gz
tar xzf redis-5.0.5.tar.gz
cd redis-5.0.5
make

Introduction to GPT-3

GPT-3 is a highly advanced language model trained on a very large corpus of text. In spite of its internal complexity, it is surprisingly simple to operate: you feed it some text, and the model generates some more, following a similar style and structure.

We can see how versatile it is even by giving it a small amount of input text. Just from a short sentence followed by "[deck]", it's able to determine that this text is structured as a deck list similar to something you'd see on an online forum, and generate a somewhat coherent example based on that. Here's an example using the input text "Here is my Yugioh deck: \n\n[deck]":

Here is my Yugioh deck:

[deck]3 x Sinister Serpent
3 x Manga Ryu-Ran
1 x Dark Hole
2 x Raigeki
1 x Heavy Storm
1 x Mystical Space Typhoon
1 x Pot of Avarice
3 x Book of Moon
1 x Limiter Removal
1 x Torrential Tribute
1 x Mirror Force
1 x Solemn Judgment
1 x Dark Bribe
2 x Enemy Controller
1 x Mind Crush
1 x Scapegoat
1 x D.D. Warrior Lady
2 x Kaiser Sea Horse
3 x Kuriboh
2 x Dekoichi the Battlechanted Locomotive
1 x Airknight Parshath
1 x Elemental Hero Prisma
1 x Elemental Hero Necroshade
1 x Elemental Hero Stratos
1 x Elemental Hero Ocean
1 x Elemental Hero Mariner
1 x Elemental Hero Wildheart
1 x Elemental Hero Shining Phoenix Enforcer
1 x Elemental Hero Bladedge
1 x Elemental Hero Rampart Blaster
1 x Elemental Hero Wild Wingman
1 x Elemental Hero Electrum[/deck]

This deck is really strong and has a good match up against most of the meta. I have not tested it against Mermail yet. It has a good match up against water, fire, wind, and dark decks. The only problem I have with this deck is that it can't handle backrow and summon negation. I started off with this deck and it has been amazing. I have only lost to backrow and summon negation. It is a really fun deck to play and is very competitive.

I don't think that deck would be great for tournament play, but it looks like those are all cards that actually exist. It even has over 40 cards so it seems to be legal with some small exceptions like having two copies of Raigeki, although as someone who hasn't seriously played this game in over 10 years it blows my mind to see that having 3 copies of Sinister Serpent is actually allowed nowadays.

As mentioned above, this project requires an API key from OpenAI. At the time I’m writing this, the only way to obtain one is by being accepted into their private beta program. You can apply on their site.

Once you have an OpenAI account, you can use the Playground to play around with GPT-3 by typing in text and having it generate more text. The Playground also has a cool feature that allows you to grab some Python code you can run, using OpenAI's Python library, for whatever you used the Playground for.

GIF showing how to export code in the OpenAI playground

Working with GPT-3 in Python using the OpenAI helper library

To test this out yourself, you'll have to install the OpenAI Python module. You can do this with the following command, using pip:

pip install openai==0.2.4

Now create an environment variable for your OpenAI API key, which you can find when you try to export code from the Playground. Actually, while you're at it you might as well grab your account credentials from your Twilio Console and make environment variables for those as well:

export OPENAI_API_KEY='YOUR_API_KEY_HERE'
export TWILIO_ACCOUNT_SID='YOUR_ACCOUNT_SID'
export TWILIO_AUTH_TOKEN='YOUR_AUTH_TOKEN'

We will use these in our code throughout the post. To try generating a deck list, open a Python shell and run the following code, most of which I took directly from the Playground:

import os
import openai

openai.api_key = os.environ.get("OPENAI_API_KEY")
prompt_text = "Here is my Yugioh deck: \n\n[deck]"

response = openai.Completion.create(
  engine="davinci",
  prompt=prompt_text,
  temperature=0.7,
  max_tokens=500
)

print(prompt_text + response['choices'][0]['text'])

You should see something like this printed to your terminal (I took empty lines out in the deck list to reduce the amount of scrolling you need to do):

Here is my new Yugioh deck list:

[deck]3 Red Gadget
2 Green Gadget
3 Yellow Gadget
3 Machina Fortress
2 Machina Gearframe
1 Machina Peacekeeper
1 Machina Warlord
1 Machina Force
1 Machina Cannon
3 Debris Dragon
1 Tragoedia
1 Cyber Dragon
2 Jinzo
2 Battle Fader
2 Cyber Valley
1 Soul Release
1 Trap Stun
1 Torrential Tribute
1 Call of the Haunted
1 Limiter Removal
1 Dark Hole
1 Giant Trunade
1 Monster Reborn
1 Reinforcement of the Army
1 Mind Control
1 Enemy Controller
3 Book of Moon
3 Sakuretsu Armor
2 Royal Decree
1 Mirror Force
1 Solemn Judgment
1 Call of the Haunted[/deck]

I also changed the Extra Deck, but you can find those as well.

Suggestions are more than welcome!

Edit: So I have been thinking about it for a few days, and I think I like the extra deck I have now better. It has the same amount of cards, but it is more consistent for me.

OpenAI has a variety of different models to choose from, and for this one we are using davinci which is the most advanced but also the slowest. I haven't been able to get good results for building decks with any of the other models. So one concern I have at this point is that it will take too long to generate this text, resulting in an HTTP timeout when we try to respond to a text message. We'll handle this later by using a tool called Redis Queue, which we also have a tutorial for.

Sending your generated decklist via SMS with Twilio

We want to make it so a person can text a phone number and receive a Yugioh deck list. Before being able to respond to messages, you’ll need a Twilio phone number. You can buy a phone number here.

As I mentioned before, generating the text takes too long to simply respond to Twilio's requests to our app for incoming text messages, so we will have to break this up into two parts. A Flask app which will handle messages from the user will begin generating a deck list asynchronously using Redis Queue, and when that task is finished a text message will be sent with the deck list.

Let's write the code for sending a text message once the deck list is generated. This will be called from our eventual Flask app. Create a file called deck_generator.py and add the following code to it:

import os

import openai
from twilio.rest import Client


openai.api_key = os.environ.get('OPENAI_API_KEY')
twilio_client = Client()


def generate_decklist(to_number, from_number):
    prompt_text = 'Here is my Yugioh deck: \n\n[deck]'

    response = openai.Completion.create(
      engine="davinci",
      prompt=prompt_text,
      temperature=0.7,
      max_tokens=300,
      presence_penalty=0.3
    )

    resp_text = response['choices'][0]['text']

    if '[/deck]' in resp_text:
        message_body = resp_text[:resp_text.lower().find('[/deck]')]
    else:
        # If it doesn't get to the end of a deck, cut off after the last newline.
        message_body = resp_text[:resp_text.rfind('\n')]

    twilio_client.messages.create(to=to_number, from_=from_number, body=message_body)

The twilio_client object implicitly accesses the account credentials you set as environment variables, and sends the text message at the end. There is also some new logic for handling cases where the script either doesn't finish generating a deck, determined by the presence of a closing "[/deck]" string in the text, or where it generates multiple (which sometimes happens). In the latter case, because the deck list begins immediately after the prompt text, we cut the string off at the beginning of the first "[/deck]" tag and it just sends the list of cards.

You can test this code out by calling the generate_decklist function in a Python shell and passing your personal phone number and your Twilio phone number to the function.

Configuring a Twilio phone number

We're going to create a web application using the Flask framework that will need to be visible from the Internet in order for Twilio to send requests to it. We will use ngrok for this, which you’ll need to install if you don’t have it. In a new terminal window, run the following command:

ngrok http 5000

ngrok terminal screen

This provides us with a publicly accessible URL to the Flask app. In your Twilio Console, configure your phone number as seen in this image so that when a text message is received, Twilio will send a POST request to the /sms route on the app we are going to build, which will sit behind your ngrok URL:

Configuring your Twilio number

With this taken care of, we can move onto building the Flask app to respond to text messages.

Responding to text messages in Python with Flask

So you have a Twilio number and are able to have OpenAI generate a deck list. It's time to link the two together. Before moving on, install the Twilio, Flask, and Redis Queue Python libraries with the following command:

pip install Flask==1.1.2 rq==1.5.2 twilio==6.44.2 

Now create a file called app.py and add the following code to it for our Flask application:

from flask import Flask, request
from redis import Redis
from rq import Queue
from twilio.twiml.messaging_response import MessagingResponse

from deck_generator import generate_decklist


app = Flask(__name__)
q = Queue(connection=Redis())


@app.route('/sms', methods=['POST'])
def sms():

    # Queue up the generate_decklist function, which will send a text message
    # to the user once the text generation is finished.
    q.enqueue(generate_decklist, request.form['From'], request.form['To'])

    # Create a TwiML response object to respond to the incoming message.
    twiml_resp = MessagingResponse()
    twiml_resp.message('Please wait while a deck list is generated for you :)')
    return str(twiml_resp)


if __name__ == '__main__':
    app.run(debug=True)

This Flask app contains one route, /sms, which will receive a POST request from Twilio whenever a text message is sent to our phone number, as configured before with the ngrok URL. In this route, a task is sent to Redis Queue to generate a deck list and send a text message when it's finished. The HTTP response will contain TwiML that has Twilio send a message to the user telling them a deck list is being made for them.

Save your code, and run it with the following command:

python app.py

You'll have to run a Redis server as well as an RQ worker before your Flask code will work. Open another terminal window, and run the following command from the directory where Redis is installed:

src/redis-server &

The & means that it will run in the background, but if you want to open up yet another terminal window you can take that out of the command. Now for the RQ worker, make sure your virtual environment is activated and run this command from the same directory where your Python code is, same rules apply with the &:

rqworker &

Because we have ngrok running on port 5000, the default port for Flask, you can now text your Twilio number and receive the list of your new Yugioh deck! Don't worry if you have to try a few times to get a good one.

It's Time to D-D-D-D-D-D-D-D-D-D-DUEL!!!

It's time to duel!

Now that you and your friends can text your Twilio number to receive new deck lists whenever you want, try throwing these cards into a deck on Dueling Book and see what kind of ridiculous matchups you can get. Maybe you can even have a tournament where every duel consists of AI-generated decks!

You can also try modifying the code a bit. Maybe you can get better results by changing the number of tokens generated or using more specific prompt text such as "The following is a Yugioh deck list for the Goat Format: [deck]" or "This is my Blue Eyes White Dragon Yugioh deck: [deck]".

If you want to do more with Twilio and GPT-3, check out this tutorial on how to build a text message based chatbot and this one on how to generate Yugioh fan fiction.

Feel free to reach out if you have any questions or comments or just want to show off any stories you generate.