Игра Connect Four - Человек против AI

PythonPythonBeginner
Практиковаться сейчас

💡 Этот учебник переведен с английского с помощью ИИ. Чтобы просмотреть оригинал, вы можете перейти на английский оригинал

Введение

Этот проект представляет собой реализацию на Python классической игры "Connect Four", в которой игрок может соревноваться с AI. Для интерфейса и управления игры используется библиотека Pygame. AI принимает решения на основе алгоритма поиска в дереве Монте-Карло, и уровень сложности может быть настроен, позволяя игрокам предлагать себе вызов с более умными противниками AI.

Основные концепции:

  • Использование Pygame для разработки игр.
  • Реализация алгоритма поиска в дереве Монте-Карло для принятия решений AI.

👀 Предварительный просмотр

Connect Four Game

🎯 Задачи

В этом проекте вы научитесь:

  • Как создавать игру с использованием Pygame
  • Как реализовать алгоритм поиска в дереве Монте-Карло для принятия решений AI
  • Как настраивать и улучшать уровень сложности AI
  • Как создать увлекательную и интерактивную игру "Connect Four" для боев между человеком и AI

🏆 Достижения

После завершения этого проекта вы сможете:

  • Разрабатывать игры с использованием Python и Pygame
  • Разбираться в принципах алгоритма поиска в дереве Монте-Карло
  • Настраивать сложность противника AI, чтобы создать захватывающий игровой опыт
  • Улучшать пользовательский интерфейс, чтобы сделать игровой опыт более увлекательным

Подготовка к разработке

Игра "Connect Four" происходит на сетке размером 7*6. Игроки ходят по очереди, опуская свои фишки сверху в колонку. Фишка падает в самую нижнюю свободную ячейку в этой колонке. Игрок, который соединяет четыре фишки в прямой линию (горизонтально, вертикально или по диагонали), выигрывает игру.

Four In A Row game grid

Создайте файл с именем fourinrow.py в директории ~/project, чтобы сохранить код для этого проекта. Кроме того, нам нужно установить библиотеку Pygame, чтобы реализовать интерфейс игры и поддерживать операции.

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

Вы можете найти требуемые изображение ресурсы для этого проекта в директории ~/project/images.

Чтобы лучше понять код в этом проекте, рекомендуется изучать его вместе с кодом полного решения.

✨ Проверить решение и практиковаться

Инициализация переменных

В использовании переменные включают ширину и высоту игровой доски (можно изменить, чтобы проектировать доски различных размеров), уровень сложности, размер игровых фишек и настройку некоторых координатных переменных.

В файле fourinrow.py введите следующий код:

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

BOARDWIDTH = 7  ## Количество столбцов на игровой доске
BOARDHEIGHT = 6 ## Количество строк на игровой доске
assert BOARDWIDTH >= 4 and BOARDHEIGHT >= 4, 'Доска должна быть по крайней мере 4x4.'

## Python утверждение assert используется для того, чтобы объявить, что данное логическое выражение должно быть истинным.
## Если выражение ложно, то возникает исключение.

DIFFICULTY = 2 ## Уровень сложности, количество ходов, которые компьютер может рассмотреть
               ## Здесь 2 означает рассмотрение 7 возможных ходов противника и как ответить на эти 7 ходов

SPACESIZE = 50 ## Размер игровых фишек

FPS = 30 ## Частота обновления экрана, 30/с
WINDOWWIDTH = 640  ## Ширина игрового экрана в пикселях
WINDOWHEIGHT = 480 ## Высота игрового экрана в пикселях

XMARGIN = int((WINDOWWIDTH - BOARDWIDTH * SPACESIZE) / 2)  ## X-координата левого края сетки
YMARGIN = int((WINDOWHEIGHT - BOARDHEIGHT * SPACESIZE) / 2) ## Y-координата верхнего края сетки
BRIGHTBLUE = (0, 50, 255) ## Синий цвет
WHITE = (255, 255, 255) ## Белый цвет

BGCOLOR = BRIGHTBLUE
TEXTCOLOR = WHITE

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

