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

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.

Related

Not displaying certain parts of the initialize - 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.

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!

Possible moves on a chess board, using instance from one class in another.

I'm working on making a chess game. I want to keep everything that has to do with possible moves inside each type of pieces class. In order to do this I need to refer back to #board[][]. The commented section of code in the possible_moves method is what I would like to do, except it does not work and throws an error.
class Board
attr_accessor :board, :choice
def initialize
#board = Array.new(8){Array.new(8," ")}
#choice = choice
end
def set_board
#board[0][2] = Bishop.new([0,2],false)
end
end
class Bishop
attr_accessor :x_position, :y_position, :piece, :color, :moves
def initialize(position,is_white)
#x_position = position[0]
#y_position = position[1]
#piece = is_white ? "♝" : "♗"
#color = is_white ? "white" : "black"
#moves = [[+1,-1],
[+1,+1],
[-1,+1],
[-1,-1]]
end
def possible_moves
move_list = Array.new
#moves.each do |moves|
x = #x_position
y = #y_position
loop do
x += moves[0]
y += moves[1]
break if x.between?(0,7) == false
break if y.between?(0,7) == false
# This is where I want to refer back to #board from the Board class.
# if #board[x][y].color.....
move_list << [x,y]
#end
end
end
p move_list
end
end
You can pass the board into the Bishop constructor:
class Bishop
attr_reader :board
# etc.
def initialize(position,is_white,board)
#board = board.board # or just #board = board and you can fetch the matrix later
# etc.
end
end
class Board
# etc.
def set_board
#board[0][2] = Bishop.new([0,2],false, self)
end
end

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

Ruby: Using variables between classes

I am making a short, text-based game as an extra credit exercise based on the Ruby I have learned so far and I'm having trouble getting classes to read and write variables between each other. I have read extensively and searched for clarification on how to do this but I haven't had much luck. I have tried using # instance variables and attr_accessible but I can't figure it out. Here is my code so far:
class Game
attr_accessor :room_count
def initialize
#room_count = 0
end
def play
while true
puts "\n--------------------------------------------------"
if #room_count == 0
go_to = Entrance.new()
go_to.start
elsif #room_count == 1
go_to = FirstRoom.new()
go_to.start
elsif #room_count == 2
go_to = SecondRoom.new()
go_to.start
elsif #room_count == 3
go_to = ThirdRoom.new()
go_to.start
end
end
end
end
class Entrance
def start
puts "You are at the entrance."
#room_count += 1
end
end
class FirstRoom
def start
puts "You are at the first room."
#room_count += 1
end
end
class SecondRoom
def start
puts "You are at the second room."
#room_count += 1
end
end
class ThirdRoom
def start
puts "You are at the third room. You have reached the end of the game."
Process.exit()
end
end
game = Game.new()
game.play
I want to have the different Room classes change the #room_count variable so that Game class knows which room to go to next. I am also trying to do this without implementing class inheritance. Thanks!
class Room
def initialize(game)
#game = game
#game.room_count += 1
end
def close
#game.room_count -= 1
end
end
class Game
attr_accessor :room_count
def initialize
#room_count = 0
end
def new_room
Room.new self
end
end
game = Game.new
game.room_count # => 0
room = game.new_room
game.room_count # => 1
room.close
game.room_count # => 0

Resources