Dopo settimane di attesa, tu e i tuoi amici vi riunite per la vostra primissima partita a Dungeons & Dragons (D&D). Dato che è la prima sessione di gioco, ogni giocatore deve creare un personaggio con cui giocare. Le abilità del personaggio si determinano lanciando dadi a 6 facce, ma dove sono i dadi? Con uno shock, ti rendi conto che i tuoi amici aspettano che sia tu a creare i dadi; dopotutto è stata una tua idea giocare a D&D! Nel panico, ti accorgi di aver dimenticato di portare i dadi, il che vorrebbe dire niente partita a D&D. Dato che hai un po' di competenze di programmazione, trovi in fretta una soluzione: scriverai un programma che simula i lanci di dadi.
In una partita a Dungeons & Dragons, ogni giocatore inizia generando un personaggio con cui giocare. Questo personaggio ha, tra le altre cose, sei abilità: strength, dexterity, constitution, intelligence, wisdom e charisma. I punteggi di queste sei abilità sono determinati in modo casuale. Per generare un punteggio, lancia quattro dadi a 6 facce e registra la somma dei tre dadi più alti. Lo fai sei volte, una per ogni abilità.
Gli hitpoints iniziali del personaggio sono 10 + il suo modifier di constitution. Per trovare il modifier di constitution, sottrai 10 dalla constitution, dividi per 2 e arrotonda per difetto.
Scrivi un generatore di personaggi casuale che segua le regole qui sopra.
Ad esempio, i sei lanci di quattro dadi potrebbero risultare così:
Dato che constitution è 3, il modifier di constitution è -4 e gli hitpoints sono 6.
La maggior parte dei linguaggi di programmazione offre generatori (pseudo-)casuali, ma pochi linguaggi di programmazione sono progettati per lanciare i dadi. Uno di questi linguaggi è Troll.
Nei linguaggi funzionali, la maggior parte delle funzioni è pura. Questo significa che restituiscono sempre lo stesso output per lo stesso insieme di argomenti e non fanno nient'altro. In altre parole, le funzioni pure sono deterministiche.
Tuttavia, per definizione, un valore casuale è imprevedibile e non deterministico. Persino la pseudocasualità, cioè il restituire una sequenza di numeri che sembrano casuali, non è così facile da ottenere in un contesto puro. Questo perché un generatore di numeri pseudocasuali (PRNG) deve tenere traccia dello stato interno per restituire il numero successivo della sequenza.
Lean offre due possibili approcci a questo problema:
Usare una monade che permetta di modificare lo stato interno di un PRNG.
C'è una funzione già pronta, IO.rand, che fa esattamente questo.
Usare funzioni pure che prendono un generatore come argomento e restituiscono non solo il valore pseudocasuale prodotto, ma anche un generatore aggiornato, «pronto» per il numero successivo della sequenza.
Questo esercizio usa il secondo approccio. A ogni funzione che deve produrre un valore pseudocasuale viene passato un generatore, e ci si aspetta che la funzione restituisca sia il valore sia un generatore aggiornato.
Nota che un dato generatore è deterministico, cioè produce sempre lo stesso valore. Per generare il valore pseudocasuale successivo di una sequenza, è necessario usare il generatore aggiornato.
In questo esercizio, la casualità viene verificata con un test chi-quadrato a un livello di significatività di p < 0.0001.
Questo significa che, se un punteggio di abilità viene generato secondo le istruzioni, c'è meno dello 0,01% di probabilità che un'implementazione corretta fallisca il test per puro caso.
Nota che, secondo le istruzioni, un punteggio di abilità è la somma dei tre risultati più alti di quattro lanci di un d6 non truccato (un dado a sei facce).
Iscriviti a Exercism per imparare e padroneggiare Lean con 100 esercizi e il mentoring di persone reali, tutto gratis.
Esploriamo come generare lanci di dadi (apparentemente) casuali. Imparerai cosa sono i semi, i numeri pseudo casuali e alcune insidie comuni. Infine, vedremo perché i test basati su proprietà sono ottimi per testare funzioni che restituiscono valori casuali.