¿Cómo evito "demasiado" rayas afortunadas / desafortunadas en la generación de números aleatorios?


30

Actualmente estoy lidiando con un sistema de combate multijugador donde el daño infligido por los jugadores siempre se multiplica por un factor aleatorio entre 0.8 y 1.2.

En teoría, un RNG verdaderamente aleatorio puede producir el mismo número muchas veces (ver el dilema de Tetris ). Esto podría resultar en una partida en la que el jugador siempre está haciendo un daño muy alto, mientras que el otro siempre hace un daño muy bajo.

¿Qué puedo hacer para asegurarme de que esto no suceda? ¿Son algunos RNG mejores que otros para evitar la repetición?


No veo cómo funciona esto. Por supuesto, obtendrás una secuencia de x1, x2, x3, x4 ... donde todas las x son grandes. ¿No es eso al azar?
El pato comunista

Respuestas:


26

Puede resolverlo de la misma manera que lo hace Tetris, haciendo una lista preestablecida de resultados de daño y barajando.

Digamos que sabes que el jugador causará daños de 0.8x a 1.2x con una distribución lineal. Tome la lista [0.8, 0.9, 1.0, 1.1, 1.2]. Al azar , se obtiene, por ejemplo, [1.2, 1.0, 0.8, 0.9, 1.1].

La primera vez que el jugador inflige daño, inflige 1.2x. Entonces 1x. Luego, etc., a 1.1x. Solo cuando la matriz está vacía, debe generar y barajar una nueva matriz.

En la práctica, es probable que desee hacer esto en más de 4 matrices a la vez (por ejemplo, comience con [0.8,0.8,0.8,0.8,0.9,0.9,0.9,0.9, ...]). De lo contrario, el período de la secuencia es lo suficientemente bajo como para que los jugadores puedan determinar si su próximo golpe es "bueno" o no. (Aunque eso también puede agregar más estrategia al combate, como en la tabla Hoimi de Dragon Quest IX , que la gente descubrió cómo investigar mirando los números de curación y los ajustes hasta que se garantice una caída rara).


3
Para hacerlo un poco más aleatorio, siempre puede tener la mitad de la lista como números aleatorios y la otra mitad calculada como (2-x) para obtener el promedio correcto.
Adam

2
@ Adam: Ese método realmente solo funciona para este ejemplo en particular; si estás repartiendo piezas de Tetris en lugar de multiplicadores de daño, ¿qué es el bloque 2 - S?

66
El término habitual para esto es una especie de sistema "aleatorio sin reemplazo". Es análogo a usar una baraja de cartas en lugar de dados, de verdad.
Kylotan

Aún mejor, podría hacer la mitad de los números realmente al azar, y solo la mitad de ellos está sujeta a esta regla.
o0 '.

1
Todavía puede resultar en que la distribución local no se parezca a la distribución global, que es exactamente lo que la pregunta no quiere. Términos como "realmente aleatorio" son pseudomatemáticas vagas; cuanto más defina qué propiedades estadísticas desea, más clara será su intención y el diseño del juego.

5

De hecho, escribí un código para hacer esto . La esencia de esto es usar estadísticas para corregir rayas desafortunadas. La forma en que puede hacer esto es hacer un seguimiento de cuántas veces se ha producido el evento y utilizarlo para sesgar el número generado por el PRNG.

En primer lugar, ¿cómo hacemos un seguimiento del porcentaje de eventos? La manera ingenua de hacer esto sería mantener todos los números generados en la memoria y promediarlos: lo que funcionaría pero es terriblemente ineficiente. Después de pensar un poco, se me ocurrió lo siguiente (que es básicamente un promedio móvil acumulativo ).

Tome las siguientes muestras de PRNG (donde procesamos si la muestra es> = 0.5):

Values: 0.1, 0.5, 0.9, 0.4, 0.8
Events: 0  , 1  , 1  , 0  , 1
Percentage: 60%

Tenga en cuenta que cada valor contribuye a 1/5 del resultado final. Miremos de otra manera:

Values: 0.1, 0.5
Events: 0  , 1

Observe que 0contribuye al 50% del valor y 1contribuye al 50% del valor. Tomado un poco más lejos:

Values: [0.1, 0.5], 0.9
Events: [0  , 1  ], 1

Ahora los primeros valores aportan el 66% del valor y el último 33%. Básicamente podemos destilar esto al siguiente proceso:

result = // 0 or 1 depending on the result of the event that was just generated
new_samples = samples + 1

average = (average * samples / new_samples) + (result * 1 / new_samples)
// Essentially:
average = (average * samples / new_samples) + (result / new_samples)

// You might want to limit this to, say, 100.
// Leaving it to carry on increasing can lead to unfairness
// if the game draws on forever.
samples = new_samples

Ahora necesitamos sesgar el resultado del valor muestreado del PRNG, porque vamos por un porcentaje de probabilidad aquí, las cosas son mucho más fáciles (en comparación con, por ejemplo, cantidades aleatorias de daño en un RTS). Esto va a ser difícil de explicar porque "se me ocurrió". Si el promedio es más bajo, significa que debemos aumentar las posibilidades de que ocurra el evento y viceversa. Entonces algunos ejemplos

average = 0.1
desired = 0.5
corrected_chance = 83%

average = 0.2
desired = 0.5
corrected_chance = 71%

average = 0.5
desired = 0.5
corrected_change = 50%

Ahora, lo que se me ocurrió 'es que en el primer ejemplo, el 83% era solo "0.5 de 0.6" (en otras palabras, "0.5 de 0.5 más 0.1"). En términos de eventos aleatorios, eso significa:

