Головоломка программиста: кодирование состояния шахматной доски на протяжении всей игры - PullRequest
91 голосов
/ 02 декабря 2009

Не совсем вопрос, скорее загадка ...

На протяжении многих лет я участвовал в нескольких технических интервью с новыми сотрудниками. Кроме того, что я задавал стандартные вопросы «Знаете ли вы X технологии», я также пытался понять, как они решают проблемы. Обычно я отправляю им вопрос по электронной почте за день до собеседования и ожидаю, что они примут решение к следующему дню.

Часто результаты были бы довольно интересными - неправильными, но интересными - и человек все равно получал бы мои рекомендации, если бы мог объяснить, почему они выбрали тот или иной подход.

Так что я решил написать один из своих вопросов для аудитории Stack Overflow.

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

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

РЕДАКТИРОВАТЬ: Как указал один из плакатов, я не учел временной интервал между ходами. Не стесняйтесь учитывать это также в качестве дополнительной опции:)

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

РЕДАКТИРОВАТЬ3: Будет трудно выбрать победителя здесь :) Много хороших ответов!

Ответы [ 31 ]

3 голосов
/ 02 декабря 2009

Положение на плате может быть определено в 7 битах (0-63, и 1 значение, указывающее, что его больше нет на плате). Поэтому для каждой фигуры на доске укажите, где она находится.

32 шт. * 7 бит = 224 бит

EDIT: как указал Кадриан ... у нас также есть дело о "продвинутой пешке королеве". Я предлагаю добавить дополнительные биты в конце, чтобы указать, какая пешка была повышена.

Таким образом, для каждой продвинутой пешки мы следуем 224 битам с 5 битами, которые указывают индекс продвинутой пешки, и 11111, если это конец списка.

Таким образом, минимальный регистр (без рекламных акций) составляет 224 бита + 5 (без рекламных акций). За каждую продвинутую пешку добавьте 5 битов.

EDIT: Как указывает косматая лягушка, нам нужен еще один бит в конце, чтобы указать, чей это ход; ^)

2 голосов
/ 02 декабря 2009

Существует 64 возможных положения платы, поэтому вам нужно 6 бит на позицию. Всего 32 начальных фрагмента, поэтому у нас всего 192 бита, где каждые 6 битов указывают положение данного фрагмента. Мы можем заранее определить порядок, в котором фигурируют фрагменты, поэтому нам не нужно указывать, какой именно.

Что, если фигура с доски? Что ж, мы можем поместить кусок в то же место, что и другой кусок, чтобы указать, что он снят с доски, иначе это было бы незаконно. Но мы также не знаем, будет ли первая фигура на доске или нет. Таким образом, мы добавляем 5 битов, указывающих, какой фрагмент является первым (32 варианта = 5 бит для представления первого фрагмента). Затем мы можем использовать это место для последующих фигур, которые не входят в игру. Это подводит нас к 197 битам. На доске должна быть хотя бы одна фигура, чтобы она работала.

Тогда нам нужен один бит, чей это ход - приводит нас к 198 битам .

А как насчет пешечного продвижения? Мы можем сделать это плохо, добавив 3 бита на пешку, добавив 42 бита. Но тогда мы можем заметить, что большую часть времени пешки не продвигаются.

Таким образом, для каждой пешки на доске бит «0» означает, что она не повышается. Если на доске нет пешки, то нам совсем немного нужно. Тогда мы можем использовать битовые строки переменной длины, для продвижения которых он имеет. Чаще всего это будет королева, поэтому «10» может означать КОРОЛЕВУ. Тогда «110» означает ладью, «1110» означает слона, а «1111» означает рыцаря.

Исходное состояние займет 198 + 16 = 214 бит , поскольку все 16 пешек находятся на доске и не продвигаются. Конечная игра с двумя продвинутыми ферзями пешек может занять что-то вроде 198 + 4 + 4, то есть 4 пешки живы и не продвинуты и 2 пешки ферзя, всего 206 бит . Кажется довольно крепким!

===

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

Поэтому разработайте схему кодирования Хаффмана для каждой отдельной позиции. Пешки, скорее всего, будут брать в среднем 3-4 бита вместо 6. Король также должен брать несколько бит.

Также в эту схему включите «взятые» в качестве возможных позиций. Это также может очень эффективно справляться с рокировкой - каждая ладья и король будут иметь дополнительное состояние «исходное положение, перемещено». Вы также можете закодировать en passant в пешках следующим образом - «исходная позиция, can en passant».

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

2 голосов
/ 02 декабря 2009

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

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

Каждому предмету будет присвоен присвоенный идентификатор. Поскольку существует 32 разных элемента, мне понадобится всего 5 бит для идентификатора элемента. Идентификаторы фигур не меняются от игры к игре. То же самое касается квадратных идентификаторов, для которых мне потребуется 6 бит.

Деревья Хаффмана будут кодироваться путем записи каждого узла по мере его прохождения в порядке (т. Е. Сначала выводится узел, затем его дочерние элементы слева направо). Для каждого узла будет один бит, указывающий, является ли это листовым узлом или узлом ветвления. Если это листовой узел, за ним следуют биты, дающие идентификатор.

Начальная позиция будет просто задана серией пар кусочек-локация. После этого для каждого хода будет одна пара кусочек-локация. Вы можете найти конец дескриптора начальной позиции (и начало дескриптора ходов), просто найдя первый фрагмент, который упоминается дважды. В случае, если пешка получит повышение, будет 2 дополнительных бита, определяющих, чем она станет, но идентификатор фигуры не изменится.

Чтобы учесть вероятность продвижения пешки в начале игры, между деревьями Хаффмана и данными также будет «таблица продвижения». Сначала будет 4 бита, указывающих, сколько пешек модернизируется. Затем для каждой пешки будет свой код, закодированный Хаффманом, и 2 бита, указывающих, кем он стал.

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

Подводя итог в графическом выражении:

<Game> := <Pieces huffman tree> <squares huffman tree> <promotion table> <initial position> (<moves> | <1 bit for next move - see Added 2 below>)

<Pieces huffman tree> := <pieces entry 1> <pieces entry 2> ... <pieces entry N>
<pieces entry> := "0" | "1" <5 bits with piece ID>

<squares huffman tree> := <squares entry 1> <squares entry 2> ... <squares entry N>
<Squares entry> := "0" | "1" <6 bits with square ID>

<promotion table> := <4 bits with count of promotions> <promotion 1> <promotion 2> ... <promotion N>
<promotion> := <huffman-encoded piece ID> <2 bits with what it becomes>

<initial position> := <position entry 1> <position entry 2> ... <position entry N>
<moves> := <position entry 1> <position entry 2> ... <position entry N>
<position entry> := <huffman-encoded piece ID> <huffman-encoded squre ID> (<2 bits specifying the upgrade - optional>)

Добавлено: Это все еще можно оптимизировать. Каждая часть имеет только несколько законных ходов. Вместо простого кодирования целевого квадрата можно указать идентификаторы на основе 0 для возможных ходов каждой фигуры. Одни и те же идентификаторы будут повторно использоваться для каждой фигуры, поэтому в общей сложности будет не более 21 разных идентификаторов (у королевы может быть не более 21 различных возможных вариантов перемещения). Поместите это в таблицу Хаффмана вместо полей.

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

В качестве альтернативы они могут быть размещены с использованием несжатых 6-битных квадратных идентификаторов.

Представит ли это общее уменьшение в размере - я не знаю. Возможно, но стоит немного поэкспериментировать.

Добавлено 2: Еще один особый случай. Если в игровом состоянии НЕТ ходов, становится важно различать, кто движется следующим. Добавьте еще один бит в конце для этого. :)

