KoTH: Gomoku (Năm liên tiếp)


10

Carô hoặc Năm liên tiếp là một trò chơi hội đồng quản trị được chơi bởi hai người chơi trên một lưới với đá màu đen và trắng. Bất cứ ai có thể đặt 5 viên đá liên tiếp (ngang, dọc hoặc chéo) sẽ thắng trò chơi.15×155

Quy tắc

Trong KoTH này, chúng ta sẽ chơi quy tắc Swap2, nghĩa là một trò chơi bao gồm hai giai đoạn: Trong giai đoạn đầu, hai người chơi sẽ xác định ai đi trước / ai chơi đen, sau đó họ sẽ đặt một viên đá mỗi vòng bắt đầu với người chơi ai chọn màu đen.

Giai đoạn đầu

Hãy để người chơi là A & BA sẽ mở trò chơi:

  • Một nơi đặt hai viên đá đen và trắng trên bảng
  • B có thể chọn một trong ba động tác sau:
    • Người chơi B quyết định chơi màu đen: giai đoạn đầu đã kết thúc
    • Người chơi B quyết định đặt một viên đá trắng và chơi trắng: giai đoạn đầu đã kết thúc
    • Người chơi B quyết định chơi một viên đá đen và trắng: A được chọn màu

Giai đoạn trò chơi

Mỗi người chơi đặt một viên đá màu của họ lên bảng, bắt đầu bằng người chơi chơi màu đen, điều này diễn ra cho đến khi không còn chỗ trống nào để chơi (trong trường hợp đó là cà vạt) hoặc một người chơi quản lý để chơi viên đá trong một hàng (trong trường hợp người chơi đó thắng).5

Một hàng có nghĩa là ngang, dọc hoặc chéo. Một chiến thắng là một chiến thắng - không quan trọng việc người chơi có thể ghi được nhiều hơn một hàng hay không.

Luật chơi của KoTH

  • mỗi người chơi đấu với nhau hai lần:
    • ban đầu nó sẽ được quyết định ngẫu nhiên ai đi trước
    • trong trò chơi tiếp theo, người chơi phải chơi lần cuối đi trước
  • một chiến thắng có giá trị 2 điểm, hòa 1 và thua 0
  • mục tiêu là ghi càng nhiều điểm càng tốt

Bot của bạn

Để làm cho thách thức này có thể truy cập được cho càng nhiều ngôn ngữ, đầu vào / đầu ra càng tốt sẽ thông qua stdin / stdout (dựa trên dòng). Chương trình thẩm phán sẽ nhắc chương trình của bạn bằng cách in một dòng tới stdin của bot và bot của bạn sẽ in một dòng tới thiết bị xuất chuẩn .

Khi bạn nhận được một EXITtin nhắn, bạn sẽ được cung cấp nửa giây để hoàn thành việc ghi vào các tập tin trước khi thẩm phán sẽ giết quá trình.

Ngẫu nhiên

Để làm cho các giải đấu có thể kiểm chứng được, thẩm phán sử dụng ngẫu nhiên hạt giống và bot của bạn cũng phải làm như vậy, vì lý do tương tự. Bot sẽ được cung cấp một hạt giống thông qua đối số dòng lệnh mà nó nên sử dụng, vui lòng tham khảo phần tiếp theo.

Tranh luận

Bot nhận được hai đối số dòng lệnh:

  1. tên đối thủ
  2. hạt giống cho sự ngẫu nhiên

Trạng thái người dùng

Bởi vì chương trình của bạn sẽ luôn được bắt đầu mới cho mỗi trò chơi, bạn sẽ cần sử dụng các tệp để duy trì bất kỳ thông tin nào bạn muốn giữ. Bạn được phép đọc / ghi bất kỳ tệp nào hoặc tạo / xóa các thư mục con trong thư mục hiện tại của bạn. Bạn không được phép truy cập bất kỳ tập tin nào trong bất kỳ thư mục gốc nào!

Định dạng đầu vào / đầu ra

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

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

Trong giai đoạn đầu có ba loại tin nhắn khác nhau:

Prompt (judge) -> Answer (bot)
"A" SP "[]"  -> XY XY XY
"B" SP BOARD -> "B" | "W" SP XY | XY XY
"C" SP BOARD -> "B" | "W"
  • Thông điệp đầu tiên yêu cầu ba bộ dữ liệu, hai bộ đầu tiên sẽ là vị trí của những viên đá màu đen và thứ ba là vị trí cho viên màu trắng.
  • Tin nhắn thứ hai yêu cầu:
    • "B" -> chọn màu đen
    • "W" SP XY -> chọn màu trắng và đặt một viên đá trắng tại XY
    • XY XY -> đặt hai viên đá (thứ nhất một màu đen và thứ hai màu trắng)
  • Người cuối cùng chỉ hỏi màu nào bạn muốn chơi

Sau đó, trò chơi thông thường sẽ bắt đầu và các tin nhắn sẽ trở nên đơn giản hơn nhiều

