Skip to content

Instantly share code, notes, and snippets.

@fowlmouth
Last active December 16, 2015 08:08
Show Gist options
  • Save fowlmouth/5403274 to your computer and use it in GitHub Desktop.
Save fowlmouth/5403274 to your computer and use it in GitHub Desktop.
SNUSP Needed a stACK
esolangs.org /==.==<==\
/wiki/SNUSP | |
/+++++++++++==&\==>===?!/==<<==#
\+++++++++++\ |
$==>==+++++++++++/ \==>==,==#
/ 'B' @=@@=@@++++#
// / 'u' @@@@@=@+++++#
// // / 'z' @=@@@@+@++++#
// // // / 'i' @@@@@@=+++++#
// // // // / 'F' @@@=@@+++++#
// // // // // / NL ++++++++++#
// // // // // // / 100 @@@=@@@=++++#
$@/>@/>@/>@/>@/>@/>@/\ 0
/ / http://rosettacode.org/wiki/FizzBuzz
! /======= Fizz <<<.<.<..>>>#
/ | \
\?!#->+ @\.>?!#->+ @\.>?!#->+@/.>\ |
/ ! ! ! / |
\?!#->+ @\.>?!#->+@\ .>?!#->+@/.>\ |
/ ! \!===\! ! / |
\?!#->+ @\.>?!#->+ @\.>?!#->+@/.>\ |
/ ! ! | ! / |
\?!#->+@\ .>?!#->+ @\.>?!#->+@/.>\ |
/ \!==========!===\! ! / |
\?!#->+ @\.>?!#->+ @\.>?!#->+@/>>@\.>/
! | | |
/==========/ \========!\=== Buzz <<<<<<<.>.>..>>>#
|
\!/=dup==?\>>@\<!/back?\<<<#
\<<+>+>-/ | \>+<- /
|
/======================/
|
| /recurse\ #/?\ zero
\print=!\@\>?!\@/<@\.!\-/
| \=/ \=itoa=@@@+@+++++#
! /+ !/+ !/+ !/+ \ mod10
/<+> -\!?-\!?-\!?-\!?-\!
\?!\-?!\-?!\-?!\-?!\-?/\ div10
# +/! +/! +/! +/! +/
class GosuInput
def initialize
@t = Gosu::TextInput.new
@i = 0
end
def open?() $window.text_input == @t end
def open
$window.text_input = @t
end
def hasInput?
@i < @t.text.size
end
def text; @t.text end
def readChar
r = text[@i]
@i += 1
r
end
def writeChar c; nil; end
end
class OutputHandler
attr_accessor :file
def initialize file
@file = file
end
def open?; !file.closed? end
def open; file.open unless open? end
def writeChar c; file.write c end
end
class GosuOutput
attr_reader :font, :text,:pos
def initialize font, text=''
@font=font
@pos = Vec.new(10,10)
@text = text
end
def pos=(p) @pos = Vec.new(p.x,p.y) end
def draw
h = font.height
text.split(?\n).each_with_index do |line, i|
font.draw line, pos.x, pos.y + i * h, 0
end
end
def writeChar c; text << c; end
def open()end
end
%w/gosu ostruct optparse/.each &method(:require)
include Gosu
file = nil
playfieldSize = nil
mode = :SNACK
ipf = 1
font = './LiberationMono-Regular.ttf'
autostart = false
opars = OptionParser.new do|o|
o.on '-f', '--file FILE', 'File to load' do|f|
unless File.exists? f
raise "File does not exist: #{f}"
else
file = f
end
end
o.on '--autostart', 'hmm' do
autostart = true
end
o.on '--font F', "specify a MONOSPACE font. (default: #{font})" do|f|
font = f
end
o.on '--ipf NUM', "specify the instructions per frame. (default: #{ipf})" do |i|
ipf = i.to_i
end
o.on '-s', '--size X,Y', 'Size of the field' do|s|
playfieldSize = Vec.new(*s.scan(/(\d+),(\d+)/).flatten.map(&:to_i))
end
o.on '--screen SIZEX,SIZEY', 'Size of the screen' do|s|
ScreenW, ScreenH = *s.scan(/(\d+),(\d+)/).flatten.map(&:to_i)
end
o.on '--fullscreen', 'Run fullscreen' do
FullScreen = true
end
o.on '-m', '--mode MODE', 'Set the operating mode (SNUSP or SNACK)' do|m|
case m
when /snusp/i
mode = :SNUSP
when /snack/i
mode = :SNACK
else
puts "Invalid mode `#{m}`"
end
end
o.on '-h', '--help' do
puts o
puts <<-eof
Use the mouse!
There are buttons.
Mouse over a cell and
right click to shift through the operations
type a character to set the cell
eof
exit
end
end
ScreenW = 640
ScreenH = 480
FullScreen = false
PlayfieldFontHeight = 16
PlayfieldFontWidth = 8
OPS = [
?$, ?\\, ?/, ?!, ??, ?#, ?@, ?&,
?a,?s,?m,?d,?e,
?0,?1,?2,?3,?4,?5,?6,?7,?8,?9,
?:,?;,?~,?`,?,,
' '
]
KeyboardToOp = { #US-QWERTY biased, sorry world
true => { #shifting
KbSemicolon => ?:, KbBacktick => ?~,
Kb1 => ?!, Kb2 => ?@, Kb3 => ?#, Kb4 => ?$,
Kb7 => ?&, Kb8 => ?*,
KbEqual => ?a,KbComma => ?<,KbPeriod => ?.,
KbSlash => ??,
}, false => {
KbSlash => ?/, KbBackslash => ?\\,
KbBacktick=>?`,
Kb0 => ?0,Kb1 => ?1,Kb2 => ?2,Kb3 => ?3,Kb4 => ?4,
Kb5 => ?5,Kb6 => ?6,Kb7 => ?7,Kb8 => ?8,Kb9 => ?9,
KbMinus=> ?s,
KbEqual => ?=,
KbComma => ?,,
KbSemicolon => ?;,
KbSpace => ' ', }
}
KeyboardToOp.each_value.each_with_object({
KbNumpad0 => ?0,KbNumpad1 => ?1,KbNumpad2 => ?2,KbNumpad3 => ?3,
KbNumpad4 => ?4,KbNumpad5 => ?5,KbNumpad6 => ?6,KbNumpad7 => ?7,
KbNumpad8 => ?8,KbNumpad9 => ?9,
KbNumpadAdd => ?a,KbA => ?a,
KbNumpadSubtract=> ?s,KbS => ?s,
KbNumpadDivide=>?d,KbD => ?d,
KbNumpadMultiply=>?m,KbM=>?m,
KbZ => ?$,
KbC => ' '
}, &:merge!)
class Space
attr_accessor :w,:h,:lines,:entrypoints
def initialize opts = {}
if opts[:str]
self.lines = opts[:str]
self.w = lines[0].size
self.h = lines.size
elsif opts[:w] and opts[:h]
self.w = opts[:w]
self.h = opts[:h]
@lines = Array.new(h) do ' '*w end
scan
else
raise "What do?"
end
end
def lines= v
v = v.split(?\n) if v.is_a? String
#dont #kill the empty lines
#v = v.map{|_| _.chomp!; _.size == 0 ? nil : _}
# .compact
#make the fat lines feel less disgusting
max = v.max_by(&:size).size
@lines = v.map{ |_| _ << (' ' * (max - _.size)) }
scan
end
def scan
#scan for entrypoints
self.entrypoints = []
lines.each_with_index do |str, y|
x = -1
loop do
x = str.index(?$, x+1)
break unless x
entrypoints << [x, y]
end
end
end
def [] x, y
lines[y][x]
end
def []= x, y, v
lines[y][x] = v
end
end
class Vec
attr_accessor :x,:y
def initialize x,y
@x,@y = x,y
end
def to_a; [x,y] end
def [] i; to_a[i] end
end
class Frame < Struct.new(:ip, :dir)
def initialize ip,dir=:right
self.ip = ip
self.dir = dir
end
def dup() Frame.new(ip, dir) end
def ip=(v) super(Vec.new(*v)) end
end
Direction2Velocity = {
up: [0, -1], down: [0, 1],
left:[-1, 0], right:[1, 0],
nw: [-1,-1], ne: [1,-1],
sw: [-1, 1], se: [1, 1]}
Deflection = {
'/' => { up: :right, left: :down, down: :left, right: :up,
nw: :se, se: :nw,
ne: :ne, sw: :sw },
?\\ => { up: :left, left: :up, down: :right, right: :down,
nw: :nw, se: :se,
ne: :sw, sw: :ne } }
class Actor < Struct.new(:frame, :callstack, :stack)
def initialize at
self.frame = at.is_a?(Actor) ?
at.frame.dup :
Frame.new(at, :right)
self.stack = []
self.callstack = []
end
def pushframe
callstack << frame.dup
end
def popframe ## returns true if out of callframes
r = callstack.size > 0 ?
(self.frame = callstack.pop; false) :
true
advance!
r
end
def push v; stack << v end
def pop; stack.pop || 0 end
def peek; stack.last end
def spawn() self.class.new(self) end
def deflect! op
frame.dir = Deflection[op][frame.dir]
end
def advance!
d2v = Direction2Velocity[frame.dir]
frame.ip.x += d2v[0]
frame.ip.y += d2v[1]
end
def x() frame.ip.x end
def y() frame.ip.y end
def x=(v) frame.ip.x = v end
def y=(v) frame.ip.y = v end
def dir() frame.dir end
def dir=(v) frame.dir = v end
end
class Game < Struct.new(:space, :bounded_mode, :actors, :state, :last_exit)
attr_reader :bounds,:inputHandler,:outputHandler
def initialize space, bounded_mode = :toroidal
self.space = space
self.bounded_mode = bounded_mode
refresh_actors
self.last_exit = 0
@inputHandler = GosuInput.new
@outputHandler = GosuOutput.new($window.pf)
post_init
end
def post_init() end
def refresh_actors
space.scan
self.actors = space.entrypoints.map{ |(x, y)|
Actor.new [x,y]
}
end
def remove_actor a
self.last_exit = a.pop
actors.delete a
end
def space= v
super v
update_bounds
end
def update_bounds
@bounds = Rect.new(0,0,space.w-1,space.h-1)
end
def self.load str, bounds = :toroidal
new Space.new(str: str), bounds
end
def tick
i = 0
new_actors = nil
while i < actors.size
act = actors[i]
op = space[act.x, act.y]
died = false
case op
when ?/, ?\\
act.deflect! op
when ?!
act.advance!
when ??
act.advance! if act.pop != 0
when ?#
died = act.popframe
when ?@
act.pushframe
when ?a
act.push act.pop+act.pop
when ?s # 1 2 - #=> 1 - 2
b = act.pop
act.push act.pop - b
when ?m
act.push act.pop * act.pop
when ?d # 1 2 / #=>
b = act.pop
act.push act.pop / b
when ?&
act.advance!
(new_actors ||= []) << act.spawn
when ?:
act.push act.peek
when ?;
act.push act.stack[-1 - act.pop]
when ?~
a = act.pop
b = act.pop
act.push b, a
when ?`
n = act.pop
a = act.pop
act.push act.stack[-n]
act.stack[-1 - n] = a
when ?,
n = act.pop
act.stack[- n] = act.pop
when ?<
b = act.pop
act.push(act.pop < b ? 1 : 0 )
when ?>
b = act.pop
act.push(act.pop > b ? 1 : 0 )
when ?=
act.push(act.pop == act.pop ? 1 : 0)
when ?0..?9
act.push op.to_i
end
act.advance!
if died or !still_alive(act)
remove_actor act
else
i += 1
end
end
new_actors && (self.actors += new_actors)
end
def still_alive actor
case bounded_mode
when :toroidal
if actor.x > space.w-1
actor.x = 0
elsif actor.x < 0
actor.x = space.w - 1
end
if actor.y > space.h - 1
actor.y = 0
elsif actor.y < 0
actor.y = space.h - 1
end
true
when :deadly
@bounds.include?(actor.x,actor.y)
end
end
def info
actors.size == 0 ?
last_exit.to_s :
[actors.first.stack.to_s, actors.first.callstack.to_s]
end
def w() space.w end
def h() space.h end
def incr_w
space.w += 1
space.lines.each {|_|_<<' '}
update_bounds
end
def incr_h
space.h += 1
space.lines << ' '*space.w
update_bounds
end
def decr_w
space.w -= 1
space.lines.map! {|_| _[0, space.w]}
update_bounds
end
def decr_h
space.h -= 1
space.lines.pop
update_bounds
end
end
Rect = Struct.new(:x,:y,:w,:h) do
def includes? x,y
x >= self.x and
x <= self.x+w and
y >= self.y and
y <= self.y+h
end
alias include? includes?
end
def button(font, text, x,y, &blck)
b = OpenStruct.new({
font: font, text: text,
pos: Vec.new(x,y), onclick: blck,
bounds: nil,
fX: 1, fY: 1 })
def b.draw(z)
font.draw text, *pos, z
end
def b.clicked?(x,y)
bounds.includes?(x,y) ?
(onclick.call rescue nil; true) :
false
end
def b.update_pos x,y
pos.x += x
pos.y += y
update_bounds
end
def b.set_pos x,y
pos.x = x
pos.y = y
update_bounds
end
def b.update_bounds
self.bounds = Rect.new(*pos, font.text_width(text, fX), PlayfieldFontHeight)
end
b.update_bounds
b
end
class GameWindow < Gosu::Window
attr_reader :game,:pf
def initialize(file, playfieldSize, game_mode, font, ipf, autostart)
super ScreenW, ScreenH, FullScreen
$window = self
@ipf = ipf
@pf = Gosu::Font.new self, font,
PlayfieldFontHeight
cls = game_mode == :SNUSP ? SNUSP : Game
@game = \
if file
cls.load(File.read(file))
elsif playfieldSize
cls.new(Space.new(w: playfieldSize[0], h: playfieldSize[1]))
else
cls.load '
$\\
0
1
@
\\ #
# '
end
@state = autostart ? :run : :pause
@buttons = []
x, y = ScreenW - 100, 10
add_button @pf, "Step", x, y, &method(:single_step)
y += PlayfieldFontHeight
add_button @pf, "Refresh actors", x, y do
game.refresh_actors
end
y += PlayfieldFontHeight
b = add_button @pf, "#{other_state}", x, y do
toggle_state
b.text = "#{other_state}"
end
if file
y += PlayfieldFontHeight
add_button @pf, "Save file #{file}", x, y do
File.open(file, "w+") do |f|
game.space.lines.each do |_|
f.puts _
end
end
end
y += PlayfieldFontHeight
add_button @pf, "Load file #{file}", x, y do
game.space = Space.new str: File.read(file)
set_buttons
end
end
@width_buttons = [
add_button(@pf, "", 0,0),
add_button(@pf, "inc", 0,0){ game.incr_w; set_buttons },
add_button(@pf, "dec", 0,0){ game.decr_w; set_buttons }
]
@height_buttons = [
add_button(@pf, "", 0,0),
add_button(@pf, "inc", 0,0){ game.incr_h; set_buttons },
add_button(@pf, "dec", 0,0){ game.decr_h; set_buttons }
]
set_buttons
def @buttons.draw(z = 0)
each_with_index do|b, i| b.draw z+i end
end
def @buttons.click(mx,my)
each do |b| break if b.clicked?(mx, my) end
end
end
def other_state() @state == :pause ? :run : :pause end
def toggle_state() @state = other_state end
def set_buttons
x, y = PlayfieldFontWidth*(1+ game.w), 0
@width_buttons[0].text = "#{game.w} wide"
@width_buttons.each do |b|
b.set_pos x, y
x += b.bounds.w + 5
end
x, y = 0, PlayfieldFontHeight*(1+ game.h)
@height_buttons[0].text = "#{game.h} high"
@height_buttons.each do |b|
b.set_pos x, y
x += b.bounds.w + 5
end
end
def add_button *args,&b
@buttons << button(*args,&b)
@buttons.last
end
def single_step; @ipf.times do game.tick end end
def button_down id
mx, my = mouse_x, mouse_y
active_tile = [(mx / PlayfieldFontWidth).to_i, (my / PlayfieldFontHeight).to_i]
game.bounds.include?(*active_tile) || active_tile = nil
case id
when MsLeft
@buttons.click mx,my
when KbSpace
single_step
when MsRight
if active_tile
op = game.space[*active_tile]
op = OPS[ ((OPS.index(op) || -1) + 1) % OPS.size]
game.space[*active_tile] = op
end
when KbP
require'pry'
binding.pry
when KbR
game.refresh_actors
when KbEscape
close
else
if active_tile
op = KeyboardToOp[button_down?(KbLeftShift)][id]
game.space[*active_tile] = op if op && OPS.include?(op)
end
end
end
def update
single_step if @state == :run
end
def needs_cursor?() true end
def draw
game.space.lines.each_with_index do |line, y|
@pf.draw line, 0, y*PlayfieldFontHeight, 0
end
game.actors.each do |a|
@pf.draw \
'O',
a.x * PlayfieldFontWidth,
a.y * PlayfieldFontHeight,
0
end
info = Vec.new(0, ScreenH - PlayfieldFontHeight)
[*game.info].reverse.each do |i|
@pf.draw i, *info, 0
info.y -= PlayfieldFontHeight
end
@buttons.draw
#draw_grid
end
GridColor = Gosu::Color::GRAY
def draw_grid
(0 .. ScreenW-1).step(PlayfieldFontWidth).each do |x|
draw_line x, 0, GridColor, x, ScreenH, GridColor
end
(0 .. ScreenH-1).step(PlayfieldFontHeight).each do |y|
draw_line 0, y, GridColor, ScreenW, y, GridColor
end
end
end
require_relative 'SNUSP'
require_relative 'input_handler'
if __FILE__==$0
ov, $VERBOSE = $VERBOSE, nil
opars.parse!
$VERBOSE = ov
GameWindow.new(file, playfieldSize, mode, font, ipf, autostart).show
end
class SNUSP_Actor < Actor
attr_accessor :ptr
def initialize at
super
@ptr = at.is_a?(SNUSP_Actor) ?
at.ptr.dup :
Vec.new(0,0)
end
end
class Mem
def initialize w=1, h=1
@mem = Array.new(h) do
[0] * w end
end
def [](x, y) @mem[y][x] end
def []=(x, y, v) @mem[y][x] = v end
def validate x,y
#stretches the membranes to fit your configuration,
#returns a better pointer
r = Vec.new(x,y)
if y >= @mem.size
(@mem.size - x).times do @mem << [0] end
elsif y < 0
r.y = 0
end
if x >= @mem[y].size
@mem[y] += [0]*(x - @mem[y].size + 1)
elsif x < 0
r.x = 0
end
r
end
end
class SNUSP < Game
attr_reader :mem
def post_init
self.bounded_mode = :deadly
inputHandler.open
outputHandler.open
@mem = Mem.new
end
def refresh_actors()
#only the first $ foun
super
self.actors = [actors.empty? ?
SNUSP_Actor.new([0,0]) :
SNUSP_Actor.new(actors.first) ]
puts actors.inspect
end
def tick
i = 0
new_actors = nil
while i < actors.size
act = actors[i]
op = space[act.x, act.y]
died,blocked = false,false
case op
when ?, # READ
blocked = !inputHandler.hasInput?
mem[*act.ptr] = inputHandler.readChar.ord \
unless blocked
when ?. # WRITE
outputHandler.writeChar mem[*act.ptr].chr
when ?% # RAND
mem[*act.ptr] = (rand * (mem[*act.ptr]+1)).to_i
when ?< # LEFT
movePtrX act, -1
when ?> # RIGHT
movePtrX act, 1
when ?: # up
movePtrY act, -1
when ?; # down
movePtrY act, 1
when ?+ # INCR
mem[*act.ptr] += 1
when ?- # DECR
mem[*act.ptr] -= 1
when ?@ # ENTER
act.pushframe
when ?# # LEAVE
died = act.popframe
when ?& # SPAWN
act.advance!
(new_actors ||= []) << act.spawn
when ?/, ?\\ # LURD,RULD
act.deflect! op
when ?! # SKIP
act.advance!
when ?? # SKIPZ
act.advance! if mem[*act.ptr] == 0
end
act.advance! unless blocked
if died or !still_alive(act)
remove_actor act
else
i += 1
end
end
self.actors += new_actors if new_actors
end
def info
"Output: #{outputHandler.text}".split(?\n) +
"Input: #{inputHandler.text}".split(?\n)
end
def movePtrX act, by
# act.ptr.x += by
# mem.validate *act.ptr
act.ptr = mem.validate act.ptr.x+by, act.ptr.y
end
def movePtrY act, by
# act.ptr.y += by
# mem.validate *act.ptr
act.ptr = mem.validate act.ptr.x, act.ptr.y+by
end
end
SNUSP-inspired
$ actor start point
/ deflection
\ deflection
! move forward
? move forward if POP != 0
# pop frame (leave function), kills the actor if the callstack is empty
@ move forward and push frame (enter function)
& move forward and spawn a new actor
stack ops:
a - add
s - sub
m - mul
d - div
= - ==
< - less than
> - less than
0-9 - push a number
: - dup
; - dup the Nth stack item
~ - swap the top two stack items
` - swap the Nth stack item
, - n = POP; stack[n] = POP
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment