KoTH: Гомоку (п'ять поспіль)


10

Гомоку або П'ять поспіль - настільна гра, яку грають два гравці на сітці з чорно-білими каменями. Хто вміє розмістити 5 каменів поспіль (горизонтальну, вертикальну чи діагональну), виграє гру.15×155

Правила

У цьому KoTH ми будемо грати правило Swap2, тобто гра складається з двох фаз: На початковій фазі два гравці визначають, хто йде першим / хто грає чорний, після чого вони розміщуватимуть один камінь у кожному раунді, починаючи з гравця хто вибрав чорне.

Початкова фаза

Нехай гравці A & B і A відкриють гру:

  • Поміщає два чорних і один білий камінь на дошці
  • B може вибрати один з наступних трьох кроків:
    • Гравець B вирішує грати в чорний колір: початкова фаза закінчена
    • Гравець В вирішує поставити білий камінь і грає в білий: початкова фаза закінчена
    • Гравець B вирішує зіграти один чорний та один білий камінь: A вибирає колір

Фаза гри

Кожен гравець розміщує один камінь свого кольору на дошці, починаючи з гравця, який грає в чорний колір, і так триває, поки не буде більше вільних місць для гри (у цьому випадку це нічия) або одного гравця вдається зіграти каменів у рядок (у такому випадку виграє той гравець).5

Ряд означає горизонтальну, вертикальну або діагональну. Виграш - це виграш - не має значення, чи вдалося гравцеві забити більше одного ряду.

Правила гри KoTH

  • кожен гравець грає один проти одного гравця двічі:
    • спочатку буде випадково вирішуватися, хто йде першим
    • У наступній грі першим виходить гравець, який повинен був грати останнім
  • виграш вартує 2 бали, нічия 1 та програш 0
  • мета - набрати якомога більше балів

Ваш бот

Зробити це завдання доступним для якомога більшої кількості мов, введення / виведення здійснюватиметься через stdin / stdout (на основі рядків). Програма судді підкаже вашу програму, надрукувавши рядок на stdin вашого бота, і ваш бот надрукує один рядок до stdout .

Як тільки ви отримаєте EXITповідомлення, вам буде надано півсекунди, щоб закінчити писати у файли, перш ніж суддя вб'є процес.

Випадковість

Щоб зробити турніри перевіреними, суддя використовує насіннєву рандомізацію, і ваш бот повинен зробити це теж з тієї ж причини. Боту буде надано насіння через аргумент командного рядка, який він повинен використовувати, зверніться до наступного розділу.

Аргументи

Бот отримує два аргументи командного рядка:

  1. ім'я противника
  2. насіння для випадковості

Стан користувача

Оскільки ваша програма завжди буде запускатися новою для кожної гри, вам потрібно буде використовувати файли для збереження будь-якої інформації, яку ви хочете зберегти. Вам дозволяється читати / писати будь-які файли або створювати / видаляти підпапки у вашому поточному каталозі. Вам заборонено отримувати доступ до жодних файлів у будь-якому батьківському каталозі!

Формат вводу / виводу

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

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

На початковій фазі є три різні види повідомлень:

Prompt (judge) -> Answer (bot)
"A" SP "[]"  -> XY XY XY
"B" SP BOARD -> "B" | "W" SP XY | XY XY
"C" SP BOARD -> "B" | "W"
  • Перше повідомлення вимагає трьох кортежів, перші два - це положення чорних каменів, а третє - положення білого.
  • Друге повідомлення просить або:
    • "B" -> вибрати чорний
    • "W" SP XY -> виберіть біле і помістіть білий камінь на XY
    • XY XY -> розмістіть два камені (перший - чорний, другий - білий)
  • Останній просто запитує, в який колір ви хочете грати

Після цього розпочнеться звичайна гра, і повідомлення стануть набагато простішими

N BOARD -> XY

N0XY


Є ще одне додаткове повідомлення, яке не очікує відповіді

"EXIT" SP NAME | "EXIT TIE"

де NAMEім’я бота, який виграв. Друге повідомлення буде надіслано, якщо гра закінчиться через те, що ніхто не виграє і більше немає вільних місця для розміщення каменів (це означає, що вашого бота не можна назвати TIE).

Форматування

Оскільки повідомлення від бота можна декодувати без пробілів, усі пробіли будуть ігноровані (наприклад (0 , 0) (0,12), обробляються так само, як (0,0)(0,12)). Повідомлення судді містять пробіл для розділення різних розділів (тобто, як зазначено вище SP), що дозволяє розділити рядок на пробіли.

Будь-яка недійсна відповідь призведе до втрати цього раунду (ви все одно отримаєте EXITповідомлення), див. Правила.

Приклад

Ось кілька прикладів фактичних повідомлень:

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

Суддя

Ви можете знайти програму судді тут : Щоб додати бота до неї, просто створіть нову папку в botsпапці, розмістіть там свої файли та додайте файл, metaщо містить ім’я , команду , аргументи та прапор 0/1 (відключити / включити stderr ) кожен на окремому рядку.

Для запуску турніру просто запустіть ./gomokuі налагоджуйте єдиний запуск бота ./gomoku -d BOT.

Примітка. Ви можете знайти більше інформації про налаштування та використання судді у сховищі Github. Також є три приклади ботів ( Haskell , Python та JavaScript ).

Правила

  • при кожній зміні бота * турнір буде повторюватися і гравець, який отримає найбільшу кількість очок, виграє (перший тайм-брейк)
  • ви можете подати більше одного бота, доки вони не грають загальну стратегію
  • вам заборонено торкатися файлів поза каталогом (наприклад, маніпулювання файлами іншого гравця)
  • якщо ваш бот виходить з ладу або надсилає недійсну відповідь, поточна гра припиняється, і ви програєте цей раунд
  • поки суддя (на даний момент) не виконує обмеження часу на раунд, вам рекомендується тримати витрачений час низьким, оскільки це може стати нездійсненним для перевірки всіх подань **
  • зловживання помилками в програмі судді вважається лазівкою

* Вам рекомендується використовувати Github для того, щоб окремо подати свого бота безпосередньо в botsкаталог (і, можливо, змінити util.sh)!

** У випадку, якщо це стане проблемою, про яку ви отримаєте сповіщення, зараз я б сказав, що все, що не перевищує 500 мс (це багато!), Має бути добре.

Чат

Якщо у вас є питання або хочете поговорити про цей KoTH, не соромтесь приєднатися до чату !



Якщо у ваших прикладах є пробіли, то мета-простірний символ символізує мій розум. Ще кілька прикладів було б непогано.
Веська

@Veskah: Три приклади ботів пов'язані, я додаю кілька прикладів для повідомлень.
ბიმო

@Veskah: Додано кілька прикладів. Btw. ви також можете спробувати налагодити приклад-бот, щоб побачити, у якому форматі вони будуть знаходитися, і перевірити, що є дійсною відповіддю.
ბიმო

Ви не давали дозволу наштовхуватися, тому я не можу підштовхнути свого бота до суглоба
Kaito Kid

Відповіді:


3

KaitoBot

Тут використовується дуже жорстка реалізація принципів MiniMax. Глибина пошуку також дуже низька, тому що в іншому випадку це відбувається занадто довго.

Можна змінити, щоб згодом покращити.

Він також намагається грати в чорний, якщо це можливо, тому що Вікіпедія, схоже, говорить, що Чорний має перевагу.

Я ніколи не грав у Гомоку, тому я встановив перші три камені навмання через відсутність кращої ідеї.

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

});

РЕДАКЦІЯ: Здійснено зміну насіння динамічно, оскільки в іншому випадку, коли насіння перевищило 2 ^ 52, JavaScript не міг правильно обробити прирост

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.