Випадковий гольф дня №6: катання на d20


17

Про серію

По-перше, ви можете ставитися до цього, як і до будь-якого іншого виклику коду для гольфу, і відповісти на нього, не турбуючись про серію взагалі. Однак існує лідери в усіх викликах. Ви можете знайти таблицю лідерів разом з додатковою інформацією про серію в першій публікації .

Хоча у мене є маса ідей, що вибудовуються для цієї серії, майбутні виклики ще не поставлені в камені. Якщо у вас є якісь пропозиції, будь ласка, повідомте мене про це у відповідній поштовій скриньці .

Отвір 6: накатуйте d20

Дуже поширеною формою у верхніх RPG є двадцятигранна штамповка ( ікосаедр , широко відомий як d20 ). Ваше завдання накатати таку матрицю. Однак якщо ви просто повертаєте випадкове число між 1 і 20, це було б трохи банально. Отже, ваше завдання - генерувати випадкову мережу для заданої плашки.

Ми будемо використовувати таку мережу:

введіть тут опис зображення

Це трикутна смужка, тому її можна легко представити у вигляді списку цілих чисел. Наприклад, якщо вам вводять дані:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]

Це могло б відповідати наступному померлому (цікавий факт: це мережа, якою користується Magic: Збирання лічильників життя / закручування кісток).

введіть тут опис зображення

Однак це не єдина сітка, що представляє цю матрицю. Залежно від того, як ми розгортаємо обличчя, існує 60 різних мереж. Ось ще два:

[1, 8, 9, 10, 2, 3, 4, 5, 6, 7, 17, 18, 19, 11, 12, 13, 14, 15, 16, 20]
[10, 9, 18, 19, 11, 12, 3, 2, 1, 8, 7, 17, 16, 20, 13, 14, 4, 5, 6, 15]

Або графічно (я не обертав мітки для простоти):

введіть тут опис зображення введіть тут опис зображення

Змагання

Враховуючи список цілих чисел, що представляють собою штамб (як описано вище), і ціле число N, виводять Nнезалежно, рівномірно випадкові сітки d20, відповідні даній маточці. (Тобто кожна з 60 можливих мереж повинна мати однакову ймовірність генерування.)

Звичайно, через технічні обмеження PRNG досконала рівномірність буде неможливою. Для оцінки однорідності ваших заявок наступні операції будуть розглядатися як такі, що дають ідеально рівномірні розподіли:

  • Отримання номера з PRNG (у будь-якому діапазоні), який задокументовано, щоб бути (приблизно) однорідним.
  • Зображення рівномірного розподілу за більшим набором чисел на менший набір за модулем або множенням (або якась інша операція, яка розподіляє значення рівномірно). Більший набір повинен містити принаймні в 1024 рази більше можливих значень, ніж менший.

Враховуючи ці припущення, ваш алгоритм повинен отримати ідеально рівномірний розподіл.

Ваша програма повинна мати можливість генерувати 100 мереж менше ніж за секунду (тому не намагайтеся генерувати випадкові мережі, поки одна не відповідає матриці, заданій вище).

Ви можете написати програму або функцію, взявши введення через STDIN (або найближчу альтернативу), аргумент командного рядка або аргумент функції та вивівши результат через STDOUT (або найближчу альтернативу), значення повернення функції або параметр функції (out).

Введення та вихід можуть бути в будь-якому зручному, однозначному, простому форматі списку. Ви можете припустити, що номінальні значення d20 - це чіткі, додатні цілі числа, які вписуються в натуральний цілий тип вашої мови.

Це код гольфу, тому виграє найкоротше подання (у байтах). І звичайно, найкоротше подання на кожного користувача також увійде в загальну таблицю лідерів серії.

Вибіркові виходи

Для введення

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]

З 60 можливих сіток (за умови, що я не помилився), в жодному конкретному порядку, це:

