Make your own Blood Bowl bot

GrodBot is protecting the ball carrier.

This tutorial will introduce you to the Fantasy Football AI framework (FFAI) that allows you to make your own Blood Bowl bot in Python. First, I will explain how to download and set up the framework, then how to make a simple bot that uses FFAI’s API to retrieve information about the game state in order to make actions. Finally, I will introduce a fully fledged bot called GrodBot (developed by Peter Moore) that you can use as solid starting point.

If you end up developing your own bot, please submit it to the Bot Bowl I competition.

Setup FFAI

FFAI is a Python module and it can be installed though pip. If you are completely new to Python, I recommend you to follow a few tutorials.

First we need to download FFAI. Either download the zip file or clone the repository using git:

git clone https://github.com/njustesen/ffai.git

Make sure that you are using python 3.6 (or newer) and that pip is installed. In your terminal, check the versions and verify that the output looks something like this:

$ python --version
Python 3.7.1
$ pip --version
pip 18.1 from /Users/noju/anaconda3/lib/python3.7/site-packages/pip (python 3.7)

For some setups, you need to use python3 and pip3 instead. If none of these are installed, please find an installation guide to install these on your OS.

If everything is okay, then navigate to the ffai directory (that you just downloaded) in your terminal. It must be the directory where there is a setup.py file. From there, use pip to install the FFAI module:

$ pip install -e .

To verify that everything is installed correctly, run the server example:

python examples/server_example.py

Then go to: http://127.0.0.1:5000/ and open up one of the games by clicking on a team name or on ‘spectate’.

Some people have reported that they get the following error when running the examples “ModuleNotFoundError: No module named ‘examples'”. While we figure out why this happens to some users, please try one of the recommended solutions https://github.com/njustesen/ffai/issues/28.

A random bot

Let’s start by making a bot that takes random actions. The code can be found in examples/random_bot_example.py.

#!/usr/bin/env python3

from ffai.core.game import *
from ffai.core.model import *
from ffai.ai.registry import register_bot, make_bot
import numpy as np


class MyRandomBot(Agent):

    def __init__(self, name, seed=None):
        super().__init__(name)
        self.my_team = None
        self.rnd = np.random.RandomState(seed)

    def new_game(self, game, team):
        self.my_team = team

    def act(self, game):
        # Select a random action type
        while True:
            action_choice = self.rnd.choice(game.state.available_actions)
            # Ignore PLACE_PLAYER actions
            if action_choice.action_type != ActionType.PLACE_PLAYER:
                break

        # Select a random position and/or player
        pos = self.rnd.choice(action_choice.positions) if len(action_choice.positions) > 0 else None
        player = self.rnd.choice(action_choice.players) if len(action_choice.players) > 0 else None

        # Make action object
        action = Action(action_choice.action_type, pos=pos, player=player)

        # Return action to the framework
        return action

    def end_game(self, game):
        pass

Let’s go through the code step by step. First, we import needed submodules from FFAI as well as numpy for taking random actions. Then, we create a new class called MyRandomBot that inherits from the Agent class. Doing so, requires us to implement three functions:

  • new_game(self, game, team) that is called whenever a new game is started with this bot, telling it which team it is controlling as well as a few initial information in the game object (such as the name of the opponent).
  • act(self, game) that is called at every step in the game where the bot is supposed to perform an action. Here, the game object is given as well which contains information about the entire game state. This function must return an instance of the class Action which contains both an action type and optionally a position or a player.
  • end_game(self, game) that is called when the game is over. Information about the game, such as the score and who the winner is can be accessed from the game object.

Because we just want to take a random action for now, let’s forget about the game object. Instead, let’s look at the Action class that we need to instantiate whenever act is called.

class Action:

    def __init__(self, action_type, pos=None, player=None):
        ...

The only required parameter in the constructor is action_type, which should be an instance of the enum ActionType. You can see all the different action types in ffai/core/table.py. Here are some examples of action that could be instantiated in a sequence of act()-calls:

Action(ActionType.START_BLITZ, player=game.get_players_on_pitch(self.my_team)[0])
Action(ActionType.MOVE, pos=Position(3,5))
Action(ActionType.MOVE, pos=Position(3,6))
Action(ActionType.BLOCK, pos=Position(4,7))
Action(ActionType.SELECT_DEFENDER_DOWN)
Action(ActionType.FOLLOW_UP)
Action(ActionType.BLOCK, pos=Position(4,8))
Action(ActionType.END_PLAYER_TURN)

But how do we know which actions that are allowed in the current step of the game? Luckily, the game object contains that information in the state:

game.state.available_actions

This is a list of possible actions that can be returned with some additional information about them, such as the required dice roll to make. An example of this list, formatted in json, looks like this:

"available_actions": [
{
"action_type": "MOVE",
"positions": [{"x": 12, "y": 6}, {"x": 14, "y": 6}, {"x": 12, "y": 7}, {"x": 12, "y": 8}],
"team_id": "human-1",
"rolls": [],
"block_rolls": [],
"agi_rolls": [[3], [5], [3], [3]],
"player_ids": [],
"disabled": false
},
{
"action_type": "BLOCK",
"positions": [{"x": 14, "y": 7}, {"x": 14, "y": 8}],
"team_id": "human-1",
"rolls": [],
"block_rolls": [1, 1],
"agi_rolls": [[], []],
"player_ids": [],
"disabled": false
},
{
"action_type": "END_PLAYER_TURN",
"positions": [],
"team_id": "human-1",
"rolls": [],
"block_rolls": [],
"agi_rolls": [],
"player_ids": [],
"disabled": false
}
]

