4.2. Games

All games, whether represented in extensive or strategic form, are handled by the C++ classes Game and GameRep. The classes which represent games and parts thereof uses the pointer-to-implementation (pimpl) idiom. Client code will generally refer to objects of type Game. This is a smart-pointer class containing a pointer to the representation class GameRep.

4.2.1. Building, loading, and saving games

For example, to create a new game with an extensive (tree) representation in C++, set its title, and print out that title:

#include <iostream>
#include "libgambit/libgambit.h"

main()
{
  Gambit::Game myGame = Gambit::NewTree();
  myGame->SetTitle("Hello, World!");
  std::cout << myGame->GetTitle() << std::endl;
}
Note that the object myGame has pointer semantics. If it points to a null or invalid pointer, attempting to dereference it will throw a C++ exception of class Gambit::NullException. Assignment of one Game to another also has pointer semantics; both objects will refer to the same physical game. Also, objects of class Game handle reference-counting, which is decremented when the object goes out of scope. Thus, in the above example, the game created is automatically deallocated when the last Game referencing it goes away at the end of main().

The parallel code for Python users is

import gambit

myGame = gambit.NewTree()
myGame.SetTitle("Hello, World!")
print myGame.GetTitle()

Constructing a game in strategic form requires specifying an initial dimension for the game. In C++, this is done using an Array of type int. For a two-player game where the first player has 2 strategies and the second player 3 strategies,

Gambit::Array<int> dim(2);
dim[1] = 2;
dim[2] = 3;
Gambit::Game myGame = Gambit::NewTable(dim);
In Python, it's a little simpler, since a Python list can be used directly:
myGame = gambit.NewTable([2,3])

As just noted, Gambit games can be expressed in either extensive or strategic representation. Games in extensive representation can only be built or modified using functions appropriate to game trees, and games in strategic representation can only use functions appropriate to setting the payoffs associated with each strategy vector. For games in extensive representation, Gambit can compute the reduced strategic representation. This is done by calling BuildComputedValues on the game object. The computed reduced strategy sets are cached internally until any structural changes in the game tree are made, at which point the cached values are cleared and BuildComputedValues must be called again to compute the revised reduced strategic representation. Note that the size of the reduced strategic representation of a game may increase very rapidly, so think carefully about building the reduced strategic representation for games of nontrivial size; it is generally recommended to analyze extensive games directly in the extensive form where possible.

After building a game, it is generally recommended to call Canonicalize on the game. This function renumbers the nodes and information sets in the game in a regular way, such that two identical games will always have their elements numbered the same way, irrespective of the order of operations used to construct them.

Games can also be read from a file. A typical C++ usage might be

Gambit::Game myGame;

try {
  std::ifstream f("mygame.efg");
  myGame = Gambit::ReadGame(f);
}
except (Gambit::InvalidFileException) {
  // report the error condition here
}
In the Python version, ReadGame takes a string with the file contents, not the file object itself, and (currently) failure results in an RuntimeError:
try:
  myGame = gambit.ReadGame(file("mygame.efg").read())
except RuntimeError:
  # handle here
ReadGame reads files in the .efg and .nfg formats described in Chapter 5. It does not (yet) handle the XML-based .gbt workbook format recently introduced in the graphical user interface. However, note that the game structure itself is stored in the .gbt file using these formats, so it can be extracted with a little preprocessing.

Writing games to these file formats is accomplished by the members WriteEfgFile and WriteNfgFile, respectively. This fragment reads in an extensive game, and writes both a copy of the game in extensive form, as well as its reduced strategic form, to separate files.

std::ifstream f1("mygame.efg");
Gambit::Game myGame = Gambit::ReadGame(f1);

std::ofstream f2("mygamecopy.efg");
myGame->WriteEfgFile(f2);

myGame->BuildComputedValues();   /* constructs the reduced strategic form */
std::ofstream f3("mygamecopy.nfg");
myGame->WriteNfgFile(f3);
Use in Python again varies slightly. In Python, the members AsEfgFile and AsNfgFile create a string representation of the game, which can be written to a file:
myGame = gambit.ReadGame(file("mygame.efg").read())
file("mygamecopy.efg", "w").write(myGame.AsEfgFile())
file("mygamecopy.nfg", "w").write(myGame.AsNfgFile())

4.2.2. Objects representing components of a game

Table 4-2. Classes representing parts of games

ClassDescription
GamePlayerA player in the game (including the chance player)
GameOutcomeAn outcome, assigning payoffs to all players
GameNodeA node in an extensive game
GameInfosetAn information set in an extensive game
GameStrategyA strategy for a player in a strategic game

Table 4-2 lists the classes which represent objects defined within a game. As with Game, each of these classes is a smart-pointer class, which references an internal representation class ending in Rep; e.g., GamePlayer is a smart pointer pointing to an object of class GamePlayerRep.

In addition to serving the usual purposes of a smart pointer, these classes also guard against attempting to access objects within a game which no longer exist. Consider this example in Python:

>>> import gambit
>>> myGame = gambit.NewTree()
>>> outcome = myGame.NewOutcome()
>>> myGame.DeleteOutcome(outcome)
>>> outcome.GetLabel()
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "/usr/lib/python2.4/site-packages/gambit.py", line 1096, in GetLabel
    def GetLabel(*args): return _gambit.GameOutcome_GetLabel(*args)
RuntimeError: operating on null object
Here, we create a variable outcome which refers to an outcome in the game. This outcome is subsequently deleted from the game. After that point, further attempts to reference the object outcome result in an exception. The C++ equivalent code fragment would be
Gambit::Game myGame = Gambit::NewTree();
Gambit::GameOutcome outcome = myGame->NewOutcome();
myGame->DeleteOutcome(outcome);
std::string label = outcome->GetLabel();
The last line would raise an exception of type Gambit::NullException.