197 lines
6.0 KiB
Python
197 lines
6.0 KiB
Python
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) |