Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • Davide.Lanti1/wumpus-tessaris-1
  • Davide.Lanti1/wumpus-tessaris
  • Asma.Tajuddin/wumpus
  • Ali.Ahmed/wumpus
  • Sanaz.Khosropour/wumpus
  • PetroMakanza.Joseph/wumpus
  • tessaris/wumpus
7 results
Show changes
Commits on Source (7)
......@@ -6,19 +6,22 @@ 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 install directly from the repository using
You can download the source code from <https://gitlab.inf.unibz.it/Davide.Lanti1/wumpus-tessaris> and use `pip install .`,
or install directly from the repository using:
```
pip install git+https://gitlab.inf.unibz.it/tessaris/wumpus.git@master
pip install git+https://gitlab.inf.unibz.it/Davide.Lanti1/wumpus-tessaris
```
The present project is a fork of the original work by Prof. Sergio Tessaris: <https://gitlab.inf.unibz.it/tessaris/wumpus>.
## Usage
To write your own player you should create a subclass of `OnlinePlayer` or `OfflinePlayer` (defined in [player.py](https://gitlab.inf.unibz.it/tessaris/wumpus/blob/master/wumpus/player.py)) and then use an instance as a parameter of the `run_episode` function (defined in [runner.py](https://gitlab.inf.unibz.it/tessaris/wumpus/-/blob/master/wumpus/runner.py)).
To write your own player you should create a subclass of `OnlinePlayer` or `OfflinePlayer` (defined in [player.py](https://gitlab.inf.unibz.it/Davide.Lanti1/wumpus-tessaris/blob/master/wumpus/player.py)) and then use an instance as a parameter of the `run_episode` function (defined in [runner.py](https://gitlab.inf.unibz.it/Davide.Lanti1/wumpus-tessaris/-/blob/master/wumpus/runner.py)).
Examples of the usage of the package can be found in the implementation of two players `RandomPlayer` and `UserPlayer` in [player.py](https://gitlab.inf.unibz.it/tessaris/wumpus/blob/master/wumpus/player.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`](https://gitlab.inf.unibz.it/tessaris/wumpus/blob/master/examples) directory of the repository.
Examples of the usage of the package can be found in the implementation of two players `RandomPlayer` and `UserPlayer` in [player.py](https://gitlab.inf.unibz.it/Davide.Lanti1/wumpus-tessaris/blob/master/wumpus/player.py), and in the files [`wumpus_usage.py`](https://gitlab.inf.unibz.it/Davide.Lanti1/wumpus-tessaris/blob/master/examples/wumpus_usage.py), [`eater_usage.py`](https://gitlab.inf.unibz.it/Davide.Lanti1/wumpus-tessaris/blob/master/examples/eater_usage.py) in the [`examples`](https://gitlab.inf.unibz.it/Davide.Lanti1/wumpus-tessaris/blob/master/examples) directory of the repository.
Your player could be also run using the script `gridrunner` script (in the repository is the [`cli.py`](https://gitlab.inf.unibz.it/tessaris/wumpus/-/blob/master/wumpus/cli.py) file) and it'll be available once the package is installed (in alternative could be executed using `python -m wumpus.cli`):
Your player could be also run using the script `gridrunner` script (in the repository is the [`cli.py`](https://gitlab.inf.unibz.it/Davide.Lanti1/wumpus-tessaris/-/blob/master/wumpus/cli.py) file) and it'll be available once the package is installed (in alternative could be executed using `python -m wumpus.cli`):
``` bash
$ gridrunner --help
......@@ -81,5 +84,5 @@ Episode terminated with a reward of -5 for agent Eater_c881e1b0
You can also use a player defined in a script; e.g., if the player class `GooPlayer` is defined in the `eater_usage.py` you can use the `eater_usage:GooPlayer` entry. Remember Python rules for finding modules, where the current directory is added to the search path. If the script is in a different directory, you can use the `--path` argument to tell the script where to find it:
```bash
gridrunner --world EaterWorld --entry eater_usage:GooPlayer --path examples --noshow --horizon 5 ./examples/eater-world.json
```
\ No newline at end of file
gridrunner --world EaterWorld --entry eater_usage:GooPlayer --path examples --noshow --horizon 5 ./resources/eater-world.json
```
......@@ -4,52 +4,67 @@ import argparse
import json
# Register the Wumpus world environment
import gym_wumpus
from gym_wumpus.envs import WumpusEnv
from gym import envs, error, make
from gymnasium import envs, error, make
def run_episode(env: WumpusEnv):
obs = env.reset()
obs, info = env.reset()
total_reward = 0
print('Observation: {}'.format(env.space_to_percept(obs)))
step=0
print(f'Observation: {env.space_to_percept(obs)}')
for step in range(1000):
env.render(mode='human')
env.render() # This will use the render_mode set during environment creation
action = env.action_space.sample() # take a random action
obs, reward, done, info = env.step(action)
print('{} -> {}, [{}], {}{}'.format(env.space_to_action(action), reward, env.space_to_percept(obs), 'done ' if done else '', info))
obs, reward, truncated, terminated, info = env.step(action)
print(f'{env.space_to_action(action)} -> {reward}, [{env.space_to_percept(obs)}], {"done " if truncated or terminated else ""}{info}')
total_reward += reward
if done:
if truncated or terminated:
break
print('Reward {} after {} steps'.format(total_reward, step))
env.close()
def main():
default_env = 'wumpus-random-v0'
# default_env = 'wumpus-random-v0'
# default_env = 'wumpus-custom-v0'
RENDER_MODE='human'
parser = argparse.ArgumentParser()
parser.add_argument('id', nargs='?', default=default_env, help='Environment name')
parser.add_argument('--list', help='Show available environments', action="store_true", default=False)
parser.add_argument('--file', type=argparse.FileType('r'), help='Read the JSON description of the world from the file')
args = parser.parse_args()
# List available Wumpus environments
if args.list:
print([e.id for e in envs.registry.all() if str(e.id).lower().find('wumpus') >= 0])
print([e.id for e in envs.registry.values() if 'wumpus' in e.id.lower()])
return
# Load environment
if args.file is not None:
env = make('wumpus-custom-v0', desc=json.load(args.file))
env = make('wumpus-custom-v0', desc=json.load(args.file), render_mode=RENDER_MODE)
else:
try:
env = make(args.id)
env = make(args.id, render_mode=RENDER_MODE)
print("Observation space:", env.observation_space)
print("Action space", env.action_space)
except error.Error as e:
print('Bad Gym environment {}, using {}. Error is {}'.format(args.id, default_env, e))
env = make(default_env)
print(f'Error while creating environment {args.id}, using {default_env}. Error: {e}')
env = make(default_env, render_mode=RENDER_MODE)
# Check if the environment has the required methods
if hasattr(env, 'space_to_percept'):
run_episode(env)
else:
print('Environment {} is not a Wumpus world.'.format(env))
print(f'Environment {env} is not a Wumpus world.')
if __name__ == "__main__":
......
......@@ -109,7 +109,7 @@ def main(*cargs):
if args.example:
ex = ex_names[args.example.lower()]
else:
# Randomly play one of the examples
# Randomly play one of the examples_11
ex = random.choice(EXAMPLES)
print('Example {}:'.format(ex.__name__))
......
try:
from gym.envs.registration import register
from gymnasium.envs.registration import register
except ImportError as e:
raise Exception('OpenAI gym not installed, you should install wumpus package with [gym] feature.') from e
raise Exception('Gymnasium not installed.') from e
register(
......
import copy
from dataclasses import dataclass
import dataclasses
from typing import Dict, Tuple, Union
from typing import Dict, Tuple, Union, Optional
from gymnasium import spaces, Env
import gym
from gym import error, spaces, utils
from gym.utils import seeding
from wumpus import WumpusWorld, Hunter
class WumpusEnv(gym.Env):
metadata = {'render.modes': ['human', 'ansi']}
class WumpusEnv(Env):
metadata = {'render_modes': ['human', 'ansi'], 'render_fps': 10}
actions = tuple(Hunter.Actions)
def __init__(self, world: WumpusWorld = None):
"""Initialise a new Gym environment with an instance of the Wumpus world
def __init__(self, world: Optional[WumpusWorld] = None, render_mode: Optional[str] = 'human'):
"""Initialise a new Gymnasium environment with an instance of the Wumpus world
Keyword Arguments:
world {WumpusWorld} -- Wumpus world, if None a random one will be created (default: {None})
"""
super().__init__()
self.__initial_world: WumpusWorld = world if isinstance(world, WumpusWorld) else WumpusWorld.classic()
# make sure there's an hunter
try:
next(iter(o for o in self.__initial_world.objects if isinstance(o, Hunter)))
except StopIteration:
raise ValueError('Missing hunter in the Wumpus environment {}'.format(self.__initial_world))
self.__world: WumpusWorld = None
self.__agent: Hunter = None
self.__done: bool = False
# Initializing other attributes
self.__world: Optional[WumpusWorld] = None
self.__agent: Optional[Hunter] = None
self.__done: Optional[bool] = False
# Actions are discrete integer values
self.action_space = spaces.Discrete(len(self.actions))
# Set up observation space as a dictionary with each percept as a Discrete space
self.observation_space = spaces.Dict({f.name: spaces.Discrete(2) for f in dataclasses.fields(Hunter.Percept)})
# Optionally set render mode (for future rendering)
self.render_mode = render_mode
def _percept_to_space(self) -> Dict[str, int]:
percept = self.__agent.percept()
return {k: (1 if v else 0) for k, v in dataclasses.asdict(percept).items()}
@classmethod
def space_to_percept(cls, obs: Dict[str, int]) -> Hunter.Percept:
"""Converts the Gym ` <http://gym.openai.com/docs/#spaces>` to the Wumpus environment percept
"""Converts the Gym ` <https://gymnasium.farama.org/api/spaces/>` to the Wumpus environment percept
Arguments:
obs {Dict[str, int]} -- an observation space for the environment, see ``WumpusEnv.observation_space`` property for details
Returns:
Hunter.Percept -- named tuple corresponding to the observation
"""
......@@ -54,30 +60,35 @@ class WumpusEnv(gym.Env):
@classmethod
def space_to_action(cls, action: int) -> Hunter.Actions:
"""Converts the integer in the environment action space to the corresponding action in the ``Hunter`` agent
Arguments:
action {int} -- an integer from zero to the number of actions minus one, see ``WumpusEnv.action_space`` property for details
Returns:
Hunter.Actions -- the corresponding action
"""
return cls.actions[action]
def step(self, action: Union[int, Hunter.Actions]) -> Tuple[Dict[str, int], float, bool, Dict]:
"""OpenAI Gym ``step`` method, see `Gym documentation <http://gym.openai.com/docs/#observations>` for details
def step(self, action: Union[int, Hunter.Actions]) -> Tuple[Optional[Dict[str, int]], float, bool, bool, Dict]:
"""Farama gymnasium ``step`` method, see `Gymnasium documentation <https://gymnasium.farama.org/api/env/#gymnasium.Env.step>` for details
Arguments:
action {Union[int, Hunter.Actions]} -- the method accepts both an action space value (integer) or a ``Hunter.Actions`` object
Returns:
Tuple[Dict[str, int], float, bool, Dict] -- see ``gym.step`` method for details
Tuple[Dict[str, int], float, bool, bool, Dict]: A tuple containing:
- observation: Current environment observation.
- reward: Reward for the current step.
- terminated: Whether the episode is terminated (e.g., success or failure).
- truncated: Whether the episode was truncated (e.g., time limit exceeded).
- info: Additional debugging or episode information.
"""
if self.__world is None:
self.reset()
if self.__done:
# episode ended, undefined result
return (None, 0.0, True, {})
info = {'alive': True}
return (None, 0.0, True, False, {})
info = {'alive': True} # Initialize the info dictionary
reward = self.__agent.do(self.actions[action] if isinstance(action, int) else action)
if self.__agent.success():
self.__done = True
......@@ -85,23 +96,48 @@ class WumpusEnv(gym.Env):
if not self.__agent.isAlive:
self.__done = True
info['alive'] = False
return (self._percept_to_space(), float(reward), self.__done, info)
return (self._percept_to_space(), float(reward), self.__done, False, info)
def reset(self, *, seed: int = None, options: dict = None) -> Tuple[Dict[str,int], dict]:
"""
Farama Gymnasium ``reset`` method.
See `Gymnasium documentation <https://gymnasium.farama.org/api/env/#gymnasium.Env.reset>` for details.
def reset(self) -> Hunter.Percept:
Arguments:
seed (int, optional): Seed for the environment’s random number generator.
options (dict, optional): Additional options to specify environment behavior during reset.
Returns:
Tuple[Hunter.Percept, dict]:
- observation: The initial observation of the environment.
- info: Additional information about the environment reset.
"""
super().reset(seed=seed)
# Reset the world and agent
self.__world = copy.deepcopy(self.__initial_world)
self.__agent = next(iter(o for o in self.__world.objects if isinstance(o, Hunter)), None)
self.__done = False
return self._percept_to_space()
# Return the initial observation and an empty info dictionary
return self._percept_to_space(), {}
def render(self, mode='human'):
def render(self) -> Optional[str]:
"""
Farama Gymnasium ``render`` method.
See `Gymnasium documentation <https://gymnasium.farama.org/api/env/#gymnasium.Env.render>` for details.
Returns:
Optional[str]: A string representation of the environment if `render_mode` is 'ansi', otherwise None.
"""
if self.__world is None:
self.reset()
if mode == 'human':
if self.render_mode == 'human':
print(self.__world)
elif mode == 'ansi':
elif self.render_mode == 'ansi':
return str(self.__world)
else:
super().render(mode=mode)
NotImplementedError(f"Render mode {self.render_mode} is not supported.")
def close(self):
self.__world = None
......@@ -109,53 +145,53 @@ class WumpusEnv(gym.Env):
self.__done = False
@classmethod
def from_dict(cls, desc: Dict):
def from_dict(cls, desc: Dict, render_mode=None):
"""Creates a new WumpusEnv object from a dictionary object with the world description.
Arguments:
desc {Dict} -- the description of the Wumpus World, see ``WumpusWorld.from_JSON`` method for details
Returns:
WumpusEnv -- a new object
"""
world = WumpusWorld.from_JSON(desc)
return cls(world=world)
return cls(world=world, render_mode=render_mode)
@classmethod
def classic(cls, size: int = 4, seed=None):
def classic(cls, size: int = 4, seed=None, render_mode=None):
"""Creates a new WumpusEnv object with pits and Wumpus positions randomly generated.
Keyword Arguments:
size {int} -- the size of the grid (default: {4})
seed {int} -- seed to initialise the random generator (default: {None})
Returns:
WumpusEnv -- a new object
"""
world = WumpusWorld.classic(size=size, seed=seed)
return cls(world=world)
return cls(world=world, render_mode=render_mode)
def wumpusenv_from_dict(desc: Dict) -> WumpusEnv:
def wumpusenv_from_dict(desc: Dict, render_mode=None) -> WumpusEnv:
"""Creates a new WumpusEnv object from a dictionary object with the world description.
Arguments:
desc {Dict} -- the description of the Wumpus World, see ``WumpusWorld.from_JSON`` method for details
Returns:
WumpusEnv -- a new object
"""
return WumpusEnv.from_dict(desc=desc)
return WumpusEnv.from_dict(desc=desc, render_mode=render_mode)
def wumpusenv_classic(size: int = 4, seed=None) -> WumpusEnv:
def wumpusenv_classic(size: int = 4, seed=None, render_mode=None) -> WumpusEnv:
"""Creates a new WumpusEnv object with pits and Wumpus positions randomly generated.
Keyword Arguments:
size {int} -- the size of the grid (default: {4})
seed {int} -- seed to initialise the random generator (default: {None})
Returns:
WumpusEnv -- a new object
"""
return WumpusEnv.classic(size=size, seed=seed)
return WumpusEnv.classic(size=size, seed=seed, render_mode=render_mode)
\ No newline at end of file
File moved
File moved
......@@ -18,4 +18,4 @@ console_scripts =
gridrunner = wumpus.cli:main
[options.extras_require]
gym = gym
gymnasium = gymnasium
\ No newline at end of file
......@@ -3,4 +3,4 @@ from .player import OfflinePlayer, OnlinePlayer, UserPlayer, RandomPlayer
from .wumpus import WumpusWorld, Hunter, Wumpus, Pit, Gold, Exit
from .runner import run_episode
__version__ = '1.0.1'
__version__ = '1.0.2'