Кроме того, нам также нужно определить некоторые глобальные переменные pygame. Эти глобальные переменные будут вызываться несколько раз в различных модулях позже. Многие из них - это переменные, которые хранят загруженные изображения, поэтому подготовительная работа занимает несколько времени, будьте терпеливы.

## Инициализировать модули pygame
pygame.init()

## Создать объект Clock
FPSCLOCK = pygame.time.Clock()

## Создать игровое окно
DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))

## Установить заголовок игрового окна
pygame.display.set_caption(u'Connect Four')

## Rect(left, top, width, height) используется для определения позиции и размера
REDPILERECT = pygame.Rect(int(SPACESIZE / 2), WINDOWHEIGHT - int(3 * SPACESIZE / 2), SPACESIZE, SPACESIZE)

## Создать нижние левую и нижнюю правую игровые фишки в окне
BLACKPILERECT = pygame.Rect(WINDOWWIDTH - int(3 * SPACESIZE / 2), WINDOWHEIGHT - int(3 * SPACESIZE / 2), SPACESIZE, SPACESIZE)

## Загрузить изображение красной игровой фишки
REDTOKENIMG = pygame.image.load('images/4rowred.png')

## Масштабировать изображение красной игровой фишки до SPACESIZE
REDTOKENIMG = pygame.transform.smoothscale(REDTOKENIMG, (SPACESIZE, SPACESIZE))

## Загрузить изображение черной игровой фишки
BLACKTOKENIMG = pygame.image.load('images/4rowblack.png')

## Масштабировать изображение черной игровой фишки до SPACESIZE
BLACKTOKENIMG = pygame.transform.smoothscale(BLACKTOKENIMG, (SPACESIZE, SPACESIZE))

## Загрузить изображение игровой доски
BOARDIMG = pygame.image.load('images/4rowboard.png')

## Масштабировать изображение игровой доски до SPACESIZE
BOARDIMG = pygame.transform.smoothscale(BOARDIMG, (SPACESIZE, SPACESIZE))

## Загрузить изображение победителя - человека
HUMANWINNERIMG = pygame.image.load('images/4rowhumanwinner.png')

## Загрузить изображение победителя - AI
COMPUTERWINNERIMG = pygame.image.load('images/4rowcomputerwinner.png')

## Загрузить изображение ничьей
TIEWINNERIMG = pygame.image.load('images/4rowtie.png')

## Вернуть объект Rect
WINNERRECT = HUMANWINNERIMG.get_rect()

## Центрировать изображение победителя на игровом окне
WINNERRECT.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2))

## Загрузить изображение стрелки для инструкции пользователю
ARROWIMG = pygame.image.load('images/4rowarrow.png')

## Вернуть объект Rect
ARROWRECT = ARROWIMG.get_rect()

## Установить левую позицию изображения стрелки
ARROWRECT.left = REDPILERECT.right + 10

## Выровнять изображение стрелки по вертикали с красной игровой фишкой под ней
ARROWRECT.centery = REDPILERECT.centery

Чтобы лучше понять код в этом проекте, рекомендуется изучать его вместе с кодом полного решения.

✨ Проверить решение и практиковаться

Дизайн игровой доски

Сначала очистите двухмерный список, представляющий игровую доску, а затем установите соответствующие позиции на доске цветами в зависимости от ходов игрока и AI.

def drawBoard(board, extraToken=None):
    ## DISPLAYSURF - это наш интерфейс, определенный в модуле инициализации переменных.
    DISPLAYSURF.fill(BGCOLOR) ## Заполните фон игрового окна цветом синим.
    spaceRect = pygame.Rect(0, 0, SPACESIZE, SPACESIZE) ## Создайте экземпляр Rect.
    for x in range(BOARDWIDTH):
        ## Определите координаты верхнего левого угла каждой ячейки в каждой строке каждого столбца.
        for y in range(BOARDHEIGHT):
            spaceRect.topleft = (XMARGIN + (x * SPACESIZE), YMARGIN + (y * SPACESIZE))

            ## Когда x = 0 и y = 0, это первая ячейка в первой строке первого столбца.
            if board[x][y] == RED: ## Если значение ячейки равно красному,
                ## нарисуйте красную фишку в игровом окне внутри spaceRect.
                DISPLAYSURF.blit(REDTOKENIMG, spaceRect)
            elif board[x][y] == BLACK: ## В противном случае нарисуйте черную фишку.
                DISPLAYSURF.blit(BLACKTOKENIMG, spaceRect)

    ## extraToken - это переменная, которая содержит информацию о позиции и цвете.
    ## Она используется для отображения указанной фишки.
    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))

    ## Нарисуйте панели для фишек.
    for x in range(BOARDWIDTH):
        for y in range(BOARDHEIGHT):
            spaceRect.topleft = (XMARGIN + (x * SPACESIZE), YMARGIN + (y * SPACESIZE))
            DISPLAYSURF.blit(BOARDIMG, spaceRect)

    ## Нарисуйте фишки в нижнем левом и нижнем правом углах игрового окна.
    DISPLAYSURF.blit(REDTOKENIMG, REDPILERECT) ## Левая красная фишка.
    DISPLAYSURF.blit(BLACKTOKENIMG, BLACKPILERECT) ## Правая черная фишка.


