Sunday, April 24, 2011

Workout Update

Back on 8 Jan, I had finished up a workout cycle and posted my lifts at that time. Since then I’ve completed four more cycles. Here are the current numbers:

Life Weight (lbs) Reps Theoretical Max (lbs)
Military Press 140 1 145
Deadlift 300 3 329
Bench Press 240 5 330
Squat 230 4 260

Just looking at the weight, you would think that I’ve gotten stronger. In fact, I feel stronger. Yet, when comparing the theoretical max between 8 Jan and now, I have dropped in all the lifts except the bench.

My guess is that I underestimated my one-rep max (ORM) for all but the bench when I started the workout. Now the reps that I’m getting are closer to those on the workout. The third week of the cycle calls for three sets at 5,3 and 1 reps respectively. Previously I was getting 10s and 12s. Now the reps are closer to schedule.

The Bench Press results are most interesting. This is the lift that I was disappointed with the most since I wasn’t seeing the gains that I was seeing with the others. In fact, I was really considering changing this part of the workout. In reality, a 60lb increase in four months is probably pretty good.

Based on the two sets of results, I think I’ll stay on the workout, unchanged, for another three or four cycles.

Sunday, April 17, 2011

Greed Kata–Second attempt

When I wrote the post on my first attempt, I mentioned that I thought I was “missing something obvious.” It occurred to me after I put up that post.

A die can only be used in one scoring combination

This is probably obvious to everyone else but I’m only responsible for what goes on in my head.

This realization push me to think that calculating the score using the Pipes and Filters pattern would probably be a good fit. I started with the data that each scorer, the filter, would operate on:

   1: public class GameContext
   2:  {
   3:      public int Score;
   4:      public int[] DiceValues;
   5:  }

Each filter would use the die values stored in the DiceValues field and update the Score field.


For the scorers, I started with the single value rules:



A single one (1) is worth 100 points.
A single five (5) is worth 50 points.


Working through lead to the following:



   1: public class ValueScorer
   2: {
   3:     private readonly int _die;
   4:     private readonly int _value;
   5:  
   6:     public ValueScorer(int die, int value)
   7:     {
   8:         _die = die;
   9:         _value = value;
  10:     }
  11:  
  12:     public GameContext Compute(GameContext context)
  13:     {
  14:         if (0 == context.DiceValues.Length) return context;
  15:         var occurances = Array.FindAll(context.DiceValues, v => v== this._die).Length;
  16:         context.Score += occurances * this._value;
  17:  
  18:         List<int> diceValues = RemoveValuesUsedInAScoringCombination(context.DiceValues, occurances);
  19:         context.DiceValues = diceValues.ToArray();
  20:  
  21:         return context;
  22:     }
  23:  
  24:     private List<int> RemoveValuesUsedInAScoringCombination(int[] currentDiceValues, int occurances)
  25:     {
  26:         var diceValues = new List<int>(currentDiceValues);
  27:         foreach (var ii in Enumerable.Range(1, occurances))
  28:         {
  29:             diceValues.Remove(this._die);
  30:         }
  31:         return diceValues;
  32:     }
  33: }


The next set of rules:


A set of three ones (1) is worth 1000 points
A set of three of any other number is worth 100 time that number (ex. {2,2,2} = 200 points}.

For which I ended up with:

 


   1: public class TripleScorer
   2: {
   3:     public GameContext Compute(GameContext context)
   4:     {
   5:         if (0 == context.DiceValues.Length) return context;
   6:  
   7:         var triples = context.DiceValues.GroupBy(d => d).Where(g => g.Count() >= 3);
   8:  
   9:         context.Score += triples.Sum(g => (g.Count() / 3) * ((1 == g.Key) ? 1000 : g.Key * 100));
  10:  
  11:         List<int> diceValues = RemoveValuesUsedInAScoringCombination(context.DiceValues, triples);
  12:         context.DiceValues = diceValues.ToArray();
  13:  
  14:         return context;
  15:     }
  16:  
  17:     private List<int> RemoveValuesUsedInAScoringCombination(int[] currentDiceValues, IEnumerable<IGrouping<int, int>> triples)
  18:     {
  19:         var diceValues = new List<int>(currentDiceValues);
  20:         foreach (var tripleGroup in triples)
  21:         {
  22:             int digitsToRemove = (tripleGroup.Count()/3)*3;
  23:             foreach (var ii in Enumerable.Range(1,digitsToRemove))
  24:             {
  25:                 diceValues.Remove(tripleGroup.Key);
  26:             }
  27:         }
  28:         return diceValues;
  29:     }
  30: }


