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