Method behaving differently for 2 classes using same module method - ruby

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

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.

Trouble getting tests to run properly

I've been trying to test a program that simulates an elevator for two days now with little success. Here's my elevator class, the program is still a work in progress and I've also commented out some methods that might not be essential to the test I'm having trouble with. I'll gladly show more code if you think it's needed
class Elevator
attr_accessor :current_floor
GROUND = 0
TOP = 15
def initialize
#floors = [] # list of floors to travel to
#pending = [] # store floors not in direction of travel
#current_floor = GROUND
#going_up = true # cannot travel downward from ground floor
#going_down = false
end
def get_input
gets.chomp
end
def run
enter_floors
sort_floors
move_to_floor
end
def enter_floors
# prompts the user for input and calls check_floor_numbers
end
def check_floor_numbers floors
# calls validate_floors to ensure user entered '2' instead of 'two'
# if proper floor numbers have been entered this method adds the number
# to #floors array other wise it calls floor_error_message
end
def floor_error_message
puts "Please enter numbers only."
enter_floors
end
def sort_floors
# if we are on the ground floor this method sorts #floors in ascending order
# if we are on the top floor it sorts #floors in descending order
# else it calls check_direction_of_travel
end
def move_to_floor
floor = #floors[0]
if #current_floor == floor
puts "You are already on floor #{floor}"
else
print_direction
(#current_floor..floor).each { |floor| puts "...#{floor}" }
#current_floor = floor # update current_floor
#floors.delete_at(0) # remove floor from list
end
check_for_more_passengers
end
def check_for_more_passengers
puts "Are there any more passengers? (Y/N)"
answer = (get_input).upcase
answer == 'Y' ? run : check_next_move
end
def check_next_move
if #floors.empty? && #pending.empty?
end_ride
else
move_to_floor
end
end
def check_direction_of_travel
# not implemented - add floor to appropriate array depending on dir
# of travel
end
def end_ride
puts "\n\nEND."
end
def print_direction
msg = " "
#going_up ? msg = "Going Up!" : msg = "Going Down!"
puts msg
end
end
I'm trying to test that the elevator can move to a specific floor. At first I was having trouble testing input from the console without running the program itself. I asked a question about this and was referred to this answer in a different question. The answer in question extract gets.chomp to a separate method then overrides the method in the tests. I ended up with something like this:
describe "it can move to a floor" do
before do
##moves = ["2", "N"]
def get_input; ##moves.next end
end
it "should move to floor 2" do
e = Elevator.new
e.run
assert_equal(e.current_floor, 2)
end
end
Problem: get_input was not properly overidden and running the test suit prompted the user for input so it was suggested that I open the Elevator class in the test itself to ensure that the method was properly overridden. Attempting to do so eventually led to a test like this:
describe "it can move to a floor" do
before do
class Elevator
attr_accessor :current_floor
##moves = ["2", "N"]
def get_input; ##moves.next end
def run; end
end
end
it "should move to floor 2" do
e = Elevator.new
e.run
assert_equal(e.current_floor, 2)
end
end
I had to override run and add an attr_accessor for current_floor because I was getting method missing errors.
Problem: This test gives the following error:
1) Failure: it can move to a floor#test_0001_should move to floor 2
[elevator_test.rb:24]: Expected: nil Actual: 2
I've tried to tidy up the Elevator class as much as possible and keep the methods as simple as I could given the parameters of the program.
Can anyone point me in the right direction towards getting this solved, with maybe pseudocode examples (if possible) to demonstrate how I should approach this problem if the answer is to refactor.
Please bear in mind that I'd also like to implement other tests like checking that the elevator class can maintain a list of floors, or that it can change direction, in the future when you answer.
Your test class ElevatorTest is redefining the Elevator to override method get_input, but it is not opening the class Elevator defined in elevator.rb, but instead it is sort of creating a new class Elevator which happens to be defined inside the class ElevatorTest. Remember every class is also a module, so now you have a new class ElevatorTest::Elevator.
To fix this issue, I have made some changes to elevator_test.rb which is shown below.
gem 'minitest', '>= 5.0.0'
require 'minitest/spec'
require 'minitest/autorun'
require_relative 'elevator'
class Elevator
##moves = ["2", "N"].each
def get_input; ##moves.next end
end
class ElevatorTest < MiniTest::Test
def test_working
assert_equal(1, 1)
end
describe "it can move to a floor" do
before do
end
it "should move to floor 2" do
e = Elevator.new
e.run
assert_equal(e.current_floor, 2)
end
end
end
Also, please remember to use .each while defining ##moves - it returns an enumerator. We can call .next only on an enumerator

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

Why does this Ruby code return the object id as well as the method I am calling?

I am currently doing pragmatic studios course and this is some code I wrote for one of the exercises. When I run the code I get an object id in addition to the method I intended to call. The exercise creates a game where you can increase or decrease a players health using a blam or w00t method.
class Player ##creates a player class
attr_accessor :fname, :lname, :health
def initialize fname, lname, health=100
#fname, #lname, #health = fname.capitalize, lname.capitalize, health
end
def to_s
#defines the method to introduce a new player
#replaces the default to_s method
puts "I'm #{#fname} with a health of #{#health}."
end
def blam #the method for a player to get blammed?
#health -= 10
puts "#{#fname} got blammed!"
end
def w00t #the method for a player getting wooted
#health += 15
puts "#{#fname} got w00ted"
end
end
larry = Player.new("larry","fitzgerald",60)
curly = Player.new("curly","lou",125)
moe = Player.new("moe","blow")
shemp = Player.new("shemp","",90)
puts larry
puts larry.blam
puts larry
puts larry.w00t
puts larry
My output looks like this:
I'm Larry with a health of 60.
#<Player:0x10e96d6d8>
Larry got blammed!
nil
I'm Larry with a health of 50.
#<Player:0x10e96d6d8>
Larry got w00ted
nil
I'm Larry with a health of 65.
#<Player:0x10e96d6d8>
I can't figure out why the object ids (or nil) is printed to the console when I run the code.
Thanks!
Because your functions should be returning strings, but they are actually returning the result of puts "something"
do this instead:
def w00t
#health += 15
"#{#fname} got w00ted"
end
Then when you do puts obj.w00t, it will perform the action, and return the string.
Every method in Ruby returns a value so the #blam method does return something. In this case it is the value of the last expression: the result of calling puts, which is nil. So when you puts larry.blam you are puts-ing the return of the last statement, puts, and thus nil.
Just remove some puts. Your methods prints everything on stdout, and you prints their result.

Resources