Shuffle Bags Efectuarea aleatorie () se simte mai aleator

Un generator de numere pseudo aleatoare (PRNG), cum ar fi Întâmplător Clasa în C # nu este un adevărat generator de numere aleatoare: scopul său este de a aproxima întâmplare cu viteza. Acest lucru înseamnă că va reveni adesea o distribuție neuniformă a valorilor, ceea ce poate nu este ceea ce doriți. În acest tutorial, vă voi arăta cum să rezolvați această problemă cu un shuffle sac.

Notă: Deși acest tutorial utilizează C #, ar trebui să puteți utiliza aceleași tehnici și concepte în aproape orice mediu de dezvoltare a jocului.


Introducere

Când am început să creez jocuri, am folosit standardul Întâmplător() metode pentru a crea varietate în gameplay, creând mari dacă / altceva până când primesc rezultatele dorite. Dacă rezultatele nu ar fi echilibrate așa cum le-am dorit, aș crea condiții suplimentare până când am simțit că jocul era distractiv. Abia de curând mi-am dat seama că există abordări mai bune în crearea unei experiențe cu adevărat distractive în joc.

Nu este nimic în neregulă cu utilizarea built-in-ului Întâmplător clasa: problema de a nu obține rezultatele dorite rezultă din implementarea metodelor. În acest articol vom folosi metoda "Shuffle Bag" Întâmplător() simti mai aleator (si mai distractiv), folosind panouri Boggle ca un exemplu practic.


Problema

Ați observat vreodată că o expresie ca:

 valoarea int = Random.Next (valori inferioare, valori superioare);

... vă oferă o distribuție neuniformă a numerelor?

Un generator de numere aleatoare care alege valori intre 0 si 1 nu ii pasa daca returneaza toate 1s, deci daca ai creat un dacă / altceva blocați utilizând expresia de mai sus pentru a alege o ramură, probabil că nu obțineți rezultatele pe care le așteptați.

 var rand = nou Random (); pentru (int i = 0; i < 10; i++) Console.WriteLine(rand.Next(0, 2));

Nu este nimic tehnic în neregulă cu Random.Next (), dar nu garantează o distribuție uniformă a numerelor. Aceasta înseamnă că în multe situații de joc, Întâmplător() nu este distractiv.


Ce este un sac de shuffle?

Un shuffle Bag este o tehnică de control al alegerii pentru a crea distribuția pe care o dorim. Ideea este:

  • Alegeți o gamă de valori cu distribuția dorită.
  • Pune toate aceste valori într-o pungă.
  • Amestecați conținutul sacului.
  • Trageți valorile unul câte unul până când ajungeți la sfârșit.
  • Odată ce ajungeți la sfârșit, începeți din nou, trăgând din nou valorile unul câte unul.

Implementarea unei pungi de shuffle

Implementarea unei pungi de shuffle în C # este simplă și tehnica poate fi ușor convertită în orice limbă.

Întrucât scopul acestui articol este de a ne concentra asupra punerii în aplicare a pachetelor Shuffle și nu a caracteristicilor lingvistice, nu vom examina utilizarea de medicamente generice. Cu toate acestea, recomand cu fermitate utilizarea genericelor deoarece acestea ne permit să realizăm structuri de date de tip sigur, fără a se angaja în tipuri de date actuale. Genericul vă permite să utilizați același cod pentru a crea pungi de shuffle care dețin mai multe tipuri diferite de date.

Iată codul de bază:

 clasa publică ShuffleBag private Random aleator = nou Random (); Lista privată date; private char currentItem; private int currentPosition = -1; private int Capacitate get return data.Capacity;  public int Mărime get return data.Count;  ShuffleBag public (int initCapacity) data = lista nouă (initCapacity); 

Începutul clasei stabilește variabilele de instanță și constructorul inițializează variabila instanței de date la capacitatea inițială a programatorului (adică cât de mare va fi punga).

 public void Adăugați (elementul char, suma int) for (int i = 0; i < amount; i++) data.Add(item); currentPosition = Size - 1; 

