-
Notifications
You must be signed in to change notification settings - Fork 41
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
Brought code in line with PEP8, added (optional) Game World interface #8
base: master
Are you sure you want to change the base?
Conversation
…, updated demo to match.
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.
Overall, I'm not happy with the direction you're taking; I think you're adding accidental complexity. This is an educational library and it is deliberately written to be slightly more accessible to beginners at the expense of doing some things that are less advisable in bigger projects (eg. relying on global state).
That's not to say that there are not changes here that I think would be valuable - all the bugs you've found, for example.
Thank you for taking the time to submit a pull request.
import re | ||
import sys | ||
import inspect | ||
import readline |
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.
Thanks -- but there changes here at once. Please make one change at a time (perhaps in different branches/pull requests).
In particular, never make style changes at the same time as functional changes. Some of your changes are bugfixes - great. I didn't realise there are so many bugs.
However, some are unnecessary. I guess your IDE is prompting you about them, but that doesn't mean it's right.
Some changes introduce bugs - and this is the real danger of doing this kind of blanket style fixing blindly.
This is the first bug - simply importing readline enables readline semantics in input(). So removing it removes those semantics.
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.
Hm, I was not aware of that behavior with readline.
def get_terminal_size(fallback=(80, 24)): | ||
return fallback | ||
try: | ||
from backports.shutil_get_terminal_size import get_terminal_size |
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.
Good catch, this is certainly what I intended. I don't know what I was smoking when I wrote this.
|
||
|
||
__all__ = ( | ||
'when', | ||
'start', | ||
'Room', | ||
'Pattern', | ||
'_handle_command', | ||
'Item', |
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.
Pattern and _handle_command are not part of the public API - they should not be in all.
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.
Ah; it was used in test_adventurelib.py
and my IDE complained, so I added it.
@@ -47,12 +53,12 @@ class Room: | |||
@staticmethod | |||
def add_direction(forward, reverse): | |||
"""Add a direction.""" | |||
for dir in (forward, reverse): | |||
if not dir.islower(): | |||
for direc in (forward, reverse): |
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.
Please don't make this change. dir is Ok if not perfect. The dir() builtin is really rarely use in real code and it certainly isn't in this function.
Most of all, please don't touch lines of code that are unrelated to your review.
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.
Fair enough; my IDE complained and half of what I did was just trying to line the codebase up with PEP8 (I thought PEP8 said something about shadowed names, but I could be wrong)
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.
The dir() builtin is really rarely use in real code
That's so not true. You can even make stuff easier that way and even more if you go for vars() / __dict__
e.g. with requests
library -> vars(requests)[get_post_whatever_str](...)
to avoid branches.
@@ -153,7 +160,7 @@ def __contains__(self, v): | |||
if isinstance(v, str): | |||
return bool(self.find(v)) | |||
else: | |||
return set.__contains__(v) | |||
return set.__contains__(self, v) |
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.
Good catch.
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.
It was mostly PyCharm TBH
pass | ||
|
||
|
||
class TerminalInterface(Interface): |
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 do like extracting this as a class. However adventurelib.say()
should delegate to it.
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.
Ah, that's a good idea, but I'm not certain how to do that. To be clear, the reason I have it set up this way is so that other interfaces- such as an interface to a webpage or a GUI- could be made. So just always delegating to TerminalInterface would break things.
"\n" | ||
"And this is a second paragraph that is\n" | ||
"separately wrapped.\n" | ||
) | ||
|
||
|
||
def test_say_paragraph(): |
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.
Well spotted here with the name duplication, but the tests are both intended to run. One tests two separate paragraphs of one source line each, one tests one paragraph of two source lines.
If a bug is introduced in say()
, the distinction between which of these tests fails can help point to the reason.
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.
Ah, I see.
def go(direction): | ||
global current_room | ||
room = current_room.exit(direction) | ||
def go(direction, game): |
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 don't like making every function take a 'game' parameter. It's not always needed. It would be better to make game
a global object in the adventurelib
namespace; then it's an entirely optional feature.
Indeed, this is really the philosophy of adventurelib - all features are optional. Items, Rooms, say(), help(), when() - they're all capabilities to make it easier to write a program, but you don't have to use any of them if you don't want to (and they don't force themselves on you).
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.
If game
is a global then you also don't need a setup kwarg to start()
in order to be able to get hold of it prior to the game starting. You can just import it and set it up yourself, either in module scope or in a setup() function that you can re-use.
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 believe I attempted to make it so that functions without a game
parameter still work BTW, so it should still be possible to use the original design.
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 believe that much of the reasoning for me behind not making game
a global is that it allows the user to have multiple games running in the same file. While this may seem like an unnecessary feature, I can't tell you how many times I've been tripped up when I discovered that the library I'm using prevents me from having multiple of whatever-it-helps-me-to-implement in one program.
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 agree that supporting multiple games from the same file would be better for more serious applications, but for education I just don't think it's necessary.
I could imagine making global state optional, by creating classes that represent all state that is currently global, and preserving the same API by having "default" instances of those classes. But really I think the extra complexity of that is not justified by its usefulness for education.
In particular, I did think about approaches for running adventurelib games in the browser, and while a backend webapp might be best to allow multiple games in the same interpreter, my preferred approach would be #11 - get the Python code working client-side in the browser.
@@ -30,6 +32,10 @@ class InvalidCommand(Exception): | |||
"""A command is not defined correctly.""" | |||
|
|||
|
|||
class InvalidDirection(Exception): |
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.
This is a good catch; I wonder how this got omitted.
print('\n\n'.join(formatted)) | ||
|
||
|
||
class Game: |
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 broadly don't like this class. I don't like that it uses attribute access magic - and all the magic really achieves is keeping world variables separate from interface
and say
. But I don't think interface
and say
belong in this class.
What I'm saying is that a simpler class could achieve the same thing. Actually, even
class Game:
pass
is workable for eliminating some of the global
statements - and can be defined in user code.
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.
That's a good enough plan I suppose.
What's your opinion of changing |
Sorry for slow responses, @hppavilion1, I've been planning a wedding and haven't had time to go through Github PRs. |
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.
Every file is chmod
ded in git
repo to 0755
from the original 0644
which I don't think is a good idea at all. Doesn't matter whether it's Pycharm or educational purpose or whatever if the chmod doesn't have a real purpose (such as a shell script or shebang Python script or ...).
The "Game World interface" is basically just a way to not keep facts about the world in the global scope, instead using an optional
game
argument to functions that (in theory) includes data about the player and world state and stuff. Also,say
is nowgame.say
, and different input/output interfaces can be used (terminal by default, but any object that inherits fromadventurelib.Interface
and definesobj.say(string)
andobj.get_command(prompt) -> str
can be used for input/output.I also updated the demo to match, deleted unused imports, and fixed an apparent error with the fallback for getting terminal size (it was
try-except-else
, but that would mean that the fallback is used ifshutil
is present iirc; it should've beentry-except(try-except)
).