Code Kata - Bowling BlackJack

Code Kata - Bowling BlackJack

As an exercise; I'm doing a CodeKata... well... working on extending a code kata via BlackJack.
We've done the kata for Bowling. Now we're going to extend it to include BlackJack scoring.

BlackJack is a fairly simple game to score. You have points between 1 and 11; you sum them.
Right.
Now make it do Bowling and BlackJack.

Oh... As an added larf; Make it output FizzBuzz.

Yes.

I sometimes question my sanity; and then realize it abandoned me long ago.

I've taken; roughly; the output of my CodeKata's of Uncle Bob's Bowling Kata (maybe I'll refactor this to include a link) and prepared to write a test.

...
My first test, nice and simple; I want to score a 2 and a 2.

public void ShouldScore2And2(){
    Game game = new Game();
    game.Roll(2);
    game.Roll(2);
    Assert.AreEqual(game.Score(), 4);
}

The test passes.
Uh-oh - There's no Red phase.
How would I do a "K"ing? It's not numeric.
Crap, this is a major refactor to be able to sum a "K" and anything...

Major Refactor ... We don't do major refactors based off of a test. If there will be substantial re-writes to get the test to pass; it's the wrong test. (I'm excluding projects full of technical debt; sometimes it takes a lot of work to get something simple to pass)

What's the simplest way to implement code to pass this test?

public void ShouldScore2And2(){
    BlackJackGame game = new BlackJack Game();
    game.Hand(new []{2, 2});
    Assert.AreEqual(game.Score(), 4);
}

Make a new object. Have the Score method return 4. That's the simplest, dirtiest way.

All sins are forgiven in the pursuit of green.

I added a few more tests to ... not have a hard coded value...
I now have working BlackJack scoring code. Time to combine with Bowling! I think...

My general rule of abstracting/de-duplicating is:

  • If you C&P; refactor.
  • If you see similarities; wait for 3.

It's what I've found works best to be able to produce the best abstraction moving forward. A little debt is fine. I find it harder to reverse a bad abstraction than the debt of not abstracting early. With 3 variations; the points that abstract well are generally pretty obvious.

You can see here where I got to with this as of this point.
It's really... pretty scored.
Now I need to stare at it in the morning about how to merge it with the Bowling game. :\

A colleague is kindly letting me bash my head against the wall.

And if anyone notices... I'll be reverting the BustException empty objects are non-objects.

.... Apparently I started the migration to merge with the BowlingGame; stopped due to work being... work. It doesn't actually compile. ... I should have a build system in place...

The Next Day

I worked on this a lot this morning. By work I mean I sat and thought a lot. I had a number of conversations with a colleague on the kata and refactoring to the combined form.

I'm really resisting where the code and even the thinking is leading me.

I went in a certain direction; and ... Yea... it works. Just feels ... off. Not sure why.
But; being the only path I can think of; and the one I should probably go down... I went ahead with it. I still feel funny about it; but for having a single "Scoring Engine" that can score many games - It works.
Appears to work.

The solution I have for Bowling and BlackJack can be found here.

The GameScoring class is in the GameScoringTests.cs.

Now I'm gonna add FizzBuzz!

FizzBuzz

Adding fizzbuzz is pretty easy. I've written it 50+ times. Even doing this "scoring" via TDD; maybe 10 minutes. If that. But I /know/ it works and can prove it.

        public string Score(int number)
        {
            StringBuilder sb = new StringBuilder();
            
            if (number % 3 == 0)
            {
                sb.Append("Fizz");
            }

            if (number % 5 == 0)
            {
                sb.Append("Buzz");
            }

            if (sb.Length == 0)
            {
                sb.Append(number);
            }

            return sb.ToString();
        }

Is it perfect, meh. Could it be further optimized; yeah?
Do I care right now? No. All I could do is move the ifs into another method/class. This is a case where an if is required. We need to know select what to do based on the value.

Now that I have three scoring things; I need to refactor to support that this takes in a single number and outputs a string.

The hacky solution is to modify FizzBuzz to take an array of size 1. And Poker/Blackjack both output their score in a string.
That works. But... It's contrived and forcing the code to do things. It corrupts the system. It's ugly.
And by golly; I don't want that.

How can I get different inputs and return types?
...
Yes. I shall drink some Tea. T? Generics use T as a placeholder! Let's use generics!

I've loved C# generics since I understood them. It opened up a whole new world for me. I've abused them. I've used them when they shouldn't be... but it's so fun.

Anyway; in this case it's the best solution I thought of, being biased and all.. Otherwise it's objects or shudder dynamic.

Generic Scoring

The quick implementation I had which made everything work was kinda ugly.

 public class GameScoring<T, TU>
    {
        public interface IScoreEngine
        {
            TU Score(T values);
        }

        public TU Score(IScoreEngine simpleScore, T values)
        {
            return simpleScore.Score(values);
        }
    }

Making the call like

int blackjackScore = new GameScoring<int[], int().Score(new BlackJackGame(), new[] { 1, 2, 3, 4 });

had it all working and tests passing! Wooo!

Blackjack and it's interface get's a little bit of a change

public class BlackJackGame : GameScoring<int[], int>.IScoreEngine

but it's all pretty small.

Polishing Generics

This generally doesn't work out well. Generics tend to be a wart in the code. It's a smell when they are used. They allow engineers to be clever. Clever isn't normally good.
And; yes - I love generics because I feel clever.

In this case; the wart is that calling the Score method requires us to declare the input and output. I don't like this.
When I had time to sit down with it again; I worked on utilizing the type inference that C# has with Generics to remove that blight in the code.
It's a pretty small change

 public class GameScoring
    {
        public interface IScoreEngine<in T, out TU>
        {
            TU Score(T values);
        }

        public TU Score<T, TU>(IScoreEngine<T, TU> simpleScore, T values)
        {
            return simpleScore.Score(values);
        }
    }

Largely moving the Generics from the GameScoring class to the interface. Applying them there and to the Score method allows C# to do type inference based off of the passed in class.
To adjust to the generic location change; we update our scoring engine declaration to be

BlackJackGame : GameScoring.IScoreEngine<int[], int>

Moving the types from GameScoring to IScoreEngine.

When we make the call to the scoring engine

int blackjackScore = new GameScoring().Score(new BlackJackGame(), new[] { 1, 2, 3, 4 });

The Score method uses the generics declaration on BlackJackGame's IScoreEngine to know what the input and output arguments are. It helps keep the code clean by reducing the amount of duplication required.

And now - we have a scoring engine capable of running the scoring rules for any game. It's a super simple implementation; but definitely a fun exercise to explore things.

I believe I have one further iteration I want to try; but not sure how to TDD it there. I'll be looking for a way to do this. If I find a way; I'll be sure to post it here. :)

The ending version; with polished generics; is available here on github

Show Comments