KoTH: Gomoku (cinq d'affilée)

10

Gomoku ou Five in a row est un jeu de société joué par deux joueurs sur une grille avec des pierres noires et blanches. Celui qui est capable de placer 5 pierres d'affilée (horizontale, verticale ou diagonale) remporte la partie.15×155

Règles

Dans ce KoTH, nous jouerons la règle Swap2, ce qui signifie qu'un jeu se compose de deux phases: dans la phase initiale, les deux joueurs déterminent qui va en premier / qui joue le noir, après quoi ils placeront une pierre à chaque tour en commençant par le joueur qui a choisi le noir.

Phase initiale

Laissez les joueurs être A & B et A ouvrira le jeu:

  • A place deux pierres noires et une blanche sur le plateau
  • B peut choisir l'un des trois mouvements suivants:
    • le joueur B décide de jouer en noir: la phase initiale est terminée
    • le joueur B décide de placer une pierre blanche et joue blanc: la phase initiale est terminée
    • le joueur B décide de jouer une pierre noire et une pierre blanche: A choisit la couleur

Phase de jeu

Chaque joueur place une pierre de sa couleur sur le plateau, en commençant par le joueur qui joue en noir, cela continue jusqu'à ce qu'il n'y ait plus d'espace libre à jouer (auquel cas c'est une égalité) ou qu'un joueur réussisse à jouer pierres dans un ligne (auquel cas ce joueur gagne).5

Une ligne signifie soit horizontale, verticale ou diagonale. Une victoire est une victoire - peu importe que le joueur réussisse à marquer plus d'une ligne.

Règles du jeu KoTH

  • chaque joueur joue deux fois contre l'autre:
    • au départ, il sera décidé au hasard qui va en premier
    • dans le jeu suivant, le joueur qui a joué le dernier passe en premier
  • une victoire vaut 2 points, une égalité 1 et une défaite 0
  • le but est de marquer autant de points que possible

Votre bot

Pour rendre ce défi accessible à autant de langues que possible, les entrées / sorties se feront via stdin / stdout (en ligne). Le programme juge invitera votre programme en imprimant une ligne sur le stdin de votre bot et votre bot imprimera une ligne sur stdout .

Une fois que vous recevez un EXITmessage, vous aurez une demi-seconde pour terminer l'écriture dans les fichiers avant que le juge ne tue le processus.

Randomness

Pour rendre les tournois vérifiables, le juge utilise la randomisation prédéfinie et votre bot doit le faire aussi, pour la même raison. Le bot recevra une graine via un argument de ligne de commande qu'il devrait utiliser, veuillez vous référer à la section suivante.

Arguments

Le bot reçoit deux arguments de ligne de commande:

  1. nom de l'adversaire
  2. graine pour le hasard

État utilisateur

Parce que votre programme sera toujours redémarré pour chaque jeu, vous devrez utiliser des fichiers pour conserver toutes les informations que vous souhaitez conserver. Vous êtes autorisé à lire / écrire des fichiers ou à créer / supprimer des sous-dossiers dans votre répertoire actuel. Vous n'êtes pas autorisé à accéder aux fichiers dans aucun répertoire parent!

Format d'entrée / sortie

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

SPXY(X,Y)[0,15)|

Dans la phase initiale, il existe trois types de messages différents:

Prompt (judge) -> Answer (bot)
"A" SP "[]"  -> XY XY XY
"B" SP BOARD -> "B" | "W" SP XY | XY XY
"C" SP BOARD -> "B" | "W"
  • Le premier message demande trois tuples, les deux premiers seront les positions des pierres noires et le troisième la position des pierres blanches.
  • Le deuxième message demande soit:
    • "B" -> choisir le noir
    • "W" SP XY -> choisissez du blanc et placez une pierre blanche à XY
    • XY XY -> placez deux pierres (la première noire et la seconde blanche)
  • Le dernier demande juste pour quelle couleur vous voulez jouer

Après cela, le jeu normal commencera et les messages deviendront beaucoup plus simples

N BOARD -> XY

N0XY


Il y a un message supplémentaire qui n'attend pas de réponse

"EXIT" SP NAME | "EXIT TIE"

NAMEest le nom du bot qui a gagné. Le deuxième message sera envoyé si le jeu se termine à cause du fait que personne n'a gagné et qu'il n'y a plus d'espaces libres pour placer des pierres (cela implique que votre bot ne peut pas être nommé TIE).

Mise en page

Étant donné que les messages du bot peuvent être décodés sans aucun espace, tous les espaces seront ignorés (par exemple, (0 , 0) (0,12)est traité de la même manière que (0,0)(0,12)). Les messages du juge ne contiennent qu'un espace pour séparer les différentes sections (c'est-à-dire comme indiqué ci-dessus avec SP), vous permettant de diviser la ligne sur les espaces.

Toute réponse invalide entraînera la perte de ce tour (vous recevrez toujours un EXITmessage), voir les règles.

Exemple

Voici quelques exemples de messages réels:

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")]

Juge

Vous pouvez trouver le programme juge ici : Pour y ajouter un bot, créez simplement un nouveau dossier dans le botsdossier, placez-y vos fichiers et ajoutez un fichier metacontenant le nom , la commande , les arguments et un indicateur 0/1 (désactiver / activer stderr ) chacun sur une ligne distincte.

Pour lancer un tournoi, il suffit de lancer ./gomokuet de déboguer une seule course de bot ./gomoku -d BOT.

Remarque: Vous pouvez trouver plus d'informations sur la configuration et l'utilisation du juge dans le référentiel Github. Il existe également trois exemples de robots ( Haskell , Python et JavaScript ).

Règles

  • à chaque changement de bot * le tournoi sera relancé et le joueur avec le plus de points l'emporte (tie-breaker est la première soumission)
  • vous pouvez soumettre plus d'un bot tant qu'ils ne jouent pas une stratégie commune
  • vous n'êtes pas autorisé à toucher des fichiers en dehors de votre répertoire (par exemple, manipuler les fichiers d'autres joueurs)
  • si votre bot plante ou envoie une réponse invalide, le jeu en cours est terminé et vous perdez ce tour
  • même si le juge (actuellement) n'applique pas de limite de temps par tour, il est conseillé de limiter le temps passé car il pourrait être impossible de tester toutes les observations **
  • abuser des bogues dans le programme juge compte comme une échappatoire

* Vous êtes encouragé à utiliser Github pour soumettre séparément votre bot directement dans le botsrépertoire (et éventuellement le modifier util.sh)!

** Au cas où cela deviendrait un problème, vous en serez averti, je dirais que tout ce qui est inférieur à 500 ms (c'est beaucoup!) Devrait être bien pour l'instant.

Bavarder

Si vous avez des questions ou souhaitez parler de ce KoTH, n'hésitez pas à rejoindre le chat !

ბიმო
la source
En relation
ბიმო
Avoir des espaces puis un caractère méta-espace dans vos exemples me souffle. Quelques exemples supplémentaires seraient bien.
Veskah
@Veskah: Il y a trois exemples de robots liés, je vais ajouter quelques exemples de messages.
ბიმო
@Veskah: Ajout de quelques exemples. Btw. vous pouvez également essayer de déboguer un exemple de bot pour voir dans quel format ils seront et tester ce qu'est une réponse valide.
ბიმო
Vous n'avez pas donné d'autorisations push, donc je ne peux pas pousser mon bot vers le git
Kaito Kid

Réponses:

3

KaitoBot

Il utilise une implémentation très grossière des principes MiniMax. La profondeur de la recherche est également très faible, car sinon cela prend beaucoup trop de temps.

Peut être modifié pour s'améliorer plus tard.

Il essaie également de jouer au noir si possible, car Wikipedia semble dire que le noir a un avantage.

Je n'ai jamais joué à Gomoku moi-même, alors j'ai installé les trois premières pierres au hasard faute d'une meilleure idée.

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

});

EDITS: fait le changement de graine dynamiquement car sinon quand les graines ont dépassé 2 ^ 52 javascript ne pouvait pas gérer l'incrément correctement

Kaito Kid
la source