Jogo Connect Four - Humano vs. IA

PythonBeginner
Pratique Agora

Introdução

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

Connect Four Game

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

Four In A Row game grid

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.

✨ Verificar Solução e Praticar

Inicializando Variáveis

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.

✨ Verificar Solução e Praticar

Design do Tabuleiro

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.

✨ Verificar Solução e Praticar

Algoritmo de IA para Movimento Ótimo

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:

AI player move analysis

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.

✨ Verificar Solução e Praticar

Movimentos da IA

passos potentialMoves bestMoveFitness bestMoves coluna
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
AI move selection flowchart

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.

AI move impact flowchart

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:

AI fitness calculation flowchart

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
✨ Verificar Solução e Praticar

Operação do Jogador

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.

✨ Verificar Solução e Praticar

Operações de IA

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.
✨ Verificar Solução e Praticar

Operação de Movimento da Peça

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.

✨ Verificar Solução e Praticar

Algumas funções de julgamento

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.

✨ Verificar Solução e Praticar

Julgamento das Condições de Vitória

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.

Winning conditions diagram
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
✨ Verificar Solução e Praticar

Criando o Loop Principal do Jogo

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
✨ Verificar Solução e Praticar

Execução e Testes

Em seguida, executaremos o programa e veremos como ele se comporta.

cd ~/project
python fourinrow.py
program execution demonstration
✨ Verificar Solução e Praticar

Resumo

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.