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 Jan 3, 2025
1 parent 4502065 commit 0cff9de
Show file tree
Hide file tree
Showing 7 changed files with 446 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
11 changes: 10 additions & 1 deletion doc/pygambit.api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ Creating, reading, and writing games
.. autosummary::
:toctree: api/

read_gbt
read_efg
read_nfg
read_agg

Game.new_tree
Game.new_table
Game.from_arrays
Expand All @@ -36,6 +41,10 @@ Creating, reading, and writing games
Game.read_game
Game.parse_game
Game.write
Game.to_efg
Game.to_nfg
Game.to_html
Game.to_latex


Transforming game trees
Expand Down Expand Up @@ -181,7 +190,7 @@ Player behavior
Game.random_strategy_profile
Game.mixed_behavior_profile
Game.random_behavior_profile
Game.support_profile
Game.strategy_support_profile


Representation of strategic behavior
Expand Down
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
237 changes: 237 additions & 0 deletions 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 @@ -27,6 +28,134 @@ import scipy.stats

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.BytesIO],
parser: GameParser):

g = cython.declare(Game)
if isinstance(filepath_or_buffer, io.BytesIO):
data = filepath_or_buffer.read()
else:
with open(filepath_or_buffer, "rb") as f:
data = f.read()
try:
g = Game.wrap(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.BytesIO]) -> Game:
"""Construct a game from its serialised representation in a GBT file.

Parameters
----------
filepath_or_buffer : str, Path or BytesIO
The path to the file containing the game representation or file-like object

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.

See Also
--------
read_efg, read_nfg, read_agg
"""
return read_game(filepath_or_buffer, parser=ParseGbtGame)


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

Parameters
----------
filepath_or_buffer : str, Path or BytesIO
The path to the file containing the game representation or file-like object

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.

See Also
--------
read_gbt, read_nfg, read_agg
"""
return read_game(filepath_or_buffer, parser=ParseEfgGame)


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

Parameters
----------
filepath_or_buffer : str, Path or BytesIO
The path to the file containing the game representation or file-like object

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.

See Also
--------
read_gbt, read_efg, read_agg
"""
return read_game(filepath_or_buffer, parser=ParseNfgGame)


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

Parameters
----------
filepath_or_buffer : str, Path or BytesIO
The path to the file containing the game representation or file-like object

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.

See Also
--------
read_gbt, read_efg, read_nfg
"""
return read_game(filepath_or_buffer, parser=ParseAggGame)


@cython.cclass
class GameOutcomes:
Expand Down Expand Up @@ -446,6 +575,10 @@ class Game:
def read_game(cls, filepath: typing.Union[str, pathlib.Path]) -> Game:
"""Construct a game from its serialised representation in a file.

.. deprecated:: 16.3.0
Method `Game.read_game` is deprecated, use one of the respective functions instead:
``read_gbt``, ``read_efg``, ``read_nfg``, ``read_agg``

Parameters
----------
filepath : str or path object
Expand Down Expand Up @@ -1060,9 +1193,113 @@ class Game:
.. versionchanged:: 16.3.0
Removed support for writing Game Theory Explorer format as the XML format
is no longer supported by recent versions of GTE.

.. deprecated:: 16.3.0
Method Game.write is deprecated, use one of the respective methods instead:
``Game.to_efg``, ``Game.to_nfg``, ``Game.to_html``, ``Game.to_latex``
"""
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

See Also
--------
to_nfg, to_html, to_latex
"""
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

See Also
--------
to_efg, to_html, to_latex
"""
return self._to_format(WriteNfgFile, 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

See Also
--------
to_efg, to_nfg, to_latex
"""
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

See Also
--------
to_efg, to_nfg, to_html
"""
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
49 changes: 49 additions & 0 deletions src/pygambit/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,55 @@ 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 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
Loading

0 comments on commit 0cff9de

Please sign in to comment.