Jestem w trakcie tworzenia nowej prostej gry na urządzenia mobilne i poświęciłem kilka dni na następną część.
Dla uproszczenia załóżmy, że mam dwóch wojowników. Jedynym ich atrybutem jest Atak i Obrona. Kiedy pierwsze ataki, jedyne, co się liczy, to atak jego i obrona przeciwnika. I wzajemnie.
Nie mają wyposażenia, przedmiotów, wytrzymałości ani zdrowia. Just Attack vs. Defense.
Przykład:
Myśliwiec 1:
Atak: 50, Obrona: 35
Myśliwiec 2:
Atak 20, Obrona: 80
Proces walki będzie tylko jednym atakiem, który określi zwycięzcę. Więc nie ma wielu ataków ani rund. Nie chcę, aby było to deterministyczne, ale dodaję lekką wersję nieoczekiwanego. Wojownik o niższym ataku będzie w stanie wygrać innego wojownika o wyższej obronie (ale oczywiście nie za każdym razem)
Moim pierwszym pomysłem było uczynienie go liniowym i wywołanie jednolitego generatora liczb losowych.
If Random() < att1 / (att1 + def2) {
winner = fighter1
} else {
winner = fighter2
}
Przykład z atakiem 50 i obroną 80, atakujący wojownik będzie miał około 38% do wygrania. Wydaje mi się jednak, że to, co nieoczekiwane, jest zbyt dalekie i najgorsi wojownicy dużo wygrają.
Zastanawiałem się, jak pracowałeś w podobnych sytuacjach.
PS Dużo szukałem w tym QnA i innych źródłach i znalazłem podobne pytania, które wymieniono jako zbyt ogólne dla SE. Ale miały one wiele atrybutów, broni, przedmiotów, klas itp., Co może sprawić, że będzie to zbyt skomplikowane. Myślę, że moja wersja jest znacznie prostsza w dopasowaniu do stylu QnA SE.
Odpowiedzi:
Jeśli chcesz, aby twoje wyniki walki były bardziej przewidywalne, ale nie całkowicie deterministyczne, wybierz najlepszy system n .
Powtórz
n
czasy walki (gdzien
powinna być nierówna liczba) i ogłoś walczącego zwycięzcą, który wygrał częściej. Im większa wartość,n
tym mniej niespodzianek wygrywasz i przegrywasz.Ten system działa tylko w szczególnym przypadku, gdy walka jest prostym binarnym wynikiem wygranej lub przegranej. Gdy walka ma bardziej złożone wyniki, na przykład gdy zwycięzca wciąż traci punkty życia w zależności od tego, jak blisko była wygrana, takie podejście już nie działa. Bardziej ogólnym rozwiązaniem jest zmiana sposobu generowania liczb losowych. Gdy wygenerujesz wiele liczb losowych, a następnie weźmiesz średnią, wyniki zostaną zgrupowane w pobliżu środka zakresu, a bardziej ekstremalne wyniki będą rzadsze. Na przykład:
będzie miał taką krzywą dystrybucji:
(zdjęcie dzięki uprzejmości Anydice - naprawdę przydatne narzędzie do projektowania formuł mechaniki gier, które wymagają losowości, nie tylko w przypadku gier stołowych)
W moim obecnym projekcie korzystam z funkcji pomocniczej, która pozwala ustawić dowolną wielkość próbki:
źródło
+
zamiast tego,*
czy źle zrozumiałem, co robi?Właśnie tego określałem zwycięzcę bitwy w moim aplecie Lords of Conquest Imitator. W tej grze, podobnie jak w twojej sytuacji, jest tylko wartość ataku i wartość obrony. Prawdopodobieństwo, że atakujący wygra, jest tym większe, im więcej punktów ma atakujący, i tym mniej punktów ma obrona, przy równych wartościach stanowiących 50% szans na udany atak.
Algorytm
Rzuć losową monetą.
1a. Głowy: obrona traci punkt.
1b. Ogony: głowy tracą punkt.
Jeśli zarówno obrona, jak i atakujący nadal mają punkty, wróć do kroku 1.
Kto ma 0 punktów, przegrywa bitwę.
3a. Atakujący do 0: Atak kończy się niepowodzeniem.
3b. Obrona do 0: Atak się udaje.
Napisałem to w Javie, ale powinno być łatwe do przetłumaczenia na inne języki.
Przykład
Załóżmy na przykład, że att = 2 i def = 2, aby upewnić się, że prawdopodobieństwo wynosi 50%.
Bitwa zostanie rozstrzygnięta w maksymalnej
n = att + def - 1
liczbie rzutów monetą lub 3 w tym przykładzie (w zasadzie jest to najlepsza z 3 tutaj). Istnieją 2 n możliwych kombinacji rzutów monetą. Tutaj „W” oznacza, że atakujący wygrał rzut monetą, a „L” oznacza, że atakujący stracił rzut monetą.Atakujący wygrywa w 4/8, czyli 50% przypadków.
Matematyka
Prawdopodobieństwa matematyczne wynikające z tego prostego algorytmu są bardziej skomplikowane niż sam algorytm.
Liczba kombinacji, w których dokładnie x Ls jest podana przez funkcję kombinacji:
Atakujący wygrywa, gdy są pomiędzy
0
iatt - 1
L. Liczba zwycięskich kombinacji jest równa sumie kombinacji od0
doatt - 1
, skumulowanego rozkładu dwumianowego:Prawdopodobieństwo atakującego wygranej w podzielonej przez 2 n , skumulowanego dwumianowego prawdopodobieństwa:
Oto kod w Javie do obliczenia tego prawdopodobieństwa dla wartości arbitralnych
att
idef
wartości:Kod testowy:
Wynik:
Spostrzeżenia
Prawdopodobieństwa są,
0.0
jeśli atakujący ma0
punkty,1.0
jeśli atakujący ma punkty, ale obrona ma0
punkty,0.5
jeśli punkty są równe, mniej niż0.5
jeśli atakujący ma mniej punktów niż obrona, i większy niż,0.5
jeśli atakujący ma więcej punktów niż obrona .Biorąc
att = 50
idef = 80
musiałem przełączyć się naBigDecimal
s, aby uniknąć przepełnienia, ale dostaję prawdopodobieństwo około 0,0040.Możesz zbliżyć prawdopodobieństwo do 0,5, zmieniając
att
wartość na średnią z wartościatt
idef
. Att = 50, Def = 80 staje się (65, 80), co daje prawdopodobieństwo 0,1056.źródło
Możesz zmodyfikować atak losową liczbą próbkowaną z normalnego rozkładu. W ten sposób przez większość czasu wynik będzie zgodny z oczekiwaniami, ale czasami wyższy atak przegra przeciwko niższej obronie lub niższy atak wygra przeciwko wyższej obronie. Prawdopodobieństwo takiego zdarzenia zmniejszy się wraz ze wzrostem różnicy między atakiem a obroną.
Funkcja
norm(x0, sigma)
zwraca liczbę zmiennoprzecinkową próbkowaną z rozkładu normalnego wyśrodkowanego na x0, ze sigma odchylenia standardowego. Większość języków programowania udostępnia bibliotekę z taką funkcją, ale jeśli chcesz sprawić, by to zrobiłaś sama, spójrz na to pytanie . Trzeba by dostosować sigma tak, aby „czuł się dobrze”, ale wartość 10–20 może być dobrym początkiem.Dla kilku wartości sigma prawdopodobieństwo zwycięstwa dla danego
att1 - def2
wygląda następująco:źródło