Juego de Cuatro en Raya - Humano vs. IA

PythonPythonBeginner
Practicar Ahora

💡 Este tutorial está traducido por IA desde la versión en inglés. Para ver la versión original, puedes hacer clic aquí

Introducción

Este proyecto es una implementación en Python del clásico juego Connect Four donde un jugador puede competir contra una IA. Utiliza la librería Pygame para la interfaz y el control del juego. La toma de decisiones de la IA se basa en el algoritmo de búsqueda de árbol Monte Carlo y el nivel de dificultad es ajustable, lo que permite a los jugadores desafiarse con oponentes de IA más inteligentes.

Conceptos clave:

  • Utilizar Pygame para el desarrollo de juegos.
  • Implementar el algoritmo de búsqueda de árbol Monte Carlo para la toma de decisiones de la IA.

👀 Vista previa

Juego Connect Four

🎯 Tareas

En este proyecto, aprenderás:

  • Cómo construir un juego utilizando Pygame
  • Cómo implementar el algoritmo de búsqueda de árbol Monte Carlo para la toma de decisiones de la IA
  • Cómo personalizar y mejorar el nivel de dificultad de la IA
  • Cómo crear un juego divertido e interactivo de Connect Four para batallas humano vs. IA

🏆 Logros

Después de completar este proyecto, podrás:

  • Desarrollar juegos utilizando Python y Pygame
  • Comprender los principios del algoritmo de búsqueda de árbol Monte Carlo
  • Ajustar la dificultad de un oponente de IA para crear una experiencia de juego desafiante
  • Mejorar las interfaces de usuario para hacer más atractiva la experiencia de juego

Preparación para el desarrollo

El juego Four-In-A-Row se juega en una cuadrícula de tamaño 7*6. Los jugadores toman turnos para dejar caer sus piezas desde la parte superior de una columna. La pieza caerá en el espacio vacío más bajo de esa columna. El jugador que logre conectar cuatro piezas en línea recta (horizontal, vertical o diagonal) gana el juego.

Cuadrícula del juego Four In A Row

Crea un archivo llamado fourinrow.py en el directorio ~/project para almacenar el código de este proyecto. Además, necesitamos instalar la librería Pygame para implementar la interfaz del juego y las operaciones de soporte.

cd ~/project
touch fourinrow.py
sudo pip install pygame

Puedes encontrar los recursos de imágenes necesarios para este proyecto en el directorio ~/project/images.

Para entender mejor el código de este proyecto, se recomienda estudiar junto con el código de la solución completa.

✨ Revisar Solución y Practicar

Inicialización de variables

Las variables utilizadas incluyen el ancho y el alto del tablero de ajedrez (pueden modificarse para diseñar tableros de diferentes tamaños), el nivel de dificultad, el tamaño de las piezas de ajedrez y la configuración de algunas variables de coordenadas.

En el archivo fourinrow.py, escribe el siguiente código:

import random, copy, sys, pygame
from pygame.locals import *

BOARDWIDTH = 7  ## Número de columnas en el tablero de juego
BOARDHEIGHT = 6 ## Número de filas en el tablero de juego
assert BOARDWIDTH >= 4 and BOARDHEIGHT >= 4, 'El tablero debe tener al menos 4x4.'

## La declaración assert de Python se utiliza para declarar que su expresión booleana dada debe ser verdadera.
## Si la expresión es falsa, lanza una excepción.

DIFFICULTY = 2 ## Nivel de dificultad, número de movimientos que la computadora puede considerar
               ## Aquí, 2 significa considerar 7 movimientos posibles del oponente y cómo responder a esos 7 movimientos

SPACESIZE = 50 ## Tamaño de las piezas de ajedrez

FPS = 30 ## Tasa de actualización de la pantalla, 30/s
WINDOWWIDTH = 640  ## Ancho de la pantalla de juego en píxeles
WINDOWHEIGHT = 480 ## Alto de la pantalla de juego en píxeles

XMARGIN = int((WINDOWWIDTH - BOARDWIDTH * SPACESIZE) / 2)  ## Coordenada X del borde izquierdo de la cuadrícula
YMARGIN = int((WINDOWHEIGHT - BOARDHEIGHT * SPACESIZE) / 2) ## Coordenada Y del borde superior de la cuadrícula
BRIGHTBLUE = (0, 50, 255) ## Color azul
WHITE = (255, 255, 255) ## Color blanco