def getNewBoard():
    board = []
    for x in range(BOARDWIDTH):
        board.append([EMPTY] * BOARDHEIGHT)
    return board ## Верните список доски с количеством BOARDHEIGHT значений None.

В вышеприведенном коде функция drawBoard() рисует игровую доску и фишки на ней. Функция getNewBoard() возвращает новую структуру данных для доски.

✨ Проверить решение и практиковаться

Алгоритм AI для наилучшего хода

Кратко поясните идею поиска в дереве Монте-Карло:

Используйте одномерный метод Монте-Карло для оценки игровой доски в Go. Конкретно, когда дана определенная ситуация на доске, программа случайным образом выбирает точку из всех доступных точек в текущей ситуации и размещает на ней фишку. Этот процесс случайного выбора доступных точек (ход броском) повторяется до тех пор, пока ни одна сторона не останется без доступных точек (игра заканчивается), а затем полученное выигрыш или проигрыш этого конечного состояния передается в качестве основания для оценки текущей ситуации.

В этом проекте AI непрерывно выбирает разные столбцы и оценивает результаты побед обоих сторон. AI в конечном итоге выберет стратегию с более высокой оценкой.

Прежде чем смотреть на картинки и текст ниже, пожалуйста, посмотрите на код в конце, а затем обратитесь к соответствующим объяснениям.

Изучите противостояние между AI и игроком на рисунке ниже:

AI player move analysis

Некоторые переменные в проекте могут интуитивно отобразить процесс операции AI с фишками:

PotentialMoves: Возвращает список, который представляет возможность выигрыша AI при размещении фишки в любом столбце из списка. Значения - это случайные числа от -1 до 1. Когда значение отрицательное, это означает, что игрок может выиграть за следующие два хода, и чем меньше значение, тем выше вероятность победы игрока. Если значение равно 0, это означает, что игрок не выиграет, и AI также не выиграет. Если значение равно 1, это означает, что AI может выиграть.

bestMoveFitness: Фитнес - это максимальное значение, выбранное из PotentialMoves.

bestMoves: Если в PotentialMoves есть несколько максимальных значений, это означает, что шансы игрока на победу наименьшие, когда AI размещает фишку в столбцах, где находятся эти значения. Поэтому эти столбцы добавляются в список bestMoves.

column: Когда в bestMoves несколько значений, случайным образом выбирается один столбец из bestMoves в качестве хода AI. Если есть только одно значение, column это уникальное значение.

В проекте, выводя эти bestMoveFitness, bestMoves, column и potentialMoves, мы можем вывести параметры каждого шага AI на рисунке выше.

✨ Проверить решение и практиковаться

Ходы AI

Шаги potentialMoves bestMoveFitness bestMoves column
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

Изучением выбора AI на третьем шаге мы можем лучше понять алгоритм:

На рисунке ниже показаны некоторые ходы AI, а также возможные варианты хода игрока, если AI разместит фишку в первом столбце, и влияние следующего хода AI на шансы игрока на победу. Через этот процесс поиска и итерации AI может определить выигрышные ситуации как для противника, так и для себя за следующие два хода и принимать решения соответственно.

AI move impact flowchart

