Ruby Warrior Strategy Guide
Computer programming can control the power grid, mobile phones, and maybe intergallactic satellites. But that’s not how you learn to love programming.
Many programmers got into programming because of games. Let’s experiment with the Ruby Warrior tutorial by Ryan Bates to refresh and refine your Ruby programming skills.
Setup
There are just a few short steps to get rolling.
Install the Gem
To begin, you need the gem. Recall that a gem is a package of Ruby code that’s been published to the http://rubygems.org website.
To fetch the gem, go to your Command Prompt and type:
Terminal
|
|
That should fetch the gem from RubyGems and make it available in your system.
Generate Your Project
After that installation completes you need to create a Ruby Warrior project directory. At your command prompt let’s create a general projects directory then use Ruby Warrior to generate its own subfolder:
Terminal
|
Then Ruby Warrior will ask you a few questions. Let’s being with the beginner
tower and decide on your own name for your hero:
Terminal
|
Open the Project
Now, open your text editor (like SublimeText or Notepad++) and open the rubywarrior
so you can see and edit all the files.
Within that directory, open the README
file.
Level 1
The README
file explains that on level one your character is facing down a hallway with no obstructions. In the diagram, @
represents your character and >
represents the stairs to the next level.
It also shows you that you can use the warrior.walk!
instruction to move your character forward.
Write the Code
Go over to the player.rb
file and tell your player to walk forward:
1 2 3 4 5 |
|
Run the Code
Go back to your terminal and run the code:
Terminal
|
Ok, it’s no Call of Duty, but we’ve made it through the first level!
What You Learned
- The
README
file will explain our challenges and abilities for each level - The
play_turn
method is run by Ruby Warrior to run our instructions - The
warrior
that’s passed intoplay_turn
has a method namedwalk!
that moves it forward - We execute our code by running
rubywarrior
from the command prompt
Level 2
You survived the first level, now return to the README
file and you’ll see new instructions.
The README
Sludge? Ick. If you look at the level diagram you’ll see the s
marker for sludge and the description says it has 12 HP which is short for hit points.
The level itself is still a straight hallway with stairs at the end.
At least we get some new abilities:
.feel
gives us back aSpace
– but what’s aSpace
?- A
Space
has an.empty?
method that will be true if the space in front of us is empty and false if there’s something there (like Sludge!) .attack!
attacks one space forward.walk!
moves one step forward – we already knew that
The Plan
We should probably:
- Step forward until we can reach the sludge
- Attack it until it’s dead
- Walk forward to the stairs
Write the Code
Now you’ll have to start tapping into your Ruby knowledge. Some pieces that will help you out:
1 2 3 4 5 |
|
Run the Code
Go back to your terminal and run rubywarrior
and see if you can pass the level.
Here’s the results I got:
1 2 3 4 5 6 |
|
What You Learned
- New warrior methods
.feel
and.attack!
- New space method
.empty?
- How to use an
if
/else
statement to control your warrior
Level 3
Return to the README
to see the next level.
The README
Four spots of sludge to defeat. You might not have noticed, but on the last level you probably lost some HPs while battling the sludge. With these four enemies on level 3, we might need to be more careful.
The README
teaches us a few new methods:
.health
with our current HPs.rest!
to stop and regain some HPs
Plan #1
Let’s try and just storm through and slaughter that sludge. If it doesn’t work, then we’ll have to go back and add in some rest!
instructions.
Code #1
Let’s just leave it the same as level 2 and see what happens!
Run #1
I made it through the first three sludge puddles then…
1 2 3 4 |
|
Nooooooooooooooo!
Plan #2
The sludge probably doesn’t respect "time out". So I’m guessing that I can’t rest when I’m in the middle of fighting.
Here’s pseudocode for a plan:
1 2 3 4 5 6 7 8 9 |
|
That should help me only start attacking when I have a decent number of HPs left. Maybe we’ll find out that 10 isn’t enough and we need to bump it up, but let’s give it a try.
Code #2
You’re going to use nested if
statements like the plan above. Nested if
s can get messy and hard to read, but it’s a good place to start.
Run #2
I implemented the pseudocode, ran it, and…
1 2 3 4 |
|
Dang! If I look back through the moves I see that the sludge was attacking me while I was trying to rest! Why?
The way I charted the logic had a flaw. If I drop below 10 HPs mid-fight, I’d stop attacking and instead start resting. That’s not going to work!
Can you figure out a way to rearrange or change the logic so we can survive the level?
Here were my results, see if you can beat me on the time bonus:
1 2 3 4 5 |
|
Level 4
README
Archers shooting arrows? I wonder how far they can shoot?
The suggestion about a @health
variable is interesting. If we store our current health into an instance variable, then the next time the play_turn
method is called we could compare the current health to the @health
and figure out whether we’ve been attacked. How will that help us?
Plan #1
It didn’t work out on the last level, but let’s just run our existing walk/rest/attack code and see what happens.
Run #1
Uh-oh.
1 2 3 4 5 |
|
After killing the big sludge, the archer started shooting me. I was resting, but it wasn’t enough to keep up with his arrows hurting me. We’ll need a new plan.
Plan #2
We should go kill that thick sludge and keep charging for the archer. Once the archer is dead then we could rest up before hitting the last thick sludge.
How would that look in code? I’m thinking…
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Run #2
I ran the code, but that archer still shot me dead. Looking back through the moves, I see that when I finished killing the first thick sludge I only had 8 HPs left.
My code realized that I was being attacked and started attacking back – but there was still one space between me and the archer so I was attacking the air! His arrows worked, my attacks didn’t.
Plan #3
Go back to the logic. If we’re being attacked we can’t just attack back, we need to make sure we can reach the attacker. How does that change your code?
Run #3
Mine didn’t work! There was an error in my logic and it took me awhile to figure out what the problem was. Here are a few techniques you might use to find your own issues:
Use puts
You can use the puts
instruction to output your own text during your turn. To help me figure out what was going wrong I used this line:
1
|
|
Remember that the #{}
will insert the value into the output, so in my turns I saw output like this:
1
|
|
Breaking Out being_attacked?
There were so many if
statements nested together I was getting confused about what matched up with what.
I decided to break some functionality into its own method. Specifically, I wanted to write a method named being_attacked?
that would return true
if I was being attacked and false
if I wasn’t. It would also be responsible for updating the @health
variable I was tracking.
Here’s the pseudocode for that method:
1 2 3 4 5 6 7 8 9 |
|
Breaking Out charge
I felt that pulling out that being_attacked?
method made things easier to read and wanted to try pulling out another method. Going back to my play_turn
method, it starts like this…
1 2 3 4 5 6 7 8 9 10 11 |
|
If I’m being attacked I should charge and attack the bad guy. I remove the nested if
and pull it out to a charge
method like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
I run the level again and it still passes.
Breaking Out rest_or_walk
That play_turn
method is a little more readable now, but let’s break out one more method named rest_or_walk
.
Do you have an if
statement that looks at the current health and, if it’s below some threshold, rests. But if it’s above the threshold, tells the warrior to walk!
? Pull that out to a method and use it in play_turn
.
Final Results
With all those methods extracted, here are the results from my run:
1 2 3 4 5 |
|
Let’s finally step up to the next level!
What You Learned
- Nested
if
statements can be hard to write, read, and debug - Extracting methods can help us understand and simplify our logic
Level 5
I’m assuming that you extracted all the methods described above to help understand the play_turn
method for this level.
README
Captives sound cool, we have to be a bit more conservative about who we attack.
- We can now call the
.captive?
method on aSpace
to see if it’s a captive. - We can use the
.rescue!
method to save a captive. - We shouldn’t kill the captives
The Plan
Returning to my play_turn
method, I think the change is actually small.
- If I’m being attacked, I still want to charge and attack
- If the space in front of me is empty, I want to either rest or walk
The last possibility is when the space in front of me is NOT empty. I now need to check…
1 2 3 4 5 |
|
The Code
First, I’m going to add that new nested if statement right in play_turn
and see if I can pass the level.
If that works, then I want to extract it to a rescue_or_attack
method.
The Run
Hooray, it worked on the first try! I then extracted the rescue_or_attack
method, ran it again, and got these results:
1 2 3 4 5 |
|
Level 6
README
Look closely at the level diagram and you’ll see there’s a captive behind us. That’s interesting.
It notes that we can pass :backward
as an argument to walk!
, feel
, rescue!
and attack!
which would look like walk!(:backward)
.
Plan #1
For this level, I want to:
- Walk backward until I see a captive, then rescue them
- Go forward and attack/rest as I have before
Doesn’t sound too hard, right? There’s a little wrinkle here. I need to manage some state. The state of a program is how you keep track of what things have already happened.
In this level, I need to go backwards and try to rescue a captive until I rescue one. Then I can go forward and kill.
My first idea is to track a @captives_rescued
instance variable that I’ll start at 0
and increase when I rescue someone. If the value is 0
, I know I need to be working backwards. If it’s greater than 0
, it’s killing time.
Code #1
Here’s ruby code for a captives_rescued?
method:
1 2 3 4 5 6 7 8 9 10 |
|
NOTE: That method can be shortened to just two lines of code if you’re slick :)
Now, back in the play_turn
, I’m going to implement something like this:
1 2 3 4 5 6 7 8 9 10 |
|
Run #1
The part about rescuing the captive then moving forward went fine, but I still died. I read through the turns and see that, when I kill the thick sludge, I have only 8 HPs left.
I then take a rest before charging, but the archers start shooting me. I can’t kill them fast enough while they’re shooting me, and I die.
Plan #2
I can see that the archers only start attacking once the giant sludge is dead. If there’s no obstruction, I’m not sure if they can attack across the whole level or only across two spaces.
I’m thinking that I need to change my being_attacked?
/charge
logic. Maybe if I’m being attacked and my health is below some threshold, I should retreat and rest up. Here’s my idea in pseudocode:
1 2 3 4 5 6 7 8 9 |
|
I really only have to add the nested if
statement for the if my health is low
, the rest is already there.
Run #2
I implemented the logic and ran it, but got to a problem. Once the sludge was dead, the archers attacked me. I backed up, rested for a turn, then charged. They attacked, my health got low, so I retreated and rested again.
I was in an infinite loop because my threshold of "how low do I need to be to rest" and "when should I stop resting" were the same.
I stopped the program by pressing Control-c
and changed the thresholds so that I’d get to a higher health before moving forward and lowered the retreat threshold.
By tweaking those values a few times I ended up with these results:
1 2 3 4 5 |
|
FYI, I had to retreat after killing the sludge, then again after killing the first archer.
Level 7
README
The level itself looks simple, except that we need to turn around first.
The Plan
Let’s check for a wall first, and if it’s found, turn around. We can remove (or maybe comment-out) the bits about captives_rescued
.
Then just run our existing strategy.
The Code
This one is up to you!
The Run
Mine passed on the first try:
1 2 3 4 5 |
|
Refactorings
That play_turn
method is getting big again. Let’s extract a few methods.
charge_or_retreat
I extracted a method that gets run when the warrior is being attacked which causes him to either retreat or charge.
move_or_interact
I extracted a method that is run when the warrior is NOT being attacked which checks whether the next space is empty and either rests, walks, rescues, or attacks.