Ruby Warrior


Ruby Warrior is a fascinating game in which the player writes a program to navigate their warrior through a tower to rescue the fair maiden, Ruby. As you progress through the tower each level becomes more complex. To compensate for the increased difficulty your warrior gains abilities like looking around the room, ranged attacks, and even walking backwards.

As a player of the game you are given an empty Ruby class, Player, which informs Ruby Warrior of you next action. Ruby Warrior runs in a loop which terminates upon reaching the stairs to the next level or death. Each iteration of the loop calls the play_turn method in your Player class which chooses an action to perform based on the current state of the world.

I decided to employ a state machine as the core of my decision making. Unfortunately, I did not explicitly use a state machine so the code is convoluted. Each transition in the state machine returns the next action as a symbol, e.g. :walk!. This symbol is then sent to the warrior to finish the turn.

As I progressed through the tower I found that it was useful to introduce transitions which did not end the turn. One example is retreat. If the warrior is about to die the top priority is finding a safe place to heal. This goal is captured in a retreat state with transitions specifically designed to hide and heal. To include non-turn-ending transitions, I modified the play_turn method to continually pick an action until one of the turn enders is selected.

Crafting the state machine was mostly trial and error. A whiteboard is also helpful at this stage. The most useful action is feel. Feeling every direction on every turn provides the most information about the warrior's surroundings which allows for a more accurate choice of the next action.

If artificial intelligence is not your cup of tea you can always abuse Ruby's dynamic nature to cheat. I came up with a six line method which breezes through both towers, albeit with a score of D. In order to check for completion of a level, Ruby Warrior compares the warrior's position with the stair's position. So hacking boils down to teleporting the warrior to the stairs or relocating the stairs to the warrior. The code for both is nearly identical. I chose stair relocation only because it is more ridiculous than teleportation, at least in the context of a video game.

It turns out that Ruby Warrior provides your Player with the entire state of the world. The state is hidden within the warrior object. inspect, methods, and instance_variables allowed me to quickly narrow down exactly the location for the state of the world. The feel method contains a @floor instance variable which is a reference to, not a copy of, the state of the world.

The reference part is important. This means that any updates to @floor are stored and used for computing the state for the next turn. My hack exploits this design and replaces the stairs' location with the warrior's current location. And on the next turn the completion condition is met.

There is one caveat. On the first level your warrior does not know :feel. Fortunately the solution in the first level is simple: always choose walk!. So I first check if feel is defined and then take the appropriate action.

def play_turn warrior
  if warrior.methods.include? :feel
    warrior_position = warrior.feel.instance_eval("@floor.units.detect{|el| el.is_a? RubyWarrior::Units::Warrior}").position
    warrior.feel.instance_eval("@floor").instance_eval("@stairs_location = [#{warrior_position.instance_eval("@x")}, #{warrior_position.instance_eval("@y")}]")
  else
    warrior.walk!
  end
end

Just for fun, here is the output of the hack in epic mode.

Welcome to Ruby Warrior
Starting Level 1
- turn 1 -
--------
|@      >|
--------
rampantmonkey does nothing
Success! You have found the stairs.
Level Score: 0
Time Bonus: 14
Clear Bonus: 3
Level Grade: S
Total Score: 17
Starting Level 2
- turn 1 -
--------
|@   s  >|
--------
rampantmonkey does nothing
Success! You have found the stairs.
Level Score: 0
Time Bonus: 19
Level Grade: C
Total Score: 17 + 19 = 36
Starting Level 3
- turn 1 -
---------
|@ s ss s>|
---------
rampantmonkey does nothing
Success! You have found the stairs.
Level Score: 0
Time Bonus: 34
Level Grade: F
Total Score: 36 + 34 = 70
Starting Level 4
- turn 1 -
-------
|@ Sa S>|
-------
rampantmonkey does nothing
Success! You have found the stairs.
Level Score: 0
Time Bonus: 44
Level Grade: F
Total Score: 70 + 44 = 114
Starting Level 5
- turn 1 -
-------
|@ CaaSC|
-------
rampantmonkey does nothing
Success! You have found the stairs.
Level Score: 0
Time Bonus: 44
Level Grade: F
Total Score: 114 + 44 = 158
Starting Level 6
- turn 1 -
--------
|C @ S aa|
--------
rampantmonkey does nothing
Success! You have found the stairs.
Level Score: 0
Time Bonus: 54
Level Grade: F
Total Score: 158 + 54 = 212
Starting Level 7
- turn 1 -
------
|>a S @|
------
rampantmonkey does nothing
Success! You have found the stairs.
Level Score: 0
Time Bonus: 29
Level Grade: F
Total Score: 212 + 29 = 241
Starting Level 8
- turn 1 -
------
|@ Cww>|
------
rampantmonkey does nothing
Success! You have found the stairs.
Level Score: 0
Time Bonus: 19
Level Grade: F
Total Score: 241 + 19 = 260
Starting Level 9
- turn 1 -
-----------
|>Ca  @ S wC|
-----------
rampantmonkey does nothing
Archer shoots forward and hits rampantmonkey
rampantmonkey takes 3 damage, 17 health power left
CONGRATULATIONS! You have climbed to the top of the tower and rescued the fair maiden Ruby.
Level Score: 0
Time Bonus: 39
Level Grade: F
Total Score: 260 + 39 = 299
Your average grade for this tower is: D

Level 1: S
Level 2: C
Level 3: F
Level 4: F
Level 5: F
Level 6: F
Level 7: F
Level 8: F
Level 9: F

To practice a level, use the -l option:

rubywarrior -l 3