N BOARD -> XY

N0XY


Có một tin nhắn bổ sung không mong đợi câu trả lời

"EXIT" SP NAME | "EXIT TIE"

nơi NAMElà tên của bot mà won. Thông báo thứ hai sẽ được gửi nếu trò chơi kết thúc do không có ai chiến thắng và không còn chỗ trống để đặt đá (điều này ngụ ý rằng bot của bạn không thể được đặt tên TIE).

Định dạng

Vì các tin nhắn từ bot có thể được giải mã mà không có bất kỳ khoảng trắng nào, tất cả các khoảng trắng sẽ bị bỏ qua (ví dụ: (0 , 0) (0,12)được xử lý giống như (0,0)(0,12)). Các thông báo từ thẩm phán chỉ chứa một SPkhoảng trắng để phân tách các phần khác nhau (ví dụ như đã lưu ý ở trên với ), cho phép bạn phân chia dòng trên các khoảng trắng.

Bất kỳ phản hồi không hợp lệ sẽ dẫn đến mất vòng đó (bạn vẫn sẽ nhận được EXITtin nhắn), xem quy tắc.

Thí dụ

Dưới đây là một số ví dụ về tin nhắn thực tế:

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

Thẩm phán

Bạn có thể tìm thấy chương trình thẩm phán ở đây : Để thêm bot vào nó, chỉ cần tạo một thư mục mới trong botsthư mục, đặt các tệp của bạn ở đó và thêm một tệp metachứa tên , lệnh , đối số và cờ 0/1 (vô hiệu hóa / bật stderr ) mỗi trên một dòng riêng biệt.

Để chạy một giải đấu chỉ cần chạy ./gomokuvà gỡ lỗi một lần chạy bot ./gomoku -d BOT.

Lưu ý: Bạn có thể tìm thêm thông tin về cách thiết lập và sử dụng thẩm phán trong kho lưu trữ Github. Ngoài ra còn có ba bot mẫu ( Haskell , PythonJavaScript ).

Quy tắc

  • trên mỗi thay đổi của bot * , giải đấu sẽ được chạy lại và người chơi có nhiều điểm nhất sẽ thắng (tie-breaker là lần gửi đầu tiên)
  • bạn có thể gửi nhiều bot miễn là chúng không chơi một chiến lược chung
  • bạn không được phép chạm vào các tệp bên ngoài thư mục của mình (ví dụ: thao túng các tệp của người chơi khác)
  • nếu bot của bạn gặp sự cố hoặc gửi phản hồi không hợp lệ, trò chơi hiện tại bị chấm dứt và bạn thua vòng đó
  • trong khi thẩm phán (hiện tại) không thi hành giới hạn thời gian mỗi vòng, bạn nên giữ thời gian ở mức thấp vì có thể không kiểm tra được tất cả các bài nộp **
  • lạm dụng lỗi trong chương trình thẩm phán được coi là kẽ hở

* Bạn được khuyến khích sử dụng Github để gửi riêng bot của bạn trực tiếp trong botsthư mục (và có khả năng sửa đổi util.sh)!

** Trong trường hợp nó trở thành vấn đề bạn sẽ được thông báo, tôi sẽ nói bất cứ điều gì dưới 500ms (rất nhiều!) Sẽ ổn ngay bây giờ.

Trò chuyện

Nếu bạn có thắc mắc hoặc muốn nói về KoTH này, vui lòng tham gia Trò chuyện !



Có không gian sau đó một nhân vật không gian meta trong các ví dụ của bạn đang thổi vào tâm trí của tôi. Một số ví dụ nữa sẽ tốt đẹp.
Veskah

@Veskah: Có ba bot mẫu được liên kết, tôi sẽ thêm một vài ví dụ cho tin nhắn.
ბიმო

@Veskah: Đã thêm một số ví dụ. Btw. bạn cũng có thể thử gỡ lỗi một bot mẫu để xem chúng sẽ ở định dạng nào và kiểm tra phản hồi hợp lệ là gì.
ბიმო

Bạn đã không cấp quyền đẩy vì vậy tôi không thể đẩy bot của mình lên git
Kaito Kid

Câu trả lời:


3

KaitoBot

Nó sử dụng một cách thực hiện rất thô sơ các nguyên tắc MiniMax. Độ sâu của tìm kiếm cũng rất thấp, vì nếu không, nó sẽ mất quá nhiều thời gian.

Có thể chỉnh sửa để cải thiện sau này.

Nó cũng cố gắng chơi Đen nếu có thể, bởi vì Wikipedia dường như nói rằng Đen có lợi thế.

Bản thân tôi chưa bao giờ chơi Gomoku, vì vậy tôi đã thiết lập ba viên đá đầu tiên một cách ngẫu nhiên vì thiếu ý tưởng tốt hơn.

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: Làm cho hạt giống thay đổi linh hoạt bởi vì nếu không thì hạt giống vượt quá 2 ^ 52 javascript không thể xử lý gia tăng chính xác

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.