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
Related
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.
I need to randomly pick a name from an array in Ruby and then check if it uppercase. So far I have:
def namegenerator
return #name.sample
end
def namechecker
if name.upcase then
check = TRUE
else
check = FALSE
end
end
It needs to be as two separate methods like this.
Something like this:
def sample_word(words)
words.sample
end
def upcase?(word)
word == word.upcase
end
And then something like:
words = %w[APPLE banana CherRy GRAPE]
word = sample_word(words)
puts word # e.g. BANANA
puts upcase?(word) # will print true
If you just want to check just the first letter:
names = %w(Kirk mccoy scott Spock)
names.sample.then { |name| [name, name[0] == name[0].upcase] }
#=> ["mccoy", false]
Maybe something like this:
class NameGenerator
def initialize(size, items)
#name = ""
#size = size
#items = items
end
def namegenerator
#name = #items.sample(#size).to_s
end
def namechecker?
#name == #name.upcase
end
def name
#name
end
end
ng = NameGenerator.new 1, ["name", "Name", "NAME"]
ng.namegenerator
puts ng.name, ng.namechecker?
Update
I've posted code without much thinking about abstraction and i think it would be much better to encapsulate name and upper case check to separate class and make it immutable, then make generator class that selects one entity from collection.
class NameGenerator
def initialize(items)
#items = items
end
def next
#items.sample
end
end
class Name
attr_reader :name
def initialize(name)
#name = name
end
def is_uppercase?
#name.match(/\p{Lower}/) == nil
end
end
ng = NameGenerator.new [
Name.new("name"),
Name.new("Name"),
Name.new("NAME"),
Name.new("na-me")
]
name = ng.next
puts name.name, name.is_uppercase?
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
From codeschool's ruby-bits course, I am trying to understand how these classes work- I have a Game class and a collection class called Library that stores a collection of games.
class Game
attr_accessor :name, :year, :system
attr_reader :created_at
def initialize(name, options={})
self.name = name
self.year = options[:year]
self.system = options[:system]
#created_at = Time.now
end
def ==(game)
name == game.name &&
system == game.system &&
year == game.year
end
end
Library class:
class Library
attr_accessor :games
def initialize(*games)
self.games = games
end
def has_game?(*games)
for game in self.games
return true if game == game
end
false
end
end
Now I create some games:
contra = Game.new('Contra', {
year: 1994,
system: 'nintendo'
})
mario = Game.new('Mario', {
year: 1996,
system: 'SNES'
})
sonic = Game.new('Sonic', {
year: 1993,
system: 'SEGA'
})
and instantiate a new collection:
myCollection = Library.new(mario, sonic)
When I try to find if a certain game is in myCollection using the has_game? method, I always get true
puts myCollection.has_game?(contra) #=> returns **true** even though this has never been inserted as part of the collection.
What am I doing wrong?
return true if game == game
I think this statement may cause problem.
It is always true.
You may want something like this:
def has_game?(wanted)
for game in self.games
return true if game == wanted
end
false
end
There are a couple things that are wrong here:
Instead of using self.XXXX to create instance variables you should
use #XXXX, it accesses the values directly, using self actually performs
another method call, refer here for more details: Instance variable: self vs #
As the others mentioned game == game will always return true, the answer that
was already posted doesn't allow for passing more than a single game to has_game?
Here are my changes that work correctly:
class Game
attr_accessor :name, :year, :system
attr_reader :created_at
def initialize(name, options={})
#name = name
#year = options[:year]
#system = options[:system]
#created_at = Time.now
end
def ==(game)
#name == game.name &&
#system == game.system &&
#year == game.year
end
end
class Library
attr_accessor :games
def initialize(*games)
#games = games
end
# only returns true if this Library
# has ALL of the games passed to has_game?
def has_game?(*_games)
_games.each do |game|
return false if not #games.include?(game)
end
return true
end
end
contra = Game.new('Contra', {
year: 1994,
system: 'nintendo'
})
mario = Game.new('Mario', {
year: 1996,
system: 'SNES'
})
sonic = Game.new('Sonic', {
year: 1993,
system: 'SEGA'
})
myCollection = Library.new(mario, sonic)
puts "Collection has Contra? #{myCollection.has_game?(contra)}"
puts "Collection has Sonic and Mario #{myCollection.has_game?(sonic, mario)}"
output:
Collection has Contra? false
Collection has Sonic and Mario true
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