Not displaying certain parts of the initialize - Ruby - ruby

I am making a class, and when I try to run a command, some of the numbers will not show up.
class Summons
attr_reader :name, :strength, :health
attr_writer :name, :strength, :health
def initialize(name, strength)
#name = name
if #strength == "1"
#health = 25
#mana = 25
elsif #strength == "2"
#health = 50
#mana = 50
elsif #strength == "3"
#health = 100
#mana = 75
end
end
def health
puts "#{#name} has #{#health} health"
end
end
summon1 = Summons.new('Test', '1')
summon2 = Summons.new('Test2', '1')
summon3 = Summons.new('Test3', '1')
summon4 = Summons.new('Test4', '2')
summon5 = Summons.new('Test5', '2')
summon6 = Summons.new('Test6', '3')
This works fine, but when I run the health command, it would say "Test6 has health" and not give me the actual number.
def initialize(name, strength)
#name = name
#health = health <---------------
if #strength == "1"
#health = 25
#mana = 25
elsif #strength == "2"
#health = 50
#mana = 50
elsif #strength == "3"
#health = 100
#mana = 75
end
end
Adding the #health = health where the arrow is did not help, as it made it display everything under the class, but still no integer. Maybe it's the strength system I have set up, but I can't figure out what's wrong with it. Any help and explanation would be lovely.

