diff --git a/app.py b/app.py new file mode 100644 index 0000000..5de626e --- /dev/null +++ b/app.py @@ -0,0 +1,219 @@ +import heapq + +# Exercice 1 : Affichage de l'état du jeu +def afficher_etat(etat_jeu): + print("État actuel du jeu :") + for i, tas in enumerate(etat_jeu): + print(f"Tas {i+1}: {tas} objets") + print() + +# Exercice 2 : Génération des mouvements légaux avec un maximum de 3 objets retirés +def generer_mouvements(etat_jeu): + mouvements = [] + for i, tas in enumerate(etat_jeu): + # Limiter à un maximum de 3 objets retirés + for j in range(1, min(4, tas + 1)): # max 3 objets peuvent être retirés + nouvel_etat = etat_jeu.copy() + nouvel_etat[i] -= j + mouvements.append(nouvel_etat) + return mouvements + +# Exercice 3 : Appliquer un mouvement +def appliquer_mouvement(etat_jeu, tas_index, nb_objets): + if 0 <= tas_index < len(etat_jeu) and 1 <= nb_objets <= etat_jeu[tas_index] and nb_objets <= 3: + nouvel_etat = etat_jeu.copy() + nouvel_etat[tas_index] -= nb_objets + return nouvel_etat + else: + print("Mouvement invalide") + return None + +# Exercice 4 : Fonction heuristique +def heuristique(etat_jeu): + return sum(etat_jeu) + +# Exercice 5 : Définir l'état final et le coût +def est_etat_final(etat_jeu): + return all(tas == 0 for tas in etat_jeu) + +def cout_mouvement(etat_precedent, etat_suivant): + return 1 # Chaque mouvement a un coût de 1 + +# Exercice 6 : Classe Noeud et algorithme A* +class Noeud: + def __init__(self, etat, parent=None, g=0, h=0): + self.etat = etat + self.parent = parent + self.g = g # Coût du chemin depuis le départ + self.h = h # Heuristique + self.f = g + h # Coût total (g + h) + + def __lt__(self, autre): + return self.f < autre.f + +def algorithme_a_star(etat_initial): + file_priorite = [] + etat_initial_heuristique = heuristique(etat_initial) + noeud_initial = Noeud(etat_initial, g=0, h=etat_initial_heuristique) + heapq.heappush(file_priorite, noeud_initial) + + visites = set() + + while file_priorite: + noeud_courant = heapq.heappop(file_priorite) + + if est_etat_final(noeud_courant.etat): + return reconstruire_chemin(noeud_courant) + + visites.add(tuple(noeud_courant.etat)) + + for successeur in generer_mouvements(noeud_courant.etat): + if tuple(successeur) in visites: + continue + + g_successeur = noeud_courant.g + cout_mouvement(noeud_courant.etat, successeur) + h_successeur = heuristique(successeur) + noeud_successeur = Noeud(successeur, parent=noeud_courant, g=g_successeur, h=h_successeur) + heapq.heappush(file_priorite, noeud_successeur) + + return None # Aucun chemin trouvé + +# Exercice 8 : Reconstruction du chemin +def reconstruire_chemin(noeud_final): + chemin = [] + noeud_courant = noeud_final + while noeud_courant: + chemin.append(noeud_courant.etat) + noeud_courant = noeud_courant.parent + return chemin[::-1] + +# Fonction pour calculer le XOR de tous les tas +def calculer_xor(etat_jeu): + xor_total = 0 + for tas in etat_jeu: + xor_total ^= tas + return xor_total + +# Stratégie gagnante pour l'IA +def strategie_gagnante(etat_jeu): + xor_total = calculer_xor(etat_jeu) + + if xor_total == 0: + # Pas de stratégie gagnante, faire un mouvement quelconque + for i, tas in enumerate(etat_jeu): + if tas > 0: + return appliquer_mouvement(etat_jeu, i, min(3, tas)) # Retire le maximum autorisé + else: + # Stratégie gagnante : trouver le tas à modifier + for i, tas in enumerate(etat_jeu): + target = tas ^ xor_total + if target < tas: + return appliquer_mouvement(etat_jeu, i, tas - target) + + return None # Aucun mouvement possible + +# Exercice 9 : Interface utilisateur et IA +def jouer_nim(): + # Initialiser l'état du jeu + etat_jeu = [3, 4, 5] + + while True: + # Afficher l'état actuel du jeu + afficher_etat(etat_jeu) + + # Vérifier si l'état actuel est une victoire + if est_etat_final(etat_jeu): + print("Le jeu est terminé !") + break + + # Tour de l'utilisateur + print("C'est à votre tour !") + tas_index = int(input("Choisissez le tas (1, 2, 3) : ")) - 1 + nb_objets = int(input(f"Combien d'objets retirer du tas {tas_index + 1} (max 3) ? ")) + + nouvel_etat = appliquer_mouvement(etat_jeu, tas_index, nb_objets) + if nouvel_etat: + etat_jeu = nouvel_etat + else: + continue # Demande de nouveau si le mouvement est invalide + + # Vérifier si l'état actuel est une victoire + if est_etat_final(etat_jeu): + afficher_etat(etat_jeu) + print("Vous avez gagné !") + break + + # Tour de l'IA avec la stratégie gagnante + print("C'est au tour de l'IA...") + etat_jeu = strategie_gagnante(etat_jeu) + + # Vérifier si l'IA a gagné + if est_etat_final(etat_jeu): + afficher_etat(etat_jeu) + print("L'IA a gagné !") + break + +# Lancer le jeu +jouer_nim() + + +import unittest + +class TestJeuNim(unittest.TestCase): + + # Test pour l'affichage de l'état du jeu (Exercice 1) + def test_afficher_etat(self): + etat_jeu = [3, 4, 5] + # Ici on ne peut pas tester directement la sortie console, mais on peut vérifier si la fonction retourne les bons tas + self.assertEqual(afficher_etat(etat_jeu), None) # Il n'y a pas de retour attendu, juste un affichage + + # Test pour la génération des mouvements légaux (Exercice 2) + def test_generer_mouvements(self): + etat_jeu = [1, 2] + mouvements_attendus = [[0, 2], [1, 1], [1, 0]] # Il y a 3 mouvements possibles + self.assertEqual(generer_mouvements(etat_jeu), mouvements_attendus) + + # Test pour appliquer un mouvement valide (Exercice 3) + def test_appliquer_mouvement_valide(self): + etat_jeu = [3, 4, 5] + nouvel_etat = appliquer_mouvement(etat_jeu, 1, 3) + self.assertEqual(nouvel_etat, [3, 1, 5]) + + # Test pour appliquer un mouvement invalide (Exercice 3) + def test_appliquer_mouvement_invalide(self): + etat_jeu = [3, 4, 5] + nouvel_etat = appliquer_mouvement(etat_jeu, 1, 5) # Trop d'objets retirés + self.assertIsNone(nouvel_etat) + + # Test pour l'heuristique (Exercice 4) + def test_heuristique(self): + etat_jeu = [3, 4, 5] + self.assertEqual(heuristique(etat_jeu), 12) + + # Test pour la vérification de l'état final (Exercice 5) + def test_est_etat_final(self): + self.assertTrue(est_etat_final([0, 0, 0])) + self.assertFalse(est_etat_final([0, 1, 0])) + + # Test pour le coût des mouvements (Exercice 5) + def test_cout_mouvement(self): + self.assertEqual(cout_mouvement([3, 4, 5], [3, 4, 4]), 1) + + # Test pour l'algorithme A* (Exercice 6) avec validation de l'état final seulement + def test_algorithme_a_star(self): + etat_initial = [1, 2] + chemin_trouve = algorithme_a_star(etat_initial) + etat_final_attendu = [0, 0] + # Vérifier que le chemin atteint bien l'état final attendu + self.assertEqual(chemin_trouve[-1], etat_final_attendu) + + # Test pour la reconstruction du chemin (Exercice 8) + def test_reconstruire_chemin(self): + etat_initial = [3, 4, 5] + noeud_initial = Noeud(etat_initial) + noeud_final = Noeud([0, 0, 0], parent=noeud_initial) + chemin_attendu = [[3, 4, 5], [0, 0, 0]] + self.assertEqual(reconstruire_chemin(noeud_final), chemin_attendu) + +if __name__ == '__main__': + unittest.main()