The method RemoveValuesUsedInAScoringCombination() is key to making this approach work. This method updates the DiceValues array in the context to remove the values that were used. This piece enforces the constraint that a die is only used once in a scoring combination.


The driver is really straight forward and look like:



   1: public class Scorer
   2: {
   3:     public int Computer(int[] diceValues)
   4:     {
   5:         if (0 == diceValues.Length) return 0;
   6:  
   7:         var tripleScorer = new TripleScorer();
   8:         var oneScorer = new ValueScorer(1,100);
   9:         var fiveScorer = new ValueScorer(5,50);
  10:  
  11:         var context = new GameContext() {DiceValues = diceValues, Score = 0};
  12:  
  13:         context = tripleScorer.Compute(context);
  14:         context = oneScorer.Compute(context);
  15:         context = fiveScorer.Compute(context);
  16:  
  17:         return context.Score;
  18:     }
  19: }

What I find really nice about this approach is that adding additional scoring combinations is just a matter of creating another scorer. For example, say that the ruling body for the game Greed introduces the following rule:



A set of five consecutive numbers (ex. {1,2,3,4,5} or {2,3,4,5,6}) is called a Straight and is worth 2000 points.


This is easily implemented with a Scorer such as:



   1: public class StraightScorer
   2: {
   3:     public GameContext Compute(GameContext context)
   4:     {
   5:         if (0 == context.DiceValues.Length) return context;
   6:  
   7:         foreach (int startingAt in Enumerable.Range(1,2))
   8:         {
   9:             if (this.DiceValuesContainStraight(startingAt, context.DiceValues))
  10:             {
  11:                 context.Score += 2000;
  12:                 context.DiceValues = this.RemoveValuesUsedInAScoringCombination(context.DiceValues, startingAt).ToArray();
  13:             }
  14:         }
  15:  
  16:         return context;
  17:     }
  18:  
  19:     private bool DiceValuesContainStraight(int straightStartsAt, int[] diceValues)
  20:     {
  21:         foreach (var n in Enumerable.Range(straightStartsAt,5))
  22:         {
  23:             if (!diceValues.Contains(n)) return false;
  24:         }
  25:         return true;
  26:     }
  27:  
  28:     private List<int> RemoveValuesUsedInAScoringCombination(int[] currentDiceValues, int startingAt)
  29:     {
  30:         var diceValues = new List<int>(currentDiceValues);
  31:         foreach (var n in Enumerable.Range(startingAt, 5))
  32:         {
  33:             diceValues.Remove(n);
  34:         }
  35:         return diceValues;
  36:     }
  37:  
  38: }

The driver gets modified as:



   1: public class Scorer
   2: {
   3:     public int Computer(int[] diceValues)
   4:     {
   5:         if (0 == diceValues.Length) return 0;
   6:  
   7:         var straightScorer = new StraightScorer();
   8:         var tripleScorer = new TripleScorer();
   9:         var oneScorer = new ValueScorer(1,100);
  10:         var fiveScorer = new ValueScorer(5,50);
  11:  
  12:         var context = new GameContext() {DiceValues = diceValues, Score = 0};
  13:  
  14:         context = straightScorer.Compute(context);
  15:         context = tripleScorer.Compute(context);
  16:         context = oneScorer.Compute(context);
  17:         context = fiveScorer.Compute(context);
  18:  
  19:         return context.Score;
  20:     }
  21: }

Modifying the previous version would not have been nearly as simple.

Wednesday, April 13, 2011

Greed Kata–First attempt

I’ve had this on my TODO list since this year’s CodeMash conference. If you’re not already familiar with the Greed kata, or kata’s in general, take a moment and read the article on Steve Gentile’s site. You can also take a look at his solution.

