Seebs wrote:
> I'm pretty sure I really do want to have these things have non-trivial
> internal state.

That's fine - make each individual attribute be an object. But you also 
want these stats to appear to be readable and writable as if they were 
vanilla integers. I think I'd do this by hiding this behavior in the 
parent class, the Character which owns the Stats.

class Stat
  attr_reader :val
  def initialize(val=0, max_val=val)
    @val, @max_val = val, max_val
  end
  def val=(x)
    @val = x
    @max_val = x if x > @max_val
  end
  def restore
    @val = @max_val
  end
  def to_i
    @val
  end
end

class Character
  attr_reader :name
  def initialize(name)
    @name = name
  end

  def str_stat
    @str_stat ||= Stat.new
  end

  def str
    str_stat.val
  end

  def str=(v)
    str_stat.val = v.to_i
  end

  def dex; 0; end  # placeholder
  def con; 0; end  # placeholder

  def level
    str + dex + con
  end
end

c = Character.new("Ogre")
c.str = 14     # => 14
puts c.level
c.str = 8      # => 8
puts c.level
c.str_stat.restore
puts c.level   # => 14

The points I'm trying to make here are:

1. Whilst you want the individual stats to behave as integers, you 
probably don't want the entire Character to behave as an integer - the 
Character is likely to have many other attributes, such as 'name' in the 
above example. So, whenever you deal with individual stats, you are 
always going to do c.str or c.str=val. This is a convenient place to 
masquerade the Stat.

2. When you set the strength of a character, you don't want to replace 
the entire Stat object, you want to update its state so it can retain 
history. So when you write

   c.str = x

then really you don't want to replace the strength Stat object inside c; 
you are sending a message to it to update its state. If you use the code 
I've shown above, then it avoids you having to write

  c.str.val = x

which is what you'd have to do if c.str returned the Stat object itself.

3. When dealing with the Character, I think you will infrequently want 
to deal with the underlying Stat object directly, but I have added an 
accessor (str_stat) to allow this if you need it. With sufficient proxy 
methods you could hide the underlying Stat objects entirely: e.g.

class Character
  def restore_strength
    @str_stat.restore
  end
end

You can still make the character objects be Comparable, if normally you 
want to order them by level.

class Character
  include Comparable
  def <=>(other)
    level <=> other.level
  end
end

And if you really want to, you can define to_int and method_missing so 
that the entire Character resolves to its level value. But I think this 
is more likely to be confusing rather than helpful. If you ever want to 
do arithmetic on levels, I think it would be clearer to see "ogre.level 
- elf.level" rather than "ogre - elf", because ogres have more 
attributes than just their level.

In other words, ogres are like onions :-)

Regards,

Brian.
-- 
Posted via http://www.ruby-forum.com/.