I'm looking for an elegant way to compute the score of a guess in the MasterMind game in C#, preferably using LINQ.
In MasterMind, the codemaker generates a secret code of 4 digits using the digits 1 through 6. A digit may be used more than once. As an example, the secret code is:
int[] secret = { 1, 2, 3, 1 };
The codebreaker tries to break the secret code by presenting a guess. In this example, the guess is:
int[] guess = { 1, 1, 2, 2 };
(Both code and guess are now stored in an array, but other collection types are okay too).
The codemaker then "scores" this guess by announcing the number of "blacks" and "whites". A black is awarded for each digit from the guess which is correct in both value and position. A white is awarded for each correct digit placed in the wrong position. In this example, the score is 1 black (for the "1" in position 1) and 2 whites (for the "1" and "2" in positions 2 and 3).
Back to the question: I'm looking for an elegant way to compute the score of a guess in C#, preferably using LINQ. So far, I've come up with a statement that computes the number of blacks:
int blacks = new int[] { 0, 1, 2, 3 }.Count(i => (guess[i] == secret[i]));
I was going to proceed along the lines that the number of whites is the total number of matches (3) minus the number of blacks. So I tried:
int whites = guess.Intersect(secret).Count() - blacks;
But, alas, IEnumerable.Intersect() produces { 1, 2 } instead of { 1, 1, 2 }, because it looks at distinct digits only. So it computes whites = 1 instead of 2.
I cannot come up with another way of computing "whites", except from using "C" style nested loops. Can you? Preferably using LINQ - I like the way an algorithm can be expressed in code using LINQ. Execution speed is not really an issue.
At the end of each game, the Codemaker scores one point for every line of Code pegs placed by the Decoder. If the Decoder does not crack the code in nine attempts, that game is over and the Codemaker scores nine points. Make a note of your score after each game. Players switch roles at the end of each game.
For information, using this type of algorithm, the best combinations (the most difficult combinations to solve) are 1221 , 2354 , 3311 , 4524 , 5656 , 6643 .
For Master Mind games (4 columns, 6 colors, 64 = 1296 codes, optimal logical strategy applied), the optimal code to play at first attempt is 1233 (or any other equivalent code with "1 double + 2 different colors") with an average number of attempts to find secret codes of 5660⁄64 ≈ 4.367 and a maximal number of ...
var black = guess
.Zip(secret, (g, s) => g == s)
.Count(z => z);
var white = guess
.Intersect(secret)
.Sum(c =>
System.Math.Min(
secret.Count(x => x == c),
guess.Count(x => x == c))) - black;
Given:
int[] secret = { 1, 2, 3, 1 };
int[] guess = { 1, 1, 2, 2 };
Then:
black == 1 && white == 2
Here's one way (assuming I've understood the problem correctly):
Find the black score - This is easy enough; it's simply a matter of zipping the sequences up and counting the number of corresponding elements that match.
Find the number of "common elements" between both sequences - This must be the sum of the white and black scores.
Find the white score - Simply the difference between 2. and 1.
// There must be a nicer way of doing this bit
int blackPlusWhite = secret.GroupBy(sNum => sNum)
.Join(guess.GroupBy(gNum => gNum),
g => g.Key,
g => g.Key,
(g1, g2) => Math.Min(g1.Count(), g2.Count()))
.Sum();
int black = guess.Zip(secret, (gNum, sNum) => gNum == sNum)
.Count(correct => correct);
int white = blackPlusWhite - black;
EDIT: Mixed up black and white.
EDIT: (The OP is not on .NET 4) In .NET 3.5, you can calculate black with:
int black = Enumerable.Range(0, secret.Count)
.Count(i => secret[i] == guess[i]);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With