4 Ecken sind gut, aber 6 ist besser: sechseckiges Schach in der Konsole und mit einem Bot

Hallo!



Wir sind in unserem ersten Studienjahr in Angewandter Mathematik und Informatik an der HSE in St. Petersburg. Während wir an einem Semesterteamprojekt in C ++ arbeiteten, beschlossen wir, eine Computerversion des Intellektors mit einem Bot zu schreiben - ein Schachspiel auf einem sechseckigen Brett mit speziellen Figuren.



In diesem Artikel werden wir darüber sprechen, wie die Entwicklung des Spiels verlaufen ist, wie man das sechseckige Brett zähmt, wie man auf der Kommandozeile zeichnet und wie wir einen Bot gemacht haben, den wir fast nicht besiegen können.







Unser Team besteht aus 3 Personen: Yura Khudyakov, Valera Golovin, Misha Savrasov. Für jeden von uns ist dies das erste Teamprojekt. Während der Arbeit haben wir nicht nur über die Profis geschrieben, sondern auch gelernt, in einem Team zu arbeiten und Versionskontrollsysteme zu verwenden (in unserem Fall git). Das Projekt weist eine Reihe von Kuriositäten auf, insbesondere Fahrräder - es gibt gute vorgefertigte Lösungen, die verwendet werden könnten. Das Ziel unseres Projekts war es jedoch, Erfahrungen zu sammeln. Deshalb haben wir selbst viel erfunden und umgesetzt.



Was ist ein Intellektueller?



Zunächst lohnt es sich zu erklären, welche Art von Spiel wir geschrieben haben.



Das Spiel "Intellector" ist kürzlich erschienen und erfreut sich immer größerer Beliebtheit. Die erste offene Meisterschaft fand in diesem Jahr in St. Petersburg statt .





Der Intellektor unterscheidet sich vom Schach in der Struktur des Feldes, den Regeln und einer Reihe von Figuren. Viele Elemente sind jedoch ähnlich: Zum Beispiel gibt es im Spiel eine Progressor-Figur, die die Rolle eines Bauern spielt. Sie geht nur vorwärts und kann sich in eine andere Figur verwandeln, wenn sie die äußerste Reihe erreicht.



Der König hier ist eine Figur namens Intellektor. Das Ziel des Spiels ist es, dieses Stück zu kürzen, nicht schachmatt zu setzen (obwohl dies fast dasselbe ist).



Unterschiede in der Spielmechanik ergeben sich aus den Besonderheiten des Feldes. Das Intellektfeld ist symmetrisch, was es mit seiner Königs- und Königinnenseite deutlich vom Schach unterscheidet.



Um diesen Artikel zu verstehen, sind Kenntnisse der Regeln und der Fähigkeit zum Spielen nicht erforderlich.



Allgemeine Architektur



Was wollen wir in unserer Bewerbung?



Damit das Spiel funktioniert, müssen Sie seine Hauptkomponente implementieren: die Logik des Spiels. Es enthält ein Board-Modell und Bewegungsregeln. Darüber hinaus lohnt es sich, die Historie der Verschiebungen beizubehalten und das Rückgängigmachen / Wiederherstellen zu implementieren.



Das Brett muss angezeigt werden und dem Benutzer das Spielen ermöglichen. Dies geschieht durch die grafische Komponente des Spiels - die Benutzeroberfläche. Eine benutzerfreundliche Oberfläche sollte Menüs und Einstellungen haben.



Schließlich braucht man einen Gegner zum Spielen. Wir haben beschlossen, einen Bot für diese Zwecke zu erstellen, damit der Spieler mit dem Computer konkurrieren kann. In diesem Fall sollte die Komplexität des Bots einstellbar sein.



