I am trying to implement a Mastermind solving algorithm in python. For this, I tried implementing the Minimax solver by Knuth, which promises a worst case of 5 steps for 4 positions and 6 colors with repetition.
My version involves 10 numbers, 4 positions and no repetition. Also, the guesses cannot start with 0. So there are 9*9*8*7 = 4536 possible codes. I let the code play "against" itself for all possible combinations, presetting the first 2 guesses, and for the 4536 possible guesses, I get 241 plays where the solver takes 7 steps. So in this case, 7 steps is the worst case. Is it possible to optimize the algorithm to win everytime in 6 steps at most?
This is my code:
import random
possible_combs = []
possible_feedbacks = [[0,0], [0,1], [0,2], [0,3], [0,4], [1,0], [1,1], [1,2], [1,3], [2,0], [2,1], [2,2], [3,0]]
def check_dup(number):
if len(set(str(number))) != 4:
return False
else:
return True
for i in range(1000,10000):
if check_dup(i):
possible_combs.append(i)
all_combs = possible_combs.copy()
def match_answer(number, last_number):
pluses = sum([1 for a, b in zip(str(number), str(last_number)) if a == b])
minuses = len(set(str(last_number)) & (set(str(number)))) - pluses
return [pluses, minuses]
def constrain(pergj, answers, feedbacks):
leng = len(answers)
valid = True
if leng == 0:
return valid
else:
for i in range(0,leng):
if match_answer(pergj, answers[i]) != feedbacks[i]:
valid = False
return valid
#the minimax implementation with some prunning to make it faster
def get_next_guess(all_combs, possible_combs, answers):
min_guess = 10000
next_guess = 1234
for i in all_combs:
max = -1000
for j in possible_feedbacks:
if max > min_guess:
break
eliminated = possible_combs.copy()
for k in possible_combs:
if max > len(eliminated):
break
elif j != match_answer(k, i):
eliminated.remove(k)
if len(eliminated) > max:
max = len(eliminated)
if max < min_guess and i not in answers:
min_guess = max
next_guess = i
return next_guess
#because the second iteration of the minimax was too slow, I precomputed these values for each feedback that is possible after the starting guess of 1234
def get_second_guess(feedback):
if feedback == [0,0]:
return 5067
elif feedback == [0,1]:
return 5167
elif feedback == [0,2]:
return 2546
elif feedback == [0,3]:
return 2015
elif feedback == [0,4]:
return 1342
elif feedback == [1,0]:
return 1035
elif feedback == [1,1]:
return 5236
elif feedback == [1,2]:
return 5236
elif feedback == [1,3]:
return 1023
elif feedback == [2,0]:
return 5014
elif feedback == [2,1]:
return 2035
elif feedback == [2,2]:
return 1023
elif feedback == [3,0]:
return 1035
def check_minus(secret, guess):
str_secret= str(secret)
str_guess = str(guess)
count=0
for i in range(0,4):
if str_guess[i] in str_secret and str_guess[i] != str_secret[i]:
count += 1
return count
def check_plus(secret, guess):
str_secret= str(secret)
str_guess = str(guess)
count=0
for i in range(0,4):
if str_guess[i] == str_secret[i]:
count += 1
return count
attempts = []
#play against itself for all possible values
for number in all_combs:
possible_combs = []
for i in range(1000,10000):
if check_dup(i):
possible_combs.append(i)
secret = number
found = False
answers = []
feedbacks = []
while not found:
eliminated = possible_combs.copy()
if answers == []:
answer_attempt = 1234 #random.choice(possible_combs)
elif len(answers) == 1:
answer_attempt = get_second_guess(feedback)
else:
answer_attempt = get_next_guess(all_combs, possible_combs, answers)
answers.append(answer_attempt)
pluses = check_plus(secret, answer_attempt)
minuses = check_minus(secret, answer_attempt)
if int(pluses) == 4:
feedbacks.append([4,0])
found = True
attempts.append(len(answers))
else:
feedback = [int(pluses), int(minuses)]
feedbacks.append(feedback)
if feedback == [0,0]:
for i in possible_combs:
if len(set(str(answer_attempt)) & (set(str(i)))) > 0:
eliminated.remove(i)
possible_combs = eliminated.copy()
for i in possible_combs:
if feedback != match_answer(i, answer_attempt):
eliminated.remove(i)
possible_combs = eliminated.copy()
print(attempts)
One possible value which I got 7 attempts was e.g., 9872.
Is there any optimization possible? Or is 7 attempts max as good as it gets in this case?
Note: By minuses I mean correct color, wrong position. By pluses: Correct color, correct position.