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

Instructions print via game #32

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

drewish
Copy link
Contributor

@drewish drewish commented Oct 30, 2017

This is a bit of a work in progress. I started out trying to get the Instruction objects sending their output to the game so my subclass could capture it. At that point it seemed pretty natural to try to handle a bit of #21. I didn't go all the way but it removes the need for withIO for output.

The commits should be pretty self contained so probably best to go through that each of those. If you like the direction I can keep going and try to get the input converted to an optional option to the Game constructor.

@@ -25,17 +23,17 @@ def initialize(game, filename, fh = nil)
end

# Compiles the specified game-source file, writing the resulting
# object file to stdout, whence it should be redirected into a
# object file to an IO string, whence it should be redirected into a
Copy link
Contributor Author

Choose a reason for hiding this comment

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

oh this should be IO stream

@output = options[:output] || $stdout
end

def puts *args
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This should probably be #:nodoc: right?

Copy link
Owner

Choose a reason for hiding this comment

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

I don't know about :nodoc -- I've abandoned the idea of using any of the inline documentation things, as I don't think anyone ever actually uses the resulting documents. See #19.

Copy link
Owner

@MikeTaylor MikeTaylor left a comment

Choose a reason for hiding this comment

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

Remind me, what is the difference in Ruby between making a method public (as in your commit) and making it private but the declaring it public after all (as in my original)? It's been a while since I read about how this stuff works in Ruby. Or is this just a style issue?

end

def puts *args
output.puts(*args)
Copy link
Owner

Choose a reason for hiding this comment

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

I'm not sure about overriding print in game.rb. Isn't the more honest approach you used in the previous commit also suitable here?

Copy link
Owner

Choose a reason for hiding this comment

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

Oh ... but you're not overriding print, are you? You're just defining a method with the same name as the underlying primitive that it wraps -- so the invocation changes from print(x) to @game.print(x). OK, that makes perfect sense. Carry on :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah kind of funky to have to do this but it's either this or passing the output object down to the instructions. This seemed cleaner and having it centralized would give you one place to hook in a driver.

Copy link
Owner

Choose a reason for hiding this comment

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

Yep, this is good.

Copy link
Owner

@MikeTaylor MikeTaylor left a comment

Choose a reason for hiding this comment

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

Overall, the approach for compilation looks good to me. I think that play should follow the same model, though, and use explicit input and output parameters, rather than relying on a new :output option. We want the compilation and playing APIs to be compatible, right?

game.load(IO.read("games/test/crystal.sao"))
withIO(StringIO.new("save game\nTMP"), File.new("/dev/null", "w")) do
withIO(StringIO.new("save game\nTMP"), $stdout) do
Copy link
Owner

Choose a reason for hiding this comment

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

Why are you no longer discarding the output of play here? We don't want that output, just the save file that is generated along the way.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This change I could probably back out. I'd started out trying to see if I could totally remove the need for withIO but left it here to pass in the save game command. Since withIO requires both params so I had to pass something in... the output is still being discarded though via the output string instead of /dev/null.

I think we probably could get rid of withIO here by just loading the game file into a StringIO object and then appending "save game\nTMP". I'll give it a try and see if that end up looking cleaner.

Copy link
Owner

Choose a reason for hiding this comment

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

I don't see how writing the output of play to $stdout can discard it -- surely it will appear on standard output?

(Probably the Ruby library provides a null-file object somewhere, which exists to discard output; that would really be the best thing to use here.)

You know, it's kind of cool that something as trivial as withIO lets you do so many things :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

there's nothing going to $stdout because it's getting sent to the :output stream instead. $stdout is only used when nothing is provided. another option would be to modify withIO to only assign non-nil values.

@drewish
Copy link
Contributor Author

drewish commented Nov 2, 2017

Remind me, what is the difference in Ruby between making a method public (as in your commit) and making it private but the declaring it public after all (as in my original)?

I think the end result is the same but just declaring it public to start results in less surprises. I remember reading through the code the first time and thinking "how is this working if these a private?" before coming across the access change later. Since they ended up public it seemed better to make the interface clearer from the start.

@MikeTaylor
Copy link
Owner

Cool, thanks for clarifying that. I agree, the way you have it is better.

@drewish
Copy link
Contributor Author

drewish commented Nov 2, 2017

I'm not sure if the other comments answered this or not:

Overall, the approach for compilation looks good to me. I think that play should follow the same model, though, and use explicit input and output parameters, rather than relying on a new :output option. We want the compilation and playing APIs to be compatible, right?