BGCOLOR = BRIGHTBLUE
TEXTCOLOR = WHITE

RED ='red'
BLACK = 'black'
EMPTY = None
HUMAN = 'human'
COMPUTER = 'computer'

Además, también necesitamos definir algunas variables globales de pygame. Estas variables globales se llamarán múltiples veces en varios módulos más adelante. Muchas de ellas son variables que almacenan imágenes cargadas, por lo que la preparación puede ser un poco larga, por favor, tenga paciencia.

## Inicializa los módulos de pygame
pygame.init()

## Crea un objeto Clock
FPSCLOCK = pygame.time.Clock()

## Crea la ventana del juego
DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))

## Establece el título de la ventana del juego
pygame.display.set_caption(u'cuatro en raya')

## Rect(left, top, width, height) se utiliza para definir posición y tamaño
REDPILERECT = pygame.Rect(int(SPACESIZE / 2), WINDOWHEIGHT - int(3 * SPACESIZE / 2), SPACESIZE, SPACESIZE)

## Crea las piezas de ajedrez en la esquina inferior izquierda y derecha de la ventana
BLACKPILERECT = pygame.Rect(WINDOWWIDTH - int(3 * SPACESIZE / 2), WINDOWHEIGHT - int(3 * SPACESIZE / 2), SPACESIZE, SPACESIZE)

## Carga la imagen de la pieza de ajedrez roja
REDTOKENIMG = pygame.image.load('images/4rowred.png')

## Escala la imagen de la pieza de ajedrez roja a SPACESIZE
REDTOKENIMG = pygame.transform.smoothscale(REDTOKENIMG, (SPACESIZE, SPACESIZE))

## Carga la imagen de la pieza de ajedrez negra
BLACKTOKENIMG = pygame.image.load('images/4rowblack.png')

## Escala la imagen de la pieza de ajedrez negra a SPACESIZE
BLACKTOKENIMG = pygame.transform.smoothscale(BLACKTOKENIMG, (SPACESIZE, SPACESIZE))

## Carga la imagen del tablero de ajedrez
BOARDIMG = pygame.image.load('images/4rowboard.png')

## Escala la imagen del tablero de ajedrez a SPACESIZE
BOARDIMG = pygame.transform.smoothscale(BOARDIMG, (SPACESIZE, SPACESIZE))

## Carga la imagen del ganador humano
HUMANWINNERIMG = pygame.image.load('images/4rowhumanwinner.png')

## Carga la imagen del ganador de la IA
COMPUTERWINNERIMG = pygame.image.load('images/4rowcomputerwinner.png')

## Carga la imagen del empate
TIEWINNERIMG = pygame.image.load('images/4rowtie.png')

## Devuelve un objeto Rect
WINNERRECT = HUMANWINNERIMG.get_rect()

## Centra la imagen del ganador en la ventana del juego
WINNERRECT.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2))

## Carga la imagen de la flecha para las instrucciones del usuario
ARROWIMG = pygame.image.load('images/4rowarrow.png')

## Devuelve un objeto Rect
ARROWRECT = ARROWIMG.get_rect()

## Establece la posición izquierda de la imagen de la flecha
ARROWRECT.left = REDPILERECT.right + 10

## Alinea la imagen de la flecha verticalmente con la pieza de ajedrez roja debajo de ella
ARROWRECT.centery = REDPILERECT.centery

Para entender mejor el código de este proyecto, se recomienda estudiar junto con el código de la solución completa.

✨ Revisar Solución y Practicar

Diseño del tablero

Inicialmente, limpia la lista bidimensional que representa el tablero y luego establece las posiciones correspondientes en el tablero con los colores según los movimientos del jugador y la IA.

def drawBoard(board, extraToken=None):
    ## DISPLAYSURF es nuestra interfaz, definida en el módulo de inicialización de variables.
    DISPLAYSURF.fill(BGCOLOR) ## Llena el color de fondo de la ventana del juego con azul.
    spaceRect = pygame.Rect(0, 0, SPACESIZE, SPACESIZE) ## Crea una instancia de Rect.
    for x in range(BOARDWIDTH):
        ## Determina las coordenadas de posición superior izquierda de cada celda en cada fila de cada columna.
        for y in range(BOARDHEIGHT):
            spaceRect.topleft = (XMARGIN + (x * SPACESIZE), YMARGIN + (y * SPACESIZE))

            ## Cuando x = 0 e y = 0, es la primera celda en la primera fila de la primera columna.
            if board[x][y] == RED: ## Si el valor de la celda es rojo,
                ## dibuja un token rojo en la ventana del juego dentro de spaceRect.
                DISPLAYSURF.blit(REDTOKENIMG, spaceRect)
            elif board[x][y] == BLACK: ## De lo contrario, dibuja un token negro.
                DISPLAYSURF.blit(BLACKTOKENIMG, spaceRect)

    ## extraToken es una variable que contiene información de posición e información de color.
    ## Se utiliza para mostrar un token específico.
    if extraToken!= None:
        if extraToken['color'] == RED:
            DISPLAYSURF.blit(REDTOKENIMG, (extraToken['x'], extraToken['y'], SPACESIZE, SPACESIZE))
        elif extraToken['color'] == BLACK:
            DISPLAYSURF.blit(BLACKTOKENIMG, (extraToken['x'], extraToken['y'], SPACESIZE, SPACESIZE))

    ## Dibuja los paneles de tokens.
    for x in range(BOARDWIDTH):
        for y in range(BOARDHEIGHT):
            spaceRect.topleft = (XMARGIN + (x * SPACESIZE), YMARGIN + (y * SPACESIZE))
            DISPLAYSURF.blit(BOARDIMG, spaceRect)

    ## Dibuja los tokens en la esquina inferior izquierda y derecha de la ventana del juego.
    DISPLAYSURF.blit(REDTOKENIMG, REDPILERECT) ## Token rojo izquierdo.
    DISPLAYSURF.blit(BLACKTOKENIMG, BLACKPILERECT) ## Token negro derecho.


def getNewBoard():
    board = []
    for x in range(BOARDWIDTH):
        board.append([EMPTY] * BOARDHEIGHT)
    return board ## Devuelve la lista del tablero con un número de valores None igual a BOARDHEIGHT.

En el código anterior, la función drawBoard() dibuja el tablero y los tokens en el tablero. La función getNewBoard() devuelve una nueva estructura de datos del tablero.

✨ Revisar Solución y Practicar

Algoritmo de IA para el movimiento óptimo

Explica brevemente la idea de la búsqueda de árbol Monte Carlo:

Utiliza el método de Monte Carlo unidimensional para evaluar el tablero de Go. Específicamente, cuando se da una situación concreta del tablero de ajedrez, el programa elige aleatoriamente un punto de todos los puntos disponibles en la situación actual y coloca una pieza de ajedrez en él. Este proceso de selección aleatoria de puntos disponibles (punto de lanzamiento) se repite hasta que ninguna de las dos partes tiene puntos disponibles (el juego termina), y luego la victoria o derrota resultante de este estado final se devuelve como base para evaluar la situación actual.

En este proyecto, la IA continúa eligiendo diferentes columnas y evalúa los resultados de la victoria de ambas partes. La IA finalmente elegirá una estrategia con una evaluación más alta.

Antes de ver las imágenes y el texto siguientes, echa un vistazo al código al final y luego refiérase a las explicaciones correspondientes.

Observando la confrontación entre la IA y el jugador en la figura siguiente:

Análisis de los movimientos de la IA y el jugador

Algunas variables en el proyecto pueden reflejar intuitivamente el proceso de las operaciones de las piezas de ajedrez de la IA:

PotentialMoves: Devuelve una lista que representa la posibilidad de que la IA gane al mover una pieza de ajedrez a cualquier columna de la lista. Los valores son números aleatorios de -1 a 1. Cuando el valor es negativo, significa que el jugador puede ganar en los próximos dos movimientos, y cuanto más pequeño es el valor, mayor es la posibilidad de que el jugador gane. Si el valor es 0, significa que el jugador no ganará y la IA tampoco ganará. Si el valor es 1, significa que la IA puede ganar.

bestMoveFitness: La aptitud es el valor máximo seleccionado de PotentialMoves.

