Vier gewinnt - Mensch gegen KI

PythonPythonBeginner
Jetzt üben

💡 Dieser Artikel wurde von AI-Assistenten übersetzt. Um die englische Version anzuzeigen, können Sie hier klicken

Einführung

Dieses Projekt ist eine Python-Implementierung des klassischen Vier-Gewinnt-Spiels, bei dem ein Spieler gegen eine KI antreten kann. Es nutzt die Pygame-Bibliothek für die Spieloberfläche und -steuerung. Die Entscheidungsfindung der KI basiert auf dem Monte-Carlo-Baumsuchalgorithmus (Monte Carlo tree search algorithm), und der Schwierigkeitsgrad ist einstellbar, sodass Spieler sich mit schlaueren KI-Gegnern herausfordern können.

Wichtige Konzepte:

  • Verwendung von Pygame für die Spieleentwicklung.
  • Implementierung des Monte-Carlo-Baumsuchalgorithmus für die Entscheidungsfindung der KI.

👀 Vorschau

Connect Four Game

🎯 Aufgaben

In diesem Projekt lernst du:

  • Wie man ein Spiel mit Pygame baut
  • Wie man den Monte-Carlo-Baumsuchalgorithmus für die Entscheidungsfindung der KI implementiert
  • Wie man den Schwierigkeitsgrad der KI anpasst und verbessert
  • Wie man ein lustiges und interaktives Vier-Gewinnt-Spiel für Mensch gegen KI-Kämpfe erstellt

🏆 Errungenschaften

Nach Abschluss dieses Projekts kannst du:

  • Spiele mit Python und Pygame entwickeln
  • Die Prinzipien des Monte-Carlo-Baumsuchalgorithmus verstehen
  • Den Schwierigkeitsgrad eines KI-Gegners einstellen, um ein herausforderndes Spielerlebnis zu schaffen
  • Benutzerinterfaces verbessern, um das Spielerlebnis noch interessanter zu gestalten

Entwicklungsvorbereitung

Das Vier-Gewinnt-Spiel wird auf einem Raster der Größe 7*6 gespielt. Die Spieler setzen abwechselnd ihre Spielsteine von oben in eine Spalte. Der Stein fällt in die unterste leere Position in dieser Spalte. Der Spieler, der vier Steine in einer Reihe (horizontal, vertikal oder diagonal) verbindet, gewinnt das Spiel.

Four In A Row game grid

Erstelle eine Datei namens fourinrow.py im Verzeichnis ~/project, um den Code für dieses Projekt zu speichern. Darüber hinaus müssen wir die Pygame-Bibliothek installieren, um die Spieloberfläche zu implementieren und die Operationen zu unterstützen.

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

Die erforderlichen Bildressourcen für dieses Projekt findest du im Verzeichnis ~/project/images.

Um den Code in diesem Projekt besser zu verstehen, wird empfohlen, ihn parallel zum Code der vollständigen Lösung zu studieren.

✨ Lösung prüfen und üben

Initialisierung der Variablen

Die verwendeten Variablen umfassen die Breite und Höhe des Schachbretts (können geändert werden, um Schachbretter unterschiedlicher Größe zu entwerfen), den Schwierigkeitsgrad, die Größe der Spielsteine und die Einstellung einiger Koordinatenvariablen.

In der Datei fourinrow.py gib den folgenden Code ein:

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

BOARDWIDTH = 7  ## Anzahl der Spalten auf dem Spielbrett
BOARDHEIGHT = 6 ## Anzahl der Reihen auf dem Spielbrett
assert BOARDWIDTH >= 4 and BOARDHEIGHT >= 4, 'Board must be at least 4x4.'

## Die Python-Assert-Anweisung wird verwendet, um zu deklarieren, dass der gegebene boolesche Ausdruck wahr sein muss.
## Wenn der Ausdruck falsch ist, wird eine Ausnahme ausgelöst.

DIFFICULTY = 2 ## Schwierigkeitsgrad, Anzahl der Züge, die der Computer betrachten kann
               ## Hier bedeutet 2, dass 7 mögliche Züge des Gegners und wie darauf reagiert werden soll, berücksichtigt werden

SPACESIZE = 50 ## Größe der Spielsteine

FPS = 30 ## Bildwiederholrate des Bildschirms, 30/s
WINDOWWIDTH = 640  ## Breite des Spielfensters in Pixeln
WINDOWHEIGHT = 480 ## Höhe des Spielfensters in Pixeln

