Estoy tratando de hacer una especie de juego donde tengo una cuadrícula de 20x20 y muestro un jugador (P), un objetivo (T) y tres enemigos (X). Todos estos tienen una coordenada X e Y que se asignan usando rand()
. El problema es que si trato de obtener más puntos en el juego (recargas de energía, etc.) se superponen con uno o más de los otros puntos porque el rango es pequeño (1 a 20 inclusive).
Estas son mis variables y cómo les estoy asignando valores: ( COORD
es una struct
con solo una X y una Y)
const int gridSize = 20;
COORD player;
COORD target;
COORD enemy1;
COORD enemy2;
COORD enemy3;
//generate player
srand ( time ( NULL ) );
spawn(&player);
//generate target
spawn(&target);
//generate enemies
spawn(&enemy1);
spawn(&enemy2);
spawn(&enemy3);
void spawn(COORD *point)
{
//allot X and Y coordinate to a point
point->X = randNum();
point->Y = randNum();
}
int randNum()
{
//generate a random number between 1 and gridSize
return (rand() % gridSize) + 1;
}
Quiero agregar más cosas al juego, pero la probabilidad de superposición aumenta cuando hago eso. ¿Hay alguna forma de arreglar esto?
rand()
es un RNG lamentable, y de todos modos con un rango tan pequeño, no solo tiene que esperar colisiones, sino que están casi garantizadas.rand()
es un RNG pésimo, probablemente sea apropiado para un juego de un solo jugador, y la calidad de RNG no es el problema aquí.rand()
parece ser irrelevante aquí. No hay criptografía involucrada, y cualquier RNG probablemente dará colisiones en un mapa tan pequeño.Respuestas:
Si bien los usuarios que se quejan
rand()
y recomiendan mejores RNG tienen razón sobre la calidad de los números aleatorios, también les falta el panorama general. Los duplicados en flujos de números aleatorios no se pueden evitar, son una realidad. Esta es la lección del problema del cumpleaños .En una cuadrícula de 20 * 20 = 400 posibles posiciones de generación, se espera un punto de generación duplicado (50% de probabilidad) incluso cuando se generan solo 24 entidades. Con 50 entidades (todavía solo el 12.5% de toda la cuadrícula), la probabilidad de un duplicado es superior al 95%. Tienes que lidiar con colisiones.
A veces puede dibujar todas las muestras a la vez, luego puede usar un algoritmo aleatorio para dibujar
n
elementos distintos garantizados. Solo necesita generar la lista de todas las posibilidades. Si la lista completa de posibilidades es demasiado grande para almacenar, puede generar posiciones de generación de una en una como lo hace ahora (solo con un mejor RNG) y simplemente volver a generar cuando se produce una colisión. Aunque es probable que haya algunas colisiones, muchas colisiones seguidas son exponencialmente improbables incluso si la mayor parte de la cuadrícula está poblada.fuente
Si siempre desea evitar jugar una nueva entidad en una ubicación que ya ha sido asignada a otra cosa, puede cambiar ligeramente su proceso. Esto garantizaría ubicaciones únicas, pero requiere un poco más de gastos generales. Aquí están los pasos:
Siempre que elimine la ubicación del conjunto del que está eligiendo, no debería haber ninguna posibilidad de que una segunda entidad reciba la misma ubicación (a menos que elija las ubicaciones de más de un hilo a la vez).
Un análogo del mundo real a esto sería robar una carta de una baraja de cartas. Actualmente, barajas el mazo, robas una carta y la marcas, vuelves a colocar la carta robada en el mazo, barajas y vuelves a dibujar. El enfoque anterior omite volver a colocar la carta en el mazo.
fuente
Perteneciente a
rand() % n
ser menos que idealHacer
rand() % n
tiene una distribución no uniforme. Obtendrá un número desproporcionado de ciertos valores porque el número de valores no es un múltiplo de 20A continuación,
rand()
suele ser un generador congruencial lineal (hay muchos otros , solo que este es el más implementado, y con parámetros menos que ideales (hay muchas formas de seleccionar los parámetros)). El mayor problema con esto es que a menudo los bits bajos (los que se obtienen con una% 20
expresión de tipo) no son tan aleatorios. Recuerdo unorand()
de hace años, donde el bit más bajo alternaba de1
a0
con cada llamada arand()
, no era muy aleatorio.Desde la página de manual de rand (3):
Esto puede relegarse ahora a la historia, pero es muy posible que todavía tenga una implementación pobre de rand () escondida en algún lugar de la pila. En cuyo caso, sigue siendo bastante aplicable.
Lo que hay que hacer es usar una buena biblioteca de números aleatorios (que proporcione buenos números aleatorios) y luego pedir números aleatorios dentro del rango que desee.
Un ejemplo de un buen bit de código de número aleatorio (a partir de las 13:00 en el video vinculado)
Compare esto con:
Ejecute ambos programas y compare con qué frecuencia surgen ciertos números (o no aparecen) en esa salida.
Video relacionado: rand () considerado dañino
Algunos aspectos históricos de rand () que causan errores en Nethack que uno debe observar y considerar en sus propias implementaciones:
Problema de Nethack RNG
Si bien lo anterior fue de 2003, aún debe tenerse en cuenta, ya que puede no ser el caso de que todos los sistemas que ejecutan el juego previsto sean un sistema Linux actualizado con una buena función rand ().
Si solo está haciendo esto por sí mismo, puede probar qué tan bueno es su generador de números aleatorios escribiendo un código y probando la salida con ent .
Sobre las propiedades de números aleatorios
Hay otras interpretaciones de 'aleatorio' que no son exactamente aleatorias. En una secuencia de datos aleatoria, es bastante posible obtener el mismo número dos veces. Si lanzas una moneda (al azar), es muy posible obtener dos caras seguidas. O tira un dado dos veces y obtén el mismo número dos veces seguidas. O girando una ruleta y obteniendo el mismo número dos veces allí.
La distribución de números.
Al reproducir una lista de canciones, las personas esperan que "aleatorio" signifique que la misma canción o artista no se reproducirá por segunda vez consecutiva. Tener una lista de reproducción en The Beatles dos veces seguidas se considera 'no aleatorio' (aunque es aleatorio). La percepción de que para una lista de reproducción de cuatro canciones tocó un total de ocho veces:
es más 'aleatorio' que:
Más sobre esto para la 'mezcla' de canciones: ¿Cómo mezclar canciones?
En valores repetidos
Si no desea repetir valores, hay que considerar un enfoque diferente. Genere todos los valores posibles y barajelos.
Si está llamando
rand()
(o cualquier otro generador de números aleatorios), lo está llamando con reemplazo. Siempre puedes obtener el mismo número dos veces. Una opción es desechar los valores una y otra vez hasta que seleccione uno que cumpla con sus requisitos. Señalaré que esto tiene un tiempo de ejecución no determinista y es posible que te encuentres en una situación en la que hay un bucle infinito a menos que comiences a hacer un rastreo más complejo.Lista y selección
Otra opción es generar una lista de todos los estados válidos posibles y luego seleccionar un elemento aleatorio de esa lista. Encuentra todos los lugares vacíos (que cumplen con algunas reglas) en la habitación y luego elige uno aleatorio de esa lista. Y luego hazlo una y otra vez hasta que hayas terminado.
Barajar
El otro enfoque es barajar como si fuera una baraja de cartas. Comience con todos los espacios vacíos en la sala y luego comience a asignarlos repartiendo los espacios vacíos, uno a la vez, a cada regla / proceso que solicite un espacio vacío. Has terminado cuando te quedas sin cartas o las cosas dejan de pedirlas.
fuente
Next, rand() is typically a linear congruential generator
Esto no es cierto en muchas plataformas ahora. Desde la página de comando man rand (3) de linux: "Las versiones de rand () y srand () en la Biblioteca Linux C usan el mismo generador de números aleatorios que random (3) y srandom (3), por lo que los bits de orden inferior debe ser tan aleatorio como los bits de orden superior ". Además, como señala @delnan, la calidad del PRNG no es el verdadero problema aquí.RAND_MAX
32767, la diferencia es 1638 formas posibles de obtener algunos números frente a 1639 para otros. Parece poco probable que haga mucha diferencia práctica para el OP.La solución más simple a este problema se ha citado en respuestas anteriores: es hacer una lista de valores aleatorios junto con cada una de sus 400 celdas, y luego, ordenar esta lista aleatoria. Su lista de celdas se ordenará como lista aleatoria y, de esta manera, se barajará.
Este método tiene la ventaja de evitar totalmente la superposición de celdas seleccionadas al azar.
La desventaja es que debe calcular un valor aleatorio en una lista separada para cada una de sus celdas. Entonces, prefieres no hacerlo mientras el juego ha comenzado.
Aquí hay un ejemplo de cómo puedes hacerlo:
Resultado:
Simplemente cambie NUMBER_OF_SPAWNS para obtener celdas más o menos aleatorias, esto no cambiará el tiempo de cálculo requerido para la tarea.
fuente