GT RoboCup SSL
Soccer software, robot firmware
Gameplay

This document covers the basics of our high-level soccer code including Plays, Behaviors, and game state evaluation. This code resides in soccer/gameplay.

As with any complex system, it's important to have some well-defined structure to keep things manageable. The soccer program is split up into several different layers for this purpose, with the gameplay layer being the most high-level. The gameplay layer is managed by the GameplayModule and evaluates the current state of the field (where the robots and ball are) and the state of the game (is it a kickoff, penalty kick, etc). This information is contained in the c++ SystemState class and the GameState class, respectively. The result of running the GameplayModule is a motion command for each of the robots as well as kick and dribble commands. Layers of the software stack below the gameplay layer ideally don't know anything about soccer and just orchestrate robot motion, radio communication, network communication, etc.

When the gameplay module is running, its job is to select the best play from a list of enabled plays by choosing the one with the lowest score() value. Plays are enabled and disabled through the GUI with the checkboxes next to play names. See the annotated screenshot below for more info.

Play Structure

The high-level strategy code is organized to be as modular as possible. To do this, it's been split up into three main parts: Skills, Tactics, and Plays. There is one Goalie (optionally) and one Play object.

Skills are behaviors that apply to a single robot. They include things like capturing the ball, moving to a particular position on the field, and kicking the ball.

Tactics can coordinate a single robot or many and generally encapsulate more complex behavior than skills. This includes things such as passing, defense, and the goalie.

Plays are responsible for coordinating the whole team of robots (although some robots may be unused). At a given time, the soccer program is running at most one play.

Used together, skills, tactics, and plays form a tree structure with the Play at the root and other behaviors below it. The C++ GameplayModule tells the current play to run, which in turn tells each of its sub-behaviors to run.

Gameplay structure

Every behavior in soccer is a state machine that subclasses the main StateMachine class. This class has methods for adding states and transitions between them as well as utility methods for showing textual and graphical descriptions of a state machine and it's sub-machines. One nifty usage of this feature is that we can easily view a diagram of every skill, tactic, and play in our library.

# Run this in a terminal in the robocup-software folder to make the diagrams
$ make behavior-diagrams

After running the above command, open up the soccer/gameplay/diagrams folder and browse around to see a diagram for each behavior. Below is the state diagram for the PivotKick behavior. A good exercise if you're new to writing plays is to compare the PivotKick init() method's state machine declarations to what you see in the diagram below.

PivotKick-state-diagram.png
PivotKick state diagram

Creating a Play

Making a new play is as simple as adding a new python file somewhere inside the soccer/gameplay/plays directory and creating a subclass of Play inside of it. There is no need to register the play, soccer will see the file in that folder and display it in the Plays tab in soccer. Generally when writing a new play, it's a good idea to base its initial structure on an existing play. A good example play to look at is the LineUp play.

Every play begins by declaring a python class that subclasses the Play class:

class MyNewPlay(play.Play):
def __init__(self):
# call superclass constructor
super().__init__(continuous=False)
# TODO: declare states and transitions if needed
# see fsm.py for more info on these methods.
# Most plays transition from Start to Running right away
self.add_transition(behavior.Behavior.State.start,
behavior.Behavior.State.running,
lambda: True,
'immediately')

After declaring the play, it's time to add in the appropriate states and state transitions to your play. Every subclass of the Behavior class automatically inherits some pre-defined states including Start, Running, and Completed and is initially started in the Start state. It's your job as the writer of a new play to define a state transition from Start to Running or a substate of Running.

The gameplay system automatically declares three methods for every state added to a behavior: on_enter_<NAME>, on_exit_<NAME>, execute_<NAME>. Where <NAME> is the name of the state. This allows us to conveniently execute code whenever we transition states or have code run repeatedly while we're in the state.

An incredibly simple example of a play that just moves a robot to a certain position on the field could be implemented as follows:

import play
import skills.move
import robocup
class MoveOneRobot(play.Play):
def __init__(self):
super().__init__(continuous=False)
self.add_transition(behavior.Behavior.State.start,
behavior.Behavior.State.running,
lambda: True,
'immediately')
def on_enter_running(self):
# Add a "Move" subbehavior that tells a robot to a specified (x, y) location
m = skills.move.Move(robocup.Point(0, 2))
self.add_subbehavior(m, name='move', required=True)
def on_exit_running(self):
# When the running state is over, we remove the subbehavior
self.remove_subbehavior('move')

Behavior Sequences

Behavior Sequences are an alternative to the state-machine based logic found elsewhere in Soccer. Many times we don't need complex transition functions, and simply want to execute behaviors in sequence. We simply model the behavior sequence as a list of behaviors to be executed in order. We can then use these sequences in Plays or Complex behaviors. There are also SingleRobot Behavior Sequences which only allow SingleRobot behaviors.

We can view the lifecycle of the class in its init method:

class BehaviorSequence(composite_behavior.CompositeBehavior):
def __init__(self, behaviors= []):
super().__init__(
continuous=True
) # Note: we don't know if the sequence will be continuous or not, so we assume it is to be safe
self.behaviors = behaviors
self._current_behavior_index = -1
self.add_transition(
behavior.Behavior.State.start, behavior.Behavior.State.running,
lambda: len(self.behaviors) > 0, 'has subbehavior sequence')
self.add_transition(
behavior.Behavior.State.running, behavior.Behavior.State.completed,
lambda: self._current_behavior_index >= len(self.behaviors),
'all subbehaviors complete')
self.add_transition(
behavior.Behavior.State.running, behavior.Behavior.State.failed,
lambda: self.current_behavior != None and self.current_behavior.is_in_state(behavior.Behavior.State.failed),
'subbehavior failed')

The behaviors are added to the sequence, and it executes them one by one. When they are all complete, the sequence ends. Alternatively, if one of the behaviors in the sequence fails then the sequence transitions to the failed state and stops executing behaviors immediately.

Role Assignment

When writing a play, you are defining a set of actions that should be taken by different robots on the field. One important thing to note though is that you don't choose which robots will fulfill these roles directly. Instead, you can define what attributes a robot should have in order for them to be a good fit for the role. At each iteration of the main run loop, the role assignment system examines the role requirements for each running behavior and uses an optimal matching algorithm (the hungarian algorithm) to find the best robot to assign to each role. In order to implement custom assignment logic for your behavior, you'll need to override the role_requirements() method.

RoboCup python module

We use a 3rd-party library called Boost Python to create an interface between the C++ code that makes up the majority of our soccer program and the gameplay system that's written in python. Boost Python is used to create a python module called "robocup" that python code can import in order to access our C++ classes and functions. The C++ classes and functions available to the python interface are created through "wrappers" in the robocup-py.cpp file. The "robocup" python module is compiled as a part of our project when you run make and is placed in the run directory as robocup.so. This can be imported like any other python module like so:

cd robocup-software
# Ensure that the latest version of the 'robocup' python module is built
make
# The python module is placed in the 'run' directory as 'robocup.so'
cd run
# Run python interpreter and import the module
python3
import robocup
# Use the help function to see a list of available classes and functions
help(robocup)

Visualization

Many plays provide visualizations for the actions they are performing to make it easier for the user to quickly see what's happening. For example, the Defense tactic draws red triangles from opponent robots and the ball to our goal to help visualize our defense's effective coverage. This functionality is provided by the many drawing methods provided by the SystemState class.