Trying to find an algorithm for the Number Mastermind game

In summary, the code that is supposed to guess the secret number for a game called "Mastermind" seems to be impossible to write without knowing the specific rules of the game first. However, by knowing the colors of the pegs and the positions of the pegs, it is possible to solve the game without needing to write code.
  • #36
Jarvis323 said:
439 (1) means P(437) > P(457) > 0
Au contraire, P(437) is zero. It cannot be the correct answer, while P(457) is 1/243.
437 has a higher probability of not scoring (0), but it has a zero probability of scoring (3).
 
Technology news on Phys.org
  • #37
Halc said:
Au contraire, P(437) is zero. It cannot be the correct answer, while P(457) is 1/243.
437 has a higher probability of not scoring (0), but it has a zero probability of scoring (3).
Oh yeah, that's true.

How about after having made these guesses?

123 (1)
353 (1)

Maybe you're right in your claim that at no point will the probability of one possible solution be different than another in the Bayesian sense (even in the generalized game to n digits)? But based on my intuition, I sort of doubt that. I might try to work it out later on when I have some more time.

It was only a suggestion, as my first thought.
 
  • Like
Likes JamieSalaor
  • #38
Jarvis323 said:
Maybe you're right in your claim that at no point will the probability of one possible solution be different than another in the Bayesian sense (even in the generalized game to n digits)?
Yes, even under the generalized game. But as I said, if P(guess) is defined as probability of not scoring zero, then your relation P(437) > P(457) above is true, at least for a low number of digits and a single initial guess scoring 1.

How about after having made these guesses?

123 (1)
353 (1)
It gets only slightly more complicated now.
If the trailing 3 is correct, then all the other digits are wrong and there are 64 possibilities remaining.
Otherwise the solution is 15x or 32x which totals more 18 possibilities, so the any given valid guess has an equal 1/82 probability of being correct. Did I do that right? The solutions in the two subsets cannot overlap, so you can just add them.

Arman777 said:
Maybe a hint ?
For the general case I'd have gone with a recursive algorithm, but the brute iteration probably works fine for these simple cases.
 
  • Like
Likes JamieSalaor
  • #39
pbuk said:
How can I tell that the secret is not 400?
From these two lines..
402 0

4 cannot be since it is 0.
pbuk said:
I'll give you another hint. From the example in #3:
Code:
Guess Mark
402    0
390    0
816    2
848    2
777    0
815    1
How can I tell that the secret is not 400?I've been doing this sort of thing for 45 years which means that (i) I have had a lot of practice and (ii) I have seen many different problems with some similar characteristics.
I understand the idea but these days I am really busy. But I ll try to write a code soon.
 
  • #40
I find the solution. But I write in R

Code:
digit_1 - c()
digit_2 - c()
digit_3 - c()
a - seq(100, 999)
for (i in a)
{
  b - unlist(strsplit(as.character(i), ))
  digit_1 - append(digit_1, as.integer(b[1]))
  digit_2 - append(digit_2, as.integer(b[2]))
  digit_3 - append(digit_3, as.integer(b[3]))
}
#creating a data frame for digits between (100, 999)
num_list - data.frame(digit1 = digit_1, digit2 = digit_2, digit3 = digit_3)

#obtaining the digits of a function as an array
digits - function(num)
{
  b - unlist(strsplit(as.character(num), ))
  return (c(as.integer(b[1]), as.integer(b[2]), as.integer(b[3])))
}

#For guess = 0
guess0 - Problem36_data$V1[Problem36_data$V2 == 0]
for (num in guess0)
{
  d - digits(num)
  ind1 - num_list$digit1 != d[1]
  ind2 - num_list$digit2 != d[2]
  ind3 - num_list$digit3 != d[3]
  num_list - num_list[ind1 & ind2 & ind3, ]
}

guess1 - Problem36_data$V1[Problem36_data$V2 == 1]
for (num in guess1)
{
  d - digits(num)
  ind1 - num_list$digit1 == d[1]
  ind2 - num_list$digit2 == d[2]
  ind3 - num_list$digit3 == d[3]
  num_list - num_list[ind1  ind2  ind3, ]
}

