KoTH: Gomoku (Lima berturut-turut)

10

Gomoku atau Five berturut-turut adalah permainan papan yang dimainkan oleh dua pemain pada kotak dengan batu hitam dan putih. Siapa pun yang dapat menempatkan 5 batu berturut-turut (horizontal, vertikal atau diagonal) akan memenangkan permainan.15×155

Aturan

Dalam KoTH ini kita akan memainkan aturan Swap2, yang berarti bahwa permainan terdiri dari dua fase: Pada fase awal kedua pemain menentukan siapa yang menjadi yang pertama / yang bermain hitam, setelah itu mereka akan menempatkan satu batu di setiap babak dimulai dengan pemain yang memilih hitam.

Tahap awal

Biarkan pemain menjadi A & B dan A akan membuka permainan:

  • Tempat dua batu hitam dan satu putih di papan tulis
  • B dapat memilih salah satu dari tiga langkah berikut:
    • pemain B memutuskan untuk bermain hitam: fase awal berakhir
    • pemain B memutuskan untuk menempatkan batu putih dan memainkan putih: fase awal berakhir
    • pemain B memutuskan untuk memainkan satu batu hitam dan satu putih: A dapat memilih warnanya

Fase permainan

Setiap pemain menempatkan satu batu dengan warna mereka di papan, dimulai dengan pemain yang bermain hitam, ini berlangsung sampai tidak ada lagi ruang kosong untuk dimainkan (dalam hal ini seri) atau satu pemain berhasil memainkan batu dalam sebuah baris (dalam hal ini pemain itu menang).5

Baris berarti horizontal, vertikal atau diagonal. Kemenangan adalah kemenangan - tidak masalah apakah pemain berhasil mencetak lebih dari satu baris.

Aturan permainan KoTH

  • setiap pemain bermain melawan satu sama lain pemain dua kali:
    • awalnya akan ditentukan secara acak siapa yang pergi dulu
    • di game berikutnya, pemain yang bermain terakhir harus pergi terlebih dahulu
  • menang bernilai 2 poin, seri 1 dan kalah 0
  • tujuannya adalah untuk mencetak poin sebanyak mungkin

Bot Anda

Untuk membuat tantangan ini dapat diakses untuk sebanyak mungkin bahasa input / output akan melalui stdin / stdout (berbasis garis). Program juri akan meminta program Anda dengan mencetak baris ke stdin bot Anda dan bot Anda akan mencetak satu baris ke stdout .

Setelah Anda menerima EXITpesan, Anda akan diberikan setengah detik untuk menyelesaikan menulis ke file sebelum hakim akan menghentikan proses.

Keserampangan

Untuk membuat turnamen diverifikasi hakim menggunakan pengacakan unggulan dan bot Anda harus melakukannya juga, untuk alasan yang sama. Bot akan diberi seed melalui argumen command-line yang harus digunakan, silakan merujuk ke bagian selanjutnya.

Argumen

Bot menerima dua argumen baris perintah:

  1. nama lawan
  2. benih untuk keacakan

Status pengguna

Karena program Anda akan selalu dimulai baru untuk setiap gim, Anda harus menggunakan file untuk mempertahankan informasi apa pun yang ingin Anda simpan. Anda diperbolehkan membaca / menulis file apa pun atau membuat / menghapus sub-folder di direktori Anda saat ini. Anda tidak diperbolehkan mengakses file apa pun di direktori orang tua mana pun!

Input / Output format

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

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

Pada fase awal ada tiga jenis pesan:

Prompt (judge) -> Answer (bot)
"A" SP "[]"  -> XY XY XY
"B" SP BOARD -> "B" | "W" SP XY | XY XY
"C" SP BOARD -> "B" | "W"
  • Pesan pertama meminta tiga tupel, dua yang pertama adalah posisi batu hitam dan yang ketiga posisi untuk yang putih.
  • Pesan kedua meminta:
    • "B" -> pilih hitam
    • "W" SP XY -> Pilih putih dan letakkan batu putih di XY
    • XY XY -> Tempatkan dua batu (yang pertama hitam dan yang kedua putih)
  • Yang terakhir hanya menanyakan warna yang ingin Anda mainkan

Setelah itu permainan reguler akan dimulai dan pesan-pesannya akan menjadi lebih sederhana

N BOARD -> XY

N0XY


Ada satu pesan tambahan yang tidak mengharapkan jawaban

