Generic class/design pattern question

I'm re-working old code from early efforts of learning Ruby. Now I'm trying
to get more OOP'ish and maybe figure out design patterns if they are
appropriate. I'm stuck on what do do with the "Career" concept.

Program design is pretty simple; there's a Class "Character" that stores
attributes of a Traveller RPG character. There's a mixin "CharacterTools"
for the methods that fill in the attributes.

In the game, a base character is created with traits like UPP, or stats.
The next process in generation is to put the character through one or more
careers; A career will usually age the character, and will likely add
skills and potentially modify the UPP.

A Base characeter has not gone through a career.
A normal character has gone through one career and gained skills and stuff.
Some characters can go through sequential or parallel careers.

The command is: "Chargen -c <career> -t <terms>", for what career to run
and for how long.

The CharacterTools mixin is used by both Character and the Careers to
modify UPP, add skills, etc. Currently each career, like "Marines" or
"Navy", is a subclass of Career. Some methods are outlined in Career and
the sub-classes provide skill lists that vary by career, and may overload
methods like "promotion" for career specific resolution.

What's the best way to approach the careers? Working code is at:

And the stuff I'm refactoring is in the "refactor_on_inheritance" branch,
though that's misnamed. I planned on inheritance but realized it might not
be the best solution.

Thanks!

Leam

···

--
Mind on a Mission <http://leamhall.blogspot.com/>

I cannot seem to find that branch. I briefly looked into master and
the code seems to be currently not in a stable state. For example
variable "options" in line 36 of Chargen is never used. In Career.rb
there are instance variables of the class defined which you seem to
want to use in an instance method by directly accessing them. But that
does not work. Also, Hash#+ is not defined. You could also spend a few
empty lines and comments which, at least for me, would make this more
readable.

Kind regards

robert

···

On Mon, Feb 13, 2017 at 8:29 PM, leam hall <leamhall@gmail.com> wrote:

What's the best way to approach the careers? Working code is at:

https://github.com/LeamHall/CT_Character_Generator

And the stuff I'm refactoring is in the "refactor_on_inheritance" branch,
though that's misnamed. I planned on inheritance but realized it might not
be the best solution.

--
[guy, jim, charlie].each {|him| remember.him do |as, often| as.you_can
- without end}
http://blog.rubybestpractices.com/

Some minor code suggestions:

1.Instead of code like

  def generate_hair
    begin
      t = get_random_line_from_file("hair_tone.txt")
      b = get_random_line_from_file("hair_body.txt")
      c = get_random_line_from_file("hair_colors.txt")
      l = get_random_line_from_file("hair_length.txt")
      new_hair = "#{b} #{t} #{c} #{l}"
    rescue SystemCallError => e
      new_hair = "Straight medium brown short"
    end
    return new_hair
  end

consider adding a default argument to get_random_line_from_file, so you can
say

      t = get_random_line_from_file("hair_tone.txt", default: "straight")

see

for details,

2. Instead of

  cash_length = muster_out["cash"].length - 1
  character.stuff["cash"] += muster_out["cash"][rand(cash_length)]

check out the Array#sample method - it's simply

  character.stuff["cash"] += muster_out["cash"].sample

3. Things like CharacterTools.modify_stat(options) where the options{} hash
contains both a character and the stats to modify it with are a bit of a
code smell - what you really want to be doing is calling
character.modify_stat(options).
A lot of the methods in CharacterTools should be methods of Character
instead

4. Another code smell:

    if skill.split.length > 1
      options["stat_level"] = skill.split[0].to_i
      options["stat"] = skill.split[1]
      self.modify_stat(options)

indicates that you are trying to pass two different pieces of data in a
string. Since this string is generated within your code, you should be
maintaining skill as a struct instead, with stat_level and stat as fields,
and join it into a string only when you want to output it.

martin

···

On Mon, Feb 13, 2017 at 11:29 AM, leam hall <leamhall@gmail.com> wrote:

I'm re-working old code from early efforts of learning Ruby. Now I'm
trying to get more OOP'ish and maybe figure out design patterns if they are
appropriate. I'm stuck on what do do with the "Career" concept.

Program design is pretty simple; there's a Class "Character" that stores
attributes of a Traveller RPG character. There's a mixin "CharacterTools"
for the methods that fill in the attributes.

In the game, a base character is created with traits like UPP, or stats.
The next process in generation is to put the character through one or more
careers; A career will usually age the character, and will likely add
skills and potentially modify the UPP.

A Base characeter has not gone through a career.
A normal character has gone through one career and gained skills and stuff.
Some characters can go through sequential or parallel careers.

The command is: "Chargen -c <career> -t <terms>", for what career to run
and for how long.