bestMoves: Si hay múltiples valores máximos en PotentialMoves, significa que las posibilidades de que el jugador gane son las más pequeñas cuando la IA mueve la pieza de ajedrez a las columnas donde se encuentran estos valores. Por lo tanto, estas columnas se agregan a la lista bestMoves.

column: Cuando hay múltiples valores en bestMoves, se elige aleatoriamente una columna de bestMoves como movimiento de la IA. Si solo hay un valor, column es este valor único.

En el proyecto, al imprimir estos bestMoveFitness, bestMoves, column y potentialMoves, podemos deducir los parámetros de cada paso de la IA en la figura anterior.

✨ Revisar Solución y Practicar

Movimientos de la IA

pasos potentialMoves bestMoveFitness bestMoves columna
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
Diagrama de flujo de selección de movimientos de la IA

Al examinar la selección de la IA en el tercer paso, podemos entender mejor el algoritmo:

La figura siguiente ilustra algunos de los movimientos de la IA, mostrando las posibles elecciones del jugador si la IA coloca una pieza en la primera columna y el impacto del siguiente movimiento de la IA en las posibilidades de victoria del jugador. A través de este proceso de búsqueda e iteración, la IA puede determinar las situaciones de victoria para el oponente y para sí misma en los próximos dos pasos y tomar decisiones en consecuencia.

Diagrama de flujo del impacto de los movimientos de la IA

La figura siguiente es un diagrama de flujo para calcular el valor de aptitud para la IA. En este proyecto, el coeficiente de dificultad es 2 y necesitamos considerar 7^4 = 2041 casos:

Diagrama de flujo de cálculo de aptitud de la IA

A partir del diagrama de flujo anterior, no es difícil encontrar que si la IA coloca su primera pieza en la columna 0, 1, 2, 4, 5 o 6, el jugador siempre puede colocar las dos piezas restantes en la columna 3 y ganar. Para facilitar la expresión, usamos una secuencia para representar las diferentes combinaciones, donde el primer elemento representa el primer movimiento de la IA, el segundo número representa la respuesta del jugador y el tercer número representa la respuesta de la IA. "X" representa cualquier movimiento válido. Por lo tanto, [0,0,x]=0, y se puede deducir que cuando la secuencia es [0,x<>3,x], el jugador no puede ganar. Solo cuando la segunda pieza del jugador está en la columna 3 y el segundo movimiento de la IA no está en la columna 3, la IA puede ganar. Por lo tanto, [0,x=3,x<>3] = -1, y hay 6 casos de este tipo. El resultado final es (0+0+...(43 veces)-1*6)/7/7 = -0.12.

Por el mismo razonamiento, los resultados para los otros cuatro casos son todos -0.12. Si el primer movimiento de la IA está en la columna 3, el jugador no puede ganar y la IA tampoco puede ganar, por lo que el valor es 0. La IA elige el movimiento con el valor de aptitud más alto, lo que significa que colocará su pieza en la columna 3.

El mismo análisis se puede aplicar a los movimientos subsiguientes de la IA. En resumen, cuanto mayor sea la posibilidad de que el jugador gane después del movimiento de la IA, menor será el valor de aptitud para la IA, y la IA elegirá el movimiento con un valor de aptitud más alto para evitar que el jugador gane. Por supuesto, si la IA puede ganar por sí misma, priorizará el movimiento que conduzca a su propia victoria.