guess1 - Problem36_data$V1[Problem36_data$V2 == 1]
for (num in guess1)
{
  d - digits(num)
  ind1 - num_list$digit1 == d[1] & num_list$digit2 == d[2]
  ind2 - num_list$digit1 == d[1] & num_list$digit3 == d[3]
  ind3 - num_list$digit2 == d[2] & num_list$digit3 == d[3]
  ind4 - ind1  ind2  ind3
  num_list - num_list[!ind4, ]
}guess2 - Problem36_data$V1[Problem36_data$V2 == 2]
for (num in guess2)
{
  d - digits(num)
  ind1 - num_list$digit1 == d[1] & num_list$digit2 == d[2]
  ind2 - num_list$digit1 == d[1] & num_list$digit3 == d[3]
  ind3 - num_list$digit2 == d[2] & num_list$digit3 == d[3]
  num_list - num_list[ind1  ind2  ind3, ]
}

First a created a data frame of digits and then applies some conditions to obtain the secret number. For instance, if we had 4 digits, could we apply the same algorithm ?
 
  • #41
pbuk said:
Well I am not sure I can give a much better hint, and there seems to be a few posts here getting lost down rabbit holes so I am just going to post a solution in the spoiler below and on repl.it.

Python:
# The number of digits in a possible solution.
DIGITS = 3

