--Multipart hu__4_Nov_2004_01_16_41_+0100_5zxzf2Ra/y LOJ
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
--Multipart hu__4_Nov_2004_01_16_41_+0100_5zxzf2Ra/y LOJ
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
--Multipart hu__4_Nov_2004_01_16_41_+0100_5zxzf2Ra/y LOJ
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
--Multipart hu__4_Nov_2004_01_16_41_+0100_5zxzf2Ra/y LOJ
Content-Type: text/x-ruby;
name istener.rb"
Content-Disposition: attachment;
filename istener.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
--Multipart hu__4_Nov_2004_01_16_41_+0100_5zxzf2Ra/y LOJ
Content-Type: text/x-ruby;
name okoban.rb"
Content-Disposition: attachment;
filename okoban.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 <