Jak stworzyć „przyjemnie” losowy, a nie pseudolosowy?

26

Tworzę grę, która po kolei przedstawia różne rodzaje łamigłówek. Wybieram każdą układankę z pseudolosowym numerem. Dla każdej układanki istnieje wiele odmian. Wybieram wariant z innym numerem pseudolosowym. I tak dalej.

Chodzi o to, że chociaż powoduje to prawie prawdziwą przypadkowość, to nie jest to, czego naprawdę chce gracz. Gracz zazwyczaj chce tego, co postrzega i określa jako losowy, ale tylko wtedy, gdy nie ma tendencji do powtarzania łamigłówek. Nie bardzo przypadkowe. Po prostu nieprzewidywalne.

Zastanawiając się nad tym, potrafię sobie wyobrazić, jak to zrobić. Na przykład tymczasowe wyeliminowanie ostatnich N wyborów ze zbioru możliwości podczas wybierania nowego wyboru. Lub przypisując każdemu wyborowi jednakowe prawdopodobieństwo, zmniejszając prawdopodobieństwo wyboru do zera przy wyborze, a następnie powoli zwiększając wszystkie prawdopodobieństwa z każdym wyborem.

Zakładam, że istnieje ustalony sposób na zrobienie tego, ale po prostu nie znam terminologii, więc nie mogę jej znaleźć. Ktoś wie? A może ktoś rozwiązał to w przyjemny sposób?

Hilton Campbell
źródło
4
Książka „AI Game Programming Wisdom 2” zawiera rozdział o filtrowanej przypadkowości, która z tego, co pamiętam, jest dokładnie tym, czego szukasz. W tej chwili tego nie mam, więc nie mogę udzielić pełnej odpowiedzi.
Anton
Aby to wyjaśnić: kiedy mówisz „nie powtarzaj łamigłówek”, masz na myśli, że po prostu nie chcesz obok siebie dwóch łamigłówek tego samego typu? Innymi słowy, jeśli właśnie wybrałeś sudoku, nie oferuj kolejnej łamigłówki sudoku, ale jeśli była to Sudoku # 19, możesz w tej chwili zaoferować Picross # 19 (innymi słowy, numer odmiany nie ma znaczenia) ?
Steven Stadnicki
2
Bardzo powiązane pytanie: stackoverflow.com/questions/910215/...
Chris Burt-Brown
1
OK, właśnie dotarła moja kopia AI Game Programming Wisdom 2. Przeczytałem rozdział o filtrowanej losowości i sprawdziłem kod źródłowy. To chyba najlepsze podejście. Pozwala mi to po prostu używać liczb losowych, ale następnie filtrować liczby, aby nie wystąpiły nieoczekiwane wzorce. Wydaje się, że jest bardziej kuloodporny niż tasująca torba.
Hilton Campbell
1
Jeszcze jedna aktualizacja ... dla mojej konkretnej aplikacji, filtrowana losowość nie całkiem tego zrobiła. Naprawdę chcę, aby gracz powtórzył wszystkie typy i podtypy przed powtórzeniem, więc wybrałem torbę losową.
Hilton Campbell

Odpowiedzi:

25

Jeśli masz skończoną liczbę zagadek, możesz:

  • Zbuduj listę puzzli, wszystkie z nich lub niektóre losowo wybrane;
  • Przetasuj tę listę (zobacz na przykład Knuff Shuffle );
  • Pozwól swojemu graczowi grać przez tę listę;
  • Gdy lista będzie pusta, zacznij od nowej.

EDYTOWAĆ

Nie wiedziałem o tym, ale przeglądanie SE uświadomiło mi, że jest to tak naprawdę znane jako „tasowanie torby”. Więcej informacji tutaj , tutaj lub tam .

EDYCJA 2

Klasyczny Knuth Shuffle idzie w ten sposób:

To shuffle an array a of n elements (indices 0..n-1):
    for i from n  1 down to 1 do
        j  random integer with 0  j  i
        exchange a[j] and a[i]

Steven Stadnicki słusznie zauważył w swoim komentarzu, że tego rodzaju rzeczy nie zapobiegają powtórzeniu się po przetasowaniu. Można to uwzględnić, dodając specjalną skrzynkę dla ostatniego elementu:

