Casuale golf del giorno n. 6: tira una d20


17

Informazioni sulla serie

Prima di tutto, puoi trattarlo come qualsiasi altra sfida di golf del codice e rispondere senza preoccuparti della serie. Tuttavia, esiste una classifica in tutte le sfide. Puoi trovare la classifica insieme ad alcune ulteriori informazioni sulla serie nel primo post .

Anche se ho un sacco di idee in programma per la serie, le sfide future non sono ancora state messe sulla pietra. Se hai qualche suggerimento, per favore fatemelo sapere sul post sandbox pertinente .

Buca 6: tira una d20

Un dado molto comune nei giochi di ruolo da tavolo è il dado a venti facce (un icosaedro , comunemente noto come d20 ). È tuo compito tirare un dado simile. Tuttavia, se restituissi un numero casuale compreso tra 1 e 20, sarebbe un po 'banale. Quindi il tuo compito è generare una rete casuale per un dato dado.

Useremo la seguente rete:

inserisci qui la descrizione dell'immagine

È una striscia triangolare, quindi può essere facilmente rappresentata come un elenco di numeri interi. Ad esempio, se ti viene dato l'input:

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

Ciò corrisponderebbe al seguente dado (fatto divertente: questa è la rete usata da Magic: the Gathering life counter / spin-down dice).

inserisci qui la descrizione dell'immagine

Tuttavia, questa non è l'unica rete che rappresenta questo dado. A seconda di come srotoliamo le facce, ci sono 60 reti diverse. Eccone altri due:

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

O graficamente (per semplicità non ho ruotato le etichette del viso):

inserisci qui la descrizione dell'immagine inserisci qui la descrizione dell'immagine

La sfida

Dato un elenco di numeri interi che rappresentano un dado (come descritto sopra) e un numero intero N, prodotto Nindipendentemente, reti d20 uniformemente casuali corrispondenti al dato dado. (Cioè, ognuna delle 60 possibili reti dovrebbe avere la stessa probabilità di essere generata.)

Naturalmente, a causa delle limitazioni tecniche dei PRNG, la perfetta uniformità sarà impossibile. Ai fini della valutazione dell'uniformità della presentazione, le seguenti operazioni saranno considerate come una distribuzione perfettamente uniforme:

  • Ottenere un numero da un PRNG (su qualsiasi intervallo), che è documentato come (approssimativamente) uniforme.
  • Mappare una distribuzione uniforme su un set di numeri più grande su un set più piccolo tramite modulo o moltiplicazione (o qualche altra operazione che distribuisce i valori in modo uniforme). Il set più grande deve contenere almeno 1024 volte il maggior numero possibile di valori del set più piccolo.

Alla luce di questi presupposti, il tuo algoritmo deve produrre una distribuzione perfettamente uniforme.

Il tuo programma dovrebbe essere in grado di generare 100 reti in meno di un secondo (quindi non provare a generare reti casuali fino a quando una non corrisponde al dado indicato sopra).

È possibile scrivere un programma o una funzione, prendendo l'input tramite STDIN (o l'alternativa più vicina), l'argomento della riga di comando o l'argomento della funzione e producendo il risultato tramite STDOUT (o l'alternativa più vicina), il valore di ritorno della funzione o il parametro della funzione (out).

L'input e l'output possono essere in qualsiasi formato elenco semplice, chiaro e non ambiguo. Puoi presumere che i valori facciali di d20 siano interi positivi distinti, che si adattano al tipo intero naturale della tua lingua.

Questo è il golf del codice, quindi vince l'invio più breve (in byte). E, naturalmente, la presentazione più breve per utente entrerà anche nella classifica generale della serie.

Output di esempio

Per l'input

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

Le 60 possibili reti (purché non abbia commesso un errore), in nessun ordine particolare, sono:

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

Per qualsiasi altra rete, è sufficiente sostituire ogni occorrenza di icon il inumero th nell'input (dove iè basato su 1).

Sfide correlate

Classifica

Il primo post della serie genera una classifica.

Per assicurarti che le tue risposte vengano visualizzate, inizia ogni risposta con un titolo, utilizzando il seguente modello Markdown:

## Language Name, N bytes

dov'è Nla dimensione del tuo invio. Se si migliora il punteggio, è possibile mantenere i vecchi punteggi nel titolo, colpendoli. Per esempio:

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

(La lingua non è attualmente visualizzata, ma lo snippet richiede e analizza, e in futuro potrei aggiungere una classifica linguistica).


Per quanto riguarda la nostra discussione, ho aggiunto la parola "must" per renderlo più chiaro. Penso che chiuda la scappatoia di cui sto approfittando. Tuttavia, penso che l'approccio che ho usato (anche se non valido) sia interessante.
Level River St,

Questo è quasi esattamente come il mio post sandbox!
Rɪᴋᴇʀ

@RikerW Questo è quello che ho pensato quando l'hai sandbox. ;) (All'epoca il mio era direttamente sotto il tuo. Pensavo che questo ispirasse il tuo.) Il tuo è ovviamente molto più semplice (il che è probabilmente una buona cosa).
Martin Ender,