2 голосов
/ 02 декабря 2009

Точно так же, как они кодируют игры на книгах и бумагах: у каждой части есть символ; поскольку это «легальная» игра, белые ходят первыми - не нужно отдельно кодировать белый или черный, просто посчитайте количество ходов, чтобы определить, кто перешел. Кроме того, каждый ход кодируется как (кусок, конечная позиция), где «конечная позиция» сокращается до минимального количества символов, что позволяет различать неоднозначности (может быть нулем). Длина игры определяет количество ходов. Можно также кодировать время в минутах (с момента последнего хода) на каждом шаге.

Кодирование части может быть выполнено либо путем назначения символа каждому (всего 32), либо путем присвоения символа классу, и использовать конечную позицию, чтобы понять, какая часть была перемещена. Например, пешка имеет 6 возможных конечных позиций; но в среднем только пара доступна для него на каждом шагу. Поэтому, по статистике, кодирование по конечной позиции может быть наилучшим для этого сценария.

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

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

2 голосов
/ 02 декабря 2009

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

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

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

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

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

for each row
    for each column
        add to list ( get list of possible moves( current piece, players turn) )

«получить список возможных ходов» будет выглядеть примерно так:

if current piece is not null 
    if current piece color is the same as the players turn
        switch( current piece type )
            king - return list of possible king moves( current piece )
            queen - return list of possible queen moves( current piece )
            rook - return list of possible rook moves( current piece )
            etc.

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

2 голосов
/ 23 августа 2014

Возможное улучшение стартовой позиции в растворе Якоби

Ни одна юридическая позиция не имеет более 16 штук каждого цвета. Количество способов разместить до 16 черных и 16 белых фигур на 64 клетках составляет около 3,63e27. Log2 (3.63e27) = 91,55. Это означает, что вы можете закодировать положение и цвет всех частей в 92 бита. Это меньше, чем 64 бита для позиции + до 32 бит для цвета, который требует решение Yacoby. Вы можете сохранить 4 бита в худшем случае за счет значительной сложности при кодировании.

С другой стороны, он увеличивает размер для позиций с 5 или более отсутствующими фигурами. Эти позиции представляют только <4% всех позиций, но, вероятно, они являются большинством случаев, когда вы хотите записать начальную позицию, отличную от начальной позиции. </p>

Это приводит к полному решению

  1. Закодируйте положение и цвет частей в соответствии с методом выше. 92 бит .
  2. Чтобы указать тип каждой части, используйте код Хаффмана: пешка: «0», ладья: «100», рыцарь: «101», слон: «110», королева: «1110», король: «1111». Для этого требуется (16 * 1 + 12 * 3 + 4 * 4) = 68 бит для полного набора частей. Полная позиция платы может быть закодирована в 92 + 68 = 160 бит максимум .
  3. Должно быть добавлено дополнительное состояние игры: виток: 1 бит, возможна блокировка: 4 бита, возможна «en passant»: до 4 бит (1 бит говорит, что это так, а 3 бита - какой). Начальная позиция кодируется в = 160 + 9 = 169 бит
  4. Для списка ходов перечислите все возможные ходы для данной позиции и сохраните позицию хода в списке. Список ходов включает в себя все особые случаи (рокировка, en passant и отставка). Используйте только столько битов, сколько необходимо для сохранения самой высокой позиции. В среднем оно не должно превышать 7 бит за ход (в среднем 16 возможных фигур и 8 легальных ходов за ход). В некоторых случаях, когда перемещение принудительно, требуется только 1 бит (перемещение или отставка).
2 голосов
/ 03 ноября 2012

Я долго думал об этом (+ - 2 часа). И нет очевидных ответов.