XMARGIN = int((WINDOWWIDTH - BOARDWIDTH * SPACESIZE) / 2)  ## X-Koordinate der linken Kante des Rasters
YMARGIN = int((WINDOWHEIGHT - BOARDHEIGHT * SPACESIZE) / 2) ## Y-Koordinate der oberen Kante des Rasters
BRIGHTBLUE = (0, 50, 255) ## Blaue Farbe
WHITE = (255, 255, 255) ## Weiße Farbe

BGCOLOR = BRIGHTBLUE
TEXTCOLOR = WHITE

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

Zusätzlich müssen wir auch einige globale Pygame-Variablen definieren. Diese globalen Variablen werden später in verschiedenen Modulen mehrmals aufgerufen. Viele von ihnen sind Variablen, die geladene Bilder speichern, daher ist die Vorbereitungsarbeit etwas langwierig, bitte habe Geduld.

## Initialisiere Pygame-Module
pygame.init()

## Erstelle ein Clock-Objekt
FPSCLOCK = pygame.time.Clock()

## Erstelle das Spielfenster
DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT))

## Setze den Titel des Spielfensters
pygame.display.set_caption(u'four in row')

## Rect(left, top, width, height) wird verwendet, um Position und Größe zu definieren
REDPILERECT = pygame.Rect(int(SPACESIZE / 2), WINDOWHEIGHT - int(3 * SPACESIZE / 2), SPACESIZE, SPACESIZE)

## Erstelle die unteren linken und rechten Spielsteine im Fenster
BLACKPILERECT = pygame.Rect(WINDOWWIDTH - int(3 * SPACESIZE / 2), WINDOWHEIGHT - int(3 * SPACESIZE / 2), SPACESIZE, SPACESIZE)

## Lade das Bild des roten Spielsteins
REDTOKENIMG = pygame.image.load('images/4rowred.png')

## Skaliere das Bild des roten Spielsteins auf SPACESIZE
REDTOKENIMG = pygame.transform.smoothscale(REDTOKENIMG, (SPACESIZE, SPACESIZE))

## Lade das Bild des schwarzen Spielsteins
BLACKTOKENIMG = pygame.image.load('images/4rowblack.png')

## Skaliere das Bild des schwarzen Spielsteins auf SPACESIZE
BLACKTOKENIMG = pygame.transform.smoothscale(BLACKTOKENIMG, (SPACESIZE, SPACESIZE))

## Lade das Bild des Schachbretts
BOARDIMG = pygame.image.load('images/4rowboard.png')

## Skaliere das Bild des Schachbretts auf SPACESIZE
BOARDIMG = pygame.transform.smoothscale(BOARDIMG, (SPACESIZE, SPACESIZE))

## Lade das Bild des menschlichen Siegers
HUMANWINNERIMG = pygame.image.load('images/4rowhumanwinner.png')

## Lade das Bild des KI-Siegers
COMPUTERWINNERIMG = pygame.image.load('images/4rowcomputerwinner.png')

## Lade das Bild eines Unentschiedens
TIEWINNERIMG = pygame.image.load('images/4rowtie.png')

## Gib ein Rect-Objekt zurück
WINNERRECT = HUMANWINNERIMG.get_rect()

## Zentriere das Siegerbild im Spielfenster
WINNERRECT.center = (int(WINDOWWIDTH / 2), int(WINDOWHEIGHT / 2))

## Lade das Pfeilbild für Benutzeranweisungen
ARROWIMG = pygame.image.load('images/4rowarrow.png')

## Gib ein Rect-Objekt zurück
ARROWRECT = ARROWIMG.get_rect()

## Setze die linke Position des Pfeilbilds
ARROWRECT.left = REDPILERECT.right + 10

## Ausrichte das Pfeilbild vertikal mit dem darunter liegenden roten Spielstein
ARROWRECT.centery = REDPILERECT.centery

Um den Code in diesem Projekt besser zu verstehen, wird empfohlen, ihn parallel zum Code der vollständigen Lösung zu studieren.

✨ Lösung prüfen und üben

Brett-Design

Zunächst wird die zweidimensionale Liste, die das Brett repräsentiert, geleert. Anschließend werden die entsprechenden Positionen auf dem Brett basierend auf den Zügen des Spielers und der KI mit Farben versehen.