[11, 10, 9, 18, 19, 20, 13, 12, 3, 2, 1, 8, 7, 17, 16, 15, 14, 4, 5, 6]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
[8, 7, 17, 18, 9, 10, 2, 1, 5, 6, 15, 16, 20, 19, 11, 12, 3, 4, 14, 13]
[3, 12, 13, 14, 4, 5, 1, 2, 10, 11, 19, 20, 16, 15, 6, 7, 8, 9, 18, 17]
[3, 4, 5, 1, 2, 10, 11, 12, 13, 14, 15, 6, 7, 8, 9, 18, 19, 20, 16, 17]
[11, 19, 20, 13, 12, 3, 2, 10, 9, 18, 17, 16, 15, 14, 4, 5, 1, 8, 7, 6]
[4, 14, 15, 6, 5, 1, 2, 3, 12, 13, 20, 16, 17, 7, 8, 9, 10, 11, 19, 18]
[2, 10, 11, 12, 3, 4, 5, 1, 8, 9, 18, 19, 20, 13, 14, 15, 6, 7, 17, 16]
[4, 5, 1, 2, 3, 12, 13, 14, 15, 6, 7, 8, 9, 10, 11, 19, 20, 16, 17, 18]
[10, 2, 1, 8, 9, 18, 19, 11, 12, 3, 4, 5, 6, 7, 17, 16, 20, 13, 14, 15]
[3, 2, 10, 11, 12, 13, 14, 4, 5, 1, 8, 9, 18, 19, 20, 16, 15, 6, 7, 17]
[7, 8, 1, 5, 6, 15, 16, 17, 18, 9, 10, 2, 3, 4, 14, 13, 20, 19, 11, 12]
[13, 12, 11, 19, 20, 16, 15, 14, 4, 3, 2, 10, 9, 18, 17, 7, 6, 5, 1, 8]
[16, 15, 14, 13, 20, 19, 18, 17, 7, 6, 5, 4, 3, 12, 11, 10, 9, 8, 1, 2]
[15, 16, 17, 7, 6, 5, 4, 14, 13, 20, 19, 18, 9, 8, 1, 2, 3, 12, 11, 10]
[20, 13, 12, 11, 19, 18, 17, 16, 15, 14, 4, 3, 2, 10, 9, 8, 7, 6, 5, 1]
[5, 4, 14, 15, 6, 7, 8, 1, 2, 3, 12, 13, 20, 16, 17, 18, 9, 10, 11, 19]
[10, 11, 12, 3, 2, 1, 8, 9, 18, 19, 20, 13, 14, 4, 5, 6, 7, 17, 16, 15]
[4, 3, 12, 13, 14, 15, 6, 5, 1, 2, 10, 11, 19, 20, 16, 17, 7, 8, 9, 18]
[19, 20, 13, 12, 11, 10, 9, 18, 17, 16, 15, 14, 4, 3, 2, 1, 8, 7, 6, 5]
[1, 8, 9, 10, 2, 3, 4, 5, 6, 7, 17, 18, 19, 11, 12, 13, 14, 15, 16, 20]
[8, 1, 5, 6, 7, 17, 18, 9, 10, 2, 3, 4, 14, 15, 16, 20, 19, 11, 12, 13]
[18, 9, 8, 7, 17, 16, 20, 19, 11, 10, 2, 1, 5, 6, 15, 14, 13, 12, 3, 4]
[12, 3, 2, 10, 11, 19, 20, 13, 14, 4, 5, 1, 8, 9, 18, 17, 16, 15, 6, 7]
[2, 3, 4, 5, 1, 8, 9, 10, 11, 12, 13, 14, 15, 6, 7, 17, 18, 19, 20, 16]
[10, 9, 18, 19, 11, 12, 3, 2, 1, 8, 7, 17, 16, 20, 13, 14, 4, 5, 6, 15]
[9, 8, 7, 17, 18, 19, 11, 10, 2, 1, 5, 6, 15, 16, 20, 13, 12, 3, 4, 14]
[16, 17, 7, 6, 15, 14, 13, 20, 19, 18, 9, 8, 1, 5, 4, 3, 12, 11, 10, 2]
[17, 7, 6, 15, 16, 20, 19, 18, 9, 8, 1, 5, 4, 14, 13, 12, 11, 10, 2, 3]
[1, 5, 6, 7, 8, 9, 10, 2, 3, 4, 14, 15, 16, 17, 18, 19, 11, 12, 13, 20]
[9, 18, 19, 11, 10, 2, 1, 8, 7, 17, 16, 20, 13, 12, 3, 4, 5, 6, 15, 14]
[16, 20, 19, 18, 17, 7, 6, 15, 14, 13, 12, 11, 10, 9, 8, 1, 5, 4, 3, 2]
[5, 1, 2, 3, 4, 14, 15, 6, 7, 8, 9, 10, 11, 12, 13, 20, 16, 17, 18, 19]
[8, 9, 10, 2, 1, 5, 6, 7, 17, 18, 19, 11, 12, 3, 4, 14, 15, 16, 20, 13]
[13, 20, 16, 15, 14, 4, 3, 12, 11, 19, 18, 17, 7, 6, 5, 1, 2, 10, 9, 8]
[6, 15, 16, 17, 7, 8, 1, 5, 4, 14, 13, 20, 19, 18, 9, 10, 2, 3, 12, 11]
[6, 5, 4, 14, 15, 16, 17, 7, 8, 1, 2, 3, 12, 13, 20, 19, 18, 9, 10, 11]
[7, 6, 15, 16, 17, 18, 9, 8, 1, 5, 4, 14, 13, 20, 19, 11, 10, 2, 3, 12]
[19, 18, 17, 16, 20, 13, 12, 11, 10, 9, 8, 7, 6, 15, 14, 4, 3, 2, 1, 5]
[14, 15, 6, 5, 4, 3, 12, 13, 20, 16, 17, 7, 8, 1, 2, 10, 11, 19, 18, 9]
[17, 18, 9, 8, 7, 6, 15, 16, 20, 19, 11, 10, 2, 1, 5, 4, 14, 13, 12, 3]
[6, 7, 8, 1, 5, 4, 14, 15, 16, 17, 18, 9, 10, 2, 3, 12, 13, 20, 19, 11]
[14, 13, 20, 16, 15, 6, 5, 4, 3, 12, 11, 19, 18, 17, 7, 8, 1, 2, 10, 9]
[20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
[7, 17, 18, 9, 8, 1, 5, 6, 15, 16, 20, 19, 11, 10, 2, 3, 4, 14, 13, 12]
[15, 6, 5, 4, 14, 13, 20, 16, 17, 7, 8, 1, 2, 3, 12, 11, 19, 18, 9, 10]
[9, 10, 2, 1, 8, 7, 17, 18, 19, 11, 12, 3, 4, 5, 6, 15, 16, 20, 13, 14]
[2, 1, 8, 9, 10, 11, 12, 3, 4, 5, 6, 7, 17, 18, 19, 20, 13, 14, 15, 16]
[12, 13, 14, 4, 3, 2, 10, 11, 19, 20, 16, 15, 6, 5, 1, 8, 9, 18, 17, 7]
[17, 16, 20, 19, 18, 9, 8, 7, 6, 15, 14, 13, 12, 11, 10, 2, 1, 5, 4, 3]
[18, 17, 16, 20, 19, 11, 10, 9, 8, 7, 6, 15, 14, 13, 12, 3, 2, 1, 5, 4]
[18, 19, 11, 10, 9, 8, 7, 17, 16, 20, 13, 12, 3, 2, 1, 5, 6, 15, 14, 4]
[11, 12, 3, 2, 10, 9, 18, 19, 20, 13, 14, 4, 5, 1, 8, 7, 17, 16, 15, 6]
[15, 14, 13, 20, 16, 17, 7, 6, 5, 4, 3, 12, 11, 19, 18, 9, 8, 1, 2, 10]
[19, 11, 10, 9, 18, 17, 16, 20, 13, 12, 3, 2, 1, 8, 7, 6, 15, 14, 4, 5]
[12, 11, 19, 20, 13, 14, 4, 3, 2, 10, 9, 18, 17, 16, 15, 6, 5, 1, 8, 7]
[20, 16, 15, 14, 13, 12, 11, 19, 18, 17, 7, 6, 5, 4, 3, 2, 10, 9, 8, 1]
[13, 14, 4, 3, 12, 11, 19, 20, 16, 15, 6, 5, 1, 2, 10, 9, 18, 17, 7, 8]
[5, 6, 7, 8, 1, 2, 3, 4, 14, 15, 16, 17, 18, 9, 10, 11, 12, 13, 20, 19]
[14, 4, 3, 12, 13, 20, 16, 15, 6, 5, 1, 2, 10, 11, 19, 18, 17, 7, 8, 9]

Для будь-якої іншої мережі, просто замінити кожне входження iз iго числа на вході (де iзнаходиться 1-основі).

Супутні виклики

Таблиця лідерів

Перший пост серії генерує таблицю лідерів.

Щоб відповіді відображалися, почніть кожну відповідь із заголовка, використовуючи такий шаблон Markdown:

## Language Name, N bytes

де Nрозмір вашого подання. Якщо ви покращите свій рахунок, ви можете зберегти старі бали у заголовку, прокресливши їх. Наприклад:

## Ruby, <s>104</s> <s>101</s> 96 bytes

(Мова наразі не відображається, але фрагмент вимагає і аналізує його, і я можу в майбутньому додати таблицю лідерів за мовою.)


Щодо нашої дискусії, я додав слово "повинен", щоб було зрозуміліше. Я думаю, що це закриває лазівку, якою я скористався. Але я вважаю, що підхід, який я застосував (хоча і недійсний), цікавий.
Рівень р. Св.

Це майже точно так само, як мій пісочний пост!
Rɪᴋᴇʀ

@RikerW Це те, що я думав, коли ви його пісочницею. ;) (У той час моє було прямо нижче твого. Я думав, що це надихнуло твого.) Але, очевидно, набагато простіше (що, мабуть, добре).
Мартін Ендер