The CharacterTools mixin is used by both Character and the Careers to
modify UPP, add skills, etc. Currently each career, like "Marines" or
"Navy", is a subclass of Career. Some methods are outlined in Career and
the sub-classes provide skill lists that vary by career, and may overload
methods like "promotion" for career specific resolution.

What's the best way to approach the careers? Working code is at:

https://github.com/LeamHall/CT_Character_Generator

And the stuff I'm refactoring is in the "refactor_on_inheritance" branch,
though that's misnamed. I planned on inheritance but realized it might not
be the best solution.

Thanks!

Leam

--
Mind on a Mission <http://leamhall.blogspot.com/&gt;

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk&gt;

Robert, thanks for the feedback!

Not sure if my mail to the group was delayed or what, that branch was
merged back into master on Sunday or Monday. I'll have to check when I get
home to see when the mail was sent.

You're right, I mixed two ways to pass options, that's now issue #31. :slight_smile:

I'm not sure I understand your comment on Career.rb, In theory Career.rb is
subclassed by each of the careers, and they override Career.rb methods if
necessary. Can you give me a line number and an example of why it's wrong?

I am pretty sure this can be improved, just trying to figure out where's
the best place to focus.

Thanks!

Leam

···

On Wed, Feb 15, 2017 at 11:54 AM, Robert Klemme <shortcutter@googlemail.com> wrote:

On Mon, Feb 13, 2017 at 8:29 PM, leam hall <leamhall@gmail.com> wrote:

> What's the best way to approach the careers? Working code is at:
>
> https://github.com/LeamHall/CT_Character_Generator
>
> And the stuff I'm refactoring is in the "refactor_on_inheritance" branch,
> though that's misnamed. I planned on inheritance but realized it might
not
> be the best solution.

I cannot seem to find that branch. I briefly looked into master and
the code seems to be currently not in a stable state. For example
variable "options" in line 36 of Chargen is never used. In Career.rb
there are instance variables of the class defined which you seem to
want to use in an instance method by directly accessing them. But that
does not work. Also, Hash#+ is not defined. You could also spend a few
empty lines and comments which, at least for me, would make this more
readable.

Kind regards

robert

--
[guy, jim, charlie].each {|him| remember.him do |as, often| as.you_can
- without end}
http://blog.rubybestpractices.com/

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk&gt;

--
Mind on a Mission <http://leamhall.blogspot.com/&gt;

Robert, thanks for the feedback!

You're welcome!

Not sure if my mail to the group was delayed or what, that branch was merged
back into master on Sunday or Monday. I'll have to check when I get home to
see when the mail was sent.

I got it on Monday.

I'm not sure I understand your comment on Career.rb, In theory Career.rb is
subclassed by each of the careers, and they override Career.rb methods if
necessary. Can you give me a line number and an example of why it's wrong?

I am looking at line 6 and 82. Basically you are doing this:

irb(main):001:0> class X
irb(main):002:1> @foo = 'Found it!'
irb(main):003:1> def m; @foo end
irb(main):004:1> end
=> :m
irb(main):005:0> X.new.m
=> nil

The code does not work the way you seem to intend it to.

Kind regards

robert

···

On Wed, Feb 15, 2017 at 6:16 PM, leam hall <leamhall@gmail.com> wrote:

--
[guy, jim, charlie].each {|him| remember.him do |as, often| as.you_can
- without end}
http://blog.rubybestpractices.com/

untested,

properties = %w(tone body colors length)
defaults = %w(straight medium brown short)
properties.zip(defaults).
   map{|x,d| get_random_line_from_file("hair_#{x}.txt") rescue d }.
   join " "

kind regards
--botp

···

On Fri, Feb 17, 2017 at 1:52 PM, Martin DeMello <martindemello@gmail.com> wrote:

      t = get_random_line_from_file("hair_tone.txt")
      b = get_random_line_from_file("hair_body.txt")
      c = get_random_line_from_file("hair_colors.txt")
      l = get_random_line_from_file("hair_length.txt")
      new_hair = "#{b} #{t} #{c} #{l}"
    rescue SystemCallError => e
      new_hair = "Straight medium brown short"

Ah, line 38 of

https://github.com/LeamHall/CT_Character_Generator/blob/mast
er/lib/Careers/Army.rb

is an example of what that's doing. Each of the careers has a skill_options
array that's different, depending on the career. Lines 81-85 of Career.rb
concatenate the two arrays if the stat is high enough.

In game terms, a person in a career can learn certain skills. If their
Education stat is high enough they have more possible skills to learn.

My assumption is that there are better ways to do this, I'm just trying to
be as explicit as possible.

Leam

···

On Wed, Feb 15, 2017 at 12:48 PM, Robert Klemme <shortcutter@googlemail.com> wrote:

On Wed, Feb 15, 2017 at 6:16 PM, leam hall <leamhall@gmail.com> wrote:
> Robert, thanks for the feedback!

You're welcome!

> Not sure if my mail to the group was delayed or what, that branch was
merged
> back into master on Sunday or Monday. I'll have to check when I get home
to
> see when the mail was sent.

I got it on Monday.

> I'm not sure I understand your comment on Career.rb, In theory Career.rb
is
> subclassed by each of the careers, and they override Career.rb methods if
> necessary. Can you give me a line number and an example of why it's
wrong?

I am looking at line 6 and 82. Basically you are doing this:

irb(main):001:0> class X
irb(main):002:1> @foo = 'Found it!'
irb(main):003:1> def m; @foo end
irb(main):004:1> end
=> :m
irb(main):005:0> X.new.m
=> nil

The code does not work the way you seem to intend it to.

Kind regards

robert

--
Mind on a Mission <http://leamhall.blogspot.com/&gt;

The point is: line 6 that I referenced above is _totally_ unrelated.
It is simply superfluous and does nothing in the scenario you
describe.

robert

···

On Wed, Feb 15, 2017 at 8:33 PM, leam hall <leamhall@gmail.com> wrote:

On Wed, Feb 15, 2017 at 12:48 PM, Robert Klemme <shortcutter@googlemail.com> > wrote:

On Wed, Feb 15, 2017 at 6:16 PM, leam hall <leamhall@gmail.com> wrote:
> Robert, thanks for the feedback!

You're welcome!

> Not sure if my mail to the group was delayed or what, that branch was
> merged
> back into master on Sunday or Monday. I'll have to check when I get home
> to
> see when the mail was sent.

I got it on Monday.

> I'm not sure I understand your comment on Career.rb, In theory Career.rb
> is
> subclassed by each of the careers, and they override Career.rb methods
> if
> necessary. Can you give me a line number and an example of why it's
> wrong?

I am looking at line 6 and 82. Basically you are doing this:

irb(main):001:0> class X
irb(main):002:1> @foo = 'Found it!'
irb(main):003:1> def m; @foo end
irb(main):004:1> end
=> :m
irb(main):005:0> X.new.m
=> nil

The code does not work the way you seem to intend it to.

Kind regards

robert

Ah, line 38 of

https://github.com/LeamHall/CT_Character_Generator/blob/master/lib/Careers/Army.rb

is an example of what that's doing. Each of the careers has a skill_options
array that's different, depending on the career. Lines 81-85 of Career.rb
concatenate the two arrays if the stat is high enough.

In game terms, a person in a career can learn certain skills. If their
Education stat is high enough they have more possible skills to learn.

My assumption is that there are better ways to do this, I'm just trying to
be as explicit as possible.

--
[guy, jim, charlie].each {|him| remember.him do |as, often| as.you_can
- without end}
http://blog.rubybestpractices.com/

I'm fixing to be late for work but will study this tonight. Thanks!

Leam

···

On 02/16/17 04:23, Robert Klemme wrote:

The point is: line 6 that I referenced above is _totally_ unrelated.
It is simply superfluous and does nothing in the scenario you
describe.

robert

Ah-ha! I looked at this some more and see what you mean. I should be able
to remove those tonight and pass all the tests.

However, there's a bigger question I'm trying to answer. Currently Chargen
calls something like "Army.new(char)", when "char =
Character.new.generate". Is there a better way? What will happen is that a
character may go through more than one career. Classes like "Army" take the
character in and modify it's attributes.

I know little of Design Patterns except that they exist, and I am trying to
learn. Is there a proper pattern for a class that modifies an object in
place?

Thanks!

Leam

···

On Thu, Feb 16, 2017 at 4:23 AM, Robert Klemme <shortcutter@googlemail.com> wrote:

The point is: line 6 that I referenced above is _totally_ unrelated.
It is simply superfluous and does nothing in the scenario you
describe.

robert

--
Mind on a Mission <http://leamhall.blogspot.com/&gt;

However, there's a bigger question I'm trying to answer. Currently Chargen calls something like "Army.new(char)", when "char = Character.new.generate". Is there a better way? What will happen is that a character may go through more than one career. Classes like "Army" take the character in and modify it's attributes.

As I see it, you have a couple of options.

1) Inject the Character class into the Army class. It kind of sounds as if you are doing that.

    class Foo
      attr_accessor thing; end
      def initialize(thing); @thing = thing; end
    end

    class Bar
      def initialize(foo); foo.thing += 12; end
    end

    f = Bar.new(Foo.new 12)
    puts f.thing

