KoTH: Gomoku (Cinco seguidos)

10

Gomoku o Five in a row es un juego de mesa jugado por dos jugadores en una cuadrícula con piedras blancas y negras. Quien sea capaz de colocar piedras en una fila (horizontal, vertical o diagonal) gana el juego.515×155 5

Reglas

En este KoTH jugaremos la regla Swap2, lo que significa que un juego consta de dos fases: en la fase inicial, los dos jugadores determinan quién va primero / quién juega negro, después de eso colocarán una piedra cada ronda comenzando con el jugador quien escogió negro.

Fase inicial

Deje que los jugadores sean A y B, y A abrirá el juego:

  • A coloca dos piedras negras y una blanca en el tablero
  • B puede elegir uno de los siguientes tres movimientos:
    • el jugador B decide jugar negro: la fase inicial ha terminado
    • el jugador B decide colocar una piedra blanca y juega blanco: la fase inicial ha terminado
    • el jugador B decide jugar una piedra negra y una blanca: A elige el color

Fase del juego

Cada jugador coloca una piedra de su color en el tablero, comenzando con el jugador que juega negro, esto continúa hasta que no haya más espacios libres para jugar (en cuyo caso es un empate) o un jugador logra jugar piedras en un fila (en cuyo caso ese jugador gana).5 5

Una fila significa horizontal, vertical o diagonal. Una victoria es una victoria: no importa si el jugador logró anotar más de una fila.

Reglas del juego KoTH

  • cada jugador juega uno contra el otro dos veces:
    • inicialmente se decidirá al azar quién va primero
    • en el próximo juego el jugador que jugó el último va primero
  • una victoria vale 2 puntos, un empate 1 y una derrota 0
  • el objetivo es anotar tantos puntos como sea posible

Su bot

Para que este desafío sea accesible para tantos idiomas como sea posible, la entrada / salida se realizará mediante stdin / stdout (basado en línea). El programa de jueces le indicará a su programa imprimiendo una línea en la entrada estándar de su bot y su bot imprimirá una línea en la salida estándar .

Una vez que reciba un EXITmensaje, se le dará medio segundo para terminar de escribir en los archivos antes de que el juez cierre el proceso.

Aleatoriedad

Para que los torneos sean verificables, el juez utiliza la asignación al azar sembrada y su bot también debe hacerlo, por la misma razón. Al bot se le dará una semilla a través de un argumento de línea de comandos que debe usar, consulte la siguiente sección.

Argumentos

El bot recibe dos argumentos de línea de comandos:

  1. nombre del oponente
  2. semilla de aleatoriedad

Estado del usuario

Debido a que su programa siempre comenzará de nuevo para cada juego, necesitará usar archivos para conservar cualquier información que desee conservar. Se le permite leer / escribir cualquier archivo o crear / eliminar subcarpetas en su directorio actual. ¡No está permitido acceder a ningún archivo en ningún directorio principal!

Formato de entrada / salida

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

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

En la fase inicial hay tres tipos diferentes de mensajes:

Prompt (judge) -> Answer (bot)
"A" SP "[]"  -> XY XY XY
"B" SP BOARD -> "B" | "W" SP XY | XY XY
"C" SP BOARD -> "B" | "W"
  • El primer mensaje pide tres tuplas, las dos primeras serán las posiciones de las piedras negras y la tercera la posición de las blancas.
  • El segundo mensaje pide:
    • "B" -> elegir negro
    • "W" SP XY -> elige blanco y coloca una piedra blanca en XY
    • XY XY -> coloca dos piedras (la primera negra y la segunda blanca)
  • El último solo pregunta qué color quieres jugar

Después de eso, comenzará el juego normal y los mensajes serán mucho más simples.

N BOARD -> XY

N0 0XY


Hay un mensaje adicional que no espera una respuesta.

"EXIT" SP NAME | "EXIT TIE"

donde NAMEestá el nombre del bot que ganó. El segundo mensaje se enviará si el juego termina debido a que nadie gana y no hay más espacios libres para colocar piedras (esto implica que su bot no puede ser nombrado TIE).

Formateo

Dado que los mensajes del bot pueden decodificarse sin espacios, todos los espacios serán ignorados (por ejemplo, (0 , 0) (0,12)se trata de la misma manera que (0,0)(0,12)). Los mensajes del juez solo contienen un espacio para separar diferentes secciones (es decir, como se señaló anteriormente con SP), lo que le permite dividir la línea en espacios.

Cualquier respuesta no válida resultará en una pérdida de esa ronda (aún recibirá un EXITmensaje), vea las reglas.

Ejemplo

Aquí hay algunos ejemplos de mensajes reales:

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

Juez

Puede encontrar el programa de jueces aquí : para agregarle un bot, simplemente cree una nueva carpeta en la botscarpeta, coloque sus archivos allí y agregue un archivo que metacontenga nombre , comando , argumentos y una marca 0/1 (deshabilitar / habilitar stderr ) cada uno En una línea separada.

Para ejecutar un torneo solo debes ejecutar ./gomokuy depurar una sola ejecución de bot ./gomoku -d BOT.

Nota: Puede encontrar más información sobre cómo configurar y usar el juez en el repositorio de Github. También hay tres bots de ejemplo ( Haskell , Python y JavaScript ).

Reglas

  • en cada cambio de bot * el torneo se volverá a ejecutar y el jugador con más puntos gana (el desempate es la primera presentación)
  • puedes enviar más de un bot siempre que no jueguen una estrategia común
  • no se le permite tocar archivos fuera de su directorio (por ejemplo, manipular los archivos de otros reproductores)
  • Si su bot se bloquea o envía una respuesta no válida, el juego actual se termina y usted pierde esa ronda.
  • Si bien el juez (actualmente) no aplica un límite de tiempo por ronda, se le recomienda que mantenga el tiempo bajo, ya que podría no ser factible probar todas las presentaciones **
  • abusar de los errores en el programa de jueces cuenta como escapatoria

* ¡Se le recomienda usar Github para enviar su bot por separado directamente en el botsdirectorio (y posiblemente modificarlo util.sh)!

** En caso de que se convierta en un problema, se le notificará, diría que cualquier cosa por debajo de 500 ms (¡eso es mucho!) Debería estar bien por ahora.

Charla

Si tiene preguntas o desea hablar sobre este KoTH, ¡siéntase libre de unirse al Chat !

ბიმო
fuente
Relacionado
ბიმო
Tener espacios, entonces un personaje metaespacio en tus ejemplos me está volviendo loco. Algunos ejemplos más serían buenos.
Veskah el
@Veskah: Hay tres bots de ejemplo vinculados, agregaré algunos ejemplos para mensajes.
ბიმო
@Veskah: se agregaron algunos ejemplos. Por cierto. También puede intentar depurar un bot de ejemplo para ver en qué formato estarán y probar cuál es una respuesta válida.
ბიმო
No me diste permisos para empujar, así que no puedo empujar mi bot al git
Kaito Kid

Respuestas:

3

KaitoBot

Utiliza una implementación muy cruda de los principios MiniMax. La profundidad de la búsqueda también es muy baja, porque de lo contrario lleva demasiado tiempo.

Podría editar para mejorar más tarde.

También trata de jugar al negro si es posible, porque Wikipedia parece decir que el negro tiene una ventaja.

Nunca he jugado a Gomoku, así que configuré las primeras tres piedras al azar por falta de una mejor idea.

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

});

EDICIONES: hizo que la semilla cambiara dinámicamente porque, de lo contrario, cuando las semillas superaran los 2 ^ 52, JavaScript no podría manejar el incremento correctamente

niño Kaito
fuente