Bewerbungsplan:



  1. Spielelogik
    • Sechseckiges Plattenmodell

      Wird als zweidimensionales Array hexagonaler Zellen gespeichert
    • Regeln für das Bewegen von Steinen

      Überprüfen der Zulässigkeit eines Zuges, Abrufen aller verfügbaren Züge für ein Stück für einen Spieler
    • Bewegungsverlauf

      rückgängig machen und einen Zug wiederholen
  2. Schnittstelle

    Geplant 2 Schnittstellen: ncurses und Qt. Im Projekt ist nur ncurses implementiert. Weitere Informationen finden Sie im Abschnitt Schnittstelle.
    • Anzeigen eines Feldes Rendern

      und Aktualisieren eines Felds in der Konsole
    • Bewegen Sie den Cursor mit den Tastaturtasten und spielen Sie ohne Maus
    • Anzeigen des Textverlaufs von Zügen
    • Hauptmenü anzeigen
  3. Der Bot
    • Zufälliger Bot
    • Gieriger Bot
    • Alpha-Beta-Bot

      Optimiert für alle Bewegungen


Wie wird es gemacht?



Eine sehr vereinfachte Anwendungsarchitektur wird in diesem Diagramm beschrieben:





Der Spielbereich ist für die gesamte Spiellogik verantwortlich und speichert das Brett.



Wenn das Spiel mit dem Computer eingeschaltet ist, interagiert das Spiel mit dem Bot vom Bot



Controller, nimmt Daten über das Spiel aus dem Spiel und überträgt sie zur Anzeige an die Benutzeroberfläche an die Benutzeroberfläche. Die Benutzeroberfläche gibt wiederum das Ergebnis der Benutzerinteraktion über den Controller an das Spiel zurück.



Die Zwischenverbindung in Form eines Controllers ist praktisch, wenn mehrere Schnittstellen vorhanden sind (ursprünglich wollten wir zwei Schnittstellen erstellen: Konsole und Grafik). Alle kommunizieren über den Controller auf einheitliche Weise mit dem Spiel, anstatt dass jeder es anders macht.



Technische Details



Spielmodell



Sechseckiges Brett



Betrachten Sie den Spielabschnitt aus dem obigen Diagramm. Es muss das Brett speichern und die gesamte Logik im Spiel verarbeiten.



Zum besseren Verständnis können Sie den Artikel lesen, aus dem wir diese Idee übernommen haben.



Wir werden die gesamte Tafel in einem zweidimensionalen Array von Zellen speichern, deren Elemente durch Koordinatenpaare indiziert sind, (w,h)wie in der folgenden Abbildung gezeigt. Eine solche Wahl der Koordinaten erscheint natürlich, ist jedoch unpraktisch für die Beschreibung der Bewegung von Formen (beobachten Sie beispielsweise, wie sich die Koordinaten ändern, wenn Sie sich entlang der Diagonale bewegen).





Koordinaten von Zellen entlang der horizontalen wund vertikalen Achseh



Zur Vereinfachung der Beschreibung der Bewegung von Figuren werden dreidimensionale Koordinaten verwendet. Wählen wir eine Zelle als Referenzzelle mit Koordinaten aus {0,0,0}(der Einfachheit halber stimmt sie mit der Zelle des (0, 0)Arrays überein ).





Dreidimensionale Koordinaten von Zellen relativ zur zentralen Zelle mit Koordinaten Die{0,0,0}



Verschiebung entlang der Diagonale "von rechts nach links, von unten nach oben" wird durch die Koordinate bezeichnet x, die Verschiebung von unten nach oben durch die Koordinate yund die Verschiebung entlang der Diagonale "von links nach rechts, von unten nach oben" durch die Koordinate z. Wenn Sie zu einer benachbarten Zelle wechseln, ändert sich die entsprechende Koordinate um eins. Somit erhält jede Zelle drei Koordinaten, wie im obigen Bild.