For my first attempt, I made an effort to stay away from Linq. I had no specific reason for this additional constraint. I was really just curious how the solution would turn out. So with that lead-in, here’s my first attempt:

   1: public class Scorer
   2: {
   3:     public int Compute(int[] diceValues)
   4:     {
   5:         if (0 == diceValues.Length) return 0;
   6:  
   7:         Dictionary<int, int> valueGroups = GroupByDiceValue(diceValues);
   8:  
   9:         int totalScore = 0;
  10:         foreach (KeyValuePair<int, int> group in valueGroups)
  11:         {
  12:             totalScore += ScoreGroup(group.Value, group.Key);
  13:         }
  14:         
  15:         return totalScore;
  16:     }
  17:  
  18:     private int ScoreGroup(int count, int value)
  19:     {
  20:         int groupScore = 0;
  21:         if (count>=3)
  22:         {
  23:             if (value == 1) groupScore += 1000;
  24:             else groupScore += value * 100;
  25:  
  26:             count -= 3;
  27:         }
  28:             
  29:         if (1 == value)
  30:         {
  31:             groupScore += count * 100;
  32:         }
  33:             
  34:         if (5 == value)
  35:         {
  36:             groupScore += count * 50;
  37:         }
  38:         return groupScore;
  39:     }
  40:  
  41:     private Dictionary<int, int> GroupByDiceValue(int[] diceValues)
  42:     {
  43:         Array.Sort(diceValues);
  44:         var valueGroups = new Dictionary<int, int>();
  45:         foreach (int value in diceValues)
  46:         {
  47:             int count = 0;
  48:             valueGroups.TryGetValue(value, out count);
  49:             valueGroups[value] = ++count;
  50:         }
  51:         return valueGroups;
  52:     }
  53: }

And here are the tests:



   1: [TestFixture]
   2: public class ScorerTests
   3: {
   4:     
   5:     [Test]
   6:     public void Compute_WhenNoValues_Score_0()
   7:     {
   8:         var scorer = new Scorer();
   9:         Assert.That(scorer.Compute(new int[]{}), Is.EqualTo(0));
  10:     }
  11:  
  12:     [Test]
  13:     public void Compute_ThreeOnes_Score_1000()
  14:     {
  15:         var scorer = new Scorer();
  16:         Assert.That(scorer.Compute(new int[] {1,1,1,2,3}), Is.EqualTo(1000));
  17:     }
  18:  
  19:     [Test]
  20:     public void Compute_ThreeTwos_Score_200()
  21:     {
  22:         var scorer = new Scorer();
  23:         Assert.That(scorer.Compute(new int[] { 3, 2, 2, 2, 3 }), Is.EqualTo(200));
  24:     }
  25:  
  26:     [Test]
  27:     public void Compute_ThreeThreess_Score_300()
  28:     {
  29:         var scorer = new Scorer();
  30:         Assert.That(scorer.Compute(new int[] { 4, 4, 3, 3, 3 }), Is.EqualTo(300));
  31:     }
  32:  
  33:     [Test]
  34:     public void Compute_ASingleOne_Score_100()
  35:     {
  36:         var scorer = new Scorer();
  37:         Assert.That(scorer.Compute(new int[] { 4, 4, 1, 2, 2 }), Is.EqualTo(100));
  38:     }
  39:  
  40:     [Test]
  41:     public void Compute_ASingleFive_Score_50()
  42:     {
  43:         var scorer = new Scorer();
  44:         Assert.That(scorer.Compute(new int[] { 4, 4, 5, 2, 2 }), Is.EqualTo(50));
  45:     }
  46:  
  47:     [Test]
  48:     public void Compute_FourOnes_Score_1100()
  49:     {
  50:         var scorer = new Scorer();
  51:         Assert.That(scorer.Compute(new int[] { 1, 1, 1, 1, 2 }), Is.EqualTo(1100));
  52:     }
  53:  
  54:     [Test]
  55:     public void Compute_ThreeOnesAndTwoFives_Score_1100()
  56:     {
  57:         var scorer = new Scorer();
  58:         Assert.That(scorer.Compute(new int[] { 5, 5, 1, 1, 1 }), Is.EqualTo(1100));
  59:     }
  60:  
  61:     [Test]
  62:     public void Compute_ThreeThreesAndOneFives_Score_350()
  63:     {
  64:         var scorer = new Scorer();
  65:         Assert.That(scorer.Compute(new int[] { 4, 5, 3, 3, 3 }), Is.EqualTo(350));
  66:     }
  67:  
  68:     [Test]
  69:     public void Compute_NoValidScoringCombinations_Score_0()
  70:     {
  71:         var scorer = new Scorer();
  72:         Assert.That(scorer.Compute(new int[] { 2, 2, 4, 4, 3 }), Is.EqualTo(0));
  73:     }
  74:  
  75: }

I’m not unhappy with the solution but the voice in my head is nagging me that I’m missing something obvious.