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()