In diesem Fall sind die Zellen mehrdeutig nummeriert. Wenn {0,0,0}wir uns beispielsweise von der zentralen Zelle mit den Koordinaten nach links unten und dann nach oben bewegen, erhalten wir die Koordinaten der Zelle {0,1,-1}. Dieselbe Zelle hat jedoch Koordinaten, {1,0,0}wenn Sie direkt von der zentralen Zelle aus darauf zugreifen, wie Sie in der vorherigen Abbildung sehen können.





Eine weitere Option zum Festlegen der Koordinaten der Zelle {1,0,0}.



Das Durchlaufen einer Zelle in einem Zyklus "von links nach unten" - "nach oben" - "nach rechts nach unten" bringt uns zur gleichen Zelle, fügt jedoch ihren Koordinaten einen Vektor hinzu, dessen {-1,1,-1}Summe der Koordinaten gleich ist -1. Wenn wir mental einen solchen Spaziergang mehrmals in derselben oder in der entgegengesetzten Richtung durchführen, können wir die Koordinaten jeder Zelle in äquivalente ändern, die sich um einen proportionalen Vektor unterscheiden {-1,1,-1}. Um Mehrdeutigkeiten zu beseitigen, wählen wir in jeder Äquivalenzklasse als Repräsentant ein Dreifach von Koordinaten, deren Summe gleich Null ist. Diese Wahl der Koordinaten ist einzigartig (beweisen Sie es!).



Beschreiben wir weiter den Algorithmus zum Konvertieren von zweidimensionalen Koordinaten in dreidimensionale Koordinaten und umgekehrt innerhalb der Klasse Position.



Position(int w, int h) //    
        : x_{-w/2 — w % 2 - h}
        , y_{w % 2 + 2 * h}
        , z_{w / 2 — h} {
}

int posW() const { //       
    return -x_ + z_;
}

int posH() const { //       
    return (x_ + z_ — (x_ + z_)%2) / 2 + y_;
}


Beachten Sie, dass der Konstruktor Koordinaten erzeugt (x,y,z), die sich zu Null addieren. In diesem Fall funktioniert die Konvertierung von Koordinaten (x,y,z)in (w,h)für jeden Koordinatensatz korrekt (die Summe muss nicht Null sein).



Wie haben wir all diese Formeln gefunden? Mit der wissenschaftlichen Poke-Methode: Durch Analyse der Änderung dreidimensionaler Koordinaten, wenn eine der zweidimensionalen Koordinaten um 1(Konstruktor) und in die entgegengesetzte Richtung (Methoden) verschoben wird .



Mit dreidimensionalen Koordinaten können wir leicht überprüfen, ob die Zellen kollinear sind. Um beispielsweise zu überprüfen, ob zwei Zellen auf derselben Diagonale liegen z, müssen Sie einen Vektor finden, der diese Zellen verbindet, und überprüfen, ob seine Äquivalenzklasse einen Vektor der Form enthält{0, 0, z}... Z kann alles sein - diese Zahl gibt den Abstand zwischen den Zellen an. Es ist sehr einfach, die Überprüfung der Bewegung auf Richtigkeit durchzuführen und alle für die Bewegung verfügbaren Zellen zu finden.



In einem zweidimensionalen Array, das die Tafel darstellt, speichern wir Informationen über die Position der Figuren. Wenn es in jeder Zelle eine Schachfigur gibt, speichern wir deren Typ und Farbe.



In unserer Implementierung in der Board-Klasse speichern wir nur die Arten von Teilen in Zellen. Wir brauchen eine Klasse, die alle möglichen Züge für Teile auf diesem Brett findet und die Richtigkeit der Züge überprüft.



Bewegt sich für Stücke



Wir haben eine Klasse erstellt FigureMoveValidator, die 6 Nachkommen für jeden Figurentyp enthält (es war möglich, auf Nachkommen zu verzichten, wenn wir bei jeder Methode einen Schalterfall für den Figurentyp erstellt haben). Der Klassenkonstruktor hat zwei Parameter: Position und Kartenreferenz. Auch in der Klasse gibt es zwei Methoden allMovesund checkMove.



Betrachten wir die Methode allMoves. Um alle Bewegungen zu finden, setzen wir eine Reihe von Vektoren möglicher Verschiebungen zusammen und gehen sie durch. Bei Teilen, die sich einen Schritt bewegen, müssen wir überprüfen, ob wir nicht vom Brett gesprungen sind und nicht in die Zelle gelangt sind, in der sich unser Teil befindet. Fügen Sie für Figuren, die mehrere Zellen in einer geraden Linie verschieben, einen Verschiebungsvektor hinzu, während die vorherige Bedingung erfüllt ist.



JetztcheckMove... Wir erinnern uns, dass wir prüfen können, ob die Zahlen auf derselben geraden Linie liegen. Wenn wir überprüfen, ob es keine anderen Teile in dieser Zeile gibt, erhalten wir eine vorgefertigte Methode für den Dominator (analog zum Turm). Wenn die Teile auf einer geraden Linie liegen, können wir den Abstand zwischen ihnen finden und Methoden für den Progressor (Bauer), den Verteidiger (geht wie ein König), die Intelligenz (der König kann nur nicht schneiden) und den Befreier (kann durch eine Zelle zu einer beliebigen gehen) ermitteln Seite). Es gibt immer noch einen Angreifer (ein Analogon eines Elefanten), der sich diagonal in sechs Richtungen von der aktuellen in Zellen bewegt (von Zelle {0, 0, 0}zu {0, 1, 1}, nach {0, 2, 2}usw .: siehe graue Zellen im Bild unten). In dieser Abbildung können Sie versuchen, eine der Koordinaten auf Null zu setzen und zu überprüfen, ob die verbleibenden 2 Koordinaten im absoluten Wert gleich sind (dank der dreidimensionalen Koordinaten).





Mögliche Bewegungen für den Angreifer



Jetzt müssen wir herausfinden, was mit den Bewegungen zu tun ist. Erstellen wir eine Verschiebungsklasse, in der alle für die Verschiebung erforderlichen Informationen gespeichert werden. Wir haben beschlossen, 2 Positionen und 4 Teile zu speichern: die Position, von der aus sich das Teil bewegt, die Position, an die es kommen wird, und Informationen darüber, welche Teile in jeder dieser Zellen standen und welche nach dem Anwenden der Bewegung stehen werden. Dies erleichtert die Implementierung des Verschiebungsverlaufssystems und des Verschiebungsrollbacks.



Schnittstelle



Die Architektur



Die Anwendung ist in der ncurses-Konsolenbibliothek geschrieben (hier ist ein Tutorial dafür ) . Mit dieser Bibliothek können Sie Pseudografiken in der Konsole erstellen. Zum Beispiel basieren Midnight Commander und Nano darauf .



Die Wahl mag sehr seltsam erscheinen: Es gibt viele andere Bibliotheken, die schöner, bequemer und plattformübergreifender sind. Dies hängt damit zusammen, dass wir ursprünglich zwei Schnittstellen geplant hatten: Konsole und Grafik. Zum Zeitpunkt der Projektlieferung konnten wir keine zwei Schnittstellen schreiben und haben stattdessen mehr Funktionen in der Konsolenversion bereitgestellt. Obwohl architektonisch, ist die Anwendung immer noch für verschiedene Schnittstellen ausgelegt.



Es gibt zwei Hauptentitäten: Anzeige und Steuerung... Die Ansichten zeigen den Spielern das Bild, und der Controller vermittelt zwischen den verschiedenen Ansichten und dem internen Spielmodell.



Das Display verwaltet alle Benutzerinteraktionen: Cursorposition und -bewegung, Formauswahl, Hervorhebung verfügbarer Felder, Abschluss des Spiels und mehr. Aktionen, die sich auf die Karte auswirken, beziehen sich auf die Steuerung und senden / empfangen die erforderlichen Informationen zum / vom Modell.



Das Display erstellt eine eigene Version der Karte, benötigt jedoch zusätzliche Parameter wie die Position des Cursors und die Farbe der Zellen. Diese Parameter können nicht zum Hauptmodell hinzugefügt werden, da unterschiedliche Zuordnungen unterschiedliche Parameter erfordern. In der Konsolenoberfläche müssen Sie beispielsweise die Position des Cursors speichern, nicht jedoch in der grafischen Oberfläche, da die Auswahl und Bewegung der Figur mit der Maus erfolgt.



Folgendes passiert, wenn ein Spieler die für den Zug verfügbaren Felder kennen möchte:



  1. Der Spieler bewegt den Cursor auf das Figurenfeld und drückt die Leertaste
  2. Das Feld mit der Form wird als ausgewählt markiert
  3. Die Schnittstelle bezieht sich auf eine Methode selectCellauf der Steuerung
  4. Methode selectCellbezieht sich auf die Methode des allFigureMovesModells
  5. allFigureMoveserstellt FigureMoveValidator, die alle verfügbaren Züge berechnet
  6. allFigureMoves gefundene Übertragungen werden zurück zum Controller verschoben
  7. Die Steuerung leitet sie an die Schnittstelle weiter
  8. Die Benutzeroberfläche zeichnet das Feld neu und hebt die verfügbaren Felder hervor




Der Cursor befindet sich in der Mitte der Tafel: auf einem hellblauen Quadrat. Vor dem Verschieben an diese Position hat der Benutzer eine Form ausgewählt. Die verfügbaren Bewegungen werden grün hervorgehoben, und die ausgewählte Zelle selbst ist lila.



Wie wird das Feld gezeichnet?



Die Konsolenoberfläche wird in einem rechteckigen Fenster mit Textzeilen gerendert. Wenn Sie Symbole an den richtigen Stellen platzieren und ausmalen, erhalten Sie ein Bild:





(Evil Pacman, gezeichnet mit den Buchstaben "o")



Eine Funktion move(int y, int x)in ncurses ändert die aktuelle Position, und die Funktion addch(chtype c)fügt ein Zeichen hinzu und verschiebt die aktuelle Position 1 nach rechts ( x —> x+1).



Ein komplexes Bild kann als zweidimensionales Array gespeichert und zeilenweise angezeigt werden: Wenn die Zeile endet, verschieben Sie die aktuelle Position an den Anfang der nächsten Zeile. Das Prinzip ist einer Schreibmaschine sehr ähnlich.



Auf dem Computer des Benutzers, wird das Feld in unserem Spiel , wenn das Terminal gefärbt wird unterstützt Farben und andere Textattribute.



Mit Ncurses kann der Entwickler die Attribute des Texts ändern, wenn er an die Konsole ausgegeben wird (Farbe, Fettdruck, Blinken). Schreiben Sie dazu den Code:



attron( *attributes* );
addch(c);
attroff( *attributes* );


Jedes Symbol hat seine eigene Farbe und Hintergrundfarbe. Moderne Konsolen unterstützen maximal 256 Farben, daher müssen Sie mit einem begrenzten Satz arbeiten : ziemlich traurig in Bezug auf das Farbdesign.



Bilder für die Ausgabe können im Code gespeichert werden (wie ursprünglich) oder in separaten Dateien gespeichert und beim Programmstart daraus gelesen werden. Dafür haben wir uns ein eigenes Dateiformat ausgedacht *.btn.



Es speichert ein Textbild, das das Spiel liest und anzeigt. Zum Beispiel eine Form oder die Aufschrift "Weiß gewinnt" / "Schwarz gewinnt" oder eine Menüschaltfläche. In diesem Fall benötigen Sie manchmal Transparenz, um das zuvor gezeichnete nicht zu überschreiben. Dazu können Sie in der ersten Zeile #und nach der Liste der "transparenten" Symbole einen Hash hinzufügen , der in der Ausgabe ignoriert wird.



Nehmen wir zum Beispiel an, wir haben 3 Rechtecke auf dem Bildschirm gezeichnet:





Fügen Sie der Mitte ein Rechteck aus der folgenden Datei hinzu:



#C
AAAAAAAAA
ACCCCCCCA
ACCCCCCCA
ACCCCCCCA
ACCCCCCCA
ACCCCCCCA
AAAAAAAAA


Und wir bekommen folgendes Bild:





(aus Gründen der Übersichtlichkeit gelb hervorgehoben)



Dieses Format war besonders nützlich bei der Entwicklung von Menüs.



Speisekarte



Das Spiel hat auch ein Menü , das Einstellungen enthält und vor dem Start des Spiels und nach dessen Ende gezeichnet wird.



Menüschaltflächen werden aus Dateien gelesen *.btn. Diese Schaltflächen sollten großen, schönen Text haben. Wir können nicht schön mit ASCII-Zeichen zeichnen, aber figlet , ein Dienstprogramm zum Anzeigen von ASCII-Text in verschiedenen Schriftarten, kann:





Die Schaltflächen rahmen die aus der Datei gelesenen Beschriftungen ein:





Sie werden dann zentriert und nacheinander ausgegeben:





In einigen Menüs können Sie fehlschlagen und beispielsweise die Komplexität und Farbe des Bots anpassen:





Der interessanteste Teil beim Entwerfen eines Menüsystems besteht darin, seine Elemente in einem System zu kombinieren. Dies geschieht durch ein separates Element des Systems, das wir als Multiplexer bezeichnet haben. Der Name ist von Terminal-Multiplexern inspiriert .



Der Multiplexer akzeptiert die vom Benutzer gedrückte Taste und sendet sie an alle aktuell angezeigten Menüs. Jedes Menü entscheidet für sich, was mit dem Schlüssel geschehen soll: ignorieren oder irgendwie verarbeiten. Das Ergebnis der Verarbeitung des Menüs wird an den Multiplexer zurückgegeben, der entscheidet, was als nächstes zu tun ist: Schließen Sie das Menü, erstellen Sie ein neues, ändern Sie die Einstellungen, schließen Sie die Anwendung ...



Dieser Ansatz hat sich für unsere Anforderungen als praktisch erwiesen, obwohl er im Allgemeinen möglicherweise nicht ausreicht: 2 verschiedene Menüs können auf dieselbe Taste reagieren, und der Benutzer sollte auswählen können, welches Menü reagieren soll. Die Lösung wäre eine spezielle Tastenkombination, mit der Sie zwischen verschiedenen Menüs wechseln können. Zum Beispiel wie in tmux . Dies ist jedoch übertrieben und wurde nicht benötigt.



Der Bot



Wie oben erwähnt, hat unser Spiel einen Bot. Wir haben versucht, es sowohl für Anfänger als auch für erfahrene Spieler interessant zu machen.



Bevor ich Bots beschreibe, möchte ich auf einige Implementierungsdetails eingehen. Wir haben jeder Form etwas Gewicht zugewiesen. Je größer es ist, desto wertvoller ist diese Zahl. Wir bestimmen, wie gut eine Position auf dem Brett ist, indem wir die Formel (Summe der Gewichte der weißen Teile) - (Summe der Gewichte der schwarzen Teile) verwenden. Es ist für Weiß von Vorteil, diesen Ausdruck zu maximieren und für Schwarz zu minimieren.



Eine vollständige Berechnung des gesamten Bewegungsbaums ist eine zu schwierige Aufgabe, daher haben wir nur die ersten paar Züge berechnet (mit Blick auf die Zukunft habe ich festgestellt, dass sich 6 Züge im Voraus berechnet haben). Wir haben alle Zustände an der Tafel in einer bestimmten Tiefe als Blätter des Traversalbaums betrachtet.



Es gibt drei verschiedene Arten von Bots im Spiel:



  • RandomBot — . .
  • GreedyBot — «» , , .
  • AlphaBetaBot — , - .


Als wir anfingen, mit Optimierungen zu experimentieren, stellten wir fest, dass wir nicht auf Unit-Tests für den Bot verzichten konnten. Deshalb haben wir einen Zwillingsbruder für AlphaBetaBot'a' erstellt, den wir benannt haben OptimizedAlphaBetaBot. Wir haben alle Optimierungsideen getestet OptimizedAlphaBetaBotund Unit-Tests haben dazu beigetragen, dass die beiden Zwillingsbrüder den gleichen nützlichen Schritt finden. RandomBot hat uns gute Dienste geleistet, indem es zufällige Muster auf dem Board generiert hat. Um dies zu tun, genügte es, RandomBotfür beide Seiten mehrere Dutzend Mal zu fragen und zu gehen.



Insgesamt OptimizedAlphaBetaBot wurden 3 Hauptoptimierungen implementiert (hier werden sie in absteigender Reihenfolge des Nutzens dargestellt):



  • Rollbacks verwenden. Nach dieser Optimierung war es nicht mehr erforderlich, das Board mehrmals zu kopieren, um einen Zug auszuführen.
  • FigureKeeper, , . std::vector .
  • std::unordered_map Zobrish hashing.


Neben größeren Optimierungen gab es auch kleinere. Wenn Sie beispielsweise vor dem Sortieren alle Werte der Positionen auf dem Brett unter Berücksichtigung einer bestimmten Bewegung berechnen, müssen Sie keine komplexen Objekte mehr sortieren Move, sondern nur noch intdie.



Ursprünglich war geplant, mehrere Bewertungsfunktionen zu implementieren: Beispielsweise wird eine von einem Feind bedrohte Zahl auf die Hälfte der Kosten geschätzt. Es stellte sich jedoch heraus, dass der Bot ziemlich "sauber" spielt und nur wenige Teile verliert, sodass sich eine einfache Menge als effektiver herausstellte.



Die Bot-Architektur unterstützt jedoch weiterhin das Hinzufügen neuer Bewertungsfunktionen. Dazu müssen Sie nur drei Dinge definieren:



  1. Funktion, wenn Sie die Kosten "von Grund auf neu" für eine bestimmte Anordnung von Zahlen berechnen müssen
  2. Delta-Funktion, die die Kosten für einen bestimmten Zug schnell neu berechnen sollte.
  3. Die Nummer dieses Funktionssatzes für den Konstruktor der benutzerdefinierten Klasse FunctionSet.


Sie können den Kampf der Bots einschalten und den Prozess beobachten.





Ein Spiel mit 2 Bots des gleichen Schwierigkeitsgrades (Level 4 von 6 möglich). Der Cursor befindet sich während des gesamten Spiels in der Mitte des Feldes



Fazit



Wir haben ein Schachspiel implementiert, das jedoch unterschiedliche Regeln und ein ungewöhnliches Brett enthält. Unsere Implementierung hat Raum für Erweiterungen. Intellector selbst entwickelt und verändert sich ebenfalls: Vor kurzem gab es eine Aktualisierung der Regeln, die wir in unserer Anwendung noch nicht unterstützt haben. Zum Beispiel können Sie jetzt die Mittellinie für die ersten 2 Runden nicht überqueren.



Darüber hinaus gibt es verschiedene Funktionen, die wir ursprünglich geplant hatten, für die zum Zeitpunkt des Projekts jedoch keine Zeit zur Implementierung hatten. Zum Beispiel würde ich in dieser Anwendung wirklich gerne ein Netzwerkspiel sehen. Auch eine schöne plattformübergreifende Oberfläche, zum Beispiel auf Qt, würde nicht schaden.



Vielleicht wird dies alles oder ein Teil davon in naher Zukunft erscheinen. Bis dahin danke fürs Lesen!



Github-Repository



All Articles