Instance variables like #strength have a default value of nil.
So without setting a value, your
if #strength == "1"
is equivalent to:
if nil == "1"
which will obviously never be true. (same for the other comparisons)
To fix this, you could either use the passed argument which is hold by the local variable strength (without #):
def initialize(name, strength)
#name = name
if strength == "1"
# ...
end
end
Or you could assign the argument to #strength: (which you might want to do either way)
def initialize(name, strength)
#name = name
#strength = strength
if #strength == "1"
# ...
end
end
Note that in the latter example, you can use either strength or #strength in your if after the assignment because they are the same at that point.

Related

Getting parts of two classes to interact with a random chance?

I know I'm asking a lot of questions, and I apologize for that.
I am trying to get 2 classes to interact with each other, but with a random chance.
class Hands
attr_reader :name, :element, :skill, :mana, :health, :attack, :fire, :water, :lyfe, :summons
attr_writer :mana, :health
attr_accessor :summon
def initialize(name, element, skill)
#mana = 100
#health = 200
#summons = []
#name = name
#element = element
#skill = skill
end
def summon
#summons << summon
random_number = [1, 2].sample
if #element == "Lyfe"
if random_number == 1
puts "#{#name} summoned #{summon1.name}"
elsif random_number == 2
puts "#{#name} summoned #{summon2.name}"
else
puts "#{#name} can not use this ability!"
end
end
end
end
class Summons
attr_reader :name, :strength, :health
attr_writer :name, :strength, :health
attr_accessor :summon
def initialize(name, strength)
#name = name
#strength = strength
if #strength == "1"
#health = 25
#mana = 25
elsif #strength == "2"
#health = 50
#mana = 50
elsif #strength == "3"
#health = 100
#mana = 75
end
end
end
player1 = Hands.new('Silk', 'Lyfe', 'Summon')
player2 = Hands.new('Nubz', 'Lyfe', 'Manipulate Wildlife')
player3 = Hands.new('Lisk', 'Water', 'Invisible')
player4 = Hands.new('Azzi', 'Water', 'Manipulate Water')
player5 = Hands.new('Zeph', 'Fire', 'Lightning')
player6 = Hands.new('Ford', 'Fire', 'Manipulate Fire')
player7 = Hands.new('Boyd', 'Fire', 'Craft')
summon1 = Summons.new('Berto', '1')
summon2 = Summons.new('Wicket', '1')
summon3 = Summons.new('Skye', '1')
summon4 = Summons.new('Test4', '2')
summon5 = Summons.new('Test5', '2')
summon6 = Summons.new('Test6', '3')
player1.summon
I know I probably have some unnecessary code in there.
I am trying to get one of the players (Hands class) to randomly "summon" one of the summons. I tried using a random_number.sample command, but I got a stack level too deep error. The random_number code is shortened to 1 & 2 for testing purposes, but if it can somehow work and expand to all 6 that would be great.
Any explanation would help!
I got a stack level too deep error
That's because you call summon from within summon:
def summon # <--+
#summons << summon # |
# ^^^^^^------+ invokes itself over and over again
But let's start from the beginning. The Player#summon method needs to somehow access the "summons".
You could create an array of available summons, e.g.
available_summons = [
Summons.new('Berto', '1'),
Summons.new('Wicket', '1'),
Summons.new('Skye', '1')
]
And then pass that array to your summon method:
player1 = Hands.new('Silk', 'Lyfe', 'Summon')
player1.summon(available_summons)
Within your method you would sample from that array and add the sampled element to the player's #summons array: (I removed any additional logic for now)
def summon(available_summons)
summon = available_summons.sample
#summons << summon
puts "#{#name} summoned #{summon.name}"
end
Example with output:
player1 = Hands.new('Silk', 'Lyfe', 'Summon')
player1.summon(summons)
# Silk summoned Berto
player1.summon(summons)
# Silk summoned Skye
The #summons array will be populated as expected:
player1.summons
#=> [
# #<Summons:0x00007fbf1c9d80b8 #name="Berto", #strength="1", #health=25, #mana=25>,
# #<Summons:0x00007fbf1c9189c0 #name="Skye", #strength="1", #health=25, #mana=25>
# ]
Note that passing the array to Player#summon is just one possible solution. You could also pass it to Player.new (i.e. initialize) and store it in an instance variable. Or – if the array is rather static – you could store it right within the Player class, maybe in a constant. It really depends on how you want to structure your code.

How to make 2 entities of the same class interact?

I am trying to get 2 "people" of the same class to interact with each other, and change each other's "stats." However, I get the error, "formal argument cannot be a constant."
class Hands
attr_reader :name, :element, :skill, :mana, :health, :attack, :fire, :water, :lyfe
def initialize(name, element, skill)
#mana = 100
#health = 200
#name = name
#element = element
#skill = skill
end
def mana
sleep 1
puts "#{#name} has #{#mana} mana."
end
def health
sleep 1
puts "#{#name} has #{#health} HP."
end
def restore(Hands)
if #element == "Lyfe"
#mana = 100
puts "#{#name} has been restored!"
else
puts "#{#name} cannot use this ability!"
end
end
end
player1 = Hands.new('Silk', 'Lyfe', 'Summon')
player2 = Hands.new('Nubz', 'Lyfe', 'Manipulate Wildlife')
player3 = Hands.new('Lisk', 'Water', 'Invisible')
player4 = Hands.new('Azzi', 'Water', 'Manipulate Water')
player5 = Hands.new('Zeph', 'Fire', 'Lightning')
player6 = Hands.new('Ford', 'Fire', 'Manipulate Fire')
player7 = Hands.new('Boyd', 'Fire', 'Craft')
player1.restore("Nubz")
Here I am trying to get player1 to "restore" player2 back to full mana.
I know the code isn't perfect, but I'm not sure how else to do this.
Focussing on the restore command, the others work fine.
You should be able to pass the instance of the player you want affected to the restore method rather than the name string. You can then update that player's attributes as needed. A quick example:
Update class to make mana writeable:
class Hands
attr_reader :name, :element, :skill, :mana, :health, :attack, :fire, :water, :lyfe
attr_writer :mana
# ...
end
Update restore method to accept player instance:
def restore(hands)
if element == "Lyfe"
hands.mana = 100
puts "#{hands.name} has been restored!"
else
puts "#{name} cannot use this ability!"
end
end
Pass instance to method:
player1 = Hands.new('Silk', 'Lyfe', 'Summon')
player2 = Hands.new('Nubz', 'Lyfe', 'Manipulate Wildlife')
player1.restore(player2)
#=> Nubz has been restored!

attr_reader is not calling variable

I am building a game that is having a problem when calling a method. A monster can appear and will get a randomized weapon, and if that weapon is ranged the monster gets a one-turn setback to give the player a fighting chance. When the method monsterRangedTurnSetback is called I get the error that it is trying to find attributes in nil:NilClass. I ended up tracing it back to a genWeapon function, and that the function isn't being able to be called. Hers some code to
def monsterRangedTurnSetback(weapon)
attribs = weapon.attributes()
attribs.each do |attrib|
if attrib == "Ranged"
return 1
else
return 0
end
end
end
def genWeapon
weaponGen = rand(1..80)
if weaponGen == 1 or weaponGen == 2
weapon = GreatSword.new
hasTwoHandedWeapon = true
elsif weaponGen == (3..23)
weapon = ShortSword.new
hasTwoHandedWeapon = false
elsif weaponGen == (24..34)
weapon = ShortBow.new
hasTwoHandedWeapon = true
elsif weaponGen == (35..48)
weapon = LongBow.new
hasTwoHandedWeapon = true
elsif weaponGen == (49..64)
weapon = Dagger.new
hasTwoHandedWeapon = false
elsif weaponGen == (65..78)
weapon = HandCrossbow.new
hasTwoHandedWeapon = false
elsif weaponGen == 79 or weaponGen == 80
weapon = HeavyCrossbow.new
hasTwoHandedWeapon = true
end
return weapon
puts weapon.name
sleep 2
end
class Orc
attr_accessor :totalDamage, :totalHealth, :armorClass, :attackText, :name,
:turnSetback
def initialize
#wep = genWeapon()
#baseDamage = 7
#weapon = #wep
#turnSetback = monsterRangedTurnSetback(#weapon)
#wep = nil
#health = 5
#hasShield = shield(#weapon)
#armorClass = 6
if #hasShield == true
#armorClass += 2
end
#challengeLevel = 1
#attackText = ["Orc stabs you", "Orc slashes at you", "Orc intimidates you"]
#name = "Orc"
end
end
class ShortSword
attr_reader :attributes, :name, :attackBonus
def initialize
#attributes = ["Melee", "1Hand"]
attackBonus = 3
name = "Short Sword"
end
end
Yes the code goes in that order, and yes I know the monster class is allowing reading of non-existent variables. Any help is appreciated.
The mistake here might be that attr_reader cannot bind to local varaibles like x but only to instance variables like #x. In your code:
attr_reader :attackBonus
def initialize
# This is a local variable that will fall out of scope once the method
# finishes. It is not saved anywhere, simply thrown away.
attackBonus = 3
end
Adding an # prefix to that will make it persist and be readable.
The same thing plays out in genWeapon where local variables are set and discarded. If you need those persisted somehow you need to include them in the return. Those properties should be part of some kind of base Weapon class anyway, where you can call Dagger.new.two_handed? or Dagger.new.hands_required.
As #engineersmnky points out there's a crippling flaw in the genWeapon method where x == (1..2) will never return true for any value of x that isn't literally (1..2). What would work is (1..2).include?(x) or (1..2) === x. Since case uses === internally it makes it easy to write:
case (rand(1..80))
when 1..2
GreatSword.new
when 3..23
ShortSword.new
# ...
end
That's still really tedious. Instead write a lookup-table:
WEAPON_PROBABILITY = {
(1..2) => GreatSword,
(3..23) => ShortSword,
(24..34) => ShortBow,
(35..48) => LongBow,
(49..64) => Dagger,
(65..78) => HandCrossbow,
(79..80) => HeavyCrossbow
}.flat_map do |range, type|
range.to_a.map do |roll|
[ roll, type ]
end
end.to_h
This maps rolls to classes. Then your generator function becomes trivial:
def gen_weapon
WEAPON_PROBABILITY[rand(1..80)].new
end
Making use of Ruby's "everything is an object" principle to make look-up tables of classes dramatically simplifies things and can make the code more immediately understandable. Always try to steer your program towards defining things in terms of data instead of procedures whenever you can.
You might want to revisit how you define some of these classes. Perhaps even include the turn_delay as a method on the Weapon class. Here's how I might refactor this to inherit specialized weapons from a weapon parent class:
class Weapon
attr_reader :attributes, :name, :attack_bonus
def initialize
#attributes = []
end
def turn_delay?
#attributes.include? :ranged
end
def two_handed?
#attributes.include? :two_hand
end
end
class ShortSword < Weapon
def initialize
#attributes = %i(melee one_hand)
#attack_bonus = 3
#name = 'Short Sword'
end
end
class LongBow < Weapon
def initialize
#attributes = %i(ranged)
#attack_bonus = 10
#name = 'Long Bow'
end
end
bow = LongBow.new
puts bow.name
puts bow.turn_delay?
sword = ShortSword.new
puts sword.name
puts sword.turn_delay?
Output:
Long Bow
true
Short Sword
false
I had too much fun with this, a large number of weapons could become cumbersome to write class definitions for. Since you picked Ruby, you can embrace some meta programming and quickly whip up new weapons by using the following (requires you've defined that base Weapon class:
[
{ klass: 'BroadSword', attributes: [:melee, :two_hand], attack_bonus: 20, name: 'Broad Sword' },
{ klass: 'Dagger', attributes: [:melee, :one_hand], attack_bonus: 1, name: 'Dagger' },
{ klass: 'ShortBow', attributes: [:ranged], attack_bonus: 5, name: 'Short Bow' },
].each do |obj|
eval <<WEAPON
class #{obj[:klass]} < Weapon
def initialize
#attributes = #{obj[:attributes]}
#name = '#{obj[:name]}'
#attack_bonus = #{obj[:attack_bonus]}
end
end
WEAPON
end
Then:
bs = BroadSword.new
puts bs.name
puts bs.two_handed?
Broad Sword
true

how to write rspec for method

I have following code in programmability folder
module Programmability
module Parameter
class Input < Parameter::Base
attr_reader :args
def initialize(name, type, **args)
super(name, type)
#args = args
end
def value=(val)
if val
val = convert_true_false(val)
--- some code ----
end
#value = val
end
def convert_true_false(val)
return val unless #args[:limit] == 1
if [:char].include?(#type) && val == 'true'
'Y'
elsif [:char].include?(#type) && val == 'false'
'N'
elsif [:bit].include?(#type) && val == 'true'
1
elsif [:bit].include?(#type) && val == 'false'
0
end
end
end
end
end
I am trying to write rspec for method convert_true_false. I am new to rspec. any help is appreciated.
I tried doing this
context 'returns Y if passed true'
input = Programmability::Parameter::Input.new(name, type, limit)
it 'returns Y' do
name = 'unregister_series'
type = ':char'
limit = 1
expect(input.value = 'true' ).to eq('Y')
end
end
but its not picking up limit value. when it reaches convert_true_false method it comes out of it since #args[:limit] is nil
Thanks
The issue is that setters always return the value passed:
class Test
def foo=(foo)
#foo = foo + 100
return 42
end
end
puts (Test.new.foo = 69) # 69
To resolve it, simply check the value after you assigned it:
# instead of
expect(input.value = 'true').to eq 'Y'
# do
input.value = 'true'
expect(input.value).to eq 'Y'

Min & max values for initialize hash

class Airplane
attr_reader :weight, :aircraft_type
attr_accessor :speed, :altitude, :course
def initialize(aircraft_type, options = {})
#aircraft_type = aircraft_type.to_s
#course = options[:course.to_s + "%"] || rand(1...360).to_s + "%"
end
How I can use minimum and maximum allowable values ​​for the hash in initialize from 1 to 360?
Example:
airplane1 = Airplane.new("Boeing 74", course: 200)
p radar1.airplanes
=> [#<Airplane:0x000000023dfc78 #aircraft_type="Boeing 74", #course="200%"]
But if I set to course value 370, airplane1 should not work
This could be refactored i'm sure but this is what i came up with
class Plane
attr_reader :weight, :aircraft_type
attr_accessor :speed, :altitude, :course
def initialize(aircraft_type, options = {})
#aircraft_type = aircraft_type.to_s
#course = options[:course] || random_course
check_course
end
def check_course
if #course < 1 or #course > 360
#course = 1
puts "Invalid course. Set min"
elsif #course > 360
#course = 360
puts "Invalid course. Set max"
else
#course = #course
end
end
def random_course
#course = rand(1..360)
end
end
course is an angle, isn't it? shouldn't it be 0...360 the valid range for it? and why the final "%"? and why work with a string instead of an integer?
Anyway, that's what I'd write:
#course = ((options[:course] || rand(360)) % 360).to_s + "%"
I think you mean you don't want to let people pass in something like {course: '9000%'} for options and you want to error out if it's invalid. If that's the case, you can just test if it's in range:
def initialize(aircraft_type, options = {})
#aircraft_type = aircraft_type.to_s
allowed_range = 1...360
passed_course = options[:course]
#course = case passed_course
when nil
"#{rand allowed_range}%"
when allowed_range
"#{passed_course}%"
else
raise ArgumentError, "Invalid course: #{passed_course}"
end
end

Resources