Jeu de Quatre en Ligne - Humain contre IA

PythonPythonBeginner
Pratiquer maintenant

💡 Ce tutoriel est traduit par l'IA à partir de la version anglaise. Pour voir la version originale, vous pouvez cliquer ici

Introduction

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

Jeu Puissance 4

🎯 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.

Grille de jeu Puissance 4

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.

✨ Vérifier la solution et pratiquer

Initialisation des variables

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.

✨ Vérifier la solution et pratiquer

Conception du plateau

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.

✨ Vérifier la solution et pratiquer

Algorithme d'IA pour le mouvement optimal

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 :

Analyse du mouvement des joueurs IA

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.

✨ Vérifier la solution et pratiquer

Coups de l'IA

étapes potentialMoves bestMoveFitness bestMoves colonne
1 [0, 0, 0, 0, 0, 0, 0] 0 [0, 1, 2, 3, 4, 5, 6] 0
2 [0, 0, 0, 0, 0, 0, 0] 0 [0, 1, 2, 3, 4, 5, 6] 6
3 [-0,12, -0,12, -0,12, 0, -0,12, -0,12, -0,12] 0 [3] 3
4 [-0,34, -0,22, 0, -0,34, -0,34, -0,22, -0,34] 0 [2] 2
Flux de décision du choix de coup de l'IA

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.

Flux de décision de l'impact du coup de l'IA

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 :

Flux de calcul de l'adaptation de l'IA

À 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
✨ Vérifier la solution et pratiquer

Opération du joueur

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.

✨ Vérifier la solution et pratiquer

Opérations de l'IA

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.
✨ Vérifier la solution et pratiquer

Opération de mouvement des jetons

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.

✨ Vérifier la solution et pratiquer

Certaines fonctions de jugement

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.

✨ Vérifier la solution et pratiquer

Jugement des conditions de victoire

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.

Diagramme des conditions de victoire
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
✨ Vérifier la solution et pratiquer

Création de la boucle principale du jeu

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
✨ Vérifier la solution et pratiquer

Exécution et test

Ensuite, nous allons exécuter le programme et voir comment il se comporte.

cd ~/projet
python fourinrow.py
Démonstration d'exécution du programme
✨ Vérifier la solution et pratiquer

Sommaire

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.