Ce projet est une implémentation en Python du jeu classique Puissance 4 où un joueur peut affronter une IA. Il utilise la bibliothèque Pygame pour l'interface et le contrôle du jeu. La décision de l'IA est basée sur l'algorithme de recherche d'arbre de Monte Carlo, et le niveau de difficulté est réglable, permettant aux joueurs de se défier contre des adversaires IA plus intelligents.
Concepts clés :
Utilisation de Pygame pour le développement de jeux.
Implémentation de l'algorithme de recherche d'arbre de Monte Carlo pour la décision de l'IA.
👀 Aperçu
🎯 Tâches
Dans ce projet, vous allez apprendre :
Comment construire un jeu à l'aide de Pygame
Comment implémenter l'algorithme de recherche d'arbre de Monte Carlo pour la décision de l'IA
Comment personnaliser et améliorer le niveau de difficulté de l'IA
Comment créer un jeu Puissance 4 amusant et interactif pour les combats humain contre IA
🏆 Récapitulatif
Après avoir terminé ce projet, vous serez capable de :
Développer des jeux en utilisant Python et Pygame
Comprendre les principes de l'algorithme de recherche d'arbre de Monte Carlo
Régler la difficulté d'un adversaire IA pour créer une expérience de jeu stimulante
Améliorer les interfaces utilisateur pour rendre l'expérience de jeu plus captivante
Préparatifs de développement
Le jeu Puissance 4 se joue sur une grille de taille 7*6. Les joueurs prennent tour à tour pour déposer leurs jetons depuis le haut d'une colonne. Le jeton tombe dans l'espace vide le plus bas de cette colonne. Le joueur qui alignes quatre jetons en ligne droite (horizontalement, verticalement ou en diagonale) gagne la partie.
Créez un fichier nommé fourinrow.py dans le répertoire ~/project pour stocker le code de ce projet. De plus, nous devons installer la bibliothèque Pygame pour implémenter l'interface du jeu et les opérations de support.
cd ~/project
touch fourinrow.py
sudo pip install pygame
Vous pouvez trouver les ressources d'images requises pour ce projet dans le répertoire ~/project/images.
Pour mieux comprendre le code de ce projet, il est recommandé de l'étudier en même temps que le code de la solution complète.
Les variables utilisées incluent la largeur et la hauteur du plateau d'échecs (peuvent être modifiées pour concevoir des plateaux de différentes tailles), le niveau de difficulté, la taille des jetons d'échecs et la définition de certaines variables de coordonnées.
Dans le fichier fourinrow.py, entrez le code suivant :
import random, copy, sys, pygame
from pygame.locals import *
LARGEUR_PLATEAU = 7 ## Nombre de colonnes sur le plateau de jeu
HAUTEUR_PLATEAU = 6 ## Nombre de lignes sur le plateau de jeu
assert LARGEUR_PLATEAU >= 4 and HAUTEUR_PLATEAU >= 4, 'Le plateau doit être au moins 4x4.'
## L'instruction assert en Python est utilisée pour déclarer que son expression booléenne donnée doit être vraie.
## Si l'expression est fausse, elle lève une exception.
DIFFICULTE = 2 ## Niveau de difficulté, nombre de coups que l'ordinateur peut considérer
## Ici, 2 signifie considérer 7 coups possibles de l'adversaire et comment répondre à ces 7 coups
TAILLE_ESPACE = 50 ## Taille des jetons d'échecs
FPS = 30 ## Taux de rafraîchissement de l'écran, 30/s
LARGEUR_FENETRE = 640 ## Largeur de l'écran de jeu en pixels
HAUTEUR_FENETRE = 480 ## Hauteur de l'écran de jeu en pixels
MARGE_X = int((LARGEUR_FENETRE - LARGEUR_PLATEAU * TAILLE_ESPACE) / 2) ## Coordonnée X du bord gauche de la grille
MARGE_Y = int((HAUTEUR_FENETRE - HAUTEUR_PLATEAU * TAILLE_ESPACE) / 2) ## Coordonnée Y du bord supérieur de la grille
BLEU_VIVANT = (0, 50, 255) ## Couleur bleue
BLANC = (255, 255, 255) ## Couleur blanche
COULEUR_FOND = BLEU_VIVANT
COULEUR_TEXTE = BLANC
ROUGE ='red'
NOIR = 'black'
VIDE = None
HUMAIN = 'human'
ORDINATEUR = 'computer'
En outre, nous devons également définir certaines variables globales de pygame. Ces variables globales seront appelées plusieurs fois dans différents modules plus tard. Beaucoup d'entre elles sont des variables qui stockent des images chargées, donc le travail de préparation est un peu long, veuillez être patient.
## Initialiser les modules pygame
pygame.init()
## Créer un objet Clock
HORLOGE_FPS = pygame.time.Clock()
## Créer la fenêtre de jeu
SURFACE_AFFICHAGE = pygame.display.set_mode((LARGEUR_FENETRE, HAUTEUR_FENETRE))
## Définir le titre de la fenêtre de jeu
pygame.display.set_caption(u'Puissance 4')
## Rect(gauche, haut, largeur, hauteur) est utilisé pour définir la position et la taille
RECT_PILE_ROUGE = pygame.Rect(int(TAILLE_ESPACE / 2), HAUTEUR_FENETRE - int(3 * TAILLE_ESPACE / 2), TAILLE_ESPACE, TAILLE_ESPACE)
## Créer les jetons en bas à gauche et en bas à droite dans la fenêtre
RECT_PILE_NOIRE = pygame.Rect(LARGEUR_FENETRE - int(3 * TAILLE_ESPACE / 2), HAUTEUR_FENETRE - int(3 * TAILLE_ESPACE / 2), TAILLE_ESPACE, TAILLE_ESPACE)
## Charger l'image du jeton rouge
IMAGE_JETON_ROUGE = pygame.image.load('images/4rowred.png')
## Redimensionner l'image du jeton rouge à TAILLE_ESPACE
IMAGE_JETON_ROUGE = pygame.transform.smoothscale(IMAGE_JETON_ROUGE, (TAILLE_ESPACE, TAILLE_ESPACE))
## Charger l'image du jeton noir
IMAGE_JETON_NOIR = pygame.image.load('images/4rowblack.png')
## Redimensionner l'image du jeton noir à TAILLE_ESPACE
IMAGE_JETON_NOIR = pygame.transform.smoothscale(IMAGE_JETON_NOIR, (TAILLE_ESPACE, TAILLE_ESPACE))
## Charger l'image du plateau d'échecs
IMAGE_PLATEAU = pygame.image.load('images/4rowboard.png')
## Redimensionner l'image du plateau d'échecs à TAILLE_ESPACE
IMAGE_PLATEAU = pygame.transform.smoothscale(IMAGE_PLATEAU, (TAILLE_ESPACE, TAILLE_ESPACE))
## Charger l'image du vainqueur humain
IMAGE_VAINQUEUR_HUMAIN = pygame.image.load('images/4rowhumanwinner.png')
## Charger l'image du vainqueur de l'IA
IMAGE_VAINQUEUR_ORDINATEUR = pygame.image.load('images/4rowcomputerwinner.png')
## Charger l'image d'égalité
IMAGE_EGALITE = pygame.image.load('images/4rowtie.png')
## Retourner un objet Rect
RECT_VAINQUEUR = IMAGE_VAINQUEUR_HUMAIN.get_rect()
## Centrer l'image du vainqueur sur la fenêtre de jeu
RECT_VAINQUEUR.center = (int(LARGEUR_FENETRE / 2), int(HAUTEUR_FENETRE / 2))
## Charger l'image de flèche pour les instructions utilisateur
IMAGE_FLECHE = pygame.image.load('images/4rowarrow.png')
## Retourner un objet Rect
RECT_FLECHE = IMAGE_FLECHE.get_rect()
## Définir la position gauche de l'image de flèche
RECT_FLECHE.left = RECT_PILE_ROUGE.right + 10
## Aligner l'image de flèche verticalement avec le jeton rouge en dessous
RECT_FLECHE.centery = RECT_PILE_ROUGE.centery
Pour mieux comprendre le code de ce projet, il est recommandé de l'étudier en même temps que le code de la solution complète.
Initialement, on vide la liste bidimensionnelle représentant le plateau, puis on attribue des couleurs aux positions correspondantes sur le plateau en fonction des coups du joueur et de l'IA.
def dessinerPlateau(plateau, jetonSupplémentaire=None):
## DISPLAYSURF est notre interface, définie dans le module d'initialisation des variables.
DISPLAYSURF.fill(COULEUR_FOND) ## Remplit le fond de la fenêtre de jeu avec la couleur bleue.
rectEspace = pygame.Rect(0, 0, TAILLE_ESPACE, TAILLE_ESPACE) ## Crée une instance de Rect.
for x in range(LARGEUR_PLATEAU):
## Détermine les coordonnées de la position en haut à gauche de chaque cellule dans chaque ligne de chaque colonne.
for y in range(HAUTEUR_PLATEAU):
rectEspace.topleft = (MARGE_X + (x * TAILLE_ESPACE), MARGE_Y + (y * TAILLE_ESPACE))
## Lorsque x = 0 et y = 0, c'est la première cellule de la première ligne de la première colonne.
if plateau[x][y] == ROUGE: ## Si la valeur de la cellule est rouge,
## dessine un jeton rouge dans la fenêtre de jeu à l'intérieur de rectEspace.
DISPLAYSURF.blit(IMAGE_JETON_ROUGE, rectEspace)
elif plateau[x][y] == NOIR: ## Sinon, dessine un jeton noir.
DISPLAYSURF.blit(IMAGE_JETON_NOIR, rectEspace)
## jetonSupplémentaire est une variable qui contient des informations de position et de couleur.
## Elle est utilisée pour afficher un jeton spécifié.
if jetonSupplémentaire!= None:
if jetonSupplémentaire['couleur'] == ROUGE:
DISPLAYSURF.blit(IMAGE_JETON_ROUGE, (jetonSupplémentaire['x'], jetonSupplémentaire['y'], TAILLE_ESPACE, TAILLE_ESPACE))
elif jetonSupplémentaire['couleur'] == NOIR:
DISPLAYSURF.blit(IMAGE_JETON_NOIR, (jetonSupplémentaire['x'], jetonSupplémentaire['y'], TAILLE_ESPACE, TAILLE_ESPACE))
## Dessine les panneaux de jetons.
for x in range(LARGEUR_PLATEAU):
for y in range(HAUTEUR_PLATEAU):
rectEspace.topleft = (MARGE_X + (x * TAILLE_ESPACE), MARGE_Y + (y * TAILLE_ESPACE))
DISPLAYSURF.blit(IMAGE_PLATEAU, rectEspace)
## Dessine les jetons en bas à gauche et en bas à droite de la fenêtre de jeu.
DISPLAYSURF.blit(IMAGE_JETON_ROUGE, RECT_PILE_ROUGE) ## Jeton rouge gauche.
DISPLAYSURF.blit(IMAGE_JETON_NOIR, RECT_PILE_NOIRE) ## Jeton noir droit.
def obtenirNouveauPlateau():
plateau = []
for x in range(LARGEUR_PLATEAU):
plateau.append([VIDE] * HAUTEUR_PLATEAU)
return plateau ## Retourne la liste de plateau avec HAUTEUR_PLATEAU nombre de valeurs None.
Dans le code ci-dessus, la fonction dessinerPlateau() dessine le plateau et les jetons sur le plateau. La fonction obtenirNouveauPlateau() retourne une nouvelle structure de données de plateau.
Expliquez brièvement l'idée de la recherche d'arbre de Monte Carlo :
Utilisez la méthode de Monte Carlo en une dimension pour évaluer le plateau de jeu du Go. Plus précisément, lorsqu'une situation de plateau spécifique est donnée, le programme sélectionne aléatoirement un point parmi tous les points disponibles dans la situation actuelle et y place un jeton. Ce processus de sélection aléatoire de points disponibles (point de lancer) est répété jusqu'à ce que ni l'un ni l'autre des deux joueurs n'ait de points disponibles (la partie se termine), puis la victoire ou la défaite résultante de cet état final est renvoyée comme base pour évaluer la situation actuelle.
Dans ce projet, l'IA choisit continuellement différentes colonnes et évalue les résultats des victoires des deux côtés. L'IA choisira finalement une stratégie avec une évaluation plus élevée.
Avant de regarder les images et le texte ci-dessous, veuillez jeter un coup d'œil au code à la fin, puis vous référer aux explications correspondantes.
Observer la confrontation entre l'IA et le joueur dans la figure ci-dessous :
Certaines variables dans le projet peuvent refléter intuitivement le processus des opérations de jetons de l'IA :
PotentialMoves : Renvoie une liste qui représente la possibilité de victoire de l'IA lorsqu'elle déplace un jeton dans n'importe quelle colonne de la liste. Les valeurs sont des nombres aléatoires de -1 à 1. Lorsque la valeur est négative, cela signifie que le joueur peut gagner dans les deux coups suivants, et plus la valeur est petite, plus la possibilité de victoire du joueur est grande. Si la valeur est 0, cela signifie que le joueur ne gagnera pas, et l'IA non plus. Si la valeur est 1, cela signifie que l'IA peut gagner.
bestMoveFitness : La valeur d'adaptation est la valeur maximale sélectionnée parmi PotentialMoves.
bestMoves : S'il y a plusieurs valeurs maximales dans PotentialMoves, cela signifie que les chances de victoire du joueur sont les plus petites lorsque l'IA déplace le jeton dans les colonnes où se trouvent ces valeurs. Par conséquent, ces colonnes sont ajoutées à la liste bestMoves.
column : Lorsqu'il y a plusieurs valeurs dans bestMoves, sélectionnez aléatoirement une colonne parmi bestMoves comme mouvement de l'IA. Si il n'y a qu'une seule valeur, column est cette valeur unique.
Dans le projet, en imprimant ces bestMoveFitness, bestMoves, column et potentialMoves, on peut déduire les paramètres de chaque étape de l'IA dans la figure ci-dessus.
En examinant le choix de l'IA au troisième pas, on peut mieux comprendre l'algorithme :
La figure ci-dessous illustre certains des coups de l'IA, montrant les choix possibles pour le joueur si l'IA place un jeton dans la première colonne, et l'impact du coup suivant de l'IA sur les chances de victoire du joueur. Grâce à ce processus de recherche et d'itération, l'IA peut déterminer les situations de victoire pour l'adversaire et elle-même dans les deux coups suivants, et prendre des décisions en conséquence.
La figure ci-dessous est un diagramme en flux de calcul de la valeur d'adaptation pour l'IA. Dans ce projet, le coefficient de difficulté est 2, et nous devons considérer 7^4 = 2041 cas :
À partir du diagramme en flux ci-dessus, il n'est pas difficile de constater que si l'IA place son premier jeton dans la colonne 0, 1, 2, 4, 5 ou 6, le joueur peut toujours placer les deux jetons restants dans la colonne 3 et gagner. Pour faciliter l'expression, nous utilisons une séquence pour représenter diverses combinaisons, où le premier élément représente le premier coup de l'IA, le deuxième nombre représente la réponse du joueur, et le troisième nombre représente la réponse de l'IA. "X" représente n'importe quel coup valide. Par conséquent, [0,0,x]=0, et on peut déduire que lorsque la séquence est [0,x<>3,x], le joueur ne peut pas gagner. Seul lorsque le deuxième jeton du joueur est dans la colonne 3, et que le deuxième coup de l'IA n'est pas dans la colonne 3, l'IA peut gagner. Par conséquent, [0,x=3,x<>3] = -1, et il y a 6 tels cas. Le résultat final est (0+0+...(43 fois)-1*6)/7/7 = -0,12.
Par la même raisonnement, les résultats pour les quatre autres cas sont tous -0,12. Si le premier coup de l'IA est dans la colonne 3, le joueur ne peut pas gagner, et l'IA non plus, donc la valeur est 0. L'IA choisit le coup avec la valeur d'adaptation la plus élevée, ce qui signifie qu'elle placera son jeton dans la colonne 3.
La même analyse peut être appliquée aux coups suivants de l'IA. En résumé, plus la possibilité de victoire du joueur est élevée après le coup de l'IA, plus la valeur d'adaptation est basse pour l'IA, et l'IA choisira le coup avec une valeur d'adaptation plus élevée pour empêcher le joueur de gagner. Bien sûr, si l'IA peut gagner elle-même, elle priorisera le coup qui conduit à sa propre victoire.
def getPotentialMoves(board, tile, lookAhead):
if lookAhead == 0 or isBoardFull(board):
'''
Si le coefficient de difficulté est 0 ou le plateau est plein,
renvoie une liste avec toutes les valeurs définies sur 0. Cela signifie que
la valeur d'adaptation est égale aux coups potentiels pour chaque colonne.
Dans ce cas, l'IA déposera le jeton aléatoirement et perdra son intelligence.
'''
return [0] * BOARDWIDTH
## Détermine la couleur du jeton de l'adversaire
if tile == RED:
enemyTile = BLACK
else:
enemyTile = RED
potentialMoves = [0] * BOARDWIDTH
## Initialise une liste de coups potentiels, avec toutes les valeurs définies sur 0
for firstMove in range(BOARDWIDTH):
## Itère sur chaque colonne et considère n'importe quel coup de l'un ou l'autre des deux joueurs comme firstMove
## Le coup de l'autre joueur est ensuite considéré comme counterMove
## Ici, notre firstMove fait référence au coup de l'IA et le coup de l'adversaire est considéré comme counterMove
## Prend une copie profonde du plateau pour éviter toute influence mutuelle entre le plateau et dupeBoard
dupeBoard = copy.deepcopy(board)
if not isValidMove(dupeBoard, firstMove):
## Si le coup de placement d'un jeton noir dans la colonne spécifiée par firstMove est invalide dans dupeBoard
continue
## Passe au next firstMove
makeMove(dupeBoard, tile, firstMove)
## Si c'est un coup valide, définit la couleur de la grille correspondante
if isWinner(dupeBoard, tile):
## Si l'IA gagne
potentialMoves[firstMove] = 1
## Le jeton gagnant obtient automatiquement une valeur élevée pour indiquer ses chances de victoire
## Plus la valeur est élevée, plus les chances de victoire sont élevées, et plus les chances de défaite de l'adversaire sont basses
break
## Ne perturbe pas le calcul des autres coups
else:
if isBoardFull(dupeBoard):
## Si il n'y a pas de grilles vides dans dupeBoard
potentialMoves[firstMove] = 0
## Il n'est pas possible de déplacer
else:
for counterMove in range(BOARDWIDTH):
## Considère le coup de l'adversaire
dupeBoard2 = copy.deepcopy(dupeBoard)
if not isValidMove(dupeBoard2, counterMove):
continue
makeMove(dupeBoard2, enemyTile, counterMove)
if isWinner(dupeBoard2, enemyTile):
potentialMoves[firstMove] = -1
## Si le joueur gagne, la valeur d'adaptation pour l'IA dans cette colonne est la plus basse
break
else:
## Appelle récursivement getPotentialMoves
results = getPotentialMoves(dupeBoard2, tile, lookAhead - 1)
## Utilise une représentation à virgule flottante ici pour des résultats plus précis
## Cela garantit que les valeurs dans potentialMoves sont dans la plage [-1, 1]
potentialMoves[firstMove] += (sum(results)*1.0 / BOARDWIDTH) / BOARDWIDTH
return potentialMoves
Faites glisser le jeton, déterminez la case où se trouve le jeton, validez le jeton, appelez la fonction de dépose du jeton et effectuez l'opération.
def getHumanMove(board, isFirstMove):
draggingToken = False
tokenx, tokeny = None, None
while True:
## Utilisez pygame.event.get() pour gérer tous les événements
for event in pygame.event.get():
if event.type == QUIT: ## Arrêtez et quittez
pygame.quit()
sys.exit()
elif event.type == MOUSEBUTTONDOWN and not draggingToken and REDPILERECT.collidepoint(event.pos):
## Si le type d'événement est un clic de souris, draggingToken est True, et la position du clic de souris est à l'intérieur de REDPILERECT
draggingToken = True
tokenx, tokeny = event.pos
elif event.type == MOUSEMOTION and draggingToken: ## Si le jeton rouge est entraîné
tokenx, tokeny = event.pos ## Met à jour la position du jeton entraîné
elif event.type == MOUSEBUTTONUP and draggingToken:
## Si la souris est relâchée, et le jeton est entraîné
## Si le jeton est entraîné directement au-dessus du plateau
if tokeny < YMARGIN and tokenx > XMARGIN and tokenx < WINDOWWIDTH - XMARGIN:
colonne = int((tokenx - XMARGIN) / TAILLE_ESPACE) ## Détermine la colonne où le jeton tombera en fonction de la coordonnée x du jeton (0,1...6)
if isValidMove(board, colonne): ## Si le mouvement du jeton est valide
"""
Tombez dans la case vide correspondante,
Cette fonction ne montre que l'effet de chute
Le remplissage de la case avec le jeton peut également être obtenu sans cette fonction grâce au code suivant
"""
animateDroppingToken(board, colonne, ROUGE)
## Définissez la case la plus basse dans la colonne vide sur rouge
board[colonne][getLowestEmptySpace(board, colonne)] = ROUGE
dessinerPlateau(board) ## Dessinez le jeton rouge dans la case où il est tombé
pygame.display.update() ## Mettez à jour la fenêtre
return
tokenx, tokeny = None, None
draggingToken = False
if tokenx!= None and tokeny!= None: ## Si un jeton est entraîné, affichez le jeton entraîné
dessinerPlateau(board, {'x':tokenx - int(TAILLE_ESPACE / 2), 'y':tokeny - int(TAILLE_ESPACE / 2), 'couleur':ROUGE})
## Ajustez les coordonnées x, y de sorte que la souris soit toujours au centre du jeton pendant le traînement
else:
dessinerPlateau(board) ## Lorsqu'il s'agit d'un mouvement invalide, après que la souris ait été relâchée, car toutes les valeurs dans le plateau sont nulles
## Lorsque vous appelez dessinerPlateau, les opérations effectuées sont d'afficher les deux jetons en dessous, ce qui est équivalent à remettre le jeton à l'endroit où il a commencé à être entraîné
if isFirstMove:
DISPLAYSURF.blit(IMAGE_FLECHE, RECT_FLECHE) ## L'IA joue en premier, affichez l'image d'opération d'aide
pygame.display.update()
HORLOGE_FPS.tick()
Dans le code ci-dessus, la fonction getHumanMove() gère le coup du joueur. La fonction animateDroppingToken() anime la chute du jeton. La fonction getLowestEmptySpace() renvoie l'espace vide le plus bas dans une colonne.
Implémentez la fonction pour animer le mouvement et l'atterrissage des jetons de l'IA sur les positions respectives.
def animateComputerMoving(board, colonne):
x = BLACKPILERECT.left ## La coordonnée gauche du jeton noir en bas
y = BLACKPILERECT.top ## La coordonnée supérieure du jeton noir en bas
vitesse = 1.0
while y > (YMARGIN - TAILLE_ESPACE): ## Lorsque y a une valeur plus grande, indiquant que le jeton est en dessous de la fenêtre
y -= int(vitesse) ## Diminuez y continuellement, ce qui signifie que le jeton monte
vitesse += 0.5 ## Augmentez la vitesse à laquelle y diminue
dessinerPlateau(board, {'x':x, 'y':y, 'couleur':NOIR})
## y change constamment, dessinant continuellement le jeton noir, créant un effet d'ascension continue
pygame.display.update()
HORLOGE_FPS.tick()
## Lorsque le jeton atteint le sommet du plateau
y = YMARGIN - TAILLE_ESPACE ## Remettez y à zéro, de sorte que le bas du jeton soit aligné avec le sommet du plateau
vitesse = 1.0
while x > (XMARGIN + colonne * TAILLE_ESPACE): ## Lorsque x est supérieur à la coordonnée x de la colonne souhaitée
x -= int(vitesse) ## Diminuez x continuellement, ce qui signifie que le jeton se déplace vers la gauche
vitesse += 0.5
dessinerPlateau(board, {'x':x, 'y':y, 'couleur':NOIR})
## À ce stade, la coordonnée y reste inchangée, ce qui signifie que le jeton se déplace horizontalement vers la colonne
pygame.display.update()
HORLOGE_FPS.tick()
## Le jeton noir atterrit dans l'espace vide calculé
animateDroppingToken(board, colonne, NOIR)
Sélectionnez le plus grand nombre de la liste de potentialMoves renvoyée, comme valeur d'adaptation, et choisissez aléatoirement parmi les colonnes ayant une valeur d'adaptation élevée comme cible de mouvement finale.
def getComputerMove(board):
potentialMoves = getPotentialMoves(board, NOIR, DIFFICULTE) ## Coups potentiels, une liste avec BOARDWIDTH valeurs
## Les valeurs de la liste sont liées au niveau de difficulté défini
bestMoves = [] ## Créez une liste bestMoves vide
bestMoveFitness = -1 ## Étant donné que la valeur minimale dans potentialMoves est -1, elle sert de limite inférieure
print(bestMoveFitness)
for i in range(len(potentialMoves)):
if potentialMoves[i] > bestMoveFitness and isValidMove(board, i):
bestMoveFitness = potentialMoves[i] ## Mettez à jour continuellement bestMoves, de sorte que chaque valeur dans bestMoves soit la plus grande
## tout en vous assurant que le mouvement est valide.
for i in range(len(potentialMoves)):
if potentialMoves[i] == bestMoveFitness and isValidMove(board, i):
bestMoves.append(i) ## Liste toutes les colonnes où le jeton peut être déplacé. Cette liste peut être vide, contenir
## une seule valeur ou plusieurs valeurs.
print(bestMoves)
return random.choice(bestMoves) ## Choisissez aléatoirement l'une des colonnes où le jeton peut être déplacé comme mouvement cible.
En changeant continuellement les coordonnées correspondantes des jetons, on obtient l'effet d'animation de chute.
def getLowestEmptySpace(board, colonne):
## Renvoie l'espace vide le plus bas dans une colonne
for y in range(BOARDHEIGHT-1, -1, -1):
if board[colonne][y] == VIDE:
return y
return -1
def makeMove(board, joueur, colonne):
lowest = getLowestEmptySpace(board, colonne)
if lowest!= -1:
board[colonne][lowest] = joueur
'''
Affecte le joueur (rouge/noir) à l'espace vide le plus bas dans la colonne.
Comme le jeton est déposé dans l'espace vide le plus bas d'une colonne,
il est considéré comme étant de la couleur de cet espace.
'''
def animateDroppingToken(board, colonne, couleur):
x = XMARGIN + colonne * TAILLE_ESPACE
y = YMARGIN - TAILLE_ESPACE
vitesseDeChute = 1.0
lowestEmptySpace = getLowestEmptySpace(board, colonne)
while True:
y += int(vitesseDeChute)
vitesseDeChute += 0.5
if int((y - YMARGIN) / TAILLE_ESPACE) >= lowestEmptySpace:
return
dessinerPlateau(board, {'x':x, 'y':y, 'couleur':couleur})
pygame.display.update()
HORLOGE_FPS.tick()
Dans le code ci-dessus, la fonction makeMove() effectue un coup sur le plateau. La fonction animateDroppingToken() anime la chute du jeton. La fonction getLowestEmptySpace() renvoie l'espace vide le plus bas dans une colonne.
Jugez la validité d'un mouvement d'un jeton, jugez s'il reste des espaces vides sur l'échiquier.
def isValidMove(board, colonne):
## Juge la validité d'un mouvement d'un jeton
if colonne < 0 ou colonne >= (BOARDWIDTH) ou board[colonne][0]!= VIDE:
## Si la colonne est inférieure à 0 ou supérieure à BOARDWIDTH, ou s'il n'y a pas d'espace vide dans la colonne
return False
## Alors il s'agit d'un mouvement invalide, sinon il est valide
return True
def isBoardFull(board):
## Si il n'y a pas d'espace vide dans la grille, renvoyez True
for x in range(BOARDWIDTH):
for y in range(BOARDHEIGHT):
if board[x][y] == VIDE:
return False
return True
Dans le code ci-dessus, la fonction isValidMove() renvoie True si le mouvement est valide. La fonction isBoardFull() renvoie True si l'échiquier est plein.
Plusieurs diagrammes sont fournis pour faciliter la compréhension des quatre conditions de victoire. Les positions montrées dans le diagramme correspondent aux valeurs extrêmes de x et y.
def isWinner(board, tuile):
## Vérifiez la situation horizontale des jetons
for x in range(BOARDWIDTH - 3): ## x prend les valeurs 0, 1, 2, 3
for y in range(BOARDHEIGHT): ## itérez sur toutes les lignes
## Si x = 0, vérifiez si les quatre premiers jetons de la ligne y sont tous de la même tuile. Cela peut être utilisé pour parcourir toutes les situations horizontales de jetons alignés en quatre. Si tout x, y est vrai, cela peut être déterminé comme une victoire
if board[x][y] == tuile and board[x+1][y] == tuile and board[x+2][y] == tuile and board[x+3][y] == tuile:
return True
## Vérifiez la situation verticale des jetons, similaire à la situation horizontale
for x in range(BOARDWIDTH):
for y in range(BOARDHEIGHT - 3):
if board[x][y] == tuile and board[x][y+1] == tuile and board[x][y+2] == tuile and board[x][y+3] == tuile:
return True
## Vérifiez la situation de la diagonale penchée vers la gauche des jetons
for x in range(BOARDWIDTH - 3): ## x prend les valeurs 0, 1, 2, 3
for y in range(3, BOARDHEIGHT): ## car lorsqu'on forme une diagonale penchée vers la gauche de quatre jetons alignés, le jeton le plus bas doit être au moins quatre cases loin du haut, c'est-à-dire y >= 3
if board[x][y] == tuile and board[x+1][y-1] == tuile and board[x+2][y-2] == tuile and board[x+3][y-3] == tuile: ## déterminez si les quatre jetons de la diagonale penchée vers la gauche sont de la même couleur
return True
## Vérifiez la situation de la diagonale penchée vers la droite des jetons, similaire à la situation de la diagonale penchée vers la gauche
for x in range(BOARDWIDTH - 3):
for y in range(BOARDHEIGHT - 3):
if board[x][y] == tuile and board[x+1][y+1] == tuile and board[x+2][y+2] == tuile and board[x+3][y+3] == tuile:
return True
return False
Enfin, nous créons la boucle principale du jeu pour maintenir le jeu en cours d'exécution de manière continue.
def main():
## Code existant omis
isFirstGame = True ## Initialise isFirstGame
while True: ## Maintenir le jeu en cours d'exécution de manière continue
runGame(isFirstGame)
isFirstGame = False
def runGame(isFirstGame):
if isFirstGame:
## Au début du premier jeu
## Laissez l'IA jouer le premier coup afin que les joueurs puissent voir comment le jeu se déroule
tour = ORDINATEUR
showHelp = True
else:
## Pour le second jeu et les suivants, attribuez les tours aléatoirement
if random.randint(0, 1) == 0:
tour = ORDINATEUR
else:
tour = HUMAIN
showHelp = False
mainBoard = getNewBoard() ## Configure la structure initiale du plateau vide
while True: ## Boucle principale du jeu
if tour == HUMAIN: ## Si c'est le tour du joueur
getHumanMove(mainBoard, showHelp) ## Appelez la méthode pour le coup du joueur, voir la méthode getHumanMove pour plus de détails
if showHelp:
## Si une image d'aide est affichée, désactivez l'aide après que l'IA ait joué le premier coup
showHelp = False
if isWinner(mainBoard, ROUGE): ## Si la pièce rouge (joueur) gagne
winnerImg = IMAGE_VICTOIRE_HUMAIN
break ## Sortir de la boucle
tour = ORDINATEUR ## Passer le premier coup à l'IA
else:
## Si c'est le tour de l'IA
colonne = getComputerMove(mainBoard) ## Appelez la méthode pour le coup de l'IA, voir la méthode getComputerMove pour plus de détails
print(colonne)
animateComputerMoving(mainBoard, colonne) ## Déplacer la pièce noire
makeMove(mainBoard, NOIR, colonne) ## Définir la case vide la plus basse de la colonne comme noire
if isWinner(mainBoard, NOIR):
winnerImg = IMAGE_VICTOIRE_ORDINATEUR
break
tour = HUMAIN ## Passer au tour du joueur
if isBoardFull(mainBoard):
## Si le plateau est plein, c'est une égalité
winnerImg = IMAGE_VICTOIRE_EGALITE
break
Sur la base de l'algorithme de Monte Carlo, ce projet a mis en œuvre un jeu d'échecs humain-vers-IA en utilisant Python avec le module Pygame. Ce projet nous a permis de devenir familiers des bases de la création d'instances et du déplacement d'objets dans Pygame, et nous a également donné une compréhension préliminaire de l'application spécifique de l'algorithme de Monte Carlo.
We use cookies for a number of reasons, such as keeping the website reliable and secure, to improve your experience on our website and to see how you interact with it. By accepting, you agree to our use of such cookies. Privacy Policy