def getDigit(n, index):
  return (n // (10 ** index)) % 10

def mark(answer, guess):
  correct = 0
  for digit in range(DIGITS):
    if getDigit(guess, digit) == getDigit(answer, digit):
      correct += 1
  return correct

def solve(problem):
  solutions = []
  # Iterate over all possible answers.
  for answer in range(10 ** DIGITS - 1):
    markIsWrong = False
    # Check the mark given for each round.
    for [guess, marked] in problem:
      if mark(answer, guess) != marked:
        markIsWrong = True
        break
    if (not markIsWrong):
      solutions.append(answer)
  return solutions

# Try the example problem.
problem = [
  [402, 0],
  [390, 0],
  [816, 2],
  [848, 2],
  [777, 0],
  [815, 1],
]

solutions = solve(problem)
count = len(solutions)

if count == 0:
  print('No solutions found')
elif count == 1:
  print('Found unique solution', solutions[0])
else:
  print('Found', count, 'solutions:', solutions)
Your algorithm seems different. Not what I was expected actually
 
  • #42
Arman777 said:
I am really not looking for a code. I am just trying to implement an algorithm. I have found one but it requires a great amount of coding and I was looking for a clever one.

Consider this as a new a game it looks like a mastermind yes, but it is different. Let me be muc more clear about the question. Suppose my friend Julia and I playing this game. She guessed a number which I don't know.

And she guessed "846". And my guesses are

402 0
390 0
816 2
848 2
777 0
815 1

For each guess Julia should answer how many digits are correct i.e. are the same in the proposed value and in his secret number - and are placed in the same position.

My algorithm should take the guess values and obtain the secret number.
The simplest method is the slowest with a max of 1,000 steps or 500 on average.
Start:
try all numbers 000 - 999 until you get 3 correct.
End:
You can speed it up because you know the changed digit(s) was the correct one.

The next method probably needs 2 or 3 random guesses at max, followed by 30 guesses at max for, say 33 guesses max or 17 on average.

Make random guesses till you have 0 correct.
Vary digit 1 till you have 1 correct, then vary digit 2 till you have 2 correct, then vary digit 3 till you have 3 correct.

The more complex the algorithm, the less steps are required. The next method should give you some ideas but is undoubtedly wrong in detail. Each step can be further optimised to solve in fewer guesses by remembering which is the correct (the varied) digit and not changing it again.

Start:
Solve_0
Make random guess (making a different guess for repeats will speed it up)
If 0 correct GOTO Solve_0
If 1 correct GOTO Solve_1
If 2 correct GOTO Solve_2
If 3 correct GOTO END

Solve_1:
REM We have 1 correct
Choose a digit at random and vary it
- If you now have none correct, the varied digit was correct. Put it back and remember it. Choose another digit and vary it. When you get 2 correct, vary the third till all 3 are correct. GOTO END
- if you still have one correct, keep varying the varied digit until you have two correct. GOTO Start_2
- if you have 2 correct GOTO Start_2

Start_2:
REM We have 2 correct
Solve_2:
Choose a digit at random and vary it (remembering correct digit(s) from Solve_1 will speed it up)
- if you still have two correct, keep varying that digit till you solve it. GOTO END
- If you now have only one correct, the one you varied was correct. Remember and vary another until you have 2 correct. Vary the third till you have 3 correct. GOTO END

END:

A recursive program would probably be just a few lines of code. To solve 3 we need to start with 2 solved. To solve 2 we need to start with 1 solved. To solve 1 we need to start with 0 solved.
 
Last edited:
  • #43
Frodo is right - the simplest is exhaustive. You have 1000 choices to check, and very conservatively, a computer can do a million per second, so you finish in a millisecond. How much faster does this need to be?

Put another way, no matter how many times a computer plays this game, you will never recover the time it takes to write a single additional line of code.

If your response is "fiddlesticks - I want speed, speed and more speed". The fastest thing to do is to look at the zeros and remove them from the searches: in the example, you know there are no 0s, 2s, 3s, 4s, 7s or 9s. That reduces the number of possibilities to check to 64. It's sure going to be faster to test all 64 than to figure out how to test a few less.

Frodo said:
You can speed it up because you know the changed digit(s) was the correct one.

Not if you run them in normal arithmetic order. If 119 has zero matches and 120 has one, is the 2 right or the 0? You would need to use something like Gray coded decimals.
 
  • Like
Likes sysprog
  • #44
Vanadium 50 said:
Not if you run them in normal arithmetic order. If 119 has zero matches and 120 has one, is the 2 right or the 0? You would need to use something like Gray coded decimals.
Sorry - I didn't make myself clear. If you are at 163 and you have zero correct, and you go to 164, and you have one correct, it must be the 4. You now only have to check the first two digits. It fails at 169 goes to 170.

More complexity results in fewer guesses needed to get the number. You could do a Gray code like progression (only one digit changes at a time) of the decimal digits - I think it results in needing about 11% more numbers to cover the field as some must be repeated to ensure only 1 decimal digit changes at each guess.

If you want the fewest number of guesses to get the solution then you need to construct a logic tree and decide what is the best guess to make now knowing everything you have already deduced - it's a type of state table.

Basically, after your first guess of a, b, c, you have four branches: 0 correct, 1 correct, 2 correct each with location unknown; or three correct.

With 0 correct, remove the 3 digits a, b, c you chose so you have only 7 left to work with. Do a random guess with d, e, f from these 7 digits.

With 1 correct you don't know where it is but you know one digit is a or b or c. Follow the logic above.

With 2 correct but not knowing which digits or where, follow the logic above.

You will, for example, need one route for "1 correct, position known" and another for "1 correct, position unknown".

This should get it done with the fewest number of guesses.

See Martin Gardner's A matchbox game learning machine Chapter 35 in The Colossal Book of Mathematics where he describes HER, a Hexapawn Educatable Robot, built from 28 matchboxes to play the game of Hexapawn. I have uploaded the pages in the PDF below. See the graph - starting from nothing it took the matchboxes 36 games, including 11 defeats, to "work out" how to play a perfect game.

You could probably make a matchbox equivalent to play your number guessing game.

graph.png

Years ago I wrote a solver for proper Mastermind using APL and it took eight lines of code. It included full error checking of the input and I didn't sweat trying to put it all on one line which was probably possible. I redid it in Basic and it was about 140 loc ... and failed on first trial when someone typed a Backspace into the input field :headbang:
 

Attachments

  • HER.pdf
    872.4 KB · Views: 272
Last edited:
  • #45
Frodo said:
Sorry - I didn't make myself clear. If you are at 163 and you have zero correct, and you go to 164, and you have one correct, it must be the 4. You now only have to check the first two digits. It fails at 169 goes to 170.

I understand that. But if you are at 119 and you have zero correct, and you go to 120, and you have one correct, you don't know if the right digit was 2 or 0. So if you want to do this, you need to organize things so you only change one digit at a time, e.g.

111
112
122
121
221
222
223
123
...
As I said above, I don't see a good reason to need to do better than a millisecond. If you feel the need to do 50 microseconds for personal pride, well...

Since this is about inferring the secret number based on someone else's guesses, and not coming up with a guessing strategy, a reasonable question is whether we are using the information in the best order.

If the first guess returns a zero, there are 343 remaining possibilities.
If the first guess returns a one, there are 243 remaining possibilities.
If the first guess returns a two, there are 27 remaining possibilities.
If the first - or any - guess returns a three, the answer is trivial.

That suggests there is something to be gained by looking at the input in the right order.
 
  • #46
Vanadium 50 said:
Well, it's hard to tell since we seem to be playing Calvinball, but I think "greedy" has at least two meanings: one would be to get the highest possible number of hits on this turn, and the other would be to remove as many incorrect solutions from the search space as possible.

The example is likely confusing because it is an example of non-optimal play:
After 848 there are only two possibilities: 846 and 818. Guessing 777 gives no new information. Guessing 717 or 776 or 778 etc. would all you to infer the answer in the next turn, but at this point no strategy is better than guessing one number and if it isn't right, guessing the other.
You know it is not 818 because 815 contains only one digit in the right place. That sequence of guesses and answers has only one remaining solution.

I will spitball how I think it might work.
I think I would create two 10x10x10 matrix which would correspond to the answer set. And have two options a 0 (for incorrect), or a 1 (for correct). Start with everything set to 0 in Matrix-A and 1 in Matrix-B. Matrix A has to be re-set to 1 after each round.

Any answer that is ZERO allows 3 sets to be marked as incorrect in the Matrix-B. So if you guess 1,1,1 and get zero, then 1,a,b is incorrect, a,1,b is incorrect, and a,b,1 is incorrect.

Any answer that is ONE allows 3 sets to be marked as correct in the Matrix-A. So if you guess 1,1,1 and get ONE, then 1,a,b is correct. As is a,1,b and a,b,1. In Matrix B, you change the 1,1,a and 1,a,1 and a,1,1 (those would have required a TWO).

Then compare Matrix B to Matrix A. Change any 1's in B to 0's if they are 0's in A.

Any answer that is TWO requires marking some other space as correct in Matrix-A. Again using 1,1,1 and getting TWO ... 1,1,a is marked as correct. 1,a,1 and a,1,1 get marked as correct. Set 1,1,1 as incorrect.

Then compare Matrix B to Matrix A. Change any 1's in B to 0's if they are 0's in B.

Codewise, you keep looping the same three counters from 0-9. You know you have an answer when the sum of the Matrix-B elements is 1.

Each guess allows you to mark elements as incorrect, in a freshly initialized Matrix A. Then transfer the information of disallowed answers into the matrix that started with everything allowed.

I think a person keeps track of the things that are excluded, or required, and tests the possible right answers against the others. Although this is not Mastermind, before you make the next guess, you see how the previous scoring would look against it. If it is not perfectly consistent with previous scoring, you try again. You have to exclude all the guesses that the current information excludes. I'm not sure how we do that in our head, but coding, I would do it with record keeping 10x10x10 matrices, and an i,j,k nested loop system.

In prior programs I have written, I have found that it is often necessary to mave more matrices than you expect. It might be good to have a 3rd. The code can often be very compact if you get the matrices and tests precisely thought out. Because you don't want to change the matrices to lose previous info, you often need a BUNCH of placeholder Matrices.

For example, the two guesses 848 and 816 in the example. The first allows 8,4,a and 8,a,8 and a,4,8. The second allows 8,1,a and 8,a,6 and a,1,6. The second guess allows a set that intersects with the set allowed by the first. If your code tracks the DISallowed sets, and keeps them cumulatively, then there ends with one answer. The other guess in that series of 815 and an answer of ONE, allows the sets 8,not-1,not-5 and not-8,1,not-5 and not-8,not-1,5 to intersect with those.

I hope that helps ... I know I have mistakes, but the gist is that if you set up the right loops and matrices, the code is not that crazy.
 
  • #47
votingmachine said:
I know I have mistakes, but the gist is that if you set up the right loops and matrices, the code is not that crazy.
The code is not crazy at all, I posted one solution in #26 and it fits into 20 SLOC.
 
Back
Top