To reshuffle an array a of n elements and prevent repetitions (indices 0..n-1):
    return if n <= 2

    // Classic Knuth Shuffle for all items *except* the last one
    for i from n  2 down to 1 do
        j  random integer with 0  j  i
        exchange a[j] and a[i]

    // Special case for the last item
    // Exchange it with an item which is *not* the first one
    r  random integer with 1  r  n - 1
    exchange a[r] and a[n - 1]
Laurent Couvidou
źródło
1
Może to działać, ale musisz zachować ostrożność, jeśli przetasujesz po każdym przejściu, aby nie zacząć od tego samego przedmiotu, na którym skończyłeś.
Steven Stadnicki
@Steven Rzeczywiście. Ten element można wykluczyć z nowej listy. Właściwie może być pomysł zbudowania listy tylko kilku losowych przedmiotów i zbudowania następnej listy tylko z pozostałymi elementami. Więc jeśli masz 100 przedmiotów, zbuduj np. Przetasowaną listę 10. Po zakończeniu z tą listą zbuduj następny z 10 przedmiotami w 90, które nie zostały wcześniej wybrane.
Laurent Couvidou
+1. Dodano obsługę tej techniki jako „więcej zabawy”: na przykład Tetris produkuje „losowe” elementy. Zestaw jednego z każdego kawałka jest tasowany i iterowany, co pozwala uniknąć długich sekwencji duplikatów, które nieuchronnie wywołałyby prawdziwą przypadkowość.
KutuluMike
1
@Hilton Nie lubię tego rodzaju „pętli while, dopóki losowe nie dadzą mi tego, czego chcę” ... Nie bardzo prawdopodobne, że spowoduje to jakiekolwiek problemy. Ale nadal mam wrażenie, że jest to wezwanie do losowych nieskończonych pętli lub spadków wydajności - które są okropne do debugowania. Wykluczenie ostatniej pozycji z poprzedniej listy z nowej pozwala tasować tylko raz, dla tego samego rezultatu.
Laurent Couvidou
1
Masz rację, a ja miałem te same zastrzeżenia. Zamiast wykluczać poprzedni ostatni element, teraz mam go tylko raz przetasować, a następnie, jeśli poprzedni ostatni element jest teraz pierwszy, zamieniam go losowo z innym innym przedmiotem.
Hilton Campbell
2

Wariant podejścia Lorancou: dla każdego rodzaju układanki zachowaj tablicę (potasowanych) numerów układanek; następnie za każdym razem, gdy trafisz w tego typu łamigłówkę, usuń kolejny numer z listy. na przykład załóżmy, że masz puzzle Sudoku, Picross i Kenken, każda z zagadkami # 1..6. Utworzyłbyś trzy przetasowane tablice liczb 1..6, po jednym dla każdego rodzaju układanki:

  • Sudoku: [5, 6, 1, 3, 4, 2]
  • Picross: [6, 2, 4, 1, 3, 5]
  • KenKen: [3, 2, 5, 6, 4, 1]

Teraz potasowałbyś typy puzzli, jak sugeruje Lorancu; powiedzmy, że się pojawi [Picross, Sudoku, Kenken]. Następnie za każdym razem, gdy trafisz łamigłówkę danego typu, użyj następnego numeru z „listy losowania”; ogólnie twoja prezentacja puzzli to [Sudoku # 5, Picross # 6, Kenken # 3, Sudoku # 6, Picross # 2, Kenken # 2, ...]

Jeśli nie chcesz utrzymywać łamigłówek w tej samej ogólnej kolejności za każdym razem przez pętlę, myślę, że najlepsza jest opcja „wybierz losowo, ignorując kilka ostatnich typów”. Istnieją sposoby, aby uczynić to nieco bardziej wydajnym; na przykład powiedzmy, że masz 20 rzeczy i chcesz zignorować ostatnie 5 wybranych. Następnie zamiast losowo wybierać liczbę 1..20 i „przewijać”, aż znajdziesz ją poza ostatnimi 5, zamiast tego po prostu wybierz liczbę 1..15 i przejdź przez różne rodzaje układanek, pomijając dowolny rodzaj układanki, który został wybrany (możesz to zrobić łatwo, utrzymując tablicę bitów zawierającą 5 ostatnio wybranych zagadek).

Steven Stadnicki
źródło