Lors de cette séance, vous écrirez et exécuterez vos premiers tests unitaires.
Important
Le but de cette séance est de vous aider à maîtriser des notions importantes en informatique. Un assistant de programmation intelligent tel que GitHub Copilot, que vous avez peut-être déjà installé, pourra vous fournir une solution à ces exercices uniquement à partir d’un nom de fichier judicieusement choisi.
Pour des raisons de formation, nous vous conseillons de désactiver d’abord ces outils.
À la fin de l’activité pratique, nous vous suggérons de retravailler l’exercice avec ces outils activés. Suivre ces deux étapes améliorera vos compétences à la fois fondamentalement et pratiquement.
De plus, nous vous fournissons les solutions des exercices. Assurez-vous de ne les consulter qu’après avoir une solution aux exercices, à des fins de comparaison ! Même si vous êtes sûr que votre solution est correcte, veuillez les consulter, car elles fournissent parfois des éléments supplémentaires que vous auriez pu manquer.
Contenu de l’activité
Préparation de l’environnement
Avant de commencer, créez un projet uv dédié à cette séance. Dans un terminal, placez-vous dans le dossier de la séance (par exemple imt/s5/info/prog/session3), puis lancez la commande suivante :
uv init --no-package
uv init --no-package
uv init --no-package
uv init --no-package
Vous écrirez tous les fichiers de cette séance dans ce projet, en ajoutant les bibliothèques dont vous avez besoin avec uv add et en exécutant vos programmes dans l’environnement virtuel associé. Au besoin, revoyez la fiche Gestion moderne de projet Python avec uv.
Argmax
On veut écrire une fonction argmax(lst) qui prend en paramètre une liste de valeurs numériques et retourne l’indice de la (première occurrence de la) valeur maximale dans cette liste. Si la liste est vide (len(lst) == 0) ou non initialisée (None), la fonction retourne -1.
Avant de coder la fonction, nous avons choisi d’écrire des tests unitaires pour décrire le comportement attendu de la fonction. Voici un exemple de test pour la fonction (copiez-collez-le dans un fichier tests_argmax.py), en supposant que argmax se trouve dans un fichier argmax.py :
# Needed importsimport unittest
from argmax import argmax
# Define a class for unit testsclassTestArgmax (unittest.TestCase):
# Here, we define a method with a simple test# You will have to write new tests, possibly in new methodsdeftest_argmax_standardcase (self):
self.assertEqual(1, argmax([1, 5, 3, 4, 2, 0]))
# Run the testsif __name__ =='__main__':
_ = unittest.main(verbosity=2)
Vous allez coder la fonction argmax(lst) et écrire d’autres tests pour vous assurer qu’elle fonctionne correctement. Avant de coder, vous devez lister quelques exemples uniques que vous pourrez également utiliser pour vos tests. Vous devez aussi documenter la fonction et, si nécessaire, commenter le code.
Important
L’instruction unittest.main() exécute les tests définis dans le fichier. La bonne pratique est de séparer les fonctions et les tests. N’oubliez donc pas d’importer le fichier (et les fonctions) dans le module contenant les tests.
À faire
Répondez à la question ci-dessus.
Correction
Cliquez ici pour afficher la correction.
Dans un fichier nommé, par exemple, argmax.py :
defargmax (lst: list[int]) -> int:
"""
This function returns the index of the maximum value occurring in a list of values.
In case of an empty list, it returns -1.
If the maximum value occurs multiple times, the index of the first occurrence is returned.
In:
* lst: The list of values.
Out:
* The index of the first occurrence of the maximum value, or -1.
"""# This whole function is equivalent to:# return lst.index(max(lst))# We detail the algorithm for pedagocical purpose# If not foundif lst isNoneor len(lst) ==0:
return-1# Otherwise, iterate on contents to find max max_val = lst[0]
max_index =0for i, v in enumerate(lst):
if v > max_val:
max_val = v
max_index = i
return max_index
Puis, dans tests_argmax.py :
# Needed importsimport unittest
from argmax import argmax
# Define a class for unit testsclassTestArgmax (unittest.TestCase):
# Here, we define a method with a simple test# You will have to write new tests, possibly in new methodsdeftest_argmax_standardcase (self):
self.assertEqual(1, argmax([1, 5, 3, 4, 2, 0]))
self.assertEqual(0, argmax([8, 5]))
self.assertEqual(1, argmax([-2, 9]))
deftest_argmax_none (self):
self.assertEqual(-1, argmax(None))
deftest_argmax_empty (self):
self.assertEqual(-1, argmax([]))
deftest_argmax_multiple_max (self):
self.assertEqual(2, argmax([1, 3, 5, 5, 2, 5]))
# Run the testsif __name__ =='__main__':
_ = unittest.main(argv=[""], verbosity=2, exit=False)
Année bissextile
Dans cet exercice, vous devrez écrire une fonction is_leap(year) qui prend une année en paramètre sous forme d’entier positif, et retourne une valeur booléenne qui est vraie si l’année est bissextile et fausse sinon.
Toute année divisible exactement par quatre est une année bissextile, sauf les années divisibles exactement par 100, mais ces années séculaires sont bissextiles si elles sont divisibles exactement par 400 (Wikipedia).
Nous ajoutons une règle supplémentaire qui précise que si l’entier passé en paramètre est strictement négatif, alors la valeur retournée est False.
Écrire les tests
Commençons par écrire un placeholder pour la fonction, qui sert juste à ce que la fonction soit définie :
defis_leap (year: int) -> bool:
"""
The function returns True if the input is a leap year and False otherwise.
This function only works for positive integers.
In:
* year: The year to be checked.
Out:
* True if the year is a leap year, False otherwise.
"""returnFalse
Avant de coder le corps de la fonction, vous écrirez les tests. Suivant le principe d’un test par concept, vous créerez les cinq tests suivants :
La valeur retournée est False pour une valeur négative (donnée en exemple).
Si la valeur d’entrée est non divisible par 4, alors le résultat attendu est False.
Si la valeur d’entrée est divisible par 100 (et donc par 4) mais pas par 400, alors le résultat attendu est False.
Si la valeur d’entrée est divisible par 4 mais pas par 100, alors le résultat attendu est True.
Si la valeur d’entrée est divisible par 400, alors le résultat attendu est True.
Voici un code de base pour commencer (en supposant que is_leap est dans un fichier isleap.py) :
# Needed importsimport unittest
from isleap import is_leap
classTestIsLeap (unittest.TestCase):
deftest_negative_value (self):
self.assertFalse(is_leap(-1000))
# Run the testsif __name__ =='__main__':
_ = unittest.main(verbosity=2)
Vous pouvez maintenant coder la logique de la fonction conformément aux spécifications. Vous devez commenter les lignes de manière significative pour rendre le code plus clair, en utilisant #.
Important
Assurez-vous que tous les tests passent !
À faire
Répondez à la question ci-dessus.
Correction
Cliquez ici pour afficher la correction.
defis_leap (year: int) -> bool:
"""
This function takes a year as input and returns True if the year is a leap year, False otherwise.
An additional constraint is that the year must be greater than 0, otherwise the function will return False.
In:
* year: The year to be checked.
Out:
* True if year is a leap year, False otherwise.
"""if year >=0:
if year %4==0:
if year %100==0:
return year %400==0# since the year is divisible by 100, # it must also be divisible by 400 to be a leap year else:
# divisible by 4 and not by 100returnTrueelse:
# not divisible by 4returnFalseelse:
# negative years are not leap yearsreturnFalse
defis_leap (year: int) -> bool:
"""
This function takes a year as input and returns True if the year is a leap year, False otherwise.
An additional constraint is that the year must be greater than 0, otherwise the function will return False.
In:
* year: The year to be checked.
Out:
* True if year is a leap year, False otherwise.
"""return year >=0and (year %4==0and (year %100!=0or year %400==0))
Mot de passe valide
Pour être considéré comme suffisamment sécurisé, un mot de passe doit respecter les règles suivantes :
Doit contenir au moins 8 caractères.
Doit contenir au moins une lettre majuscule.
Doit contenir au moins une lettre minuscule.
Doit contenir au moins un chiffre.
Doit contenir au moins un caractère spécial (parmi !@#$%^&*()).
Écrivez la fonction is_strong_password(password) qui prend en entrée une chaîne de caractères (a.k.a., séquence de caractères) et retourne un booléen indiquant si le mot de passe entré est fort. Pour aider à concevoir la logique de la fonction, vous pouvez la décomposer en sous-fonctions, chacune étant testée indépendamment. Un squelette de la fonction est fourni ci-dessous, divisé en sous-fonctions. Vous devez aussi documenter et commenter votre code.
Enfin, vous devez vérifier que les règles exprimées dans le cahier des charges sont bien prises en compte en écrivant des tests unitaires. La classe de test n’est pas fournie, vous devrez donc l’écrire vous-même. Chaque sous-fonction doit être testée indépendamment avec une variété d’exemples. Une fois chaque sous-fonction testée, vous pouvez tester la fonction principale.
Cette dernière étape peut être réalisée en binôme : chaque personne écrit les tests pour l’autre. Ensuite, vous pouvez évaluer la justesse de votre fonction en lançant les tests. Possiblement, soit la fonction, soit les tests devront être modifiés pour garantir que la fonction fonctionne correctement.
"""
A class to check if a password is strong.
There are 5 rules to check if a password is strong:
1. Must contain at least 8 characters.
2. Must contain at least one uppercase letter.
3. Must contain at least one lowercase letter.
4. Must contain at least one number.
5. Must contain at least one special character (from `!@#$%^&*()`).
"""special_chars ="!@#$%^&*()"defcheck_len(password: str) -> bool:
"""Check if the password has at least 8 characters.
Parameters:
password (str): The password
Returns:
bool: True, if password contains at least 8 characters, False otherwise
"""return len(password) >=8defcheck_uppercase(password: str) -> bool:
"""
Check if the password has at least one uppercase letter.
:param password: str, the password
:return: bool, True if the password has at least one uppercase letter, False otherwise
"""return any(char.isupper() for char in password)
defcheck_lowercase(password: str) -> bool:
"""
Check if the password has at least one lowercase letter.
:param password: str, the password
:return: bool, True if the password has at least one lowercase letter, False otherwise
"""return any(char.islower() for char in password)
defcheck_number(password: str) -> bool:
"""
Check if the password has at least one number.
:param password: str, the password
:return: bool, True if the password has at least one number, False otherwise
"""return any(char.isdigit() for char in password)
defcheck_special_char(password: str) -> bool:
"""
Check if the password has at least one special character.
:param password: str, the password
:return: bool, True if the password has at least one special character, False otherwise
"""return any(char in special_chars for char in password)
defis_strong(password: str) -> bool:
"""
Check if the password is strong.
To be strong, the password must satisfy the 5 rules.
:param password: str, the password
:return: bool, True if the password is strong, False otherwise
"""return all([
check_len(password),
check_uppercase(password),
check_lowercase(password),
check_number(password),
check_special_char(password)
])
if __name__ =='__main__':
password ="StrongPassword123!" print(is_strong(password))
Takuzu est un jeu de logique où vous devez compléter une grille pré-remplie $n\times n$ en utilisant uniquement des 0 et des 1. Une grille est correctement complétée si elle respecte les trois règles :
Chaque ligne et chaque colonne doit contenir un nombre égal de 0 et de 1.
Pas plus de deux nombres similaires consécutifs sont autorisés.
Chaque paire de lignes est différente, et chaque paire de colonnes est différente.
Un exemple de grille $4\times 4$ correctement complétée :
0 1 1 0
1 0 0 1
0 0 1 1
1 1 0 0
Vous êtes invité(e) à coder un vérificateur de grille Takuzu. Un squelette de module servira de base à votre travail. Vous devez respecter la partition des fonctions et implémenter chacune d’elles. Bien sûr, vous devrez aussi créer et compléter une classe de test.
"""
Takuzu is a binary puzzle game.
The objective is to fill a nxn grid with 0s and 1s according to the following rules:
1. Each row and column must contain an equal number of 0s and 1s.
2. No more than two consecutive similar numbers are allowed.
3. Each pair of rows is different, and each pair of columns is different.
The game is won when the grid is filled according to the rules.
This class provides a method to check if a grid is valid.
"""defcheck_is_square(grid: list[list[int]]) -> bool:
"""
Checks if the grid is square, that is, it has the same number of rows and columns.
:return: bool, True if the grid is square, False otherwise
"""return all(len(row) == len(grid) for row in grid)
defcheck_only_0s_and_1s(grid: list[list[int]]) -> bool:
"""
Checks if the grid contains only 0s and 1s.
:return: bool, True if the grid contains only 0s and 1s, False otherwise
"""return all(all(cell in [0, 1] for cell in row) for row in grid)
defcheck_number_of_0s_and_1s(seq: list[int]) -> bool:
"""
Checks if the sequence satisfies the first rule of Takuzu,
that is, each row and column must contain an equal number of 0s and 1s.
:return: bool, True if the sequence satisfies the rule, False otherwise
"""return seq.count(0) == seq.count(1)
defcheck_no_more_than_two_similar_numbers_consecutive(seq: list[int]) -> bool:
"""
Checks if the sequence satisfies the second rule of Takuzu,
that is, no more than two consecutive similar numbers are allowed.
:return: bool, True if the sequence satisfies the rule, False otherwise
"""return all(seq[i] != seq[i +1] or seq[i] != seq[i +2] for i in range(len(seq) -2))
defcheck_uniqueness(seq1: list[int], seq2: list[int]) -> bool:
"""
Checks if pair of rows (resp. columns) is different.
This helps verifying the 3rd rule of Takuzu.
:param seq1: list[int], the first sequence
:param seq2: list[int], the second sequence
:return: bool, True if the sequences are unique, False otherwise
"""return seq1 != seq2
defgrid_checker(grid: list[list[int]]) -> bool:
"""
Checks if the grid is valid according to the rules of
The three rules are:
1. Each row and column must contain an equal number of 0s and 1s.
2. No more than two similar numbers adjacent to each other are allowed.
3. Each pair of rows is different, and each pair of columns is different.
:param grid: list[list[int]], the grid to be checked
:return: bool, True if the grid is valid, False otherwise
"""ifnot check_is_square(grid):
returnFalseifnot check_only_0s_and_1s(grid):
returnFalsefor row in grid:
ifnot check_number_of_0s_and_1s(row):
returnFalseifnot check_no_more_than_two_similar_numbers_consecutive(row):
returnFalsefor col in range(len(grid)):
ifnot check_number_of_0s_and_1s([grid[row][col] for row in range(len(grid))]):
returnFalseifnot check_no_more_than_two_similar_numbers_consecutive(
[grid[row][col] for row in range(len(grid))]):
returnFalsefor i in range(len(grid)):
for j in range(i +1, len(grid)):
ifnot check_uniqueness(grid[i], grid[j]):
returnFalseifnot check_uniqueness([grid[row][i] for row in range(len(grid))],
[grid[row][j] for row in range(len(grid))]):
returnFalsereturnTrueif __name__ =='__main__':
grid = [[0, 1, 1, 0], [1, 0, 0, 1], [0, 0, 1, 1], [1, 1, 0, 0]]
print(grid_checker(grid))
Préparez-vous à coder le jeu Mastermind. Dans ce jeu, un codeur crée un code utilisant 4 chiffres de 1 à 6. Un décodeur doit faire jusqu’à dix essais pour casser le code. Pour chaque essai, le codeur donne des indices au décodeur. Les indices sont composés de 2 chiffres :
Le premier chiffre indique le nombre de bons chiffres à la bonne position.
Le second chiffre indique le nombre de bons chiffres à la mauvaise position.
Les occurrences multiples d’une valeur sont autorisées. S’il y a des valeurs en double, elles ne peuvent pas toutes recevoir un indice à moins qu’elles correspondent au même nombre de couleurs dupliquées dans le code caché. Par exemple, si le code est [6, 5, 1, 4] et la proposition est [1, 1, 2, 2], les indices sont (0, 1). En fait, la valeur 1 apparaît deux fois dans la proposition, mais une seule fois dans le code. Puisqu’elle n’est pas à la bonne place, elle compte pour une dans la mauvaise place. De plus, la valeur 2 apparaît aussi deux fois dans la proposition, mais pas dans le code. Elle n’apparaît pas dans les indices.
Pour avancer progressivement, nous allons décomposer la logique du jeu en fonctions et les coder au fur et à mesure. Nous utiliserons la description fournie pour documenter le code et écrire des tests.
Nous fournissons un projet composé de plusieurs classes (Mastermind, Player) partiellement implémentées. Des commentaires sont fournis pour toutes les méthodes présentes, qu’elles soient implémentées ou non. Des classes de tests sont également fournies pour vous aider à valider votre code.
Vous devrez coder les méthodes check_code(code), has_remaining_attempts(), is_right_answer(hints) et guess_pattern(code, guess). Nous recommandons de traiter une fonction après l’autre, en écrivant les tests avant ou juste après avoir codé la fonction. Comme la dernière fonction est la plus compliquée, vous pouvez, si vous le souhaitez, la décomposer en sous-fonctions, chacune testée indépendamment.
À faire
Pour commencer, vous devez télécharger le squelette de code fourni dans le dossier compresser mastermind.zip et le décompresser dans un dossier de votre choix. Puis, depuis VS Code Fichier > Ouvrir Dossier... sélectionnez le dossier décompressé.
Correction
Cliquez ici pour afficher la correction.
Seuls les quatre fichiers ci-dessous diffèrent du squelette fourni : main.py et mastermind/__init__.py sont à prendre tels quels.
import random
classMastermind:
"""
Classe représentant le jeu Mastermind.
Mastermind est un jeu où le joueur doit deviner un code secret composé de 4 chiffres entre 1 et 6.
Le joueur dispose de 10 tentatives pour deviner le code et gagne s'il réussit à le trouver en moins de 10 essais.
À chaque tentative, le jeu retourne un tuple de 2 nombres entre 0 et 4.
Les indices sont les suivants :
- Le premier chiffre indique le nombre de chiffres corrects à la bonne position
- Le second chiffre indique le nombre de chiffres corrects mais à la mauvaise position
Remarque : S'il y a des couleurs en double dans la proposition, elles ne peuvent pas toutes recevoir un indice sauf
si elles correspondent au même nombre de couleurs en double dans le code secret.
"""def__init__(self, chances: int =10, nb_digits: int =4, nb_colors: int =6):
"""
Initialise le jeu Mastermind avec un code secret.
:param chances: int, le nombre de chances dont dispose le joueur pour deviner le code. Par défaut 10.
:param nb_digits: int, le nombre de chiffres dans le code. Par défaut 4.
:param nb_colors: int, le nombre de couleurs (ou chiffres) pouvant être utilisés dans le code. Par défaut 6.
""" self.__nb_digits__ = nb_digits
self.__nb_colors__ = nb_colors
self.__code__ = [random.randint(1, nb_colors) for _ in range(nb_digits)]
self.__chances__ = chances
defset_code(self, code: list[int]):
"""
Définit le code secret. Cette méthode est utile à des fins de test.
Elle appelle la méthode check_code pour valider le code.
:param code: list[int], le code secret
:raise ValueError: si le code est invalide
"""if self.check_code(code):
self.__code__ = code
else:
raiseValueError(
f"Invalid code. Please enter {self.__nb_digits__} numbers between 1 and {self.__nb_colors__}.")
defcheck_code(self, code: list[int]) -> bool:
"""
Vérifie si le code est valide.
:param code: list[int], le code à vérifier
:return: bool, True si le code est valide, False sinon
"""# BEGIN SOLUTIONreturn len(code) == self.__nb_digits__ and all(1<= x <= self.__nb_colors__ for x in code)
# END SOLUTION@staticmethoddefprovide_hints(code: list[int], guess: list[int]) -> tuple[int, int]:
"""
Après chaque proposition du joueur, le jeu renverra un couple de nombres entre 0 et 4.
Les indices sont les suivants :
- Le premier chiffre indique le nombre de chiffres corrects à la bonne position
- Le second chiffre indique le nombre de chiffres corrects mais à la mauvaise position
Remarque : S'il y a des couleurs en double dans la proposition, elles ne peuvent pas toutes recevoir un indice sauf
si elles correspondent au même nombre de couleurs en double dans le code secret.
:param code: list[int], le code secret
:param guess: list[int], la proposition du joueur
:return: tuple[int, int], les indices
"""# BEGIN SOLUTION nb_correct =0 nb_misplaced =0 i =0# travailler sur des copies pour éviter de modifier les listes originales code = code.copy()
guess = guess.copy()
# D'abord, compte le nombre de chiffres corrects à la bonne positionwhile i < len(code):
if code[i] == guess[i]:
nb_correct +=1 code.pop(i)
guess.pop(i)
else:
i +=1# Ensuite, compte le nombre de chiffres corrects mais à la mauvaise position i =0while i < len(code):
if guess[i] in code:
nb_misplaced +=1 code.remove(guess[i])
guess.pop(i)
else:
i +=1return nb_correct, nb_misplaced
# END SOLUTIONdefevaluate_guess(self, guess: list[int]) -> tuple[bool, int, int]:
"""
Evalue la proposition du joueur et renvoie les indices.
:param guess: list[int], la proposition
:return: tuple[bool, int, int], le résultat de la proposition et les indices
"""# BEGIN SOLUTION self.__chances__ -=1 nb_correct, nb_misplaced = self.provide_hints(self.__code__, guess)
success = self.is_right_answer((nb_correct, nb_misplaced))
return success, nb_correct, nb_misplaced
# END SOLUTIONdefis_right_answer(self, hints: tuple[int, int]) -> bool:
"""
Check if the guess is the right answer.
Instead of checking the guess, the hints are provided as input.
:return: bool, True if the hints are (4,0), False otherwise
"""# BEGIN SOLUTIONreturn hints == (self.__nb_digits__, 0)
# END SOLUTIONdefhas_remaining_attempts(self) -> bool:
"""
Check if the player has remaining attempts.
:return: bool, True if the player has remaining attempts, False otherwise
"""# BEGIN SOLUTIONreturn self.__chances__ >0# END SOLUTION
from abc import ABC, abstractmethod
import itertools
import mastermind.mastermind
classPlayer(ABC):
"""
Classe abstraite pour un joueur dans le jeu Mastermind.
Cette classe définit les méthodes que tout joueur doit implémenter pour interagir avec le jeu Mastermind.
"""@abstractmethoddef__init__(self, nb_digits: int =4, nb_colors: int =6):
"""
Initialise le joueur.
Cette méthode peut être étendue par les sous-classes pour initialiser des attributs spécifiques au joueur.
""" self.nb_digits = nb_digits
self.nb_colors = nb_colors
@abstractmethoddefguess(self):
"""
Méthode pour faire une proposition de code secret.
Cette méthode doit être implémentée par les sous-classes pour permettre au joueur de faire une devinette.
:return: list[int], la proposition du joueur sous forme de liste d'entiers.
"""pass@abstractmethoddefreceive_feedback(self, success:bool, nb_correct:int, nb_misplaced:int):
"""
Méthode pour recevoir des retours sur la dernière devinette. Doit être implémentée par les sous-classes.
:param success: Indique si la devinette était correcte.
:param nb_correct: Nombre de positions correctes.
:param nb_misplaced: Nombre de couleurs mal placées.
"""passclassHumanPlayer(Player):
"""
Classe pour un joueur humain dans le jeu Mastermind.
Cette classe implémente les méthodes de la classe abstraite Player pour permettre à un joueur humain de jouer.
"""def__init__(self, nb_digits: int =4, nb_colors: int =6):
"""
Initialise le joueur humain.
:param nb_digits: int, le nombre de chiffres dans le code. Par défaut 4.
:param nb_colors: int, le nombre de couleurs (ou chiffres) pouvant être utilisés dans le code. Par défaut 6.
""" super().__init__(nb_digits, nb_colors)
defguess(self):
"""
Demande au joueur de faire une devinette et retourne la proposition sous forme de liste d'entiers.
:return: list[int], la proposition du joueur.
""" user_guess = input("Entrez votre proposition (sans caractères entre chaque chiffre): ")
# Convert the guess as a n-digit number to a list of integers# Assuming the input is not space-separated digits guess = [int(digit) for digit in user_guess if digit.isdigit()]
return guess
defreceive_feedback(self, nb_correct:int, nb_misplaced:int):
"""
Reçoit les retours du jeu après avoir fait une devinette.
:param success: Indique si la devinette était correcte.
:param nb_correct: Nombre de positions correctes.
:param nb_misplaced: Nombre de couleurs mal placées.
"""# Aucune action nécessaire pour le joueur humainpassclassKnuthPlayer(Player):
"""
Classe pour un joueur utilisant l'algorithme de Knuth dans le jeu Mastermind.
Cette classe implémente les méthodes de la classe abstraite Player pour permettre à un joueur utilisant l'algorithme de Knuth de jouer.
"""def__init__(self, nb_digits: int =4, nb_colors: int =6):
"""
Initialise le joueur Knuth.
""" super().__init__(nb_digits, nb_colors)
# BEGIN SOLUTION self.attempts =0# Initialisation des codes possibles selon les règles du jeu self.possible_codes = [list(code) for code in itertools.product(range(1, nb_colors +1), repeat=nb_digits)]
# La proposition courante du joueur self.current_guess =None# END SOLUTIONdefguess(self):
"""
Implémentation de la méthode guess pour l'algorithme de Knuth.
:return: list[int], la proposition du joueur.
"""# BEGIN SOLUTIONif self.attempts ==0:
# Premier coup de Knuth, si il y a 4 couleurs, on commence par 1,1,2,2 self.current_guess = [1, 1, 2, 2]
else:
# Algo minmax pour choisir la prochaine proposition# On choisit la première proposition possible qui n'a pas été éliminée par les retours précédents self.current_guess = self._minimax_guess()
# Retourne la proposition actuelle self.attempts +=1return self.current_guess
def_minimax_guess(self):
"""
Implémente l'algorithme minimax pour choisir la prochaine proposition.
:return: list[int], la prochaine proposition du joueur.
"""# Initialise les scores pour chaque code possible scores = {}
# Pour chaque code possible restantfor code in self.possible_codes:
# Considère ce code comme une solution potentielle feedback_counts = {}
# Pour chaque proposition possiblefor guess in self.possible_codes:
# Compare la proposition (guess) à la solution (code) feedback = mastermind.mastermind.Mastermind.provide_hints(code, guess)
# Compte le nombre de propositions donnant le même retour feedback_counts[feedback] = feedback_counts.get(feedback, 0) +1# Le score du code est le nombre maximal de propositions pour un même retour scores[tuple(code)] = max(feedback_counts.values())
# Sélectionne le code avec le score minimal best_guess = min(scores, key=scores.get)
return list(best_guess)
# END SOLUTIONdefreceive_feedback(self, nb_correct:int, nb_misplaced:int):
"""
Reçoit les retours du jeu après avoir fait une devinette.
:param nb_correct: Nombre de positions correctes.
:param nb_misplaced: Nombre de couleurs mal placées.
"""# BEGIN SOLUTION# Implémentation spécifique pour traiter les retours# Retire de S tout code qui ne donnerait pas cette réponse de pions colorés et blancs# si la proposition actuelle était la solution self.possible_codes = [
code for code in self.possible_codes
if mastermind.mastermind.Mastermind.provide_hints(self.current_guess, code) == (nb_correct, nb_misplaced)]
# END SOLUTION
L’algorithme de Knuth est une méthode efficace pour résoudre le jeu Mastermind en un nombre minimal de coups. Il utilise une stratégie d’élimination pour réduire le nombre de possibilités à chaque coup. L’algorithme commence par générer une liste de tous les codes possibles. Ensuite, il fait une supposition initiale, généralement [1, 1, 2, 2]. Après chaque coup, il utilise les indices fournis pour éliminer les codes qui ne correspondent pas aux indices. Il continue à faire des suppositions jusqu’à ce qu’il trouve le code secret ou qu’il ait épuisé le nombre de coups autorisés. L’algorithme de Knuth est connu pour sa capacité à résoudre le jeu en un maximum de 5 coups, ce qui en fait une méthode très efficace pour jouer au Mastermind.
Vous pouvez implémenter cet algorithme en Python, en déclarant une classe KnuthPlayer qui devra s’intégrer dans le jeu Mastermind.
Dans premier temps, si vous le souhaitez, vous pouvez ne pas implémenter l’algorithme minimax, mais simplement éliminer les combinaisons ne correspondant pas aux indices.
Bien entendu, il est indispensable de tester chaque fonction que vous implémentez pour vous assurer de leur bon fonctionnement.