Skip to content

Instantly share code, notes, and snippets.

@ohmree
Last active November 1, 2019 21:24
Show Gist options
  • Save ohmree/a37c0c492f0463b8fbc80d13adc45b55 to your computer and use it in GitHub Desktop.
Save ohmree/a37c0c492f0463b8fbc80d13adc45b55 to your computer and use it in GitHub Desktop.
HERE BE DRAGONS
#!/usr/bin/env ruby
# frozen_string_literal: true
# A direction (y,x).
# The order follows the curses convention.
module Directions
RIGHT = { y: 0, x: 1 }.freeze
LEFT = { y: 0, x: -1 }.freeze
UP = { y: 1, x: 0 }.freeze
DOWN = { y: -1, x: 0 }.freeze
end
# The main befunge interpreter class.
class Interpreter
def initialize(code = nil)
# TODO: figure out a good starting size for the stack.
@stack = Array.new(500)
@direction = Directions::RIGHT
# If no code string given, initialize with an empty
# array of 25 lines and 80 columns.
@grid = if code
self.class.grid_from_string(code)
else
Array.new(25) { Array.new(80) { ' ' } }
end
@instruction_pointer = { y: 0, x: 0 }
# A shortcut for "the character at the instruction pointer".
@cursor = @grid[0][0]
end
# Move once in the direction `@direction`.
def move
# Wrap around if exceeding 25 y or 80 x.
# From my understanding this is the
# correct behavior in Befunge 93
# but not in Befunge 98.
y_result = @instruction_pointer[:y] + @direction[:y]
y_result = 0 if y_result >= 25
x_result = @instruction_pointer[:x] + @direction[:x]
x_result = 0 if x_result >= 80
@instruction_pointer = { y: y_result, x: x_result }
@cursor = @grid[y_result][x_result]
end
# The `s` parameter is the current instance.
@instructions = {
' ' => ->(_) {},
'0' => ->(s) { s.stack.push(0) },
'1' => ->(s) { s.stack.push(1) },
'2' => ->(s) { s.stack.push(2) },
'3' => ->(s) { s.stack.push(3) },
'4' => ->(s) { s.stack.push(4) },
'5' => ->(s) { s.stack.push(5) },
'6' => ->(s) { s.stack.push(6) },
'7' => ->(s) { s.stack.push(7) },
'8' => ->(s) { s.stack.push(8) },
'9' => ->(s) { s.stack.push(9) },
'"' => lambda do |s|
until @cursor == '"'
s.move
s.stack.push(s.cursor.ord)
end
end,
',' => ->(s) { putc s.stack.pop },
# FIXME: these four are the problematic ones
'>' => ->(s) { s.direction = Directions::RIGHT },
'<' => ->(s) { s.direction = Directions::LEFT },
'v' => ->(s) { s.direction = Directions::DOWN },
'^' => ->(s) { s.direction = Directions::UP },
'@' => ->(s) { s.quit },
'*' => ->(s) { s.stack.push(s.stack.pop * s.stack.pop) }
}
# Might need to add more exit behavior later.
def quit
exit 0
end
# Step once in the direction `@direction`.
#
# If the char at the instruction pointer
# is a valid instruction, call it.
#
# We pass self as a parameter because class instance
# vars can't know about the current instance.
def step
if self.class.instructions.include? @cursor
self.class.instructions[@cursor].call(self)
end
move
end
def print
@grid.each do |ary|
puts ary.join
end
puts
puts "stack: #{@stack.join(' ')}"
end
attr_reader :direction, :grid, :stack, :cursor, :instruction_pointer
class << self
attr_reader :instructions
# Split a string of Befunge code into a 2d array for `@grid`
# TODO: pad with spaces so that lines = 25, cols = 80
# (this is part of the Befunge specification)
def grid_from_string(str)
str.split("\n").map(&:chars)
end
end
private
# We don't want the outside world
# to be able to modify the direction.
attr_writer :direction
end
code = <<~CODE.lstrip
> v
v ,,,,,"Hello"<
>48*, v
v,,,,,,"World!"<
>25*,@
CODE
# Example usage
i = Interpreter.new(code)
i.print
i.step
i.print
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment