Search code examples
rubyruby-hash

Why is only one value in my hash being changed?


I'm making a simple RPG as a learning project, and am having an issue with part of the character creator.

This code should determine what skill string is assigned to player[:caste][:skill] and player[:sub][:skill], then increase each respective skill's value in player[:skills] by 2. This code should work regardless of what string is assigned to player[:caste][:skill] and player[:sub][:skill], as long as it is equal to player[:skills].to_s.

Currently, it is only applying the change to player[:skills][:endurance] but not player[:skills][:athletics].

player = {
  caste: {skill: "athletics"},
  sub: {skill: "endurance"},
  skills: {acrobatics: 0, athletics: 0, engineering: 0, endurance: 0, heal: 0, history: 0, influence: 0, insight: 0, magicka: 0, perception: 0, riding: 0, stealth: 0, streetwise: 0, thievery: 0},
}

player[:skills] = player[:skills].map do |skill, mod|
  [skill, (mod += 2 if skill.to_s == player[:caste][:skill])]
  [skill, (mod += 2 if skill.to_s == player[:sub][:skill])]
end.to_h

In other words, my code is returning the following player[:skills] hash:

skills: {acrobatics: 0, athletics: 0, engineering: 0, endurance: 2, heal: 0, history: 0, influence: 0, insight: 0, magicka: 0, perception: 0, riding: 0, stealth: 0, streetwise: 0, thievery: 0}

but I want it to return:

skills: {acrobatics: 0, athletics: 2, engineering: 0, endurance: 2, heal: 0, history: 0, influence: 0, insight: 0, magicka: 0, perception: 0, riding: 0, stealth: 0, streetwise: 0, thievery: 0}

Please let me know if there is a simpler way to do this. I've also tried the following:

player[:skills] = player[:skills].map do |skill, mod|
  [skill, (mod += 2 if skill.to_s == (player[:caste][:skill] || player[:sub][:skill]))]
end.to_h

which only affects the skill found in player[:caste][:skill].


Solution

  • When I run your code I get this as the result.

    {:acrobatics=>nil, :athletics=>nil, :engineering=>nil, :endurance=>2, :heal=>nil, :history=>nil, :influence=>nil, :insight=>nil, :magicka=>nil, :perception=>nil, :riding=>nil, :stealth=>nil, :streetwise=>nil, :thievery=>nil}
    

    That's because map returns last statement executed. In addition you actually only set a value for skill when it's matches the sub skill otherwise, it is set to nil.

    So whats happening in your code is that each iteration is returning the following which is the result of the last statement in the block passed into map.

    [:acrobatics, nil]
    [:athletics, nil]
    [:engineering, nil]
    [:endurance, 2]
    [:heal, nil]
    [:history, nil]
    [:influence, nil]
    [:insight, nil]
    [:magicka, nil]
    [:perception, nil]
    [:riding, nil]
    [:stealth, nil]
    [:streetwise, nil]
    [:thievery, nil]
    

    The final result being an array that looks like this.

    [[:acrobatics, nil], [:athletics, nil], [:engineering, nil], [:endurance, 2], [:heal, nil], [:history, nil], [:influence, nil], [:insight, nil], [:magicka, nil], [:perception, nil], [:riding, nil], [:stealth, nil], [:streetwise, nil], [:thievery, nil]]
    

    Which is finally mapped to a new hash

    {:acrobatics=>nil, :athletics=>nil, :engineering=>nil, :endurance=>2, :heal=>nil, :history=>nil, :influence=>nil, :insight=>nil, :magicka=>nil, :perception=>nil, :riding=>nil, :stealth=>nil, :streetwise=>nil, :thievery=>nil}
    

    The reason you get all those nil's is because in your statements the result of the case were the if statement is not true is nil. For example:

    [skill (mod += 2 if skill.to_s == player[:caste][:skill])]
    

    will return [the_skill, nil] for the cases were skill.to_s == player[:caste][:skill] is not true

    To see what's happening try this in irb.

    x = 0
    => 0
    x += 1 if false
    => nil
    x += 1 if true
    => 1 
    

    You could get past that using something like this.

    [skill, skill.to_s == player[:caste][:skill] ? mod + 2 : mod ]
    

    or using the above example:

    x = 0
    => 0
    x =  false ? x + 1 : x 
    => 0
    x =  true ? x + 1 : x
    => 1
    

    The following modified version of your code should work.

    player[:skills] = player[:skills].map do |skill, mod|
      [skill, skill.to_s == player[:caste][:skill] || skill.to_s == player[:sub][:skill] ? mod + 2 : mod ]
    end.to_h
    

    However, here is a slightly more verbose, but hopefully much easier to follow way to accomplish what you want to do and allows for added modifications in the future with out the code getting too confusing.

    player = {
      caste: {skill: "athletics"},
      sub: {skill: "endurance"},
      skills: {acrobatics: 0, athletics: 0, engineering: 0, endurance: 0, heal: 0, history: 0, influence: 0, insight: 0, magicka: 0, perception: 0, riding: 0, stealth: 0, streetwise: 0, thievery: 0},
    }
    
    player_caste_skill = player[:caste][:skill]
    player_sub_skill = player[:sub][:skill]
    current_skills = player[:skills]
    updated_skills = {}
    current_skills.each_pair do |skill, prev_value| 
      new_value = prev_value 
      case skill.to_s
        when player_caste_skill, player_sub_skill
          new_value = prev_value + 2
        when "some_other_skill"  
          new_value = prev_value + 3
      end
      updated_skills[skill] = new_value
    end
    puts current_skills
    puts updated_skills