which are the available actions in this situation:

A human lineman has taken a Blitz action and can thus both move and block.

By iterating the available actions, we can thus easily select one that we like. For our random bot, this is done by first sampling a random action type:

action_choice = self.rnd.choice(game.state.available_actions)

We do not want to sample the ActionType.PLACE_PLAYER, which is used during the setup phase, as we don’t want to rely in our bot to randomly come up with a valid starting formation. Instead, we allow it to select one of the built-in starting formations that are available as actions. After selecting an action type, we can sample a position or player if it is needed:

pos = self.rnd.choice(action_choice.positions) if len(action_choice.positions) > 0 else None

player = self.rnd.choice(action_choice.players) if len(action_choice.players) > 0 else None

Finally, we can instantiate the Action object and return it:

action = Action(action_choice.action_type, pos=pos, player=player)
return action

To run this agent, simply run the following in your terminal:

$ python examples/random_bot_example.py

This is, however, not very exciting to watch. In the examples/server_example.py, we can easily add new games with any bot registered in FFAI. To allow FFAI to see the bot, it must be registered with a unique ID like this:

register_bot('my-random-bot', MyRandomBot)

The server example is, however, already populated with several games, e.g. with a random bot vs. a human player (the first one on the list).

This concludes the first part of the tutorial. Please continue to learn how to make a more challenging bot.

A procedure-based bot

FFAI offers a built-in template for scripted bots with an simple structure that calls different functions depending on the current procedure of the game. FFAI has a number of different procedures for each part of the game, such as ‘Turn’, ‘Move’, ‘Block’, ‘Pass’, etc. The procedure-based bot template ‘ProcBot’ has one function for each of these procedures:

class ProcBot(Agent):

   ...

    def coin_toss_flip(self, game):
        raise NotImplementedError("This method must be overridden by non-human subclasses")

    def coin_toss_kick_receive(self, game):
        raise NotImplementedError("This method must be overridden by non-human subclasses")

    def setup(self, game):
        raise NotImplementedError("This method must be overridden by non-human subclasses")

    def place_ball(self, game):
        raise NotImplementedError("This method must be overridden by non-human subclasses")

    def high_kick(self, game):
        raise NotImplementedError("This method must be overridden by non-human subclasses")

    def touchback(self, game):
        raise NotImplementedError("This method must be overridden by non-human subclasses")

    def turn(self, game):
        raise NotImplementedError("This method must be overridden by non-human subclasses")

    ...

Instead of implementing a bot that inherits from Agent, one can make a bot that inherits from ProcBot. This means, that instead of implementing the act() function, you need to implement all of these procedure functions. Here are a few examples of simple implementations:

def coin_toss_flip(self, game):
    """
    Select heads/tails and/or kick/receive
    """
    return Action(ActionType.TAILS)

def place_ball(self, game):
    """
    Place the ball when kicking.
    """
    left_center = Square(7, 8)
    right_center = Square(20, 8)
    if game.is_team_side(left_center, self.opp_team):
        return Action(ActionType.PLACE_BALL, pos=left_center)
    return Action(ActionType.PLACE_BALL, pos=right_center)

def touchback(self, game):
    """
    Select player to give the ball to.
    """
    p = None
    for player in game.get_players_on_pitch(self.my_team, up=True):
        if Skill.BLOCK in player.skills:
            return Action(ActionType.SELECT_PLAYER, player=player)
        p = player
    return Action(ActionType.SELECT_PLAYER, player=p)

While the logic behind these functions are quite simple, it becomes more complicated to implement the functions ‘turn’ and ‘player_action’. We have provided a fully implemented, however, also super simple, procedure-based bot in examples/scripted_bot_example.py. This examples will give you inspiration on how to use FFAI to write you own bot.

GrodBot

FFAI also comes with a fully-fledged bot called GrodBot. This bot is also procedure-based and it makes heavy use of a pathfinding algorithm that is a part of FFAI. Let’s start by looking at the pathfinding.

The ffai.util.AStarPathFinder class can be used like this to find all the possible paths that a player can take, or the most optimal:

import ffai.util.pathfinding as pf

...

# Make a TileMap from the game state
ff_map = pf.FfTileMap(game.state)

# Make a Mover from the player
player_mover = pf.FfMover(player)

# Make a Path finder object
finder = pf.AStarPathFinder(ff_map, player.move_allowed(), allow_diag_movement=True, heuristic=pf.BruteForceHeuristic(), probability_costs=True)

# Get all paths from the player's current position
paths = finder.find_paths(player_mover, player.position.x, player.position.y)

# Find the optimal path from the player's current position to (6,4)
path = finder.find_paths(player_mover, player.position.x, player.position.y, 6, 4)

The pathfinder will return one or more paths, which have two properties: cost and steps.

GrodBot uses a scoring system to rate all the possible actions that are available. We will not describe GrodBot further while the code can give you plenty of inspiration or you can simply use it as a starting point for your own bot. The code is available at examples/grodbot.py. It is, in fact, perfectly fine to modify an existing bot and the submit it to the Bot Bowl competition, as long as the modifications has significant impact on how it plays.

I will conclude this tutorial with a video of GrodBot playing GrodBot. Enjoy!

If you have questions about this tutorial, FFAI or Bot Bowl, please join our discord channel.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: