Как мне моделировать шахматную доску при программировании компьютера для игры в шахматы? - PullRequest
21 голосов
/ 02 сентября 2008

Какие структуры данных вы бы использовали для представления шахматной доски для компьютерной шахматной программы?

Ответы [ 14 ]

12 голосов
/ 28 августа 2013

Для серьезного шахматного движка использование битбордов является эффективным способом представления шахматной доски в памяти. Битовые доски работают быстрее, чем любое представление на основе массива, особенно в 64-битных архитектурах, где битовая доска может помещаться в одном регистре процессора.

Bitboards

Основная идея битбордов - представлять каждый тип шахматной фигуры в 64 битах. В C ++ / C # это будет ulong/UInt64. Таким образом, вы будете поддерживать 12 UInt64 переменных для представления вашей шахматной доски: две (одна черная и одна белая) для каждого типа фигуры, а именно: пешка, ладья, рыцарь, слон, королева и король. Каждый бит в UInt64 будет соответствовать квадрату на шахматной доске. Как правило, младший значащий бит будет квадратом a1, следующим b1, затем c1 и так далее в мажорной строке. Бит, соответствующий местоположению фигуры на шахматной доске, будет установлен в 1, все остальные будут установлены в 0. Например, если у вас есть два белых ладьи на a2 и h1, тогда битборд белых ладей будет выглядеть так:

0000000000000000000000000000000000000000000000000000000110000000

Теперь, например, если вы хотите переместить ладью с a2 на g2 в приведенном выше примере, все, что вам нужно сделать, это XOR, который вы используете для битборта:

0000000000000000000000000000000000000000000000000100000100000000

Битборды имеют преимущество в производительности, когда дело доходит до генерации ходов. Есть и другие преимущества в производительности, которые естественным образом проистекают из представления битбордов. Например, вы можете использовать хеш-таблицы без блокировки , которые являются огромным преимуществом при распараллеливании вашего алгоритма поиска.

Дополнительная литература

Основным ресурсом для развития шахматного движка является Chess Programming Wiki . Я недавно написал этот шахматный движок , который реализует битборды в C #. Еще лучший шахматный движок с открытым исходным кодом - StockFish , который также поддерживает битборды, но на C ++.

12 голосов
/ 02 сентября 2008

Первоначально используйте целочисленный массив 8 * 8 для представления шахматной доски.

Вы можете начать программирование, используя эту запись. Дайте значения баллов за кусочки. Например:

**White**
9 = white queen
5 = white rook
3 = bishop
3 = knight
1 = pawn

**black**
-9 = white queen
-5 = white rook
-3 = bishop
-3 = knight
-1 = pawn

White King: very large positive number
Black King: very large negative number

и т.д.. (Обратите внимание, что приведенные выше баллы являются приблизительными значениями торговых возможностей каждой шахматной фигуры)

После того, как вы разработаете основные основы своего приложения и четко поймете, как работают используемые алгоритмы, попробуйте повысить производительность с помощью битовых плат.

В битовых досках вы используете восемь 8-битных слов для представления досок. Это представление нуждается в доске для каждой шахматной фигуры. На одной битовой доске вы будете хранить положение ладьи, в то время как на другой вы будете хранить положение коня ... и т. Д.

Битовые платы могут значительно улучшить производительность вашего приложения, потому что манипулирование кусочками с помощью битовых плат очень просто и быстро.

Как вы указали,

Большинство шахматных программ сегодня, особенно те, которые работают на 64-битном процессоре, используют растровый подход для представления Шахматная доска и генерировать ходы. х88 это альтернативная модель платы для машин без 64-битных процессоров.

9 голосов
/ 02 сентября 2008

Простой подход - использовать массив целых чисел 8x8. Используйте 0 для пустых квадратов и присвойте значения фигурам:

1 white pawns
2 white knights
3 white bishops
4 white rooks
5 white queens
6 white king

Black pieces use negative values
-1 black pawn
-2 black knight
etc

8| -4 -2 -3 -5 -6 -3 -2 -4
7| -1 -1 -1 -1 -1 -1 -1 -1
6|  0  0  0  0  0  0  0  0
5|  0  0  0  0  0  0  0  0
4|  0  0  0  0  0  0  0  0
3|  0  0  0  0  0  0  0  0
2|  1  1  1  1  1  1  1  1 
1|  4  2  3  5  6  3  2  4
  -------------------------
   1  2  3  4  5  6  7  8

