Skip to content

Commit

Permalink
Implementing functions for reading and writing game files of speciali…
Browse files Browse the repository at this point in the history
…zed formats

Reading
* `read_efg` - reads an .efg file format
* `read_nfg` - reads an .nfg file format
* `read_gbt` - reads a .gbt file (XML files produced by the GUI)
* `read_agg` - reads an action-graph games file format

Writing
* `Game.to_efg` - writes an .efg file
* `Game.to_nfg` - writes an .nfg file
* `Game.to_html` - writes out HTML tables
* `Game.to_latex` - writes a .tex file

New tests in test_io.py
  • Loading branch information
d-kad committed Dec 6, 2024
1 parent 25742d2 commit 6b37eb4
Show file tree
Hide file tree
Showing 6 changed files with 432 additions and 1 deletion.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ missing
gambit
.python-version
dist
.venv
9 changes: 9 additions & 0 deletions src/pygambit/gambit.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -379,8 +379,17 @@ cdef extern from "games/behavspt.h":
cdef extern from "util.h":
c_Game ReadGame(char *) except +IOError
c_Game ParseGame(char *) except +IOError
c_Game ParseGbtGame(string) except +IOError
c_Game ParseEfgGame(string) except +IOError
c_Game ParseNfgGame(string) except +IOError
c_Game ParseAggGame(string) except +IOError
string WriteGame(c_Game, string) except +IOError
string WriteGame(c_StrategySupportProfile) except +IOError
string WriteEfgFile(c_Game)
string WriteNfgFile(c_Game)
string WriteLaTeXFile(c_Game)
string WriteHTMLFile(c_Game)
# string WriteGbtFile(c_Game)

c_Rational to_rational(char *) except +

Expand Down
220 changes: 219 additions & 1 deletion src/pygambit/game.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
import io
import itertools
import pathlib

Expand All @@ -28,6 +29,119 @@ import scipy.stats
import pygambit.gte
import pygambit.gameiter

ctypedef string (*GameWriter)(const c_Game &) except +IOError
ctypedef c_Game (*GameParser)(const string &) except +IOError


@cython.cfunc
def read_game(filepath_or_buffer: typing.Union[str, pathlib.Path, io.BufferedReader],
parser: GameParser):

g = cython.declare(Game)
g = Game()
if isinstance(filepath_or_buffer, io.BufferedReader):
data = filepath_or_buffer.read()
else:
with open(filepath_or_buffer, "rb") as f:
data = f.read()
try:
g.game = parser(data)
except Exception as exc:
raise ValueError(f"Parse error in game file: {exc}") from None
return g


def read_gbt(filepath_or_buffer: typing.Union[str, pathlib.Path, io.BufferedReader]) -> Game:
"""Construct a game from its serialised representation in a GBT file.

Parameters
----------
filepath : str or path object
The path to the file containing the game representation.

Returns
-------
Game
A game constructed from the representation in the file.

Raises
------
IOError
If the file cannot be opened or read
ValueError
If the contents of the file are not a valid game representation.
"""
return read_game(filepath_or_buffer, parser=ParseGbtGame)


def read_efg(filepath_or_buffer: typing.Union[str, pathlib.Path, io.BufferedReader]) -> Game:
"""Construct a game from its serialised representation in a EFG file.

Parameters
----------
filepath : str or path object
The path to the file containing the game representation.

Returns
-------
Game
A game constructed from the representation in the file.

Raises
------
IOError
If the file cannot be opened or read
ValueError
If the contents of the file are not a valid game representation.
"""
return read_game(filepath_or_buffer, parser=ParseEfgGame)


def read_nfg(filepath_or_buffer: typing.Union[str, pathlib.Path, io.BufferedReader]) -> Game:
"""Construct a game from its serialised representation in a NFG file.

Parameters
----------
filepath : str or path object
The path to the file containing the game representation.

Returns
-------
Game
A game constructed from the representation in the file.

Raises
------
IOError
If the file cannot be opened or read
ValueError
If the contents of the file are not a valid game representation.
"""
return read_game(filepath_or_buffer, parser=ParseNfgGame)


def read_agg(filepath_or_buffer: typing.Union[str, pathlib.Path, io.BufferedReader]) -> Game:
"""Construct a game from its serialised representation in a AGG file.

Parameters
----------
filepath : str or path object
The path to the file containing the game representation.

Returns
-------
Game
A game constructed from the representation in the file.

Raises
------
IOError
If the file cannot be opened or read
ValueError
If the contents of the file are not a valid game representation.
"""
return read_game(filepath_or_buffer, parser=ParseAggGame)


@cython.cclass
class GameOutcomes:
Expand Down Expand Up @@ -1009,7 +1123,9 @@ class Game:
)

def write(self, format="native") -> str:
"""Produce a serialization of the game.
"""Deprecated in favour of to_xxx methods.

Produce a serialization of the game.

Several output formats are
supported, depending on the representation of the game.
Expand Down Expand Up @@ -1049,6 +1165,108 @@ class Game:
else:
return WriteGame(self.game, format.encode("ascii")).decode("ascii")

@cython.cfunc
def _to_format(
self,
writer: GameWriter,
filepath_or_buffer: typing.Union[str, pathlib.Path, io.BufferedWriter, None] = None
):
serialized_game = writer(self.game)
if filepath_or_buffer is None:
return serialized_game.decode()
if isinstance(filepath_or_buffer, io.BufferedWriter):
filepath_or_buffer.write(serialized_game)
else:
with open(filepath_or_buffer, "wb") as f:
f.write(serialized_game)

