diff --git a/bin/scottkit b/bin/scottkit index 789cd33..5da3529 100755 --- a/bin/scottkit +++ b/bin/scottkit @@ -17,7 +17,6 @@ $VERBOSE = true require 'optparse' require 'stringio' require 'scottkit/game' -require 'scottkit/withio' def play(game) @@ -110,19 +109,17 @@ exitval = 0 game = ScottKit::Game.new(options) case mode when :play_from_source - f = StringIO.new - withIO(nil, f) do - if !game.compile_to_stdout(ARGV[0]) - $stderr.puts "#$0: compilation failed: not playing" - exitval = 1 - end + compiled_game = StringIO.new + if !game.compile(compiled_game, ARGV[0]) + $stderr.puts "#$0: compilation failed: not playing" + exitval = 1 end if exitval == 0 - game.load(f.string) + game.load(compiled_game.string) play(game) end when :compile - if !game.compile_to_stdout(ARGV[0]) + if !game.compile($stdout, ARGV[0]) $stderr.puts "#$0: compilation failed" exitval = 1 end diff --git a/lib/scottkit/compile.rb b/lib/scottkit/compile.rb index a66c0ac..4fd2d4f 100644 --- a/lib/scottkit/compile.rb +++ b/lib/scottkit/compile.rb @@ -5,8 +5,6 @@ module ScottKit class Game class Compiler - private - # Creates a new compiler for the specified game, set up ready to # compile the game in the specified file. # @@ -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 stream, 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. # - def compile_to_stdout + def compile_to(output) begin tree = parse - generate_code(tree) + generate_code(tree, output) true rescue return false if String($!) == "syntax error" @@ -81,6 +79,8 @@ def parse items, actions, verbgroups, noungroups) end + private + def parse_room match :room name = match :symbol @@ -139,7 +139,7 @@ def parse_occur end conds, instructions, comment = parse_actionbody - CAction.new(nil, chance, conds, instructions, comment) + CAction.new(nil, chance, conds, instructions, comment) end def parse_actionbody @@ -207,7 +207,7 @@ def match(token, estr = nil); @lexer.match token, estr; end def error(str); @lexer.error str; end - def generate_code(tree) + def generate_code(tree, output) lintOptions = @game.options[:lint] || '' @had_errors = false rooms = tree.rooms @@ -267,7 +267,7 @@ def generate_code(tree) dead_ends = [] rooms.each.with_index do |room, index| next if index == 0 - next if + next if if room.exits.length == 0 no_exits.push room.name elsif room.exits.length == 1 @@ -439,76 +439,76 @@ def generate_code(tree) return if @had_errors # Write header - puts tree.unknown1 || 0 - puts items.size-1 - puts actions.size-1 - puts verbs.size-1 - puts rooms.size-1 - puts tree.maxload || -1 - puts startindex - puts items.select { |x| x.desc[0] == "*" }.count - puts tree.wordlen - puts tree.lighttime || -1 - puts messages.size-1 - puts treasuryindex - puts # Blank line + output.puts tree.unknown1 || 0 + output.puts items.size-1 + output.puts actions.size-1 + output.puts verbs.size-1 + output.puts rooms.size-1 + output.puts tree.maxload || -1 + output.puts startindex + output.puts items.select { |x| x.desc[0] == "*" }.count + output.puts tree.wordlen + output.puts tree.lighttime || -1 + output.puts messages.size-1 + output.puts treasuryindex + output.puts # Blank line # Actions actions.each do |action| - print 150*action.verb + action.noun, " " + output.print 150*action.verb + action.noun, " " - print action.conds.map { |x| String(x[0] + 20 * x[1]) + " " }.join - print action.gathered_args.map { |x| String(20*x) + " " }.join + output.print action.conds.map { |x| String(x[0] + 20 * x[1]) + " " }.join + output.print action.gathered_args.map { |x| String(20*x) + " " }.join nconds = action.conds.size + action.gathered_args.size raise "condition has #{nconds} conditions" if nconds > 5 - (5-nconds).times { print "0 " } + (5-nconds).times { output.print "0 " } ins = action.instructions.map { |x| x[0] } (4-ins.count).times { ins << 0 } - puts "#{150*ins[0] + ins[1]} #{150*ins[2] + ins[3]}\n" + output.puts "#{150*ins[0] + ins[1]} #{150*ins[2] + ins[3]}\n" end - puts # Blank line + output.puts # Blank line # Vocab verbs.each.with_index do |verb, i| - puts "\"#{verb}\" \"#{nouns[i]}\"" + output.puts "\"#{verb}\" \"#{nouns[i]}\"" end - puts # Blank line + output.puts # Blank line # Rooms rooms.each do |room| 0.upto(5).each do |i| exit = room.exits[@game.dirname(i)] - print(exit ? exit : 0, " ") + output.print(exit ? exit : 0, " ") end - print "\"#{room.desc}\"\n" + output.print "\"#{room.desc}\"\n" end - puts # Blank line + output.puts # Blank line # Messages messages.each do |message| - puts "\"#{message}\"\n" + output.puts "\"#{message}\"\n" end - puts # Blank line + output.puts # Blank line # Items items.each do |item| desc = item.desc desc += "/" + item.called.upcase[0, @wordlen] + "/" if item.called - puts "\"#{desc}\" #{item.where}" + output.puts "\"#{desc}\" #{item.where}" end - puts # Blank line + output.puts # Blank line # Action comments actions.each do |action| - puts "\"#{action.comment || ""}\"\n" + output.puts "\"#{action.comment || ""}\"\n" end - puts # Blank line + output.puts # Blank line # Trailer - puts tree.version || 0 - puts tree.ident || 0 - puts tree.unknown2 || 0 + output.puts tree.version || 0 + output.puts tree.ident || 0 + output.puts tree.unknown2 || 0 end def room_by_name(loc, roommap) @@ -560,10 +560,6 @@ def gwarning(str) 0 end - public :compile_to_stdout # Must be visible to Game.compile() - public :parse # Used by test_compile.rb - - CGame = Struct.new(:ident, :version, :unknown1, :unknown2, :start, :treasury, :maxload, :wordlen, :lighttime, :lightsource, :rooms, :items, diff --git a/lib/scottkit/game.rb b/lib/scottkit/game.rb old mode 100755 new mode 100644 index 7130fdf..c5a0db1 --- a/lib/scottkit/game.rb +++ b/lib/scottkit/game.rb @@ -20,11 +20,12 @@ class Game attr_reader :flags, :counters, :saved_rooms, :noun #:nodoc: attr_accessor :loc, :counter, :saved_room, :lampleft #:nodoc: - private + attr_reader :input, :output # Creates a new game, with no room, items or actions -- load must - # be called to make the game ready for playing, or - # compile_to_stdout can be called to generate a new game-file. + # be called to make the game ready for playing, or compile can be + # called to generate a new game-file. + # # The options hash affects various aspects of how the game will be # loaded, played and compiled. The following symbols are # recognised as keys in the options hash: @@ -44,10 +45,18 @@ class Game # file to restore before starting to play. # # [+read_file+] If specified, the name of a file of game - # commands to be run after restoring s saved + # commands to be run after restoring a saved # game (if any) and before starting to read # commands from the user. # + # [+output+] If specified, a writable IO stream for + # capturing the game's output. Defaults to + # $stdout if nothing is provided. + # + # [+input+] If specified, a readable IO stream for + # capturing the game's input. Defaults to + # $stdin if nothing is provided. + # # [+echo_input+] If true, then game commands are echoed # before being executed. This is useful # primarily if input is being redirected @@ -106,8 +115,25 @@ def initialize(options) @options = options @rooms, @items, @actions, @nouns, @verbs, @messages = [], [], [], [], [], [] + @input = options[:input] || $stdin + @output = options[:output] || $stdout + end + + def puts *args + output.puts(*args) + end + + def print *args + output.print(*args) end + # Print debugging output + def dputs(level, *args) #:nodoc: + super.puts args.map { |x| "# #{x}" } if @options[level] + end + + private + # Virtual accessor def dark_flag #:nodoc: @flags[FLAG_DARK] @@ -261,10 +287,6 @@ def restore(name) @items.each { |item| item.loc = f.gets.to_i } end - def dputs(level, *args) #:nodoc: - puts args.map { |x| "# #{x}" } if @options[level] - end - # 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 @@ -283,14 +305,12 @@ def dputs(level, *args) #:nodoc: # function is that its behaviour is influenced by the game's # options.) # - 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) + ScottKit::Game::Compiler.new(self, filename, fh).compile_to(out) end - public :load, :compile_to_stdout # Must be visible to driver program + public :load, :compile # Must be visible to driver program public :roomname, :itemname # Needed by Condition.render() - public :dputs # Needed for contained classes' debugging output public :dirname # Needed by compiler public :dark_flag= # Invoked from Instruction.execute() diff --git a/lib/scottkit/play.rb b/lib/scottkit/play.rb index c3d1421..5cf3abe 100644 --- a/lib/scottkit/play.rb +++ b/lib/scottkit/play.rb @@ -93,14 +93,15 @@ def process_lighting end end - # Get a line from @fh if defined, otherwise $stdin + # Get a line from @fh if defined, otherwise input def gets line = nil if (@fh) line = @fh.gets + # Clear the read_file handle when all the input is consumed. @fh = nil if !line end - line = $stdin.gets if !line + line = input.gets if !line return nil if !line puts line if @fh || options[:echo_input] line @@ -411,15 +412,15 @@ def execute(args) if (@op == 0) return false # shouldn't happen elsif (@op <= 51) - puts @game.messages[@op].gsub('`', '"') + @game.puts @game.messages[@op].gsub('`', '"') return false elsif (@op >= 102) - puts @game.messages[@op-50].gsub('`', '"') + @game.puts @game.messages[@op-50].gsub('`', '"') return false else case @op when 52 then if @game.ncarried == @game.maxload - puts "I've too much to carry!" + @game.puts "I've too much to carry!" else @game.items[args.shift].loc = ROOM_CARRIED end @@ -432,7 +433,7 @@ def execute(args) when 59 then @game.items[args.shift].loc = ROOM_NOWHERE when 60 then @game.flags[args.shift] = false when 61 then - puts "I am dead."; @game.flags[15] = false; + @game.puts "I am dead."; @game.flags[15] = false; @game.loc = @game.rooms.size-1; @game.need_to_look when 62 then i = args.shift; @game.items[i].loc = args.shift when 63 then @game.finish(0) @@ -457,7 +458,7 @@ def execute(args) @game.items[i1].loc = @game.items[i2].loc when 76 then @game.need_to_look when 77 then @game.counter -= 1 - when 78 then print @game.counter, " " + when 78 then @game.print @game.counter, " " when 79 then @game.counter = args.shift when 80 then @game.loc, @game.saved_room = @game.saved_room, @game.loc when 81 then which = args.shift @@ -465,9 +466,9 @@ def execute(args) @game.counters[which], @game.counter when 82 then @game.counter += args.shift when 83 then @game.counter -= args.shift - when 84 then print @game.noun - when 85 then puts @game.noun - when 86 then puts + when 84 then @game.print @game.noun + when 85 then @game.puts @game.noun + when 86 then @game.puts when 87 then which = args.shift @game.loc, @game.saved_rooms[which] = @game.saved_rooms[which], @game.loc diff --git a/lib/scottkit/withio.rb b/lib/scottkit/withio.rb deleted file mode 100644 index e35e388..0000000 --- a/lib/scottkit/withio.rb +++ /dev/null @@ -1,15 +0,0 @@ -# Provides a utility method withIO() used by several test-cases. Runs -# the specified block with stdin and stdout replumbed to the provided -# file-handles; the old values of stdin and stdout are passed to the -# block, in case they should be needed. - -def withIO(newin, newout) - old_STDIN = $stdin - old_STDOUT = $stdout - $stdin = newin - $stdout = newout - yield old_STDIN, old_STDOUT -ensure - $stdin = old_STDIN - $stdout = old_STDOUT -end diff --git a/scottkit.gemspec b/scottkit.gemspec index 803dd3f..eb23821 100644 --- a/scottkit.gemspec +++ b/scottkit.gemspec @@ -99,7 +99,6 @@ Gem::Specification.new do |s| "lib/scottkit/decompile.rb", "lib/scottkit/game.rb", "lib/scottkit/play.rb", - "lib/scottkit/withio.rb", "scottkit.gemspec", "test/test_canonicalise.rb", "test/test_compile.rb", @@ -107,7 +106,6 @@ Gem::Specification.new do |s| "test/test_play.rb", "test/test_playadams.rb", "test/test_save.rb", - "test/withio_test.rb" ] s.homepage = "http://github.com/MikeTaylor/scottkit".freeze s.licenses = ["GPL-2.0".freeze] diff --git a/test/test_canonicalise.rb b/test/test_canonicalise.rb index 79d5a26..c6c5438 100644 --- a/test/test_canonicalise.rb +++ b/test/test_canonicalise.rb @@ -1,7 +1,6 @@ require 'test/unit' require 'scottkit/game' require 'stringio' -require 'scottkit/withio' # The idea here is that we can start with a source file and compile it # once. Decompiling the result will of course yield a different @@ -17,7 +16,7 @@ def test_1canonicalise_tutorials def test_2canonicalise_crystal canonicalise("crystal/crystal.sck", :source) end - + def test_3canonicalise_adams %w{ adv01 @@ -44,7 +43,7 @@ def test_3canonicalise_adams x[0] = "" options[:bug_tolerant] = true end - canonicalise("adams/#{x}.dat", :object, options) + canonicalise("adams/#{x}.dat", :object, options) end end @@ -76,12 +75,10 @@ def canonicalise(name, type, options = {}) def compile(source, options) game = ScottKit::Game.new(options) - f = StringIO.new - withIO(nil, f) do - game.compile_to_stdout(nil, StringIO.new(source)) or - raise "couldn't compile" - end - f.string + output = StringIO.new + game.compile(output, nil, StringIO.new(source)) or + raise "couldn't compile" + output.string end def decompile(object, options) diff --git a/test/test_compile.rb b/test/test_compile.rb index 5bb3d29..5560103 100644 --- a/test/test_compile.rb +++ b/test/test_compile.rb @@ -1,7 +1,6 @@ require 'test/unit' require 'scottkit/game' require 'stringio' -require 'scottkit/withio' class TestCompile < Test::Unit::TestCase #:nodoc: EXPECTED = [ @@ -44,11 +43,9 @@ def test_parser def test_code_generator game = ScottKit::Game.new({}) - f = StringIO.new - withIO(nil, f) do - game.compile_to_stdout("games/test/crystal.sck") or - raise "couldn't compile crystal.sck" - end - assert_equal(f.string, File.read("games/test/crystal.sao")) + compiled_game = StringIO.new + game.compile(compiled_game, "games/test/crystal.sck") or + raise "couldn't compile crystal.sck" + assert_equal(compiled_game.string, File.read("games/test/crystal.sao")) end end diff --git a/test/test_play.rb b/test/test_play.rb index 4602b33..0ca5f95 100644 --- a/test/test_play.rb +++ b/test/test_play.rb @@ -1,20 +1,19 @@ require 'test/unit' require 'scottkit/game' require 'stringio' -require 'scottkit/withio' class TestPlay < Test::Unit::TestCase #:nodoc: def test_play_tutorial7; play("t7"); end def test_play_crystal; play("crystal"); end def play(name) - game = ScottKit::Game.new({ :read_file => - "games/test/#{name}.solution", - :random_seed => 12368, :no_wait => true }) + game = ScottKit::Game.new(read_file: "games/test/#{name}.solution", + output: StringIO.new, random_seed: 12368, + no_wait: true) game.load(IO.read("games/test/#{name}.sao")) - f = StringIO.new - withIO(nil, f) { game.play } + game.play + expected = File.read("games/test/#{name}.transcript") - assert_equal(expected, f.string) + assert_equal(expected, game.output.string) end end diff --git a/test/test_playadams.rb b/test/test_playadams.rb index de091c4..4efbcd5 100644 --- a/test/test_playadams.rb +++ b/test/test_playadams.rb @@ -2,7 +2,6 @@ require 'scottkit/game' require 'stringio' require 'digest/md5' -require 'scottkit/withio' class TestPlayAdams < Test::Unit::TestCase #:nodoc: def test_play_adams @@ -15,13 +14,12 @@ def play(name) gamefile = "games/adams/#{name}.dat" if(File::readable?(gamefile)) - game = ScottKit::Game.new({ :read_file => - "games/test/adams/#{name}.solution", - :random_seed => 12368, :no_wait => true }) + game = ScottKit::Game.new(output: StringIO.new, + read_file: "games/test/adams/#{name}.solution", + random_seed: 12368, no_wait: true ) game.load(IO.read gamefile) - f = StringIO.new - withIO(nil, f) { game.play } - digest = Digest::MD5.hexdigest(f.string) + game.play + digest = Digest::MD5.hexdigest(game.output.string) expected = File.read("games/test/adams/#{name}.transcript.md5").chomp assert_equal(expected, digest) else diff --git a/test/test_save.rb b/test/test_save.rb index ff7693e..a6e174e 100644 --- a/test/test_save.rb +++ b/test/test_save.rb @@ -1,30 +1,28 @@ require 'test/unit' require 'scottkit/game' require 'stringio' -require 'scottkit/withio' class TestSave < Test::Unit::TestCase #:nodoc: # Can't use a setup() method here as the two test-cases need the # games to be initialised with different options. def test_save_crystal - game = ScottKit::Game.new({ :random_seed => 12368, :echo_input => true }) + game = ScottKit::Game.new(random_seed: 12368, echo_input: true, + input: File.new("games/test/crystal.save-script"), + output: StringIO.new) game.load(IO.read("games/test/crystal.sao")) - withIO(File.new("games/test/crystal.save-script"), - File.new("/dev/null", "w")) do - game.play() - end + game.play() assert_equal(File.read("TMP"), File.read("games/test/crystal.save-file")) File.unlink "TMP" end def test_resave_crystal - game = ScottKit::Game.new({ :random_seed => 12368, :echo_input => true, - :restore_file => "games/test/crystal.save-file" }) + game = ScottKit::Game.new(random_seed: 12368, echo_input: true, + input: StringIO.new("save game\nTMP"), + output: StringIO.new, + restore_file: "games/test/crystal.save-file") game.load(IO.read("games/test/crystal.sao")) - withIO(StringIO.new("save game\nTMP"), File.new("/dev/null", "w")) do - game.play() - end + game.play() assert_equal(File.read("TMP"), File.read("games/test/crystal.save-file")) File.unlink "TMP" end diff --git a/test/withio_test.rb b/test/withio_test.rb deleted file mode 100755 index 3f2b6c9..0000000 --- a/test/withio_test.rb +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/local/bin/ruby -w - -# Run as: echo Bart Lisa | ./withio_test.rb && echo == && cat /tmp/xcv -# Expecting the output: -# first line is 'Bart Lisa' -# saved line 'Eric' -# died in withIO: divided by 0 -# saved line was 'Eric' -# == -# first line was 'Bart Lisa' -# oldout is of class IO - -require 'stringio' -require 'scottkit/withio' - -saved = nil -begin - line = gets - puts "first line is '#{line.chomp}'" - withIO(StringIO.new("Eric\nthe"), File.new("/tmp/xcv", "w")) do - |oldin, oldout| - puts "first line was '#{line.chomp}'" - puts "oldout is of class #{oldout.class}" - saved = gets - oldout.puts "saved line '#{saved.chomp}'" - puts 1/0 - end -rescue Exception => e - puts "died in withIO: #{e}" -end -puts "saved line was '#{saved.chomp}'"