KoTH : Gomoku (5 행)


10

Gomoku 또는 Five in a row 는 검은 색과 흰색 돌이 있는 그리드 에서 두 명의 플레이어가 즐기는 보드 게임 입니다. 5 개의 돌을 연속으로 (수평, 수직 또는 대각선) 배치 할 수있는 사람이 게임에서 승리합니다.15×155

규칙

이 KoTH에서는 Swap2 규칙을 사용합니다. 이는 게임이 두 단계로 구성됨을 의미합니다. 초기 단계에서 두 명의 플레이어는 누가 먼저 갔는지 / 흑인을하는지 결정합니다. 누가 검은 색을 선택했습니다.

초기 단계

플레이어가 A & B가 되고 A 가 게임을 열게하십시오.

  • A는 보드에 검은 색이 하나의 흰 돌을 배치
  • B 는 다음 세 가지 동작 중 하나를 선택할 수 있습니다.
    • 플레이어 B 는 검은 색으로 플레이하기로 결정합니다 : 초기 단계가 끝났습니다
    • 플레이어 B 는 흰 돌을 놓기로 결정하고 흰색을한다 : 초기 단계는 끝났다
    • 플레이어 B는 하나 개의 검은 한 흰 돌을 연주하기로 결정 : A는 색상을 선택하는 얻는다

게임 단계

각 플레이어는 자신의 색깔의 돌 하나를 보드에 놓고 검은 색을하는 플레이어부터 시작합니다. 더 이상 여유 공간이 없을 때 (동점 인 경우) 또는 한 플레이어가 돌 을 재생할 수 있습니다 . 행 (이 경우 해당 선수가 승리).5

행은 가로, 세로 또는 대각선을 의미합니다. 승리는 승리입니다. 플레이어가 두 행 이상을 득점했는지 여부는 중요하지 않습니다.

KoTH 게임 규칙

  • 각 플레이어는 서로에게 두 번 플레이합니다.
    • 처음에는 누가 먼저 갈지 무작위로 결정됩니다
    • 다음 게임에서 마지막으로 플레이 한 플레이어가 먼저갑니다
  • 승리는 2 점, 동점 1, 패 0입니다
  • 목표는 가능한 많은 점수를 얻는 것입니다

당신의 로봇

가능한 많은 언어에서이 문제에 액세스 할 수 있도록하려면 stdin / stdout (라인 기반)을 통해 입력 / 출력을 수행하십시오 . 판사 프로그램은 봇의 표준 입력에 라인을 인쇄하여 프로그램에 프롬프트를 표시 하고 봇은 stdout에 한 라인을 인쇄 합니다.

EXIT메시지 를 받으면 판사가 프로세스를 종료하기 전에 파일 쓰기를 완료하는 데 0.5 초의 시간이 주어집니다.

무작위성

토너먼트를 확인하기 위해 판사는 시드 무작위 배정을 사용하며 같은 이유로 봇도 그렇게해야합니다. 봇은 사용할 명령 줄 인수를 통해 시드를 제공받습니다. 다음 섹션을 참조하십시오.

인수

봇은 두 가지 명령 줄 인수를받습니다.

  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 리포지토리에서 판단을 설정하고 사용하는 방법에 대한 자세한 정보를 찾을 수 있습니다. 3 개의 예제 봇 ( Haskell , PythonJavaScript )도 있습니다.

규칙

  • 봇이 바뀔 때마다 * 토너먼트가 재실행되고 가장 많은 점수를 얻은 플레이어가 승리합니다 (타이 브레이커가 먼저 제출 됨)
  • 일반적인 전략을 따르지 않는 한 둘 이상의 봇을 제출할 수 있습니다.
  • 디렉토리 외부의 파일 (예 : 다른 플레이어의 파일 조작)을 만질 수 없습니다
  • 봇이 충돌하거나 유효하지 않은 응답을 보내면 현재 게임이 종료되고 해당 라운드를 잃습니다.
  • 심사 위원 (현재)은 라운드 당 시간 제한을 시행하지 않지만 모든 제출물을 테스트하는 것이 불가능할 수 있으므로 시간을 적게 유지하는 것이 좋습니다 **
  • 판사 프로그램의 남용 버그는 허점으로 간주

* Github을 사용하여 bots디렉토리 에서 직접 봇을 직접 제출 하고 수정 할 수 util.sh있습니다!

** 그것이 당신에게 통보 될 문제가된다면, 500ms 이하 (많은 것입니다)는 지금은 좋을 것이라고 말하고 싶습니다.

잡담

궁금한 점이 있거나이 KoTH에 대해 이야기하고 싶다면 언제든지 채팅 에 참여하십시오 !



예를 들어 공백이 있으면 메타 공간 문자가 마음을 아프게합니다. 더 많은 예제가 좋을 것입니다.
Veskah

@Veskah : 3 개의 예제 봇이 연결되어 있습니다. 메시지에 대한 몇 가지 예를 추가하겠습니다.
ბიმო

@Veskah : 몇 가지 예를 추가했습니다. Btw. 또한 예제 봇을 디버깅하여 형식이 어떤 형식인지 확인하고 유효한 응답이 무엇인지 테스트 할 수 있습니다.
ბიმო

푸시 권한을 부여하지 않아 봇을 자식으로 푸시 할 수 없습니다.
Kaito Kid

답변:


3

카이토 봇

MiniMax 원칙의 매우 조잡한 구현을 사용합니다. 그렇지 않으면 너무 오래 걸리기 때문에 검색 깊이도 매우 낮습니다.

나중에 개선하기 위해 편집 할 수 있습니다.

위키 백과는 블랙이 장점이 있다고 말하는 것처럼 보이기 때문에 가능하면 블랙을 시도합니다.

나는 Gomoku를 한 번도 해본 적이 없으므로 더 나은 아이디어가 없기 때문에 처음 세 개의 돌을 무작위로 설정했습니다.

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 : 시드가 2 ^ 52를 초과하면 자바 스크립트가 증가를 올바르게 처리 할 수 ​​없기 때문에 시드가 동적으로 변경되었습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.