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

Refactor game loop to allow easy external use #16

Merged
merged 2 commits into from
Oct 24, 2017

Conversation

drewish
Copy link
Contributor

@drewish drewish commented Oct 24, 2017

This "Fixes #8" but is really just a conversation starter. It's currently rebased on #15 so we can ensure the specs pass.

Here's the hacky script I'm using so you can get and idea of how the input and output are handled:

require 'slack-ruby-client'
require 'scottkit/game'

# The game sends it output to stdout but we just capture the strings in a buffer
# and pull them out as chunks which we send in a single message. We ignore the
# play method since it blocks on `gets` and just call some methods directly.
class MyGame < ScottKit::Game
  attr_reader :output_buffer

  def initialize(options)
    @output_buffer = StringIO.new
    super
  end

  def puts *args
    output_buffer.puts *args
  end

  def print *args
    output_buffer.print *args
  end

  # Grab all the output and reset the buffer
  def prompt_for_turn
    super

    string = output_buffer.string
    output_buffer.reopen('')
    string
  end
end

Slack.configure do |config|
  config.token = ENV['SLACK_API_TOKEN']
  config.logger = Logger.new(STDOUT)
  config.logger.level = Logger::INFO
end

client = Slack::RealTime::Client.new
channel = nil

file_name = 'game.sao'
game = MyGame.new(output_buffer: StringIO.new)
game.load(IO.read(file_name))
game.decompile(StringIO.new)

client.on :hello do
  puts "Successfully connected, welcome '#{client.self.name}' to the '#{client.team.name}' team at https://#{client.team.domain}.slack.com."

  # Limit the conversation to one channel for now
  channel = client.channels.values.find { |c| c.name == 'bots' }

  game.prepare_to_play
  client.message channel: channel['id'], text: game.prompt_for_turn
end

client.on :message do |data|
  next if data.channel != channel['id']
  # TODO: Figure out what do do about starting a new game
  next if game.finished?

  client.typing channel: data.channel

  game.process_turn(data.text)
  client.message channel: data.channel, text: game.prompt_for_turn
end

client.on :close do |_data|
  puts "Client is about to disconnect"
end

client.on :closed do |_data|
  puts "Client has disconnected successfully!"
end

client.start_async

loop do
  Thread.pass
end

@drewish
Copy link
Contributor Author

drewish commented Oct 24, 2017

I went ahead and put that up at https://github.com/drewish/scottbot I'll work on getting a proper readme in there to make it a little clearer how to get it up and running.

@MikeTaylor
Copy link
Owner

It's nice to see this progressing. The changes to ScottKit itself are obviously benign, and I will merge them momentarily. A couple of questions about your client code, though ...

  • Why does MyGame need to be a subclass of ScottKit::Game? So far as I can see, it merely uses stuff from a ScottKit::Game, so delegation seems more appropriate than inheritance?

  • Why do you call game.decompile(StringIO.new)?

@MikeTaylor MikeTaylor merged commit 6070456 into MikeTaylor:master Oct 24, 2017
@drewish
Copy link
Contributor Author

drewish commented Oct 24, 2017

The only reason I'm subclassing at this point is to replace $stdout with my object. We could make that an option that defaults to the current value.

Good note on the decompile. I think it was copy pasta'ed from the play method in the binary that runs from source. I'd switched to an .sao at some point and never removed it.

@MikeTaylor
Copy link
Owner

I was going to say "the only reason ever to call decompile is if you want to get a SCK". But I see from my own bin/scottkit that that's not quite true:

def play(game)
  # Decompile (and discard result) to get entity names resolved in
  # right order.  This ensures that debugging output that uses these
  # names will use them in the same way as decompiler output.
  dummy = StringIO.new
  game.decompile(dummy)
  game.play
end

@drewish
Copy link
Contributor Author

drewish commented Oct 24, 2017

Right, that's exactly where I'd gotten it. I didn't realize it still came into play with the compiled files as well.

@MikeTaylor
Copy link
Owner

Well, it doesn't really. I should wrap that use. New issue incoming.

@drewish
Copy link
Contributor Author

drewish commented Oct 30, 2017

Just a note that this isn't capturing output from instructions so output from actions that call print will be lost. #32 starts to address this issue.

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.

Thoughts on driving the play loop externally?
2 participants