def getPotentialMoves(board, tile, lookAhead):
    if lookAhead == 0 or isBoardFull(board):
        '''
        Si el coeficiente de dificultad es 0 o el tablero está lleno,
        devuelve una lista con todos los valores establecidos en 0. Esto significa que
        el valor de aptitud es igual a los movimientos potenciales para cada columna.
        En este caso, la IA dejará caer la pieza al azar y perderá su inteligencia.
        '''
        return [0] * BOARDWIDTH

    ## Determina el color de la pieza del oponente
    if tile == RED:
        enemyTile = BLACK
    else:
        enemyTile = RED
    potentialMoves = [0] * BOARDWIDTH
    ## Inicializa una lista de movimientos potenciales, con todos los valores establecidos en 0
    for firstMove in range(BOARDWIDTH):
        ## Itera sobre cada columna y considera cualquier movimiento de cualquiera de las dos partes como el firstMove
        ## El movimiento de la otra parte se considera como el counterMove
        ## Aquí, nuestro firstMove se refiere al movimiento de la IA y el movimiento del oponente se considera como counterMove
        ## Haz una copia profunda del tablero para evitar la influencia mutua entre el tablero y dupeBoard
        dupeBoard = copy.deepcopy(board)
        if not isValidMove(dupeBoard, firstMove):
        ## Si el movimiento de colocar una pieza negra en la columna especificada por firstMove es inválido en dupeBoard
            continue
            ## Continúa con el siguiente firstMove
        makeMove(dupeBoard, tile, firstMove)
        ## Si es un movimiento válido, establece el color de la cuadrícula correspondiente
        if isWinner(dupeBoard, tile):
        ## Si la IA gana
            potentialMoves[firstMove] = 1
            ## La pieza ganadora automáticamente obtiene un valor alto para indicar sus posibilidades de ganar
            ## Cuanto mayor sea el valor, mayores serán las posibilidades de ganar y menores las posibilidades del oponente de ganar
            break
            ## No interfiera con el cálculo de otros movimientos
        else:
            if isBoardFull(dupeBoard):
            ## Si no hay cuadrículas vacías en dupeBoard
                potentialMoves[firstMove] = 0
                ## No es posible moverse
            else:
                for counterMove in range(BOARDWIDTH):
                ## Considera el movimiento del oponente
                    dupeBoard2 = copy.deepcopy(dupeBoard)
                    if not isValidMove(dupeBoard2, counterMove):
                        continue
                    makeMove(dupeBoard2, enemyTile, counterMove)
                    if isWinner(dupeBoard2, enemyTile):
                        potentialMoves[firstMove] = -1
                        ## Si el jugador gana, el valor de aptitud para la IA en esta columna es el más bajo
                        break
                    else:
                        ## Llama recursivamente a getPotentialMoves
                        results = getPotentialMoves(dupeBoard2, tile, lookAhead - 1)
                        ## Use la representación de punto flotante aquí para obtener resultados más precisos
                        ## Esto asegura que los valores en potentialMoves estén en el rango [-1, 1]
                        potentialMoves[firstMove] += (sum(results)*1.0 / BOARDWIDTH) / BOARDWIDTH
    return potentialMoves
✨ Revisar Solución y Practicar

Operación del jugador

Arrastra la pieza de ajedrez, determina el cuadrado donde se encuentra la pieza, valida la pieza, llama a la función de soltar la pieza y completa la operación.

def getHumanMove(board, isFirstMove):
    draggingToken = False
    tokenx, tokeny = None, None
    while True:
        ## Utiliza pygame.event.get() para manejar todos los eventos
        for event in pygame.event.get():
            if event.type == QUIT: ## Detiene y sale
                pygame.quit()
                sys.exit()

            elif event.type == MOUSEBUTTONDOWN and not draggingToken and REDPILERECT.collidepoint(event.pos):
                ## Si el tipo de evento es clic del botón del mouse, draggingToken es True y la posición del clic del mouse está dentro de REDPILERECT
                draggingToken = True
                tokenx, tokeny = event.pos

            elif event.type == MOUSEMOTION and draggingToken: ## Si la pieza roja se está arrastrando
                tokenx, tokeny = event.pos ## Actualiza la posición de la pieza arrastrada

            elif event.type == MOUSEBUTTONUP and draggingToken:
                ## Si se suelta el mouse y la pieza de ajedrez se está arrastrando
                ## Si la pieza de ajedrez se arrastra directamente encima del tablero
                if tokeny < YMARGIN and tokenx > XMARGIN and tokenx < WINDOWWIDTH - XMARGIN:
                    column = int((tokenx - XMARGIN) / SPACESIZE) ## Determina la columna donde caerá la pieza de ajedrez según la coordenada x de la pieza de ajedrez (0,1...6)
                    if isValidMove(board, column): ## Si el movimiento de la pieza de ajedrez es válido
                        """
                        Caer en el cuadrado vacío correspondiente,
                        Esta función solo muestra el efecto de caída
                        La pieza que llena el cuadrado también se puede lograr sin esta función con el siguiente código
                        """
                        animateDroppingToken(board, column, RED)

                        ## Establece el cuadrado más bajo en la columna vacía a rojo
                        board[column][getLowestEmptySpace(board, column)] = RED
                        drawBoard(board) ## Dibuja la pieza de ajedrez roja en el cuadrado donde cayó
                        pygame.display.update() ## Actualización de la ventana
                        return
                tokenx, tokeny = None, None
                draggingToken = False

        if tokenx!= None and tokeny!= None: ## Si se está arrastrando una pieza de ajedrez, muestra la pieza arrastrada
            drawBoard(board, {'x':tokenx - int(SPACESIZE / 2), 'y':tokeny - int(SPACESIZE / 2), 'color':RED})
            ## Ajusta las coordenadas x, y para que el mouse siempre esté en la posición central de la pieza de ajedrez durante el arrastre

        else:
            drawBoard(board) ## Cuando es un movimiento inválido, después de soltar el mouse, porque todos los valores en el tablero son none
            ## Cuando se llama a drawBoard, las operaciones realizadas son mostrar las dos piezas de abajo, lo que es equivalente a devolver la pieza a la ubicación donde comenzó a arrastrarse

        if isFirstMove:
            DISPLAYSURF.blit(ARROWIMG, ARROWRECT) ## La IA juega primero, muestra la imagen de operación de pista

        pygame.display.update()
        FPSCLOCK.tick()

