Currently I’ve been playing KeyForge and Gwent. One of my first posts was about Magic: the Gathering and my 3D printed dice and token boxes also made an appearance on this blog. So it is no secret I like card games, and my collection of decks both on- and off-line is rapidly growing. There are plenty of tools to keep track of your decks for a single game, however I want to create something myself that supports all games I play.

This was also a good opportunity to learn Pelican, a static website generator for Python. I’ve been using Jekyll for some time, though developing extra features (like the ones I presented in the previous post) requires the programming language Ruby. As I’m not well versed in Ruby, it would be far more efficient to use my preferred programming language Python. Pelican seems to fit very well and this was a perfect project to check this out myself.

You can see the result of this blog here. The code and instructions to build DeckLock with your own decks can be found on GitHub: https://github.com/4dcu-be/DeckLock

Overview of my KeyForge deck generated using DeckLock

Introduction

KeyForge

KeyForge is a game designed by Dr. Richard Garfield, famous for creating Magic: the Gathering over 25 years ago. This game however is one-of-a-kind, as each deck you buy is a unique combination of cards designed to be played only in that specific configuration. Unlike other collectible card games, there is no trading individual cards. Furthermore, each deck comes with a QR-code, which will register that deck on https://www.keyforgegame.com/ when it is scanned using the KeyForge App (available in the App and Play store).

This is prefect for our application, we can get all information (name, decklist, …) of a scanned deck directly from the KeyForge website. The only thing we need is the decks unique identifier, everything else we’ll fetch automatically. Furthermore, using the same identifier additional statistics on the deck (like the estimated quality of the cards, synergies between them, …) can be obtained from [Deck of KeyForge].

Pelican

Static website generators, like Jekyll and Pelican take your content in a simple format (usually Markdown or reStructuredText) and templates that define how the final pages should look. These are combined into static html pages which can be hosted pretty much everywhere as there is no need for a database, php, … This elegant design with a clear separation between content and visualization has several advantages. The content is in a simple, text based format which can easily be stored, read, edited and reused. Sites are lightning fast as everything is pre-computed.

Getting started

First go to https://github.com/4dcu-be/DeckLock and create your own fork of the repository. Next, use the commands below to clone your own repository, create a virtual environment and install all required packages.

git clone <url to your fork of DeckLock> ./DeckLock
cd DeckLock
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt

On windows the line to activate the virtual environment (source venv/bin/activate) will not work, use the line below instead.

venv\Scripts\activate.bat

Setting up DeckLock

Configuring the Makefile

As we are using a virtual environment with pelican installed there, the patch to the executable in the environment needs to be set in the Makefile. Open the file and change the path on the line below to match your system. The pelican executable should be in venv/bin or venv/Scripts.

PELICAN?=d:\Git\DeckLock\venv\Scripts\pelican

pelicanconf.py and publishconf.py

pelicanconf.py should be ready to go, though feel free to have a look to see if any of the settings and paths need to be changed.

In publishconf.py however you will need to specify the final url of your site.

SITEURL = "https://4dcu.be/DeckLock"

Decks of KeyForge API key

If you want to include deck statistics from Decks of KeyForge you’ll have to create an account and get an API key from https://decksofkeyforge.com/. Create a file .env in the and add the line below.

A .env file, which will not be committed, is used to keep your api key secret.

DOK_API_KEY=your_api_key

KeyForge Deck IDs

Next, you’ll have to specify where the KeyForge data can be found (folder), in pelicanconf.py. Note that this path is relative to the content folder

KEYFORGE_PATH = "./data"

Now, add a keyforge.json file to ./content/data, structured as followed with the identifiers of the decks to include. A file with the KeyForge Decks I own is included as an example, the structure should be a shown below.

[
  {
    "deck_id" : "a4268ae8-a9f6-48c7-9739-b28a3553b108"
  }, {
    "deck_id" : "bfbf6786-218c-4320-a7b1-7ed4d6eddc69"
  }
]

