Use a method from one class within method from another class? - ruby

I've created this game thing in order to learn OOP and I'm having trouble with part of it. Here's what's causing me problems:
I have two classes. On line 3 of class Player, I have some code which is probably way wrong, but basically, what I'm trying to do is use armor to modify how much damage a player receives. I'm getting an error, though: "undefined method 'protection' for nil:NilClass (NoMethodError)
I have Armor as another class. I think the problem might relate to the fact that I am calling #armor.protection when protection is mentioned in Armor and #armor is mentioned in Player, but I am unsure how to fix this. I have added all the code I believe is relevant to my question below. Like I said, I'm really new at this, so please use terminology a noob could understand.
class Player
def equip(armor)
#armor = armor
end
def hit(damage)
#damage = damage - #armor.protection
#health -= damage
end
end
class Armor
def initialize(name, protection)
#protection = protection
end
end
EDIT: added additional code to show all of what I've got going on for clarification. I don't expect anyone to read all of what I've got, though. :S It's probabably scary and snarled up. :P
class Player
def initialize(name, health)
#name = name
#health = health
end
def equip(armor)
#armor = armor
end
def health
#health
end
def health=(value)
#health = value
end
def hit(damage)
damage = damage - #armor.protection
#health -= damage
end
def dead?
if #health <= 0
return true
elsif #health > 0
return false
end
end
def name
#name
end
def attack(target)
damage = rand(30)
puts "#{#name} attacks #{target.name}"
target.hit(damage)
puts "#{#name} hits #{target.name} for #{damage} damage."
end
end
class Armor
def initialize(name, protection)
#protection = protection
end
end
player1 = Player.new("Melanie", 100)
player2 = Player.new("a Monster", 200)
shirt = Armor.new('shirt', 4)
player1.equip(shirt)
while player1.dead? == false && player2.dead? == false
player1.attack(player2)
if player2.health > 0
puts "#{player2.name}'s health is at #{player2.health}."
elsif player2.health <= 0
puts "#{player2.name} has no health."
end
player2.attack(player1)
if player1.health > 0
puts "#{player1.name}'s health is at #{player1.health}."
elsif player1.health <= 0
puts "#{player1.name} has no health."
end
end
if player1.health > player2.health
puts "#{player2.name} is dead."
puts "#{player1.name} wins."
elsif player2.health > player1.health
puts "#{player1.name} is dead."
puts "#{player2.name} wins."
elsif player2.health == player1.health
puts "#{player1.name} and #{player2.name} killed each other."
end

If your Armor class has a protection method, it would work fine. However it doesn't, so even if you were to call it from inside the Armor class, you'd get the same error. To define it you can either use attr_reader or attr_accessor or define it by hand.
class Armor
attr_accessor :protection
def initialize(name, protection)
#protection = protection
end
end
or
class Armor
def initialize(name, protection)
#protection = protection
end
def protection
#protection
end
end

I just ran your 2nd (full) example.
Besides the accessor problem explained in the other answers (just add attr_reader :protection to class Armor), you overlooked something in the test scenario :)
The error message gives a hint: undefined method 'protection' for nil:NilClass (NoMethodError). Given that this is caused in the 1st line of the hit method, this means #armor was nil, and of course nil is not an instance of Armor, so it has no protection method. Why was it nil? Well, look at how your fight begins:
player1 = Player.new("Melanie", 100)
player2 = Player.new("a Monster", 200)
shirt = Armor.new('shirt', 4)
player1.equip(shirt)
Only Melanie has a shirt, and you gave the monster no armor at all! Not very fair, is it :)
To fix that, you either need to give him some armor, or change your hit method so that it still works when #armor was not initialized. A nice OO way of doing that is to initialize all players with a default dummy armor that provides no protection:
class Player
def initialize(name, health)
#armor = Armor.new('nothing', 0)
# ...
Done!
Now, since that dummy armor will be useful whatever the specific rules of your game are, I'd abstract it from the point of view of class Player, by making class Armor responsible for creating it instead:
class Armor
class << self # define a class method, like new
def none
self.new('nothing', 0)
end
end
# ...
Then you can just say Armor.none instead of Armor.new('nothing', 0) in Player.initialize. This way, if you need to change how Armor works internally, you can update the dummy armor at the same time, and without touching other classes.

The problem here is that you have a #protection instance variable, but no "accessor" to get to it. Instance variables are private to the instance of the class they're owned by, so if you want to expose them to the outside world, you have to set up accessors to do so. In this case, you want:
class Armor
attr_reader :protection
...
end
This will let you call #armor.protection, and will return the value of your armor instance's #protection variable.

Try this:
player = Player.new
armor = Armor.new('Mythril', 100)
player = player.equip(armor) #Initialise the armor object inside Player.
player.hit(10)

Related

How i can solve this issue ? undefined method for class

Have a problem, when run a code allways have error.
Expect: for the user add win or reduce his balance.
undefined method `balance=' for #<Dice:0x0000563d4d4dfd88 #name="foo", #balance=600, #bet=300>
Did you mean? balance
(repl):22:in `increase_decrease_cash'
(repl):62:in `<class:Game>'
(repl):29:in `<main>'
This error always comes out, retried everything I could guess, but nothing came of it and I don’t understand how it can be googled
class Dice
attr_accessor :name, :bet
attr_reader :balance
def initialize(name, balance, bet)
#name = name
#balance = balance
#bet = bet
end
def self.roll
#roll_dice = rand(1..2)
end
def self.check_bet
if #player.bet > #player.balance
puts "Enter number from 1 to #{#player.balance}"
end
end
def self.increase_decrease_cash
if #roll == #my_number
#player.balance += #player.bet
else
#player.balance -= #player.bet
end
end
end
class Game < Dice
#player = Dice.new("foo", 600, 0)
puts "Hello #{#player.name} your balance is: #{#player.balance}"
puts "Bones throwing count times"
a = 2 #gets.chomp.to_i
while a > 0 do
puts ""
puts "Enter your bet !!!"
# PLAYER BET
#player.bet = 300 #gets.chomp.to_i
check_bet
puts "Respected #{#player.name} your bet is: #{#player.bet}"
puts "Now select number 1-2"
# BONES ROLL
#my_number = roll # gets.chomp.to_i
puts "###################"
puts "Now we throw bones"
#roll = roll
puts "Nuber is #{roll}"
if #roll == #my_number
puts "Your win, you get #{#player.bet}"
else
puts "You lose #{#player.bet}"
end
p "$$$$"
p #player.balance
p "$$$$"
a -= 1
increase_decrease_cash
end
end
This error always comes out, retried everything I could guess, but nothing came of it and I don’t understand how it can be googled
attr_reader creates only the get method for balance. You need both get and set method for balance. Because you set balance in the initialize method. So, you should use attr_accessor instead of attr_reader.
attr_accessor :balance

Passing argument in instance variable in Ruby gets error

class Account
attr_reader :name
attr_reader :balance
def initialize(name, balance=100)
#name = name
#balance = balance
end
public
def display_balance(pin_number)
if pin_number == pin
puts "Balance: $#{#balance}."
else
puts pin_error
end
end
def withdraw(pin_number,amount)
if pin_number == #pin
#balance -= amount
puts "Withdrew #{amount}."
else
puts pin_error
end
end
def deposit(pin_number,amount)
if pin_number ==#pin
#balance+=amount
puts"Deposited"
else
puts pin_error
end
end
private
def pin
#pin = 1
end
def pin_error
return "Access denied: incorrect PIN."
end
end
checking_account=Account.new("bob",200)
checking_account.deposit(1,20)
When i try deposit i get an error on pin, but when i remove the # in the pin checks and treat it as a normal variable it works. In codeacademy it shows that the correct way to check the pin is with
if pin_number==#pin
yet it doesn't work, even though it should, why is that?
pin_number==#pin will return false, because you never set #pin - so it will still be nil.
Instead of using a private method, you could do something like this:
def initialize(name, pin, balance=100)
#name = name
#pin = pin
#balance = balance
end
# ...
checking_account=Account.new("bob", 1, 200)
checking_account.deposit(1, 20)
yet it doesn't work, even though it should, why is that?
Because your #pin is never initialized. Something must be different between your code and what they have at codecademy (they likely initialize #pin in the initializer, which you do not).
but when i remove the # in the pin checks and treat it as a normal variable
Wrong.. This is not a variable. When you make it pin instead of #pin, it starts pointing to that private method of yours. Which returns a pin number. That's why it works.
Note that your code will work as-is, if you simply display_balance before making a deposit. This is called "call order dependency" and it's bad.

Why this instance variable is not incrementing?