def drawBoard(board, extraToken=None):
    ## DISPLAYSURF ist unsere Schnittstelle, die im Variablen-Initialisierungsmodul definiert ist.
    DISPLAYSURF.fill(BGCOLOR) ## Fülle den Hintergrund des Spielfensters mit blauer Farbe.
    spaceRect = pygame.Rect(0, 0, SPACESIZE, SPACESIZE) ## Erstelle eine Rect-Instanz.
    for x in range(BOARDWIDTH):
        ## Bestimme die Koordinaten der oberen linken Ecke jeder Zelle in jeder Reihe jeder Spalte.
        for y in range(BOARDHEIGHT):
            spaceRect.topleft = (XMARGIN + (x * SPACESIZE), YMARGIN + (y * SPACESIZE))

            ## Wenn x = 0 und y = 0, handelt es sich um die erste Zelle in der ersten Reihe der ersten Spalte.
            if board[x][y] == RED: ## Wenn der Zellwert rot ist,
                ## zeichne einen roten Spielstein im Spielfenster innerhalb von spaceRect.
                DISPLAYSURF.blit(REDTOKENIMG, spaceRect)
            elif board[x][y] == BLACK: ## Andernfalls zeichne einen schwarzen Spielstein.
                DISPLAYSURF.blit(BLACKTOKENIMG, spaceRect)

    ## extraToken ist eine Variable, die Positions- und Farbinformationen enthält.
    ## Sie wird verwendet, um einen bestimmten Spielstein anzuzeigen.
    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))

    ## Zeichne die Spielsteinfelder.
    for x in range(BOARDWIDTH):
        for y in range(BOARDHEIGHT):
            spaceRect.topleft = (XMARGIN + (x * SPACESIZE), YMARGIN + (y * SPACESIZE))
            DISPLAYSURF.blit(BOARDIMG, spaceRect)

    ## Zeichne die Spielsteine in der unteren linken und rechten Ecke des Spielfensters.
    DISPLAYSURF.blit(REDTOKENIMG, REDPILERECT) ## Linker roter Spielstein.
    DISPLAYSURF.blit(BLACKTOKENIMG, BLACKPILERECT) ## Rechter schwarzer Spielstein.


def getNewBoard():
    board = []
    for x in range(BOARDWIDTH):
        board.append([EMPTY] * BOARDHEIGHT)
    return board ## Gib die Brettliste mit BOARDHEIGHT Anzahl von None-Werten zurück.

Im obigen Code zeichnet die Funktion drawBoard() das Brett und die Spielsteine darauf. Die Funktion getNewBoard() gibt eine neue Brett-Datenstruktur zurück.

✨ Lösung prüfen und üben

KI-Algorithmus für optimale Züge

Kurze Erklärung der Idee der Monte-Carlo-Baumsuche (Monte Carlo tree search):

Verwende die eindimensionale Monte-Carlo-Methode, um das Go-Spielbrett zu bewerten. Genauer gesagt, wenn eine bestimmte Brettsituation gegeben ist, wählt das Programm zufällig einen Punkt aus allen verfügbaren Punkten in der aktuellen Situation aus und setzt einen Spielstein darauf. Dieser Prozess der zufälligen Auswahl verfügbarer Punkte (Rollpunkt) wird wiederholt, bis keine Seite mehr einen verfügbaren Punkt hat (das Spiel endet). Anschließend wird der resultierende Gewinn oder Verlust dieses Endzustands als Grundlage für die Bewertung der aktuellen Situation zurückgeführt.

In diesem Projekt wählt die KI kontinuierlich verschiedene Spalten aus und bewertet die Ergebnisse des Sieges beider Seiten. Die KI wird letztendlich eine Strategie mit einer höheren Bewertung wählen.

Bevor du dir die folgenden Bilder und Texte ansiehst, schau dir zuerst den Code am Ende an und beziehe dich dann auf die entsprechenden Erklärungen.

Beobachte die Konfrontation zwischen KI und Spieler in der folgenden Abbildung:

AI player move analysis

Einige Variablen im Projekt können den Prozess der Spielsteinoperationen der KI intuitiv widerspiegeln:

PotentialMoves: Gibt eine Liste zurück, die die Wahrscheinlichkeit des Sieges der KI darstellt, wenn ein Spielstein in eine beliebige Spalte in der Liste gesetzt wird. Die Werte sind Zufallszahlen zwischen -1 und 1. Wenn der Wert negativ ist, bedeutet dies, dass der Spieler in den nächsten zwei Zügen gewinnen könnte, und je kleiner der Wert, desto größer die Wahrscheinlichkeit, dass der Spieler gewinnt. Wenn der Wert 0 ist, bedeutet dies, dass der Spieler nicht gewinnen wird und die KI auch nicht gewinnen wird. Wenn der Wert 1 ist, bedeutet dies, dass die KI gewinnen kann.

bestMoveFitness: Die Fitness ist der maximale Wert, der aus PotentialMoves ausgewählt wird.

bestMoves: Wenn es mehrere maximale Werte in PotentialMoves gibt, bedeutet dies, dass die Chancen des Spielers, zu gewinnen, am geringsten sind, wenn die KI den Spielstein in die Spalten setzt, in denen sich diese Werte befinden. Daher werden diese Spalten der Liste bestMoves hinzugefügt.

column: Wenn es mehrere Werte in bestMoves gibt, wird zufällig eine Spalte aus bestMoves als Zug der KI ausgewählt. Wenn es nur einen Wert gibt, ist column dieser eindeutige Wert.

In diesem Projekt können wir durch das Ausgeben dieser bestMoveFitness, bestMoves, column und potentialMoves die Parameter jedes Schritts der KI in der obigen Abbildung ableiten.

✨ Lösung prüfen und üben

KI-Züge

Schritt potentialMoves bestMoveFitness bestMoves Spalte
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

Durch die Untersuchung der Auswahl der KI im dritten Schritt können wir das Algorithmus besser verstehen:

Die folgende Abbildung zeigt einige Züge der KI, die möglichen Entscheidungen des Spielers, wenn die KI einen Stein in die erste Spalte setzt, und die Auswirkungen des nächsten Zuges der KI auf die Gewinnchancen des Spielers. Durch diesen Such- und Iterationsprozess kann die KI die Gewinnsituationen des Gegners und ihrer eigenen in den nächsten zwei Schritten bestimmen und entsprechend Entscheidungen treffen.

AI move impact flowchart

Die folgende Abbildung ist ein Flussdiagramm zur Berechnung des Fitnesswerts für die KI. In diesem Projekt beträgt der Schwierigkeitskoeffizient 2, und wir müssen 7^4 = 2041 Fälle berücksichtigen:

AI fitness calculation flowchart

Aus dem obigen Flussdiagramm lässt sich leicht erkennen, dass, wenn die KI ihren ersten Stein in die Spalte 0, 1, 2, 4, 5 oder 6 setzt, der Spieler immer die verbleibenden zwei Steine in die Spalte 3 setzen kann und gewinnt. Zur einfacheren Darstellung verwenden wir eine Sequenz, um verschiedene Kombinationen darzustellen, wobei das erste Element den ersten Zug der KI, die zweite Zahl die Reaktion des Spielers und die dritte Zahl die Reaktion der KI darstellt. "X" steht für jeden gültigen Zug. Daher ist [0,0,x] = 0, und man kann ableiten, dass, wenn die Sequenz [0,x<>3,x] ist, der Spieler nicht gewinnen kann. Nur wenn der zweite Stein des Spielers in der Spalte 3 ist und der zweite Zug der KI nicht in der Spalte 3 ist, kann die KI gewinnen. Daher ist [0,x=3,x<>3] = -1, und es gibt 6 solche Fälle. Das Endergebnis ist (0+0+...(43 mal)-1*6)/7/7 = -0.12.

Nach demselben Prinzip sind die Ergebnisse für die anderen vier Fälle alle -0.12. Wenn der erste Zug der KI in der Spalte 3 ist, kann der Spieler nicht gewinnen, und die KI kann auch nicht gewinnen, also ist der Wert 0. Die KI wählt den Zug mit dem höchsten Fitnesswert, was bedeutet, dass sie ihren Stein in die Spalte 3 setzen wird.

Die gleiche Analyse kann auf die nachfolgenden Züge der KI angewendet werden. Zusammenfassend lässt sich sagen, dass je höher die Wahrscheinlichkeit ist, dass der Spieler nach dem Zug der KI gewinnt, desto niedriger der Fitnesswert für die KI ist, und die KI wird den Zug mit einem höheren Fitnesswert wählen, um zu verhindern, dass der Spieler gewinnt. Natürlich wird die KI, wenn sie selbst gewinnen kann, den Zug priorisieren, der zu ihrem eigenen Sieg führt.