Ніколи не бачив свого. Це дивно, я думав, що прочитав усі з них на першій сторінці. Але ні, я зробив свою самостійно.
Rɪᴋᴇʀ

Чи не слід називати "розгорнути d20"?
Тит

Відповіді:


7

Ruby, 160 байт (об. Б)

17 байт збережено завдяки пропозиціям Мартіна Бюттнера.

->a,n{n.times{k=rand 60
%w{ABCD@GHIJKLMNEFPQRSO PFENOSRQHG@DCMLKJIAB GFPQHIA@DENOSRJKBCML}.map{|b|k.times{a=b.chars.map{|i|a[i.ord-64]}}}
k>29&&a.reverse!
p a}}

Ruby, 177 байт (об. A)

->n,a{n.times{k=rand(60)
h=->b{k.times{|j|a=(0..19).map{|i|a[b[i].ord-64]}}}
h['ABCD@GHIJKLMNEFPQRSO']
h['PFENOSRQHG@DCMLKJIAB']
h['GFPQHIA@DENOSRJKBCML']
k>29&&a.reverse!
p a}}

Пояснення

Можна генерувати всі можливі орієнтації обертаннями на одну грань (у 3 рази), на одну вершину (у 5 разів) та на два ребра (у 2 рази). Але не будь-яке обличчя та краї. Оси повинні мати правильне співвідношення і обертання повинні здійснюватися в правильному порядку, інакше можуть статися дивні речі.

