attr_reader没有调用变量

时间:2017-09-22 15:43:03

标签: ruby

我正在构建一个在调用方法时遇到问题的游戏。一个怪物可以出现,并将得到一个随机武器,如果该武器射程远,怪物会得到一个回合挫折给玩家一个战斗机会。当调用方法monsterRangedTurnSetback时,我得到它试图在nil中找到属性的错误:NilClass。我最终追溯到genWeapon函数,并且该函数无法被调用。她的一些代码

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

是的,代码按顺序排列,是的,我知道怪物类允许读取不存在的变量。任何帮助表示赞赏。

2 个答案:

答案 0 :(得分:1)

这里的错误可能是attr_reader无法绑定到像x这样的本地变量,而只能绑定到像@x这样的实例变量。在您的代码中:

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

为其添加@前缀将使其保持不变并且可读。

同样的事情发生在genWeapon中,其中设置并丢弃局部变量。如果你需要那些持久化,你需要在回报中包含它们。无论如何,这些属性应该是某种基础武器类的一部分,您可以在其中调用Dagger.new.two_handed?Dagger.new.hands_required

正如@engineersmnky所指出的那样genWeapon方法存在严重缺陷,x == (1..2)永远不会为任何x的{​​{1}}值返回true。 的工作原因是(1..2)(1..2).include?(x)。由于(1..2) === x在内部使用case,因此可以轻松编写:

===

那仍然很乏味。而是写一个查找表:

case (rand(1..80))
when 1..2
  GreatSword.new
when 3..23
  ShortSword.new
# ...
end

这个地图滚动到类。然后你的发电机功能变得微不足道了:

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

利用Ruby的“一切都是对象”原则来制作类的查找表,大大简化了事情,并使代码更容易理解。始终尝试引导您的程序根据数据而不是程序来定义事物。

答案 1 :(得分:1)

您可能想要重新审视如何定义其中一些类。甚至可能在{Weapon}类中包含turn_delay作为方法。以下是我如何重构这一点来从武器父类中继承专用武器:

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?

输出:

Long Bow
true
Short Sword
false

我对此有太多的乐趣,大量的武器可能会变得很麻烦,为其编写类定义。既然你选择了Ruby,就可以使用以下内容来接受一些元编程并快速制作新武器(要求你已经定义了基类Weapon类:

[
  { 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

然后:

bs = BroadSword.new
puts bs.name
puts bs.two_handed?

Broad Sword
true