import heapq import random class Noeud: def __init__(self, etat, parent, cout, heuristique): self.etat = etat self.parent = parent self.cout = cout self.heuristique = heuristique def __lt__(self, other): return self.cout + self.heuristique < other.cout + other.heuristique def __eq__(self, other): return self.etat == other.etat def __hash__(self): return hash(tuple(self.etat)) def __str__(self): return str(self.etat) def afficher_etat(etat_jeu) : print(etat_jeu) def xor_etat(etat_jeu): xor_total = 0 for nb_objets in etat_jeu: xor_total ^= nb_objets return xor_total def heuristique(etat_jeu): xor_valeur = xor_etat(etat_jeu) if xor_valeur == 0: # Position désavantageuse (perdante) return 100 + sum(etat_jeu) # Pénalité else: # Position avantageuse (gagnante) return sum(etat_jeu) # Heuristique standard def appliquer_mouvement(etat_jeu, tas_index, nombre_objets) : if nombre_objets > etat_jeu[tas_index] : print("Erreur : nombre d'objets à retirer supérieur au nombre d'objets dans le tas.") return etat_jeu else : etat_jeu[tas_index] -= nombre_objets return etat_jeu def cout_transition(etat_courant, etat_suivant): return 1 def est_etat_final(etat): return all(pile == 0 for pile in etat) def generer_successeurs(etat): successeurs = [] for i in range(len(etat)): if etat[i] > 0: for j in range(1, etat[i] + 1): nouvel_etat = etat[:] nouvel_etat[i] -= j successeurs.append(nouvel_etat) return successeurs def algorithme_a_star(etat_initial): noeud_initial = Noeud(etat_initial, None, 0, heuristique(etat_initial)) frontiere = [] heapq.heappush(frontiere, noeud_initial) visites = set() while frontiere: noeud_courant = heapq.heappop(frontiere) if est_etat_final(noeud_courant.etat): chemin = [] while noeud_courant: chemin.append(noeud_courant.etat) noeud_courant = noeud_courant.parent return chemin[::-1] visites.add(tuple(noeud_courant.etat)) for successeur in generer_successeurs(noeud_courant.etat): if tuple(successeur) not in visites: cout = noeud_courant.cout + cout_transition(noeud_courant.etat, successeur) heur = heuristique(successeur) noeud_successeur = Noeud(successeur, noeud_courant, cout, heur) heapq.heappush(frontiere, noeud_successeur) return None def reconstruire_chemin(noeud_final): """ Reconstruit la séquence de mouvements depuis l’état initial jusqu’à l’état final. :param noeud_final: Le nœud final de l'algorithme A*. :return: Une liste des états représentant le chemin de l'état initial à l'état final. """ chemin = [] noeud_courant = noeud_final while noeud_courant: chemin.append(noeud_courant.etat) noeud_courant = noeud_courant.parent return chemin[::-1] def sauvegarder_partie(historique, fichier="historique_parties.txt"): """ Sauvegarde l'historique de la partie dans un fichier. :param historique: Liste des coups effectués pendant la partie. :param fichier: Nom du fichier où sauvegarder l'historique. """ with open(fichier, "a") as f: f.write("Nouvelle partie\n") for coup in historique: f.write(f"{coup}\n") f.write("\n") def jeu_nim(etat_initial): etat = etat_initial[:] historique = [] while not est_etat_final(etat): afficher_etat(etat) try: pile = int(input("Entrez le numéro de la pile (1, 2, 3, ...): ")) - 1 nb_objets = int(input("Entrez le nombre d'objets à retirer: ")) historique.append(f"Joueur: Pile {pile + 1}, Objets retirés: {nb_objets}") appliquer_mouvement(etat, pile, nb_objets) afficher_etat(etat) except ValueError: print("Entrée invalide. Veuillez entrer des nombres entiers.") continue if est_etat_final(etat): print("\nFélicitations ! Vous avez gagné !") break # Tour de l'IA chemin_optimal = algorithme_a_star(etat) if chemin_optimal and len(chemin_optimal) > 1: etat_ia = chemin_optimal[1] historique.append(f"IA: Pile {pile + 1}, Objets retirés: {nb_objets}") print("\nL'IA joue...") etat = etat_ia if est_etat_final(etat): print("\n L'IA a gagné !") break sauvegarder_partie(historique) def pile_random(etat) : piles_non_vides = [i for i, pile in enumerate(etat) if pile > 0] return random.choice(piles_non_vides) def nb_objets_random(etat, pile): return random.randint(1, etat[pile]) def jeu_nim_random(etat_initial): etat = etat_initial[:] historique = [] while not est_etat_final(etat): afficher_etat(etat) try: pile = pile_random(etat) nb_objets = nb_objets_random(etat, pile) historique.append(f"Joueur: Pile {pile + 1}, Objets retirés: {nb_objets}") appliquer_mouvement(etat, pile, nb_objets) afficher_etat(etat) except ValueError: print("Entrée invalide. Veuillez entrer des nombres entiers.") continue if est_etat_final(etat): print("\nFélicitations ! Vous avez gagné !") break # Tour de l'IA chemin_optimal = algorithme_a_star(etat) if chemin_optimal and len(chemin_optimal) > 1: etat_ia = chemin_optimal[1] historique.append(f"IA: Pile {pile + 1}, Objets retirés: {nb_objets}") print("\nL'IA joue...") etat = etat_ia if est_etat_final(etat): print("\n L'IA a gagné !") break sauvegarder_partie(historique) etat_initial = [3, 4, 5] jeu_nim_random(etat_initial)