Skip to content
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

Glulx game logger #19

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open

Glulx game logger #19

wants to merge 11 commits into from

Conversation

taodav
Copy link
Contributor

@taodav taodav commented Aug 15, 2018

Working game logger for the GitGlulxMLGameState.

A few things I've been thinking about for this feature:

  • Maybe we should allow the user to save the logs somewhere with a .save() method that saves to a JSON file?
  • Are the tests enough?

@taodav taodav requested a review from MarcCote August 15, 2018 18:08
@MarcCote
Copy link
Contributor

@taodav you are right the GameLogger class should implement a save and load (to/from JSON file).

@taodav
Copy link
Contributor Author

taodav commented Aug 15, 2018

@MarcCote what would the load function do exactly? Only load all the logs?

Copy link
Contributor

@MarcCote MarcCote left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't checked the unit tests yet.

def reset(self) -> GameState:
"""
Reset the environment.
Also clears logs.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't we say we are keeping multiple trajectories? Calling reset should append to a list of logs.

Reset the environment.
Also clears logs.
"""
self._logs = []
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Member variables should be first declared in the __init__. That way, one can easily see what are the member variables at first glance. It also helps minimizing side-effects to some extent (e.g. logger.logs would fail before a call to env.reset() is made!).

def add_commands(self, commands: List[str], scores: Optional[Union[Iterable[float], Sized]]=None) -> None:
"""
Add custom commands to the logger. Optionally add scores for each command.
:param commands:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are using Google docstring standard now (https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html). See, core.py for examples on how to format them.

assert len(scores) == len(commands)
command_mapping = {a: p for a, p in zip(commands, scores)}

self._current['command_distribution'] = command_mapping
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't know if the scores are normalized, so I would avoid using the term distribution. Also, if scores is None, then it's not a mapping. We could simply store commands and scores separately and it would be up to the viewer/user to deal with it accordingly.

Get all logs
:return: List of all logs
"""
logs = self._logs[:]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the purpose of this line? Make a shallow copy? If so, why do you append to it before returning it?

return logs

@property
def gamefile(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you need this method. If I remember correctly, Wrappers instances automatically call method of the unwrapped environment if not defined/overwritten in the wrapper.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry what do you mean exactly? I don't see this in the Wrapper class.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevermind, I was thinking of some unmerged code.


if index < len(self._logs) - 1:
return self._logs[index]
return self._current
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is _current not always the last element of _logs?

Get serialized mappings of logs.
:return: List of serialized mappings.
"""
return self.logs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing newline at the end of the file (PEP8).

@MarcCote
Copy link
Contributor

@taodav good point. I guess we should have a separate GameLogger (or another name) class which is saveable/loadable. Then, the wrapper logger environment would just populate a given GameLogger object. That way we would have a logger object that can be loaded separately from the wrapper.

@tavianator
Copy link
Contributor

@MarcCote Perhaps GameLog for the log itself?

@MarcCote
Copy link
Contributor

@tavianator Haha, that was too obvious. Thanks 👍

@taodav
Copy link
Contributor Author

taodav commented Aug 20, 2018

@MarcCote refactored the game logs into a separate class! Also fixed the comments you mentioned above.

Copy link
Contributor

@MarcCote MarcCote left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some minor comments.

@@ -0,0 +1 @@

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revert unneeded change.

@@ -0,0 +1,268 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT license.
import json
Copy link
Contributor

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.

class GameLog:
def __init__(self):
"""
GameLog object. Allows your to load and save previous game logs.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

your -> you
Also, put this docstring at the class level instead of the init method.

GameLog object. Allows your to load and save previous game logs.
"""
self._logs = [[]]
self._current_game = self._logs[-1]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No needed, see my comments on current_game property.

Gets current game we're logging.
Returns: list of logs from current game.
"""
return self._current_game
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of having a private variable _current_game simple make this property return return self._logs[-1]


return game_state

def add_commands(self, commands: List[str], scores: Optional[Union[Iterable[float], Sized]]=None) -> None:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the Union? Isn't Iterable[float] enough?

self.serialized_game = env.game.serialize()
self._gamefile = env.gamefile

self._logs = GameLog()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's called this variable self._game_log

"""
Appends optional information to current game
Args:
value: Value to append
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe mention value needs to be JSON serializable.

"""
return self._gamefile

def __getitem__(self, index: int) -> Mapping:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure we need this anymore.

"""
return self._logs.logs

def save(self, filename) -> None:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need save/load/serialize anymore. Users can call save directly on the gamelog object, i.e. env.game_log.save()

@taodav
Copy link
Contributor Author

taodav commented Aug 21, 2018

@MarcCote fixed all the above comments!

Copy link
Contributor

@MarcCote MarcCote left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tavianator do you think we could set up something like https://codecov.io/ to report code coverage and make sure it is high enough when accepting a PR?

self._filename = ''

def __getitem__(self, idx: int) -> list:
"""
"""A
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Returns: log object for current game.
"""
if len(self._current_game) > 0:
if len(self.current_game) > 0:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you need this check?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

@MarcCote
Copy link
Contributor

Also, @tavianator did you say you had an idea for solving the urllib3.exceptions.ProtocolError: ('Connection aborted.', ConnectionResetError(104, 'Connection reset by peer')) issue we have sometimes when running Travis.

@tavianator
Copy link
Contributor

@MarcCote I'll look into codecov.io. In the meantime you can check the Travis logs, they still print out that coverage table at the end.

I'm not sure about the connection reset error, but I'll take a look.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants