09
Bardzo często przy tworzeniu różnego rodzaju aplikacji wykorzystujemy wartości losowe. Na ogół generują je odpowiednie funkcje wbudowane w biblioteki matematyczne danego języka programowania. Na przykład język javascript ma wbudowaną funkcję random, c++ natomiast funkcję rand. Zastanawialiście się kiedyś, jak one działają? Dzisiaj stworzymy własną funkcję generującą liczby losowe i prześledzimy działanie takiego mechanizmu. Zapraszam!
Do generowania przewidywalnych liczb losowych wykorzystamy pewien powszechnie znany, chaotyczny algorytm. Jego logika jest następująca:
- Wybieramy dowolną liczbę Gen1 o stosunkowo dużej wartości (najlepiej, jeśli będzie to liczba zmiennoprzecinkowa) i na jej podstawie tworzymy drugą liczbę Gen2, 2 razy większą od Gen1. Są to nastawy algorytmu.
- Wybieramy wartość Max, która będzie oznaczała największą możliwą wylosowaną liczbę.
- Określamy zarodek algorytmu. Jeśli osiągnąć chcemy nieprzewidywalne liczby (za każdym razem inne wartości), podajemy tutaj odczyt zegara systemowego. Jeśli nie, wybieramy określoną przez nas wartość. Dla zwiększenia chaotyczności wyników, warto wybrać liczbę zmiennoprzecinkową.
- Dla każdej iteracji: Mnożymy Gen1 przez zarodek i dodajemy Gen2. Wynik to reszta dzielenia otrzymanej wartości przez Max. Wynik traktujemy jako zarodek przy kolejnej iteracji.
- Zwracamy wynik przy każdej iteracji.
Napiszemy teraz prosty program, który będzie wykorzystywał powyższy mechanizm. Jego działanie zobaczyc możecie tutaj.
Na początku tworzymy klasę, która będzie reprezentowała nasz generator liczb losowych.
function c_Pseudo_random(){
this.Gen1 = null;
this.Gen2 = null;
this.Max = null;
this.Zarodek = null;
this.init = c_Pseudo_random_init;
this.generate = c_Pseudo_random_generate;
}
Klasa zawiera trzy zmienne, od których zależy zachowanie algorytmu, oraz dwie metody. Znaczenie zmiennych opisane zostało powyżej.
function c_Pseudo_random_init(p_gen1, p_zarodek, p_max){
this.Gen1 = p_gen1;
this.Gen2 = this.Gen1*2;
this.Zarodek = p_zarodek;
this.Max = p_max;
}
Pierwsza metda służy do inicjalizacji obiektu. Za jej pomocą określamy wartości zmiennych klasy.
function c_Pseudo_random_generate(){
this.Zarodek = ((this.Zarodek * this.Gen1)+ this.Gen2) % this.Max;
return this.Zarodek;
}
Druga metoda służy do wygenerowania jednej liczby losowej na podstawie wartości nastaw generatora oraz aktualnej wartości zarodka. Metoda jednocześnie ustawia wygenerowaną liczbę jako zarodek, dzięki czemu będzie ona wykorzystana przy kolejnym wywołaniu funkcji (generowaniu kolejnej liczby).
Tworzymy dwie zmienne
var Rand1 = new c_Pseudo_random(); var Out_array = new Array();
Pierwsza Rand1 reprezentuje obiekt klasy generatora, natomiast druga Out_array zawierała będzie tablicę wygenerowanych liczb.
W sekcji body tworzymy pola tekstowe, dzięki którym użytkownik będzie mógł ustawić zmienne generatora: Gen1, Max, wartość zarodka oraz liczbę iteracji, określającą ilość wygenerowanych liczb.
Dodatkowo tworzymy dwa przyciski. Pierwszy służy do wygenerowania zarodka na podstawie aktualnej wartości zegara systemowego:
function Zarodek_generate(){
zarodek.value = (new Date).getTime();
}
Drugi przycisk Generuj służy do wywołania funkcji, która wypełni tablicę losowymi wartościami:
function Generate(){
Rand1.init(gen1.value, zarodek.value, max.value);
Na początku inicjujemy obiekt generatora na podstawie zmiennych wprowadzonych przez użytkownika.
for(var i=0; i<iteracje.value; i++){
Out_array[i] = Math.round(Rand1.generate());
}
Następnie w pętli wypełniamy tablicę wynikową losowymi liczbami zwracanymi przez nasz generator. Zaokrąglanie wyników jest opcjionalne. Jeśli chcemy, żeby generowane liczby miały wartości zmiennoprzecinkowe, możemy odpuścić sobie zaokrąglanie.
Display_out_text(); }
I na końcu wyświetlamy wartości tablicy za pomocą stworzoej przez siebie funkji. Sposób wyśwetlania wyników nie jest dla nas istotny, powiem tylko, że program wyświetla tablicę wynikową w odpowiedniej tabeli.
Do czego może nam być przydatna stworzona dzisiaj klasa? Ano jeżeli np. chcemy generować różne światy „w locie” na podstawie losowych wartości i jednocześnie chcemy, aby za każdym razem tworzone światy wyglądały identycznie, możemy użyć naszej funkcj, wywołując ja za każdym razem z tym samym zarodkiem. Taki mechanizm wykorzystują gry, których światy tworzone są na zasadzie algorytmu nieskończonych wszechświatów. Dzięki temu dane charakteryzujące świat gry zajmują śladowe miejsce w pamięci (wystarczy przechowywać zarodek funkcji losującej), wszyscy gracze grają w tę samą grę (niezależnie od środowiska, za każdym razem wygenerowany zostanie dokładnie taki sam świat) i jednocześnie wielkość takiego świata może być praktycznie nieograniczona (za równo ze względu na jego wielkość, jak i szczegółowość). Mechanizm ten wykorzystuje wiele starszych produkcji, których akcja dzieje sie we wszechświecie wypełnionym wieloma różnorodnymi planetami. Dobrym przykładem jest tutaj stara gra Elite(1980r.), która mimo bardzo ograniczonych warunków sprzętowych(16kB RAM), zadziwia różnorodnością świata gry.
Możemy pomyśleć o tym mechaniźmie przy tworzeniu gier intenetowych opartych na WebGL’u. Ograniczona przepustowość łączy internetowych nie pozwala na przesyłanie dużych ilości danych opisujących poszczególne etapy, więc generowanie ich wlocie na podstawie zarodków może być bardzo użyteczne. Kod stworzonego dzisiaj programu pobrać możecie tutaj. Zachęcam do eksperymentów!