def getPotentialMoves(board, tile, lookAhead):
    if lookAhead == 0 or isBoardFull(board):
        '''
        Wenn der Schwierigkeitskoeffizient 0 ist oder das Brett voll ist,
        gib eine Liste zurück, bei der alle Werte auf 0 gesetzt sind. Dies bedeutet, dass
        der Fitnesswert gleich den potenziellen Zügen für jede Spalte ist.
        In diesem Fall wird die KI den Stein zufällig fallen lassen und ihre Intelligenz verlieren.
        '''
        return [0] * BOARDWIDTH

    ## Bestimme die Farbe des Gegnersteins
    if tile == RED:
        enemyTile = BLACK
    else:
        enemyTile = RED
    potentialMoves = [0] * BOARDWIDTH
    ## Initialisiere eine Liste potenzieller Züge, bei der alle Werte auf 0 gesetzt sind
    for firstMove in range(BOARDWIDTH):
        ## Iteriere über jede Spalte und betrachte jeden Zug von beiden Seiten als firstMove
        ## Der Zug der anderen Seite wird dann als counterMove betrachtet
        ## Hier bezieht sich unser firstMove auf den Zug der KI, und der Zug des Gegners wird als counterMove betrachtet
        ## Mache eine tiefe Kopie des Bretts, um gegenseitige Einflüsse zwischen board und dupeBoard zu vermeiden
        dupeBoard = copy.deepcopy(board)
        if not isValidMove(dupeBoard, firstMove):
        ## Wenn der Zug, einen schwarzen Stein in die von firstMove angegebene Spalte zu setzen, in dupeBoard ungültig ist
            continue
            ## Gehe zum nächsten firstMove über
        makeMove(dupeBoard, tile, firstMove)
        ## Wenn es ein gültiger Zug ist, setze die entsprechende Rasterfarbe
        if isWinner(dupeBoard, tile):
        ## Wenn die KI gewinnt
            potentialMoves[firstMove] = 1
            ## Der gewinnende Stein erhält automatisch einen hohen Wert, um seine Gewinnchancen anzuzeigen
            ## Je größer der Wert, desto höher die Gewinnchancen und desto niedriger die Gewinnchancen des Gegners
            break
            ## Störe nicht die Berechnung anderer Züge
        else:
            if isBoardFull(dupeBoard):
            ## Wenn es keine leeren Raster in dupeBoard gibt
                potentialMoves[firstMove] = 0
                ## Es ist nicht möglich, einen Zug zu machen
            else:
                for counterMove in range(BOARDWIDTH):
                ## Betrachte den Zug des Gegners
                    dupeBoard2 = copy.deepcopy(dupeBoard)
                    if not isValidMove(dupeBoard2, counterMove):
                        continue
                    makeMove(dupeBoard2, enemyTile, counterMove)
                    if isWinner(dupeBoard2, enemyTile):
                        potentialMoves[firstMove] = -1
                        ## Wenn der Spieler gewinnt, ist der Fitnesswert für die KI in dieser Spalte am niedrigsten
                        break
                    else:
                        ## Rufe getPotentialMoves rekursiv auf
                        results = getPotentialMoves(dupeBoard2, tile, lookAhead - 1)
                        ## Verwende hier eine Fließkommadarstellung für genauere Ergebnisse
                        ## Dies stellt sicher, dass die Werte in potentialMoves im Bereich [-1, 1] liegen
                        potentialMoves[firstMove] += (sum(results)*1.0 / BOARDWIDTH) / BOARDWIDTH
    return potentialMoves
✨ Lösung prüfen und üben

Spieleraktion

Ziehe den Spielstein, bestimme das Feld, in dem sich der Spielstein befindet, validiere den Spielstein, rufe die Funktion zum Fallenlassen des Spielsteins auf und beende die Aktion.