procced = (sample * 0.6) > 0.1
// or
procced = (sample * 0.6) <= 0.5

Entonces, para generar un evento, básicamente usarías el siguiente código:

total = average + desired
sample = rng_sample() * total // where the RNG provides a value between 0 and 1
procced = sample <= desired

Y por lo tanto, obtienes el código que puse en la esencia. Estoy bastante seguro de que todo esto se puede usar en el escenario de caso de daño aleatorio, pero no me he tomado el tiempo para resolverlo.

Descargo de responsabilidad: se trata de estadísticas nacionales, no tengo educación en el campo. Sin embargo, mis pruebas unitarias sí pasan.


Parece un error en su primer ejemplo porque tanto un valor 0.1 como un 0.9 dan como resultado un evento 0. Pero básicamente está describiendo mantener un promedio móvil acumulativo ( en.wikipedia.org/wiki/Moving_average#Cumulative_moving_average ) y corregir en función de eso. Un riesgo es que cada resultado estaría significativamente correlacionado inversamente con el resultado anterior, aunque esta correlación disminuiría con el tiempo.
Kylotan

1
Me sentiría tentado a alterar esto para usar un sistema de 'integrador con fugas': comience con el promedio inicializado a 0.5 y, en lugar de contar muestras, elija un valor constante arbitrario (por ejemplo, 10, 20, 50 o 100) que no se incrementa . Entonces, al menos, la correlación entre 2 valores posteriores es constante durante todo el uso del generador. También puede ajustar el valor constante: los valores más grandes significan una corrección más lenta y una aleatoriedad más aparente.
Kylotan

@Kylotan gracias, gracias por proporcionar el nombre. No estoy seguro exactamente a qué te refieres con tu segundo comentario, ¿tal vez dar una nueva respuesta?
Jonathan Dickinson

Eso es bastante inteligente y no tiene las limitaciones de las matrices. Entiendo la sugerencia de Kylotan, que es inicializar samplesen su valor máximo (en este caso, 100) desde el principio. De esa manera, el RNG no necesita 99 iteraciones para estabilizarse. De cualquier manera, la única desventaja que puedo ver con este método es que no garantiza la equidad, simplemente garantiza un promedio constante.
Usuario no encontrado

@jSepia: de hecho, aún obtendría rachas de equidad / injusticia, pero serían seguidas (generalmente) por una racha equilibrada. Por ejemplo, en mi prueba de unidad 'forcé' 100 no procesadores y me encontré con ~ 60 procesamientos cuando hice las muestras reales. En situaciones sin influencia (si observa el código), un proceso de 50% generalmente ve, en el peor de los casos, carreras de 2/3 en cualquier dirección. Pero un jugador podría tener una carrera que les permita derrotar al otro jugador. Si desea sesgo explica con mayor intensidad a la feria: total = (average / 2) + desired.
Jonathan Dickinson

3

Lo que está pidiendo es en realidad lo contrario de la mayoría de los PRNG, una distribución no lineal. Simplemente coloque algún tipo de lógica de rendimientos decrecientes en sus reglas, suponiendo que todo sobre 1.0x sea un "golpe crítico" de algún tipo, solo diga que cada ronda sus posibilidades de obtener un crítico aumentan en X, hasta que obtenga uno en en qué punto se restablecen a Y. Luego haces dos tiradas cada ronda, una para determinar el crítico o no, y luego otra para la magnitud real.


1
Este es el enfoque general que tomaría, usted usa la distribución uniforme del RNG pero la transforma. También puede usar la salida del RNG como entrada a su propia distribución personalizada que se reajusta en función de la historia reciente, es decir, para forzar la variación en las salidas, de modo que se vea "más aleatorio" en términos de percepción humana.
Michael

3
En realidad, sé de un MMO que hace algo como esto, pero la probabilidad de un crítico en realidad aumenta cada vez que obtienes uno hasta que no lo obtienes, luego se restablece a un valor muy bajo. Esto lleva a raras rachas de críticas que son muy satisfactorias para el jugador.
coderanger

Suena como un buen alg, los períodos largos y secos siempre han sido frustrantes, pero no conducen a rachas de crits.
Michael

2
Arreglar esto no requiere una distribución no lineal, solo requiere que los subconjuntos secuenciales cortos de la distribución tengan las mismas propiedades que la distribución misma.

así es como lo hacen los juegos de Blizzard, al menos desde Warcraft 3
dreta

2

Sid Meier tuvo un excelente discurso en GDC 2010 sobre este tema y los juegos de Civilization. Intentaré encontrar y pegar el enlace más tarde. En esencia, la aleatoriedad percibida no es lo mismo que la aleatoriedad verdadera. Para que las cosas se sientan justas, debe analizar los resultados anteriores y prestar atención a la psicología de los jugadores.

Evite las rachas de mala suerte a toda costa (si los dos turnos anteriores fueron desafortunados, el próximo debe garantizar que tenga suerte). El jugador debe ser siempre más afortunado que el oponente AI.


0

Use un sesgo cambiante

0 01rsi0 0

La distribución general estará sesgada por la siguiente fórmula:

rexp(-si)

si1si0 0

Tome este número y escale adecuadamente al rango deseado.

Cada vez que un jugador ruede favorablemente, resta del sesgo. Cada vez que el jugador ruede desfavorablemente, agregue al sesgo. La cantidad modificada podría ser escalada según cuán (des) favorable sea la tirada o podría ser una cantidad fija (o una combinación). Deberá ajustar valores específicos para adaptarse a la sensación que busca.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.