[SUMMARY] Sokoban (#5)

R

Ruby Quiz

Well, how far did you get? I'm stuck on level 28 myself.

As those of you who have now built Sokoban know, the game is quite trivial to
implement. Here's a very brief solution from Dennis Ranke:

class Level
def initialize(level)
@level = level
end

def play
while count_free_crates > 0
printf "\n%s\n\n> ", self
c = gets
c.each_byte do |command|
case command
when ?w
move(0, -1)
when ?a
move(-1, 0)
when ?s
move(0, 1)
when ?d
move(1, 0)
when ?r
return false
end
end
end
printf "\n%s\nCongratulations, on to the next level!\n", self
return true
end

private

def move(dx, dy)
x, y = find_player
dest = self[x+dx, y+dy]
case dest
when ?#
return
when ?o, ?*
dest2 = self[x+dx*2, y+dy*2]
if dest2 == 32
self[x+dx*2, y+dy*2] = ?o
elsif dest2 == ?.
self[x+dx*2, y+dy*2] = ?*
else
return
end
dest = (dest == ?o) ? 32 : ?.
end
self[x+dx, y+dy] = (dest == 32) ? ?@ : ?+
self[x, y] = (self[x, y] == ?@) ? 32 : ?.
end

def count_free_crates
@level.scan(/o/).size
end

def find_player
pos = @level.index(/@|\+/)
return pos % 19, pos / 19
end

def [](x, y)
@level[x + y * 19]
end

def []=(x, y, v)
@level[x + y * 19] = v
end

def to_s
(0...16).map {|i| @level[i * 19, 19]}.join("\n")
end
end

levels = File.readlines('sokoban_levels.txt')
levels = levels.map {|line| line.chomp.ljust(19)}.join("\n")
levels = levels.split(/\n {19}\n/).map{|level| level.gsub(/\n/, '')}

levels.each do |level|
redo unless Level.new(level.ljust(19*16)).play
end

Dennis decided to keep the levels in their text format and lean on Ruby's text
processing strengths. This probably doesn't make for the prettiest of
solutions, but it is short.

Level.play() is the primary interface for the code above. It handles one level,
start to finish, returning true if the level was solved and false if it was
restarted. It checks for a level being solved by looping until
Levels.count_free_crates() returns 0. That method simply scan()s for "o"
characters, returning a count. When a player enters a move command, work is
handed off to the game-play routine Level.move().

The first step to performing a move is to find the player. Level.find_player()
uses a combination of index() and simple math to locate a "@" or "+" character.
(Note: This is a weakness of Dennis' solution. It only works for levels 19
characters wide and smaller.) Once found, move() checks the square in front of
the player. If it's a wall, it disallows the move and if it's open, the player
moves. The special case is when there is a crate in front of the player. When
found, move() looks farther forward to ensure that the path is clear and if it
is, both crate and player are moved. All this testing and swapping is handled
with Level.[]() and Level.[]=(), which function as expected.

For a more abstract OO solution, check out the code sent in by Dave Burt.

So, as I've said and we've now seen, implementing Sokoban is pretty easy stuff.
Extra features weren't too popular, but some did allow for a way to restart
levels. That is pretty critical in Sokoban, where you can easily get yourself
stuck. Dave Burt's solution includes a debugging mode that allows you to feed
the game engine pure Ruby. My solution also provided Undo, Save, and Load. I
think a level editor and a solver would make interesting additions.

Let's talk about one more thing though. Interface. To a game, interface is
critical. Dennis Ranke's and Dave Burt's games read line-oriented input,
requiring you to push enter/return to send a move. While they do allow you to
queue up a long line of moves, this tires my poor little fingers out, especially
on involved levels.

That begs the question, why did they use this approach?

Portability, would be my guess. Reading a single character from a terminal
interface can get tricky, depending on which operating system you are running
on. Here's how I do it on Unix:

def read_char
system "stty raw -echo"
STDIN.getc
ensure
system "stty -raw echo"
end

Here's one way you might try the same thing on Windows:

def read_char
require "Win32API"

Win32API.new("crtdll", "_getch", [], "L").Call
end

If you want your game to run on both, you may need to write code to detect the
platform and use the proper method. Here's one way you might accomplish that:

begin
require "Win32API"

def read_char
Win32API.new("crtdll", "_getch", [], "L").Call
end
rescue LoadError
def read_char
system "stty raw -echo"
STDIN.getc
ensure
system "stty -raw echo"
end
end

That doesn't cover every platform, but I believe it will work with Windows and
most Unix flavors (including Mac OS X). That may be enough for some purposes.

Other submissions dealt with this differently. G.Durga Prasad's solution used
Curses. Curses is standard Ruby, but unfortunately not so standard in the
Windows world. A great advantage to this approach was being able to use the
arrow keys, which makes for the best interface, I think.

I believe Florian Gross also used the arrow keys, but his solution doesn't seem
to support my platform. He used the Ruby/Gosu game library to build a graphical
Sokoban.

Thomas Leitner provided another Curses solution in addition to one that requires
FXRuby, a Ruby interface to the FOX GUI library. Thomas's submission is also a
brilliant piece of AI, since it knew to tell me "You are the greatest player in
history!!!"

My own solutions either relied on a Unix terminal or a Ruby/OpenGL library being
installed.

Interface work can get neck deep in external dependancies pretty quick it seems.
Since games are largely defined by their interface, that can make for some
complex choices. Maybe we should hope for a Swing-like addition to the Ruby
Standard Library sometime in the future.

My thanks to the game writers and game players.

Look for our second contributed quiz topic tomorrow morning, this time by Jamis
Buck...
 
F

Florian Gross

Ruby said:
I believe Florian Gross also used the arrow keys, but his solution doesn't seem
to support my platform. He used the Ruby/Gosu game library to build a graphical
Sokoban.

It depends. Linux support is there in case you have Ruby compiled with
pthread support. Details on how to get Ruby/Gosu working:

http://www.raschke.de/julian/gosu/docs/using-ruby.html

Sorry for all the trouble. I hope that this process will get easier in
the future, though I can't directly influence this.
 
D

Dave Burt

Ruby Quiz said:
Well, how far did you get? I'm stuck on level 28 myself.

As those of you who have now built Sokoban know, the game is quite trivial
to
implement. Here's a very brief solution from Dennis Ranke: (...)

Wow! That's only 2 pages, about 0.08 small kLoC, or just over 1kB.
requiring you to push enter/return to send a move. While they do allow
you to
queue up a long line of moves, this tires my poor little fingers out,
especially
on involved levels.

That begs the question, why did they use this approach?

Portability, would be my guess. (...)

Yes, I didn't like it, either. While standard input does confer the
advantage of being able to save solutions and feed them in to your program
using pipes or terminal cut/pasting, it still isn't my preference. If Ruby
on Windows came with Curses, it would have been done that way. If I could be
bothered learning Fox, it would have been done that way.

How close is Fox to being "Swing for Ruby"? I ask because it works OK out of
the one-click installer.

The idea just popped into my head, what about using the Swing API itself?
It's all lightweight itself, in Java, isn't it? Only 3 native classes, or
something like that... I put no weight on this, though.
begin
require "Win32API"
def read_char
Win32API.new("crtdll", "_getch", [], "L").Call
end
rescue LoadError
def read_char
system "stty raw -echo"
STDIN.getc
ensure
system "stty -raw echo"
end
end

(Saved for later use - thanks)

Cheers,
Dave
 
J

James Edward Gray II

It depends. Linux support is there in case you have Ruby compiled with
pthread support.

Sorry, should have specified. My platform is Mac OS X.

James Edward Gray II
 
J

James Edward Gray II

Yes, I didn't like it, either. While standard input does confer the
advantage of being able to save solutions and feed them in to your
program
using pipes or terminal cut/pasting, it still isn't my preference.

This is probably better for controlling it programatically too.
Another reason to try a solver. ;)

