## 20/05/2010

### Kata Mastermind in ioke

A few months ago I was introduced to the Dojo XP France. A french and parisian occurrence of a programming dojo concentrating on practicing and discussing TDD.

While "every monday 18:30" is a bit too much for me to follow (not to mention go to as I have almost an hour of transit to go to the usual dojo place) I have been working on some of the Katas.

The last I played with was the Mastermind kata, more precisely a Ioke implementation of the kata.

I have been able to find a very pleasing solution to the kata (hopefully a valid solution too :) ) which I present below. I tried to separate several stages of the code, I hope this will make the progression easier to follow. Obviously the best way to see the code evolve is to come to the Dojo.

### Stage 1 : find the correct and well placed pegs

First the tests ...

```describe("evalm",
;stage 1
it("should return [0,0] when all pegs are the wrong color",
evalm([]("M","M","M","M"), []("B","B","B","B")) should == [](0,0)
)
it("should return [1,0] when the first peg of both secret and guess is the same",
evalm([]("B","M","M","M"), []("B","B","B","B")) should == [](1,0)
)
it("should return [1,0] when the second peg of both secret and guess is the same",
evalm([]("M","B","M","M"), []("B","B","B","B")) should == [](1,0)
)
it("should return [2,0] when the first 2 peg of both secret and guess is the same",
evalm([]("B","B","M","M"), []("B","B","B","B")) should == [](2,0)
)
)
```

Then the code ...

```;stage 1
evalm=method(guess, secret,
if(
guess==secret,
if(
guess==secret,
[](2,0),
[](1,0)
),
if(
guess==secret,
[](1,0),
[](0,0)
)
)
)
```

### Stage 2 : Refactor the code to DRY

Same tests as we refactor, thus the code ...

```;stage2 == stage 1 refactored
evalm=method(guess, secret,
good=guess zip(secret) filter(inject(==)) count
[](good, 0)
)
```

### Stage 3 : Introduce the notion of misplaced pegs for the "R"ed color

```describe("evalm",
;stage 1
it("should return [0,0] when all pegs are the wrong color",
evalm([]("M","M","M","M"), []("B","B","B","B")) should == [](0,0)
)
it("should return [1,0] when the first peg of both secret and guess is the same",
evalm([]("B","M","M","M"), []("B","B","B","B")) should == [](1,0)
)
it("should return [1,0] when the second peg of both secret and guess is the same",
evalm([]("M","B","M","M"), []("B","B","B","B")) should == [](1,0)
)
it("should return [2,0] when the first 2 peg of both secret and guess is the same",
evalm([]("B","B","M","M"), []("B","B","B","B")) should == [](2,0)
)
;stage 2 we refactor the good to use zip
;stage 3 introduce the misplaced
it("should return [0,1] when one red peg is misplaced",
evalm([]("M","R","M","M"), []("R","B","B","B")) should == [](0,1)
)
it("should return [0,2] when two red pegs are misplaced",
evalm([]("M","R","M","R"), []("R","B","R","B")) should == [](0,2)
)
)
```

And writing more code ...

```;stage 3
evalm=method(guess, secret,
good=guess zip(secret) filter(inject(==)) count
)
```

### Stage 4 : Generalize to all the colors

And yet more tests :) ...

```describe("evalm",
;stage 1
it("should return [0,0] when all pegs are the wrong color",
evalm([]("M","M","M","M"), []("B","B","B","B")) should == [](0,0)
)
it("should return [1,0] when the first peg of both secret and guess is the same",
evalm([]("B","M","M","M"), []("B","B","B","B")) should == [](1,0)
)
it("should return [1,0] when the second peg of both secret and guess is the same",
evalm([]("M","B","M","M"), []("B","B","B","B")) should == [](1,0)
)
it("should return [2,0] when the first 2 peg of both secret and guess is the same",
evalm([]("B","B","M","M"), []("B","B","B","B")) should == [](2,0)
)
;stage 2 we refactor the good to use zip
;stage 3 introduce the misplaced
it("should return [0,1] when one red peg is misplaced",
evalm([]("M","R","M","M"), []("R","B","B","B")) should == [](0,1)
)
it("should return [0,2] when two red pegs are misplaced",
evalm([]("M","R","M","R"), []("R","B","R","B")) should == [](0,2)
)
;stage 4 generalize to all colors
it("should return [0,2] when a red peg and a yellow peg are misplaced",
evalm([]("M","R","M","Y"), []("R","B","Y","B")) should == [](0,2)
)
)
```

For more code ...

```;stage 4
evalm=method(guess, secret,
good=guess zip(secret) filter(inject(==)) count
)
```

### Stage 5 : Refactor for DRY

Same tests, different code ...

```;stage 5
evalm=method(guess, secret,
sorted = guess zip(secret) groupBy(inject(==))
good = (sorted[true]||[]) count
)
```

While this code is longer, it uses groupBy instead of zipping and filtering the list twice. I would like to improve on this by ensuring that groupBy always returns an empty list [] whether there are values or not. However I haven't found an elegant way to do this ... yet.

### Stage 6: the complete solution with acceptance tests

The tests

```describe("evalm",
;stage 1
it("should return [0,0] when all pegs are the wrong color",
evalm([]("M","M","M","M"), []("B","B","B","B")) should == [](0,0)
)
it("should return [1,0] when the first peg of both secret and guess is the same",
evalm([]("B","M","M","M"), []("B","B","B","B")) should == [](1,0)
)
it("should return [1,0] when the second peg of both secret and guess is the same",
evalm([]("M","B","M","M"), []("B","B","B","B")) should == [](1,0)
)
it("should return [2,0] when the first 2 peg of both secret and guess is the same",
evalm([]("B","B","M","M"), []("B","B","B","B")) should == [](2,0)
)
;stage 2 we refactor the good to use zip
;stage 3 introduce the misplaced
it("should return [0,1] when one red peg is misplaced",
evalm([]("M","R","M","M"), []("R","B","B","B")) should == [](0,1)
)
it("should return [0,2] when two red pegs are misplaced",
evalm([]("M","R","M","R"), []("R","B","R","B")) should == [](0,2)
)
;stage 4 generalize to all colors
it("should return [0,2] when a red peg and a yellow peg are misplaced",
evalm([]("M","R","M","Y"), []("R","B","Y","B")) should == [](0,2)
)
)
;stage 5 refactor to use groupBy
;stage 6 acceptance tests
describe("recette",
it("should return [0,0] for guess(M,M,M,M) secret(B,B,B,B)",
evalm([]("M","M","M","M"), []("B","B","B","B")) should == [](0,0)
)
it("should return [4,0] for guess(B,B,B,B) secret(B,B,B,B)",
evalm([]("B","B","B","B"), []("B","B","B","B")) should == [](4,0)
)
it("should return [0,4] for guess(A,B,C,D) secret(D,A,B,C)",
evalm([]("A","B","C","D"), []("D","A","B","C")) should == [](0,4)
)
it("should return [2,2] for guess(A,B,C,B) secret(C,B,A,B)",
evalm([]("A","B","C","B"), []("C","B","A","B")) should == [](2,2)
)
)
```

And the code

```;stage 5
evalm=method(guess, secret,
sorted = guess zip(secret) groupBy(inject(==))
good = (sorted[true]||[]) count