Building the platform

You can use make to build the website (if make is available on your system), use make html to create a local instance to test in the _site directory. Use make release to create the version for publication in the ./docs folder.

make html

make release

alternatively you can use pelican directly, the content is in the folder ./content and the output folder should be set to ./_site for a local test build. Write the output to the ./docs folder with the publication settings so this can be hosted easily on GitHub.

pelican ./content -o ./_site

pelican ./content -o ./docs -s publishconf.py

Hosting locally for testing

You can use Pelican’s built in webserver using the command make serve.

Or you can build the site using make html, navigate to the _site folder and start a webserver by running the command python -m http.server.

In both cases you can see your site by pointing your browser to http://localhost:8000.

Hosting on GitHub

DeckLock includes a make release command which will write the final version of the website to the ./docs folder. Make sure to commit and push all files in your repository. On GitHub you can specify that this folder is used for the project pages, enable this in the settings and you’ll have free hosting to show off the decks you have in your card game collection.

How it works

All code is available on https://github.com/4dcu-be/DeckLock. In a nutshell, Pelican can trigger functions at different stages of the build. By creating a plugin a function get_keyforge_external_data was added that will trigger when Pelican initializes. This function will read the settings, find where keyforge.json is stored and the API key and connect to the different websites, pulling in all required information.

Pelican uses three components to create pages, Readers (which reads files and converts them to a page e.g. blog posts, articles, …), Generators that take data, processes it, generates a url and sends it with the correct template to a Writer that will combine data and template into a single html file. Generators are use to generate overview pages per category, … The default Writer is usually sufficient, so adding Readers and Generators is where the magic happens to create your own specific site’s structure.

The function get_keyforge_external_data will write everything required to a single json file, so we can forgo a Reader and use a Generator, that loads the data, builds a url for each deck and ships the relevant data to our templates.

Here is a stub on how to create a function and register it to trigger on initialization and how to create a generator and register it with Pelican so it will produce our html files.

from pelican import signals, generators

class KeyForgeGenerator(generators.Generator):
    """ Generator Class to produce pages based on keyforge.cache.json """

    template_overview = "keyforge_overview.html"
    template_deck = "keyforge_deck.html"
    
    def __init__(self, context, settings, path, theme, output_path, **kwargs):
        # Initialization function
        
    def generate_output(self, writer):
        # This function is required, this will be started by Pelican
        # Here we'll call two other functions, one to create an overview page
        # and one to create a page with deck details.
        self.generate_keyforge_overview_page(writer, self.keyforge_data)

        for k, v in self.keyforge_data.items():
            self.generate_keyforge_deck_page(writer, k, v)
        
    def generate_keyforge_deck_page(self, writer, deck_id, data):
        # Function to generate deck pages
        
    def generate_keyforge_overview_page(self, writer, data):
        # Function to generate overview page        

def get_keyforge_external_data(generator):
    # Here we can get all settings from generator.settings ...
    
    # Access APIs
    
    # Write all output to disk

def get_generators(generators):
    return KeyForgeGenerator

def register():
    """Register our function to get all data and generator to create the pages"""
    signals.initialized.connect(get_keyforge_external_data)

Conclusion

While Pelican did require a bit more setting up than Jekyll making it harder to get started, the fact that I could use Python to extend it was a significant advantage. Implementing complex features, like pulling in data from external APIs, wasn’t all that hard with packages like requests and the built-in json module available. So Pelican is a great framework for developing static pages that require more demanding processing of the input data or connections with external data. For a basic blog you are probably faster up and running with Jekyll.

Note that there are various frameworks in other languages available which I did not consider. Most notably Gatsby which uses the javascript ecosystem under the hood and Hugo which is powered by GO-lang. The former is particularly interesting as it is entwined with react which could prove handy to create a powerful, interactive front-end….

Support for Magic: the Gathering was implemented in the next post. Gwent has been added in part 3.