Предполагая, что:

  1. Игнорирование состояния времени (у игрока не было ограничения по времени, поэтому он мог форсировать ничью, не играя)
  2. Когда игра была сыграна?!? Это важно, потому что правила менялись с течением времени (поэтому предполагается, что современная игра в последующем будет современной игрой ...). Пожалуйста, обратитесь к правилу мертвой пешки, например (в Википедии есть очень известная проблема с его отображением), и если вы хотите чтобы вернуться в прошлое, удача епископу делали только медленно, а кости использовали раньше. лол.

... так что современные современные правила таковы. Сначала независимо от повторения и перемещения лимита повторения.

-C 25 байтов округлено (64b + 32 * 4b + 5b = 325b)

= 64 бита (что-то / ничего) + 32 * 4 бита [1 бит = цвет {черный / с} + 3 бита = тип фигуры {король, королева, епископ, kNight, ладья, пешка, MovedPawn} NB: перемещенная пешка ... например, если это была последняя перемещенная пешка в предыдущем ходу, указывающая, что «пассивный» возможен. ] + 5 бит за фактическое состояние (чей ход, проходной, возможность раскачивания или нет с каждой стороны)

Пока все хорошо. Вероятно, может быть улучшено, но тогда будут переменная длина и продвижение, чтобы принять во внимание!?

Теперь следующие правила применимы только тогда, когда игрок подает заявку на ничью, ЭТО НЕ АВТОМАТ! Поэтому считайте, что эти 90 ходов без захвата или ход пешки возможны, если ни один из игроков не требует ничьей! Это означает, что все ходы должны быть записаны ... и доступны.

-D повторение позиции ... например состояние совета, как упомянуто выше (см. C) или нет ... (см. ниже о правилах ФИДЕ) -E Это оставляет сложную проблему с разрешением 50 ходов без захвата или перемещения пешки там, где необходим счетчик ... Однако.

Так как вы справляетесь с этим? ... Ну, на самом деле нет никакого способа. Потому что ни один из игроков не хочет рисовать или осознавать, что это произошло. Теперь в случае E счетчика может быть достаточно ... но здесь есть хитрость и даже чтение правил ФИДЕ (http://www.fide.com/component/handbook/?id=124&view=article) Я не могу найти ответ ... как насчет потери способности ладьи. Это повторение? I не думайте, но тогда это размытая тема, не затронутая, не уточненная.

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

Таким образом, единственный способ по-настоящему закодировать игру - это записать все с самого начала ... что затем вступает в противоречие (или нет?) С вопросом "состояния доски".

Надеюсь, это поможет ... не слишком много математики :-) Просто чтобы показать, что некоторые вопросы не так просты, слишком открыты для интерпретации или предварительного знания, чтобы быть правильными и эффективными. Не тот, кого я бы посчитал для собеседования, так как в нем слишком много червячной банки.

2 голосов
/ 02 декабря 2009

Я бы использовал кодировку длины пробега. Некоторые части уникальны (или существуют только дважды), поэтому я могу опустить длину после них. Как и Cletus, мне нужно 13 уникальных состояний, поэтому я могу использовать клев (4 бита) для кодирования фрагмента. Тогда начальная доска будет выглядеть так:

White Rook, W. Knight, W. Bishop, W. Queen, W. King, W. Bishop, W. Knight, W. Rook,
W. Pawn, 8,
Empty, 16, Empty, 16
B. Pawn, 8,
B. Rook, B. Knight, B. Bishop, B. Queen, B. King, B. Bishop, B. Knight, B. Rook

, что оставляет мне 8 + 2 + 4 + 2 + 8 клев = 24 клев = 96 бит. Я не могу закодировать 16 с клочком, но так как «Пусто, 0» не имеет смысла, я могу трактовать «0» как «16».

Если доска пуста, но для единственной пешки в верхнем левом углу, я получаю «Пешка, 1, Пусто, 16, Пусто, 16, Пусто 16, Пусто, 15" = 10 полубайтов = 40 бит.