Движения частей могут быть рассчитаны с использованием индексов массива. Например, белые пешки двигаются путем увеличения индекса строки на 1 или на 2, если это первый ход пешки. Таким образом, белая пешка на [2] [1] может переместиться на [3] [1] или [4] [1].

Однако это простое представление массива 8x8 имеет шахматную доску, имеет несколько проблем. В частности, когда вы перемещаете «скользящие» фигуры, такие как грачи, слоны и королевы, вам необходимо постоянно проверять индексы, чтобы увидеть, не сместилась ли фигура с доски.

Большинство современных шахматных программ, особенно те, которые работают на 64-битном процессоре, используют растровый подход для представления шахматной доски и генерирования ходов. x88 - альтернативная модель платы для машин без 64-битных процессоров.

4 голосов
/ 27 июля 2009

Массив из 120 байтов.

Это шахматная доска размером 8x8, окруженная часовыми квадратами (например, 255 для обозначения того, что фигура не может переместиться в этот квадрат). Часовые имеют глубину два, поэтому рыцарь не может перепрыгнуть.

Для перемещения вправо добавьте 1. Для перемещения влево добавьте -1. Вверх на 10, вниз -10, вверх и вправо по диагонали 11 и т. Д. Квадрат A1 - это индекс 21. H1 - это индекс 29. H8 - это индекс 99.

Все разработано для простоты. Но он никогда не будет таким же быстрым, как битборды.

4 голосов
/ 15 апреля 2009

При создании шахматного движка я изначально использовал подход [8] [8], однако недавно я изменил свой код для представления шахматной доски с использованием массива из 64 предметов. Я обнаружил, что эта реализация была примерно на 1/3 более эффективной, по крайней мере, в моем случае.

Одна из вещей, которую вы хотите учитывать при использовании подхода [8] [8], - это описание позиций. Например, если вы хотите описать правильный ход для шахматной фигуры, вам потребуется 2 байта для этого. Хотя с массивом элементов [64] вы можете сделать это одним байтом.

Чтобы преобразовать из позиции на доске предметов [64] в доску [8] [8], вы можете просто использовать следующие вычисления:

Строка = (байт) (индекс / 8)

Col = (байт) (индекс% 8)

Хотя я обнаружил, что вам никогда не придется делать это во время рекурсивного поиска перемещения, который чувствителен к производительности.

Для получения дополнительной информации о создании шахматного движка, не стесняйтесь посетить мой блог, который описывает процесс с нуля: www.chessbin.com

Адам Берент

4 голосов
/ 02 сентября 2008

Конечно, существует несколько разных способов представления шахматной доски, и наилучший способ будет зависеть от того, что для вас наиболее важно.

Вы можете выбрать между скоростью и четкостью кода.

Если скорость является вашим приоритетом, то вы должны использовать 64-битный тип данных для каждого набора фигур на доске (например, белые пешки, черные королевы, пешки на проходе). Затем вы можете использовать собственные побитовые операции при генерации ходов и тестировании легальности ходов.

Если ясность кода является приоритетом, тогда забудьте о перемешивании битов и перейдите к красиво абстрагированным типам данных, как уже предлагали другие. Просто помните, что если вы пойдете этим путем, вы, вероятно, достигнете предела производительности раньше.

