Learn Ruby The Hard Way #43 - ruby

Can someone please explain me what is happening here? I know that this code is simple, but it is hard for me, I am ultimate beginner. I am stucked for many hours here, I cant figure it out..
When I run code, CentralCorridor class is run where we have basic gets.chomp prompt. All answers("shoot!", "dodge!", "tell a joke") for that prompt end program. I dont know how to enter to other scenes (class TheBridge, class EscapePod, class LaserWeaponArmory...). I really want to make my own game like this, but i cant figure it out..
Also, if someone can explain me this enter() method.
Please help :)
here is link: http://ruby.learncodethehardway.org/book/ex43.html
or code:
class Scene
def enter() # what this method do?
exit(1)
end
end
class Engine
def initialize(scene_map)
#scene_map = scene_map
end
def play()
current_scene = #scene_map.opening_scene()
last_scene = #scene_map.next_scene('finished')
while current_scene != last_scene
next_scene_name = current_scene.enter()
current_scene = #scene_map.next_scene(next_scene_name)
end
# be sure to print the last scene
current_scene.enter()
end
end
class Death < Scene
##quips = [
"You died. You kinda suck at this",
"Your mom would be proud...if she were smarter.",
"Such a luser.",
"I have a small puppy that's better at this."
]
def enter()
puts ##quips[rand(0..(##quips.length - 1))] # 2. what is this?
exit(1)
end
end
class CentralCorridor < Scene
def enter()
puts "The Gothons of Planet Percal #25 have invaded your ship and destroyed"
puts "your entire crew. You are the last surviving member and your last"
puts "mission is to get the neutron destruct bomb from the Weapons Armory,"
puts "put it in the bridge, and blow the ship up after getting into an "
puts "escape pod."
puts "\n"
puts "You're running down the central corridor to the Weapons Armory when"
puts "a Gothon jumps out, red scaly skin, dark grimy teeth, and evil clown costume"
puts "flowing around his hate filled body. He's blocking the door to the"
puts "Armory and about to pull a weapon to blast you."
print "> "
action = $stdin.gets.chomp
if action == "shoot!"
puts "Quick on the draw you yank out your blaster and fire it at the Gothon."
puts "His clown costume is flowing and moving around his body, which throws"
puts "off your aim. Your laser hits his costume but misses him entirely. This"
puts "completely ruins his brand new costume his mother bought him, which"
puts "makes him fly into an insane rage and blast you repeatedly in the face until"
puts "you are dead. Then he eats you."
return 'death'
elsif action == "dodge!"
puts "Like a world class boxer you dodge, weave, slip and slide right"
puts "as the Gothon's blaster cranks a laser past your head."
puts "In the middle of your artful dodge your foot slips and you"
puts "bang your head on the metal wall and pass out."
puts "You wake up shortly after only to die as the Gothon stomps on"
puts "your head and eats you."
return 'death'
elsif action == "tell a joke"
puts "Lucky for you they made you learn Gothon insults in the academy."
puts "You tell the one Gothon joke you know:"
puts "Lbhe zbgure vf fb sng, jura fur fvgf nebhaq gur ubhfr, fur fvgf nebhaq gur ubhfr."
puts "The Gothon stops, tries not to laugh, then busts out laughing and can't move."
puts "While he's laughing you run up and shoot him square in the head"
puts "putting him down, then jump through the Weapon Armory door."
return 'finished'
else
puts "DOES NOT COMPUTE!"
return 'central_corridor'
end
end
end
class LaserWeaponArmory < Scene
def enter()
puts "You do a dive roll into the Weapon Armory, crouch and scan the room"
puts "for more Gothons that might be hiding. It's dead quiet, too quiet."
puts "You stand up and run to the far side of the room and find the"
puts "neutron bomb in its container. There's a keypad lock on the box"
puts "and you need the code to get the bomb out. If you get the code"
puts "wrong 10 times then the lock closes forever and you can't"
puts "get the bomb. The code is 3 digits."
code = "#{rand(1..9)}#{rand(1..9)}#{rand(1..9)}"
print "[keypad]> "
guess = $stdin.gets.chomp
guesses = 0
while guess != code && guesses < 10
puts "BZZZZEDDD!"
guesses += 1
print "[keypad]> "
guess = $stdin.gets.chomp
end
if guess == code
puts "The container clicks open and the seal breaks, letting gas out."
puts "You grab the neutron bomb and run as fast as you can to the"
puts "bridge where you must place it in the right spot."
return 'the_bridge'
else
puts "The lock buzzes one last time and then you hear a sickening"
puts "melting sound as the mechanism is fused together."
puts "You decide to sit there, and finally the Gothons blow up the"
puts "ship from their ship and you die."
return 'death'
end
end
end
class TheBridge < Scene
def enter()
puts "You burst onto the Bridge with the netron destruct bomb"
puts "under your arm and surprise 5 Gothons who are trying to"
puts "take control of the ship. Each of them has an even uglier"
puts "clown costume than the last. They haven't pulled their"
puts "weapons out yet, as they see the active bomb under your"
puts "arm and don't want to set it off."
print "> "
action = $stdin.gets.chomp
if action == "throw the bomb"
puts "In a panic you throw the bomb at the group of Gothons"
puts "and make a leap for the door. Right as you drop it a"
puts "Gothon shoots you right in the back killing you."
puts "As you die you see another Gothon frantically try to disarm"
puts "the bomb. You die knowing they will probably blow up when"
puts "it goes off."
return 'death'
elsif action == "slowly place the bomb"
puts "You point your blaster at the bomb under your arm"
puts "and the Gothons put their hands up and start to sweat."
puts "You inch backward to the door, open it, and then carefully"
puts "place the bomb on the floor, pointing your blaster at it."
puts "You then jump back through the door, punch the close button"
puts "and blast the lock so the Gothons can't get out."
puts "Now that the bomb is placed you run to the escape pod to"
puts "get off this tin can."
return 'escape_pod'
else
puts "DOES NOT COMPUTE!"
return "the_bridge"
end
end
end
class EscapePod < Scene
def enter()
puts "You rush through the ship desperately trying to make it to"
puts "the escape pod before the whole ship explodes. It seems like"
puts "hardly any Gothons are on the ship, so your run is clear of"
puts "interference. You get to the chamber with the escape pods, and"
puts "now need to pick one to take. Some of them could be damaged"
puts "but you don't have time to look. There's 5 pods, which one"
puts "do you take?"
good_pod = rand(1..5)
print "[pod #]> "
guess = $stdin.gets.chomp.to_i
if guess != good_pod
puts "You jump into pod %s and hit the eject button." % guess
puts "The pod escapes out into the void of space, then"
puts "implodes as the hull ruptures, crushing your body"
puts "into jam jelly."
return 'death'
else
puts "You jump into pod %s and hit the eject button." % guess
puts "The pod easily slides out into space heading to"
puts "the planet below. As it flies to the planet, you look"
puts "back and see your ship implode then explode like a"
puts "bright star, taking out the Gothon ship at the same"
puts "time. You won!"
return 'finished'
end
end
end
class Finished < Scene
def enter()
puts "You won! Good job."
end
end
class Map
##scenes = {
'central_corridor' => CentralCorridor.new(),
'laser_weapon_armory' => LaserWeaponArmory.new(),
'the_bridge' => TheBridge.new(),
'escape_pod' => EscapePod.new(),
'death' => Death.new(),
'finished' => Finished.new(),
}
def initialize(start_scene)
#start_scene = start_scene
end
def next_scene(scene_name)
val = ##scenes[scene_name]
return val
end
def opening_scene()
return next_scene(#start_scene)
end
end
a_map = Map.new('central_corridor')
a_game = Engine.new(a_map)
a_laser = EscapePod.new()
a_game.play()

Aleksar
You have a game with a number of components:
The Engine - the main entry point to the game (you can tell because it has the play method)
You've got a Map object which is just a collection of the different scenes that can be played in the game. Each scene is just another scenario in the game with its own challenges. E.g. in on scene you are on the bridge in another you are in an escape pod. In another you have a weapon whatever.
All of scenes in the game inherit their behavior from the Scene class as in this example.
class EscapePod < Scene
#....
#Scene stuff
#...
end
The code above basically says create a class called EscapePod that can do everything that a Scene can do.
What is throwing you off is that the Scene class doesn't do much. It has only one behavior (this is a slight over simplification) which is it that it knows how to do something called enter which in this case just immediately exits. This Scene class is a template for all of the other scenes that are more interesting.
class Scene
def enter() # what this method do?
exit(1)
end
end
Each scene subclass overrides the enter method from the template to do something more interesting when enter is called rather than just exiting.
The next simplest scene is the Finish scene it is a little more interesting it tells you did a good job.
class Finished < Scene
def enter()
puts "You won! Good job."
end
end
One last thing. It is clear that intention is not to use Scene class directly but only the subclasses EscapePod, Finish etc. which must override the enter method.
If this were production code you might even see something like this:
class Scene
def enter() # what this method do?
raise "Scene 'enter' must not be used directly.Please subclass Scene and provide a meaningful implementation for the 'enter' method"
end
end
I hope this helps.

Related

How do I get my game to run properly (battle system)?

As a beginner learning to program, it is extremely helpful to have such a supportive community out there!
I am having trouble getting this 'sample' game working. I am trying to develop a battle system where the player comes across opponents as they progress through a number of rooms. For some reason, when I run it on command prompt, it simply displays "you died" then exits. I am not sure where to go from here.
Any help would be greatly appreciated.
class Player
attr_accessor :hit_points, :attack_power
def initialize(hit_points, attack_power)
#hit_points = hit_points
#attack_power = attack_power
end
def alive?
#hit_points < 1
death
end
def hurt
#hit_points -= Opponent.attack_power
end
def print_status
puts "*" * 80
puts "HP: #{hit_points}/#{MAX_HIT_POINTS}"
puts "*" * 80
end
end
class Death
puts "You died"
exit(1)
end
class Opponent
def initialize (hit_points, attack_power)
#hit_points = hit_points
#attack_power = attack_power
puts "you come across this awful opponent"
end
def alive?
#hit_points < 1
death
end
def hurt
#hit_points -= player.attack_power
end
def interact(player)
while player.alive?
hurt
break if #hit_points < 1
alive?
end
if player.alive?
print "You took #{player_damage_taken} damage and dealt #{player_damage_done} damage, killing your opponent."
room
else
death
end
end
end
class Room
puts "you are now in the scary room, and you see an opponent!"
puts "You come accross a weaker opponent. It is a fish."
puts "Do you want to (F)ight or (L)eave?"
action = $stdin.gets.chomp
if action.downcase == "f"
fish = Opponent.new(2, 1)
fish.interact
else
death
end
end
Player.new(200, 1)
Room.new
class Engine
end
This is breaking because Death is a class and all the code within it is in the body of the class. That means this code will be executed when the class is defined, not at the time that death is called.
You haven't defined a method named death.
Because the Death class is tiny, and it would be awkward to name a method within it that stops the game (Death.death, Death.die, Death.run, Death.execute... Not great), and you don't need any of the advantages of a class (such as multiple instances or attributes stored in instance variables), I suggest you make the death action a part of the Player class.
class Player
# ...
def die
puts "You died"
exit(1)
end
end
Then when you've called death (the currently undefined method) Replace it with player.die.
As noted by #Kennycoc, you'll need to define a method for the death of an enemy, too.
So, it seems like you're under the impression that the code in the top level of the class is run when the class is instantiated (Class.new). This is not the case! Everything in the top level of the class is run as it is defined.
The simplest fix to get this running would be to add all the code in your top level of each class under a method named initialize this is what's run when the class is instantiated.
Also, you're using your Death class as if it were a method. You could either change it from class Death to def death or change your calls to Death.new after moving the code to the initialize method (this is not a normal pattern, but would work).

Can't access array defined outside my ruby function

How can I access an array defined outside the function? I've tried adding $coins.length as I had read somewhere for global variable — it didn't work. The reason I define the array outside is because there are other functions below that will be pushing a new item into that same array.
coins = []
def start
puts "It's a sunny day. It's warm and you feel great."
puts "You've picked up your son from creche. He's in a good mood and you've got home no problem."
puts "This is when troubles start, and you know it."
puts "You go out of the elevator and before you reach the door... 'give me the keeys'!"
puts "Do you give him the keys? (Y/N)"
print "> "
while keys = gets.chomp.downcase
case keys
when "y"
puts "you are in, good job. And you get a coin"
coins.push("1")
puts "you now have #{coins.length} coins"
room
when "n"
cry("I wanted to dooooooo iiiiiiiit!")
else
again
end
end
end
Ruby is an object oriented language. Even more, Ruby appeared on the scene with a motto “everything is an object.” Even numbers and (sic!) nil in Ruby are objects:
▶ 42.class
#⇒ Fixnum < Integer
▶ nil.__id__
#⇒ 8
So, you are supposed to use objects for anything that is slightly more complicated than a one-liner. Objects have a lot of goodness out of the box: instance variables, lifecycle, etc.
class Game
def initialize
#coins = []
end
def add_coin(value)
#coins << value
end
def coins
#coins
end
def amount
#coins.size
end
end
Now you might create in instance of this class and while it’s alive, it will hold the value of #coins:
game = Game.new
game.add_coin("1")
puts game.coins
#⇒ ["1"]
puts game.amount
#⇒ 1
game.add_coin("1")
game.add_coin("2")
puts game.coins
#⇒ ["1", "1", "2"]
puts game.amount
#⇒ 3
instead of defining global variable, try to define a method which you can use everywhere, like:
def coins
#coins ||= []
end
def start
puts "It's a sunny day. It's warm and you feel great."
puts "You've picked up your son from creche. He's in a good mood and you've got home no problem."
puts "This is when troubles start, and you know it."
puts "You go out of the elevator and before you reach the door... 'give me the keeys'!"
puts "Do you give him the keys? (Y/N)"
print "> "
while keys = gets.chomp.downcase
case keys
when "y"
puts "you are in, good job. And you get a coin"
coins.push("1")
puts "you now have #{coins.length} coins"
room
when "n"
cry("I wanted to dooooooo iiiiiiiit!")
else
again
end
end
end
This way, the method will not be global and other methods can also access this method(coins) within the file.

Restart Ruby program internally

I'm writing a very simple dungeon adventure game in Ruby (practice for a newb). I want to address the player by name throughout, so naturally I want to be sure that if a player passes an empty name to my Player initialize method if tells them they can't do that and then prompts them to retry.
class Player
attr_accessor :name, :location
def initialize(name)
if name.empty? == false
#name = name
else
puts "You did not enter your name! Try again, please"
load 'game.rb'
end
end
end
The file name is 'game.rb' so I'm basically reloading the entire file here each time the player decides not to enter their name. Which is stupid...
It works, but in the worst way...I had to be 'clever' about where I exited the program so that the player would be insulated from the fact that I'm basically going all Inception on everyone and launching a game within a game every time the player starts a new game and neglects to enter their name. For instance, if they don't notice 3 times that they need to input their name because they're not paying attention, I effectively have 4 games running (the original, and the 3 the prompted by not entering their name) and they'd either need to end each of those games when they're tired of playing, or else I had to basically exit the whole thing hard at one keyword.
My question is this: is there a way to write my error "You did not enter..." exit the current game session, and relaunch the game? All I really want to do is ensure an empty string doesn't get passed to my initialize method, maybe by raising an exception, and then start over at the beginning of the script without having a game-within-a-game.
Here is a link to the full code for more insight: http://repl.it/8QY
It's hard to know without seeing the whole game structure, but your main file could look like
game_initialized = false
while ! game_initialized
begin
# here, initialize the game, including initialization
game_initialized = true
rescue NoNameError
# do nothing, but it will restart the game
end
end
# now play the game
And in your constructor, you add
class NoNameError < Exception
end
class Player
attr_accessor :name, :location
def initialize(name)
if name.empty? == false
#name = name
else
puts "You did not enter your name! Try again, please"
raise NoNameError
end
end
end
That should get you going.
I think Vincent's answer is good, it certainly is more OO than mine but a simple approach would be to do something like this at game start:
print "Welcome! "
in_name = ""
while true
puts "What is your name?"
in_name = gets.chomp
in_name.empty? ? (puts "You must enter a name before continuing") : break
end
Example:
Welcome! What is your name?
You must enter a name before continuing
What is your name?
Anthony
If you want your logic to be in Player, you can try something like this:
class Player
attr_accessor :name, :location
def initialize(name)
#name = keep_asking_for_name_if_not_already_entered(name)
end
def keep_asking_for_name_if_not_already_entered(name)
return name unless name.empty?
loop do
puts 'You did not enter your name! Enter a name, please:'
name = gets.chomp
break(name) unless name.empty?
end
end
end

Learn Ruby the Hard Way ex.43 Extra Credit

I'm working through Learn Ruby the Hard Way by Zed Shaw and I wanted to see if I was on track with the first extra credit. He asks you to explain how returning the first room works. I think I understand most of it except how the .call() works. This is what I came up with, can anyone tell me if I'm getting it or if I'm off base?
class Game
def initialize(start)
#quips = [
"You died. You kinda suck at this.",
"Nice job, you died...jackass.",
"Such a luser.",
"I have a small puppy that's better at this."
]
#start = start
end
def prompt()
print"> "
end
def play()
next_room = #start
# While next_room = #start (only in the beginning), room then gets
# defined as room = method(next_room)
# a method is a set of expressions that returns a value.
# with methods, you can organize code into subroutines that
# can be easily invoked from other areas of their program.
# so, this method sets room to next_room, and next_room is then
# set to room.call()
# room.call() is then set by what is returned at the end of the code.
#at every room, it returns either death or
# the name of the next room/function
while true
puts "\n---------"
room = method(next_room)
next_room = room.call()
end
end
def death()
puts #quips[rand(#quips.length())]
Process.exit(1)
end
def central_corridor()
puts "The Gothons of Planet Percal #25 have invaded your ship and destroyed"
puts "your entire crew. You are the last surviving member and your last"
puts "mission is to get the neutron destruct bomb from the Weapons Armory,"
puts "put it in the bridge, and blow the ship up after getting into an "
puts "escape pod."
puts "\n"
puts "You're running down the central corridor to the Weapons Armory when"
puts "a Gothon jumps our, red scaly skin, dark grimy teeth, and evil clown costume"
puts "flowing around his hate filled body. He's blocking the door to the"
puts "Armory and about to pull a weapon to blast you."
prompt()
action = gets.chomp()
if action == "shoot!"
puts "Quick on the draw you yank our yoru blaster and fire it at the Gothon."
puts "His clown costume is flowing and moving around his body, which throws"
puts "off your aim. Your laser hits his costume but misses him entirely. This"
puts "completely ruins his brand new costume his mother bought him, which"
puts "makes him fly into an insane rage and blas you repeatedly in the face until"
puts "you are dead. Then he eats you."
return :death
elsif action == "dodge!"
puts "Like a world class boxer you dodge, weave, slipe and slide right"
puts "as the Gothon's blaster cranks a laser past your head."
puts "In the middle of your artful dodge your foot slips and you"
puts "bang your head on the metal wall and pass out."
puts "You wake up shortly after only to die as the Gothon stomps on"
puts "your head and eats you."
return :death
elsif action == "tell a joke"
puts "Luck for you they made you learn Gothon incults in teh academy."
puts "You tell the one Gothon joke you know:"
puts "Lbhe zbgure vf fb sng, jura fur fvgf nebhaq gur ubhfr, fur fgvf nebhaq gur ubhfr."
puts "The GOthon stops, tries not to laugh, then busts our laughing and can't move."
puts "While he's laughing you run up and shoot him square in the head"
puts "putting him down, then jump through the Weapon Armory door."
return :laser_weapon_armory
else
puts "DOES NOT COMPUTE!"
return :central_corridor
end
end
it execute the literal passed to the method as if they were called as a function
Here is an example
def test
"Hello World"
end
p method(:test).call
#=>"Hello World"
p method("test").call
#=>"Hello World"