def getHumanMove(board, isFirstMove):
    draggingToken = False
    tokenx, tokeny = None, None
    while True:
        ## Verwende pygame.event.get(), um alle Ereignisse zu verarbeiten
        for event in pygame.event.get():
            if event.type == QUIT: ## Stoppe und beende
                pygame.quit()
                sys.exit()

            elif event.type == MOUSEBUTTONDOWN and not draggingToken and REDPILERECT.collidepoint(event.pos):
                ## Wenn der Ereignistyp Mausklick ist, draggingToken ist True und die Mausklickposition innerhalb von REDPILERECT liegt
                draggingToken = True
                tokenx, tokeny = event.pos

            elif event.type == MOUSEMOTION and draggingToken: ## Wenn der rote Spielstein gezogen wird
                tokenx, tokeny = event.pos ## Aktualisiere die Position des gezogenen Spielsteins

            elif event.type == MOUSEBUTTONUP and draggingToken:
                ## Wenn die Maustaste losgelassen wird und der Spielstein gezogen wird
                ## Wenn der Spielstein direkt über dem Brett gezogen wird
                if tokeny < YMARGIN and tokenx > XMARGIN and tokenx < WINDOWWIDTH - XMARGIN:
                    column = int((tokenx - XMARGIN) / SPACESIZE) ## Bestimme die Spalte, in die der Spielstein fallen wird, basierend auf der x-Koordinate des Spielsteins (0,1...6)
                    if isValidMove(board, column): ## Wenn der Spielsteinzug gültig ist
                        """
                        Lasse ihn in das entsprechende leere Feld fallen,
                        Diese Funktion zeigt nur den Fall-Effekt an
                        Das Füllen des Felds mit dem Spielstein kann auch ohne diese Funktion mit folgendem Code erreicht werden
                        """
                        animateDroppingToken(board, column, RED)

                        ## Setze das unterste Feld in der leeren Spalte auf rot
                        board[column][getLowestEmptySpace(board, column)] = RED
                        drawBoard(board) ## Zeichne den roten Spielstein in das Feld, in das er gefallen ist
                        pygame.display.update() ## Fensteraktualisierung
                        return
                tokenx, tokeny = None, None
                draggingToken = False

        if tokenx!= None and tokeny!= None: ## Wenn ein Spielstein gezogen wird, zeige den gezogenen Spielstein an
            drawBoard(board, {'x':tokenx - int(SPACESIZE / 2), 'y':tokeny - int(SPACESIZE / 2), 'color':RED})
            ## Passe die x-, y-Koordinaten an, damit die Maus während des Ziehens immer in der Mittelposition des Spielsteins ist

        else:
            drawBoard(board) ## Wenn es ein ungültiger Zug ist, nachdem die Maustaste losgelassen wurde, da alle Werte im Brett none sind
            ## Beim Aufruf von drawBoard werden die Operationen ausgeführt, um die beiden unteren Spielsteine anzuzeigen, was gleichbedeutend damit ist, den Spielstein an den Ort zurückzusetzen, von dem aus er gezogen wurde

        if isFirstMove:
            DISPLAYSURF.blit(ARROWIMG, ARROWRECT) ## Die KI beginnt, zeige das Hinweisbild für die Aktion an

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

Im obigen Code behandelt die Funktion getHumanMove() den Zug des Spielers. Die Funktion animateDroppingToken() animiert das Fallenlassen des Spielsteins. Die Funktion getLowestEmptySpace() gibt den untersten leeren Platz in einer Spalte zurück.

✨ Lösung prüfen und üben

KI-Aktionen

Implementiere die Funktion, um die Bewegung des Computers und das Platzieren der KI-Spielsteine an den jeweiligen Positionen zu animieren.

def animateComputerMoving(board, column):
    x = BLACKPILERECT.left ## Die linke Koordinate des schwarzen Spielsteins unten
    y = BLACKPILERECT.top ## Die obere Koordinate des schwarzen Spielsteins unten
    speed = 1.0
    while y > (YMARGIN - SPACESIZE): ## Wenn y einen größeren Wert hat, was bedeutet, dass der Spielstein unterhalb des Fensters ist
        y -= int(speed) ## Verringere y kontinuierlich, was bedeutet, dass der Spielstein nach oben bewegt wird
        speed += 0.5 ## Erhöhe die Geschwindigkeit, mit der y verringert wird
        drawBoard(board, {'x':x, 'y':y, 'color':BLACK})
        ## y ändert sich ständig, zeichne kontinuierlich den schwarzen Spielstein, um einen Effekt des kontinuierlichen Aufstiegs zu erzeugen
        pygame.display.update()
        FPSCLOCK.tick()
    ## Wenn der Spielstein an die Spitze des Bretts aufgestiegen ist
    y = YMARGIN - SPACESIZE ## Setze y zurück, damit die Unterkante des Spielsteins mit der Oberkante des Bretts fluchtet
    speed = 1.0
    while x > (XMARGIN + column * SPACESIZE): ## Wenn x größer als die x-Koordinate der gewünschten Spalte ist
        x -= int(speed) ## Verringere x kontinuierlich, was bedeutet, dass der Spielstein nach links bewegt wird
        speed += 0.5
        drawBoard(board, {'x':x, 'y':y, 'color':BLACK})
        ## An diesem Punkt bleibt die y-Koordinate unverändert, was bedeutet, dass der Spielstein horizontal zur Spalte bewegt wird
        pygame.display.update()
        FPSCLOCK.tick()
    ## Der schwarze Spielstein landet auf dem berechneten leeren Platz
    animateDroppingToken(board, column, BLACK)

