Este projeto é uma implementação em Python do clássico jogo Connect Four, onde um jogador pode competir contra uma IA. Ele utiliza a biblioteca Pygame para a interface e controle do jogo. A tomada de decisão da IA é baseada no algoritmo de busca em árvore Monte Carlo (Monte Carlo tree search), e o nível de dificuldade é ajustável, permitindo que os jogadores se desafiem com oponentes de IA mais inteligentes.
Conceitos Chave:
Utilização do Pygame para o desenvolvimento do jogo.
Implementação do algoritmo de busca em árvore Monte Carlo para a tomada de decisão da IA.
👀 Pré-visualização
🎯 Tarefas
Neste projeto, você aprenderá:
Como construir um jogo usando Pygame
Como implementar o algoritmo de busca em árvore Monte Carlo para a tomada de decisão da IA
Como personalizar e aprimorar o nível de dificuldade da IA
Como criar um jogo Connect Four divertido e interativo para batalhas humano vs. IA
🏆 Conquistas
Após concluir este projeto, você será capaz de:
Desenvolver jogos usando Python e Pygame
Compreender os princípios do algoritmo de busca em árvore Monte Carlo
Ajustar a dificuldade de um oponente de IA para criar uma experiência de jogo desafiadora
Aprimorar interfaces de usuário para tornar a experiência de jogo mais envolvente
Preparação para o Desenvolvimento
O jogo Four-In-A-Row (Quatro em Linha) é jogado em uma grade de tamanho 7*6. Os jogadores se revezam para soltar suas peças do topo de uma coluna. A peça cairá no espaço vazio mais inferior daquela coluna. O jogador que conectar quatro peças em linha reta (horizontal, vertical ou diagonal) vence o jogo.
Crie um arquivo chamado fourinrow.py no diretório ~/project para armazenar o código deste projeto. Além disso, precisamos instalar a biblioteca Pygame para implementar a interface do jogo e suportar operações.
cd ~/project
touch fourinrow.py
sudo pip install pygame
Você pode encontrar os recursos de imagem necessários para este projeto no diretório ~/project/images.
Para entender melhor o código neste projeto, é recomendado que você o estude em conjunto com o código da solução completa.
As variáveis usadas incluem a largura e altura do tabuleiro (podem ser modificadas para projetar tabuleiros de diferentes tamanhos), o nível de dificuldade, o tamanho das peças e a configuração de algumas variáveis de coordenadas.
No arquivo fourinrow.py, insira o seguinte código:
import random, copy, sys, pygame
from pygame.locals import *
BOARDWIDTH = 7 ## Número de colunas no tabuleiro do jogo
BOARDHEIGHT = 6 ## Número de linhas no tabuleiro do jogo
assert BOARDWIDTH >= 4 and BOARDHEIGHT >= 4, 'Board must be at least 4x4.'
## A instrução assert do python é usada para declarar que sua expressão booleana fornecida deve ser verdadeira.
## Se a expressão for falsa, ela levanta uma exceção.
DIFFICULTY = 2 ## Nível de dificuldade, número de movimentos que o computador pode considerar
## Aqui, 2 significa considerar 7 movimentos possíveis do oponente e como responder a esses 7 movimentos
SPACESIZE = 50 ## Tamanho das peças
FPS = 30 ## Taxa de atualização da tela, 30/s
WINDOWWIDTH = 640 ## Largura da tela do jogo em pixels
WINDOWHEIGHT = 480 ## Altura da tela do jogo em pixels
XMARGIN = int((WINDOWWIDTH - BOARDWIDTH * SPACESIZE) / 2) ## Coordenada X da borda esquerda da grade
YMARGIN = int((WINDOWHEIGHT - BOARDHEIGHT * SPACESIZE) / 2) ## Coordenada Y da borda superior da grade
BRIGHTBLUE = (0, 50, 255) ## Cor azul
WHITE = (255, 255, 255) ## Cor branca
BGCOLOR = BRIGHTBLUE
TEXTCOLOR = WHITE
RED = 'red'
BLACK = 'black'
EMPTY = None
HUMAN = 'human'
COMPUTER = 'computer'
Além disso, também precisamos definir algumas variáveis globais do pygame. Essas variáveis globais serão chamadas várias vezes em vários módulos posteriormente. Muitas delas são variáveis que armazenam imagens carregadas, então o trabalho de preparação é um pouco longo, por favor, seja paciente.
## Inicializa os módulos pygame
pygame.init()
## Cria um objeto Clock
FPSCLOCK = pygame.time.Clock()
## Cria a janela do jogo
DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))
## Define o título da janela do jogo
pygame.display.set_caption(u'four in row')
## Rect(left, top, width, height) é usado para definir a posição e o tamanho
REDPILERECT = pygame.Rect(int(SPACESIZE / 2), WINDOWHEIGHT - int(3 * SPACESIZE / 2), SPACESIZE, SPACESIZE)
## Cria as peças pretas inferiores esquerda e inferior direita na janela
BLACKPILERECT = pygame.Rect(WINDOWWIDTH - int(3 * SPACESIZE / 2), WINDOWHEIGHT - int(3 * SPACESIZE / 2), SPACESIZE, SPACESIZE)
## Carrega a imagem da peça vermelha
REDTOKENIMG = pygame.image.load('images/4rowred.png')
## Redimensiona a imagem da peça vermelha para SPACESIZE
REDTOKENIMG = pygame.transform.smoothscale(REDTOKENIMG, (SPACESIZE, SPACESIZE))
## Carrega a imagem da peça preta
BLACKTOKENIMG = pygame.image.load('images/4rowblack.png')
## Redimensiona a imagem da peça preta para SPACESIZE
BLACKTOKENIMG = pygame.transform.smoothscale(BLACKTOKENIMG, (SPACESIZE, SPACESIZE))
## Carrega a imagem do tabuleiro
BOARDIMG = pygame.image.load('images/4rowboard.png')
## Redimensiona a imagem do tabuleiro para SPACESIZE
BOARDIMG = pygame.transform.smoothscale(BOARDIMG, (SPACESIZE, SPACESIZE))
## Carrega a imagem do vencedor humano
HUMANWINNERIMG = pygame.image.load('images/4rowhumanwinner.png')
## Carrega a imagem do vencedor da IA
COMPUTERWINNERIMG = pygame.image.load('images/4rowcomputerwinner.png')
## Carrega a imagem de empate
TIEWINNERIMG = pygame.image.load('images/4rowtie.png')
## Retorna um objeto Rect
WINNERRECT = HUMANWINNERIMG.get_rect()
## Centraliza a imagem do vencedor na janela do jogo
WINNERRECT.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2))
## Carrega a imagem da seta para instruções do usuário
ARROWIMG = pygame.image.load('images/4rowarrow.png')
## Retorna um objeto Rect
ARROWRECT = ARROWIMG.get_rect()
## Define a posição esquerda da imagem da seta
ARROWRECT.left = REDPILERECT.right + 10
## Alinha a imagem da seta verticalmente com a peça vermelha abaixo dela
ARROWRECT.centery = REDPILERECT.centery
Para entender melhor o código neste projeto, é recomendado que você o estude em conjunto com o código da solução completa.
Inicialmente, limpe a lista bidimensional que representa o tabuleiro e, em seguida, defina as posições correspondentes no tabuleiro com cores com base nos movimentos do jogador e da IA.
def drawBoard(board, extraToken=None):
## DISPLAYSURF é nossa interface, definida no módulo de inicialização de variáveis.
DISPLAYSURF.fill(BGCOLOR) ## Preenche a cor de fundo da janela do jogo com azul.
spaceRect = pygame.Rect(0, 0, SPACESIZE, SPACESIZE) ## Cria uma instância Rect.
for x in range(BOARDWIDTH):
## Determina as coordenadas da posição superior esquerda de cada célula em cada linha de cada coluna.
for y in range(BOARDHEIGHT):
spaceRect.topleft = (XMARGIN + (x * SPACESIZE), YMARGIN + (y * SPACESIZE))
## Quando x = 0 e y = 0, é a primeira célula na primeira linha da primeira coluna.
if board[x][y] == RED: ## Se o valor da célula for vermelho,
## desenha um token vermelho na janela do jogo dentro de spaceRect.
DISPLAYSURF.blit(REDTOKENIMG, spaceRect)
elif board[x][y] == BLACK: ## Caso contrário, desenha um token preto.
DISPLAYSURF.blit(BLACKTOKENIMG, spaceRect)
## extraToken é uma variável que contém informações de posição e informações de cor.
## É usado para exibir um token especificado.
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))
## Desenha os painéis de token.
for x in range(BOARDWIDTH):
for y in range(BOARDHEIGHT):
spaceRect.topleft = (XMARGIN + (x * SPACESIZE), YMARGIN + (y * SPACESIZE))
DISPLAYSURF.blit(BOARDIMG, spaceRect)
## Desenha os tokens na parte inferior esquerda e inferior direita da janela do jogo.
DISPLAYSURF.blit(REDTOKENIMG, REDPILERECT) ## Token vermelho esquerdo.
DISPLAYSURF.blit(BLACKTOKENIMG, BLACKPILERECT) ## Token preto direito.
def getNewBoard():
board = []
for x in range(BOARDWIDTH):
board.append([EMPTY] * BOARDHEIGHT)
return board ## Retorna a lista do tabuleiro com um número BOARDHEIGHT de valores None.
No código acima, a função drawBoard() desenha o tabuleiro e os tokens no tabuleiro. A função getNewBoard() retorna uma nova estrutura de dados do tabuleiro.
Explicação breve da ideia da busca em árvore Monte Carlo:
Use o método Monte Carlo unidimensional para avaliar o tabuleiro do jogo Go. Especificamente, quando uma situação específica do tabuleiro é dada, o programa seleciona aleatoriamente um ponto de todos os pontos disponíveis na situação atual e coloca uma peça de xadrez nele. Essa seleção aleatória de pontos disponíveis (ponto de rolagem) é repetida até que nenhum dos lados tenha pontos disponíveis (o jogo termina), e então a vitória ou derrota resultante desse estado final é retroalimentada como base para avaliar a situação atual.
Neste projeto, a IA escolhe continuamente diferentes colunas e avalia os resultados das vitórias de ambos os lados. A IA acabará por escolher uma estratégia com uma avaliação mais alta.
Antes de olhar para as imagens e o texto abaixo, por favor, dê uma olhada no código no final e, em seguida, consulte as explicações correspondentes.
Observando o confronto entre a IA e o jogador na figura abaixo:
Algumas variáveis no projeto podem refletir intuitivamente o processo das operações da peça de xadrez da IA:
PotentialMoves: Retorna uma lista que representa a possibilidade de a IA vencer ao mover uma peça de xadrez para qualquer coluna na lista. Os valores são números aleatórios de -1 a 1. Quando o valor é negativo, significa que o jogador pode vencer nos próximos dois movimentos, e quanto menor o valor, maior a possibilidade de o jogador vencer. Se o valor for 0, significa que o jogador não vencerá, e a IA também não vencerá. Se o valor for 1, significa que a IA pode vencer.
bestMoveFitness: Fitness é o valor máximo selecionado de PotentialMoves.
bestMoves: Se houver vários valores máximos em PotentialMoves, significa que as chances de vitória do jogador são as menores quando a IA move a peça de xadrez para as colunas onde esses valores estão localizados. Portanto, essas colunas são adicionadas à lista bestMoves.
column: Quando há vários valores em bestMoves, selecione aleatoriamente uma coluna de bestMoves como o movimento da IA. Se houver apenas um valor, column é este valor único.
No projeto, imprimindo esses bestMoveFitness, bestMoves, column e potentialMoves, podemos deduzir os parâmetros de cada etapa da IA na figura acima.
Ao examinar a seleção da IA no terceiro passo, podemos ter uma melhor compreensão do algoritmo:
A figura abaixo ilustra alguns dos movimentos da IA, mostrando as possíveis escolhas para o jogador se a IA colocar uma peça na primeira coluna e o impacto do próximo movimento da IA nas chances de vitória do jogador. Através deste processo de busca e iteração, a IA pode determinar as situações de vitória tanto para o oponente quanto para si mesma nos próximos dois passos e tomar decisões de acordo.
A figura abaixo é um fluxograma do cálculo do valor de fitness para a IA. Neste projeto, o coeficiente de dificuldade é 2, e precisamos considerar 7^4=2041 casos:
Do fluxograma acima, não é difícil descobrir que, se a IA colocar sua primeira peça nas colunas 0, 1, 2, 4, 5 ou 6, o jogador sempre poderá colocar as duas peças restantes na coluna 3 e vencer. Para facilitar a expressão, usamos uma sequência para representar várias combinações, onde o primeiro elemento representa o primeiro movimento da IA, o segundo número representa a resposta do jogador e o terceiro número representa a resposta da IA. "X" representa qualquer movimento válido. Portanto, [0,0,x]=0, e pode-se deduzir que quando a sequência é [0,x<>3,x], o jogador não pode vencer. Somente quando a segunda peça do jogador estiver na coluna 3, e o segundo movimento da IA não estiver na coluna 3, a IA pode vencer. Portanto, [0,x=3,x<>3] = -1, e existem 6 casos como este. O resultado final é (0+0+...(43 vezes)-1*6)/7/7 = -0.12.
Pelo mesmo raciocínio, os resultados para os outros quatro casos são todos -0.12. Se o primeiro movimento da IA for na coluna 3, o jogador não pode vencer, e a IA também não pode vencer, então o valor é 0. A IA escolhe o movimento com o valor de fitness mais alto, o que significa que ela colocará sua peça na coluna 3.
A mesma análise pode ser aplicada aos movimentos subsequentes da IA. Em resumo, quanto maior a possibilidade de o jogador vencer após o movimento da IA, menor o valor de fitness para a IA, e a IA escolherá o movimento com um valor de fitness mais alto para impedir que o jogador vença. Claro, se a IA puder vencer por si só, ela priorizará o movimento que leva à sua própria vitória.
def getPotentialMoves(board, tile, lookAhead):
if lookAhead == 0 or isBoardFull(board):
'''
Se o coeficiente de dificuldade for 0 ou o tabuleiro estiver cheio,
retorna uma lista com todos os valores definidos como 0. Isso significa que
o valor de fitness é igual aos movimentos potenciais para cada coluna.
Nesse caso, a IA soltará a peça aleatoriamente e perderá sua inteligência.
'''
return [0] * BOARDWIDTH
## Determina a cor da peça do oponente
if tile == RED:
enemyTile = BLACK
else:
enemyTile = RED
potentialMoves = [0] * BOARDWIDTH
## Inicializa uma lista de movimentos potenciais, com todos os valores definidos como 0
for firstMove in range(BOARDWIDTH):
## Itera sobre cada coluna e considera qualquer movimento de ambos os lados como o firstMove
## O movimento do outro lado é então considerado como o counterMove
## Aqui, nosso firstMove se refere ao movimento da IA e o movimento do oponente é considerado como counterMove
## Faça uma cópia profunda do tabuleiro para evitar a influência mútua entre board e dupeBoard
dupeBoard = copy.deepcopy(board)
if not isValidMove(dupeBoard, firstMove):
## Se o movimento de colocar uma peça preta na coluna especificada por firstMove for inválido em dupeBoard
continue
## Continue para o próximo firstMove
makeMove(dupeBoard, tile, firstMove)
## Se for um movimento válido, defina a cor da grade correspondente
if isWinner(dupeBoard, tile):
## Se a IA vencer
potentialMoves[firstMove] = 1
## A peça vencedora automaticamente recebe um valor alto para indicar suas chances de vencer
## Quanto maior o valor, maiores as chances de vencer e menores as chances de o oponente vencer
break
## Não interfira no cálculo de outros movimentos
else:
if isBoardFull(dupeBoard):
## Se não houver grades vazias em dupeBoard
potentialMoves[firstMove] = 0
## Não é possível mover
else:
for counterMove in range(BOARDWIDTH):
## Considere o movimento do oponente
dupeBoard2 = copy.deepcopy(dupeBoard)
if not isValidMove(dupeBoard2, counterMove):
continue
makeMove(dupeBoard2, enemyTile, counterMove)
if isWinner(dupeBoard2, enemyTile):
potentialMoves[firstMove] = -1
## Se o jogador vencer, o valor de fitness para a IA nesta coluna é o mais baixo
break
else:
## Chama recursivamente getPotentialMoves
results = getPotentialMoves(dupeBoard2, tile, lookAhead - 1)
## Use a representação de ponto flutuante aqui para resultados mais precisos
## Isso garante que os valores em potentialMoves estejam dentro do intervalo [-1, 1]
potentialMoves[firstMove] += (sum(results)*1.0 / BOARDWIDTH) / BOARDWIDTH
return potentialMoves
Arraste a peça de xadrez, determine o quadrado onde a peça de xadrez está localizada, valide a peça de xadrez, chame a função de soltar a peça de xadrez e complete a operação.
def getHumanMove(board, isFirstMove):
draggingToken = False
tokenx, tokeny = None, None
while True:
## Use pygame.event.get() para lidar com todos os eventos
for event in pygame.event.get():
if event.type == QUIT: ## Parar e sair
pygame.quit()
sys.exit()
elif event.type == MOUSEBUTTONDOWN and not draggingToken and REDPILERECT.collidepoint(event.pos):
## Se o tipo de evento for mouse button down, draggingToken for True e a posição do clique do mouse estiver dentro de REDPILERECT
draggingToken = True
tokenx, tokeny = event.pos
elif event.type == MOUSEMOTION and draggingToken: ## Se a peça vermelha for arrastada
tokenx, tokeny = event.pos ## Atualiza a posição da peça arrastada
elif event.type == MOUSEBUTTONUP and draggingToken:
## Se o mouse for solto e a peça de xadrez for arrastada
## Se a peça de xadrez for arrastada diretamente acima do tabuleiro
if tokeny < YMARGIN and tokenx > XMARGIN and tokenx < WINDOWWIDTH - XMARGIN:
column = int((tokenx - XMARGIN) / SPACESIZE) ## Determina a coluna onde a peça de xadrez cairá com base na coordenada x da peça de xadrez (0,1...6)
if isValidMove(board, column): ## Se o movimento da peça de xadrez for válido
"""
Cair no quadrado vazio correspondente,
Esta função mostra apenas o efeito de queda
A peça de xadrez preenchendo o quadrado também pode ser alcançada sem esta função pelo seguinte código
"""
animateDroppingToken(board, column, RED)
## Define o quadrado mais inferior na coluna vazia como vermelho
board[column][getLowestEmptySpace(board, column)] = RED
drawBoard(board) ## Desenha a peça de xadrez vermelha no quadrado solto
pygame.display.update() ## Atualização da janela
return
tokenx, tokeny = None, None
draggingToken = False
if tokenx != None and tokeny != None: ## Se uma peça de xadrez for arrastada, exibe a peça de xadrez arrastada
drawBoard(board, {'x':tokenx - int(SPACESIZE / 2), 'y':tokeny - int(SPACESIZE / 2), 'color':RED})
## Ajusta as coordenadas x, y para que o mouse esteja sempre na posição central da peça de xadrez durante o arrasto
else:
drawBoard(board) ## Quando é um movimento inválido, após o mouse ser solto, porque todos os valores no tabuleiro são none
## Ao chamar drawBoard, as operações realizadas são exibir as duas peças de xadrez abaixo, o que equivale a retornar a peça de xadrez para o local onde ela começou a arrastar
if isFirstMove:
DISPLAYSURF.blit(ARROWIMG, ARROWRECT) ## A IA se move primeiro, exibe a imagem de operação de dica
pygame.display.update()
FPSCLOCK.tick()
No código acima, a função getHumanMove() lida com o movimento do jogador. A função animateDroppingToken() anima a queda do token. A função getLowestEmptySpace() retorna o espaço vazio mais baixo em uma coluna.
Implemente a função para animar o movimento do computador e a aterrissagem das peças da IA nas respectivas posições.
def animateComputerMoving(board, column):
x = BLACKPILERECT.left ## A coordenada esquerda da peça preta na parte inferior
y = BLACKPILERECT.top ## A coordenada superior da peça preta na parte inferior
speed = 1.0
while y > (YMARGIN - SPACESIZE): ## Quando y tem um valor maior, indicando que a peça está abaixo da janela
y -= int(speed) ## Diminui y continuamente, o que significa que a peça se move para cima
speed += 0.5 ## Aumenta a velocidade com que y diminui
drawBoard(board, {'x':x, 'y':y, 'color':BLACK})
## y continua mudando, desenhando continuamente a peça preta, criando um efeito de ascensão contínua
pygame.display.update()
FPSCLOCK.tick()
## Quando a peça sobe para o topo do tabuleiro
y = YMARGIN - SPACESIZE ## Redefine y, para que a parte inferior da peça seja alinhada com a parte superior do tabuleiro
speed = 1.0
while x > (XMARGIN + column * SPACESIZE): ## Quando x é maior que a coordenada x da coluna desejada
x -= int(speed) ## Diminui x continuamente, o que significa que a peça se move para a esquerda
speed += 0.5
drawBoard(board, {'x':x, 'y':y, 'color':BLACK})
## Neste ponto, a coordenada y permanece inalterada, o que significa que a peça se move horizontalmente para a coluna
pygame.display.update()
FPSCLOCK.tick()
## A peça preta pousa no espaço vazio calculado
animateDroppingToken(board, column, BLACK)
Selecione o número mais alto da lista de potentialMoves retornada, como o valor de fitness, e escolha aleatoriamente dentre aquelas colunas com altos valores de fitness como o alvo final do movimento.
def getComputerMove(board):
potentialMoves = getPotentialMoves(board, BLACK, DIFFICULTY) ## Movimentos potenciais, uma lista com valores BOARDWIDTH
## Os valores na lista estão relacionados ao nível de dificuldade definido
bestMoves = [] ## Cria uma lista bestMoves vazia
bestMoveFitness = -1 ## Como o valor mínimo em potentialMoves é -1, ele serve como o limite inferior
print(bestMoveFitness)
for i in range(len(potentialMoves)):
if potentialMoves[i] > bestMoveFitness and isValidMove(board, i):
bestMoveFitness = potentialMoves[i] ## Atualiza continuamente bestMoves, para que cada valor em bestMoves seja o maior
## garantindo que o movimento seja válido.
for i in range(len(potentialMoves)):
if potentialMoves[i] == bestMoveFitness and isValidMove(board, i):
bestMoves.append(i) ## Lista todas as colunas onde a peça pode ser movida. Esta lista pode estar vazia, conter
## apenas um valor ou vários valores.
print(bestMoves)
return random.choice(bestMoves) ## Escolhe aleatoriamente uma das colunas onde a peça pode ser movida como o movimento alvo.
Ao alterar continuamente as coordenadas correspondentes das peças, obtenha o efeito de animação de queda.
def getLowestEmptySpace(board, column):
## Retorna o espaço vazio mais baixo em uma coluna
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
'''
Atribui o jogador (vermelho/preto) ao espaço vazio mais baixo na coluna.
Como a peça é solta no espaço vazio mais baixo em uma coluna,
ela é considerada como a cor desse espaço.
'''
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()
No código acima, a função makeMove() faz um movimento no tabuleiro. A função animateDroppingToken() anima a queda do token. A função getLowestEmptySpace() retorna o espaço vazio mais baixo em uma coluna.
Julga a validade do movimento de uma peça, julga se ainda existem espaços vazios no tabuleiro.
def isValidMove(board, column):
## Julga a validade do movimento de uma peça
if column < 0 or column >= (BOARDWIDTH) or board[column][0] != EMPTY:
## Se a coluna for menor que 0 ou maior que BOARDWIDTH, ou não houver espaço vazio na coluna
return False
## Então é um movimento inválido, caso contrário, é válido
return True
def isBoardFull(board):
## Se não houver espaços vazios na grade, retorna True
for x in range(BOARDWIDTH):
for y in range(BOARDHEIGHT):
if board[x][y] == EMPTY:
return False
return True
No código acima, a função isValidMove() retorna True se o movimento for válido. A função isBoardFull() retorna True se o tabuleiro estiver cheio.
Vários diagramas são fornecidos para facilitar a compreensão das quatro condições de vitória. As posições mostradas no diagrama correspondem aos valores extremos de x e y.
def isWinner(board, tile):
## Verifica a situação horizontal das peças
for x in range(BOARDWIDTH - 3): ## x assume os valores 0, 1, 2, 3
for y in range(BOARDHEIGHT): ## itera por todas as linhas
## Se x = 0, verifica se as primeiras quatro peças na y-ésima linha são todas do mesmo tile. Isso pode ser usado para percorrer todas as situações horizontais de peças conectando quatro em linha. Se qualquer x, y for verdadeiro, pode ser determinado como uma vitória
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
## Verifica a situação vertical das peças, semelhante à situação 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
## Verifica a situação diagonal inclinada para a esquerda das peças
for x in range(BOARDWIDTH - 3): ## x assume os valores 0, 1, 2, 3
for y in range(3, BOARDHEIGHT): ## porque ao formar uma diagonal inclinada para a esquerda com quatro em linha, a peça mais inferior deve estar pelo menos quatro quadrados de distância do topo, ou seja, 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: ## determina se as quatro peças diagonais inclinadas para a esquerda são da mesma cor
return True
## Verifica a situação diagonal inclinada para a direita das peças, semelhante à situação diagonal inclinada para a esquerda
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, criamos o loop principal do jogo para manter o jogo rodando continuamente.
def main():
## Código existente omitido
isFirstGame = True ## Inicializa isFirstGame
while True: ## Mantém o jogo rodando continuamente
runGame(isFirstGame)
isFirstGame = False
def runGame(isFirstGame):
if isFirstGame:
## No início do primeiro jogo
## Deixa a IA fazer o primeiro movimento para que os jogadores possam ver como o jogo é jogado
turn = COMPUTER
showHelp = True
else:
## Para o segundo jogo e seguintes, atribui turnos aleatoriamente
if random.randint(0, 1) == 0:
turn = COMPUTER
else:
turn = HUMAN
showHelp = False
mainBoard = getNewBoard() ## Configura a estrutura inicial do tabuleiro vazio
while True: ## Loop principal do jogo
if turn == HUMAN: ## Se for a vez do jogador
getHumanMove(mainBoard, showHelp) ## Chama o método para o movimento do jogador, veja o método getHumanMove para detalhes
if showHelp:
## Se houver uma imagem de dica, desliga a dica depois que a IA faz o primeiro movimento
showHelp = False
if isWinner(mainBoard, RED): ## Se a peça vermelha (jogador) vencer
winnerImg = HUMANWINNERIMG ## Carrega a imagem de vitória do jogador
break ## Sai do loop
turn = COMPUTER ## Entrega o primeiro movimento para a IA
else:
## Se for a vez da IA
column = getComputerMove(mainBoard) ## Chama o método para o movimento da IA, veja o método getComputerMove para detalhes
print(column)
animateComputerMoving(mainBoard, column) ## Move a peça preta
makeMove(mainBoard, BLACK, column) ## Define o slot vazio mais inferior na coluna como preto
if isWinner(mainBoard, BLACK):
winnerImg = COMPUTERWINNERIMG
break
turn = HUMAN ## Muda para a vez do jogador
if isBoardFull(mainBoard):
## Se o tabuleiro estiver cheio, é um empate
winnerImg = TIEWINNERIMG
break
Com base no algoritmo Monte Carlo, este projeto implementou um jogo de xadrez humano contra IA usando Python com o módulo Pygame. O projeto nos permitiu familiarizar-nos com os fundamentos da criação de instâncias e movimentação de objetos no Pygame, e também nos deu uma compreensão preliminar da aplicação específica do algoritmo Monte Carlo.