Jak poprawić generowanie liczb losowych w moim kontekście?

11

W mojej grze na górze ekranu jest słowo, litery z góry spadają, a użytkownik musi je dotknąć, aby je uzupełnić.

Obecnie generuję litery losowo (tak naprawdę liczby losowe i liczby są indeksem tablic liter, np .: 0 = a, 1 = b), ale problem polega na tym, że uzyskanie wszystkich wymaganych liter zajmuje zbyt dużo czasu słowo.

Chcę, aby generowane losowo liczby generowały wymagane litery częściej, aby gracz nie musiał spędzać całego dnia na uzupełnieniu jednego słowa.

Próbowałem następujących metod:

  1. Wykryj wszystkie litery w słowie (słowo ma zawsze 6 liter), wygeneruj tablicę indeksów o długości 6, przypisz każdy indeks tablicy do losowej liczby od litery-2 do litery + 2, a na końcu wybierz losowo jeden indeks z tablicy do pokazania.

  2. Mieć zmienną selektora, której wartość mieści się w zakresie [0..2], generowaną losowo, jeśli selektor == 0, to wykrywaj litery tworzące słowo i losowo wybieraj jedną literę, w przeciwnym razie losowo uzyskaj dowolny alfabet z az.

Obie te metody nie zapewniły mi żadnej pomocy. Będę bardzo szczęśliwy, jeśli możesz mi pomóc.

Dziękuję za przeczytanie tego, mam nadzieję, że zrozumiałeś pytanie i czekam na odpowiedź.

Daniyal Azram
źródło
2
„obie te metody nie zapewniły mi żadnej pomocy” Dlaczego nie? Co nie działało z tymi metodami?
Vaillancourt
Nie wiem dlaczego, ale wciąż potrzebuję zbyt wiele czasu, jak 1 minuta, aby uzyskać wszystkie wymagane alfabety.
Daniyal Azram,
@DaniyalAzram prawdopodobnie powinieneś zwiększyć częstotliwość, jeśli litery te nie pojawiają się wystarczająco często, ponieważ wygląda na to, że na tym polega twój problem.
JFA

Odpowiedzi:

21

Tak naprawdę nie chcesz losowej dystrybucji. Wskazuję to wyraźnie, ponieważ to, co uważamy za „losowe” w projekcie, zwykle nie jest prawdziwą przypadkowością.

Teraz, mając to na uwadze, dodajmy kilka poprawek - są to rzeczy, którymi będziesz się bawić, dopóki projekt nie będzie „poprawny”.

ChooseLetter() {
    const float WordLetterProbability = 0.5f;
    if (Random.NextFloat01() < WordLetterProbability) {
        // Pick a random letter in the word
    }
    else {
        // Pick a random letter *not* in the word
    }
}

Prawdopodobieństwo kontroluje, jak prawdopodobne jest, że dane połączenie z SelectLetter da ci literę słowa - przy 0,5 otrzymasz literę słowa mniej więcej za każdym razem. Przy 0,25 jedna czwarta to litera słowa itp.

To wciąż jest nieco uproszczone - ponieważ losowość jest, cóż, losowa , tak naprawdę nie masz gwarancji, jak długo będziesz przechodził między literami słów. (Teoretycznie możesz przejść na zawsze bez litery słowa, jest to po prostu bardzo mało prawdopodobne.) Zamiast tego możemy dodać czynnik zwiększający prawdopodobieństwo litery słowa za każdym razem, gdy jej nie otrzymamy:

const float BaseWordLetterProbability = 0.5f;
const float WordLetterProbabilityIncrease = 0.25f;
float WordLetterProbability = BaseWordLetterProbability;
ChooseLetter() {
    if (Random.NextFloat01() < WordLetterProbability) {
        // Pick a random letter in the word
        WordLetterProbability = BaseWordLetterProbability;
    }
    else {
        // Pick a random letter *not* in the word
        WordLetterProbability += WordLetterProbabilityIncrease;
    }
}

Okej, więc teraz nigdy nie idziemy więcej niż dwie litery bez litery słowa. (Ponieważ po dwóch nieudanych próbach istnieje 1,0 prawdopodobieństwo otrzymania litery słowa.)

Na koniec musimy wziąć pod uwagę, jak działa wybór litery w słowie. Aby dostarczyć graczowi litery, których naprawdę potrzebują, musimy usunąć litery z zestawu, gdy tylko je otrzymają.

Na przykład, jeśli słowem jest „test”, a gracz już ma „s”, nie chcemy dawać innym „s”, ponieważ nie potrzebują go!

Odtąd reszta jest dostosowywana do Twojego projektu.

ACEfanatic02
źródło
Łał! jak to wymyśliłeś?: p Jutro przetestuję twoją metodę jutro rano i myślę, że zadziała, przekażę więcej informacji zwrotnych po przetestowaniu tej metody. Dziękuję bardzo za taką odpowiedź.
Daniyal Azram
4
Statystyka! Jako twórca gry lub programista statystyki są bardzo warte nauki. Przynajmniej zrozumienie prawdopodobieństwa (i jak je połączyć) jest niezwykle przydatne.
ACEfanatic02
1
Przyjdź, aby zasugerować to samo. Świetna odpowiedź. Pamiętaj również, że takie rozwiązanie może być sposobem na wprowadzenie poziomów trudności. Łatwe = 0,5, średnie = 0,25, twarde = 0,10. itp.
Tasos,
Nie zgadzam się, że to nie jest przypadkowe. To jest losowe, to tylko rozkład częściowy, podczas gdy zwykle myślimy o rozkładzie jednolitym. I aby dodać do twojego pomysłu, poszedłbym dalej niż tylko „Wybierz losową literę w słowie”. Rozprowadzę ją według liter, które występują najczęściej. Na przykład „mississippi” potrzebuje więcej s, więc wybieram ich więcej.
Blaine
21

Prawdopodobieństwo ważenia wszystkich liter można wyważyć na podstawie częstotliwości ich występowania w języku, w którym znajdują się słowa. Dobrą wskazówką jest zestaw scrabble . Na przykład wersja angielska ma 12 E, ale tylko jedno Z i jedno Q.

Prostym sposobem na wdrożenie tego jest umieszczenie wszystkich liter w ciągu po kolei, tak aby każda litera pojawiała się tak często, jak to pożądane, a następnie niech RNG pobierze literę z losowej pozycji. Przykład pseudokodu:

const String letters = "AAAAAAAAABBCCDDDDEEEEEEEEEEEEFFGGGHHIIIIIIIIIJ/*...and so on...*/"

char randomLetter = letters[randomIntegerBetween(0, letters.length - 1)];
Philipp
źródło
2
+1 To dobra koncepcja, ale podejrzewam, że jest bardziej elegancka implementacja.
Evorlor,
Aby uzyskać bardziej szczegółowy rozkład, możesz przechowywać tabelę częstotliwości liter skalowaną tak, aby sumowały się do 1, generować liczbę losową od 0 do 1 i zapętlać tabelę odejmując częstotliwość każdej litery od liczby losowej, aż do staje się zerowy lub ujemny. Możesz to zoptymalizować, sortując najpierw najczęściej używane litery w tabeli. Lub użyj czegoś takiego jak metoda aliasowa, aby całkowicie uniknąć zapętlania tabeli.
Ilmari Karonen
4
To nie rozwiązałoby problemu. Problem polega na tym, że użytkownik potrzebuje określonych liter, aby ukończyć grę. Powiedz, że potrzebują Z? Co wtedy? To sprawiłoby, że rzadkie litery byłyby trudniejsze, co jeszcze bardziej frustrowałoby użytkownika.
AmazingDreams,
@AmazingDreams wskazuje na dobrą rzecz, ale możemy ją nieco zmodyfikować, aby przypisać większą wagę wymaganym alfabetom i mniejszą wagę innym. Muszę powiedzieć, że jest to bardzo dobra koncepcja do naśladowania.
Daniyal Azram
4

Oto jeden ze sposobów na ulepszenie go za pomocą jednego parametru k, który można dostosować.

Zamiast wybierać losową literę:

  1. wybierz losową literę A
  2. wybierz losową liczbę X
  3. jeśli X > k i A nie ma [list of remaining needed letters], spróbuj ponownie od 1.

Im mniejszy k, tym częściej ostatnia litera Abędzie taką, która jest rzeczywiście potrzebna.

Aby ulepszyć algorytm, graj dowolną wartością k, np k = 0.5 . Jeśli uznasz, że gra jest zbyt trudna, spróbuj 0.4zamiast tego itp., Aż znajdziesz rozsądną wartość. Daje to również bezpośrednio ustawienie trudności , które możesz na przykład zwiększyć w miarę postępów gracza.

sam hocevar
źródło
Dzięki za odpowiedź, ale wygląda to tak jak odpowiedź ACEfanatic02.
Daniyal Azram
3

Prostym sposobem na zagwarantowanie, że wymagane litery pojawią się w danym czasie, jest wypełnienie tablicy literami w słowie i pozostałej części alfabetu (być może powtórzonej), a następnie losowe przetasowanie tablicy (c ++ ma std :: random_shuffle w standardowej bibliotece, jeśli używasz innego języka, wdrożenie nie jest trudne).

Jeśli chcesz, aby litery w słowie były wyświetlane szybciej, umieść więcej kopii słów-liter w tablicy.

GuyRT
źródło
0

Jeśli używasz C ++, możesz użyć już istniejącej dystrybucji http://en.cppreference.com/w/cpp/numeric/random/piecewise_constant_distribution

Ma również zamortyzowaną stałą złożoność czasu, co jest lepsze niż naiwne metody. przykład:

#include <iostream>
#include <string>
#include <map>
#include <random>
#include <numeric>

int main()
{
    constexpr double containedLetterWeight = 3.0;
    constexpr int iterations = 10000;
    std::string word = "DISTRIBUTION";

    std::mt19937 rng(123);

    std::vector<double> values('Z' - 'A' + 2);
    std::iota(values.begin(), values.end(), 0.0);

    std::vector<double> weights('Z' - 'A' + 1, 1.0);
    for(const auto& c : word) weights[c - 'A'] = containedLetterWeight;

    std::piecewise_constant_distribution<> dist(values.begin(), values.end(), weights.begin());

    std::map<char, int> results;
    for(int n = 0; n < iterations; ++n)
    {
        ++results[static_cast<char>(dist(rng)) + 'A'];
    }
    for(const auto& p : results)
    {
        std::cout << p.first << ' ' << static_cast<float>(p.second) / iterations << '\n';
    }
}
Sopel
źródło