Golfe aleatório do dia # 6: Role um d20

17

Sobre a série

Primeiro, você pode tratar isso como qualquer outro desafio de código de golfe e respondê-lo sem se preocupar com a série. No entanto, existe uma tabela de classificação em todos os desafios. Você pode encontrar a tabela de classificação junto com mais informações sobre a série no primeiro post .

Embora eu tenha várias idéias alinhadas para a série, os desafios futuros ainda não estão definidos. Se você tiver alguma sugestão, informe-me na postagem da sandbox relevante .

Buraco 6: Rolar um d20

Um dado muito comum nos RPGs de mesa é o dado de vinte lados (um icosaedro , conhecido como d20 ). É sua tarefa rolar tal dado. No entanto, se você estivesse retornando um número aleatório entre 1 e 20, isso seria um pouco trivial. Portanto, sua tarefa é gerar uma rede aleatória para um dado dado.

Usaremos a seguinte rede:

insira a descrição da imagem aqui

É uma faixa triangular, portanto pode ser facilmente representada como uma lista de números inteiros. Por exemplo, se você receber a entrada:

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

Isso corresponderia ao seguinte dado (fato interessante: essa é a rede usada pelo Magic: the Gathering life counters / spin-down data).

insira a descrição da imagem aqui

No entanto, essa não é a única rede que representa esse dado. Dependendo de como desenrolamos os rostos, existem 60 redes diferentes. Aqui estão mais dois:

[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]

Ou graficamente (não girei os rótulos dos rostos por simplicidade):

insira a descrição da imagem aqui insira a descrição da imagem aqui

O desafio

Dada uma lista de números inteiros representando um dado (como descrito acima) e um número inteiro N, produz Nindependentemente, redes d20 uniformemente aleatórias correspondentes ao dado dado. (Ou seja, cada uma das 60 redes possíveis deve ter a mesma probabilidade de ser gerada.)

Obviamente, devido às limitações técnicas dos PRNGs, a uniformidade perfeita será impossível. Com o objetivo de avaliar a uniformidade de seu envio, as seguintes operações serão consideradas como produzindo distribuições perfeitamente uniformes:

  • Obtenção de um número de um PRNG (acima de qualquer intervalo), que está documentado para ser (aproximadamente) uniforme.
  • Mapear uma distribuição uniforme sobre um conjunto maior de números para um conjunto menor via módulo ou multiplicação (ou alguma outra operação que distribua valores uniformemente). O conjunto maior deve conter pelo menos 1024 vezes o maior número possível de valores que o conjunto menor.

Dadas essas suposições, seu algoritmo deve produzir uma distribuição perfeitamente uniforme.

Seu programa deve ser capaz de gerar 100 redes em menos de um segundo (portanto, não tente gerar redes aleatórias até que uma corresponda ao dado dado acima).

Você pode escrever um programa ou função, recebendo entrada via STDIN (ou alternativa mais próxima), argumento da linha de comando ou argumento da função e emitindo o resultado via STDOUT (ou alternativa mais próxima), valor de retorno da função ou parâmetro da função (saída).

A entrada e a saída podem estar em qualquer formato de lista simples, conveniente e inequívoco. Você pode supor que os valores de face do d20 são números inteiros positivos e distintos, que se encaixam no tipo inteiro natural do seu idioma.

Isso é código de golfe, então a submissão mais curta (em bytes) vence. E, é claro, o menor envio por usuário também entrará na tabela geral de líderes da série.

Saídas de amostra

Para a entrada

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

As 60 redes possíveis (desde que eu não tenha cometido um erro), em nenhuma ordem específica, são:

[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]

Para qualquer outra rede, basta substituir todas as ocorrências de icom o inúmero th na entrada (onde ié baseado em 1).

Desafios relacionados

Entre os melhores

O primeiro post da série gera uma tabela de classificação.

Para garantir que suas respostas sejam exibidas, inicie todas as respostas com um título, usando o seguinte modelo de remarcação:

## Language Name, N bytes

onde Nestá o tamanho do seu envio. Se você melhorar sua pontuação, poderá manter as pontuações antigas no título, identificando-as. Por exemplo:

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

(O idioma não é mostrado no momento, mas o snippet exige e o analisa, e eu posso adicionar um cabeçalho por idioma no futuro.)

Martin Ender
fonte
Em relação à nossa discussão, adicionei a palavra "must" para torná-la mais clara. Eu acho que isso fecha a brecha que eu tenho aproveitado. Ainda assim, acho que a abordagem que usei (embora inválida) é interessante.
Level River St
Isso é quase exatamente como minha postagem na sandbox!
Rɪᴋᴇʀ
@RikerW Foi o que eu pensei quando você entrou na caixa de areia. ;) (Na época, o meu estava diretamente abaixo do seu. Pensei que este inspirasse o seu.) O seu é obviamente muito mais simples (o que provavelmente é uma coisa boa).
Martin Ender
Nunca vi o seu. Que estranho, pensei ter lido todos na primeira página. Mas não, eu fiz o meu de forma independente.
Rɪᴋᴇʀ
Não deveria ser intitulado "desdobrar um d20"?
Titus

Respostas:

7

Ruby, 160 bytes (rev. B)

17 bytes salvos graças a sugestões de Martin Büttner.

->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 bytes (rev 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}}

Explicação

É possível gerar todas as orientações possíveis por rotações em torno de uma face (3 vezes), um vértice (5 vezes) e duas arestas (2 vezes). Mas não apenas qualquer rosto e arestas. Os eixos devem ter o relacionamento correto e as rotações devem ser feitas na ordem correta, ou coisas estranhas podem acontecer.

Foi assim que eu fiz (embora eu entenda que Martin fez diferente).

Todas as orientações de um tetraedro podem ser geradas por combinações das três operações de simetria a seguir:

a) Rotação em torno de dois eixos de duas dobras à direita através dos pontos médios das arestas (estes estão em ângulos retos entre si. Se os multiplicamos juntos, descobrimos um terceiro eixo de duas dobras através do par de arestas restante).

b) Rotação em torno de um eixo de 3 dobras na diagonal para os eixos de 2 dobras, passando por um vértice e uma face.

A simetria do icosaedro é um superconjunto da do tetraedro. Na imagem abaixo em https://en.wikipedia.org/wiki/Icosahedron , as faces amarelas e vermelhas definem dois tetraedros diferentes (ou alternativamente um único octaedro), enquanto as arestas comuns a duas faces azuis estão em três pares de ângulos retos (e estão nas faces de um cubo.)

Todas as orientações do icosaedro podem ser geradas pelas operações de simetria mencionadas acima, além de uma operação adicional de 5 vezes.

insira a descrição da imagem aqui

As rotações em torno de três dos quatro eixos mencionados acima são representadas pelas cordas mágicas entre as ''marcas. A rotação em torno do segundo eixo de 2 vezes é diferente e pode ser feita apenas invertendo a matriz a[].

Sem jogar no programa de teste:

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]

Solução alternativa 131 bytes (inválido devido à abordagem de passeio aleatório binário, fornece apenas uma distribuição aproximadamente correta).

->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)}}

Esta é uma disputa (bem como os programas usados ​​para embaralhar o cubo de rubik.)

As rotações específicas que utilizo são duas das mais óbvias:

-Uma rotação de 120 graus (sobre as faces 1 e 20 conforme o primeiro diagrama)

-Uma rotação de 72 graus (sobre os vértices comuns a 1,2,3,4,5 e 16,17,18,19,20 pelo primeiro diagrama.)

jogamos uma moeda 99 vezes, e cada vez que executamos uma dessas duas rotações, dependendo se é cara ou coroa.

Observe que alternar esses deterministicamente leva a seqüências razoavelmente curtas. Por exemplo, com os sentidos de rotação corretos, uma rotação de 180 graus pode ser produzida com um período de apenas 2.

Level River St
fonte
Parece que jogar uma moeda para escolher uma operação renderá algo mais próximo de uma distribuição binomial do que de uma distribuição uniforme.
Sparr
@Sparr que seria o caso se o espaço de estado fosse maior que a caminhada aleatória. Mas, neste caso, uma caminhada aleatória de apenas 6 etapas pode abrir até 2 ^ 6 = 64 possibilidades (eu não as contei), e nosso espaço de estados é de apenas 60. Após 99 etapas (2 ^ 99 caminhos diferentes) tudo deve ser pelo menos tão uniformemente distribuído quanto uma única amostra do PRNG usado para gerar os números.
Level River St
@ MartinBüttner Obrigado pelas dicas, apliquei as que funcionam. b.mapnão funciona diretamente, preciso b.chars.mapfazê-lo funcionar (BTW que não funciona na minha máquina como eu tenho o Ruby 1.9.3, mas funciona com o Ideone.) É uma economia justa. Eu não acho que alterar as seqüências de caracteres mágicas para caracteres não imprimíveis para salvar o -64funcionará: %w{}interpreta \n(assim como espaço) como um separador entre as seqüências de caracteres na matriz gerada. Não tenho idéia do que fará com outros caracteres ASCII não imprimíveis.
Level River St
@ Martin, isso foi mais difícil do que eu esperava - no começo fiquei perplexo quando meu código não funcionou corretamente, então fiz uma pausa e de repente percebi que as simetrias de duas e três vezes tinham que ter o mesmo relacionamento mútuo que em um tetraedro (eu tive que mudar a face triangular que eu estava girando para uma face triangular diferente).
Level River St
11
Parabéns por ser o primeiro usuário com o selo de geometria recém-desbloqueado . :)
Martin Ender
2

Código de máquina IA-32, 118 bytes

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

É um pouco longo, então algumas explicações vão primeiro. Eu usei quase o mesmo algoritmo como o existente resposta pelo nível do rio St . Para tornar minha resposta diferente, tomei outras permutações básicas, que não necessariamente têm um significado geométrico intuitivo, mas são de alguma forma mais convenientes.

O código basicamente precisa gerar uma permutação, que é uma aplicação seqüencial do seguinte:

  1. Uma permutação da ordem 3, que eu chamo p3, aplicada 0 ... 2 vezes
  2. Uma permutação da ordem 2, que eu chamo q2, aplicada 0 ou 1 vezes
  3. Uma permutação da ordem 5, que eu chamo p5, aplicada 0 ... 4 vezes
  4. Outra permutação da ordem 2, que eu chamo p2, aplicada 0 ou 1 vezes

Existem muitas opções possíveis para essas permutações. Um deles é o seguinte:

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]

Essa escolha é melhor que outras, porque as permutações aqui têm longas execuções de índices seqüenciais, que podem ser compactadas pela codificação de duração da execução - apenas 29 bytes para as 4 permutações.

Para simplificar a geração de números aleatórios, escolhi os poderes (quantas vezes cada permutação é aplicada) no intervalo de 1 a 30 para todos eles. Isso leva a muito trabalho extra no código, porque, por exemplo, p3se torna uma permutação de identidade cada vez que é multiplicado por ele mesmo 3 vezes. No entanto, o código é menor dessa maneira e, desde que o intervalo seja divisível por 30, a saída terá distribuição uniforme.

Além disso, as potências devem ser positivas para que a operação de decodificação de execução seja executada pelo menos uma vez.

O código possui 4 loops aninhados; o esboço é assim:

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;
    }
}

Espero que esse pseudo-código seja mais claro que o código de montagem em linha abaixo.

_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
    }
}

Alguns detalhes divertidos de implementação:

  • Eu usei assembly recuado, como em idiomas de alto nível; caso contrário, o código seria uma bagunça incompreensível
  • Eu uso calle subseqüentemente poppara acessar dados (permutações codificadas) armazenados no código
  • A jecxzinstrução convenientemente me permite usar um byte zero como finalização para o processo de decodificação de duração
  • Por sorte, o número 30 (2 * 3 * 5) é quase uma potência de 2. Isso me permite usar um código curto para gerar um número no intervalo de 1 ... 30:

            and ecx, 31
            dec ecx
            jle make_rand
    
  • Eu uso uma instrução "divisão de uso geral" ( aam) para separar um byte em campos de bits (comprimento de 3 bits e índice de 5 bits); por sorte, nessa posição no código cl = 0, então me beneficio dos dois "lados" dexchg

anatolyg
fonte