Mai visto il tuo. È strano, pensavo di aver letto tutti quelli sulla prima pagina. Ma no, ho fatto il mio in modo indipendente.
Rɪᴋᴇʀ

Non dovrebbe essere intitolato "unfold a d20"?
Tito

Risposte:


7

Rubino, 160 byte (rev B)

17 byte salvati grazie ai suggerimenti di 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}}

Rubino, 177 byte (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}}

Spiegazione

È possibile generare tutti i possibili orientamenti mediante rotazioni attorno a una faccia (3 volte), un vertice (5 volte) e due bordi (2 volte). Ma non solo qualsiasi faccia e bordi. Gli assi devono avere la relazione corretta e le rotazioni devono essere eseguite nell'ordine corretto, altrimenti possono accadere cose strane.

Questo è il modo in cui l'ho fatto (anche se capisco che Martin ha fatto diversamente.)

Tutti gli orientamenti di un tetraedro possono essere generati da combinazioni delle seguenti tre operazioni di simmetria:

a) Rotazione intorno a due assi 2 volte a destra attraverso i punti medi dei bordi (questi sono ad angolo retto l'uno rispetto all'altro. Se li moltiplichiamo insieme scopriamo un terzo asse 2 volte attraverso la restante coppia di bordi.)

b) Rotazione attorno a un asse di 3 volte in diagonale rispetto agli assi di 2 volte, passando attraverso un vertice e una faccia.

La simmetria dell'icosaedro è un superset di quella del tetraedro. Nell'immagine seguente da https://en.wikipedia.org/wiki/Icosahedron , le facce gialle e le facce rosse definiscono due diversi tetraedri (o in alternativa un singolo ottaedro), mentre i bordi comuni a due facce blu sono in tre coppie a angoli retti (e giacciono sulle facce di un cubo.)

Tutti gli orientamenti dell'icosaedro possono essere generati da quelle operazioni di simmetria menzionate sopra più un'operazione di 5 volte aggiuntiva.

inserisci qui la descrizione dell'immagine

Le rotazioni circa tre dei quattro assi sopra menzionati sono rappresentate dalle corde magiche tra i ''segni. La rotazione attorno al secondo asse 2 volte è diversa e può essere eseguita semplicemente invertendo l'array a[].

Non registrato nel programma di test:

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]

