-
Notifications
You must be signed in to change notification settings - Fork 191
Glulx game logger #19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 2 commits
66d1776
f8de962
336354b
2522015
862cba1
ad262f8
a4ca143
eb4e0c5
5c498d5
2cd2d92
1c99564
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +0,0 @@ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| # Copyright (c) Microsoft Corporation. All rights reserved. | ||
| # Licensed under the MIT license. | ||
|
|
||
| import json | ||
|
|
||
| from typing import Tuple, List, Optional, Iterable, Union, Sized, Any, Mapping | ||
|
|
@@ -9,20 +10,21 @@ | |
|
|
||
|
|
||
| class GameLog: | ||
| """ | ||
| GameLog object. Allows you to load and save previous game logs. | ||
| """ | ||
| def __init__(self): | ||
| """ | ||
| GameLog object. Allows your to load and save previous game logs. | ||
| """ | ||
| self._logs = [[]] | ||
| self._current_game = self._logs[-1] | ||
| self._filename = '' | ||
|
|
||
| def __getitem__(self, idx: int) -> list: | ||
| """ | ||
| """A | ||
|
||
| Gets a particular game log at index idx. | ||
|
|
||
| Args: | ||
| idx: index to retrieve | ||
| Returns: | ||
|
|
||
| Returns: list of logs at idx | ||
|
|
||
| """ | ||
| assert idx <= len(self._logs) | ||
|
|
@@ -35,63 +37,69 @@ def __len__(self) -> int: | |
| def current_game(self) -> list: | ||
| """ | ||
| Gets current game we're logging. | ||
|
|
||
| Returns: list of logs from current game. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is a newline after |
||
| """ | ||
| return self._current_game | ||
| return self._logs[-1] | ||
|
|
||
| @property | ||
| def logs(self) -> list: | ||
| """ | ||
| Get all logs from all games. | ||
|
|
||
| Returns: All logs from all games. | ||
| """ | ||
| return self._logs | ||
|
|
||
| def new_game(self): | ||
| def new_game(self) -> list: | ||
| """ | ||
| Start logs for a new game. | ||
|
|
||
| Returns: log object for current game. | ||
| """ | ||
| if len(self._current_game) > 0: | ||
| if len(self.current_game) > 0: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you need this check?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need this check to see if the current game is already new. We initialize our logs with a game inside, so there would be a redundancy if we didn't do this check. |
||
| self._logs.append([]) | ||
| self._current_game = self._logs[-1] | ||
| return self._current_game | ||
| return self.current_game | ||
|
|
||
| def set(self, key: Any, value: Any) -> None: | ||
| """ | ||
| Sets value for latest game | ||
|
|
||
| Args: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There should be a blank line between the description and the |
||
| key: Key to set | ||
| value: Value to set | ||
|
|
||
| """ | ||
| current = self._current_game[-1] | ||
| current = self.current_game[-1] | ||
| current[key] = value | ||
|
|
||
| def append_optional(self, value: Any) -> None: | ||
| """ | ||
| Appends optional information to current game | ||
|
|
||
| Args: | ||
| value: Value to append | ||
| value: Value to append. Must be JSON serializable. | ||
|
|
||
| """ | ||
| current = self._current_game[-1] | ||
| current = self.current_game[-1] | ||
| if 'optional' not in current: | ||
| current['optional'] = [] | ||
| current['optional'].append(value) | ||
|
|
||
| def add_log(self, log: Mapping): | ||
| """ | ||
| Adds a new log to our logs | ||
|
|
||
| Args: | ||
| log: Mapping of a log | ||
|
|
||
| """ | ||
| self._current_game.append(log) | ||
| self.current_game.append(log) | ||
|
|
||
| def save(self, filename): | ||
| def save(self, filename: str) -> None: | ||
| """ | ||
| Save current logs to specified file name | ||
|
|
||
| Args: | ||
| filename: File path to save to (should have JSON extension) | ||
|
|
||
|
|
@@ -103,39 +111,42 @@ def save(self, filename): | |
| except TypeError as e: | ||
| raise TypeError('Log not serializable') | ||
|
|
||
| def load(self, filename): | ||
| def load(self, filename: str) -> None: | ||
| """ | ||
| Loads a JSON object as logs | ||
|
|
||
| Args: | ||
| filename: file path to load. | ||
|
|
||
| """ | ||
| self._filename = filename | ||
| with open(filename) as f: | ||
| self._logs= json.load(f) | ||
| self._logs = json.load(f) | ||
|
|
||
|
|
||
| class GlulxLogger(Wrapper): | ||
| def __init__(self, env: GitGlulxMLEnvironment) -> None: | ||
| """ | ||
| Wrap around a TextWorld GitGlulxML environment to provide logging capabilities. | ||
| """ | ||
| Wrapper around a TextWorld GitGlulxML environment to provide logging capabilities. | ||
|
|
||
| Args: | ||
| env: The GitGlulxML environment to wrap. | ||
| """ | ||
| Args: | ||
| env: The GitGlulxML environment to wrap. | ||
| """ | ||
| def __init__(self, env: GitGlulxMLEnvironment) -> None: | ||
| super().__init__(env) | ||
| self.activate_state_tracking() | ||
|
|
||
| self.serialized_game = env.game.serialize() | ||
| self._gamefile = env.gamefile | ||
|
|
||
| self._logs = GameLog() | ||
| self._game_log = GameLog() | ||
|
|
||
| def step(self, command: str) -> Tuple[GlulxGameState, float, bool]: | ||
| """ | ||
| Take a step in the environment. | ||
|
|
||
| Args: | ||
| command: input string for taking an action | ||
|
|
||
| Returns: | ||
| GlulxGameState, score and done. | ||
| """ | ||
|
|
@@ -150,72 +161,74 @@ def step(self, command: str) -> Tuple[GlulxGameState, float, bool]: | |
| new_log['description'] = game_state.description | ||
| new_log['inventory'] = game_state.inventory | ||
| new_log['state'] = game_state.state.serialize() | ||
| self._logs.add_log(new_log) | ||
| self._game_log.add_log(new_log) | ||
|
|
||
| return game_state, score, done | ||
|
|
||
| def reset(self) -> GameState: | ||
| """ | ||
| Reset the environment. | ||
| Adds a new game into the logs. | ||
|
|
||
| Returns: | ||
| GameState | ||
| """ | ||
| new_log = {} | ||
| self._logs.new_game() | ||
| self._game_log.new_game() | ||
|
|
||
| game_state = super().reset() | ||
| new_log['optional'] = [] | ||
| new_log['done'] = False | ||
| new_log['description'] = game_state.description | ||
| new_log['inventory'] = game_state.inventory | ||
| new_log['state'] = game_state.state.serialize() | ||
| self._logs.add_log(new_log) | ||
| self._game_log.add_log(new_log) | ||
|
|
||
| return game_state | ||
|
|
||
| def add_commands(self, commands: List[str], scores: Optional[Union[Iterable[float], Sized]]=None) -> None: | ||
| def add_commands(self, commands: List[str], scores: Optional[Iterable[float]]=None) -> None: | ||
| """ | ||
| Add custom commands to the logger. Optionally add scores for each command. | ||
|
|
||
| Args: | ||
| commands: A list of commands. | ||
| scores: scores for each command. Must be same size as commands if provided. | ||
|
|
||
| """ | ||
| if scores is not None: | ||
| self._logs.set('command_scores', scores) | ||
| self._game_log.set('command_scores', scores) | ||
|
|
||
| self._logs.set('commands', commands) | ||
| self._game_log.set('commands', commands) | ||
|
|
||
| def add(self, info: Any) -> None: | ||
| """ | ||
| Add any additional information you want to log. | ||
|
|
||
| Args: | ||
| info: Additional information to log for the current game state. | ||
| """ | ||
| self._logs.append_optional(info) | ||
| self._game_log.append_optional(info) | ||
|
|
||
| @property | ||
| def current(self) -> Mapping: | ||
| """ | ||
| Returns: | ||
| Current game state logs. | ||
| """ | ||
| return self._logs.current_game[-1] | ||
| return self._game_log.current_game[-1] | ||
|
|
||
| @property | ||
| def logs(self) -> List[Mapping]: | ||
| """ | ||
| Returns: List of all logs from this game. | ||
| """ | ||
| return self._logs.current_game | ||
| return self._game_log.current_game | ||
|
|
||
| @property | ||
| def all_logs(self) -> GameLog: | ||
| """ | ||
| Returns: GameLog object containing all logs. | ||
| """ | ||
| return self._logs | ||
| return self._game_log | ||
|
|
||
| @property | ||
| def gamefile(self) -> str: | ||
|
|
@@ -228,41 +241,25 @@ def gamefile(self) -> str: | |
| def __getitem__(self, index: int) -> Mapping: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure we need this anymore. |
||
| """ | ||
| Get a certain log at a given index. | ||
|
|
||
| Args: | ||
| index: index of log to get. | ||
|
|
||
| Returns: | ||
| log at index | ||
| """ | ||
| assert index <= len(self._logs) | ||
| assert index <= len(self._game_log) | ||
|
|
||
| return self._logs.current_game[index] | ||
| return self._game_log.current_game[index] | ||
|
|
||
| def __str__(self) -> str: | ||
| return str(self._logs.current_game) | ||
| return str(self._game_log.current_game) | ||
|
|
||
| def serialize(self) -> List[Mapping]: | ||
| """ | ||
| Get serialized mappings of logs. | ||
|
|
||
| Returns: | ||
| List of serialized mappings. | ||
| """ | ||
| return self._logs.logs | ||
|
|
||
| def save(self, filename) -> None: | ||
| """ | ||
| Saves all logs given a filename | ||
| Returns: None | ||
| """ | ||
| self._logs.save(filename) | ||
|
|
||
| def load(self, filename) -> None: | ||
| """ | ||
| Loads logs from a file | ||
| Args: | ||
| filename: | ||
| string representing file location | ||
| Returns: None | ||
| """ | ||
| self._logs.load(filename) | ||
|
|
||
|
|
||
| return self._game_log.logs | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need a blank space after the license.