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