Це так, як я це зробив (хоча я розумію, що Мартін це робив інакше.)

Всі орієнтації тетраедра можна генерувати комбінаціями трьох наступних операцій симетрії:

а) Обертання навколо двох двократних осей прямо через середні точки ребер (це під прямим кутом одна до одної. Якщо їх помножити разом, ми відкриємо третю 2-кратну вісь через решту пари ребер.)

б) Обертання навколо діагоналі осі в 3 рази до осі в два рази, що проходить через вершину та грань.

Симетрія ікосаедра - це надмножина сітки тетраедра. На зображенні нижче https://en.wikipedia.org/wiki/Icosahedron жовті грані та червоні грані визначають два різних тетраедра (або, можливо, один октаедр), а краї, спільні для двох синіх граней, розташовані у трьох парах на прямі кути (і лежать на гранях куба.)

Усі орієнтації ікосаедра можуть бути згенеровані тими операціями симетрії, згаданими вище плюс додаткова 5-кратна операція.

введіть тут опис зображення

Повороти приблизно трьох із чотирьох згаданих вище осей представлені магічними рядками між ''знаками. Обертання навколо другої двосхилої осі відрізняється, і це може бути здійснено лише шляхом реверсування масиву a[].

Невикористані в тестовій програмі:

f=->a,n{
  n.times{                     #Iterate n times, using the result from the previous iteration to generate the next
    k=rand(60)                 #pick a random number

    h=->b{                     #helper function taking a string representing a transformation
      k.times{|j|              #which is performed on a using the number of times according to k
        a=(0..19).map{|i|a[b[i].ord-64]}
      }
    }

    #Rotate about axes k times (one 5-fold, one 3-fold, two 2-fold)
    #The first three axes have coprime rotation orders
    #And the rotations themselves take care of the modulus operation so no need to add it.
    #The second 2-fold rotation is equivalent to reversing the order
    #And is applied to the last 30 numbers as it is not coprime with the first 2-fold rotation.

    h['ABCD@GHIJKLMNEFPQRSO']  #rotate k times about 5-fold axis
    h['PFENOSRQHG@DCMLKJIAB']  #rotate k times about 3-fold axis
    h['GFPQHIA@DENOSRJKBCML']  #rotate k times about 2-fold axis
    k>29&&a.reverse!
    p a
  }
}

