Основные принципы генератора ходов (Bitboard и 0x88)

Тема в разделе "Машинное отделение", создана пользователем Chemer, 30 ноя 2006.

  1. TopicStarter Overlay

    Chemer Максим

    • Участник
    Рег.:
    14.09.2006
    Сообщения:
    1.674
    Симпатии:
    13
    Репутация:
    0
    Адрес:
    Запорожье
    Оффлайн
    Начал писать свой собственный движок. Подскажите пожалуста, основные принципы генератора ходов Bitboard и 0x88. И где можно нарыть инфы по сабжу. Начал писать свой собственный (представление доски в виде одномерного массива) - получается слишком медленно.
  2. WinPooh В.М.

    • Команда форума
    Рег.:
    13.02.2006
    Сообщения:
    9.491
    Симпатии:
    3.118
    Репутация:
    95
    Адрес:
    Москва
    Оффлайн
    http://en.wikipedia.org/wiki/Board_representation - общие слова

    http://www.seanet.com/~brucemo/topics/0x88.htm - хорошее объяснение 0x88
    http://en.wikipedia.org/wiki/Bitboard - про битборды начать можно тоже с Википедии, затем почитать, например, Роберта Хьятта (есть ссылки в конце статьи)

    Разница в скорости генератора ходов между грамотными реализациями 0x88, битбордов и mailbox (одномерного массива) не столь велика, как может показаться. Среди сильных программ встречаются все три метода. Битборды позволяют делать быстрые и эффективные проверки в оценке, и с переходом на 64-битные системы будут становиться всё более популярными...
  3. TopicStarter Overlay

    Chemer Максим

    • Участник
    Рег.:
    14.09.2006
    Сообщения:
    1.674
    Симпатии:
    13
    Репутация:
    0
    Адрес:
    Запорожье
    Оффлайн
    Спасибо! Неплохоб ещеб сишный или паскалевский исходник, где 0х88 реальзовано.
  4. WinPooh В.М.

    • Команда форума
    Рег.:
    13.02.2006
    Сообщения:
    9.491
    Симпатии:
    3.118
    Репутация:
    95
    Адрес:
    Москва
    Оффлайн
    Фрукт. Тога. Миллион других программ.

    http://arctrix.com/nas/fruit/
  5. TopicStarter Overlay

    Chemer Максим

    • Участник
    Рег.:
    14.09.2006
    Сообщения:
    1.674
    Симпатии:
    13
    Репутация:
    0
    Адрес:
    Запорожье
    Оффлайн
    Еще вопрос. В альфабете необходима продцедура, которая востанавливает позицию после совершения хода типа Unmove(*pos;move). Непойму, как сделать, чтобы востанавливала позицию после взятия.
  6. WildCat Коршунов Игорь

    • Команда форума
    Рег.:
    04.05.2006
    Сообщения:
    3.599
    Симпатии:
    4
    Репутация:
    0
    Адрес:
    Гомель
    Оффлайн
    Можно запомнить какую фигуру мы сбили. И в Unmove() ставим ее на поле, куда ходили (или чуть рядом, если это было взятие на проходе).
  7. WinPooh В.М.

    • Команда форума
    Рег.:
    13.02.2006
    Сообщения:
    9.491
    Симпатии:
    3.118
    Репутация:
    95
    Адрес:
    Москва
    Оффлайн
    Можно и вообще без unmove обойтись, просто при совершении хода копировать структуру позиции.
    Да, это будет медленно - но зато меньше шансов ошибиться. Потом всегда можно будет к этому месту вернуться.
    Главное, помнить завет Кнута/Хора про преждевременную оптимизацию.
  8. TopicStarter Overlay

    Chemer Максим

    • Участник
    Рег.:
    14.09.2006
    Сообщения:
    1.674
    Симпатии:
    13
    Репутация:
    0
    Адрес:
    Запорожье
    Оффлайн
    Я думаю лучше прикрепить к move поле move.kill_figura и в нем передовать взятие. Чтобы при рекурсии не создавать лишних проблем
  9. WinPooh В.М.

    • Команда форума
    Рег.:
    13.02.2006
    Сообщения:
    9.491
    Симпатии:
    3.118
    Репутация:
    95
    Адрес:
    Москва
    Оффлайн
    Можно и так. Я пробовал разные варианты. Разницы по скорости почти никакой. Не в этом суть.
    Выбирайте тот метод, который лично вам покажется наиболее удобным, простым для понимания и реализации (это главное!)

    На любительском уровне хорошо играет не самая навороченная программа, а самая свободная от ошибок.
  10. WildCat Коршунов Игорь

    • Команда форума
    Рег.:
    04.05.2006
    Сообщения:
    3.599
    Симпатии:
    4
    Репутация:
    0
    Адрес:
    Гомель
    Оффлайн
    На профессиональном тоже :)

    ЗЫ Тема ветки как-то неадекватна. Винни, может стоит ее подправить?
    Например лучше: "Основные принципы генератора ходов (Bitboard и 0x88)"
  11. bankuss Александр

    • Заслуженный
    • Участник
    • Старожил
    Рег.:
    24.05.2006
    Сообщения:
    1.084
    Симпатии:
    38
    Репутация:
    6
    Оффлайн
    о скорости на данном этапе можно не думать - главное сделать рабочий движок. :)
  12. ProstoTak Старожил

    • Ветеран
    • Старожил
    Рег.:
    12.02.2006
    Сообщения:
    5.479
    Симпатии:
    123
    Репутация:
    1
    Оффлайн
    ПУХ, ты садюга. По русски нет что ли статей? Дай по русски, будь человеком, а не только медведем :)
  13. bankuss Александр

    • Заслуженный
    • Участник
    • Старожил
    Рег.:
    24.05.2006
    Сообщения:
    1.084
    Симпатии:
    38
    Репутация:
    6
    Оффлайн
    ProstoTak по русски тоже есть но малооо :)
  14. TopicStarter Overlay

    Chemer Максим

    • Участник
    Рег.:
    14.09.2006
    Сообщения:
    1.674
    Симпатии:
    13
    Репутация:
    0
    Адрес:
    Запорожье
    Оффлайн
    Мужики, так что там насчет по-русски? То по-аглицки я не очень. Можно и своими словами подробно рассказать про 0х88.
  15. WildCat Коршунов Игорь

    • Команда форума
    Рег.:
    04.05.2006
    Сообщения:
    3.599
    Симпатии:
    4
    Репутация:
    0
    Адрес:
    Гомель
    Оффлайн
    Русских статей по этой теме я не встречал.

    Проще всего mailbox:

    Square Board[120]; // доска из 120 полей

    Номера некоторых полей:

    #define A1 91
    #define B1 92
    #define C1 93
    #define D1 94
    #define E1 95
    #define F1 96
    #define G1 97
    #define H1 98

    #define A8 21
    #define B8 22
    #define C8 23
    #define D8 24
    #define E8 25
    #define F8 26
    #define G8 27
    #define H8 28

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

    Думаю, что предсставление доски понятно.

    Для примера генерация ходов слонов, ладей, ферзей вдоль одного луча.
    Код:
    void GenSlideRay(MoveList *ml, int from, int inc)
    {
        int to = from + inc;
        while (IS_EMPTY(to))
        {
            ml.AddMove(from, to);
            to += inc;
        }
        if (IS_OPPONENT(to)) ml.AddCapture(from, to);
    }
    Макросы (или красивее сделать inline-функциями):
    IS_EMPTY(to) - свободно ли поле to
    IS_OPPONENT(to) - на поле to фигура противника?

    Думаю, что идея понятна. Что может быть проще?
  16. TopicStarter Overlay

    Chemer Максим

    • Участник
    Рег.:
    14.09.2006
    Сообщения:
    1.674
    Симпатии:
    13
    Репутация:
    0
    Адрес:
    Запорожье
    Оффлайн
    Я похожее на выходных ваял - слона, ладью, ферзя, короля(без шаха). Но мне кажетяся по лучам - неочень изящно.
  17. TopicStarter Overlay

    Chemer Максим

    • Участник
    Рег.:
    14.09.2006
    Сообщения:
    1.674
    Симпатии:
    13
    Репутация:
    0
    Адрес:
    Запорожье
    Оффлайн
    А доска 120 - здорово! по 2 поля вокруг основной доски для аутов! А то я задолбался проверки делать на выход за границы!
    Век живи - век учись - все равно дураком сдохнешь! :)
  18. WinPooh В.М.

    • Команда форума
    Рег.:
    13.02.2006
    Сообщения:
    9.491
    Симпатии:
    3.118
    Репутация:
    95
    Адрес:
    Москва
    Оффлайн
    120 x 120 многовато будет :)

    Доски используют самых разных размеров, некоторые считают самым удачным 10x12, другие - 12x16, или 10x8... Базовая идея одна и та же - использовать факт, что в двоичном представлении для одной координаты достаточно трёх бит, а если вылезли в четвёртый - значит, аут. Другие, кроме классического 16x8, размеры используют для разных специфических ускорений в генераторе - например, быстрый поиск нужных диагоналей.

    Ещё одно полезное свойство 16x8 - то, что разность двух полей всегда зависит только от относительного вектора, соединяющего одно с другим, и не зависит от положения на доске (имеется в виду параллельный перенос пары полей куда угодно). Например, (B3 - A1) == (G6 - F4). Это существенная помощь в проверке на шахи (в данном случае, коня).
  19. TopicStarter Overlay

    Chemer Максим

    • Участник
    Рег.:
    14.09.2006
    Сообщения:
    1.674
    Симпатии:
    13
    Репутация:
    0
    Адрес:
    Запорожье
    Оффлайн
    Насчет 120х120 - описка - конечно же просто 120!
  20. WildCat Коршунов Игорь

    • Команда форума
    Рег.:
    04.05.2006
    Сообщения:
    3.599
    Симпатии:
    4
    Репутация:
    0
    Адрес:
    Гомель
    Оффлайн
    Другого нам не дано.
  21. NS Нефёдов Сергей

    • Заслуженный
    • Ветеран
    • Старожил
    Рег.:
    02.05.2006
    Сообщения:
    6.811
    Симпатии:
    96
    Репутация:
    3
    Адрес:
    Санкт-Петербург
    Оффлайн
    Проверка за выход доски в 0x88 (это в десятиной системе 136)
    if (square and 136)=0 then //поле на доске
    if (square and 136)<>0 then //поле не на доске
    Вроде не очень сложно :)
  22. WildCat Коршунов Игорь

    • Команда форума
    Рег.:
    04.05.2006
    Сообщения:
    3.599
    Симпатии:
    4
    Репутация:
    0
    Адрес:
    Гомель
    Оффлайн
    В mailbox вообще не надо проверять выход за доску.
  23. TopicStarter Overlay

    Chemer Максим

    • Участник
    Рег.:
    14.09.2006
    Сообщения:
    1.674
    Симпатии:
    13
    Репутация:
    0
    Адрес:
    Запорожье
    Оффлайн
    Переделаю свой горе-генератор в mailbox WildCat-а вреде идея неплохая и переделывать не очень много.
  24. NS Нефёдов Сергей

    • Заслуженный
    • Ветеран
    • Старожил
    Рег.:
    02.05.2006
    Сообщения:
    6.811
    Симпатии:
    96
    Репутация:
    3
    Адрес:
    Санкт-Петербург
    Оффлайн
    Да, но он вроде получается чуть медленней, так как вектора возможных ходов фигур храняться не в регистрах, а в хеш-памяти.
    Хотя на 1С у меня такой генератор получился более быстрым, чем генератор 10x10 - но это специфика языка.
    Или я путаю типы генераторов? И то о чем я говорю называется иначе? (список легальных ходов каждой фигуры с каждого поля)
    В генераторе WildCat-а есть проверка за выходы за поле, только в отличии от 0x88 она делается по-другому.
    Если в 0x88 не надо обращаться к памяти для проверки выхода за доску, то в его генераторе проверяется маркер на поле - то есть идет обращение к оперативной памяти. Но такой генератор получается чуть более быстрым чем 0x88
  25. WinPooh В.М.

    • Команда форума
    Рег.:
    13.02.2006
    Сообщения:
    9.491
    Симпатии:
    3.118
    Репутация:
    95
    Адрес:
    Москва
    Оффлайн
    Оптимизировать надо прежде всего генератор взятий. Именно он вызывается в программе чаще всего, в каждом конечном узле основного поиска - один раз как минимум! А его оптимизировать можно только переходя на битборды...
  26. WildCat Коршунов Игорь

    • Команда форума
    Рег.:
    04.05.2006
    Сообщения:
    3.599
    Симпатии:
    4
    Репутация:
    0
    Адрес:
    Гомель
    Оффлайн
    В любом случае надо проверять маркер на поле (хотя бы чтобы проверить отсутсвие своей фигуры на этом поле).
    В 0x88 эту проверку можно пропустить только для полей вне доски. Т.е. мой будет и быстрее и удобнее.

    Генератор взятий не явл. чем-то критическим. И вряд-ли из-за него стоит заморачиваться. На битборды стоит переходить только, если в ОФ есть признаки вычисляемые на битбордах намного эффективнее.
    Например, битборды очень удобны для оценки пешечной структуры. Но можно воспользоваться трюком как в Фрукте - хранить битборды только с пешками, остальные фигуры как обычно. С другой стороны оценка пешечной структуры хорошо кешируется. Значит никакой существенной прибавки мы здесь не получим.
  27. TopicStarter Overlay

    Chemer Максим

    • Участник
    Рег.:
    14.09.2006
    Сообщения:
    1.674
    Симпатии:
    13
    Репутация:
    0
    Адрес:
    Запорожье
    Оффлайн
    Все даделал майлбокс по WildCat получилось очень красиво. Немного запары с енпасантом. Теперь думаю как написать проверялку на шахи, и генерировать легальные ходы под шахом.
  28. ProstoTak Старожил

    • Ветеран
    • Старожил
    Рег.:
    12.02.2006
    Сообщения:
    5.479
    Симпатии:
    123
    Репутация:
    1
    Оффлайн
    Кстати, проверки на шах тот ещё гемор. Времени жрёт много. Надо бы совет как оптимизировать.
  29. WinPooh В.М.

    • Команда форума
    Рег.:
    13.02.2006
    Сообщения:
    9.491
    Симпатии:
    3.118
    Репутация:
    95
    Адрес:
    Москва
    Оффлайн
    Первое правило оптимизации: НЕ ОПТИМИЗИРУЙ!
    Второе правило оптимизации (только для экспертов): если всё же без оптимизации не обойтись, ВСЁ РАВНО НЕ ОПТИМИЗИРУЙ!
  30. WildCat Коршунов Игорь

    • Команда форума
    Рег.:
    04.05.2006
    Сообщения:
    3.599
    Симпатии:
    4
    Репутация:
    0
    Адрес:
    Гомель
    Оффлайн
    Для проверки на шах проще всего сходить от короля по всем направлениям и посмотреть нет ли шахующей фигуры.

    А можно просто следить объявил ли противник шах свом последним ходом. Здесь все просто, смотрим дает ли шах ходившая фигура, если нет, то смотрим может получился вскрытый шах. Еще надо не забыть про рокировки и взятия на проходе.
  31. NS Нефёдов Сергей

    • Заслуженный
    • Ветеран
    • Старожил
    Рег.:
    02.05.2006
    Сообщения:
    6.811
    Симпатии:
    96
    Репутация:
    3
    Адрес:
    Санкт-Петербург
    Оффлайн
    Проверка на Шах для 0x88
    Просьба не пугаться именам переменных, и прописанных напрямую в коде значений констант.

    Код:
    function check_itW(var pos:board;otkuda:byte):boolean; inline;
      var kuda:byte;
      var chto:shortint;
      begin
        with pos do
        begin
        if ChKor[otkuda-Bking+128]>0 Then
             Begin
                Check_itW:=true;
                exit;
             end;
        // Пешка.
        if BKolPeshka>0 then
        begin
          kuda:=otkuda+17;
          if ((kuda and 136)=0) and (shortint(pole[kuda])=-1) then
          Begin
            Check_itW:=true;
            exit;
          end;
          kuda:=otkuda+15;
          if ((kuda and 136)=0) and (shortint(pole[kuda])=-1) then
          Begin
            Check_itW:=true;
            exit;
          end;
        end;
        // конь
        if BKolKon>0 then
        begin
          kuda:=otkuda+byte(33);
          if ((kuda and 136)=0) and (shortint(pole[kuda])=-2) then
          Begin
            Check_itW:=true;
            exit;
          end;
          kuda:=otkuda+byte(-33);
          if ((kuda and 136)=0) and (shortint(pole[kuda])=-2) then
          Begin
            Check_itW:=true;
            exit;
          end;
           kuda:=otkuda+byte(31);
          if ((kuda and 136)=0) and (shortint(pole[kuda])=-2) then
          Begin
            Check_itW:=true;
            exit;
          end;
          kuda:=otkuda+byte(-31);
          if ((kuda and 136)=0) and (shortint(pole[kuda])=-2) then
          Begin
            Check_itW:=true;
            exit;
          end;
             kuda:=otkuda+byte(14);
          if ((kuda and 136)=0) and (shortint(pole[kuda])=-2) then
          Begin
            Check_itW:=true;
            exit;
          end;
          kuda:=otkuda+byte(-14);
          if ((kuda and 136)=0) and (shortint(pole[kuda])=-2) then
          Begin
            Check_itW:=true;
            exit;
          end;
           kuda:=otkuda+byte(18);
          if ((kuda and 136)=0) and (shortint(pole[kuda])=-2) then
          Begin
            Check_itW:=true;
            exit;
          end;
          kuda:=otkuda+byte(-18);
          if ((kuda and 136)=0) and (shortint(pole[kuda])=-2) then
          Begin
            Check_itW:=true;
            exit;
          end;
        end;
          // слон или ферзь
          if (BKolBishop+BKolFerz)>0 then
          begin
          kuda:=otkuda+byte(-17);
          while (kuda and 136)=0 do
            begin
            chto:=shortint(pole[kuda]);
            if chto<>0 then
              begin
              if (chto=-3)or(chto=-5) then
               begin
               check_itW:=true;
               exit;
               end;
              break;
              end;
              kuda:=kuda+byte(-17);
            end;
    
            kuda:=otkuda+byte(17);
          while (kuda and 136)=0 do
            begin
            chto:=shortint(pole[kuda]);
            if chto<>0 then
              begin
              if (chto=-3)or(chto=-5) then
               begin
               check_itW:=true;
               exit;
               end;
              break;
              end;
              kuda:=kuda+byte(17);
            end;
          kuda:=otkuda+byte(-15);
          while (kuda and 136)=0 do
            begin
            chto:=shortint(pole[kuda]);
            if chto<>0 then
              begin
              if (chto=-3)or(chto=-5) then
               begin
               check_itW:=true;
               exit;
               end;
              break;
              end;
              kuda:=kuda+byte(-15);
            end;
    
          kuda:=otkuda+byte(15);
          while (kuda and 136)=0 do
            begin
            chto:=shortint(pole[kuda]);
            if chto<>0 then
              begin
              if (chto=-3)or(chto=-5) then
               begin
               check_itW:=true;
               exit;
               end;
              break;
              end;
              kuda:=kuda+byte(15);
            end;
           end;
            // Ладья или ферзь
          if (BKolRook+BKolFerz)>0 then
          begin
          kuda:=otkuda+byte(-16);
          while (kuda and 136)=0 do
            begin
            chto:=shortint(pole[kuda]);
            if chto<>0 then
              begin
              if (chto=-4)or(chto=-5) then
               begin
               check_itW:=true;
               exit;
               end;
              break;
              end;
              kuda:=kuda+byte(-16);
            end;
    
            kuda:=otkuda+byte(16);
          while (kuda and 136)=0 do
            begin
            chto:=shortint(pole[kuda]);
            if chto<>0 then
              begin
              if (chto=-4)or(chto=-5) then
               begin
               check_itW:=true;
               exit;
               end;
              break;
              end;
              kuda:=kuda+byte(16);
            end;
          kuda:=otkuda+byte(-1);
          while (kuda and 136)=0 do
            begin
            chto:=shortint(pole[kuda]);
            if chto<>0 then
              begin
              if (chto=-4)or(chto=-5) then
               begin
               check_itW:=true;
               exit;
               end;
              break;
              end;
              kuda:=kuda+byte(-1);
            end;
    
          kuda:=otkuda+byte(1);
          while (kuda and 136)=0 do
            begin
            chto:=shortint(pole[kuda]);
            if chto<>0 then
              begin
              if (chto=-4)or(chto=-5) then
               begin
               check_itW:=true;
               exit;
               end;
              break;
              end;
              kuda:=kuda+byte(1);
            end;
          end;
    
        check_itW:=false;
        end;
      end;
    Все направления прописаны напрямую в коде - чтоб не оптимизировать вызовы функций.
  32. NS Нефёдов Сергей

    • Заслуженный
    • Ветеран
    • Старожил
    Рег.:
    02.05.2006
    Сообщения:
    6.811
    Симпатии:
    96
    Репутация:
    3
    Адрес:
    Санкт-Петербург
    Оффлайн
    Обявление структуры позиции

    type BoardPole = array[0..127] of byte;
    type board=record
    Rokir:byte; // 0000 KB DB KW DW
    pole: BoardPole;
    prohod:byte; //ноль, или координата
    hod:shortint;//больше или меньше нуля
    WKolRook:byte;//Количество Ладей
    BKolRook:byte;
    WKolBiShop:byte;//Количество Слонов
    BKolBishop:byte;
    WKolFerz:byte;//Количество Ферзей
    BKolFerz:byte;
    WKolKon:byte;//Количество коней
    BKolKon:byte;
    WKolPeshka:byte;//Количество пешек
    BKolPeshka:byte;
    WKing:Byte; // координата
    BKing:Byte;
    end;
  33. ProstoTak Старожил

    • Ветеран
    • Старожил
    Рег.:
    12.02.2006
    Сообщения:
    5.479
    Симпатии:
    123
    Репутация:
    1
    Оффлайн
    Значицца так. Докладаю. После некоторой доделки мой генератор даёт 5 000 000 генераций всех (20) ходов из начальной позиции за 22 секунды. Это получается около 4,5 миллионов ходов в секунду генерит. Правда пока нет проверки на шах и превращение только в ферзя. Проц Celeron 900 MHz.
    Это много или мало?
  34. ProstoTak Старожил

    • Ветеран
    • Старожил
    Рег.:
    12.02.2006
    Сообщения:
    5.479
    Симпатии:
    123
    Репутация:
    1
    Оффлайн
    А как именно медленно? Какие цифры?
  35. WinPooh В.М.

    • Команда форума
    Рег.:
    13.02.2006
    Сообщения:
    9.491
    Симпатии:
    3.118
    Репутация:
    95
    Адрес:
    Москва
    Оффлайн
    Это нормально. Только учти, что если ты считаешь только для первого хода, то совершенно не задействована часть, отвечающая за ходы дальнобойных фигур - самая прожорливая часть во всех mailbox-генераторах.

    Реализуй как можно скорее функцию perft. Проверь корректность генератора на вот этих позициях:
    http://mypage.bluewin.ch/romanhartmann/perft.html

Поделиться этой страницей