2) Define Army as a decorator for the Character class.

    class Foo
      attr_accessor thing; end
      def initialize(thing); @thing = thing; end
    end

    Module Bar
      def thing; thing += 12; end
    end

    f = Foo.new(12).extend(Bar)
    puts f.thing

The decorator pattern is quite neat In that you can stack multiple decorators for multiple careers. But it really comes into its own if you actually want different behaviour from the core class as well as different numbers. My feeling is you don't need that. So your approach is probably basically correct.

Click here to view Company Information and Confidentiality Notice.<http://www.jameshall.co.uk/index.php/small-print/email-disclaimer&gt;

I do not think solution 2 is appropriate here as the association
between the career and the character is just temporary. After going
through one career a character can have another career mixing all of
them into the instance can cause name clashes between career methods
which are unnecessary and it clutters a character's interface
unnecessarily with methods which are not needed any more - or could
even cause harm.

For the same reason (assignment between career and character being
temporary only) I would also not use the current approach because that
establishes the association between a career and a character in the
career's constructor and makes the character part of the career's
state. I would use a different approach and make the career a
manipulator of characters where the association is temporary -
something like command pattern:

class Army < Career
  def update_character(char)
    char.whatever += 1
    char.title = my_dienstgrad # some German fits nicely here :wink:
    # etc.
  end
end

If you like the wording you can even invoke this through Character, e.g.

class Character
  def go_through(career)
    career.update_character(self)
  end
end

fritz = Character.new ...
klaus = Character.new ...

army = Army.new

fritz.go_through army
klaus.go_through army

etc.

Cheers

robert

···

On Thu, Feb 16, 2017 at 4:36 PM, Andy Jones <Andy.Jones@jameshall.co.uk> wrote:

However, there's a bigger question I'm trying to answer. Currently Chargen calls something like "Army.new(char)", when "char = Character.new.generate". Is there a better way? What will happen is that a character may go through more than one career. Classes like "Army" take the character in and modify it's attributes.
<<<<<<<<

As I see it, you have a couple of options.

1) Inject the Character class into the Army class. It kind of sounds as if you are doing that.

    class Foo
      attr_accessor thing; end
      def initialize(thing); @thing = thing; end
    end

    class Bar
      def initialize(foo); foo.thing += 12; end
    end

    f = Bar.new(Foo.new 12)
    puts f.thing

2) Define Army as a decorator for the Character class.

    class Foo
      attr_accessor thing; end
      def initialize(thing); @thing = thing; end
    end

    Module Bar
      def thing; thing += 12; end
    end

    f = Foo.new(12).extend(Bar)
    puts f.thing

The decorator pattern is quite neat In that you can stack multiple decorators for multiple careers. But it really comes into its own if you actually want different behaviour from the core class as well as different numbers. My feeling is you don't need that. So your approach is probably basically correct.

--
[guy, jim, charlie].each {|him| remember.him do |as, often| as.you_can
- without end}
http://blog.rubybestpractices.com/

I'm working on this again, and liking the last idea. Haven't gotten it fully functional yet, though.

···

On 02/16/17 11:42, Robert Klemme wrote:

On Thu, Feb 16, 2017 at 4:36 PM, Andy Jones <Andy.Jones@jameshall.co.uk> wrote:

However, there's a bigger question I'm trying to answer. Currently Chargen calls something like "Army.new(char)", when "char = Character.new.generate". Is there a better way? What will happen is that a character may go through more than one career. Classes like "Army" take the character in and modify it's attributes.
<<<<<<<<

As I see it, you have a couple of options.

1) Inject the Character class into the Army class. It kind of sounds as if you are doing that.

    class Foo
      attr_accessor thing; end
      def initialize(thing); @thing = thing; end
    end

    class Bar
      def initialize(foo); foo.thing += 12; end
    end

    f = Bar.new(Foo.new 12)
    puts f.thing

2) Define Army as a decorator for the Character class.

    class Foo
      attr_accessor thing; end
      def initialize(thing); @thing = thing; end
    end

    Module Bar
      def thing; thing += 12; end
    end

    f = Foo.new(12).extend(Bar)
    puts f.thing

The decorator pattern is quite neat In that you can stack multiple decorators for multiple careers. But it really comes into its own if you actually want different behaviour from the core class as well as different numbers. My feeling is you don't need that. So your approach is probably basically correct.

I do not think solution 2 is appropriate here as the association
between the career and the character is just temporary. After going
through one career a character can have another career mixing all of
them into the instance can cause name clashes between career methods
which are unnecessary and it clutters a character's interface
unnecessarily with methods which are not needed any more - or could
even cause harm.

For the same reason (assignment between career and character being
temporary only) I would also not use the current approach because that
establishes the association between a career and a character in the
career's constructor and makes the character part of the career's
state. I would use a different approach and make the career a
manipulator of characters where the association is temporary -
something like command pattern:

class Army < Career
  def update_character(char)
    char.whatever += 1
    char.title = my_dienstgrad # some German fits nicely here :wink:
    # etc.
  end
end

If you like the wording you can even invoke this through Character, e.g.

class Character
  def go_through(career)
    career.update_character(self)
  end
end

fritz = Character.new ...
klaus = Character.new ...

army = Army.new

fritz.go_through army
klaus.go_through army

etc.

Cheers

robert

Guys, I just wanted to follow up on this. Thank you very much for your suggestions! Also, though he probably hears it often, I appreciate Matz and Ruby. Saturday's work was to implement the combination of careers, something I've been struggling with for a few months. I'd already done some refactoring based on your comments and expected to spend most of my morning trying to make this do what I wanted. I really wasn't sure I could do it in my morning, even.

Two cups of coffee and a few minutes later I edited one line of code, ran the program a different way, and the problem was solved.

Leam

···

On 02/16/17 11:42, Robert Klemme wrote:

On Thu, Feb 16, 2017 at 4:36 PM, Andy Jones <Andy.Jones@jameshall.co.uk> wrote:

However, there's a bigger question I'm trying to answer. Currently Chargen calls something like "Army.new(char)", when "char = Character.new.generate". Is there a better way? What will happen is that a character may go through more than one career. Classes like "Army" take the character in and modify it's attributes.
<<<<<<<<

As I see it, you have a couple of options.

1) Inject the Character class into the Army class. It kind of sounds as if you are doing that.

    class Foo
      attr_accessor thing; end
      def initialize(thing); @thing = thing; end
    end

    class Bar
      def initialize(foo); foo.thing += 12; end
    end

    f = Bar.new(Foo.new 12)
    puts f.thing

2) Define Army as a decorator for the Character class.

    class Foo
      attr_accessor thing; end
      def initialize(thing); @thing = thing; end
    end

    Module Bar
      def thing; thing += 12; end
    end

    f = Foo.new(12).extend(Bar)
    puts f.thing

The decorator pattern is quite neat In that you can stack multiple decorators for multiple careers. But it really comes into its own if you actually want different behaviour from the core class as well as different numbers. My feeling is you don't need that. So your approach is probably basically correct.

I do not think solution 2 is appropriate here as the association
between the career and the character is just temporary. After going
through one career a character can have another career mixing all of
them into the instance can cause name clashes between career methods
which are unnecessary and it clutters a character's interface
unnecessarily with methods which are not needed any more - or could
even cause harm.

For the same reason (assignment between career and character being
temporary only) I would also not use the current approach because that
establishes the association between a career and a character in the
career's constructor and makes the character part of the career's
state. I would use a different approach and make the career a
manipulator of characters where the association is temporary -
something like command pattern:

class Army < Career
  def update_character(char)
    char.whatever += 1
    char.title = my_dienstgrad # some German fits nicely here :wink:
    # etc.
  end
end

If you like the wording you can even invoke this through Character, e.g.

class Character
  def go_through(career)
    career.update_character(self)
  end
end

fritz = Character.new ...
klaus = Character.new ...

army = Army.new

fritz.go_through army
klaus.go_through army

etc.

Cheers

robert

I have this working. However, I feel like I'm missing the OOP boat and just don't get it. The program flow document is:

https://github.com/LeamHall/CT_Character_Generator/blob/master/docs/Program_flow.txt

This line: https://github.com/LeamHall/CT_Character_Generator/blob/master/bin/Chargen#L75

Calls: https://github.com/LeamHall/CT_Character_Generator/blob/master/lib/Tools/Character.rb#L46-L48

Which starts here: https://github.com/LeamHall/CT_Character_Generator/blob/master/lib/Tools/Career.rb#L55

Note that the last one, Class Career is a super class. Each actual career is a sub-class that adds data to various structures.

Am I doing this right or can this be cleaner and simpler. Note that I'm still a Ruby Newbie so explanations would preferably be simple. :slight_smile:

Thanks!

Leam

···

On 02/16/17 11:42, Robert Klemme wrote:

On Thu, Feb 16, 2017 at 4:36 PM, Andy Jones <Andy.Jones@jameshall.co.uk> wrote:

However, there's a bigger question I'm trying to answer. Currently Chargen calls something like "Army.new(char)", when "char = Character.new.generate". Is there a better way? What will happen is that a character may go through more than one career. Classes like "Army" take the character in and modify it's attributes.
<<<<<<<<

As I see it, you have a couple of options.

