KoTH: Gomoku (Cinco em linha)

10

Gomoku ou Five in a row é um jogo de tabuleiro jogado por dois jogadores em uma grade de x com pedras em preto e branco. Quem conseguir colocar pedras seguidas (horizontal, vertical ou diagonal) vence o jogo.515×155

Regras

Neste KoTH, jogaremos a regra Swap2, o que significa que um jogo consiste em duas fases: na fase inicial, os dois jogadores determinam quem vai primeiro / quem joga de preto; depois disso, eles colocam uma pedra a cada rodada começando com o jogador. quem escolheu preto.

Fase inicial

Sejam os jogadores A e B e A deve abrir o jogo:

  • A coloca duas pedras pretas e uma branca no quadro
  • B pode escolher um dos três seguintes movimentos:
    • o jogador B decide jogar preto: a fase inicial acabou
    • o jogador B decide colocar uma pedra branca e joga em branco: a fase inicial acabou
    • O jogador B decide jogar uma pedra preta e uma branca: A escolhe a cor

Fase do jogo

Cada jogador coloca uma pedra da sua cor no tabuleiro, começando com o jogador que joga preto, e continua até que não haja mais espaços livres para jogar (nesse caso, é um empate) ou um jogador consegue jogar pedras em um linha (nesse caso, o jogador vence).5

Uma linha significa horizontal, vertical ou diagonal. Uma vitória é uma vitória - não importa se o jogador conseguiu marcar mais de uma linha.

Regras do jogo KoTH

  • cada jogador joga contra o outro jogador duas vezes:
    • inicialmente será decidido aleatoriamente quem vai primeiro
    • no próximo jogo, o jogador que jogou por último vai primeiro
  • uma vitória vale 2 pontos, um empate 1 e uma perda 0
  • o objetivo é marcar o máximo de pontos possível

Seu bot

Para tornar esse desafio acessível para o maior número possível de idiomas, a entrada / saída será via stdin / stdout (baseado em linha). O programa juiz julgará seu programa imprimindo uma linha no stdin do seu bot e ele imprimirá uma linha no stdout .

Depois de receber uma EXITmensagem, você receberá meio segundo para terminar de gravar os arquivos antes que o juiz encerre o processo.

Aleatoriedade

Para tornar os torneios verificáveis, o juiz usa a randomização semeada e seu bot também deve, pelo mesmo motivo. O bot receberá uma semente via argumento da linha de comando que ele deve usar; consulte a próxima seção.

Argumentos

O bot recebe dois argumentos de linha de comando:

  1. nome do oponente
  2. semente por acaso

Estado do usuário

Como seu programa sempre será iniciado de novo para cada jogo, você precisará usar arquivos para manter as informações que deseja manter. Você tem permissão para ler / gravar qualquer arquivo ou criar / remover subpastas em seu diretório atual. Você não tem permissão para acessar nenhum arquivo em nenhum diretório pai!

Formato de entrada / saída

BOARDdenotará uma lista das pedras de corrente, só lista as posições onde uma pedra é colocado e cada entrada vai ser da forma ((X,Y),COLOR)em que Xe Yserá um número inteiro no intervalo e será ou (preto) ou ( branco).[0,15)COLOR"B""W"

Além disso, SPdenota um único espaço, XYuma tupla (X,Y)de dois números inteiros, cada um no intervalo e indica uma escolha.[0,15)|

Na fase inicial, existem três tipos diferentes de mensagens:

Prompt (judge) -> Answer (bot)
"A" SP "[]"  -> XY XY XY
"B" SP BOARD -> "B" | "W" SP XY | XY XY
"C" SP BOARD -> "B" | "W"
  • A primeira mensagem pede três tuplas, as duas primeiras serão as posições das pedras negras e a terceira a posição das brancas.
  • A segunda mensagem pede:
    • "B" -> escolha preto
    • "W" SP XY -> escolha branco e coloque uma pedra branca em XY
    • XY XY -> coloque duas pedras (uma preta e a segunda branca)
  • O último pergunta apenas qual a cor que você deseja tocar

