KoTH: Gomoku (Cinque di fila)


10

Gomoku o Five di fila è un gioco da tavolo giocato da due giocatori su una griglia con pietre bianche e nere. Chiunque sia in grado di posizionare 5 pietre di fila (orizzontale, verticale o diagonale) vince la partita.15×155

Regole

In questo KoTH giocheremo la regola Swap2, il che significa che una partita è composta da due fasi: Nella fase iniziale i due giocatori determinano chi inizia per primo / chi gioca nero, dopodiché posizioneranno una pietra ogni round a partire dal giocatore chi ha scelto il nero.

Fase iniziale

Lascia che i giocatori siano A e B e A aprirà il gioco:

  • A piazza due pietre nere e una bianca sulla scacchiera
  • B può scegliere una delle tre mosse seguenti:
    • il giocatore B decide di giocare in nero: la fase iniziale è terminata
    • il giocatore B decide di posizionare una pietra bianca e gioca bianco: la fase iniziale è finita
    • il giocatore B decide di giocare una pietra nera e una bianca: A arriva a scegliere il colore

Fase di gioco

Ogni giocatore piazza una pietra del suo colore sul tabellone, a partire dal giocatore che gioca in nero, continua fino a quando non ci sono più spazi liberi da giocare (nel qual caso è un pareggio) o un giocatore riesce a giocare pietre in un riga (nel qual caso vince quel giocatore).5

Una riga significa orizzontale, verticale o diagonale. Una vittoria è una vittoria - non importa se il giocatore è riuscito a segnare più di una riga.

Regole del gioco KoTH

  • ogni giocatore gioca due volte contro l'altro:
    • inizialmente verrà deciso casualmente chi inizia per primo
    • nella prossima partita il giocatore che ha giocato per ultimo va per primo
  • una vittoria vale 2 punti, un pareggio 1 e una perdita 0
  • l'obiettivo è quello di segnare il maggior numero di punti possibile

Il tuo bot

Per rendere questa sfida accessibile per quante più lingue possibile, l'input / output avverrà tramite stdin / stdout (basato su linea). Il programma jud chiederà al tuo programma stampando una riga sullo stdin del tuo bot e il tuo bot stamperà una riga su stdout .

Una volta ricevuto un EXITmessaggio, ti verrà dato mezzo secondo per terminare la scrittura su file prima che il giudice uccida il processo.

casualità

Per rendere verificabili i tornei, il giudice usa la randomizzazione seed e anche il tuo bot deve farlo, per lo stesso motivo. Al bot verrà assegnato un seed tramite l'argomento della riga di comando che dovrebbe utilizzare, fare riferimento alla sezione successiva.

argomenti

Il bot riceve due argomenti della riga di comando:

  1. nome dell'avversario
  2. seme per casualità

Stato dell'utente

Poiché il tuo programma verrà sempre avviato come nuovo per ogni gioco, dovrai utilizzare i file per conservare le informazioni che desideri conservare. Puoi leggere / scrivere qualsiasi file o creare / rimuovere sottocartelle nella tua directory corrente. Non ti è permesso accedere ad alcun file in nessuna directory principale!

Formato di input / output

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

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

Nella fase iniziale ci sono tre diversi tipi di messaggi:

Prompt (judge) -> Answer (bot)
"A" SP "[]"  -> XY XY XY
"B" SP BOARD -> "B" | "W" SP XY | XY XY
"C" SP BOARD -> "B" | "W"
  • Il primo messaggio richiede tre tuple, le prime due saranno le posizioni delle pietre nere e la terza la posizione per quelle bianche.
  • Il secondo messaggio chiede:
    • "B" -> scegli il nero
    • "W" SP XY -> scegli bianco e posiziona una pietra bianca su XY
    • XY XY -> posizionare due pietre (la prima nera e la seconda bianca)
  • L'ultimo chiede solo per quale colore vuoi giocare

Successivamente inizierà il gioco normale e i messaggi diventeranno molto più semplici

N BOARD -> XY

N0XY


