--------------070207070000060105060509
Content-Type: text/plain; charset=us-ascii; format=flowed
Content-Transfer-Encoding: 7bit

Florian Gross wrote:

> Ruby Quiz wrote:
> 
>> This week's quiz is to implement the game of Sokoban with the 
>> interface of your
>> choosing and any extra features you would like to have.
> 
> 
> Interesting. In fact I did a cross between Sokoban and Dr. Mario (a nice 
> variation of Tetris) for this year's first Ludum Dare 48 hour game 
> development contest.

And here's my solution to this quiz. I used Ruby/Gosu again. It's very 
simplistic and doesn't even include a turn counter, but it should still 
be playable and work correctly.

You can use cursor left / right / up / down to move. ESC restarts the 
current level.

Windows executable: http://noegnud.sourceforge.net/.flgr/sokoban.zip
Linux package: http://noegnud.sourceforge.net/.flgr/sokoban.tar.gz

I've also attached the source code to this mail for convenience.

--------------070207070000060105060509
Content-Type: text/plain;
 nameame.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filenameame.rb"

require 'gosu'

include Gosu

module ZLevel
  FloorFirst, DropZoneFirst, WallFirst  , 4, 8
  TileSpan  

  Player, Boulder  2, 13
end

class Game < Window
  def initialize
    @screen_width, @screen_height  00, 600
    @scrolling  .95
    super(@screen_width, @screen_height, false, 20)
    self.caption  Sokoban"

    @images  ash.new do |hash, key|
      hash[key]  mage.load_tiles(self, "media/#{key}.png", 50, 50, false)
    end

    @level_number  

    @deforms  ash.new do |hash, key|
      zlevel  and(ZLevel::TileSpan)
      zoom_x  and / 5 + 1.05
      zoom_y  and / 5 + 1.05
      angle  and(4) * 90 + rand(10) - 5
      hash[key]  zlevel, angle, zoom_x, zoom_y]
    end

    load_levels
    reload_level
  end

  def load_levels
    @levels  ile.read("media/levels.txt").split(/^$/).map do |data|
      player  il

      y  
      tiles  ata.split("\n").map do |line|
        if x  ine.index(/[@+]/) then
          player  x, y]
        end

        y + 
        line.tr("@+", " .").split(//)
      end

      Level.new(tiles, player)
    end
  end

  def next_level
    @level_number + 
    reload_level
  end

  def reload_level
    @level  arshal.load(Marshal.dump(@levels[@level_number]))
    @view_x  level.px * 50 - @screen_width / 2
    @view_y  level.py * 50 - @screen_height / 2
    self.close unless @level
    @deforms.clear
  end

  def update
    next_level if @level.finished?
  end

  def set_view(x, y)
    new_view_x   - @screen_width / 2
    new_view_y   - @screen_height / 2

    of, nf  scrolling, 1.0 - @scrolling
    @view_x  @view_x * of + new_view_x * nf).round
    @view_y  @view_y * of + new_view_y * nf).round
  end

  def draw
    set_view(@level.px * 50, @level.py * 50)

    # Draw map
    @level.tiles.each_with_index do |line, ty|
      line.each_with_index do |tile, tx|
        ti  ile_to_index(tile)
        x, y  x * 50 + 25, ty * 50 + 25
        zoff, angle, zoom_x, zoom_y  @deforms[x + y << 3]

        zlevel  off + case tile_to_index(tile)
          when 0 then ZLevel::WallFirst
          when 1 then ZLevel::FloorFirst
          when 2 then ZLevel::DropZoneFirst
        end

        dx, dy   - @view_x / 2, y - @view_y / 2

        @images["tiles"][ti].draw_rot(dx, dy, zlevel, angle,
          0.5, 0.5, zoom_x, zoom_y)

        if @level.boulder?(tx, ty) then
          @images["objects"][1].draw_rot(dx, dy, ZLevel::Boulder)
        end
      end
    end

    # Draw player
    dx, dy  level.px * 50 - @view_x / 2, @level.py * 50 - @view_y / 2
    @images["objects"][0].draw(dx, dy, ZLevel::Player)
  end

  def tile_to_index(tile)
    "# .".index(tile.tr("*o", ". "))
  end

  def button_down(button_id)
    case button_id
      when Button::KbLeft then @level.go_left
      when Button::KbRight then @level.go_right
      when Button::KbUp then @level.go_up
      when Button::KbDown then @level.go_down
      when Button::KbEscape then reload_level
    end
  end
end

class Level
  attr_reader :tiles, :width, :height

  def initialize(tiles, player)
    @tiles, @player  iles, player
    @height  iles.size * 50
    width  iles.map { |line| line.size }.max
    @tiles.map! do |line|
      line + Array.new(width - line.size) { " " }
    end
    @width  idth * 50
  end

  def boulder?(x, y) "o*".index(@tiles[y][x]) end
  def wall?(x, y) "#".index(@tiles[y][x]) end
  def solid?(x, y) wall?(x, y) or boulder?(x, y) end
  def free?(x, y) not solid?(x, y) end

  def finished?
    @tiles.all? do |line|
      not line.any? do |tile|
        "o.".index(tile)
      end
    end
  end

  def px() @player[0] end
  def py() @player[1] end

  def free_boulder(bx, by)
    @tiles[by][bx]  ase @tiles[by][bx]
      when "o" then " "
      when "*" then "."
    end
  end

  def put_boulder(bx, by)
    @tiles[by][bx]  ase @tiles[by][bx]
      when " " then "o"
      when "." then "*"
    end
  end

  def move_boulder(bx, by, vx, vy)
    nx, ny  x + vx, by + vy
    if free?(nx, ny) then
      free_boulder(bx, by)
      put_boulder(nx, ny)
      return true
    end
    return false
  end

  def move_player(vx, vy)
    nx, ny  x + vx, py + vy
    pp  ambda { |l,y| p [l,y]; y }
    success  free?(nx, ny) or
      (boulder?(nx, ny) and move_boulder(nx, ny, vx, vy)))

    if success then
      @player  nx, ny]
      return true
    end
  end

  def go_left()  move_player(-1, 0) end
  def go_right() move_player(+1, 0) end
  def go_up()    move_player(0, -1) end
  def go_down()  move_player(0, +1) end
end

Game.new.show

--------------070207070000060105060509--