Depois disso, o jogo regular começará e as mensagens se tornarão muito mais simples

N BOARD -> XY

onde Né o número da rodada (começando com ) e será a posição em que você coloca uma pedra.0XY


Há uma mensagem adicional que não espera uma resposta

"EXIT" SP NAME | "EXIT TIE"

onde NAMEé o nome do bot que ganhou. A segunda mensagem será enviada se o jogo terminar devido a ninguém ganhar e a não haver mais espaços livres para colocar pedras (isso implica que seu bot não pode ser nomeado TIE).

Formatação

Como as mensagens do bot podem ser decodificadas sem espaços, todos os espaços serão ignorados (por exemplo, (0 , 0) (0,12)é tratado da mesma forma que (0,0)(0,12)). As mensagens do juiz contêm apenas um espaço para separar seções diferentes (ou seja, conforme observado acima SP), permitindo que você divida a linha em espaços.

Qualquer resposta inválida resultará na perda dessa rodada (você ainda receberá uma EXITmensagem). Consulte as regras.

Exemplo

Aqui estão alguns exemplos de mensagens reais:

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

Juiz

Você pode encontrar o programa do juiz aqui : Para adicionar um bot a ele, simplesmente crie uma nova pasta na botspasta, coloque seus arquivos lá e adicione um arquivo metacontendo nome , comando , argumentos e um sinalizador 0/1 (desativar / ativar stderr ) cada em uma linha separada.

Para executar um torneio, basta executar ./gomokue depurar uma única corrida de bot ./gomoku -d BOT.

Nota: Você pode encontrar mais informações sobre como configurar e usar o juiz no repositório do Github. Existem também três exemplos de bots ( Haskell , Python e JavaScript ).

Regras

  • em cada mudança de um bot * o torneio será executado novamente e o jogador com mais pontos ganha (o desempate é o primeiro envio)
  • você pode enviar mais de um bot, desde que eles não adotem uma estratégia comum
  • você não tem permissão para tocar em arquivos fora do seu diretório (por exemplo, manipular os arquivos de outros jogadores)
  • se o seu bot travar ou enviar uma resposta inválida, o jogo atual será encerrado e você perderá a rodada
  • embora o juiz (atualmente) não imponha um limite de tempo por rodada, é aconselhável manter o tempo gasto baixo, pois seria inviável testar todos os envios **
  • abusar de bugs no programa do juiz conta como brecha

* Você é incentivado a usar o Github para enviar seu bot separadamente diretamente no botsdiretório (e potencialmente modificar util.sh)!

** Caso se torne um problema, você será notificado, diria que qualquer coisa abaixo de 500ms (isso é muito!) Deve ficar bem por enquanto.

Bate-papo

Se você tiver dúvidas ou quiser falar sobre esse KoTH, sinta-se à vontade para participar do bate - papo !

ბიმო
fonte
Ter espaços, em seguida, um caractere metaespaço em seus exemplos está me surpreendendo. Mais alguns exemplos seriam bons.
Veskah 23/08/18
@Veskah: Existem três exemplos de bots vinculados, vou adicionar alguns exemplos para mensagens.
ბიმო
@Veskah: Adicionado alguns exemplos. Btw. você também pode tentar depurar um exemplo de bot para ver em qual formato ele estará e testar o que é uma resposta válida.
ბიმო
Você não deu permissões de pressão, então não posso empurrar o meu bot ao git
Kaito Kid

Respostas:

3

KaitoBot

Ele usa uma implementação muito grosseira dos princípios MiniMax. A profundidade da pesquisa também é muito baixa, porque, caso contrário, leva muito tempo.

Pode editar para melhorar mais tarde.

Ele também tenta jogar preto, se possível, porque a Wikipedia parece dizer que o preto tem uma vantagem.

Eu nunca joguei Gomoku, então montei as três primeiras pedras aleatoriamente por falta de uma idéia melhor.

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

});

EDITAS: Alterou a semente dinamicamente porque, caso contrário, quando as sementes excederam 2 ^ 52, o javascript não conseguiu lidar com o incremento corretamente

Kaito Kid
fonte