1) Inject the Character class into the Army class. It kind of sounds as if you are doing that.

    class Foo
      attr_accessor thing; end
      def initialize(thing); @thing = thing; end
    end

    class Bar
      def initialize(foo); foo.thing += 12; end
    end

    f = Bar.new(Foo.new 12)
    puts f.thing

2) Define Army as a decorator for the Character class.

    class Foo
      attr_accessor thing; end
      def initialize(thing); @thing = thing; end
    end

    Module Bar
      def thing; thing += 12; end
    end

    f = Foo.new(12).extend(Bar)
    puts f.thing

The decorator pattern is quite neat In that you can stack multiple decorators for multiple careers. But it really comes into its own if you actually want different behaviour from the core class as well as different numbers. My feeling is you don't need that. So your approach is probably basically correct.

I do not think solution 2 is appropriate here as the association
between the career and the character is just temporary. After going
through one career a character can have another career mixing all of
them into the instance can cause name clashes between career methods
which are unnecessary and it clutters a character's interface
unnecessarily with methods which are not needed any more - or could
even cause harm.

For the same reason (assignment between career and character being
temporary only) I would also not use the current approach because that
establishes the association between a career and a character in the
career's constructor and makes the character part of the career's
state. I would use a different approach and make the career a
manipulator of characters where the association is temporary -
something like command pattern:

class Army < Career
  def update_character(char)
    char.whatever += 1
    char.title = my_dienstgrad # some German fits nicely here :wink:
    # etc.
  end
end

If you like the wording you can even invoke this through Character, e.g.

class Character
  def go_through(career)
    career.update_character(self)
  end
end

fritz = Character.new ...
klaus = Character.new ...

army = Army.new

fritz.go_through army
klaus.go_through army

etc.

Cheers

robert

Two cups of coffee and a few minutes later I edited one line of code,
ran the program a different way, and the problem was solved.

There are times when the Ruby language just seems to read your mind. One of the reasons I fell in love with it.

Click here to view Company Information and Confidentiality Notice.<http://www.jameshall.co.uk/index.php/small-print/email-disclaimer&gt;

Not sure about others here, but I would find it useful to see at least a rough idea of how your classes look. Personally, I like the superclass idea, but know from personal experience, that designing the superclass is difficult, mainly in respect of how much detail it should include. This is especially important when dealing with serialized, stored data, as changing the class/es tends to break the serializer!

···

On 23/04/2017 22:55, Leam Hall wrote:

On 02/16/17 11:42, Robert Klemme wrote:

On Thu, Feb 16, 2017 at 4:36 PM, Andy Jones >> <Andy.Jones@jameshall.co.uk> wrote:

However, there's a bigger question I'm trying to answer. Currently Chargen calls something like "Army.new(char)", when "char = Character.new.generate". Is there a better way? What will happen is that a character may go through more than one career. Classes like "Army" take the character in and modify it's attributes.
<<<<<<<<

As I see it, you have a couple of options.

1) Inject the Character class into the Army class. It kind of sounds as if you are doing that.

    class Foo
      attr_accessor thing; end
      def initialize(thing); @thing = thing; end
    end

    class Bar
      def initialize(foo); foo.thing += 12; end
    end

    f = Bar.new(Foo.new 12)
    puts f.thing

2) Define Army as a decorator for the Character class.

    class Foo
      attr_accessor thing; end
      def initialize(thing); @thing = thing; end
    end

    Module Bar
      def thing; thing += 12; end
    end

    f = Foo.new(12).extend(Bar)
    puts f.thing

The decorator pattern is quite neat In that you can stack multiple decorators for multiple careers. But it really comes into its own if you actually want different behaviour from the core class as well as different numbers. My feeling is you don't need that. So your approach is probably basically correct.

I do not think solution 2 is appropriate here as the association
between the career and the character is just temporary. After going
through one career a character can have another career mixing all of
them into the instance can cause name clashes between career methods
which are unnecessary and it clutters a character's interface
unnecessarily with methods which are not needed any more - or could
even cause harm.

For the same reason (assignment between career and character being
temporary only) I would also not use the current approach because that
establishes the association between a career and a character in the
career's constructor and makes the character part of the career's
state. I would use a different approach and make the career a
manipulator of characters where the association is temporary -
something like command pattern:

class Army < Career
  def update_character(char)
    char.whatever += 1
    char.title = my_dienstgrad # some German fits nicely here :wink:
    # etc.
  end
end

If you like the wording you can even invoke this through Character, e.g.

class Character
  def go_through(career)
    career.update_character(self)
  end
end

fritz = Character.new ...
klaus = Character.new ...

army = Army.new

fritz.go_through army
klaus.go_through army

etc.

Cheers

