mastermind python

Un MasterMind en Python

Nous allons voir comment implémenter un MasterMind en Python, mais uniquement en mode console.

MasterMind en Python: le jeu

Le jeu du MasterMind consiste deviner une combinaison de couleurs.

À chaque proposition, votre adversaire doit vous dire:

  • combien de couleurs sont au(x) bon(s) endroit(s), sans indiquer lesquelles;
  • combien de couleurs sont bonnes mais pas au bon endroit.

Vous l’aurez compris, votre adversaire, ce sera l’ordinateur… C’est lui qui décidera la combinaison de couleurs à trouver.

MasterMind en Python: implémentation

Préliminaires

Nous allons décider du fait que les couleurs seront représentées par des lettres capitalisées de l’alphabet latin: A, B, C, D, …

Nous allons donc écrire une première fonction qui retourne l’alphabet sur lequel s’appuyer pour générer une combinaison de couleurs à trouver:

from string import ascii_uppercase

def gen_colors(code_size):
    if code_size <= 26:
        return ascii_uppercase[:code_size]
    else:
        return ascii_uppercase

Cette fonction retourne donc les n premières lettres de l’alphabet si n< 27, et retourne l’alphabet complet si n > 26.

>>> gen_colors(5)
'ABCDE'
>>> gen_colors(200)
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'

Il nous faut maintenant une fonction retournant une combinaison aléatoire de n couleurs prises parmi toutes celles de ce nouvel ensemble.

from random import choice
def gen_code(code_size, colors):
    r = ''
    for _ in range(code_size):
        r += choice(colors)
>>> gen_code(5,'ABCDEFGH')
'GCBAG'

Vérifications

Nous allons dans un premier temps écrire une fonction check_guess(guess, code_size, colors) qui vérifie si, d’une part, la longueur de “guess” est la même que code_size, et d’autre part si une couleur présente dans “guess” est aussi dans “colors”:

def check_guess(guess, code_size, colors):
    present_colors = [ i in colors for i in guess ]
    return len( guess ) == code_size and False not in present_colors

J’ai opté pour cette solution, mais il y a d’autres possibilités.

Ici, je construis une liste de booléens correspondants au fait que chaque élément de “guess” se trouve ou non dans “colors”. Si il n’y a aucun “False” dans cette liste, cela signifie que toutes les couleurs proposées sont dans l’alphabet autorisé.

Si la longueur de “guess” est la même que celle de “colors” ET si aucun “False” est dans la liste construite, la fonction renvoie “True”.

Renvoi du nombre de bonnes couleurs

def score_guess(code, guess):
    n_good_position = 0
    n_false_position = 0
    if len( code ) == len( guess ):
        for i in range( len(code) ):
            if code[i] == guess[i]:
                n_good_position += 1
            elif guess[i] in code:
                n_false_position += 1
        
        return n_good_position , n_false_position
>>> score_guess('ABCD' , 'AABC')
(1, 3)

Il y a en effet 1 bonne lettre (à sa place) et en tout 3 lettres correctes mais pas à la bonne place (le second ‘A’ est à la deuxième position et non à la 1ère, le ‘B’ et le ‘C’ sont bonnes mais pas au bon endroit).

Le jeu

def play(code_size, nb_colors):
    print(f'Les différentes couleurs possibles sont: {gen_colors(nb_colors)}.')
    print(f'Le code à trouver est de longueur {code_size}.')
    n = 0
    to_find = gen_code(code_size, gen_colors(nb_colors)) # combinaison à trouver
    while True:
        guess = input(f'{n} --> ').upper()
        if not check_guess(guess, code_size, gen_colors(nb_colors)):
            print('Mauvaise taille ou couleur...')
        elif guess != to_find:
            print( score_guess(to_find, guess) )
            n += 1
        else:
            print( f'Félicitations, vous avez trouvé après {n+1} essais!' )
            break
>>> play(4,4)
Les différentes couleurs possibles sont: ABCD.
Le code à trouver est de longueur 4.
0 --> ABCD
(1, 2)
1 --> ABBB
(0, 1)
2 --> CBCC
(1, 2)
3 --> BBDD
(1, 1)
4 --> DBCB
(0, 2)

Bon, je ne sait pas pour vous mais c’est assez frustrant de ne pas trouver après un certains nombre d’essais.

Je vais donc ajouter un argument à ma fonction pour limiter le nombre de coups maximum.

def play(code_size, nb_colors , nb_max = None):
    print(f'Possible colors are: {gen_colors(nb_colors)}.')
    print(f'Code size is {code_size}.')
    n = 0
    count = 0
    to_find = gen_code(code_size, gen_colors(nb_colors)) # combinaison à trouver
    while True:
        if nb_max != None and n <= nb_max or nb_max == None:
            guess = input(f'{n} --> ').upper()
            if not check_guess(guess, code_size, gen_colors(nb_colors)):
                print('Mauvaise taille ou couleur...')
            elif guess != to_find:
                print( score_guess(to_find, guess) )
                n += 1
            else:
                print( f'Félicitations, vous avez trouvé après {n+1} essais!' )
                break
        else:
            print(f'Il fallait trouver: {to_find}')
            break
>>> play(4,4,3)
Possible colors are: ABCD.
Code size is 4.
0 --> aaaa
(1, 3)
1 --> bbbb
(1, 3)
2 --> cccc
(1, 3)
3 --> abcd
(1, 3)
Il fallait trouver: DACB

Mémorisation des propositions

Dans la pratique, il est possible de s’y perdre avec toutes les propositions que l’on a faite.

Si l’on souhaite ne pas compter les coups identiques, on peut mémoriser tous les coups dans une liste L.

def play(code_size, nb_colors , nb_max = None):
    print(f'Possible colors are: {gen_colors(nb_colors)}.')
    print(f'Code size is {code_size}.')
    n = 0
    count = 0
    to_find = gen_code(code_size, gen_colors(nb_colors)) # combinaison à trouver
    L = []
    while True:
        if nb_max != None and n <= nb_max or nb_max == None:
            guess = input(f'{n} --> ').upper()
            if not check_guess(guess, code_size, gen_colors(nb_colors)):
                print('Mauvaise taille ou couleur...')
            elif  guess in L:
                print('Proposition déjà faite avant...')
            elif guess != to_find:
                print( score_guess(to_find, guess) )
                L.append( guess )
                n += 1
            else:
                print( f'Félicitations, vous avez trouvé après {n+1} essais!' )
                break
        else:
            print(f'Il fallait trouver: {to_find}')
            break
>>> play(4,4,3)
Possible colors are: ABCD.
Code size is 4.
0 --> abcd
(0, 4)
1 --> abcd
Proposition déjà faite avant...
1 --> aabb
(0, 4)
2 --> bbbb
(1, 3)
3 --> dcba
(2, 2)
Il fallait trouver: BCDA

Bon, là, on est pas mal non ?

Laisser un commentaire