Send to KindleRuby Programming Challenge For Newbies
RPCFN: Interactive Fiction (#9)
By Avdi Grimm
About Avdi Grimm
Avdi Grimm is a husband, father, software cultivator living in southern Pennsylvania, USA. He has been been working with the Ruby language for almost ten years, and is still finding new reasons to love it. He is the author of NullDB, Hammertime, AlterEgo, HookR and numerous other Rubygems and has contributed code to Gemcutter, UtilityBelt, the DataMapper/SimpleDB adapter, and other projects. He writes about Ruby and software development at his blog Virtuous Code.
Avdi has this to say about the challenge:
One of the hardest parts of getting started with a new programming language is picking problems to practice on. The problems need to be difficult enough to give you an opportunity to apply the features of the language, but small enough to complete in a reasonable amount of time. RPCFN is a great way to find practice problems which have been scoped to that “just right” size by experienced Rubyists. Complete these challenges and you will be sure to gain confidence and proficiency in the Ruby language.
Our Awesome Sponsors
This monthly programming challenge is co-sponsored by ELC Technologies and Backup My App.
Founded in 1991, ELC Technologies delivers the value of next-generation Web technologies to today’s businesses, harnessing the power of mobile applications, cloud computing, and software as a service (SaaS). ELC Technologies’ leadership in agile software development processes has brought success to business-critical implementations for clients ranging from Cisco to Tribune Interactive to LiveNation. The company is a worldwide leader in Ruby on Rails development and has pioneered dynamic language development across multiple platforms. Short iterations, demos early and often, and constant client communication regarding vision and progress are all part of the ELC promise and what makes them such a valuable development partner for Global 2000 companies.
ELC Technologies is headquartered in Santa Barbara, California.
Backup My App is an automatic backup service for Ruby on Rails applications. You simply install the plugin to your Rails application and they handle the rest of the process. They store backup history for several weeks and you can restore any of them automatically. Try out their 1 GB plan for free. Backup My App has co-sponsored this challenge and is proud to make this contribution to the Ruby community.
Prizes
- The participant with the best Ruby solution (if there is a tie between answers, then the one who posted first will be the winner) will be awarded any one of PeepCode’s Ruby on Rails screencasts and a free 10 GB account for a year from Backup My App.
- From the remaining working Ruby solutions, three participants would be selected randomly and each one would be awarded any one of Pragmatic’s The Ruby Object Model and Metaprogramming screencasts.
- All the participants in this challenge (except the participant with the best Ruby solution) will get a free 5 GB account for 6 months from Backup My App.
The four persons who win, can’t win again in the next immediate challenge but can still participate.
The Ruby Challenge

The Challenge
The entire challenge details are available at: http://github.com/avdi/rpcfn-interactive-fiction.
How to Enter the Challenge
Read the Challenge Rules. By participating in this challenge, you agree to be bound by these Challenge Rules. It’s free and registration is optional. You can enter the challenge just by posting the following as a comment to this blog post:
- Your name:
- Country of Residence:
- GIST URL of your Solution (i.e. Ruby code) with explanation and / or test cases:
- Code works with Ruby 1.8 / 1.9 / Both:
- Email address (will not be published):
- Brief description of what you do (will not be published):
Note:
- As soon as we receive your GIST URL, we will fork your submission. This means that your solution is frozen and accepted. Please be sure that is the solution you want, as it is now recorded in time and is the version that will be evaluated.
- All solutions posted would be hidden to allow participants to come up with their own solutions.
- You should post your entries before midnight of 24th May 2010 (Indian Standard Time). No new solutions will be accepted from 25th May onwards.
- On 25th May 2010 all the solutions will be thrown open for everyone to see and comment upon.
- The winning entries will be announced on this blog before 30th May 2010. The winners will be sent their prizes by email.
More details on the RPCFN?
Please refer to the RPCFN FAQ for answers to the following questions:
- What Is The Ruby Programming Challenge For Newbies (RPCFN)?
- How does RPCFN benefit you?
- Challenge Rules
- Best Solution
- Can I Submit A Ruby Programming Challenge Topic?
Donations
RPCFN is entirely financed by RubyLearning and sometimes sponsors, so if you enjoy solving Ruby problems and would like to give something back by helping with the running costs then any donations are gratefully received.
Acknowledgements
Special thanks to:
- Avdi Grimm.
- Sponsors ELC Technologies and Backup My App.
- GitHub, for giving us access to a private repository on GitHub to store all the submitted solutions.
- The RubyLearning team.
Questions?
Contact Satish Talim at satish [dot] talim [at] gmail.com OR if you have any doubts / questions about the challenge (the current problem statement), please post them as comments to this post and the author will reply asap.
The Participants
There are two categories of participants. Some are vying for the prizes and some are participating for the fun of it.
In the competition
- Aldric Giacomoni, USA
- Benoit Daloze, Belgium
- James Martin, Australia
Just for Fun
- Tanzeeb Khalili, Canada
- Vojto Rinik, Slovakia
The Winners
![]()
Congratulations to the winners of this Ruby Challenge. They are:
- Benoit Daloze from Belgium (his Ruby Challenge solution) – the person with the best and most creative Ruby solution. He wins any one of PeepCode’s Ruby on Rails screencasts and a free 10 GB account for a year from Backup My App.
- Aldric Giacomoni from USA and James Martin from Australia win any one of Pragmatic’s The Ruby Object Model and Metaprogramming screencasts.
- All the participants in this challenge (except Benoit Daloze who gets a free 10 GB account for a year) will get a free 5 GB account for 6 months from Backup My App.
Previous Challenge
RPCFN: XML Transformer (#8) by Jamie van Dyke.
Note: All the previous challenges, sponsors and winners can be seen on the Ruby Programming Challenge for Newbies page.

- This challenge is now closed.
- The (#10) challenge by Ryan Bates, USA is scheduled for 1st June 2010.
Technorati Tags: Ruby, The Ruby Programming Language, Ruby Programming Challenge For Newbies, Programming, RPCFN, Avdi Grimm
Posted by Satish Talim



{ 28 comments… read them below or add one }
I am having the hardest time just getting the output to fit! I am getting odd spaces where I don’t expect them, a trailing last space which ‘strip’ doesn’t take care of, duplications of spaces which ‘squeeze’ doesn’t remove …
Does anybody have any hints on this?
Aldric, is your code online somewhere I could review it and maybe give you a hint or two?
Hi Avdi,
Nice avatar.
Here’s what I have so far: http://gist.github.com/401343
Aldric, when I run your code all the specs fail on missing method #we_know_this(). Did you post the complete code?
‘Complete’ yes. I haven’t started parsing commands, so — indeed — #we_know_this fails, because it is not implemented. It suffices to throw an error instead of attempting to parse anything to see the actual failures. My apologies.
Soo… I just checked out your code again, and copied over my code where applicable.
The first test passed. Apparently it’s some kind of quirk, either on Windows or whatever version of Ruby I was running on that Windows machine. Sadly, I don’t have access to that machine anymore so I can’t troubleshoot this further.
Solution (#1):
Aldric Giacomoni, USA
Code works with 1.8 and, after very brief testing, it seems like 1.9, too.
Solution: http://gist.github.com/401343
No extra credit; I will probably attempt to implement that to learn some more about things like instance_eval, but I won’t have time before the deadline — it will involve some tweaking.
Ran his code with ruby 1.8.6 (2010-02-04 patchlevel 398) [i386-mingw32] and confirmed author’s sample interaction. The following is the log:
————–
C:rpcfn-interactive-fiction>ruby bin/play.rb data/petite_cave.if
bin/play.rb:27: warning: parenthesize argument(s) for future version
You are standing at the end of a road before a small brick building. Around you
is a forest. A small stream flows out of the building and down a gully.
north
There is no way to go in that direction
east
You are inside a building, a well house for a large spring.
There are some keys on the ground here.
There is a shiny brass lamp nearby.
There is food here.
There is a bottle of water here.
get keys
OK
get food
OK
get lantern
OK
get water
OK
inventory
Small bottle
bottle
water
water bottle
Tasty food
food
Set of keys
keys
Brass lantern
brass lamp
lamp
lantern
west
You’re at end of road again.
s
You are in a valley in the forest beside a stream tumbling along a rocky bed.
s
At your feet all the water of the stream splashes into a 2-inch slit in the rock
. Downstream the streambed is bare rock.
s
You are in a 20-foot depression floored with bare dirt. Set into the dirt is a s
trong steel grate mounted in concrete. A dry streambed leads into the depression
.
C:rpcfn-interactive-fiction>
————–
He writes `No extra credit`, so his code works well.
My trivial comment is,
- it’s better to show a prompt for user input.
- it’s better to revise the `inventory` command output. No need to show all synonyms.
And his code also works well with Ruby 1.9.1, if he edits line 130 like this:
commands = commands.split “\n”
Aldric, thank you for your entry. Here are some comments:
Nice use of String#scan for parsing.
Unfortunately, it looks like your solution is tied to specific lines in the petite_cave.if file – it won’t generalize to other adventures.
You could simplify your use of the File class:
def open file raw_data = "" begin File.open(file, 'r') { |f| raw_data << f.read } rescue throw ArgumentError, "File not found or impossible to open." end raw_data endCould instead be:
def open file File.open(file, 'r') { |f| f.read } rescue throw ArgumentError, "File not found or impossible to open." endOr simply:
def open file File.read(file) rescue throw ArgumentError, "File not found or impossible to open." endYour method we_know_this:
def we_know_this command # Alright; this is kinda hackish. I'm lazy. Sue me. # I mean, don't. Jeez. Where's your sense of humor? if @commands.keys.include? command return @commands[command] else return false end endCould just be:
Since it will return nil if no command is found, and nil is a “falsy” value.
Try to avoid the use of local variables when you can:
def find_casecmp array, item found = false array.each do |x| if x.casecmp(item) == 0 found = true break end end found endCould be:
def find_casecmp array, item array.each do |x| return true if x.casecmp(item) == 0 end endOr even better:
def find_casecmp array, item array.any? { |x| x.casecmp(item) == 0 } endNice job, and happy hacking!
Note: Because the comment forms on the RubyLearning site don’t seem to support much in the way of formatting for code samples, etc. I’ve instead pout my comments in the challenge README at
http://github.com/avdi/rpcfn-interactive-fiction
Solution (#2):
Benoit Daloze, Belgium
GIST URL of your Solution (i.e. Ruby code) with explanation and / or test cases: https://gist.github.com/56114e98b2109df8865a
Code works with Ruby 1.8 / 1.9 / Both: both, helped with backports
This solution pass all the test, use a simple parser using the indentation.
The abstract parsing is done in parser.rb, and all objects parse these results to fit the needs.
I don’t have Ruby 1.9.2. So, I added `require ‘backport’` in his play.rb and ran with Ruby 1.9.1. Then his code worked very well. It also supported Extra Credit! Awesome!
This is a log:
——————
C:\rpcfn-interactive-fiction>ruby bin/play.rb data/petite_cave.if
You are standing at the end of a road before a small brick building.
Around you is a forest. A small stream flows out of the building and
down a gully.
> n
There is no way to go in that direction
> e
You are inside a building, a well house for a large spring.
There are some keys on the ground here.
There is a shiny brass lamp nearby.
There is food here.
There is a bottle of water here.
> get keys
OK
> get lamp
OK
> i
Set of keys
Brass lantern
> w
You’re at end of road again.
> s
You are in a valley in the forest beside a stream tumbling along a
rocky bed.
> s
At your feet all the water of the stream splashes into a 2-inch slit
in the rock. Downstream the streambed is bare rock.
> s
You are in a 20-foot depression floored with bare dirt. Set into the
dirt is a strong steel grate mounted in concrete. A dry streambed
leads into the depression.
> s
There is no way to go in that direction
> enter
You can’t go through a locked steel grate!
> unlock grate
The grate is now unlocked
> e
There is no way to go in that direction
> enter
You are in a small chamber beneath a 3×3 steel grate to the surface.
A low crawl over cobbles leads inward to the west.
> q
C:\rpcfn-interactive-fiction>
> I don’t have Ruby 1.9.2. So, I added `require ‘backport’` in his play.rb and ran with Ruby 1.9.1.
Sorry, I just forgot a “.2″ somewhere to compare in main.rb
(git and gist updated !)
> Then his code worked very well. It also supported Extra Credit! Awesome!
Also available on my git repo:
http://github.com/eregon/rpcfn-interactive-fiction
Writing a prompt (“>”) only when run standalone is a nice touch.
This solution is very nicely factored out into small classes and files.
Impressive use of many different built-in String, Enumerable, and Array methods. Although this makes the some of the parser code a little too dense to easily follow.
I like the use of the #<> to remove objects from a room – it’s nonobvious, and using #delete would match Ruby standard libraries better.
While for the most part the code neatly divides responsibilities between classes, it seems like the parsing code is split between the parser and the individual class initializers.
Yay extra credit!
> While for the most part the code neatly divides responsibilities between classes, it seems like the parsing code is split between the parser and the individual class initializers.
So I changed that and now classes like Action are really short and nice. (See the git or the gist).
It was not that easy to move the code, but now I got rid of the specific constructors and they really use their superclass: AbstractObject.
I think my code is even better now
(except probably a bit of headache in Parser)
Solution (#3):
James Martin, Australia
Gist URL: https://gist.github.com/829a163a5135eec612cb/60b796f2cc70547d5c69c9f79740582193fcde65
Code works with: version 1.8.7 of Ruby.
Email address: jimmymartin@gmail.com
Ran his code with ruby 1.9.1p376 (2009-12-07 revision 26041) [i386-mswin32]. It works well. I guess that his code doesn’t support Extra Credit.
Log is here:
——————-
C:\rpcfn-interactive-fiction>ruby bin/play.rb data/petite_cave.if
You are standing at the end of a road before a small brick building. Around you
is a forest. A small stream flows out of the building and down a gully.
north
There is no way to go in that direction
east
You are inside a building, a well house for a large spring.
There are some keys on the ground here.
There is a shiny brass lamp nearby.
There is food here.
There is a bottle of water here.
get keys
OK
get lamp
OK
get food
OK
get water
OK
i
Set of keys, Brass lantern, Tasty food, Small bottle
w
You’re at end of road again.
s
You are in a valley in the forest beside a stream tumbling along a rocky bed.
s
At your feet all the water of the stream splashes into a 2-inch slit in the rock
. Downstream the streambed is bare rock.
s
You are in a 20-foot depression floored with bare dirt. Set into the dirt is a
strong steel grate mounted in concrete. A dry streambed leads into the depressi
on.
s
There is no way to go in that direction
unlock grate
There is no way to go in that direction
open grate
There is no way to go in that direction
enter
There is no way to go in that direction
quit
Thanks for playing!
C:\rpcfn-interactive-fiction>
Nice use of meaningfully-named predicates like #current_room_has_an_exit_named?.
Some of the methods and classes are very long, and could have benefitted from being factored into smaller units.
When creating an empty Hash it’s a bit more conventional to use {} instead of Hash.new.
Prefixing reader methods with “get_” is a Java-style convention. In Ruby, we just name the method after what we are getting. E.g. Instead of:
get_description_of_objects_in_current_room, just description_of_objects_in_current_room
Try to use more specific Enumerable methods when applicable. E.g.
@rooms.each do |room|
if room["name"] == room_name
@current_room = room
end
end
Has the same semantics as:
if room = @rooms.detect{|room| room["name"] == room_name }
@current_room = room if room
end
And:
items = []
@inventory.each do |item|
items << get_object_terms_by_name(item)
end
becomes:
items = @inventory.map { |item| get_object_terms_by_name(item) }
or even:
items = @inventory.map(&method(:get_object_terms_by_name))
Solution (#4):
Tanzeeb Khalili, Canada
Just for Fun entry: http://gist.github.com/412189
Fairly simple solution, all files can go under bin/. Uses regular expressions to transform *.if files into executable code. No hope of running under $SAFE, but should qualify for extra credit.
Would like to extend a special thanks to Satish, Satoshi and Avdi for this month’s contest.
Very creative and fun, and also quite the challenge!
Ran his code with Ruby 1.9.1. It worked very well. It also supported Extra Credit! Awesome!
This is a log:
C:\rpcfn-interactive-fiction>ruby bin/play.rb data/petite_cave.if
You are standing at the end of a road before a small brick building. Around you
is a forest. A small stream flows out of the building and down a gully.
> north
There is no way to go in that direction
> east
You are inside a building, a well house for a large spring.
There are some keys on the ground here.
There is a shiny brass lamp nearby.
There is food here.
There is a bottle of water here.
> get lamp
OK
> get food
OK
> i
Brass lantern
Tasty food
> w
You’re at end of road again.
> s
You are in a valley in the forest beside a stream tumbling along a rocky bed.
> s
At your feet all the water of the stream splashes into a 2-inch slit in the rock
. Downstream the streambed is bare rock.
> s
You are in a 20-foot depression floored with bare dirt. Set into the dirt is a
strong steel grate mounted in concrete. A dry streambed leads into the depressi
on.
> s
There is no way to go in that direction
> enter
You can’t go through a locked steel grate!
> unlock grate
You have no keys!
> n
You’re at slit in streambed.
> n
You’re in valley.
> n
You’re at end of road again.
> e
You’re inside building.
> look
You are inside a building, a well house for a large spring.
There are some keys on the ground here.
There is a bottle of water here.
> get keys
OK
> w
You’re at end of road again.
> s
You’re in valley.
> s
You’re at slit in streambed.
> s
You’re outside grate.
> open grate
The grate is now unlocked
> enter
You are in a small chamber beneath a 3×3 steel grate to the surface. A low crawl
over cobbles leads inward to the west.
> exit
You’re outside grate.
C:\rpcfn-interactive-fiction>
This entry is a work of art. It should be mounted on the wall of a museum of beautiful code.
The code is neatly broken down into small classes and very short methods.
It makes good use of String/Enumerable built-in methods.
I love the pattern where Player#do_* methods become the commands available at the command line.
Using regular expressions to turn the story definition into executable code makes for an astonishingly succinct parser. I’m glad someone chose to go this route, because I think it’s a great, pragmatic technique for parsing DSLs.
One nitpick: instead of:
@exits[direction] || [nil, GUARDS[:none]]
use:
@exits.fetch(direction) {[nil, GUARDS[:none]]}
Wow, thank you Avdi!
Also, thanks again for the challenge. Very well executed, by far my favourite .
Hey there,
now I know I didn’t make it on time, I just now realized it’s *indian time* (wtf).
Anyway. I don’t know if this is a bug or what. But my tests are getting passed when I run
cucumber features/petite_cave.feature:105
When I run cucumber with everything, this test (drop keys) fails.
I don’t know why Cuke acts like this, the Background task should reset everything. But I found out it’s failing, because and the beginning of “Drop an object” scenario, there are already no keys in @building.
I have modified this scenario, so that error would be more obvious:
Scenario: Drop an object
When I enter “look”
Then I should not see “keys”
When I enter “east”
Then I should see “keys” <– here it fails
And I enter "get keys"
And I enter "west"
And I enter "drop keys"
And I enter "look"
Then I should see "There are some keys on the ground here."
When I enter "inventory"
Then I should not see "keys"
Again, it only happens when you run the whole thing at once, and I don't know — maybe this is happening only for me?
I'll post my solution (no gist, I'll just fork it, I can't participate anyway). I think it's pretty neat, I built a parser that I can use to parse pretty much any tree-structured DSL.
Solution (#5):
Um, so yeah, just for fun, but anyway I think I can post a little info about me:
Vojto Rinik, Slovakia
http://github.com/vojto/rpcfn-interactive-fiction (sorry guys, I don’t want to fit it into one gist, I really like my file structure)
Yay no tests. Vojto, stop being lazy and write tests!
Works with 1.8
^ If my previous comment got approved:
The problem was that I used Singleton class for describing world. The state of world persisted between tests.
Now here’s my solution: http://github.com/vojto/rpcfn-interactive-fiction
I’m late, I know, here in Europe it’s midnight right now, but whatever it’s not about winning, it’s about the fun.
Also, any comments to my code are welcome.
–
Ran his code with Ruby 1.9.1. It worked and got the following log.
It doesn’t seem to support Extra Credit.
—————–
C:\rpcfn-interactive-fiction>ruby bin/play.rb data/petite_cave.if
You’re at end of road again.
["You are standing at the end of a road before a small brick building.\n", "Arou
nd you is a forest. A small stream flows out of the building and\n", "down a gu
lly.\n"]
north
There is no way to go in that direction
east
You’re inside building.
["You are inside a building, a well house for a large spring.\n"]
There are some keys on the ground here.
There is a shiny brass lamp nearby.
There is food here.
There is a bottle of water here.
get keys
OK
get brass lamp
OK
get water
OK
i
Set of keys
Brass lantern
Small bottle
w
You’re at end of road again.
["You are standing at the end of a road before a small brick building.\n", "Arou
nd you is a forest. A small stream flows out of the building and\n", "down a gu
lly.\n"]
s
You’re in valley.
["You are in a valley in the forest beside a stream tumbling along a\n", "rocky
bed.\n"]
s
You’re at slit in streambed.
["At your feet all the water of the stream splashes into a 2-inch slit\n", "in t
he rock. Downstream the streambed is bare rock.\n"]
s
You’re outside grate.
["You are in a 20-foot depression floored with bare dirt. Set into the\n", "dir
t is a strong steel grate mounted in concrete. A dry streambed\n", "leads into
the depression.\n"]
enter
Unknown command enter
unlock grate
Unknown command unlock
s
There is no way to go in that direction
exit
Unknown command exit
bin/play.rb:139:in `eof?’: Interrupt
from bin/play.rb:139:in `ended?’
from bin/play.rb:16:in `play!’
from bin/play.rb:150:in `’
C:\rpcfn-interactive-fiction>
—————–
His code looks almost good for accepting basic commands. But it’s better to improve output format and need quit command.
The parser tracks offsets (indentation) in the story definition file in order to determine the current scope. I like this!
Different responsibilities are nicely factored out into separate classes and methods. My only caveat is that, like some of the other solutions, the responsibility for parsing the story file seems to be partly split between the parser and the classes representing Rooms, etc.
This is an easily-understandable, straightforward solution.
Forgot to add my just-for-fun entry – http://gist.github.com/427312.
Regexps the data into a YAML document easy indentation-aware parsing. The rest of the code is brief but messy – first thing that worked.
Don’t think I really understand ruby’s SAFE levels (first time I ever used them), but had a crack at getting that to work too.
Nice challenge!
{ 24 trackbacks }