James Edward Gray II
 
D

Dave Burt

James Edward Gray II said:
This is probably better for controlling it programatically too. Another
reason to try a solver. ;)

This is the second time this has been told to me. I think I will, you know.
"c" for cheat? Level editing, saving and loading would be easier than the
solver, so those may yet make their way in, too. And maybe a Fox front-end,
eventually.

See you guys in a couple of years :)
Dave
 
J

James Edward Gray II

This is the second time this has been told to me. I think I will, you
know.
"c" for cheat? Level editing, saving and loading would be easier than
the
solver, so those may yet make their way in, too. And maybe a Fox
front-end,
eventually.

See you guys in a couple of years :)

Just don't forget to share when you're done!

James Edward Gray II
 
D

Dennis Ranke

Let's talk about one more thing though. Interface. To a game,
interface is
critical. Dennis Ranke's and Dave Burt's games read line-oriented input,
requiring you to push enter/return to send a move. While they do allow
you to
queue up a long line of moves, this tires my poor little fingers out,
especially
on involved levels.

That begs the question, why did they use this approach?

Speaking for myself, I decided to try to write the most simple solution
(in terms of code) I could come up with. First, to show that a fascinating
game can still be extremely simple at it's core and second, because I
wrote it in a 20 minute break at work. ;)
I realized from the beginning that this would make for a terrible actual
game (and in fact I had to force myself to even play through the first
level this way when testing the code ;).

When comparing my solution to others (both in terms of code and
playability) it shows very nicely that when trying to write a nice game
you'll have to pay as much attention to the interface as to the actual
gameplay and that's an important lesson for all aspiring game programmers.
:)

(fyi. I'm programming games for a living, hopefully all with better
interfaces than my sokoban ;)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads


Members online

No members online now.

Forum statistics

Threads
473,982
Messages
2,570,190
Members
46,740
Latest member
AdolphBig6

Latest Threads

Top