Wähle die höchste Zahl aus der Liste der zurückgegebenen potentialMoves als Fitnesswert aus und wähle zufällig aus den Spalten mit hohen Fitnesswerten als endgültiges Ziel für den Zug aus.

def getComputerMove(board):
    potentialMoves = getPotentialMoves(board, BLACK, DIFFICULTY) ## Potenzielle Züge, eine Liste mit BOARDWIDTH Werten
               ## Die Werte in der Liste hängen von der eingestellten Schwierigkeitsstufe ab
    bestMoves = [] ## Erstelle eine leere bestMoves-Liste
    bestMoveFitness = -1 ## Da der Mindestwert in potentialMoves -1 ist, dient er als untere Grenze
    print(bestMoveFitness)
    for i in range(len(potentialMoves)):
        if potentialMoves[i] > bestMoveFitness and isValidMove(board, i):
            bestMoveFitness = potentialMoves[i] ## Aktualisiere bestMoves kontinuierlich, damit jeder Wert in bestMoves der größte ist
            ## und gleichzeitig sicherstellen, dass der Zug gültig ist.

    for i in range(len(potentialMoves)):
        if potentialMoves[i] == bestMoveFitness and isValidMove(board, i):
            bestMoves.append(i) ## Liste alle Spalten auf, in die der Spielstein bewegt werden kann. Diese Liste kann leer sein,
            ## nur einen Wert oder mehrere Werte enthalten.
    print(bestMoves)
    return random.choice(bestMoves) ## Wähle zufällig eine der Spalten aus, in die der Spielstein bewegt werden kann, als Zielzug aus.
✨ Lösung prüfen und üben

Spielsteinbewegungsoperation

Durch kontinuierliche Änderung der entsprechenden Koordinaten der Spielsteine wird der Animations-Effekt des Fallens erreicht.

def getLowestEmptySpace(board, column):
    ## Gib den untersten leeren Platz in einer Spalte zurück
    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
        '''
        Weise den Spieler (rot/schwarz) dem untersten leeren Platz in der Spalte zu.
        Da der Spielstein in den untersten leeren Platz einer Spalte fallen gelassen wird,
        wird dieser als die Farbe dieses Platzes betrachtet.
        '''

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

Im obigen Code führt die Funktion makeMove() einen Zug auf dem Brett aus. Die Funktion animateDroppingToken() animiert das Fallenlassen des Spielsteins. Die Funktion getLowestEmptySpace() gibt den untersten leeren Platz in einer Spalte zurück.

✨ Lösung prüfen und üben

Einige Prüfungsfunktionen

Prüfe die Gültigkeit eines Spielsteinzugs und ob es noch leere Felder auf dem Schachbrett gibt.

def isValidMove(board, column):
    ## Prüfe die Gültigkeit eines Spielsteinzugs
    if column < 0 or column >= (BOARDWIDTH) or board[column][0]!= EMPTY:
    ## Wenn die Spalte kleiner als 0 oder größer als BOARDWIDTH ist oder es kein leeres Feld in der Spalte gibt
        return False
        ## Dann ist es ein ungültiger Zug, sonst ist er gültig
    return True


def isBoardFull(board):
    ## Wenn es keine leeren Felder im Raster gibt, gib True zurück
    for x in range(BOARDWIDTH):
        for y in range(BOARDHEIGHT):
            if board[x][y] == EMPTY:
                return False
    return True

Im obigen Code gibt die Funktion isValidMove() True zurück, wenn der Zug gültig ist. Die Funktion isBoardFull() gibt True zurück, wenn das Brett voll ist.

✨ Lösung prüfen und üben

Gewinnbedingungsüberprüfung

Es werden mehrere Diagramme bereitgestellt, um die vier Gewinnbedingungen leicht verständlich zu machen. Die in den Diagrammen gezeigten Positionen entsprechen den Extremwerten von x und y.

Gewinnbedingungen-Diagramm
def isWinner(board, tile):
    ## Überprüfe die horizontale Anordnung der Spielsteine
    for x in range(BOARDWIDTH - 3): ## x nimmt die Werte 0, 1, 2, 3 an
        for y in range(BOARDHEIGHT): ## iteriere über alle Zeilen
            ## Wenn x = 0, überprüfe, ob die ersten vier Spielsteine in der y-ten Zeile alle der gleichen Art sind. Dies kann verwendet werden, um alle horizontalen Anordnungen von vier in einer Reihe zu durchlaufen. Wenn irgendein x, y wahr ist, kann dies als Gewinn festgestellt werden
            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

    ## Überprüfe die vertikale Anordnung der Spielsteine, ähnlich wie bei der horizontalen Anordnung
    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

    ## Überprüfe die links geneigte diagonale Anordnung der Spielsteine
    for x in range(BOARDWIDTH - 3): ## x nimmt die Werte 0, 1, 2, 3 an
        for y in range(3, BOARDHEIGHT): ## weil beim Bilden einer links geneigten Diagonalen von vier in einer Reihe der unterste Spielstein mindestens vier Felder von der Spitze entfernt sein muss, d.h. 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: ## überprüfe, ob die vier links geneigten diagonalen Spielsteine die gleiche Farbe haben
                return True

    ## Überprüfe die rechts geneigte diagonale Anordnung der Spielsteine, ähnlich wie bei der links geneigten Diagonalen
    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
✨ Lösung prüfen und üben

Erstellen der Spielhauptschleife

Schließlich erstellen wir die Spielhauptschleife, um das Spiel kontinuierlich laufen zu lassen.

def main():

    ## Bestehender Code ausgelassen

    isFirstGame = True ## Initialisiere isFirstGame

    while True: ## Lasse das Spiel kontinuierlich laufen
        runGame(isFirstGame)
        isFirstGame = False


def runGame(isFirstGame):
    if isFirstGame:
        ## Zu Beginn des ersten Spiels
        ## Lasse die KI den ersten Zug machen, damit die Spieler sehen können, wie das Spiel gespielt wird
        turn = COMPUTER
        showHelp = True
    else:
        ## Ab dem zweiten Spiel werden die Züge zufällig zugewiesen
        if random.randint(0, 1) == 0:
            turn = COMPUTER
        else:
            turn = HUMAN
        showHelp = False
    mainBoard = getNewBoard() ## Setze die anfängliche leere Brettstruktur auf
    while True: ## Spielhauptschleife
        if turn == HUMAN: ## Wenn es der Zug des Spielers ist

            getHumanMove(mainBoard, showHelp) ## Rufe die Methode für den Zug des Spielers auf, Details siehe Methode getHumanMove
            if showHelp:
                ## Wenn es ein Hinweissymbol gibt, schalte den Hinweis aus, nachdem die KI den ersten Zug gemacht hat
                showHelp = False
            if isWinner(mainBoard, RED): ## Wenn der rote Spielstein (Spieler) gewinnt
                winnerImg = HUMANWINNERIMG ## Lade das Bild für den Sieger des Spielers
                break ## Verlasse die Schleife
            turn = COMPUTER ## Übergebe den ersten Zug an die KI
        else:
            ## Wenn es der Zug der KI ist
            column = getComputerMove(mainBoard) ## Rufe die Methode für den Zug der KI auf, Details siehe Methode getComputerMove
            print(column)
            animateComputerMoving(mainBoard, column) ## Bewege den schwarzen Spielstein
            makeMove(mainBoard, BLACK, column) ## Setze den untersten leeren Platz in der Spalte auf schwarz
            if isWinner(mainBoard, BLACK):
                winnerImg = COMPUTERWINNERIMG
                break
            turn = HUMAN ## Wechsle zum Zug des Spielers

        if isBoardFull(mainBoard):
            ## Wenn das Brett voll ist, ist es ein Unentschieden
            winnerImg = TIEWINNERIMG
            break
✨ Lösung prüfen und üben

Ausführen und Testen

Als Nächstes werden wir das Programm ausführen und sehen, wie es funktioniert.

cd ~/project
python fourinrow.py
Programmausführungsdemonstration
✨ Lösung prüfen und üben

Zusammenfassung

Basierend auf dem Monte-Carlo-Algorithmus wurde in diesem Projekt ein Mensch gegen KI-Schachspiel mit Python und dem Pygame-Modul implementiert. Das Projekt ermöglichte es uns, uns mit den Grundlagen der Instanzierung und Bewegung von Objekten in Pygame vertraut zu machen und gab uns einen ersten Einblick in die konkrete Anwendung des Monte-Carlo-Algorithmus.