Чтобы начать, посмотрите код Лукавый (C) и SharpChess (C #).

3 голосов
/ 20 декабря 2013

Альтернативой стандартной плате 8x8 является почтовый ящик 10x12 (так называемый, потому что, я думаю, он выглядит как почтовый ящик). Это одномерный массив, который включает часовых вокруг своих «границ», чтобы помочь с генерацией хода. Это выглядит так:

-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, "a8", "b8", "c8", "d8", "e8", "f8", "g8", "h8", -1,
-1, "a7", "b7", "c7", "d7", "e7", "f7", "g7", "h7", -1,
-1, "a6", "b6", "c6", "d6", "e6", "f6", "g6", "h6", -1,
-1, "a5", "b5", "c5", "d5", "e5", "f5", "g5", "h5", -1,
-1, "a4", "b4", "c4", "d4", "e4", "f4", "g4", "h4", -1,
-1, "a3", "b3", "c3", "d3", "e3", "f3", "g3", "h3", -1,
-1, "a2", "b2", "c2", "d2", "e2", "f2", "g2", "h2", -1,
-1, "a1", "b1", "c1", "d1", "e1", "f1", "g1", "h1", -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1

Вы можете создать этот массив следующим образом (в JavaScript):

function generateEmptyBoard() {
    var row = [];
    for(var i = 0; i < 120; i++) {
        row.push((i < 20 || i > 100 || !(i % 10) || i % 10 == 9) 
            ? -1 
            : i2an(i));
    }
    return row;
}

// converts an index in the mailbox into its corresponding value in algebraic notation
function i2an(i) {
    return "abcdefgh"[(i % 10) - 1] + (10 - Math.floor(i / 10));
}

Конечно, в реальной реализации вы бы поместили реальные кусочные объекты там, где находятся метки доски. Но вы бы оставили отрицательные (или что-то эквивалентное). Эти местоположения делают генерацию ходов намного менее болезненной, потому что вы можете легко определить, когда сбежали с доски, проверив это специальное значение.

Давайте сначала посмотрим на создание законных ходов для коня (нескользящая фигура):

function knightMoves(square, board) {
    var i = an2i(square);
    var moves = [];
    [8, 12, 19, 21].forEach(function(offset) {
        [i + offset, i - offset].forEach(function(pos) {
            // make sure we're on the board
            if (board[pos] != -1) {
                // in a real implementation you'd also check whether 
                // the squares you encounter are occupied
                moves.push(board[pos]);
            }
        });
    });
    return moves;
}

// converts a position in algebraic notation into its location in the mailbox
function an2i(square) {
    return "abcdefgh".indexOf(square[0]) + 1 + (10 - square[1]) * 10;
}

Мы знаем, что действительные ходы Рыцаря находятся на фиксированном расстоянии от начальной точки фигуры, поэтому нам нужно было только проверить, что эти локации действительны (т. Е. Не часовые квадраты).

Скользящие части не намного сложнее. Давайте посмотрим на епископа:

function bishopMoves(square, board) {
    var oSlide = function(direction) {
        return slide(square, direction, board);
    }
    return [].concat(oSlide(11), oSlide(-11), oSlide(9), oSlide(-9)); 
}

function slide(square, direction, board) {
    var moves = [];
    for(var pos = direction + an2i(square); board[pos] != -1; pos += direction) {
        // in a real implementation you'd also check whether 
        // the squares you encounter are occupied
        moves.push(board[pos]);
    }
    return moves;
}

Вот несколько примеров:

knightMoves("h1", generateEmptyBoard()) => ["f2", "g3"]
bishopMoves("a4", generateEmptyBoard()) => ["b3", "c2", "d1", "b5", "c6", "d7", "e8"]

Обратите внимание, что функция slide является общей реализацией. Вы должны быть в состоянии довольно легко смоделировать законные ходы других скользящих частей.

3 голосов
/ 02 сентября 2008

Ну, не уверен, поможет ли это, но Deep Blue использовал одно 6-битное число для представления определенной позиции на доске. Это помогло ему сэкономить место на кристалле по сравнению с конкурентом, который использовал 64-битный битборд.

Это может быть неактуально, так как, скорее всего, у вас уже есть 64-битные регистры на целевом оборудовании.

2 голосов
/ 07 февраля 2009

Использование битборда было бы эффективным способом представления состояния шахматной доски. Основная идея состоит в том, что вы используете 64-битные наборы битов для представления каждого из квадратов на доске, где первый бит обычно представляет A1 (нижний левый квадрат), а 64-й бит представляет H8 (диагонально противоположный квадрат). Каждый тип фигуры (пешка, король и т. Д.) Каждого игрока (черный, белый) получает свою собственную битовую доску, и все 12 из этих досок составляют игровое состояние. Для получения дополнительной информации проверьте эту Википедию статья .

1 голос
/ 02 сентября 2008

Я бы использовал многомерный массив, чтобы каждый элемент в массиве был ссылкой в ​​сетку на квадрат на доске.

Таким образом

board = arrary(A = array (1,2,3,4,5,5,6,7,8),
               B = array (12,3,.... etc...
               etc...          
               )

Тогда доска [A] [1] - это доска квадрат А1.

На самом деле вы должны использовать цифры, а не буквы, чтобы сохранить свою математическую логику для того, где части могут переходить к простым.

...