--Multiparthu__4_Nov_2004_01_16_41_+0100_5zxzf2Ra/yLOJ
Content-Type: text/plain; charset=US-ASCII
Content-Transfer-Encoding: 7bit

Again, very late, but here is my solution. A central game core and two front ends, a curses one and and FXRuby one.

Thomas Leitner


--Multiparthu__4_Nov_2004_01_16_41_+0100_5zxzf2Ra/yLOJ
Content-Type: text/x-ruby;
 name
urses.rb" Content-Disposition: attachment; filename
urses.rb" Content-Transfer-Encoding: 8bit require 'curses' require 'sokoban' sokoban okoban.new sokoban.load_levels( File.read( 'sokoban_levels.txt' ) ) puts "Welcome to Curses-Sokoban!" print "Select the level (0..#{sokoban.levels.length-1}): " sokoban.select_level( gets.to_i ) Curses::init_screen Curses::noecho width okoban.cur_level.map.width + 4 height okoban.cur_level.map.height + 4 win urses::Window.new( height, width, (Curses::lines - height) / 2 , (Curses::cols - width) / 2 ) win.box( ?|, ?- ) win.keypad rue begin y sokoban.cur_level.map.each_row do |item| win.setpos( y, 2 ) win.addstr( item.pack('C*') ) y + end win.refresh char in.getch case char when ?w, Curses::Key::UP then sokoban.cur_level.move( :up ) when ?s, Curses::Key::DOWN then sokoban.cur_level.move( :down ) when ?a, Curses::Key::LEFT then sokoban.cur_level.move( :left ) when ?d, Curses::Key::RIGHT then sokoban.cur_level.move( :right ) end end while char ! q && !sokoban.cur_level.level_finished? win.close Curses::close_screen if sokoban.cur_level.level_finished? puts "You are the greatest player in history!!!" else puts "You have given up too easily!!!" end --Multiparthu__4_Nov_2004_01_16_41_+0100_5zxzf2Ra/yLOJ Content-Type: text/x-ruby; nameox.rb" Content-Disposition: attachment; filenameox.rb" Content-Transfer-Encoding: 8bit require 'fox' require 'fox/colors' require 'sokoban' include Fox class SokobanWindow < FXMainWindow def initialize( app ) super( app, "Sokoban for Ruby Quiz #5", nil, nil, DECOR_ALL, 0, 0, 300, 300 ) menubar XMenubar.new( self ) filemenu XMenuPane.new( self ) levelmenu XMenuPane.new( self ) FXMenuTitle.new( menubar, "&File", nil, filemenu ) FXMenuTitle.new( menubar, "&Levels", nil, levelmenu ) FXMenuCommand.new( filemenu, "&Quit\tCtl-Q", nil, getApp(), FXApp::ID_QUIT ) @sokoban okoban.new @sokoban.load_levels( File.read( 'sokoban_levels.txt' ) ) @sokoban.select_level( 0 ) menu il @sokoban.levels.each_with_index do |level, index| if index % 15 0 menu XMenuPane.new( self ) FXMenuCascade.new( levelmenu, "#{index}++", nil, menu ) end icon XIcon.new( app, nil, 0, IMAGE_KEEP | IMAGE_OPAQUE, 50, 50 ) icon.create FXDCWindow.new( icon ) do |dc| paint_map( 50, 50, dc, level.map ) end item XMenuCommand.new( menu, nil, icon ) item.connect( SEL_COMMAND, method( :on_level_chosen ) ) item.userData truct.new(:level, :index).new( level, index ) end @canvas XCanvas.new( self, nil, 0, LAYOUT_FILL_X | LAYOUT_FILL_Y ) @canvas.connect( SEL_PAINT, method( :on_canvas_repaint ) ) @canvas.connect( SEL_KEYPRESS, method( :on_canvas_keypress ) ) end def create super show end def drawMan(dc, x, y, delta ) dc.foreground XColor::Green dc.lineWidth dc.drawLine( x*delta + 1, y*delta + 1, x*delta + delta -1, y*delta + delta - 1 ) dc.drawLine( x*delta + delta - 1, y*delta + 1, x*delta + 1, y*delta + delta - 1 ) end def drawCrate(dc, x, y, delta ) dc.foreground XColor::Blue dc.fillRectangle( x*delta + delta/4, y*delta + delta/4, delta/2, delta/2 ) end def drawStorage(dc, x, y, delta ) dc.foreground XColor::Red dc.fillCircle( x*delta + delta/2, y*delta + delta/2, delta/2 ) end def paint_map( width, height, dc, map ) dx idth / map.width dy eight / map.height delta dx > dy ? dy : dx ) dc.foreground XColor::White dc.fillRectangle( 0, 0, width, height ) y map.each_row do |row| row.each_with_index do |cell, x| case cell when Map::Wall dc.foreground XColor::SandyBrown dc.fillRectangle( x*delta, y*delta, delta, delta ) when Map::Storage drawStorage( dc, x, y, delta ) when Map::Crate drawCrate( dc, x, y, delta ) when Map::Man drawMan( dc, x, y, delta ) when Map::CrateOnStorage drawStorage( dc, x, y, delta ) drawCrate( dc, x, y, delta ) when Map::ManOnStorage drawStorage( dc, x, y, delta ) drawMan( dc, x, y, delta ) end end y + end end def on_level_chosen( sender, sel, event ) @sokoban.select_level( sender.userData.index ) @canvas.focus end def on_menu_levels_paint( sender, sel, event ) dc XDCWindow.new( sender ) paint_map( sender.width, sender.height, dc, sender.userData.level.map ) dc il GC.start end def on_canvas_repaint( sender, sel, event ) dc XDCWindow.new( sender ) paint_map( sender.width, sender.height, dc, @sokoban.cur_level.map) if @sokoban.cur_level ! il dc.foreground XColor::Red dc.drawText( 10, 10, 'Level finished!!!' ) if @sokoban.cur_level.level_finished? dc il GC.start end def on_canvas_keypress( sender, sel, event ) case event.code when KEY_Left then @sokoban.cur_level.move( :left ) when KEY_Right then @sokoban.cur_level.move( :right ) when KEY_Up then @sokoban.cur_level.move( :up ) when KEY_Down then @sokoban.cur_level.move( :down ) when KEY_Escape then @sokoban.cur_level.reset end @canvas.update end end app XApp.new( "Sokoban", "Sokoban" ) SokobanWindow.new( app ) app.create app.run --Multiparthu__4_Nov_2004_01_16_41_+0100_5zxzf2Ra/yLOJ Content-Type: text/x-ruby; nameistener.rb" Content-Disposition: attachment; filenameistener.rb" Content-Transfer-Encoding: 8bit module Listener # Adds a new message listener for the object. The message +msgName+ # will be dispatched to either the given +callableObject+ (has to respond # to +call+) or the given block. If both are specified the +callableObject+ # is used. def add_msg_listener( msgName, callableObject il, &block ) return unless defined?( @msgNames ) && @msgNames.has_key?( msgName ) if !callableObject.nil? raise NoMethodError, "listener needs to respond to 'call'" unless callableObject.respond_to? :call @msgNames[msgName].push callableObject elsif !block.nil? @msgNames[msgName].push block else raise "you have to provide a callback object or a block" end end # Removes the given object from the dispatcher queue of the message +msgName+. def del_msg_listener( msgName, object ) @msgNames[msgName].delete object if defined? @msgNames end ####### private ####### # Adds a new message target called +msgName+ def add_msg_name( msgName ) @msgNames } unless defined? @msgNames @msgNames[msgName] ] unless @msgNames.has_key? msgName end # Deletes the message target +msgName+. def del_msg_name( msgName ) @msgNames.delete msgName if defined? @msgNames end # Dispatches the message +msgName+ to all listeners for this message, # providing the given arguments def dispatch_msg( msgName, *args ) if defined? @msgNames and @msgNames.has_key? msgName @msgNames[msgName].each do |obj| obj.call( *args ) end end end end --Multiparthu__4_Nov_2004_01_16_41_+0100_5zxzf2Ra/yLOJ Content-Type: text/x-ruby; nameokoban.rb" Content-Disposition: attachment; filenameokoban.rb" Content-Transfer-Encoding: 8bit require 'listener' Position truct.new( :x, :y ) LevelChange truct.new( :object, :old_pos, :new_pos ) class Map Man @ Crate o Wall # Storage . Floor \s ManOnStorage + CrateOnStorage * include Enumerable attr_reader :width attr_reader :height def initialize( str_map ) @map tr_map.split( /\n/ ).collect {|row| row.unpack( 'C*' ) } @width map.max {|a,b| a.length <b.length}.length @height map.length end def set_pos( pos, item ) @map[pos.y][pos.x] tem end def get_pos( pos ) @map[pos.y][pos.x] end def each @map.each {|row| row.each {|field| yield field } } end def each_row @map.each {|row| yield row} end def each_with_pos @map.each_with_index do |row, y| row.each_with_index do |cell, x| yield cell, Position.new( x, y ) end end end end class Level include Listener attr_reader :map attr_reader :man_pos def initialize( map ) @original_map ap.new( map ) reset add_msg_name :level_changed add_msg_name :move_impossible add_msg_name :level_finished end def move( direction ) move_possible rue newpos evel.new_pos( @man_pos, direction ) case @map.get_pos( newpos ) when Map::Floor, Map::Storage old_man_pos man_pos move_man( newpos ) dispatch_msg( :level_changed, [LevelChange.new( :man, old_man_pos, newpos )] ) when Map::Wall dispatch_msg( :move_impossible, [LevelChange.new( :man, @man_pos, newpos )] ) move_possible alse when Map::Crate, Map::CrateOnStorage crate_new_pos evel.new_pos( newpos, direction ) case @map.get_pos( crate_new_pos ) when Map::Wall, Map::Crate, Map::CrateOnStorage dispatch_msg( :move_impossible, [LevelChange.new( :man, @man_pos, newpos )] ) move_possible alse else move_crate( newpos, crate_new_pos ) old_man_pos man_pos move_man( newpos ) dispatch_msg( :level_changed, [LevelChange.new( :man, old_man_pos, newpos ), LevelChange.new( :crate, newpos, crate_new_pos )] ) end end dispatch_msg( :level_finished ) if level_finished? return move_possible end def move_man( newpos ) case @map.get_pos( newpos ) when Map::Floor then @map.set_pos( newpos, Map::Man ) when Map::Storage then @map.set_pos( newpos, Map::ManOnStorage ) end case @map.get_pos( @man_pos ) when Map::Man then @map.set_pos( @man_pos, Map::Floor ) when Map::ManOnStorage then @map.set_pos( @man_pos, Map::Storage ) end @man_pos ewpos end def move_crate( oldpos, newpos ) case @map.get_pos( newpos ) when Map::Floor then @map.set_pos( newpos, Map::Crate ) when Map::Storage then @map.set_pos( newpos, Map::CrateOnStorage ) end case @map.get_pos( oldpos ) when Map::Crate then @map.set_pos( oldpos, Map::Floor ) when Map::CrateOnStorage then @map.set_pos( oldpos, Map::Storage ) end end def level_finished? !( @map.any? {|item| item Map::Storage } ) end def reset @map arshal.load( Marshal.dump( @original_map ) ) @man_pos evel.find_man( @original_map ) end def Level.find_man( map ) map.each_with_pos {|cell, pos| return pos if cell Map::Man || cell Map::ManOnStorage } end def Level.new_pos( pos, direction ) case direction when :left then Position.new( pos.x - 1, pos.y ) when :right then Position.new( pos.x + 1, pos.y ) when :up then Position.new( pos.x, pos.y - 1 ) when :down then Position.new( pos.x, pos.y + 1) end end end class Sokoban attr_reader :levels attr_reader :cur_level def load_levels( str ) @levels tr.split( /\n\n/ ).collect {|levelStr| Level.new( levelStr )} end def select_level( index ) @cur_level levels[index] @cur_level.reset end end --Multiparthu__4_Nov_2004_01_16_41_+0100_5zxzf2Ra/yLOJ--