Adăuga metoda adaugă pur și simplu carboniza la date de câte ori specifică programatorul.

Rețineți că pozitie curenta este setat la sfârșitul listei, deoarece vom trece mai târziu de la final. De ce de la sfârșitul listei? S-ar putea face ca sacul Shuffle sa treaca de la inceput, dar incepand de la sfarsit si lucrand in spate, face ca un cod mai curat.

 public char Următorul () if (currentPosition < 1)  currentPosition = Size - 1; currentItem = data[0]; return currentItem;  var pos = random.Next(currentPosition); currentItem = data[pos]; data[pos] = data[currentPosition]; data[currentPosition] = currentItem; currentPosition--; return currentItem; 

Următor → metoda este carnea acestei tehnici.

Dacă pozitie curenta este mai mică decât una, ne reinițializăm pentru a indica sfârșitul listei și a returna primul element din geantă. (Aceasta acoperă situația în care am trecut prin toate articolele și acum doriți să începeți din nou.)

În caz contrar, vom folosi random.Next () pentru a alege un articol aleatoriu din sac, de undeva între primul element și elementul din poziția actuală. Schimbăm acest element selectat aleatoriu cu elementul în poziția curentă și apoi scădăm pozitie curenta de 1.

În cele din urmă, vom returna elementul selectat aleatoriu. Rezultatul este că vom continua să alegem obiecte pe care nu le-am ales înainte, în timp ce amestecăm sacul pe măsură ce mergem. Aceasta înseamnă că conținutul său este într-o ordine diferită atunci când dorim să o traversăm din nou.

Acum este timpul să încercăm clasa nouă creată.


Utilizarea clasei Bag Shuffle

Cu câțiva ani în urmă am creat o clonă Boggle pentru iPhone.


Credit de imagine: Rich Brooks

O problemă cu care am confruntat a fost crearea de panouri dense care au folosit doar 16 litere, dar le-a permis utilizatorului să formeze sute de cuvinte cu cele 16 litere. Am aflat despre frecvențele scrisorilor și cum aș putea să le folosesc pentru a crea o experiență pozitivă pentru utilizatori.

Folosind frecvența în care literele apar în textul englezesc, putem construi un dicționar ponderat.

 private static Dicționar letterFrequencies = un nou dicționar 'E', 12.702, 'T', 9.056, 'A', 8.167, 'O', 7.507, 'I', 6.966 , 'S', 6.327, 'H', 6.094, 'R', 5.987, 'D', 4.253, 'L', 4.025 'U', 2.758, 'M', 2.406, 'W', 2.306, 'F', 2.228,  P ', 1.929, ' B ', 1.492, ' V ', 0.978, ' K ', 0.772,  , 0,095, 'Z', 0,074; // total: 99,965

Notă: Q este tratat un pic diferit de celelalte litere. Acesta păstrează valoarea din tabelul de frecvență a literelor, dar apare ca Qu în multe jocuri de cuvinte.

Acum putem crea o instanță a clasei noastre Shuffle Bag, umpleți sacul Shuffle cu date și creați dulapuri bogate de Boggle.

 static void principal (șir [] args) var shuffleBag = nou ShuffleBag (88000); suma int = 0; foreach (litera var în litereFrecvențe) amount = (int) letter.Value * 1000; shuffleBag.Add (letter.Key, suma);  pentru (int i = 0; i < 16; i++) Console.Write(shuffleBag.Next()); Console.WriteLine(); 

Notă: Cel mai important lucru pe care trebuie să-l îndepărtați de acest cod este acela Cantitate. Un multiplicator de 1000 returnează rezultate mai bune decât un multiplicator de 10.

Rulați rezultatele printr-un solver online. Câte cuvinte găsești?


Concluzie

În acest articol, am recunoscut problema în utilizarea valorilor aleatoare cu dacă / altceva am introdus o soluție folosind Shuffle Bags și am demonstrat o utilizare prin crearea de panouri dense de Boggle. Folosind Shuffle Bags, preluăm controlul Întâmplător() metode și să creeze o distribuție uniformă a valorilor care ajută la o experiență de joc pozitivă.