На рисунке ниже приведена диаграмма алгоритма вычисления значения фитнеса для AI. В этом проекте коэффициент сложности равен 2, и мы должны рассмотреть 7^4 = 2041 случаев:

AI fitness calculation flowchart

Из приведенной выше диаграммы алгоритма不难看出, если AI разместит свою первую фишку в столбце 0, 1, 2, 4, 5 или 6, игрок всегда сможет разместить оставшиеся две фишки в столбце 3 и выиграть. Для удобства выражения мы используем последовательность для представления различных комбинаций, где первый элемент представляет первый ход AI, второй номер - ответ игрока, а третий номер - ответ AI. "X" представляет любой допустимый ход. Поэтому [0,0,x]=0, и можно вывести, что когда последовательность имеет вид [0,x<>3,x], игрок не может выиграть. Только когда вторая фишка игрока находится в столбце 3, а второй ход AI не находится в столбце 3, AI может выиграть. Поэтому [0,x=3,x<>3] = -1, и таких случаев 6. Финальный результат равен (0+0+...(43 раза)-1*6)/7/7 = -0.12.

По аналогии для других четырех случаев результаты равны -0.12. Если первый ход AI находится в столбце 3, игрок не может выиграть, и AI также не может выиграть, поэтому значение равно 0. AI выбирает ход с наивысшим значением фитнеса, что означает, что он разместит свою фишку в столбце 3.

Та же аналитика может быть применена к последующим ходам AI. В целом, чем выше шансы игрока на победу после хода AI, тем ниже значение фитнеса для AI, и AI выбирает ход с более высоким значением фитнеса, чтобы предотвратить победу игрока. Конечно, если AI может выиграть сам, он будет приоритетно выбирать ход, ведущий к своей победе.

def getPotentialMoves(board, tile, lookAhead):
    if lookAhead == 0 or isBoardFull(board):
        '''
        Если коэффициент сложности равен 0 или доска заполнена,
        возвращается список, все значения которого равны 0. Это означает, что
        значение фитнеса равно потенциальным ходам для каждого столбца.
        В этом случае AI будет случайным образом сбрасывать фишку и терять свою интеллектуальность.
        '''
        return [0] * BOARDWIDTH

    ## Определить цвет фишки противника
    if tile == RED:
        enemyTile = BLACK
    else:
        enemyTile = RED
    potentialMoves = [0] * BOARDWIDTH
    ## Инициализировать список потенциальных ходов, все значения которого равны 0
    for firstMove in range(BOARDWIDTH):
        ## Перебрать каждый столбец и рассматривать любой ход стороны как firstMove
        ## Ход другой стороны рассматривается как counterMove
        ## Здесь наш firstMove относится к ходу AI, а ход противника рассматривается как counterMove
        ## Сделать глубокую копию доски, чтобы предотвратить взаимное влияние между доской и dupeBoard
        dupeBoard = copy.deepcopy(board)
        if not isValidMove(dupeBoard, firstMove):
        ## Если ход размещения черной фишки в столбце, указанном firstMove, недопустим в dupeBoard
            continue
            ## Продолжить с следующим firstMove
        makeMove(dupeBoard, tile, firstMove)
        ## Если это допустимый ход, установить соответствующий цвет ячейки
        if isWinner(dupeBoard, tile):
        ## Если AI выигрывает
            potentialMoves[firstMove] = 1
            ## Выигрышная фишка автоматически получает высокое значение, чтобы показать ее шансы на победу
            ## Чем больше значение, тем выше шансы на победу, и тем ниже шансы противника на победу
            break
            ## Не вмешиваться в расчет других ходов
        else:
            if isBoardFull(dupeBoard):
            ## Если в dupeBoard нет пустых ячеек
                potentialMoves[firstMove] = 0
                ## Невозможно сделать ход
            else:
                for counterMove in range(BOARDWIDTH):
                ## Рассмотреть ход противника
                    dupeBoard2 = copy.deepcopy(dupeBoard)
                    if not isValidMove(dupeBoard2, counterMove):
                        continue
                    makeMove(dupeBoard2, enemyTile, counterMove)
                    if isWinner(dupeBoard2, enemyTile):
                        potentialMoves[firstMove] = -1
                        ## Если игрок выигрывает, значение фитнеса для AI в этом столбце является самым низким
                        break
                    else:
                        ## Рекурсивно вызвать getPotentialMoves
                        results = getPotentialMoves(dupeBoard2, tile, lookAhead - 1)
                        ## Использовать представление с плавающей точкой здесь для более точных результатов
                        ## Это гарантирует, что значения в potentialMoves находятся в диапазоне [-1, 1]
                        potentialMoves[firstMove] += (sum(results)*1.0 / BOARDWIDTH) / BOARDWIDTH
    return potentialMoves