This short code is working when I'm using class variable ##points instead of #points. I wonder why it's happening like this? Someone can explain me? It looks like #points is always nil.
class Game
#points = 0
def start
until #points == 10
puts "Guess number between 0 and 10:"
num = gets.chomp.to_i
break if #points == 0
guess_number(num)
puts "Your score is: #{#points}"
end
end
def guess_number(num)
#points += 1 if num == rand(0..10)
end
end
game = Game.new
game.start
Because #points is a class instance variable, and to access it from within the instance method's scope you'd have to either do
self.class.instance_variable_get(:#points)
or define an attr_accessor in Game's singleton_class
class Game; class << self; attr_accessor :points; end; end
and then you'd be able to do
self.class.points
But neither of these is what you really want.
code is working when I'm using class variable ##points instead of
#points
It is working, because you do have access to class variable from within a scope of an instance methods.
It looks like #points is always nil
It is always nil, because you never defined an instance variable #points, but, as said, class instance variable.
So these three things are different (you could read up something about Ruby scoping - do not mix with AR scopes):
class variable
class instance variable
instance variable
To solve it there are many ways, but if you want to keep it on the instance level, wrap #points into a method:
def points
#points ||= 0
end
And then use it as points - now it'll work as you are expecting.
Thanks to answer by Andrey Deineko. I came up with such solution to use instance variable here.
class Game
def initialize
#points = 0
end
def start
until points == 10
puts "Guess number between 0 and 10:"
num = gets.chomp.to_i
break if points == 10
guess_number(num)
puts "Your score is: #{points}"
end
end
private
def guess_number(num)
if num == rand(0..10)
increment_points
end
end
def points
#points
end
def increment_points
#points += 1
end
end
game = Game.new
game.start

Passing variables between classes in Ruby

I am trying to figure out how to pass variables between classes in Ruby. The example I am working on now is a game, where the players health, equipment, etc keeps changing and is passed from scene to scene until the game is over. Here is what I have so far:
class Player
def enter()
end
end
class MyPlayer < Player
def initialize()
dog_biscuits = false
end
end
class Scene
def enter()
end
end
class Entrance < Scene
def enter(player)
puts "You are in the entrance"
if player.dog_biscuits == false
puts "You don't have any biscuits."
end
end
end
player = MyPlayer.new
entrance = Entrance.new
entrance.enter(player)
Whenever I run this, I get the following error message:
entrance.rb:20:in `enter': undefined method `dog_biscuits' for #<MyPlayer:0x007fbfe2167f20> (NoMethodError)
I am running ruby 2.2.3p173 on OSX El Capitan.
Do this:
class MyPlayer < Player
attr_accessor :dog_biscuits
def initialize()
#dog_biscuits = false
end
end
Using attr_accessor will allow you to set and get instance variables. Remember also that you have to prefix instance variables with #.
class MyPlayer < Player
def initialize()
#dog_biscuits = false
end
def has_no_dog_biscuits?
#dog_biscuits == false
end
end
It is better to create method has_no_dog_biscuits? then to have attr_reader and to expose attribute to outer world, this way, you can always check if player has not dog_biscuits.

undefined method `[]' for nil:NilClass (NoMethodError) in tic-tac-toe game

I am building a Tic-Tac-Toe game to be played on the command line.
module TicTacToe
class Player
attr_accessor :symbol
def initialize(symbol)
#symbol = symbol
end
end
class Board
attr_reader :spaces
def initialize
#spaces = Array.new(9)
end
def to_s
output = ""
0.upto(8) do |position|
output << "#{#spaces[position] || position}"
case position % 3
when 0, 1 then output << " | "
when 2 then output << "\n-----------\n" unless position == 8
end
end
output
end
def space_available(cell, sym)
if spaces[cell].nil?
spaces[cell] = sym
else
puts "Space unavailable"
end
end
end
class Game < Board
attr_reader :player1, :player2
def initialize
play_game
end
def play_game
#player1 = Player.new("X")
#player2 = Player.new("O")
puts Board.new
#current_turn = 1
turn
end
def move(player)
while victory != true
puts "Where would you like to move?"
choice = gets.chomp.to_i
space_available(choice, player.symbol)
puts Board
#current_turn += 1
turn
end
end
def turn
#current_turn.even? ? move(#player2) : move(#player1)
end
def victory
#still working on this
end
end
end
puts TicTacToe::Game.new
The method that is to take a user's cell choice (space_available) and alter the array with their piece ('X' or 'O') is giving me an error. I can't find why my code is throwing this particular error.
The problem is that you don't call the parent constructor in your Game class, therefore #spaces is not initialized.
Your hierarchy decision is questionable, but to make it work, you can simply change the Game constructor to:
def initialize
super
play_game
end
You are calling spaces[cell]. The error is telling you that you are calling [] on nil, which means that spaces must be nil.
Perhaps you mean #spaces? Otherwise - you need to tell the program how spaces is defined and how it is initialized. A simple spaces = {} unless spaces would work
Another way of initialising your spaces variable would be to call super when you initialize the Game:
class Game < Board
attr_reader :player1, :player2
def initialize
super
play_game
end
...

Resources