En el código anterior, la función getHumanMove() maneja el movimiento del jugador. La función animateDroppingToken() anima la caída de la pieza. La función getLowestEmptySpace() devuelve el espacio vacío más bajo en una columna.

✨ Revisar Solución y Practicar

Operaciones de la IA

Implementa la función para animar el movimiento y el aterrizaje de las piezas de la IA en las respectivas posiciones.

def animateComputerMoving(board, column):
    x = BLACKPILERECT.left ## La coordenada izquierda de la pieza negra en la parte inferior
    y = BLACKPILERECT.top ## La coordenada superior de la pieza negra en la parte inferior
    speed = 1.0
    while y > (YMARGIN - SPACESIZE): ## Cuando y tiene un valor más grande, lo que indica que la pieza está debajo de la ventana
        y -= int(speed) ## Disminuye y continuamente, lo que significa que la pieza se mueve hacia arriba
        speed += 0.5 ## Aumenta la velocidad a la que y disminuye
        drawBoard(board, {'x':x, 'y':y, 'color':BLACK})
        ## y sigue cambiando, dibujando continuamente la pieza negra, creando un efecto de ascenso continuo
        pygame.display.update()
        FPSCLOCK.tick()
    ## Cuando la pieza asciende hasta la parte superior del tablero
    y = YMARGIN - SPACESIZE ## Reinicia y, para que la parte inferior de la pieza quede alineada con la parte superior del tablero
    speed = 1.0
    while x > (XMARGIN + column * SPACESIZE): ## Cuando x es mayor que la coordenada x de la columna deseada
        x -= int(speed) ## Disminuye x continuamente, lo que significa que la pieza se mueve hacia la izquierda
        speed += 0.5
        drawBoard(board, {'x':x, 'y':y, 'color':BLACK})
        ## En este momento, la coordenada y permanece inmutable, lo que significa que la pieza se mueve horizontalmente hacia la columna
        pygame.display.update()
        FPSCLOCK.tick()
    ## La pieza negra aterriza en el espacio vacío calculado
    animateDroppingToken(board, column, BLACK)

Selecciona el número más alto de la lista de potentialMoves devuelta, como el valor de aptitud, y elige al azar de entre aquellas columnas con valores de aptitud altos como el objetivo final de movimiento.