✨ Проверить решение и практиковаться

Операции игрока

Перетаскивайте игровую фишку, определяйте квадрат, в котором находится фишка, проверяйте валидность фишки, вызывайте функцию по падению фишки и завершайте операцию.

def getHumanMove(board, isFirstMove):
    draggingToken = False
    tokenx, tokeny = None, None
    while True:
        ## Используйте pygame.event.get() для обработки всех событий
        for event in pygame.event.get():
            if event.type == QUIT: ## Остановить и выйти
                pygame.quit()
                sys.exit()

            elif event.type == MOUSEBUTTONDOWN and not draggingToken and REDPILERECT.collidepoint(event.pos):
                ## Если тип события - нажатие кнопки мыши, draggingToken - истина, и позиция клика мыши внутри REDPILERECT
                draggingToken = True
                tokenx, tokeny = event.pos

            elif event.type == MOUSEMOTION and draggingToken: ## Если красная фишка перетаскивается
                tokenx, tokeny = event.pos ## Обновить позицию перетаскиваемой фишки

            elif event.type == MOUSEBUTTONUP and draggingToken:
                ## Если кнопка мыши отпущена, и фишка перетаскивается
                ## Если фишка перетащена прямо над игровой доской
                if tokeny < YMARGIN and tokenx > XMARGIN and tokenx < WINDOWWIDTH - XMARGIN:
                    column = int((tokenx - XMARGIN) / SPACESIZE) ## Определить столбец, в который будет падать фишка, на основе x-координаты фишки (0,1...6)
                    if isValidMove(board, column): ## Если ход фишки допустим
                        """
                        Падать в соответствующий пустой квадрат,
                        Эта функция только показывает эффект падения
                        Заполнение квадрата фишкой также можно достичь без этой функции с помощью следующего кода
                        """
                        animateDroppingToken(board, column, RED)

                        ## Установить самый нижний пустой квадрат в пустом столбце в красный цвет
                        board[column][getLowestEmptySpace(board, column)] = RED
                        drawBoard(board) ## Нарисовать красную игровую фишку в выброшенном квадрате
                        pygame.display.update() ## Обновить окно
                        return
                tokenx, tokeny = None, None
                draggingToken = False

        if tokenx!= None and tokeny!= None: ## Если фишка перетаскивается, отобразить перетаскиваемую фишку
            drawBoard(board, {'x':tokenx - int(SPACESIZE / 2), 'y':tokeny - int(SPACESIZE / 2), 'color':RED})
            ## Применить корректировки к x, y координатам, чтобы при перетаскивании курсор мыши всегда находился в центре фишки

        else:
            drawBoard(board) ## Когда ход недействителен, после отпускания кнопки мыши, потому что все значения на доске - это None
            ## При вызове drawBoard выполняемые операции - это отображение двух игровых фишек ниже, что эквивалентно возврату фишки в место, откуда она начинала перетаскиваться

        if isFirstMove:
            DISPLAYSURF.blit(ARROWIMG, ARROWRECT) ## AI ходит первым, отобразить изображение подсказки по операции

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

В приведенном выше коде функция getHumanMove() обрабатывает ход игрока. Функция animateDroppingToken() анимирует падение фишки. Функция getLowestEmptySpace() возвращает самый нижний пустой квадрат в столбце.

✨ Проверить решение и практиковаться

Операции AI

Реализуйте функцию для анимации движения компьютера и падения фишек AI в соответствующие позиции.

