Не понимая, как техника доски работает для шахматных досок

Мой мозг курит, пытаясь понять механику этой техники. Чтобы сделать его простым, представьте себе, что вместо шахмат и множества сложных движений штук у нас есть игра только с двумя частями и одна из 8 позиций. Одна часть представляет собой треугольник, а другая - круг, например:

┌───┬───┬───┬───┬───┬───┬───┬───┐
│   │   │ ▲ │   │   │ ● │   │   │
└───┴───┴───┴───┴───┴───┴───┴───┘ 

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

Теперь представьте, что пользователь перемещает треугольник в последнюю позицию, например:

┌───┬───┬───┬───┬───┬───┬───┬───┐
│   │   │   │   │   │ ● │   │ ▲ │
└───┴───┴───┴───┴───┴───┴───┴───┘

В этом примере битрейт перемещения треугольника

1 1 0 1 1 1 1 1

и маска положения круга

0 0 0 0 0 1 0 0

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

6
задан SpaceDog 02 авг. '16 в 21:51
источник поделиться

1 ответ

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

Шахматный случай

Самые последние шахматные двигатели используют технику, известную как "Магические биты" .

Реализации различаются, но основной принцип всегда один и тот же:

  • Изолируйте квадраты, которые данный предмет может достигнуть из заданной позиции, не принимая во внимание занятость борта. Это дает нам 64-битную битовую маску потенциальных квадратов цели. Позвольте называть его T (для цели).

  • Выполните побитовое И T с битовой маской занятых квадратов на доске. Позвольте называть последний O (для занятых).

  • Умножьте результат на магическое значение M и сдвиньте результат вправо магическим количеством S. Это дает нам I (для индекса).

  • Используйте I как индекс в таблице поиска, чтобы получить битовую маску квадратов, которые можно реально достичь с помощью этой конфигурации.

Подводя итог:

I = ((T & O) * M) >> S
reachable_squares = lookup[I]

T, M, S и поиск все предварительно вычисляются и зависят от положения куска ( 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, которые будут создавать таблицу поиска без столкновений как можно меньше. Как заметил Бо Перссон в комментариях, это проблема Perfect Hash Function. Однако до сих пор не найдено идеального хэширования для магических битов, что означает, что используемые таблицы поиска обычно содержат много неиспользуемых "дыр". В большинстве случаев они создаются путем выполнения расширенного поиска грубой силы.

Ваш тестовый пример

Теперь вернемся к вашему примеру:

┌───┬───┬───┬───┬───┬───┬───┬───┐
│   │   │ ▲ │   │   │ ● │   │   │
└───┴───┴───┴───┴───┴───┴───┴───┘ 
  7   6   5   4   3   2   1   0

Здесь позиция фрагмента находится в [0 ... 7], а битбакс заполнения - [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>
5
ответ дан Arnauld 03 авг. '16 в 11:47
источник поделиться

Другие вопросы по меткам