Soluzione alternativa 131 byte (non valida a causa dell'approccio binario di camminata casuale, fornisce solo una distribuzione approssimativamente corretta.)

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

Questo è uno scramble (molto simile ai programmi utilizzati per rimescolare il cubo di Rubik.)

Le rotazioni specifiche che utilizzo sono due delle più ovvie:

-Una rotazione di 120 gradi (circa le facce 1 e 20 per il primo diagramma)

-Una rotazione di 72 gradi (attorno ai vertici comuni a 1,2,3,4,5 e 16,17,18,19,20 per il primo diagramma.)

lanciamo una moneta 99 volte e ogni volta eseguiamo una di queste due rotazioni a seconda che si tratti di testa o croce.

Si noti che alternare questi deterministicamente porta a sequenze abbastanza brevi. Ad esempio, con i sensi di rotazione corretti, è possibile produrre una rotazione di 180 gradi con un periodo di soli 2.


Sembra che lanciare una moneta per scegliere un'operazione produrrà qualcosa di più vicino a una distribuzione binomiale di una distribuzione uniforme.
Sparr l'

@Sparr sarebbe il caso se lo spazio degli stati fosse più grande della camminata casuale. Ma in questo caso una camminata casuale di soli 6 passi può aprire fino a 2 ^ 6 = 64 possibilità (non le ho contate) e il nostro spazio degli stati è solo 60. Dopo 99 passi (2 ^ 99 percorsi diversi) tutto dovrebbe essere distribuito in modo uniforme almeno quanto un singolo campione del PRNG utilizzato per generare i numeri.
Level River St,

@ MartinBüttner Grazie per i suggerimenti, ho applicato quelli che funzionano. b.mapnon funziona direttamente, devo b.chars.mapfarlo funzionare (BTW che non funziona nella mia macchina in quanto ho Ruby 1.9.3 ma funziona su Ideone.) È un discreto risparmio. Non penso che cambiare le stringhe magiche per i caratteri non stampabili per salvare la -64volontà funzionerà: %w{}interpreta \n(così come lo spazio) come un separatore tra le stringhe nell'array generato. Non ho idea di cosa farà con altri caratteri ASCII non stampabili.
Level River St,

@Martin questo è stato più difficile di quanto mi aspettassi - all'inizio ero sconcertato quando il mio codice non funzionava correttamente, poi mi sono preso una pausa e improvvisamente ho capito che le simmetrie 2 e 3 volte dovevano avere la stessa relazione reciproca di su un tetraedro (ho dovuto cambiare la faccia triangolare su cui stavo ruotando per una diversa faccia triangolare.)
Level River St,

1
Congratulazioni per essere il primo utente con il badge geometria appena sbloccato . :)
Martin Ender,

2

Codice macchina IA-32, 118 byte

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

È un po 'lungo, quindi alcune spiegazioni vanno per prime. Ho usato quasi lo stesso algoritmo come l'attuale risposta dal livello del fiume St . Per rendere diversa la mia risposta, ho preso altre permutazioni di base, che non hanno necessariamente un significato geometrico intuitivo, ma sono in qualche modo più convenienti.

Fondamentalmente il codice deve generare una permutazione, che è un'applicazione sequenziale di quanto segue:

  1. Una permutazione dell'ordine 3, che io chiamo p3, applicata 0 ... 2 volte
  2. Una permutazione dell'ordine 2, che io chiamo q2, applicata 0 o 1 volte
  3. Una permutazione dell'ordine 5, che io chiamo p5, applicata 0 ... 4 volte
  4. Un'altra permutazione dell'ordine 2, che chiamo p2, è stata applicata 0 o 1 volte

Esistono molte scelte possibili per queste permutazioni. Uno di questi è il seguente:

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]

Questa scelta è migliore di altre perché le permutazioni qui hanno lunghe sequenze di indici sequenziali, che possono essere compressi mediante codifica run-length - solo 29 byte per le 4 permutazioni.

Per semplificare la generazione di numeri casuali, ho scelto i poteri (quante volte viene applicata ogni permutazione) nell'intervallo 1 ... 30 per tutti loro. Questo porta a molto lavoro extra nel codice, perché ad esempio p3diventa una permutazione dell'identità ogni volta che viene moltiplicato da solo 3 volte. Tuttavia, il codice è più piccolo in questo modo e finché l'intervallo è divisibile per 30, l'output avrà una distribuzione uniforme.

Inoltre, i poteri dovrebbero essere positivi, quindi l'operazione di decodifica della lunghezza di esecuzione viene eseguita almeno una volta.

Il codice ha 4 loop nidificati; il contorno è così:

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

Spero che questo pseudo-codice sia più chiaro del codice di assemblaggio in linea di seguito.

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

Alcuni divertenti dettagli di implementazione:

  • Ho usato un assemblaggio rientrato, come in linguaggi di alto livello; altrimenti il ​​codice sarebbe un pasticcio incomprensibile
  • Uso calle successivamente popper accedere ai dati (permutazioni codificate) memorizzati nel codice
  • L' jecxzistruzione mi consente di utilizzare un byte zero come terminazione per il processo di decodifica di lunghezza utile
  • Per fortuna, il numero 30 (2 * 3 * 5) è quasi una potenza di 2. Questo mi consente di utilizzare il codice funzione per generare un numero nell'intervallo 1 ... 30:

            and ecx, 31
            dec ecx
            jle make_rand
    
  • Uso un'istruzione "general purpose division" ( aam) per separare un byte in campi bit (lunghezza a 3 bit e indice a 5 bit); per fortuna, in quella posizione nel codice cl = 0, quindi traggo beneficio da entrambi i "lati" dixchg

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.