def animateComputerMoving(board, column):
    x = BLACKPILERECT.left ## Левая координата черной фишки внизу
    y = BLACKPILERECT.top ## Верхняя координата черной фишки внизу
    speed = 1.0
    while y > (YMARGIN - SPACESIZE): ## Когда y имеет более большую величину, это означает, что фишка находится ниже окна
        y -= int(speed) ## Постепенно уменьшайте y, что означает, что фишка движется вверх
        speed += 0.5 ## Увеличивайте скорость уменьшения y
        drawBoard(board, {'x':x, 'y':y, 'color':BLACK})
        ## y постоянно меняется, непрерывно рисуется черная фишка, создавая эффект непрерывного подъема
        pygame.display.update()
        FPSCLOCK.tick()
    ## Когда фишка поднимается до верха доски
    y = YMARGIN - SPACESIZE ## Сбросьте y, чтобы нижняя часть фишки была выровнена с верхом доски
    speed = 1.0
    while x > (XMARGIN + column * SPACESIZE): ## Когда x больше x-координаты целевого столбца
        x -= int(speed) ## Постепенно уменьшайте x, что означает, что фишка движется влево
        speed += 0.5
        drawBoard(board, {'x':x, 'y':y, 'color':BLACK})
        ## В этом случае координата y остается неизменной, что означает, что фишка движется горизонтально в столбец
        pygame.display.update()
        FPSCLOCK.tick()
    ## Черная фишка приземляется на вычисленное пустое место
    animateDroppingToken(board, column, BLACK)

Выберите наибольшее число из списка возвращаемых potentialMoves в качестве значения фитнеса и случайным образом выберите из тех столбцов с высокими значениями фитнеса в качестве конечной цели движения.

def getComputerMove(board):
    potentialMoves = getPotentialMoves(board, BLACK, DIFFICULTY) ## Потенциальные ходы, список с BOARDWIDTH значениями
               ## Значения в списке связаны с установленным уровнем сложности
    bestMoves = [] ## Создайте пустой список bestMoves
    bestMoveFitness = -1 ## Поскольку минимальное значение в potentialMoves равно -1, оно служит в качестве нижней границы
    print(bestMoveFitness)
    for i in range(len(potentialMoves)):
        if potentialMoves[i] > bestMoveFitness and isValidMove(board, i):
            bestMoveFitness = potentialMoves[i] ## Постепенно обновляйте bestMoves, чтобы каждое значение в bestMoves было наибольшим
            ## при этом гарантируя, что ход допустим.

    for i in range(len(potentialMoves)):
        if potentialMoves[i] == bestMoveFitness and isValidMove(board, i):
            bestMoves.append(i) ## Список всех столбцов, в которые можно переместить фишку. Этот список может быть пустым, содержать
            ## только одно значение или несколько значений.
    print(bestMoves)
    return random.choice(bestMoves) ## Случайным образом выберите один из столбцов, в которые можно переместить фишку, в качестве целевого хода.
✨ Проверить решение и практиковаться

Операция перемещения фишек

Постоянным изменением соответствующих координат фишек достигается эффект анимации падения.

def getLowestEmptySpace(board, column):
    ## Возвращает самый нижний пустой квадрат в столбце
    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
        '''
        Назначает игроку (красный/черный) самый нижний пустой квадрат в столбце.
        Поскольку фишка падает в самый нижний пустой квадрат в столбце,
        она считается цветом этого квадрата.
        '''

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()

В приведенном выше коде функция makeMove() совершает ход на доске. Функция animateDroppingToken() анимирует падение фишки. Функция getLowestEmptySpace() возвращает самый нижний пустой квадрат в столбце.

✨ Проверить решение и практиковаться

Некоторые функции проверки

Проверьте валидность хода фишки, проверьте, остались ли еще пустые клетки на шахматной доске.

def isValidMove(board, column):
    ## Проверьте валидность хода фишки
    if column < 0 or column >= (BOARDWIDTH) or board[column][0]!= EMPTY:
    ## Если столбец меньше 0 или больше BOARDWIDTH, или в столбце нет пустой клетки
        return False
        ## Тогда это недопустимый ход, в противном случае он допустим
    return True


def isBoardFull(board):
    ## Если в сетке нет пустых клеток, верните True
    for x in range(BOARDWIDTH):
        for y in range(BOARDHEIGHT):
            if board[x][y] == EMPTY:
                return False
    return True