Худший случай, когда у меня есть пустой квадрат между каждой частью. Но для кодирования части мне просто нужно 13 из 16 значений, так что, возможно, я могу использовать другое, чтобы сказать «Пусто1». Тогда мне нужно 64 клева == 128 бит.

Для движений мне нужно 3 бита для фигуры (цвет определяется тем, что белый всегда движется первым), плюс 5 бит (0..63) для новой позиции = один байт на движение. Большую часть времени мне не нужна старая позиция, так как в пределах досягаемости будет только одна фигура. В нечетном случае я должен использовать один неиспользованный код (мне просто нужно 7 кодов для кодирования фрагмента), а затем 5 бит для старого и 5 бит для новой позиции.

Это позволяет мне закодировать рокировку в 13 укусов (я могу переместить Короля в Ладу, что достаточно, чтобы сказать, что я намерен).

[РЕДАКТИРОВАТЬ] Если вы разрешите интеллектуальный кодировщик, тогда мне понадобится 0 битов для начальной настройки (потому что это не должно кодироваться каким-либо образом: это статично) плюс один байт на ход.

[EDIT2], который оставляет пешечную трансформацию. Если пешка достигает последней строки, я могу переместить ее на место, чтобы сказать «трансформируется», а затем добавить 3 бита для фигуры, которой она заменена (вам не нужно использовать ферзь; вы можете заменить пешку на что угодно кроме короля).

2 голосов
/ 23 декабря 2009

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

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

На доске 64 квадрата, 64 = 2 ^ 6. Если мы сохраним только начальный и последний квадрат каждого хода, который будет занимать 12 бит (повышение будет выполнено позже). Обратите внимание, что эта схема уже охватывает игрока для перемещения, выделения, захвата фигуры, рокировки и т. Д .; так как они могут быть построены из простого воспроизведения списка перемещений.

для продвижения мы можем сохранить отдельный массив векторов, который будет говорить "на шаге N повышать до части XYZ". мы можем сохранить вектор (int, byte).

Соблазнительно также оптимизировать вектор (To, From), поскольку многие из этих векторов (To, From) в шахматах невозможны. например. не будет перехода от e1 к d8 и т. д. Но я не мог придумать какую-либо схему. Любые дальнейшие идеи приветствуются.

2 голосов
/ 03 декабря 2009

[отредактировано после правильного прочтения вопроса] Если вы предполагаете, что каждая юридическая позиция может быть достигнута из начальной позиции (что является возможным определением «юридической»), то любая позиция может быть выражена как последовательность шагов от начала , Фрагмент игры, начинающейся с нестандартной позиции, может быть выражен как последовательность ходов, необходимых для достижения начала, переключатель для включения камеры с последующими последующими ходами.

Итак, давайте назовем начальное состояние платы одним битом «0».

Движения из любой позиции могут быть перечислены путем нумерации квадратов и упорядочения ходов по (начало, конец), с обычным прыжком в 2 квадрата, обозначающим рокировку. Нет необходимости кодировать нелегальные ходы, потому что позиция доски и правила всегда уже известны. Флаг включения камеры может быть выражен либо как специальный внутриполосный ход, либо более разумно как номер внеполосного движения.

Существует 24 открывающих хода для каждой стороны, которые могут вмещаться по 5 битов каждый. Последующие шаги могут потребовать большего или меньшего количества битов, но допустимые шаги всегда можно перечислить, поэтому ширина каждого шага может успешно увеличиваться или увеличиваться. Я не рассчитал, но я думаю, что 7-битные позиции будут редкостью.

Используя эту систему, игра в 100 с половиной хода может быть закодирована примерно в 500 битов. Тем не менее, было бы разумно использовать вступительную книгу. Предположим, он содержит миллион последовательностей. Итак, начальные 0 указывают начало со стандартной платы, а 1, за которым следует 20-битное число, указывают начало этой последовательности открытия. Игры с несколько обычными дебютами могут быть сокращены, скажем, на 20 полушагов или 100 битов.

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

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

...