KoTH:五目並べ(5行連続)


10

Gomoku or Five in a lineは、黒と白の石を使ったグリッドで2人のプレイヤーがプレイするボードゲームです。石を(横、縦、または対角線)続けて配置できる人がゲームに勝利します。515×155

ルール

このKoTHでは、Swap2ルールを再生します。つまり、ゲームは2つのフェーズで構成されます。最初のフェーズでは、2人のプレーヤーが誰が先に行くか、誰が黒をプレーするかを決定し、その後、プレーヤーから始めて各ラウンドに1つの石を置きます。黒を選んだ人。

初期段階

プレーヤーをABにしてAがゲームを開くようにします。

  • Aはボード上に2つの黒と1つの白い石を配置します
  • Bは、次の3つの移動のいずれかを選択できます。
    • プレーヤーBが黒を再生することを決定:初期フェーズが終了しました
    • プレーヤーBが白い石を置くことに決め、白を再生する:初期フェーズが終了しました
    • プレーヤーBが1つの黒と1つの白の石を再生することを決定:Aが色を選択する

ゲームフェーズ

各プレーヤーは、黒をプレイするプレーヤーから始めて、自分の色の石を1つボード上に置きます。これは、プレイする空きスペースがなくなるまで続きます(この場合はタイです)、または1人のプレーヤーがなんとかして石をプレイします行(その場合、そのプレイヤーが勝ちます)。5

行は、水平、垂直、または対角線を意味します。勝利は勝利です。プレーヤーが複数の行を獲得できたかどうかは関係ありません。

KoTHゲームのルール

  • 各プレイヤーは他のプレイヤーと2回対戦します:
    • 最初はだれが最初に行くかはランダムに決定されます
    • 次のゲームでは、最後にプレイしなければならないプレイヤーが最初になります
  • 勝利は2ポイント、同点1、負け0
  • 目標は、できるだけ多くのポイントを獲得することです

あなたのボット

可能な限り多くの言語でこの課題にアクセスできるようにするために、入出力はstdin / stdout(行ベース)を介して行われます。ジャッジプログラムは、ボットのstdinに 1行を出力することによってプログラムにプロンプ​​トを表示し、ボットは1行をstdoutに出力します。

EXITメッセージを受信すると、裁判官がプロセスを強制終了する前に、ファイルへの書き込みを完了するための0.5秒が与えられます。

ランダム性

トーナメントを検証可能にするために、裁判官はシードされたランダム化を使用し、ボットも同じ理由でそうする必要があります。ボットには、使用するコマンドライン引数を介してシードが与えられます。次のセクションを参照してください。

議論

ボットは2つのコマンドライン引数を受け取ります。

  1. 対戦相手の名前
  2. ランダム性の種

ユーザーの状態

プログラムはゲームごとに常に新しく開始されるため、ファイルを使用して、保持したい情報を保持する必要があります。現在のディレクトリにあるすべてのファイルの読み取り/書き込み、またはサブフォルダーの作成/削除を行うことができます。親ディレクトリ内のファイルへのアクセスは許可されていません!

入出力フォーマット

BOARD現在の石のリストを表すであろう、それだけ石が置かれる位置を示し、各エントリの形式であろうと範囲内の整数であろうとのいずれかであろう(黒)または(白い)。((X,Y),COLOR)XY[0,15)COLOR"B""W"

さらにSP、単一のスペース、それぞれがの範囲にある2つの整数のXYタプル(X,Y)を示し、選択肢を示します。[0,15)|

初期段階では、3種類のメッセージがあります。

Prompt (judge) -> Answer (bot)
"A" SP "[]"  -> XY XY XY
"B" SP BOARD -> "B" | "W" SP XY | XY XY
"C" SP BOARD -> "B" | "W"
  • 最初のメッセージは3つのタプルを要求します。最初の2つは黒い石の位置で、3番目は白い石の位置です。
  • 2番目のメッセージは、次のいずれかを要求します。
    • "B" ->黒を選択
    • "W" SP XY ->白を選び、白い石を XY
    • XY XY -> 2つの石を配置します(1つ目は黒、2つ目は白)。
  • 最後の1つは、再生する色を尋ねるだけです。

その後、通常のゲームが始まり、メッセージがはるかに簡単になります

N BOARD -> XY

どこNラウンドの数である(で始まる)とあなたが石を置く位置になります。0XY


回答を期待しない追加のメッセージが1つあります

"EXIT" SP NAME | "EXIT TIE"

NAME勝ったボットの名前はどこですか。2番目のメッセージは、誰も勝てず、石を置くための空きスペースがなくなったためにゲームが終了した場合に送信されます(これは、ボットに名前を付けることができないことを意味します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/1stderrを無効/有効にする)をそれぞれ含むファイルを追加します別の行に。

トーナメントを実行するには、ただ実行./gomokuし、単一のボットをデバッグして実行し./gomoku -d BOTます。

注:ジャッジのセットアップと使用方法の詳細については、Githubリポジトリを参照してください。3つのボットの例(HaskellPythonJavaScript)もあります

ルール

  • ボットの変更ごとに*トーナメントが再実行され、最も多くのポイントを獲得したプレイヤーが勝利します(タイブレーカーが最初に提出されます)
  • 共通の戦略を実行しない限り、複数のボットを送信できます
  • ディレクトリ外のファイルにアクセスすることはできません(例:他のプレイヤーのファイルの操作)
  • ボットがクラッシュしたり無効な応答を送信したりすると、現在のゲームが終了し、そのラウンドに敗北します
  • 裁判官は(現在)ラウンドごとの時間制限を強制していませんが、すべての提出物をテストすることが不可能になる可能性があるため、費やす時間を低く保つことをお勧めします**
  • 裁判官プログラムのバグの悪用は抜け穴として数えられる

* Githubを使用して、ボットを直接botsディレクトリに直接送信することをお勧めします(変更する可能性もありますutil.sh

**通知される問題が発生した場合は、500ミリ秒未満(それで十分です)であれば今のところ問題ないはずです。

チャット

質問がある場合、またはこのKoTHについて話したい場合は、チャットに参加してください。



あなたの例にスペースとメタスペースのキャラクターがあることは私の心を吹き飛ばしています。さらにいくつかの例がいいでしょう。
ベスカ

@Veskah:リンクされたボットの例が3つあります。メッセージの例をいくつか追加します。
ბიმო

@Veskah:いくつかの例を追加しました。ところで また、例のボットをデバッグして、それらがどの形式になるかを確認し、有効な応答をテストすることもできます。
ბიმო

あなたはプッシュ権限を与えなかったので、ボットをgitにプッシュできません
Kaito Kid

回答:


3

KaitoBot

MiniMaxの原則の非常に粗雑な実装を使用しています。検索の深さも非常に低くなります。さもないと、時間がかかりすぎるためです。

後で改善するために編集する場合があります。

ウィキペディアはブラックが有利だと言っているように見えるので、それはまた可能であればブラックをプレイしようとします。

私は五目並べをしたことがないので、最初の3つの石をランダムに設定しました。

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が増分を正しく処理できなかったため、シードを動的に変更しました。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.