Risk Card Sets

The venerable game of Risk™ has been around for over 60 years. It is a board game focused on international conflict. The tools of conflict? Little plastic armies, cards and dice. Players roll dice to determine conflict outcomes. They also collect and redeem sets of cards to gain additional armies.

The Risk Card Set kata involves building code to determine if the cards collected by an individual player represent a complete set. The criteria for what makes a valid set is defined by a few simple rules around the types of cards involved.

I wrote up the Risk Card Set kata a couple years ago after seeing an implementation (in a real, production version of the game) that bewildered me. The implementation was about five times longer than the code needed to be.

This kata is split into two parts. The first part drives in code to support the question, “do these three cards represent a valid set?” The second part of the kata adds a small nuance: “within this collection of one-through-five-or-more cards, are there any combinations that represent a valid set?” The two-part nature of the kata allows for some discussion of incrementalism in TDD.

Rather than me re-detail the original blog post here, please jump over to the original post. Within you’ll find all of the rules for the kata, as well as a number of questions intended to trigger discussion.

I’ve only used this kata a couple times in a learning setting, and with seasoned developers in both. They finished it reasonably quickly–in under 25 minutes each time.

The time to implement varies dramatically between people using high order array functions and those using rote, old school loop-based solutions: Even in a fairly verbose language like Java, my more-functional solution was only 8 statements (one constructor and 7 methods, each only one line of code). A tight old school solution is going to be at least twice as long.

I’ve kept this kata out of my TDD training for the time being, mostly out of concern that the results (how much people struggle, how long will they take) seem like they’re going to be wildly inconsistent. It also goes against my notion to not involve games in first-day TDD, lest people dismiss the exercise as too much of a “toy” problem.

Soundex

Soundex is a known algorithm for encoding last names into a 4-character string. The goal is to encode similar-sounding names to the same representation, so that searches with slightly misspelled names will still find appropriate matches. Langer, Langre, Langr, and Lungrub, for example, all end up encoded in Soundex as L526.

Retain the first letter of the name and drop all other occurrences of a, e, i, o, u, y, h, w.

Replace consonants with digits as follows (after the first letter):

b, f, p, v → 1

c, g, j, k, q, s, x, z → 2

d, t → 3

l → 4

m, n → 5

r → 6

If two or more letters with the same number are adjacent in the original name (before step 1), only retain the first letter; also two letters with the same number separated by ‘h’ or ‘w’ are coded as a single number, whereas such letters separated by a vowel are coded twice. This rule also applies to the first letter.

If you have too few letters in your word that you can’t assign three numbers, append with zeros until there are three numbers. If you have more than 3 letters, just retain the first 3 numbers.

Three of the rules (the first, second, and fourth) are straightforward and likely represent the first set of rules tackled. A first positive test could be as simple as ensuring that ‘A’ encodes to ‘A000’.

I take no credit for the third rule–I copied that glorious real-world prose verbatim from Wikipedia. Good luck with understanding it: I’ve run through the exercise several times, and each time I still am a little uncertain about just what it’s saying.

As a result of its good potential for confusion (the implementation can get just a little tricky too), I decided to abandon Soundex for use as a day-one TDD exercise. I’d rather students focus on things that don’t distract much from the TDD learning due to their complexity.

I do think Soundex remains a nice kata that demands a decent amount of “real” thinking.

The exercise is closed, in that the set of four rules above represent the complete functionality for the Soundex algorithm. There are no (non-contrived) ways to extend the exercise. However, there are variants to Soundex, including something known as Metaphone. One simple variant, “Reverse Soundex,” involves prefixing the last letter of the name rather than the first. For purposes of a slightly more interesting exercise, then, you could introduce a new requirement to allow for changing the algorithm based on some configuration value.

Duration**: 30-45 minutes

Core themes:

Incremental growth of a solution

Translating requirements into a test list

Test-driving something “real”

Getting green on read: Adding tests for confidence?

I used the Soundex exercise for a number of training sessions. For the book Modern C++ With Test-Driven Development, I used Soundex as the introductory lesson for teaching the fundamentals of test-driven development. I also later included a chapter that demonstrated use of the transformation priority premise as applied to the derivation of Soundex.

Name Normalizer

The name normalizer transforms a name from its typical western form (for example, “Henry David Thoreau”, where the surname appears last) into surname-comma-first form (for example, “Thoreau, Henry D.”), presumably to facilitate sorting a list of names by last name. Here’s an ordered list of tests to drive an incremental derivation of the name normalizer:

Returns empty string given an empty string or null

Returns a single-word name (mononym) straight-up (e.g. “Plato”)

Swaps first and last names (“Haruki Murakami” => “Murakami, Haruki”)

Trims leading & trailing whitespace

Initializes the middle name (“Langr, Jeffrey J.”)

Does not initialize a single-letter middle name (“Truman, Harry S”… the only one I can think of)

Remove periods from UK salutations (use some sort of property setting?) when last letter of salutation is same as last letter of the abbreviated version (e.g. “Mister” => “Mr”); do not remove period otherwise (“Captain => Capt.”)

Instead of throwing on two commas, assume that the first comma sets of a list of suffixes, each separated by commas

Alphabetize by de when surname is one syllable; otherwise alphabetize by last name (e.g. “De Claire, Jaime”; “Maupassant, Guy de”). This feature can drive the introduction of test doubles (i.e. a service that returns the number of syllables given a surname).

This is one of those exercises that you could probably keep busy with for a full morning if you really wanted–there are plenty more interesting rules about names throughout the world.

