## Session 9

### Let's Play a Game!

Today is a quiz day, so we don't want to wear ourselves out. Let's just play a little Bulls and Cows. Find a partner.

Each player writes down a 4-digit "secret number", hidden from the opponent. The four digits must be all different. The players then alternate, trying to guess their opponent's number. After each guess, the code maker gives feedback in turns of, you guessed, bulls and cows. If the guess contains a digit in its correct position, it is a "bull". If a digit is in the code but in a different position, it is a "cow".

For example, if the secret number is 3540 and the guess is 1234, the code maker responds "0 bull and 2 cows". "3" and "4" are cows. If the next guess is 3042, the feedback is "2 bulls and 1 cow".

The first player to reveal the other's secret number wins the game. Alternate going first, because the first guesser has an obvious advantage!

Play a few games of Bulls and Cows, but with three-digit numbers.

How easy is it to break the code in this version of the game?

Now, play a few games using a new feedback rule.

The feedback consists of a single number, computed by first multiplying the corresponding digits in the code and the guess, and then adding these products. For example, if the code is "4 7 2" and the guess is "1 2 3", then the code maker will give the number (4*1 + 7*2 + 2*3) = "24" as feedback.

How easy is it to break the code in this version of the game?

This may not seem easier, but the feedback actually tells us a lot... if we remember some algebra.

Now that you've played a bit, let's sketch some algorithms:

• an algorithm that is guaranteed to find the correct answer, with no regard for how long it takes
• an algorithm that takes advantage of the fact that we only need n equations to solve a system of n variables
• an algorithm that takes advantage of the place value of numbers

Huh? That last one must be require us to "zoom in" on some property.

### Algorithms and Knowledge

Writing an algorithm that guarantees an answer, if slowly, is pretty easy:

```    for i := 1 to 3 do
for j := 1 to 3 do
for k := 1 to 3 do
if code = [i, j, k]
return [i, j, k]
fail
```

This is a prototypical example of brute force: just whack away at the problem until we find a solution. Because we whack systematically, we know that we will find the answer -- eventually.

Could we do better by applying a little knowledge about the problem? When we play the version of the game with the dot product feedback, we know that each bit of feedback tells us this about the code:

```    gi1 * c1  +  gi2 * c2  +  gi3 * c3  =  feedbacki
```

So, we only need n equations before we will be able to solve the system algebraically. Such an algorithm, with some high-level steps, might look like:

```    for i := 1 to 3 do
guess gi,1gi,2gi,3
remember feedbacki
solve the system of 3 equations
```

This algorithm depends on the human reader to fill in some details, but if we take "solve the system" as an operator, it is reasonably clear. It's also reasonably efficient and as fast as we can expect on the average case.

Can we do better by choosing guesses systematically or based on the feedback given? For example, what does a guess of "1 1 1" tell us?

How about three guesses of "1 0 0", "0 1 0", and "0 0 1"? Three 'equations' and no real computation to be done...

Somewhat amazingly, we can do even better -- if we zoom in on the nature of the problem and are willing to make a first guess that is guaranteed to be wrong -- and if we are willing to play a little looser with the rules. Recall the nature of our feedback:

```    gi1 * c1  +  gi2 * c2  +  gi3 * c3  =  feedbacki
```

Because we know that the code consists of 1-digit numbers, we can make our first guess with:

```    100 10 1
```

100 and 10 aren't single digits, so we know they are wrong. But the feedback we receive will be:

```   100c1  +  10c2  +  1c3
```

So the feedback will tell our the code in place-value order! Our second 'guess' is guaranteed to succeed.

Solving the code on the second guess isn't much of an improvement in the case of a three-number code. With the systems-of-equations approach, we can solve it on our fourth guess in the worst case. But the system-of-equations approach requires n+1 guesses for a code of length n, whereas the zoom-in approach requires only two guesses regardless of the value of n.

What if we play with two-digit numbers? No problem; the place-value approach generalizes! For two-digit numbers, guess "10000 100 1". And for three-digit numbers, guess "1,000,000 1,000 1"; for n-digit numbers, guess "102n 10n 100".

But what if we play with longer strings, say, 100 5-digit numbers? Again, not a problem. Grow your guess string following the same pattern. It still works.

Understanding the nature of a problem -- bringing knowledge to bear in designing an algorithm -- can confer great power on the problem solver. We should seek to do so whenever the benefit exceeds the costs: the cost of acquiring the needed data, storing the needed data, computing the intermediate values, etc.

"But what if the problem we're solving doesn't involve integers?" Representation. Problem transformation.

### References

Bulls and Cows is one variation of a common code-breaking game played around the world. In the 1970s, it became a commercial success as Mastermind. In place of numbers, Mastermind uses pegs of six colors, and allows repeats in the code. This is equivalent to limiting the codes to the numbers [1111..6666].

Donald Knuth developed a clever algorithm that combines set coverage and minimax to solve win every Mastermind game in five moves or fewer. (See Knuth's original paper for even more.) The best known algorithm for playing Mastermind requires 4.34 turns on average.

There is a lot of code available on-line for playing both Mastermind and Bulls and Cows. Mastermind is a popular assignment in our Intermediate Computing course.