C'è un ulteriore messaggio che non prevede una risposta

"EXIT" SP NAME | "EXIT TIE"

dov'è NAMEil nome del bot che ha vinto. Il secondo messaggio verrà inviato se il gioco termina a causa di nessuno che vince e non ci sono più spazi liberi per posizionare le pietre (questo implica che il tuo bot non può essere nominato TIE).

Formattazione

Poiché i messaggi dal bot possono essere decodificati senza spazi, tutti gli spazi verranno ignorati (ad es. (0 , 0) (0,12)Vengono trattati come (0,0)(0,12)). I messaggi del giudice contengono solo uno spazio per separare sezioni diverse (es. Come notato sopra con SP), che consente di dividere la linea su spazi.

Qualsiasi risposta non valida comporterà la perdita di quel round (riceverai comunque un EXITmessaggio), vedi le regole.

Esempio

Ecco alcuni esempi di messaggi reali:

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

Giudice

Puoi trovare il programma judge qui : Per aggiungere un bot ad esso semplicemente crea una nuova cartella nella botscartella, posiziona i tuoi file lì e aggiungi un file metacontenente nome , comando , argomenti e un flag 0/1 (disabilita / abilita stderr ) ciascuno su una linea separata.

Per eseguire un torneo basta eseguire ./gomokue eseguire il debug di una singola corsa bot ./gomoku -d BOT.

Nota: è possibile trovare ulteriori informazioni su come impostare e utilizzare il giudice nel repository Github. Ci sono anche tre robot di esempio ( Haskell , Python e JavaScript ).

Regole

  • ad ogni cambio di bot * il torneo verrà ripetuto e il giocatore con il maggior numero di punti vince (il tie-break è il primo invio)
  • puoi inviare più di un bot purché non svolgano una strategia comune
  • non ti è permesso di toccare file al di fuori della tua directory (es. manipolazione dei file di altri giocatori)
  • se il tuo bot si arresta in modo anomalo o invia una risposta non valida, il gioco corrente viene chiuso e perdi quel round
  • mentre il giudice (attualmente) non impone un limite di tempo per round, si consiglia di mantenere basso il tempo speso in quanto potrebbe diventare impossibile testare tutti gli invii **
  • abusare di bug nel programma del giudice conta come scappatoia

* Sei incoraggiato a usare Github per inviare separatamente il tuo bot direttamente nella botsdirectory (e potenzialmente modificarlo util.sh)!

** Nel caso in cui diventi un problema che ti verrà comunicato, direi che qualsiasi cosa sotto 500ms (che è molto!) Dovrebbe andare bene per ora.

Chiacchierare

Se hai domande o vuoi parlare di questo KoTH, sentiti libero di unirti alla chat !



Avere spazi quindi un personaggio meta-spazio nei tuoi esempi mi sta facendo impazzire. Alcuni altri esempi sarebbero belli.
Veskah,

@Veskah: ci sono tre esempi di robot collegati, aggiungerò alcuni esempi per i messaggi.
ბიმო

@Veskah: aggiunti alcuni esempi. Btw. puoi anche provare a eseguire il debug di un bot di esempio per vedere in quale formato saranno e testare quale è una risposta valida.
ბიმო

Non hai dato i permessi push quindi non posso spingere il mio bot alla git
Kaito Kid

Risposte:


3

KaitoBot

Utilizza un'implementazione molto grezza dei principi MiniMax. Anche la profondità della ricerca è molto bassa, perché altrimenti impiega troppo tempo.

Potrebbe modificare per migliorare in seguito.

Cerca anche di giocare al Nero, se possibile, perché Wikipedia sembra dire che il Nero ha un vantaggio.

Non ho mai giocato a Gomoku da solo, quindi ho creato le prime tre pietre a caso per mancanza di un'idea migliore.

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

});

EDIT: ha fatto cambiare il seme in modo dinamico perché altrimenti quando i semi hanno superato 2 ^ 52 javascript non è stato in grado di gestire correttamente l'incremento

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.