diff --git a/app.py b/app.py index bcdef69..c0f92dd 100644 --- a/app.py +++ b/app.py @@ -1,247 +1,251 @@ -import heapq -import csv -from datetime import datetime - -# 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)), i + 1, 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), i + 1, tas - target - - return None, None, None # Aucun mouvement possible - -# Fonction pour sauvegarder l'historique dans un fichier CSV, format sur une seule ligne -def sauvegarder_historique(date_partie, vainqueur, historique, fichier="historique_parties.csv"): - # Construire la séquence des coups - coups = [] - for tas, nb_objets, joueur in historique: - coups.append(f"{joueur}({tas},{nb_objets})") - - with open(fichier, mode='a', newline='') as file: - writer = csv.writer(file) - # Une seule ligne avec la date, le vainqueur et tous les coups - writer.writerow([date_partie, vainqueur] + coups) - -# Exercice 9 : Interface utilisateur et IA -def jouer_nim(): - # Initialiser l'état du jeu - etat_jeu = [3, 4, 5] - historique = [] # Historique des coups (tas, nb_objets, joueur) - - 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: - historique.append((tas_index + 1, nb_objets, "Joueur")) - 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é !") - date_partie = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - sauvegarder_historique(date_partie, "Joueur", historique) - break - - # Tour de l'IA avec la stratégie gagnante - print("C'est au tour de l'IA...") - nouvel_etat, tas_ia, nb_objets_ia = strategie_gagnante(etat_jeu) - - # Gérer les mouvements invalides pour l'IA - if nouvel_etat is None: - print("IA n'a pas trouvé de mouvement valide.") - continue - - historique.append((tas_ia, nb_objets_ia, "IA")) - etat_jeu = nouvel_etat - - # Vérifier si l'IA a gagné - if est_etat_final(etat_jeu): - afficher_etat(etat_jeu) - print("L'IA a gagné !") - date_partie = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - sauvegarder_historique(date_partie, "IA", historique) - 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() +import heapq +import csv +from datetime import datetime + +# 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 (corrigée) +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)), i + 1, min(3, tas) # Retire le maximum autorisé + else: + # Stratégie gagnante : trouver le tas à modifier + for i, tas in enumerate(etat_jeu): + # Calculer ce qu'il reste si on change ce tas pour atteindre un XOR de 0 + target = tas ^ xor_total + if target < tas: + nb_a_retirer = tas - target + if nb_a_retirer > 0 and nb_a_retirer <= 3: # Vérifier qu'on retire entre 1 et 3 objets + return appliquer_mouvement(etat_jeu, i, nb_a_retirer), i + 1, nb_a_retirer + + # Si aucun mouvement n'est possible (ce qui ne devrait jamais arriver), renvoyer None + return None, None, None + +# Fonction pour sauvegarder l'historique dans un fichier CSV, format sur une seule ligne +def sauvegarder_historique(date_partie, vainqueur, historique, fichier="historique_parties.csv"): + # Construire la séquence des coups + coups = [] + for tas, nb_objets, joueur in historique: + coups.append(f"{joueur}({tas},{nb_objets})") + + with open(fichier, mode='a', newline='') as file: + writer = csv.writer(file) + # Une seule ligne avec la date, le vainqueur et tous les coups + writer.writerow([date_partie, vainqueur] + coups) + +# Exercice 9 : Interface utilisateur et IA +def jouer_nim(): + # Initialiser l'état du jeu + etat_jeu = [3, 4, 5] + historique = [] # Historique des coups (tas, nb_objets, joueur) + + 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: + historique.append((tas_index + 1, nb_objets, "Joueur")) + 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é !") + date_partie = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + sauvegarder_historique(date_partie, "Joueur", historique) + break + + # Tour de l'IA avec la stratégie gagnante + print("C'est au tour de l'IA...") + nouvel_etat, tas_ia, nb_objets_ia = strategie_gagnante(etat_jeu) + + # Gérer les mouvements invalides pour l'IA + if nouvel_etat is None: + print("IA n'a pas trouvé de mouvement valide.") + continue + + historique.append((tas_ia, nb_objets_ia, "IA")) + etat_jeu = nouvel_etat + + # Vérifier si l'IA a gagné + if est_etat_final(etat_jeu): + afficher_etat(etat_jeu) + print("L'IA a gagné !") + date_partie = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + sauvegarder_historique(date_partie, "IA", historique) + 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()