Method behaving differently for 2 classes using same module method

I had a previous question that I asked here: I (think) I'm getting objects returned when I expect my array to have just 2 properties available
that may give some background. Having received a solution for that, I immediately moved to showing each player hand. The below module is included in both the Dealer and Player classes.
module Hand
def show_hand
if self.class == Dealer
#Need to cover up 1 of the 2 cards here. Dealer doesn't show both!
print "The dealer is showing: "
print self.hand[0].show_card
puts ''
elsif self.class == Player
print "You have: "
self.hand.each do |item|
item.show_card
end
puts ''
else
puts "A Random person is showing their hand."
end
end
end
I know this customization defeats the purpose of the module, just using it to reinforce the concept of modules. The above solution worked fine for the Dealer portion. But when the Player portion was called it printed a blank block. On a .inspect of each "item" in the Player block it confirmed that the items were in fact Card objects as expected. Here was the previous show_card method:
def show_card
"[#{#card_type} of #{#suit}]"
end
So it just returned a string with the card_type and suit.
I was able to fix the problem for the Player object portion just by changing the method to this:
def show_card
print "[#{#card_type} of #{#suit}]"
end
Why was this happening? I'm assuming it has something to do with the call the "each" on the Player Hand. Really just curious what the difference was and why these Card objects wouldn't print without the explicit "print" in there vice returning via String object.
Hope I was descriptive enough. This one just baffles me and I'm really trying to grasp these little things since I know it will prevent future errors like this. Thanks!
In Ruby you must print with puts or print. Just returning the string doesn't print it. The reason your Dealer class prints is because you did a print, but in your Player class, as you noted, you had no print. You only returned the string without printing.
As you noted, you were able to fix it by including the print:
def show_card
print "[#{#card_type} of #{#suit}]"
end
You could do this instead:
def show_card
"[#{#card_type} of #{#suit}]"
end
...
module Hand
def show_hand
if self.class == Dealer
#Need to cover up 1 of the 2 cards here. Dealer doesn't show both!
print "The dealer is showing: "
puts self.hand[0].show_card # PRINT THE CARD
elsif self.class == Player
print "You have: "
self.hand.each do |item|
print item.show_card # PRINT THE CARD
end
puts ''
else
puts "A Random person is showing their hand."
end
end
end
Which would be a little more "symmetrical" and prints what you want.
A slightly more compact would be:
def show_card
"[#{#card_type} of #{#suit}]"
end
...
module Hand
def show_hand
if self.class == Dealer
#Need to cover up 1 of the 2 cards here. Dealer doesn't show both!
puts "The dealer is showing: #{self.hand[0].show_card}"
elsif self.class == Player
print "You have: "
self.hand.each { |item| print item.show_card }
puts ''
else
puts "A Random person is showing their hand."
end
end
end

Resources