I'm not sure I understand this. Play should be using the :read_file and :output options. Or are you saying that the compilation should stop sending stuff to $stdout and use the :output option instead?

@MikeTaylor
Copy link
Owner

Sorry, this has got complicated because of all the different comment-threads!

All I am saying is that if the compilation API is now compile_to(stream), then surely the playing API should be play_to(stream) as well, instead of using an output stream hidden in the options block?

But I do see that there is then an asymmetry with input. But remember that :read_file is not just providing an input source: it's a bit more specific than that, it's saying "take the initial set of in-game commands from here, then continue as normal". (Its purpose is to allow you to replay partially-completed solutions, then continue from there manually.) So it's not really the counterpart of your :output.

@drewish
Copy link
Contributor Author

drewish commented Nov 4, 2017

That last comment makes sense. I hadn't really understood that read_file was just initial commands and then the input was consumed after that. With that in mind I think an :input option would make sense.

@drewish drewish force-pushed the instructions-print-via-game branch 3 times, most recently from d26898f to 8e389b5 Compare November 4, 2017 19:30
Copy link
Contributor Author

@drewish drewish left a comment

Choose a reason for hiding this comment

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

I realized I had misunderstood the last comment. I think the nicer thing about the compiler is that it's pretty distinct from the game (I'd actually make it more of an explicit split and just have the compiler take an options hash rather than relying on the game's options). If the player was also a distinct object that captured the instructions' output then having a play_with(input, output) method would make a ton of sense. But since the play methods are tangled up with the Game object I don't think it makes sense to inject the input and output through the play methods.

But even with that misunderstanding I think It was a good push to add an input option. It lets us remove the last usages of withIO.

def compile_to_stdout(filename, fh = nil)
compiler = ScottKit::Game::Compiler.new(self, filename, fh)
compiler.compile_to_stdout
def compile(out, filename, fh = nil)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not in love with this argument ordering but it felt like the least bad option considering the optional fh argument. If it was me I'd just ditch this method and let the callers instantiate and call the compiler themselves.

Copy link
Owner

Choose a reason for hiding this comment

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

If it was me I'd just ditch this method and let the callers instantiate and call the compiler themselves.

I'm not following. What would that look like?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll respond on the main thread to keep things centrally visible.

@drewish
Copy link
Contributor Author

drewish commented Nov 5, 2017

There's only a couple of places where the compiler is used, in some tests and then in the scottkit binary. So it would be simple enough to just switch those to the long way

when :play_from_source
  compiled_game = StringIO.new
  compiler = ScottKit::Game::Compiler.new(game, ARGV[0])
  if !compiler.compile_to(compiled_game)
    $stderr.puts "#$0: compilation failed: not playing"
    exitval = 1
  end
  if exitval == 0
    game.load(compiled_game.string)
    play(game)
  end
when :compile
  compiler = ScottKit::Game::Compiler.new(game, ARGV[0])
  if !compiler.compile_to($stdout)
    $stderr.puts "#$0: compilation failed"
    exitval = 1
  end

The thing that seems confusing to me about having game.compile is that it doesn't compile and load the results into the game object. The comments around the compilation methods seem to hint at this:

    # Compiles the specified game-source file, writing the resulting
    # object file to stdout, whence it should be redirected into a
    # file so that it can be played.  Yes, this API is sucky: it would
    # be better if we had a simple compile method that builds the game
    # in memory in a form that can by played, and which can then also
    # be saved as an object file by some other method -- but that
    # would have been more work for little gain.

The Game seems like it wants to be the in memory structure but compiler bypasses it in favor of its own structs.

This PR's grown big enough so I'm not looking to increase the scope any more, just pointing out things have have jumped out at me. If they're things you're interested in changing I'm happy to help.

@drewish drewish force-pushed the instructions-print-via-game branch from 8e389b5 to 78d2c29 Compare November 8, 2017 15:45
@drewish
Copy link
Contributor Author

drewish commented Nov 12, 2017

@MikeTaylor Just wanted to check in with you and see if this is something you're interested in pursuing or not. If not are there commits that you'd accept? I'm happy to just keep it in my fork but would love to land any changes that make sense to you.

@MikeTaylor
Copy link
Owner

I agree there is a issue here, but it needs thought and I won't be able to give it any for 72 hours. If you can wait, that'd be best for me.

@drewish
Copy link
Contributor Author

drewish commented Nov 12, 2017

No rush at all. Just wanted to give you an easy way to say no if it wasn’t a direction that makes sense to you.

@drewish drewish force-pushed the instructions-print-via-game branch from 78d2c29 to 7dba848 Compare December 5, 2017 03:34
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.

2 participants