В приведенном выше коде функция isValidMove() возвращает True, если ход допустим. Функция isBoardFull() возвращает True, если доска заполнена.

✨ Проверить решение и практиковаться

Определение условий победы

Предоставлены несколько схем для легкого понимания четырех условий победы. Позиции, показанные на схеме, соответствуют экстремальным значениям x и y.

Диаграмма условий победы
def isWinner(board, tile):
    ## Проверьте горизонтальное расположение фишек
    for x in range(BOARDWIDTH - 3): ## x принимает значения 0, 1, 2, 3
        for y in range(BOARDHEIGHT): ## перебираем все строки
            ## Если x = 0, проверьте, все ли четыре первые фишки в y-й строке одного и того же типа. Это можно использовать для обхода всех горизонтальных расположений фишек, соединенных в ряд из четырех. Если любая пара x, y дает истину, можно определить победу
            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

    ## Проверьте вертикальное расположение фишек, аналогично горизонтальному
    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

    ## Проверьте наклон вправо диагональное расположение фишек
    for x in range(BOARDWIDTH - 3): ## x принимает значения 0, 1, 2, 3
        for y in range(3, BOARDHEIGHT): ## потому что при образовании наклонного вправо диагонали из четырех фишек нижняя фишка должна быть не менее на четыре квадрата выше верхней, то есть 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: ## определите, все ли четыре наклонные вправо диагональные фишки одного цвета
                return True

    ## Проверьте наклон влево диагональное расположение фишек, аналогично наклонному вправо диагональному
    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
✨ Проверить решение и практиковаться

Создание главного цикла игры

Наконец, мы создаем главный цикл игры, чтобы держать игру в постоянном движении.

def main():

    ## Пропущено существующий код

    isFirstGame = True ## Инициализируем isFirstGame

    while True: ## Держим игру в постоянном движении
        runGame(isFirstGame)
        isFirstGame = False


def runGame(isFirstGame):
    if isFirstGame:
        ## В начале первой игры
        ## Позволяем AI сделать первый ход, чтобы игроки могли наблюдать, как играется
        turn = COMPUTER
        showHelp = True
    else:
        ## Во второй и последующих играх назначайте ходы случайным образом
        if random.randint(0, 1) == 0:
            turn = COMPUTER
        else:
            turn = HUMAN
        showHelp = False
    mainBoard = getNewBoard() ## Создаем начальную пустую структуру доски
    while True: ## Главный цикл игры
        if turn == HUMAN: ## Если ходит игрок
            getHumanMove(mainBoard, showHelp) ## Вызываем метод для хода игрока, детали см. в методе getHumanMove
            if showHelp:
                ## Если есть изображение подсказки, выключаем подсказку после первого хода AI
                showHelp = False
            if isWinner(mainBoard, RED): ## Если красная фишка (игрок) выигрывает
                winnerImg = HUMANWINNERIMG ## Загружаем изображение победы игрока
                break ## Выйти из цикла
            turn = COMPUTER ## Передаем первый ход AI
        else:
            ## Если ходит AI
            column = getComputerMove(mainBoard) ## Вызываем метод для хода AI, детали см. в методе getComputerMove
            print(column)
            animateComputerMoving(mainBoard, column) ## Перемещаем черную фишку
            makeMove(mainBoard, BLACK, column) ## Устанавливаем самый нижний пустой слот в столбце как черный
            if isWinner(mainBoard, BLACK):
                winnerImg = COMPUTERWINNERIMG
                break
            turn = HUMAN ## Переключаемся на ход игрока

        if isBoardFull(mainBoard):
            ## Если доска заполнена, это ничья
            winnerImg = TIEWINNERIMG
            break
✨ Проверить решение и практиковаться

Запуск и тестирование

Далее мы запустим программу и посмотрим, как она работает.

cd ~/project
python fourinrow.py
демонстрация выполнения программы
✨ Проверить решение и практиковаться

Обзор

На основе алгоритма Монте-Карло этот проект реализовал игру в шахматы между человеком и AI на Python с использованием модуля Pygame. Проект позволил нам познакомиться с основами создания экземпляров и перемещения объектов в Pygame, а также дать предварительное понимание конкретного применения алгоритма Монте-Карло.