def to_efg(self,
filepath_or_buffer: typing.Union[str, pathlib.Path, io.BufferedWriter, None] = None
) -> typing.Union[str, None]:
"""Save the game to an .efg file or return its serialized representation

Parameters
----------
filepath_or_buffer : str or Path or BufferedWriter or None, default None
String, path object, or file-like object implementing a write() function.
If None, the result is returned as a string.

Return
------
String representation of the game or None if the game is saved to a file
"""
return self._to_format(WriteEfgFile, filepath_or_buffer)

def to_nfg(self,
filepath_or_buffer: typing.Union[str, pathlib.Path, io.BufferedWriter, None] = None
) -> typing.Union[str, None]:
"""Save the game to a .nfg file or return its serialized representation

Parameters
----------
filepath_or_buffer : str or Path or BufferedWriter or None, default None
String, path object, or file-like object implementing a write() function.
If None, the result is returned as a string.

Return
------
String representation of the game or None if the game is saved to a file
"""
return self._to_format(WriteNfgFile, filepath_or_buffer)

# def to_gbt(
# self,
# filepath_or_buffer: typing.Union[str, pathlib.Path, io.BufferedWriter, None] = None
# ) -> typing.Union[str, None]:
# """Save the game to a .gbt file or return its serialized representation

# Parameters
# ----------
# filepath_or_buffer : str or Path or BufferedWriter or None, default None
# String, path object, or file-like object implementing a write() function.
# If None, the result is returned as a string.

# Return
# ------
# String representation of the game or None if the game is saved to a file
# """
# return self._to_format(WriteGbtFile, filepath_or_buffer)

def to_html(self,
filepath_or_buffer: typing.Union[str, pathlib.Path, io.BufferedWriter, None] = None
) -> typing.Union[str, None]:
"""Export the game to an .html file or return its serialized representation

Parameters
----------
filepath_or_buffer : str or Path or BufferedWriter or None, default None
String, path object, or file-like object implementing a write() function.
If None, the result is returned as a string.

Return
------
String representation of the game or None if the game is exported to a file
"""
return self._to_format(WriteHTMLFile, filepath_or_buffer)

def to_latex(
self,
filepath_or_buffer: typing.Union[str, pathlib.Path, io.BufferedWriter, None] = None
) -> typing.Union[str, None]:
"""Export the game to a .tex file or return its serialized representation

Parameters
----------
filepath_or_buffer : str or Path or BufferedWriter or None, default None
String, path object, or file-like object implementing a write() function.
If None, the result is returned as a string.

Return
------
String representation of the game or None if the game is exported to a file
"""
return self._to_format(WriteLaTeXFile, filepath_or_buffer)

def _resolve_player(self,
player: typing.Any, funcname: str, argname: str = "player") -> Player:
"""Resolve an attempt to reference a player of the game.
Expand Down
58 changes: 58 additions & 0 deletions src/pygambit/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include <sstream>
#include "gambit.h"
#include "games/nash.h"
// #include "gui/gamedoc.h"

using namespace std;
using namespace Gambit;
Expand All @@ -50,6 +51,63 @@ Game ParseGame(char *s)
return ReadGame(f);
}

Game ParseGbtGame(std::string const &s)
{
std::istringstream f(s);
return ReadGbtFile(f);
}

Game ParseEfgGame(std::string const &s)
{
std::istringstream f(s);
return ReadEfgFile(f);
}

Game ParseNfgGame(std::string const &s)
{
std::istringstream f(s);
return ReadNfgFile(f);
}

Game ParseAggGame(std::string const &s)
{
std::istringstream f(s);
return ReadAggFile(f);
}

std::string WriteEfgFile(const Game &p_game)
{
std::ostringstream f;
p_game->Write(f, "efg");
return f.str();
}

std::string WriteNfgFile(const Game &p_game)
{
std::ostringstream f;
p_game->Write(f, "nfg");
return f.str();
}

// std::string WriteGbtFile(const Game &p_game)
// {
// std::ostringstream f;
// auto document = gbtGameDocument(p_game);
// document.SaveDocument(f);
// return f.str();
// }

std::string WriteHTMLFile(const Game &p_game)
{
return WriteHTMLFile(p_game, p_game->GetPlayer(1), p_game->GetPlayer(2));
}

std::string WriteLaTeXFile(const Game &p_game)
{
return WriteLaTeXFile(p_game, p_game->GetPlayer(1), p_game->GetPlayer(2));
}

/// @deprecated Deprecated in favour of WriteXXXFile
std::string WriteGame(const Game &p_game, const std::string &p_format)
{
if (p_format == "html") {
Expand Down
49 changes: 49 additions & 0 deletions tests/test_games/2x2.agg
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#AGG
# Generated by GAMUT v1.0.1
# Random Symmetric Action Graph Game
# Game Parameter Values:
# Random seed: 1306765487422
# Cmd Line: -players 2 -actions 2 -g RandomSymmetricAGG -output SpecialOutput -random_params -f 2x2.agg
# Players: 2
# Actions: 2 2
# players: 2
# actions: [2]
# graph: RandomGraph
# graph_params: null
# Graph Params:
# { nodes: 2, edges: 4, sym_edges: false, reflex_ok: true }
# Players: 2
# Actions: [ 2 2 ]

#number of players:
2
#number of action nodes:
2
#number of func nodes:
0

#sizes of action sets:
2 2

#action sets:
0 1
0 1


#the action graph:
2 0 1
2 1 0

#the types of func nodes:
#0: sum
#1: existence
#2: highest
#3: lowest


#the payoffs:
#now the payoff values: one row per action node.
#For each row: first, the type of the payoff format
#Then payoffs are given in lexicographical order of the input configurations
0 35.622809717175556 -3.7188980070375948
0 -10.180526107272556 95.1203958671928
Loading

0 comments on commit 6b37eb4

Please sign in to comment.