z=(1..20).map{|i|i} 
f[z,50]

Альтернативне рішення 131 байт (Недійсне через бінарний підхід випадкового ходу, дає лише приблизно правильний розподіл.)

->a,n{(n*99).times{|i|s=['@DEFGHIABCMNOPQRJKLS','ABCD@GHIJKLMNEFPQRSO'][rand(2)] 
a=(0..19).map{|i|a[s[i].ord-64]}
i%99==98&&p(a)}}

Це сутичка (подібно до програм, які використовуються для кодування куба рубіка.)

Конкретні обертання, які я використовую, є двома найбільш очевидними:

-Поворот на 120 градусів (приблизно 1 та 20 на першій діаграмі)

-Обертання на 72 градуси (про вершини, загальні для 1,2,3,4,5 та 16,17,18,19,20 за першою діаграмою.)

ми перевертаємо монету 99 разів, і кожен раз, коли виконуємо одне з цих двох обертів, залежно від того, чи це голови, чи хвости.

Зауважимо, що чергування цих детерміновано призводить до досить коротких послідовностей. Наприклад, при правильних відчуттях обертання можна здійснити обертання на 180 градусів із періодом всього 2.


Схоже, перегортання монети для вибору операції призведе до наближення до двочленного розподілу, ніж до рівномірного розподілу.
Спарр

@Sparr, це було б так, якби простір станів був більшим, ніж випадкова прогулянка. Але в цьому випадку випадкова прогулянка всього 6 кроків може відкрити цілих 2 ^ 6 = 64 можливості (я їх не рахував), а наш стан просто 60. Після 99 кроків (2 ^ 99 різних контурів) все повинно бути принаймні рівномірно розподіленим, як єдиний зразок PRNG, який використовується для генерації чисел.
Рівень р. Св.