This has become my go-to first TDD exercise; I have students do it using “TDD paint by numbers,” i.e. they are provided with the tests already written (then uncomment and implement them, one-by-one). TDD paint-by-numbers allows you to launch very quickly into the first exercise, with minimal need for up-front discussion or explanation. Particularly, you can avoid any discussion about the testing or assertion framework.

Often I will show students a horrible implementation of the name normalizer before discussing their exercise, then ask them what sorts of problems it exhibits. I ask how safe they would feel if they had to add a new feature.

Recently I have been starting this exercise with support for the first ~3 tests already coded. First, this allows me to set the stage for what I hope their code looks like (highly declarative). Second, since they aren’t coding the first test, I can defer the typical angst about providing a hard-coded first implementation. (That discussion comes up and is addressed in a second exercise.) Third, it suggests that TDD isn’t just for “from-scratch” things. Fourth, it allows me to reiterate the point about the safety of making incremental additions to existing code with good tests.

Additional behaviors for which students may want tests (depending on their implementation or confidence level) can include appending suffixes to mononyms or stripping spaces from mononyms.

If I’ve shown students the horrible implementation first, talked a bit about declarative coding or programming by intention, and then stayed atop of them as they do their exercises, it’s possible they will produce reasonably refactored code for this exercise. (But you know how people are…)

Without these caveats, the solutions are generally a big mess. I’ve sometimes gone the route of letting them produce a mess, then re-running the exercise as a demo or in a mob, in which case I press the issue about appropriate refactoring.

My GitHub page contains a repository for Name Normalizer, in which you can find some starter tests in various programming languages. Others have already contributed; please feel free to do so. If you poke around at the branches, you will find some sample solutions as well (not all languages come with solutions–feel free, too, to provide one).

Answers the total count of values stored across all keys (e.g. “count of all definitions”)

Answers whether or not it is empty

Throws / returns an error when attempting to store a null key

Duration**: 45 minutes

Core themes:

Test ordering

Sticking with the R-G-R rhythm

Incrementally moving from specific to more generalized implementations

I used the Multimap as an introductory TDD exercise for a while many years ago. In turn working backward, this exercise replaced my use of test-driving a stack as an exercise; I moved off of that due to the negative feedback it garnered because of its over-simplicity (which appeared to help reinforce resistances to TDD).

Test-driving a multimap works well as a first exercise (or second exercise if the first was done using “TDD paint by numbers,” in which case you’d want to discuss a starter test list with the group). It does provide a couple opportunities for the students to create mildly interesting defects. It is “real” enough in that I’ve found a few occasions to use it in production code.

Note that some languages and / or frameworks (e.g. Guava for Java and C++11 / later) already provide a Multimap implementation. In this case, test-driving a multimap can still work as an exercise, though it could also provoke thoughts of “why are we wasting our time building something already readily available?”

Some possibilities for extra credit / extending the exercise.

Support adding multiple values at a key in addition to singular values

Provide the ability to find a key based on a predicate against the values

When training on TDD, I agonize most about the exercises. Students should be challenged enough to remain engaged, but not so much that they mire in a problem solution, to the detriment of their learning important TDD concepts. I’ve written a few blog posts about what I thought made for a good exercise–not overly trivial, not too short, not too long, not too mathematical, etc.–as well as how to run the session for a very first TDD exercise:

What would be nice is if every exercise came with training notes: information that would help you understand when and if it’s appropriate for your teaching context: how long will it take, what are the key themes the exercise helps impart, challenges to look for, and so on.

Over this and the next handful of blog posts, I’ll provide this information for some TDD exercises that I’ve devised.

The duration I suggest for each is roughly what it takes in a classroom setting where students are pairing. The duration can be impacted by a number of factors:

Is it their first TDD exercise or a subsequent one?

What’s their general level of programming proficiency?

What programming language is used? (C++ programmers, for example, will typically take longer than others)

Are they pairing, mobbing, or doing solo coding (not suggested)?

In the remainder of this post, I’ll describe the stock portfolio exercise.

Incrementally moving from specific to more generalized implementations

For me, this is usually the students’ second exercise; the prior is some form of TDD “paint by numbers” (where the tests were already written for them). Since they are writing their own tests for the first time, then, a core focus is on helping them understand where to start and what tests to write next.

Generally we talk through the first 5 or so tests as a group; I write these test names on the whiteboard and we discuss their potential implementation. It’s possible to write the first handful or so of tests–all of them around concepts of emptiness and symbol count–without having to introduce a key-value structure. (Even when they get around to the test that says the symbol count shouldn’t increase for a repeat purchase for a symbol, they can use a set before they need to introduce the key-value store.)

Students will likely come up with some additional validation test cases–negative numbers, nulls, etc. That’s ok though not very interesting. A key case to include and discuss is what the portfolio answers when asked for shares of a symbol not yet purchased (it should probably be 0). A test that not everyone will think of is what should happen to the count of unique symbols when all shares of a symbol have been sold.

One big plus of this exercise is that it supports many additional stories if needed. Here are two key ones:

List transaction history given certain criteria (or list all). Will require addition of a timestamp, and probably demonstrate that a key-value store might not be the best structure (time series, maybe?).

Calculate value of the portfolio (to help teach test doubles)

There are good opportunities for refactoring in this kata. Similarities between purchasing and selling should result in some useful common abstractions. Also, once the time series becomes a thing, it’s probably a good point to introduce another module / class.

Overall I’ve found that the stock portfolio exercise works very well. It provides enough opportunities for students to trip up, and starts to bang in the concept of incrementalism.