def getComputerMove(board):
    potentialMoves = getPotentialMoves(board, BLACK, DIFFICULTY) ## Movimientos potenciales, una lista con BOARDWIDTH valores
               ## Los valores en la lista están relacionados con el nivel de dificultad establecido
    bestMoves = [] ## Crea una lista bestMoves vacía
    bestMoveFitness = -1 ## Dado que el valor mínimo en potentialMoves es -1, sirve como límite inferior
    print(bestMoveFitness)
    for i in range(len(potentialMoves)):
        if potentialMoves[i] > bestMoveFitness and isValidMove(board, i):
            bestMoveFitness = potentialMoves[i] ## Actualiza continuamente bestMoves, de modo que cada valor en bestMoves sea el mayor
            ## mientras se asegura de que el movimiento sea válido.

    for i in range(len(potentialMoves)):
        if potentialMoves[i] == bestMoveFitness and isValidMove(board, i):
            bestMoves.append(i) ## Lista todas las columnas a las que se puede mover la pieza. Esta lista puede estar vacía, contener
            ## solo un valor o múltiples valores.
    print(bestMoves)
    return random.choice(bestMoves) ## Elije al azar una de las columnas a las que se puede mover la pieza como el movimiento objetivo.
✨ Revisar Solución y Practicar

Operación del movimiento de las piezas

Al cambiar continuamente las coordenadas correspondientes de las piezas, se logra el efecto de animación de caída.

def getLowestEmptySpace(board, column):
    ## Devuelve el espacio vacío más bajo en una columna
    for y in range(BOARDHEIGHT-1, -1, -1):
        if board[column][y] == EMPTY:
            return y
    return -1

def makeMove(board, player, column):
    lowest = getLowestEmptySpace(board, column)
    if lowest!= -1:
        board[column][lowest] = player
        '''
        Asigna el jugador (rojo/negro) al espacio vacío más bajo de la columna.
        Debido a que la pieza se deja caer en el espacio vacío más bajo de una columna,
        se considera como el color de ese espacio.
        '''

def animateDroppingToken(board, column, color):
    x = XMARGIN + column * SPACESIZE
    y = YMARGIN - SPACESIZE
    dropSpeed = 1.0
    lowestEmptySpace = getLowestEmptySpace(board, column)

    while True:
        y += int(dropSpeed)
        dropSpeed += 0.5
        if int((y - YMARGIN) / SPACESIZE) >= lowestEmptySpace:
            return
        drawBoard(board, {'x':x, 'y':y, 'color':color})
        pygame.display.update()
        FPSCLOCK.tick()

En el código anterior, la función makeMove() realiza un movimiento en el tablero. La función animateDroppingToken() anima la caída de la pieza. La función getLowestEmptySpace() devuelve el espacio vacío más bajo en una columna.

✨ Revisar Solución y Practicar

Algunas funciones de juicio

Juzgar la validez de un movimiento de una pieza, juzgar si todavía hay espacios vacíos en el tablero de ajedrez.

def isValidMove(board, column):
    ## Juzgar la validez de un movimiento de una pieza
    if column < 0 or column >= (BOARDWIDTH) or board[column][0]!= EMPTY:
    ## Si la columna es menor que 0 o mayor que BOARDWIDTH, o no hay un espacio vacío en la columna
        return False
        ## Entonces es un movimiento inválido, de lo contrario es válido
    return True


def isBoardFull(board):
    ## Si no hay espacios vacíos en la cuadrícula, devuelve True
    for x in range(BOARDWIDTH):
        for y in range(BOARDHEIGHT):
            if board[x][y] == EMPTY:
                return False
    return True

En el código anterior, la función isValidMove() devuelve True si el movimiento es válido. La función isBoardFull() devuelve True si el tablero está lleno.

✨ Revisar Solución y Practicar

Juicio de las condiciones de victoria

Se proporcionan varios diagramas para facilitar la comprensión de las cuatro condiciones de victoria. Las posiciones mostradas en el diagrama corresponden a los valores extremos de x e y.

