KoTH: Gomoku (ห้าแถว)


10

Gomokuหรือห้าในแถวเป็นเกมกระดานที่เล่นโดยผู้เล่นสองคนในตารางด้วยหินสีดำและสีขาว ใครก็ตามที่สามารถวาง5หินในแถว (แนวนอนแนวตั้งหรือแนวทแยงมุม) ชนะเกม15×155

กฎระเบียบ

ใน KoTH นี้เราจะเล่นกฎ Swap2 ซึ่งหมายความว่าเกมประกอบด้วยสองขั้นตอน: ในระยะแรกผู้เล่นสองคนจะตัดสินว่าใครจะไปก่อน / ผู้ที่เล่นเป็นสีดำหลังจากนั้นพวกเขาจะวางหินหนึ่งก้อนในแต่ละรอบโดยเริ่มจากผู้เล่น ใครเลือกสีดำ

ระยะแรก

ให้ผู้เล่นเป็นA & BและAจะเปิดเกม:

  • Aวางสองหินสีดำและสีขาวบนกระดาน
  • Bสามารถเลือกหนึ่งในสามการเคลื่อนไหวต่อไปนี้:
    • ผู้เล่นBตัดสินใจที่จะเล่นเป็นสีดำ: เฟสแรกสิ้นสุดลง
    • ผู้เล่นBตัดสินใจที่จะวางหินสีขาวและเล่นสีขาว: ช่วงแรกจบลง
    • ผู้เล่นBตัดสินใจที่จะเล่นหนึ่งดำและหินขาวหนึ่งก้อน: Aจะเลือกสี

เฟสเกม

ผู้เล่นแต่ละคนวางก้อนหินสีเดียวบนกระดานเริ่มจากผู้เล่นที่เล่นเป็นสีดำสิ่งนี้จะดำเนินต่อไปจนกว่าจะไม่มีที่ว่างในการเล่น (ในกรณีนี้คือเน็คไท) หรือผู้เล่นคนหนึ่งเล่นก้อนใน แถว (ในกรณีที่ผู้เล่นชนะ)5

แถวหมายถึงแนวนอนแนวตั้งหรือแนวทแยง การชนะคือการชนะ - ไม่สำคัญว่าผู้เล่นจะทำคะแนนได้มากกว่าหนึ่งแถวหรือไม่

กฎของเกม KoTH

  • ผู้เล่นแต่ละคนเล่นกับผู้เล่นคนอื่นสองครั้ง:
    • เริ่มแรกมันจะถูกสุ่มเลือกว่าใครจะไปก่อน
    • ในเกมถัดไปผู้เล่นที่ได้เล่นครั้งสุดท้ายต้องไปก่อน
  • การชนะนั้นมีค่า 2 คะแนน, เสมอ 1 และแพ้ 0
  • เป้าหมายคือการทำคะแนนให้ได้มากที่สุด

บอทของคุณ

ในการทำให้ความท้าทายนี้สามารถเข้าถึงได้สำหรับหลายภาษาเท่าที่เป็นไปได้อินพุต / เอาต์พุตจะต้องผ่านstdin / stdout (ตามบรรทัด) โปรแกรมจะแจ้งให้ผู้พิพากษาโปรแกรมของคุณโดยการพิมพ์บรรทัดบอทของคุณstdinและบอทของคุณจะพิมพ์หนึ่งบรรทัดเพื่อstdout

เมื่อคุณได้รับEXITข้อความคุณจะได้รับครึ่งวินาทีในการเขียนไฟล์ให้เสร็จก่อนที่ผู้พิพากษาจะฆ่ากระบวนการ

randomness

ในการทำให้ทัวร์นาเมนต์สามารถตรวจสอบได้ผู้ตัดสินใช้การสุ่มแบบสุ่มและบอทของคุณต้องทำเช่นเดียวกันด้วยเหตุผลเดียวกัน บอทจะได้รับเมล็ดพันธุ์ผ่านอาร์กิวเมนต์บรรทัดคำสั่งที่ควรใช้โปรดอ้างอิงในส่วนถัดไป

ข้อโต้แย้ง

บอทรับอาร์กิวเมนต์สองบรรทัดคำสั่ง:

  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"
  • ข้อความแรกขอสาม tuples สองคนแรกจะเป็นตำแหน่งของหินสีดำและคนที่สามตำแหน่งสำหรับสีขาว
  • ข้อความที่สองขอให้:
    • "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)

** ในกรณีที่เป็นปัญหาคุณจะได้รับการแจ้งเตือนฉันจะพูดอะไรที่ต่ำกว่า 500ms (นั่นมาก!) ควรจะเรียบร้อยแล้ว

การพูดคุย

หากคุณมีคำถามหรือต้องการพูดคุยเกี่ยวกับ KoTH นี้อย่าลังเลที่จะเข้าร่วมแชท !



การมีช่องว่างจากนั้นอักขระเมตาสเปซในตัวอย่างของคุณกำลังทำให้ใจของฉัน ตัวอย่างเพิ่มเติมจะดี
Veskah

@Veskah: มีบอทตัวอย่างสามตัวที่เชื่อมโยงกันฉันจะเพิ่มตัวอย่างสำหรับข้อความ
ბიმო

@Veskah: เพิ่มตัวอย่างบางส่วน Btw คุณสามารถลองดีบักบอทตัวอย่างเพื่อดูว่าฟอร์แมตนั้นอยู่ในรูปแบบใดและทดสอบว่าอะไรคือคำตอบที่ถูกต้อง
ბიმო

คุณไม่ได้ให้สิทธิ์ในการพุชดังนั้นฉันไม่สามารถผลักดันบอทของฉันไปที่คอมไพล์
ไคโตะคิดส

คำตอบ:


3

KaitoBot

มันใช้หลักการ MiniMax ที่หยาบคาย ความลึกของการค้นหานั้นต่ำมากเพราะไม่เช่นนั้นจะใช้เวลานานเกินไป

อาจแก้ไขเพื่อปรับปรุงในภายหลัง

นอกจากนี้ยังพยายามเล่น Black หากเป็นไปได้เพราะ Wikipedia ดูเหมือนจะบอกว่า Black มีข้อได้เปรียบ

ฉันไม่เคยเล่น 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);

});

การแก้ไข: ทำให้เมล็ดเปลี่ยนแบบไดนามิกเพราะมิฉะนั้นเมื่อเมล็ดเกิน 2 ^ 52 javascript ไม่สามารถจัดการกับส่วนเพิ่มได้อย่างถูกต้อง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.