class Sokoban::Level

Constants

DIRECTIONS

Attributes

lnum[R]
pushes[R]

Public Class Methods

new(lnum = 1) click to toggle source
# File lib/sokoban.rb, line 131
def initialize(lnum = 1)
  unless lnum.between?(1, 50)
    raise RangeError, "Level out of range. Choose 1-50"
  end
  @lnum = lnum
  @moves = []
  @level = LEVELS[@lnum - 1].ljust(19 * 16)
  @state = [@level.dup]
  @pushes = 0
end

Public Instance Methods

free_boxes() click to toggle source
# File lib/sokoban.rb, line 180
def free_boxes
  @level.scan(/o/).size
end
inspect() click to toggle source
# File lib/sokoban.rb, line 193
def inspect
  s = "Level: #{@lnum} Moves: #{moves} Pushes: #{@pushes} "
  s << @moves.join
end
load_game() click to toggle source
# File lib/sokoban.rb, line 205
def load_game
  file = Zlib::GzipReader.open(SAV_FILE) {|f| f.read }
  initialize(file.slice!(/\d+/).to_i)  # Init @lnum
  file.each_char {|c| move(c.to_sym) } # Rebuild @moves
end
moves() click to toggle source
# File lib/sokoban.rb, line 184
def moves
  @moves.size
end
parse_move(input) click to toggle source
# File lib/sokoban.rb, line 164
def parse_move(input)
  # Handle multiple moves
  input.each_char {|c| parse_move(c) } if input.size > 1
  case input.downcase
  when 'b'; $batch = ! $batch
  when 'w', 'k', '8'; move(:W) # up
  when 'a', 'h', '4'; move(:A) # left
  when 's', 'j', '2'; move(:S) # down
  when 'd', 'l', '6'; move(:D) # right
  when 'r'; initialize(@lnum)
  when 'u'; undo
  when 'q'; quit
  when "\u0003"; puts; exit # ^C - Quit without save
  end
end
play() click to toggle source
# File lib/sokoban.rb, line 142
def play
  while free_boxes > 0
    print self
    if $batch
      # Input a string terminated by newline
      parse_move(gets.strip)
    else
      # Handle single key press
      parse_move(STDIN.getch)
    end
    if $hax
      break if @moves.size > 3 # Hax
    end
  end
  print self
  print "Level #{@lnum} complete! "
  unless @lnum == 50
    print "Congratulations, on to level #{@lnum + 1}. "
  end
  STDIN.getch
end
restart(lnum) click to toggle source

The public interface to initialize()

# File lib/sokoban.rb, line 189
def restart(lnum)
  initialize(lnum)
end
to_s() click to toggle source
# File lib/sokoban.rb, line 198
def to_s
  clear_screen
  s = "Level: #{@lnum}\n\n"
  s << (0...16).map {|i| @level[i * 19, 19] }.join("\n")
  s << "\nMoves: #{moves} Pushes: #{@pushes}\n> "
end

Private Instance Methods

[](x, y) click to toggle source
# File lib/sokoban.rb, line 258
def [](x, y)
  @level[x + y * 19]
end
[]=(x, y, v) click to toggle source
# File lib/sokoban.rb, line 262
def []=(x, y, v)
  @level[x + y * 19] = v
end
box_moved?() click to toggle source
# File lib/sokoban.rb, line 231
def box_moved?
  @level.split(//).each_with_index do |char, i|
    if char =~ /o|\*/
      return true if @state.last[i] != char
    end
  end
  return false
end
clear_screen() click to toggle source
# File lib/sokoban.rb, line 218
def clear_screen
  30.times { puts }
end
find_player() click to toggle source
# File lib/sokoban.rb, line 253
def find_player
  pos = @level.index(/@|\+/)
  return pos % 19, pos / 19
end
move(direction) click to toggle source
# File lib/sokoban.rb, line 273
def move(direction)
  x, y = find_player
  dx = DIRECTIONS[direction][:x]
  dy = DIRECTIONS[direction][:y]
  dest = self[x + dx, y + dy]

  case dest
  when '#' # This is a wall, no move
    return
  when 'o', '*'
    # Check dest2 behind box, same direction times 2
    dest2 = self[x + dx * 2, y + dy * 2]
    if dest2 == ' '
      self[x + dx * 2, y + dy * 2] = 'o'
    elsif dest2 == '.'
      self[x + dx * 2, y + dy * 2] = '*'
    else # Destination is blocked, no move
      return
    end
    # Update previous box position
    dest = (dest == 'o') ? ' ' : '.'
    @pushes += 1
  end

  # Update player position
  self[x + dx, y + dy] = (dest == ' ') ? '@' : '+'
  # Update previous position
  self[x, y] = (self[x, y] == '@') ? ' ' : '.'
  @moves << direction.to_s
  # Save a copy of the level in case of undo
  @state << @level.dup
end
quit() click to toggle source
# File lib/sokoban.rb, line 240
def quit
  print "Save game? [y/n] "
  puts c = STDIN.getch
  case c
  when 'y', 'Y'
    print "Saving... "
    save_game
  end
  puts "Goodbye"
  # rescue RuntimeError in Game class
  raise RuntimeError
end
save_game() click to toggle source
# File lib/sokoban.rb, line 213
def save_game
  file = ([@lnum] << @moves).flatten.join
  Zlib::GzipWriter.open(SAV_FILE) {|f| f.write(file) }
end
undo() click to toggle source
# File lib/sokoban.rb, line 222
def undo
  if @moves.size > 0
    @moves.pop
    @state.pop
    @pushes -= 1 if box_moved?
    @level = @state.last.dup
  end
end