"EXIT" SP NAME | "EXIT TIE"

di mana NAMEnama bot yang menang. Pesan kedua akan dikirim jika permainan berakhir karena tidak ada yang menang dan tidak ada lagi ruang kosong untuk meletakkan batu (ini menyiratkan bahwa bot Anda tidak dapat disebutkan namanya TIE).

Memformat

Karena pesan dari bot dapat didekodekan tanpa spasi, semua spasi akan diabaikan (mis. (0 , 0) (0,12)Diperlakukan sama dengan (0,0)(0,12)). Pesan dari juri hanya berisi spasi untuk memisahkan bagian yang berbeda (mis. Seperti yang disebutkan di atas dengan SP), memungkinkan Anda untuk membagi garis pada spasi.

Setiap tanggapan yang tidak valid akan mengakibatkan hilangnya putaran itu (Anda masih akan menerima EXITpesan), lihat aturan.

Contoh

Berikut ini beberapa contoh pesan aktual:

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

Hakim

Anda dapat menemukan program juri di sini : Untuk menambahkan bot ke sana cukup buat folder baru di botsfolder, letakkan file Anda di sana dan tambahkan file yang metaberisi nama , perintah , argumen , dan flag 0/1 (nonaktifkan / aktifkan stderr ) masing-masing pada jalur terpisah.

Untuk menjalankan turnamen, jalankan saja ./gomokudan debug satu bot yang dijalankan ./gomoku -d BOT.

Catatan: Anda dapat menemukan informasi lebih lanjut tentang cara mengatur dan menggunakan juri di repositori Github. Ada juga tiga bot contoh ( Haskell , Python dan JavaScript ).

Aturan

  • pada setiap perubahan bot * turnamen akan dijalankan kembali dan pemain dengan poin terbanyak menang (tie-breaker adalah pengiriman pertama)
  • Anda dapat mengirimkan lebih dari satu bot selama mereka tidak memainkan strategi yang sama
  • Anda tidak diperbolehkan menyentuh file di luar direktori Anda (mis. memanipulasi file pemain lain)
  • jika bot Anda crash atau mengirim respons yang tidak valid, game saat ini dihentikan dan Anda kehilangan putaran itu
  • sementara juri (saat ini) tidak memberlakukan batas waktu per putaran, Anda disarankan untuk menjaga waktu yang dihabiskan rendah karena bisa menjadi tidak layak untuk menguji semua pengajuan **
  • bug yang menyalahgunakan dalam program hakim dianggap sebagai celah

* Anda disarankan untuk menggunakan Github untuk mengirimkan bot Anda secara langsung ke botsdirektori secara terpisah (dan berpotensi memodifikasi util.sh)!

** Jika itu menjadi masalah Anda akan diberitahu, saya akan mengatakan apa pun di bawah 500ms (itu banyak!) Harus baik-baik saja untuk saat ini.

Obrolan

Jika Anda memiliki pertanyaan atau ingin membicarakan KoTH ini, silakan bergabung dengan Obrolan !

ბიმო
sumber
Terkait
ბიმო
Memiliki spasi maka karakter ruang meta dalam contoh Anda membuat pikiran saya melayang. Beberapa contoh lagi akan menyenangkan.
Veskah
@ Veska: Ada tiga contoh bot yang ditautkan, saya akan menambahkan beberapa contoh untuk pesan.
ბიმო
@ Veskah: Menambahkan beberapa contoh. Btw. Anda juga dapat mencoba men-debug bot contoh untuk melihat format apa yang akan digunakan dan menguji apa respons yang valid.
ბიმო
Anda tidak memberikan izin push jadi saya tidak bisa mendorong bot saya ke git
Kaito Kid

Jawaban:

3

KaitoBot

Ini menggunakan implementasi prinsip MiniMax yang sangat kasar. Kedalaman pencarian juga sangat rendah, karena kalau tidak, akan terlalu lama.

Mungkin edit untuk ditingkatkan nanti.

Ia juga mencoba memainkan Black jika memungkinkan, karena Wikipedia tampaknya mengatakan bahwa Black memiliki kelebihan.

Saya belum pernah bermain Gomoku sendiri, jadi saya mengatur tiga batu pertama secara acak karena kurang ide yang lebih baik.

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

});

EDIT: Membuat benih berubah secara dinamis karena jika benih melebihi 2 ^ 52 javascript tidak dapat menangani kenaikan dengan benar

Kaito Kid
sumber