I have a confession to make, my code for Ruby Warrior Level 4 and 5 is broken! How did this happen? I made sure all the levels passed! It was through answering someone else’s question in a Lighthouse Labs forum that I realized my own mistake.
If you look at the above code, you’ll see I set @health in the Player class. Then in the play_turn function, I set @health = warrior.health if @health.nil? returns true. I initially created the variable in the class, thinking it would be passed down into any instances of Player. When the @health variable was nil… I just assigned it the value of warrior.health… that fixed the problem, but why was it nil?
The real problem was that I did not understand what I was doing with the @health variable in the first place. When I set the @health variable in class Player, I incorrectly assumed it would be passed down to warrior (since warrior is-a Player). This is WRONG.
I went back to the documentation for Class.The very first line says, classes are first-class objects in Ruby. Every class is an instance of Class. When a new class is created, such as our class Player, an object of type Class is created and assigned to the global constant, in this case player.
This is very important, because every class is an instance of Class, it is an object. What does that mean? A class, as an instance, has its own instance variables.
If we look at our code, we have a class Player and a warrior object. The warrior object is an instance of the class Player. But the Player class and the warrior object are two different objects and have their own instance variables.
Here’s an example. I create a class Character and give it an instance variable of @name with the value “Disney Name”. I then create a class function that prints the name to the screen. When I call sayname() on the Character class, we get “Disney Name”. Then, I create an instance of Character and assign it to mickey. It should have the same name right? Nope. If we call the sayname() function on mickey, we see that the @name variable is nil.
This is exactly how I went wrong in Ruby Warrior. I set @health in the class Player, and then wrongly assumed that the warrior would have a @health variable. Class instance variables are specific to that instance!
In the first line of play_turn(), @health is nil for warrior because it was never created. If we want each instance of class Player to have a @health instance variable, we have to put that in the initialize function. Then, every time a new instance of Player is created, that instance will have its own @health instance variable.
I fixed my code for Ruby Warrior Level 4 as below:
I set @health to 0, so that under_attack will return false in the very first turn. (Because warrior.health will not be less than 0 in the first turn.) Then at the end of the each turn, I update @health to warrior.health for comparison in the next turn.
I also moved the min_health variable from the class to the should_walk? function. When I tried to run the original code again, it gave me an undefined variable error for line 24. This makes sense because the function could not access the min_health variable. I did not put min_health as an instance variable because it is a constant and would not vary between different instances.
For Level 5, captives are added. If the space before the warrior is not empty, we want to check whether it’s a captive. We’ll rescue if it’s a captive, otherwise we attack!