#!/usr/bin/ruby class VT102 attr_reader :cols, :rows, :x, :y def initialize(cols, rows) @cols = cols # The number of columns (x-dimension) in the screen @rows = rows # The number of rows (y-dimension) in the screen @x = 0 # The current cursor x position @y = 0 # The current cursor y position @save_x = 0 # The saved cursor x position @save_y = 0 # The saved cursor y position @parm1 = 1 # VT102 data parameter 1 @parm2 = 1 # VT102 data parameter 2 @parm1_def = 1 @mode = :text @debug_byte = 0 _clear_screen() end def _clear_screen() @screen = Array.new() (1..@rows).each {|discard| @screen.push(" " * @cols) } _set_x(0) _set_y(0) end def _reset_parms() @mode = :text @parm1 = 1 @parm2 = 1 @parm1_def = 1 end def _set_x(value) if (value < 0) @x = 0 elsif (value >= @cols) # TODO: Perhaps enable screen-scrolling when we increment Y off the bottom of the page. @x = value % @cols print_screen if $config && $config.opt["vt102_debug"] print "Warning -- Autoincrimented Y\n" _set_y(@y + (value / @cols).to_i) else @x = value end end def _set_y(value) if (value < 0) @y = 0 elsif (value >= @rows) raise "Tried to set Y past row boundary of " + @rows.to_s + " to " + value.to_s + " at debug byte " + @debug_byte.to_s + "\n" @y = @rows - 1 else @y = value end end def process(text) text.each_byte do |c| @debug_byte += 1 i = c c = c.chr case @mode when :text case c when "\e" @mode = :escape1 else # Sanity check if (@y < @rows) && (@x < @cols) if (i == 10) # Line feed _set_y(@y + 1) elsif (i == 13) # Carriage return _set_x(0) elsif (i == 8) # Backspace _set_x(@x - 1) elsif (i >= 20) # Don't display junk bytes @screen[@y][@x] = c _set_x(@x + 1) end else print "Warning -- Tried to print '" + c + "' to a bad location, " + x.to_s + ", " + y.to_s + "\n" end end when :escape1 case c when "[" # Terminal movement/coloring commands @mode = :escape2 when "(" # Charset command @mode = :charset _reset_parms() when ")" # Charset command @mode = :charset _reset_parms() when ">" # not sure what this does...? _reset_parms() when '=' # not sure what this does either...? _reset_parms() else print "Got weird escaped character " + i.to_s + " that looks like '" + c.to_s + "' at " + @debug_byte.to_s + "\n" _reset_parms() end when :charset case c when "0" _reset_parms() when "B" _reset_parms() else print "Got weird charset character " + i.to_s + " that looks like '" + c.to_s + "' at " + @debug_byte.to_s + "\n" end when :escape2 case c when "0".."9" # Parameter data if (@parm1_def == 1) @parm1 = c.to_i @parm1_def = 0 else @parm1 = 10*@parm1 + c.to_i end when ";" # Next parameter # TODO: When we want to support coloring information, we may have to support up to 3 parameters. Use an array? @parm2 = @parm1 @parm1 = 1 @parm1_def = 1 when 'H' # Move cursor to position _set_x(@parm1 - 1) # parm1 is the second in the list, parm2 actually came first in the byte stream. _set_y(@parm2 - 1) _reset_parms() when 'A' # Move cursor up _set_y(@y - @parm1) _reset_parms() when 'B' # Move cursor down _set_y(@y + @parm1) _reset_parms() when 'C' # Move cursor right _set_x(@x + @parm1) _reset_parms() when 'D' # Move cursor left _set_x(@x - @parm1) _reset_parms() when 'J' # Erase screen #if (@parm1 == 2) # complete display... _set_x(0) _set_y(0) _clear_screen() #else # print "Unknown clear request..." #end _reset_parms() when 'K' # Erase to end of current line case @parm1 when 1 # right @screen[@y][@x..@cols] = " " * (@cols-@x) when 0 # left #NOTE: Is this right??? All I see dGameLaunch sending is K without a parameter. @screen[@y][0..@x] = " " * (@x + 1) when 2 # full line @screen[@y] = " " * @cols end _reset_parms() when 's' # Saved current cursor position # NOTE: This is part of the ANSI standard, not VT100 or VT102. It should never be used in Nethack, but we're including it for completeness. @save_x = @x @save_y = @y _reset_parms() when 'u' # Recall previously saved cursor position # NOTE: This is part of the ANSI standard, not VT100 or VT102. It should never be used in Nethack, but we're including it for completeness. _set_x(@save_x) _set_y(@save_y) _reset_parms() when 'h' # Coloring _reset_parms() when 'm' # dGameLaunch seems to also use "m" as opposed to just the ansi standard "h". Wierd. # TODO: Implement this. For now, just ignore it. #foreach (int p in parmList) # if (p == 0) # currentMessageColor = new jtbvMessageColor(); # else if ((p >= 1) && (p <= 8)) # currentMessageColor.Attr = (jtbvTextAttrib) p; # else if ((p >= 30) && (p <= 38)) # currentMessageColor.FGColor = (jtbvTextColor) p - 30; # else if ((p >= 40) && (p <= 48)) # currentMessageColor.BGColor = (jtbvTextColor) p - 40; # else # Console.WriteLine("Un-understood ANSI coloring option: " + p.ToString()); #end _reset_parms() when '?' # Set various options... not sure how to handle... _reset_parms() when 'r' # not sure what this does...? _reset_parms() when 'l' # not sure what this does...? _reset_parms() when 'd' # Set row??? _set_y(@parm1) _reset_parms() else if (i == 10) # Carriage return _set_x(0) elsif (i == 13) # Line feed _set_y(@y + 1) else raise "Unknown cmd '" + c + "' (" + i.to_s + ") in mode: " + @mode.to_s + " at " + @debug_byte.to_s end end end end end def row_text(row) String.new(@screen[row]) end def print_screen() print "*** Begin screen dump ***\n" print " 12345678901234567890123456789012345678901234567890123456789012345678901234567890\n" print "|" + @screen.join("|\n|") + "|\n" print " 12345678901234567890123456789012345678901234567890123456789012345678901234567890\n" print "**** End screen dump ****\n" end def process_file(filename) print "*** Running test with " + filename + " ***\n" File.new(filename).each_byte do |char| char = char.chr process(char) #print_screen() #print "Processed up through debug byte " + @debug_byte.to_s + "\n" @screen.each do |r| assert_eq("Row length remains the same", r.length, 80) end end print "* Finished file process *\n" end end if __FILE__ == $0 require "../utils/assert.rb" screen = VT102.new(80, 24) assert_eq("VT102 initialized with null bytes", screen.row_text(0), " " * 80) assert_eq("VT102 initialized with null bytes, using row_text", screen.row_text(0), " " * 80) assert_eq("Row length remains the same", screen.row_text(0).length, 80) screen.process("foo") assert_eq("handles plaintext", screen.row_text(0), "foo" + " " * 77) assert_eq("Row length remains the same", screen.row_text(0).length, 80) screen.process("bar") assert_eq("handles more plaintext", screen.row_text(0), "foo" + "bar" + " " * 74) assert_eq("Row length remains the same", screen.row_text(0).length, 80) screen.process("\e[Bbaz") assert_eq("handles \\e[B #1", screen.row_text(0), "foo" + "bar" + " " * 74) assert_eq("handles \\e[B #2", screen.row_text(1), " " * 6 + "baz" + " " * 71) assert_eq("Row length remains the same", screen.row_text(0).length, 80) screen.process("\e[0;33;6m") assert_eq("ignores \\e[...m", screen.row_text(1), " " * 6 + "baz" + " " * 71) assert_eq("Row length remains the same", screen.row_text(0).length, 80) screen.process("\e") screen.process("[") screen.process("B") screen.process("hehe!") assert_eq("handles information across multiple calls to process", screen.row_text(2), " " * 9 + "hehe!" + " " * 66) assert_eq("Row length remains the same", screen.row_text(0).length, 80) screen.process("\e[2Bmore!") assert_eq("handles \\e2B", screen.row_text(4), " " * 14 + "more!" + " " * 61) assert_eq("Row length remains the same", screen.row_text(0).length, 80) screen.process("\e[2J") assert_eq("handles \\e[2J (clear screen)", screen.row_text(0) + screen.row_text(1), " " * 160) assert_eq("Row length remains the same", screen.row_text(0).length, 80) screen.process("fooba\e[sr") assert_eq("handles saving cursor position", screen.row_text(0), "foobar" + " " * 74) assert_eq("Row length remains the same", screen.row_text(0).length, 80) screen.process("\e[uz") assert_eq("handles loading cursor position", screen.row_text(0), "foobaz" + " " * 74) assert_eq("Row length remains the same", screen.row_text(0).length, 80) screen.print_screen() screen._clear_screen() screen.process_file("test_small.ttydump") screen.print_screen() puts "unit test succeeded" end