IGameListener for bespoke reporting

IGameListener for bespoke reporting

Both GameReport and RoundRobinTournament (see Running a game and Game Reporting) allow bespoke reporting of matters of interest.

The framework has a number of interfaces:

IGameListener

IGameListener : Listens to a Game (with a classic Observer pattern) for events that mark:

  • the start or end of the game
  • the end of a round
  • the end of an individual player’s turn
  • each time a player makes a decision.

The when an event can be logged is defined by CoreConstants.GameEvents

  • ABOUT_TO_START
  • GAME_OVER
  • ROUND_OVER
  • TURN_OVER
  • ACTION_CHOSEN : immediately after an Action is chosen, but before it is implemented
  • ACTION_TAKEN : immediately after an Action has been implemented and the game state advanced
  • GAME_EVENT : a game-specific event that is worthy of notice and is not linked directly to a player action

Default generation of these event has been added to the core framework at appropriate places, and they can be overridden as required for specific games. ROUND_OVER and TURN_OVER are implemented in TurnOrder, so if a bespoke version of endRound or endPlayerTurn is implemented you will need to add in the relevant calls (see the parent abstract TurnOrder class).

GAME_EVENT is the one event that is not implemented in any default part of the framework. It is designed to be used in a game when an event occurs that is important to log to understand how the game is going. One example might be if a player is knocked out of the game on another player’s turn. Generating a GAME_EVENT with appropriate detail text will ensure that this is logged appropriately.

Bespoke IGameListeners can do anything required on receipt of a game event by implementing the onEvent and onGameEvent methods (see class documentation for details). A common use-case is to log/report data on a game. The following interfaces are provided to support this use-case.

IStatisticLogger

IStatisticLogger : This is responsible for how to record data (where the IGameListener controls when data is extracted). Two implementations (SummaryLogger and FileStatsLogger) currently either log summary data to the screen, or write detailed data to a file (one row per event). Implementations can easily be added to send data to a database, or a GUI for example. Generally the IStatisticLogger to use is injected into the IGameListener as a constructor argument.

IGameAttribute

IGameAttribute : extracts a single piece of information (Attribute) from either of:

  • (AbstractGameState state, AbstractAction action)
  • (AbstractGameState state, int player) This can be a String, Integer, Boolean, Double, or something else bespoke.

Generally speaking each IGameListener will have a list of IGameAttributes that it extracts to record interesting information about the event. For an example of some game-specific metrics used in reporting, see games.dominion.DominionListener and games.dominion.DominionGameAttributes. utilities.ActionListener and utilities.ActionSimpleAttributes provide a simple game-agnostic example of tracking a user-readable log of all actions taken in a game. These are primarily designed for reporting user-friendly data, in particular any String data.

IStateFeatureVector / IActionFeatureVector

These interfaces take a state, or a (state, action) pair and generate a numeric array (double[]). In contrast to the IGameAttribute interface above, these are useful for output that is designed to be machine-readable, and to project any state or action into an n-dimensional vector. Examples of IStateFeatureVector are implemented currently for Dots and Boxes, Love Letter and Dominion.

Two useful IGameListener implementations are provided to generate game trajectories at the desired level of granularity: StateFeatureListener and ActionFeatureListener. These can be configured with a specific IStatisticLogger, GameEvent to define when a snapshot should be recorded, and the I[State|Action]FeatureVector that defines the snapshot vector.

When using GameReport, this can all be defined easily in a JSON config file. An example is below:

{
    "class" : "utilities.StateFeatureListener",
    "args" : [
        {
            "class" : "utilities.FileStatsLogger",
            "args" : ["DataFile.txt"]
        },
        { "class" : "games.dotsboxes.DBStateFeatures" },
        { 
            "enum" : "core.CoreConstants$GameEvents", 
            "value" : "ACTION_TAKEN"
        },
        false
    ]
}

In this example we want a StateFeatureListener (the class in the top-level). We then list the args that the constructor takes. In this case there are four arguments: IStatisticsLogger, IStateFeatureVector, CoreConstant.GameEvent, boolean. The values for each of these are specified in the top-level args array (inside the […]) - and must be in the same order as the relevant class constructor.

This is recursive to allow quite complex details to be configured. At the second level of nesting in this example we specify:

  • We want to use a FileStatsLogger, specifying the output file this should write its data to (the one constructor argument).
  • The implementation of IStateFeatureVector to use is DBStateFeatures, which has a no-arg constructor.
  • We wish to record data every time an action is taken (any one of the valid GameEvent constants is valid, so we could have chosen to record at the end of a player’s turn for example).
  • Finally, the false indicates that we want to record data for every player at each event (setting this to true would only record data for the current player at that point in the game).

The valid JSON tags are:

  • class. The full package path of the class to be used.
  • args. An array of the values to use in the class constructor. These must be in the order they appear in the class constructor.
  • enum. A special case for when the class is an enum. This replaces the use of class.
  • value. Provides the value to use for an emum, and only valid in this situation.

The JSON values can then be any of a String, Integer, Double, Boolean.