@ MartinBüttner Дякую за поради, я застосував ті, що працюють. b.mapне працює безпосередньо, мені потрібно, b.chars.mapщоб це працювало (BTW, який не працює в моїй машині, як у мене Ruby 1.9.3, але він працює на Ideone.) Це справедлива економія. Я не думаю, що зміна магічних рядків для недрукованих символів для збереження -64волі буде працювати: %w{}інтерпретує \n(як і пробіл) як роздільник між рядками у створеному масиві. Я поняття не маю, що це буде робити з іншими недрукованими символами ASCII.
Рівень р. Св.

@Martin це було складніше, ніж я очікував - спочатку я був приголомшений, коли мій код не працював належним чином, потім я зробив перерву і раптом зрозумів, що 2-кратна та 3-кратна симетрії повинні мати ті ж взаємні стосунки, що і на тетраедрі (мені довелося змінити трикутне обличчя, яке я обертав для іншого трикутного обличчя.)
рівень річки Св.

1
Вітаю вас як перший користувач із нещодавно розблокованим значком геометрії . :)
Мартін Ендер

2

Машинний код IA-32, 118 байт

Hexdump:

60 33 c0 51 8b 74 24 28 8b fa 6a 05 59 f3 a5 e8
21 00 00 00 20 c4 61 cd 6a 33 00 84 80 ad a8 33
32 00 46 20 44 8e 48 61 2d 2c 33 32 4a 00 21 20
a7 a2 90 8c 00 5b b1 04 51 0f c7 f1 83 e1 1f 49
7e f7 51 8b f2 56 8d 7c 24 e0 b1 14 f3 a4 5f 8b
f3 ac 8b ee d4 20 86 cc e3 0a 56 8d 74 04 e0 f3
a4 5e eb ed 59 e2 db 8b dd 59 e2 cc 59 83 c2 14
e2 91 61 c2 04 00

Це трохи довго, тому деякі пояснення йдуть спочатку. Я використовував майже той же алгоритм, що і існуючий відповідь за рівнем річки Св . Щоб зробити свою відповідь різною, я взяв інші основні перестановки, які не обов'язково мають інтуїтивне геометричне значення, але є якось зручнішими.

Код в основному повинен генерувати перестановку, яка є послідовною програмою наступного:

  1. Перестановка порядку 3, яку я називаю p3, застосована 0 ... 2 рази
  2. Перестановка порядку 2, яку я називаю q2, застосовується 0 або 1 раз
  3. Перестановка порядку 5, яку я називаю p5, застосована 0 ... 4 рази
  4. Ще одна перестановка порядку 2, яку я називаю p2, застосовується 0 або 1 раз

Для цих перестановок існує багато можливих варіантів. Один з них такий:

p3 = [0   4   5   6   7   8   9   1   2   3  13  14  15  16  17  18  10  11  12  19]
q2 = [4   5   6   7   0   1   2   3  13  14  15  16  17   8   9  10  11  12  19  18]
p5 = [6   7   0   4   5  14  15  16  17   8   9   1   2   3  13  12  19  18  10  11]
p2 = [1   0   7   8   9  10  11   2   3   4   5   6  16  17  18  19  12  13  14  15]

Цей вибір кращий, ніж інші, тому що перестановки тут мають тривалі послідовні індекси, які можна стиснути кодуванням довжини виконання - всього 29 байт для 4 перестановок.

Для спрощення генерації випадкових чисел я вибрав сили (скільки разів застосовано кожну перестановку) в діапазоні 1 ... 30 для всіх. Це призводить до набагато додаткової роботи в коді, оскільки, наприклад p3, перестановка ідентичності стає щоразу, коли вона множиться на 3 рази. Однак код таким чином менший, і поки діапазон ділиться на 30, вихід матиме рівномірний розподіл.

Також повноваження повинні бути позитивними, тому операція декодування довжини пробігу виконується принаймні один раз.

Код має 4 вкладені петлі; контур такий:

void doit(int n, uint8_t* output, const uint8_t input[20])
{    
    uint8_t temp[20];

    n_loop: for i_n = 0 ... n
    {
        memcpy(output, input, 20);
        expr_loop: for i_expr = 0 ... 3
        {
            power = rand(1 ... 30);
            power_loop: for i_power = 0 ... power
            {
                memcpy(temp, output, 20);
                output_index = 0;
                perm_loop: do while length > 0
                {
                    index = ...; // decode it
                    length = ...; // decode it
                    memcpy(output + output_index, temp + index, length);
                    output_index += length;
                }
            }
        }
        output += 20;
    }
}

Я сподіваюся, що цей псевдокод зрозуміліший, ніж код вбудованої вбудованої версії нижче.

_declspec(naked) void __fastcall doit(int n, uint8_t* output, const uint8_t* input)
{
    _asm {
        pushad
        xor eax, eax

        n_loop:
            push ecx

            ; copy from input to output
            mov esi, [esp + 0x28]
            mov edi, edx
            push 5
            pop ecx
            rep movsd

            call end_of_data
#define rl(index, length) _emit(length * 32 + index)
            rl(0, 1)
            rl(4, 6)
            rl(1, 3)
            rl(13, 6)
            rl(10, 3)
            rl(19, 1)
            _emit(0)

            rl(4, 4)
            rl(0, 4)
            rl(13, 5)
            rl(8, 5)
            rl(19, 1)
            rl(18, 1)
            _emit(0)

            rl(6, 2)
            rl(0, 1)
            rl(4, 2)
            rl(14, 4)
            rl(8, 2)
            rl(1, 3)
            rl(13, 1)
            rl(12, 1)
            rl(19, 1)
            rl(18, 1)
            rl(10, 2)
            _emit(0)

            rl(1, 1)
            rl(0, 1)
            rl(7, 5)
            rl(2, 5)
            rl(16, 4)
            rl(12, 4)
            _emit(0)

            end_of_data:
            pop ebx ; load the address of the encoded data
            mov cl, 4

            expr_loop:
                push ecx

                make_rand:
                rdrand ecx
                and ecx, 31
                dec ecx
                jle make_rand

                ; input: ebx => encoding of permutation
                ; output: ebp => encoding of next permutation
                power_loop:
                    push ecx

                    ; copy from output to temp
                    mov esi, edx
                    push esi
                    lea edi, [esp - 0x20]
                    mov cl, 20
                    rep movsb
                    pop edi

                    ; ebx => encoding of permutation
                    ; edi => output
                    mov esi, ebx
                    perm_loop:
                        ; read a run-length
                        lodsb
                        mov ebp, esi

                        _emit(0xd4)             ; divide by 32, that is, split into
                        _emit(32)               ; index (al) and length (ah)
                        xchg cl, ah             ; set ecx = length; also makes eax = al
                        jecxz perm_loop_done    ; zero length => done decoding
                        push esi
                        lea esi, [esp + eax - 0x20]
                        rep movsb
                        pop esi
                        jmp perm_loop

                    perm_loop_done:
                    pop ecx
                    loop power_loop

                mov ebx, ebp
                pop ecx
                loop expr_loop

            pop ecx
            add edx, 20
            loop n_loop

        popad
        ret 4
    }
}

Деякі цікаві деталі реалізації:

  • Я використовував відступні збори, як на мовах високого рівня; інакше код був би незрозумілим безладом
  • Я використовую callі надалі popдля доступу до даних (закодованих перестановок), що зберігаються в коді
  • jecxzІнструкція зручно дозволяє мені використовувати нульові байти в якості закінчення процесу декодування довжини прогону
  • На щастя, число 30 (2 * 3 * 5) майже потужність 2. Це дозволяє мені використовувати короткий код для генерації числа в діапазоні 1 ... 30:

            and ecx, 31
            dec ecx
            jle make_rand
    
  • Я використовую інструкцію "розділення загального призначення" ( aam) для розділення байта на бітові поля (3-бітна довжина та 5-бітний індекс); на щастя, на цій позиції в коді cl = 0, тому я виграю з обох "сторін"xchg

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.