Skip to content
Snippets Groups Projects
Commit 105c8b5b authored by Tessaris Sergio's avatar Tessaris Sergio
Browse files

feat: runner update and updated documentation

parent c5c8f73b
No related branches found
No related tags found
No related merge requests found
......@@ -6,7 +6,7 @@ The package has been written to be used in the master course of AI taught at the
## Install
You can download the source code from <https://gitlab.inf.unibz.it/tessaris/wumpus> and use `pip install .` or installing directly from the repository using
You can download the source code from <https://gitlab.inf.unibz.it/tessaris/wumpus> and use `pip install .`, or install directly from the repository using
```
pip install https://gitlab.inf.unibz.it/tessaris/wumpus/-/archive/master/wumpus-master.tar.gz
......@@ -14,6 +14,71 @@ pip install https://gitlab.inf.unibz.it/tessaris/wumpus/-/archive/master/wumpus-
## Usage
To write your own player you should create a subclass of `Player` (defined in [gridworld.py](https://gitlab.inf.unibz.it/tessaris/wumpus/blob/master/wumpus/gridworld.py)) and then use an instance as a parameter of the `run_episode` method of `GridWorld` class (defined in [gridworld.py](https://gitlab.inf.unibz.it/tessaris/wumpus/blob/master/wumpus/gridworld.py)). Instances of the `Player` subclasses should be created using its `player` class method, where you can decide whether to give the player access to the underlying environment or to rely on the state information provided by the `play` method.
To write your own player you should create a subclass of `Player` (defined in [gridworld.py](https://gitlab.inf.unibz.it/tessaris/wumpus/blob/master/wumpus/gridworld.py)) and then use an instance as a parameter of the `run_episode` method of `GridWorld` class (defined in [gridworld.py](https://gitlab.inf.unibz.it/tessaris/wumpus/blob/master/wumpus/gridworld.py)).
Examples of the usage of the package can be found in the implementation of two players `RandomPlayer` and `UserPlayer` from [gridworld.py](https://gitlab.inf.unibz.it/tessaris/wumpus/blob/master/wumpus/gridworld.py), and in the file [`wumpus-usage.py`](https://gitlab.inf.unibz.it/tessaris/wumpus/blob/master/examples/wumpus-usage.py) in the `examples` directory of the repository.
\ No newline at end of file
Examples of the usage of the package can be found in the implementation of two players `RandomPlayer` and `UserPlayer` from [gridworld.py](https://gitlab.inf.unibz.it/tessaris/wumpus/blob/master/wumpus/gridworld.py), and in the files [`wumpus-usage.py`](https://gitlab.inf.unibz.it/tessaris/wumpus/blob/master/examples/wumpus-usage.py), [`eater-usage.py`](https://gitlab.inf.unibz.it/tessaris/wumpus/blob/master/examples/eater-usage.py) in the [`examples`](/wumpus-usage.py), [`eater-usage.py`]() directory of the repository.
Your player could be also run using the script `gridrunner` script (in the repository is the `runner.py` file) and it'll be available once the package is installed (in alternative could be executed using `python -m wumpus.runner`):
```
$ gridrunner --help
usage: gridrunner [-h] [--name NAME] [--path PATH] --entry ENTRY
[--world {EaterWorld,WumpusWorld}] [--horizon HORIZON]
[--noshow] [--out OUT] [--version]
[infiles [infiles ...]]
Run episodes on worlds using the specified player.
positional arguments:
infiles world description JSON files, they must be compatible
with the world type (see --world option). (default:
None)
optional arguments:
-h, --help show this help message and exit
--name NAME, -n NAME name of the player, default to the name of the player
class (default: None)
--path PATH, -p PATH path of the player library, it's prepended to the
sys.path variable (default: .)
--entry ENTRY, -e ENTRY
object reference for a Player subclass in the form
'importable.module:object.attr'. See
<https://packaging.python.org/specifications/entry-
points/#data-model> for details. (default: None)
--world {EaterWorld,WumpusWorld}, -w {EaterWorld,WumpusWorld}
class name of the world (default: EaterWorld)
--horizon HORIZON, -z HORIZON
maximum number of steps (default: 20)
--noshow prevent the printing the world at each step (default:
True)
--out OUT, -o OUT write output to file (default: <_io.TextIOWrapper
name='<stdout>' mode='w' encoding='UTF-8'>)
--version show program's version number and exit
```
For example:
``` bash
$ gridrunner --world EaterWorld --entry wumpus:RandomPlayer --noshow --horizon 5 --path examples ./examples/eater-world.json
┌────────┐
│.....│
│.██...│
│.....│
│.....│
│🍌🐒.🍌.│
└────────┘
Step 0: agent Eater_df9919cd executing W -> reward 9
Step 1: agent Eater_df9919cd executing S -> reward -1
Step 2: agent Eater_df9919cd executing N -> reward -1
Step 3: agent Eater_df9919cd executing W -> reward -1
Step 4: agent Eater_df9919cd executing N -> reward -1
Episode terminated by maximum number of steps (5).
┌─────────┐
│.....│
│.██...│
│🐒....│
│.....│
│...🍌.│
└─────────┘
Episode terminated with a reward of 5 for agent Eater_df9919cd
```
......@@ -8,20 +8,58 @@ import argparse
import importlib
import io
import os
import re
import sys
from typing import ClassVar, Iterator
from . import __version__
from .wumpus import WumpusWorld
from .gridworld import Player, EaterWorld, GridWorld
def get_player_class(modulename: str, classname: str, path: os.PathLike = None) -> ClassVar[Player]:
if path is not None and path not in sys.path:
sys.path.append(path)
player_module = importlib.import_module(modulename)
def get_subclasses(cls):
for subclass in cls.__subclasses__():
yield from get_subclasses(subclass)
yield subclass
def check_entrypoint(arg_value: str, pattern: re.Pattern = re.compile(r"^[\w.-]+:[\w.-]+$")) -> str:
"""Checks that the argument is a valid object reference specification in the form 'importable.module:object.attr'. See <https://packaging.python.org/specifications/entry-points/#data-model> for details.
Args:
arg_value (str): object reference
pattern ([type], optional): regular expression defining the required reference. Defaults to '^[\w.-]+:[\w.-]+$'.
Raises:
argparse.ArgumentTypeError: if the argument doesn't comply with the pattern
Returns:
str: the given argument if it complies with the pattern
"""
if not pattern.match(arg_value):
raise argparse.ArgumentTypeError("the argument doesn't comply with {}".format(pattern.pattern))
return arg_value
player_class = getattr(player_module, classname)
assert issubclass(player_class, Player)
def get_player_class(object_ref: str, path: os.PathLike = None) -> ClassVar[Player]:
if path is not None and path not in sys.path:
if not os.path.isdir(path):
raise FileNotFoundError('Directory <{}> not found'.format(path))
sys.path.insert(0, path)
# see <https://packaging.python.org/specifications/entry-points/#data-model>
modname, qualname_separator, qualname = object_ref.partition(':')
obj = importlib.import_module(modname)
if qualname_separator:
for attr in qualname.split('.'):
try:
obj = getattr(obj, attr)
except AttributeError as e:
raise ImportError('Cannot import {} object: {}'.format(object_ref, e))
player_class = obj
if not issubclass(player_class, Player):
raise NotImplementedError('class {} is not a subclass of Player'.format(player_class))
return player_class
......@@ -61,30 +99,35 @@ def gridrunner(*args):
"""
Run episodes on worlds using the specified player.
"""
world_classes = sorted(get_subclasses(GridWorld), key=lambda c: c.__name__)
parser = argparse.ArgumentParser(description=gridrunner.__doc__, formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('infiles', type=argparse.FileType('r'), nargs='*', help='world description JSON files')
parser.add_argument('--name', type=str, default='wumpy', help='name of the player')
parser.add_argument('--path', type=str, default='.', help='path of the player library')
parser.add_argument('--module', type=str, required=True, help='name of the player class module')
parser.add_argument('--class', type=str, required=True, help='name of the player class')
parser.add_argument('--world', type=str, default='WumpusWorld', help='class name of the world')
parser.add_argument('--horizon', type=int, default=20, help='maximum number of steps')
parser.add_argument('infiles', type=argparse.FileType('r'), nargs='*', help='world description JSON files, they must be compatible with the world type (see --world option).')
parser.add_argument('--name', '-n', type=str, help='name of the player, default to the name of the player class')
parser.add_argument('--path', '-p', type=str, default='.', help="path of the player library, it's prepended to the sys.path variable")
parser.add_argument('--entry', '-e', type=check_entrypoint, required=True, help="object reference for a Player subclass in the form 'importable.module:object.attr'. See <https://packaging.python.org/specifications/entry-points/#data-model> for details.")
parser.add_argument('--world', '-w', type=str, default=world_classes[0].__name__, choices=[c.__name__ for c in world_classes], help='class name of the world')
parser.add_argument('--horizon', '-z', type=int, default=20, help='maximum number of steps')
parser.add_argument('--noshow', action='store_false', help="prevent the printing the world at each step")
parser.add_argument('--out', type=argparse.FileType('w'), default=sys.stdout, help="write output to file")
parser.add_argument('--out', '-o', type=argparse.FileType('w'), default=sys.stdout, help="write output to file")
parser.add_argument('--version', action='version', version='%(prog)s ' + __version__)
args_dict = vars(parser.parse_args(args))
name = args_dict['name']
path = os.path.abspath(args_dict['path']) if args_dict['path'] != '.' else os.getcwd()
modulename = args_dict['module']
classname = args_dict['class']
obj_ref = args_dict['entry']
world_type = args_dict['world']
horizon = args_dict['horizon']
show = args_dict['noshow']
outf = args_dict['out']
player_class = get_player_class(modulename, classname, path=path)
player_class = get_player_class(obj_ref, path=path)
world_class = get_world_class(world_type)
if name is None:
name = player_class.__name__
if len(args_dict['infiles']) > 0:
for world in worlds(args_dict['infiles'], world_class):
play_episode(world, player_class, player_name=name, horizon=horizon, show=show, outf=outf)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment