KoTH: Gomoku (fünf in einer Reihe)


10

Gomoku oder Fünf in einer Reihe ist ein Brettspiel, das von zwei Spielern auf einem Gitter mit schwarzen und weißen Steinen gespielt wird. Wer 5 Steine ​​hintereinander platzieren kann (horizontal, vertikal oder diagonal), gewinnt das Spiel.15×155

Regeln

In diesem KoTH spielen wir die Swap2-Regel, was bedeutet, dass ein Spiel aus zwei Phasen besteht: In der Anfangsphase bestimmen die beiden Spieler, wer zuerst geht / wer schwarz spielt. Danach legen sie jede Runde eine Klappe, beginnend mit dem Spieler wer wählte schwarz.

Anfangsphase

Lassen Sie die Spieler A & B sein und A soll das Spiel eröffnen:

  • A legt zwei schwarze und einen weißen Stein auf das Brett
  • B kann einen der folgenden drei Züge wählen:
    • Spieler B entscheidet sich für Schwarz: Die Anfangsphase ist beendet
    • Spieler B beschließt, einen weißen Stein zu platzieren und spielt Weiß: Die Anfangsphase ist vorbei
    • Spieler B beschließt, einen schwarzen und einen weißen Stein zu spielen: A darf die Farbe auswählen

Spielphase

Jeder Spieler legt einen Stein seiner Farbe auf das Brett, beginnend mit dem Spieler, der Schwarz spielt. Dies geht so lange weiter, bis keine freien Plätze mehr zum Spielen vorhanden sind (in diesem Fall ist es ein Unentschieden) oder ein Spieler es schafft, Steine ​​in einem zu spielen Reihe (in diesem Fall gewinnt der Spieler).5

Eine Reihe bedeutet entweder horizontal, vertikal oder diagonal. Ein Gewinn ist ein Gewinn - es spielt keine Rolle, ob der Spieler mehr als eine Reihe erzielt hat.

KoTH-Spielregeln

  • Jeder Spieler spielt zweimal gegeneinander:
    • Zunächst wird nach dem Zufallsprinzip entschieden, wer zuerst geht
    • Im nächsten Spiel geht der Spieler, der zuletzt gespielt hat, zuerst
  • Ein Sieg ist 2 Punkte wert, ein Unentschieden 1 und eine Niederlage 0
  • Ziel ist es, möglichst viele Punkte zu erzielen

Dein Bot

Um diese Herausforderung für möglichst viele Sprachen zugänglich zu machen, erfolgt die Eingabe / Ausgabe über stdin / stdout ( zeilenbasiert ). Das Richterprogramm fordert Ihr Programm auf, indem es eine Zeile in den Standard Ihres Bots druckt, und Ihr Bot druckt eine Zeile in den Standard .

Sobald Sie eine EXITNachricht erhalten haben, haben Sie eine halbe Sekunde Zeit, um das Schreiben in Dateien abzuschließen, bevor der Richter den Prozess abbricht.

Zufälligkeit

Um die Turniere überprüfbar zu machen, verwendet der Richter eine gesetzte Randomisierung, und Ihr Bot muss dies aus demselben Grund auch tun. Der Bot erhält über das Befehlszeilenargument einen Startwert, den er verwenden sollte. Weitere Informationen finden Sie im nächsten Abschnitt.

Argumente

Der Bot erhält zwei Befehlszeilenargumente:

  1. Name des Gegners
  2. Samen für Zufälligkeit

Benutzerstatus

Da Ihr Programm für jedes Spiel immer neu gestartet wird, müssen Sie Dateien verwenden, um alle Informationen beizubehalten, die Sie behalten möchten. Sie dürfen alle Dateien in Ihrem aktuellen Verzeichnis lesen / schreiben oder Unterordner erstellen / entfernen. Sie dürfen nicht auf Dateien in einem übergeordneten Verzeichnis zugreifen!

Eingabe- / Ausgabeformat

BOARD((X,Y),COLOR)XY[0,15)COLOR"B""W"

SPXY(X,Y)[0,fünfzehn)|

In der Anfangsphase gibt es drei verschiedene Arten von Nachrichten:

Prompt (judge) -> Answer (bot)
"A" SP "[]"  -> XY XY XY
"B" SP BOARD -> "B" | "W" SP XY | XY XY
"C" SP BOARD -> "B" | "W"
  • Die erste Nachricht fragt nach drei Tupeln, die ersten beiden sind die Positionen der schwarzen Steine ​​und die dritte die Position der weißen.
  • Die zweite Nachricht fragt entweder nach:
    • "B" -> wähle schwarz
    • "W" SP XY -> Weiß auswählen und einen weißen Stein darauf legen XY
    • XY XY -> zwei Steine ​​platzieren (der erste schwarz und der zweite weiß)
  • Der letzte fragt nur, welche Farbe Sie spielen möchten

Danach beginnt das reguläre Spiel und die Nachrichten werden viel einfacher

N BOARD -> XY

N0XY


Es gibt eine zusätzliche Nachricht, die keine Antwort erwartet

"EXIT" SP NAME | "EXIT TIE"

Wo NAMEist der Name des Bots, der gewonnen hat? Die zweite Nachricht wird gesendet, wenn das Spiel endet, weil niemand gewinnt und keine freien Plätze mehr zum Platzieren von Steinen vorhanden sind (dies bedeutet, dass Ihr Bot nicht benannt werden kann TIE).

Formatierung

Da Nachrichten vom Bot ohne Leerzeichen dekodiert werden können, werden alle Leerzeichen ignoriert (z. B. werden sie (0 , 0) (0,12)wie behandelt (0,0)(0,12)). Nachrichten des Richters enthalten nur ein Leerzeichen, um verschiedene Abschnitte zu trennen (dh wie oben mit SP), sodass Sie die Linie auf Leerzeichen aufteilen können.

Jede ungültige Antwort führt zum Verlust dieser Runde (Sie erhalten weiterhin eine EXITNachricht) (siehe Regeln).

Beispiel

Hier sind einige Beispiele für aktuelle Nachrichten:

A []
B [((0,0),"B"),((0,1),"W"),((14,14),"B")]
1 [((0,0),"B"),((0,1),"W"),((1,0),"B"),((1,1),"W"),((14,14),"B")]

Richter

Das Richterprogramm finden Sie hier : Um einen Bot hinzuzufügen, erstellen Sie einfach einen neuen Ordner im botsOrdner, platzieren Sie Ihre Dateien dort und fügen Sie eine Datei hinzu, metadie Name , Befehl , Argumente und jeweils ein Flag 0/1 (disable / enable stderr ) enthält in einer separaten Zeile.

Um ein Turnier zu starten, laufen Sie einfach ./gomokuund debuggen Sie einen einzelnen Bot-Lauf ./gomoku -d BOT.

Hinweis: Weitere Informationen zum Einrichten und Verwenden des Richters finden Sie im Github-Repository. Es gibt auch drei Beispiel-Bots ( Haskell , Python und JavaScript ).

Regeln

  • Bei jedem Wechsel eines Bots * wird das Turnier wiederholt und der Spieler mit den meisten Punkten gewinnt (Tie-Breaker ist die erste Einreichung).
  • Sie können mehr als einen Bot einreichen, solange diese keine gemeinsame Strategie spielen
  • Sie dürfen keine Dateien außerhalb Ihres Verzeichnisses berühren (z. B. die Dateien anderer Player bearbeiten).
  • Wenn Ihr Bot abstürzt oder eine ungültige Antwort sendet, wird das aktuelle Spiel beendet und Sie verlieren diese Runde
  • Während der Richter (derzeit) kein Zeitlimit pro Runde festlegt, wird empfohlen, die aufgewendete Zeit niedrig zu halten, da es unmöglich werden könnte, alle Einsendungen zu testen **
  • Der Missbrauch von Fehlern im Richterprogramm gilt als Lücke

* Sie werden aufgefordert, Github zu verwenden, um Ihren Bot separat direkt im botsVerzeichnis einzureichen (und möglicherweise zu ändern util.sh)!

** Falls es zu einem Problem wird, werden Sie benachrichtigt. Ich würde sagen, dass alles unter 500 ms (das ist viel!) Im Moment in Ordnung sein sollte.

Plaudern

Wenn Sie Fragen haben oder über dieses KoTH sprechen möchten, können Sie sich gerne dem Chat anschließen !



Leerzeichen zu haben, dann ein Meta-Leerzeichen in Ihren Beispielen, ist mir ein Rätsel. Einige weitere Beispiele wären schön.
Veskah

@Veskah: Es sind drei Beispiel-Bots verknüpft. Ich werde einige Beispiele für Nachrichten hinzufügen.
23.

@ Veskah: Einige Beispiele hinzugefügt. Übrigens. Sie können auch versuchen, einen Beispiel-Bot zu debuggen, um zu sehen, in welchem ​​Format sie vorliegen, und um zu testen, was eine gültige Antwort ist.
23.

Sie haben keine Push-Berechtigungen erteilt, daher kann ich meinen Bot nicht an den Git senden
Kaito Kid

Antworten:


3

KaitoBot

Es verwendet eine sehr grobe Implementierung der MiniMax-Prinzipien. Die Suchtiefe ist ebenfalls sehr gering, da sie sonst viel zu lange dauert.

Könnte später bearbeitet werden, um sich zu verbessern.

Es wird auch versucht, wenn möglich Schwarz zu spielen, da Wikipedia zu sagen scheint, dass Schwarz einen Vorteil hat.

Ich habe selbst noch nie Gomoku gespielt, deshalb habe ich die ersten drei Steine ​​zufällig aufgestellt, da ich keine bessere Idee hatte.

const readline = require('readline');
const readLine = readline.createInterface({ input: process.stdin });

var debug = true;
var myColor = '';
var opponentColor = '';
var board = [];
var seed = parseInt(process.argv[3]);

function random(min, max) {
    changeSeed();
    var x = Math.sin(seed) * 10000;
    var decimal = x - Math.floor(x);
    var chosen = Math.floor(min + (decimal * (max - min)));
    return chosen;
}

function changeSeed() {
    var x = Math.sin(seed++) * 10000;
    var decimal = x - Math.floor(x);
    seed = Math.floor(100 + (decimal * 9000000));
}

function KaitoBot(ln) {
    var ws = ln.split(' ');

    if (ws[0] === 'A') {
        // Let's play randomly, we don't care.
        var nums = [];
        nums[0] = [ random(0, 15), random(0, 15) ];
        nums[1] = [ random(0, 15), random(0, 15) ];
        nums[2] = [ random(0, 15), random(0, 15) ];
        while (nums[1][0] == nums[0][0] && nums[1][1] == nums[0][1])
        {
            nums[1] = [ random(0, 15), random(0, 15) ];
        }
        while ((nums[2][0] == nums[0][0] && nums[2][1] == nums[0][1]) || (nums[2][0] == nums[1][0] && nums[2][1] == nums[1][1]))
        {
            nums[2] = [ random(0, 15), random(0, 15) ];
        }
        console.log('(' + nums[0][0] + ',' + nums[0][1] + ') (' + nums[1][0] + ',' + nums[1][1] + ') (' + nums[2][0] + ',' + nums[2][1] + ')');
    }
    else if (ws[0] === 'B') {
        // we're second to play, let's just pick black
        myColor = 'B';
        opponentColor = 'W';
        console.log('B');
    }
    else if (ws[0] === 'C') {
        // the other player chose to play 2 stones more, we need to pick..
        // I would prefer playing Black
        myColor = 'B';
        opponentColor = 'W';
        console.log('B');
    }
    else if (ws[0] === 'EXIT') {
        process.exit();
    }
    else {
        board = [];
        var json = JSON.parse(ws[1].replace(/\(\(/g,'{"xy":[')
                .replace(/"\)/g,'"}')
                .replace(/\),/g,'],"colour":'));
        // loop over all XYs and make a board object I can use
        for (var x = 0; x < 15; x++) {
            var newRow = []
            for (var y = 0; y < 15; y++) {
                var contains = false;
                json.forEach(j => {
                    if (j.xy[0] == x && j.xy[1] == y) {
                        contains = true;
                        newRow[newRow.length] = j.colour;
                    }
                });
                if (!contains) {
                    newRow[newRow.length] = ' ';
                }
            }
            board[board.length] = newRow;
        }
        // If we never picked Black, I assume we're White
        if (myColor == '') {
            myColor = 'W';
            opponentColor = 'B';
        }
        var bestMoves = ChooseMove(board, myColor, opponentColor);
        var chosenMove = bestMoves[random(0, bestMoves.length)];
        console.log('(' + chosenMove.X + ',' + chosenMove.Y + ')');
    }
}

function IsSquareRelevant(board, x, y) {
    return (board[x][y] == ' ' && 
        ((x > 0 && board[x - 1][y] != ' ') 
        || (x < 14 && board[x + 1][y] != ' ') 
        || (y > 0 && board[x][y - 1] != ' ') 
        || (y < 14 && board[x][y + 1] != ' ')
        || (x > 0 && y > 0 && board[x - 1][y - 1] != ' ') 
        || (x < 14 && y < 14 && board[x + 1][y + 1] != ' ') 
        || (y > 0 && x < 14 && board[x + 1][y - 1] != ' ') 
        || (y < 14 && x > 0 && board[x - 1][y + 1] != ' ')));
}

function ChooseMove(board, colorMe, colorOpponent) {
    var possibleMoves = [];
    for (var x = 0; x < 15; x++) {
        for (var y = 0; y < 15; y++) {
            if (IsSquareRelevant(board, x, y)) {
                possibleMoves[possibleMoves.length] = {X:x, Y:y};
            }
        }
    }
    var bestValue = -9999;
    var bestMoves = [possibleMoves[0]];
    for (var k in possibleMoves) {
        var changedBoard = JSON.parse(JSON.stringify(board));
        changedBoard[possibleMoves[k].X][possibleMoves[k].Y] = colorMe;
        var value = analyseBoard(changedBoard, colorMe, colorOpponent, colorOpponent, 2);
        if (value > bestValue) {
            bestValue = value;
            bestMoves = [possibleMoves[k]];
        } else if (value == bestValue) {
            bestMoves[bestMoves.length] = possibleMoves[k];
        }
    }
    return bestMoves;
}

function analyseBoard(board, color, opponent, nextToPlay, depth) {
    var tBoard = board[0].map((x,i) => board.map(x => x[i]));
    var score = 0.0;
    for (var x = 0; x < board.length; x++) {
        var inARow = 0;
        var tInARow = 0;
        var opponentInARow = 0;
        var tOpponentInARow = 0;
        var inADiago1 = 0;
        var opponentInADiago1 = 0;
        var inADiago2 = 0;
        var opponentInADiago2 = 0;

        for (var y = 0; y < board.length; y++) {
            if (board[x][y] == color) {
                inARow++;
                score += Math.pow(2, inARow);
            } else {
                inARow = 0;
            }
            if (board[x][y] == opponent) {
                opponentInARow++;
                score -= Math.pow(2, opponentInARow);
            } else {
                opponentInARow = 0;
            }
            if (tBoard[x][y] == color) {
                tInARow++;
                score += Math.pow(2, tInARow);
            } else {
                tInARow = 0;
            }
            if (tBoard[x][y] == opponent) {
                tOpponentInARow++;
                score -= Math.pow(2, tOpponentInARow);
            } else {
                tOpponentInARow = 0;
            }

            var xy = (y + x) % 15;
            var xy2 = (x - y + 15) % 15;
            if (xy == 0) {
                inADiago1 = 0;
                opponentInADiago1 = 0;
            }
            if (xy2 == 0) {
                inADiago2 = 0;
                opponentInADiago2 = 0;
            }

            if (board[xy][y] == color) {
                inADiago1++;
                score += Math.pow(2, inADiago1);
            } else {
                inADiago1 = 0;
            }
            if (board[xy][y] == opponent) {
                opponentInADiago1++;
                score -= Math.pow(2, opponentInADiago1);
            } else {
                opponentInADiago1 = 0;
            }
            if (board[xy2][y] == color) {
                inADiago2++;
                score += Math.pow(2, inADiago2);
            } else {
                inADiago2 = 0;
            }
            if (board[xy2][y] == opponent) {
                opponentInADiago2++;
                score -= Math.pow(2, opponentInADiago2);
            } else {
                opponentInADiago2 = 0;
            }


            if (inARow == 5 || tInARow == 5) {
                return 999999999.0;
            } else if (opponentInARow == 5 || tOpponentInARow == 5) {
                return -99999999.0;
            }
            if (inADiago1 == 5 || inADiago2 == 5) {
                return 999999999.0;
            } else if (opponentInADiago1 == 5 || opponentInADiago2 == 5) {
                return -99999999.0;
            }
        }
    }

    if (depth > 0) {
        var bestMoveValue = 999999999;
        var nextNextToPlay = color;
        if (nextToPlay == color) {
            nextNextToPlay = opponent;
            bestMoveValue = -999999999;
        }
        for (var x = 0; x < board.length; x++) {
            for (var y = 0; y < board.length; y++) {
                if (IsSquareRelevant(board, x, y)) {
                    var changedBoard = JSON.parse(JSON.stringify(board));
                    changedBoard[x][y] = nextToPlay;
                    var NextMoveValue = (analyseBoard(changedBoard, color, opponent, nextNextToPlay, depth - 1) * 0.1);

                    if (nextToPlay == color) {
                        if (NextMoveValue > bestMoveValue) {
                            bestMoveValue = NextMoveValue;
                        }
                    } else {
                        if (NextMoveValue < bestMoveValue) {
                            bestMoveValue = NextMoveValue;
                        }
                    }
                }
            }
        }
        score += bestMoveValue * 0.1;
    }
    return score;
}

readLine.on('line', (ln) => {

    KaitoBot(ln);

});

BEARBEITUNGEN: Der Startwert wurde dynamisch geändert, da Javascript das Inkrement ansonsten nicht korrekt verarbeiten konnte, wenn der Startwert 2 ^ 52 überschritt

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.