robert

I have this working. However, I feel like I'm missing the OOP boat and just don't get it. The program flow document is:

https://github.com/LeamHall/CT_Character_Generator/blob/master/docs/Program_flow.txt

This line: https://github.com/LeamHall/CT_Character_Generator/blob/master/bin/Chargen#L75

Calls: https://github.com/LeamHall/CT_Character_Generator/blob/master/lib/Tools/Character.rb#L46-L48

Which starts here: https://github.com/LeamHall/CT_Character_Generator/blob/master/lib/Tools/Career.rb#L55

Note that the last one, Class Career is a super class. Each actual career is a sub-class that adds data to various structures.

Am I doing this right or can this be cleaner and simpler. Note that I'm still a Ruby Newbie so explanations would preferably be simple. :slight_smile:

Thanks!

Leam

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk&gt;

--
Patrick Bayford

Hey Patrick, thanks!

The Character class:
https://github.com/LeamHall/CT_Character_Generator/blob/master/lib/Tools/Character.rb

Uses Module CharacterTools to modify most of the data:
https://github.com/LeamHall/CT_Character_Generator/blob/master/lib/Tools/CharacterTools.rb

The Career class has the methods for running a career:
https://github.com/LeamHall/CT_Character_Generator/blob/master/lib/Tools/Career.rb

And uses data from subclasses, like:
https://github.com/LeamHall/CT_Character_Generator/blob/master/lib/Careers/Citizen.rb

That help?

Leam

···

On 04/23/17 23:20, Patrick Bayford wrote:

Not sure about others here, but I would find it useful to see at least a
rough idea of how your classes look. Personally, I like the superclass
idea, but know from personal experience, that designing the superclass
is difficult, mainly in respect of how much detail it should include.
This is especially important when dealing with serialized, stored data,
as changing the class/es tends to break the serializer!

Thanks Leam - will take a day or two to plough through these, but that was what I asked for.

···

On 24/04/2017 09:56, Leam Hall wrote:

On 04/23/17 23:20, Patrick Bayford wrote:

Not sure about others here, but I would find it useful to see at least a
rough idea of how your classes look. Personally, I like the superclass
idea, but know from personal experience, that designing the superclass
is difficult, mainly in respect of how much detail it should include.
This is especially important when dealing with serialized, stored data,
as changing the class/es tends to break the serializer!

Hey Patrick, thanks!

The Character class:
https://github.com/LeamHall/CT_Character_Generator/blob/master/lib/Tools/Character.rb

Uses Module CharacterTools to modify most of the data:
https://github.com/LeamHall/CT_Character_Generator/blob/master/lib/Tools/CharacterTools.rb

The Career class has the methods for running a career:
https://github.com/LeamHall/CT_Character_Generator/blob/master/lib/Tools/Career.rb

And uses data from subclasses, like:
https://github.com/LeamHall/CT_Character_Generator/blob/master/lib/Careers/Citizen.rb

That help?

Leam

Unsubscribe: <mailto:ruby-talk-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-talk&gt;

--
Patrick Bayford

Just looking at one thing and iterating on it:

# 0) Delete all trailing whitespace. Use your editor to help you code like a pro. Use paragraphs/blank-lines to separate concepts:

def self.title(character)
  nobility = Hash.new
  nobility["B"] = { "f" => "Dame", "m" => "Knight" }
  nobility["C"] = { "f" => "Baroness", "m" => "Baron" }
  nobility["D"] = { "f" => "Marquesa", "m" => "Marquis" }
  nobility["E"] = { "f" => "Countess", "m" => "Count" }
  nobility["F"] = { "f" => "Duchess", "m" => "Duke" }

  soc = character.upp[5,1].upcase

  if nobility.has_key?(soc)
    if character.gender.downcase == "female"
      title = nobility[soc]["f"]
      character.title = title
    else
      title = nobility[soc]["m"]
      character.title = title
    end
  end

  return title
end

# 1) upcase & downcase is showing that you’re not normalizing data
# when it comes in / gets stored. I haven’t looked at your code,
# but chances are all this data is internal and doesn’t need ANY
# up/down case.

def self.title(character)
  nobility = Hash.new
  nobility["B"] = { "f" => "Dame", "m" => "Knight" }
  nobility["C"] = { "f" => "Baroness", "m" => "Baron" }
  nobility["D"] = { "f" => "Marquesa", "m" => "Marquis" }
  nobility["E"] = { "f" => "Countess", "m" => "Count" }
  nobility["F"] = { "f" => "Duchess", "m" => "Duke" }

  soc = character.upp[5,1]

  if nobility.has_key?(soc)
    if character.gender == "female"
      title = nobility[soc]["f"]
      character.title = title
    else
      title = nobility[soc]["m"]
      character.title = title
    end
  end
  return title