Diagrama de las condiciones de victoria
def isWinner(board, tile):
    ## Verificar la situación horizontal de las piezas
    for x in range(BOARDWIDTH - 3): ## x toma los valores 0, 1, 2, 3
        for y in range(BOARDHEIGHT): ## recorrer todas las filas
            ## Si x = 0, verificar si las primeras cuatro piezas en la fila y son todas la misma ficha. Esto se puede utilizar para recorrer todas las situaciones horizontales de piezas conectadas en línea de cuatro. Si cualquier x, y es verdadero, se puede determinar como una victoria
            if board[x][y] == tile and board[x+1][y] == tile and board[x+2][y] == tile and board[x+3][y] == tile:
                return True

    ## Verificar la situación vertical de las piezas, similar a la situación horizontal
    for x in range(BOARDWIDTH):
        for y in range(BOARDHEIGHT - 3):
            if board[x][y] == tile and board[x][y+1] == tile and board[x][y+2] == tile and board[x][y+3] == tile:
                return True

    ## Verificar la situación de la diagonal hacia la izquierda de las piezas
    for x in range(BOARDWIDTH - 3): ## x toma los valores 0, 1, 2, 3
        for y in range(3, BOARDHEIGHT): ## porque al formar una diagonal hacia la izquierda de cuatro en línea, la pieza más baja debe estar al menos cuatro casillas lejos de la parte superior, es decir, y >= 3
            if board[x][y] == tile and board[x+1][y-1] == tile and board[x+2][y-2] == tile and board[x+3][y-3] == tile: ## determinar si las cuatro piezas de la diagonal hacia la izquierda son del mismo color
                return True

    ## Verificar la situación de la diagonal hacia la derecha de las piezas, similar a la situación de la diagonal hacia la izquierda
    for x in range(BOARDWIDTH - 3):
        for y in range(BOARDHEIGHT - 3):
            if board[x][y] == tile and board[x+1][y+1] == tile and board[x+2][y+2] == tile and board[x+3][y+3] == tile:
                return True
    return False
✨ Revisar Solución y Practicar

Creando el bucle principal del juego

Finalmente, creamos el bucle principal del juego para mantener el juego en ejecución de manera continua.

def main():

    ## Código existente omitido

    isFirstGame = True ## Inicializar isFirstGame

    while True: ## Mantener el juego en ejecución de manera continua
        runGame(isFirstGame)
        isFirstGame = False


def runGame(isFirstGame):
    if isFirstGame:
        ## Al comienzo del primer juego
        ## Dejar que la IA haga el primer movimiento para que los jugadores puedan ver cómo se juega el juego
        turn = COMPUTER
        showHelp = True
    else:
        ## Para el segundo juego y posteriores, asignar los turnos al azar
        if random.randint(0, 1) == 0:
            turn = COMPUTER
        else:
            turn = HUMAN
        showHelp = False
    mainBoard = getNewBoard() ## Establecer la estructura inicial del tablero vacío
    while True: ## Bucle principal del juego
        if turn == HUMAN: ## Si es el turno del jugador

            getHumanMove(mainBoard, showHelp) ## Llamar al método para el movimiento del jugador, ver el método getHumanMove para detalles
            if showHelp:
                ## Si hay una imagen de ayuda, desactivar la ayuda después de que la IA haga el primer movimiento
                showHelp = False
            if isWinner(mainBoard, RED): ## Si la ficha roja (jugador) gana
                winnerImg = HUMANWINNERIMG ## Cargar la imagen de victoria del jugador
                break ## Salir del bucle
            turn = COMPUTER ## Entregar el primer movimiento a la IA
        else:
            ## Si es el turno de la IA
            column = getComputerMove(mainBoard) ## Llamar al método para el movimiento de la IA, ver el método getComputerMove para detalles
            print(column)
            animateComputerMoving(mainBoard, column) ## Mover la ficha negra
            makeMove(mainBoard, BLACK, column) ## Establecer el hueco vacío más bajo de la columna como negro
            if isWinner(mainBoard, BLACK):
                winnerImg = COMPUTERWINNERIMG
                break
            turn = HUMAN ## Cambiar al turno del jugador

        if isBoardFull(mainBoard):
            ## Si el tablero está lleno, es un empate
            winnerImg = TIEWINNERIMG
            break
✨ Revisar Solución y Practicar

Ejecución y pruebas

A continuación, ejecutaremos el programa y veremos cómo se comporta.

cd ~/project
python fourinrow.py
Demostración de la ejecución del programa
✨ Revisar Solución y Practicar

Resumen

Basado en el algoritmo de Monte Carlo, este proyecto implementó un juego de ajedrez humano vs. IA utilizando Python con el módulo Pygame. El proyecto nos permitió familiarizarnos con los conceptos básicos de creación de instancias y movimiento de objetos en Pygame, y también nos dio una comprensión preliminar de la aplicación específica del algoritmo de Monte Carlo.