Не понимая, как работает техника bitboard для шахматных досок
мой мозг курит, пытаясь понять механику этой техники bitboard. Чтобы сделать его простым, давайте представим, что вместо шахмат и множества сложных движений фигур у нас есть игра только с двумя фигурами и одной строкой из 8 позиций. Одна часть-треугольник, а другая-круг, Вот так:
┌───┬───┬───┬───┬───┬───┬───┬───┐
│ │ │ ▲ │ │ │ ● │ │ │
└───┴───┴───┴───┴───┴───┴───┴───┘
треугольник может двигаться как ладья. Любое количество позиций по горизонтали, но не может перепрыгнуть через круг.
а теперь представьте, что пользователь перемещает треугольник в последнюю позицию, например:
┌───┬───┬───┬───┬───┬───┬───┬───┐
│ │ │ │ │ │ ● │ │ ▲ │
└───┴───┴───┴───┴───┴───┴───┴───┘
для этого примера битовая панель перемещения треугольника -
1 1 0 1 1 1 1 1
и маска положения круга
0 0 0 0 0 1 0 0
очевидно, что перемещение незаконно, потому что треугольник не может перепрыгнуть через круг, но как программное обеспечение может проверить, является ли перемещение законным с помощью волшебной техники bitboard?
1 ответов
вы правы, что невозможно определить допустимые ходы для скользящих частей с помощью только побитовые операции. Вам понадобятся побитовые операции и предварительно вычисляемые таблицы.
шахматный ящик
самые последние шахматные движки используют технику, известную как Магия Bitboards.
реализации варьируются, но основным принципом всегда является же:
изолировать квадраты, которые данный кусок может достичь из заданного положения, без учета занятости доски. Это дает нам 64-битную битовую маску потенциальных целевых квадратов. Назовем это T (по мишени).
выполните побитовое и из T С битовой маской занятых квадратов на доске. Назовем последнее O (для Оккупированный.)
умножьте результат на магия стоимостью M и сдвиньте результат вправо на магия в сумме S. Это дает нам я (для индекса).
использовать я как индекс в таблице подстановки, чтобы получить битовую маску квадратов, которые фактически могут быть достигнуты с этой конфигурацией.
подводя вверх:
I = ((T & O) * M) >> S
reachable_squares = lookup[I]
T, M, S и поиск все precomputed и зависят от положения части (P = 0 ... 63). Таким образом, более точной формулой было бы:
I = ((T[P] & O) * M[P]) >> S[P]
reachable_squares = lookup[P][I]
целью шага #3 является преобразование 64-битного значения T & O
в гораздо меньший, так что можно использовать таблицу разумного размера. Что мы получаем, вычисляя ((T & O) * M) >> S
по существу случайная последовательность биты, и мы хотим сопоставить каждую из этих последовательностей с уникальной битовой маской достижимых целевых квадратов.
"волшебная" часть в этом алгоритме заключается в определении M и S значения, которые будут производить collision - Free таблица поиска как можно меньше. Как заметил Бо Перссон в комментариях, это Идеальная Хэш-Функция проблема. Однако до сих пор не найдено идеального хэширования для волшебных битбордов, что означает что используемые таблицы поиска обычно содержат много неиспользуемых "отверстий". Большую часть времени, они построены путем бега обширного поиска грубой силы.
тестовый случай
теперь вернемся к вашему примеру:
┌───┬───┬───┬───┬───┬───┬───┬───┐
│ │ │ ▲ │ │ │ ● │ │ │
└───┴───┴───┴───┴───┴───┴───┴───┘
7 6 5 4 3 2 1 0
здесь, положение части в [0 ... 7]
и bitmask занятости находится в [0x00 ... 0xFF]
(поскольку он 8-битный).
таким образом, вполне возможно построить таблицу прямого поиска на основе позиции и текущего доска без применения "волшебной" части.
мы имеем:
reachable_squares = lookup[P][board]
это приведет к таблице поиска, содержащей:
8 * 2^8 = 2048 entries
очевидно, мы не можем сделать это для шахмат, так как он будет содержать:
64 * 2^64 = 1,180,591,620,717,411,303,424 entries
следовательно, необходимость в магических операциях умножения и сдвига для хранения данных более компактным способом.
Ниже приведен фрагмент JS для иллюстрации этого метода. Нажмите на доску, чтобы переключить противника куски.
var xPos = 5, // position of the 'X' piece
board = 1 << xPos, // initial board
lookup = []; // lookup table
function buildLookup() {
var i, pos, msk;
// iterate on all possible positions
for(pos = 0; pos < 8; pos++) {
// iterate on all possible occupancy masks
for(lookup[pos] = [], msk = 0; msk < 0x100; msk++) {
lookup[pos][msk] = 0;
// compute valid moves to the left
for(i = pos + 1; i < 8 && !(msk & (1 << i)); i++) {
lookup[pos][msk] |= 1 << i;
}
// compute valid moves to the right
for(i = pos - 1; i >= 0 && !(msk & (1 << i)); i--) {
lookup[pos][msk] |= 1 << i;
}
}
}
}
function update() {
// get valid target squares from the lookup table
var target = lookup[xPos][board];
// redraw board
for(var n = 0; n < 8; n++) {
if(n != xPos) {
$('td').eq(7 - n)
.html(board & (1 << n) ? 'O' : '')
.toggleClass('reachable', !!(target & (1 << n)));
}
}
}
$('td').eq(7 - xPos).html('X');
$('td').click(function() {
var n = 7 - $('td').index($(this));
n != xPos && (board ^= 1 << n);
update();
});
buildLookup();
update();
td { width:16px;border:1px solid #777;text-align:center;cursor:pointer }
.reachable { background-color:#8f8 }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table>
<tr><td></td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr>
</table>