end

# 2) nobility doesn't change. Pull it out and make it a constant:

NOBILITY = {
            "B" => { "f" => "Dame", "m" => "Knight" },
            "C" => { "f" => "Baroness", "m" => "Baron" },
            "D" => { "f" => "Marquesa", "m" => "Marquis" },
            "E" => { "f" => "Countess", "m" => "Count" },
            "F" => { "f" => "Duchess", "m" => "Duke" },
           }

def self.title(character)
  soc = character.upp[5,1]

  if NOBILITY.has_key?(soc)
    if character.gender == "female"
      title = NOBILITY[soc]["f"]
      character.title = title
    else
      title = NOBILITY[soc]["m"]
      character.title = title
    end
  end
  return title
end

# 3) You're using data to make a decision w/ soc, but then skip that
# concept for gender. And then duplicate code entirely:

NOBILITY = {
            "B" => { "f" => "Dame", "m" => "Knight" },
            "C" => { "f" => "Baroness", "m" => "Baron" },
            "D" => { "f" => "Marquesa", "m" => "Marquis" },
            "E" => { "f" => "Countess", "m" => "Count" },
            "F" => { "f" => "Duchess", "m" => "Duke" },
           }

def self.title(character)
  soc = character.upp[5,1]
  gender = character.gender[0]

  if NOBILITY.has_key?(soc)
    title = NOBILITY[soc][gender]
    character.title = title
  end
  return title
end

# 4) Don't use return when it isn't an early exit

NOBILITY = {
            "B" => { "f" => "Dame", "m" => "Knight" },
            "C" => { "f" => "Baroness", "m" => "Baron" },
            "D" => { "f" => "Marquesa", "m" => "Marquis" },
            "E" => { "f" => "Countess", "m" => "Count" },
            "F" => { "f" => "Duchess", "m" => "Duke" },
           }

def self.title(character)
  soc = character.upp[5,1]
  gender = character.gender[0]

  if NOBILITY.has_key?(soc)
    title = NOBILITY[soc][gender]
    character.title = title
  end

  title
end

# 5) Everything can be collapsed to the one real concept here:

NOBILITY = {
            "B" => { "f" => "Dame", "m" => "Knight" },
            "C" => { "f" => "Baroness", "m" => "Baron" },
            "D" => { "f" => "Marquesa", "m" => "Marquis" },
            "E" => { "f" => "Countess", "m" => "Count" },
            "F" => { "f" => "Duchess", "m" => "Duke" },
           }

def self.title(character)
  soc = character.upp[5,1]
  gender = character.gender[0]

  character.title = NOBILITY[soc][gender] if NOBILITY.has_key?(soc)
end

# 6) Which shows one real problem, this is called `title` but really
# sets it and returns it, every time. Either rename it or just
# return and set it somewhere else.

NOBILITY = {
            "B" => { "f" => "Dame", "m" => "Knight" },
            "C" => { "f" => "Baroness", "m" => "Baron" },
            "D" => { "f" => "Marquesa", "m" => "Marquis" },
            "E" => { "f" => "Countess", "m" => "Count" },
            "F" => { "f" => "Duchess", "m" => "Duke" },
           }

def self.calculate_title(character)
  soc = character.upp[5,1]
  gender = character.gender[0]

  character.title = NOBILITY[soc][gender] if NOBILITY.has_key?(soc)
end

# 7) The other real problem is that this is a class method on a module
# that is acting on Character. This isn't OO. This is procedural.
# It is the responsibility of a Character to know its title and
# shouldn't be externalized.

···

On Apr 24, 2017, at 1:56 AM, Leam Hall <leamhall@gmail.com> wrote:

On 04/23/17 23:20, Patrick Bayford wrote:

Not sure about others here, but I would find it useful to see at least a
rough idea of how your classes look. Personally, I like the superclass
idea, but know from personal experience, that designing the superclass
is difficult, mainly in respect of how much detail it should include.
This is especially important when dealing with serialized, stored data,
as changing the class/es tends to break the serializer!

Hey Patrick, thanks!

The Character class:
https://github.com/LeamHall/CT_Character_Generator/blob/master/lib/Tools/Character.rb

Uses Module CharacterTools to modify most of the data:
https://github.com/LeamHall/CT_Character_Generator/blob/master/lib/Tools/CharacterTools.rb

The Career class has the methods for running a career:
https://github.com/LeamHall/CT_Character_Generator/blob/master/lib/Tools/Career.rb

And uses data from subclasses, like:
https://github.com/LeamHall/CT_Character_Generator/blob/master/lib/Careers/Citizen.rb