rand () restituisce gli stessi numeri per un intervallo limitato

9

Sto provando a fare una specie di gioco in cui ho una griglia di 20x20 e visualizzo un giocatore (P), un bersaglio (T) e tre nemici (X). Tutti questi hanno una coordinata X e una Y che sono assegnati usando rand() . Il problema è che se cerco di ottenere più punti nel gioco (ricariche per l'energia, ecc.) Si sovrappongono a uno o più degli altri punti, perché l'intervallo è ridotto (da 1 a 20 inclusi).

Queste sono le mie variabili e come le assegno i valori: (il COORD è un struct con solo una X e 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;
}

Voglio aggiungere più cose al gioco ma la probabilità di sovrapposizione aumenta quando lo faccio. C'è un modo per risolvere questo problema?

    
posta Rabeez Riaz 20.07.2015 - 01:36
fonte

4 risposte

41

Mentre gli utenti che si lamentano di rand() e raccomandano migliori RNG hanno ragione riguardo alla qualità dei numeri casuali, mancano anche l'immagine più grande. I duplicati in flussi di numeri casuali non possono essere evitati, sono un dato di fatto. Questa è la lezione del problema del compleanno .

Su una griglia di 20 * 20 = 400 possibili posizioni di spawn, è previsto un doppio punto di spawn (probabilità del 50%) anche quando si generano solo 24 entità. Con 50 entità (ancora solo il 12,5% dell'intera griglia), la probabilità di un duplicato è superiore al 95%. Devi fare i conti con le collisioni.

A volte puoi disegnare tutti i campioni contemporaneamente, quindi puoi utilizzare un algoritmo shuffle per pesca n oggetti garantiti-distinti. Hai solo bisogno di generare l'elenco di tutte le possibilità. Se l'elenco completo delle possibilità è troppo grande per essere archiviato, è possibile generare le posizioni di spawn una alla volta come si fa ora (solo con un RNG migliore) e semplicemente rigenerare quando si verifica una collisione. Anche se è probabile che siano presenti collisioni alcune , molte collisioni di fila sono esponenzialmente improbabili anche se la maggior parte della griglia è popolata.

    
risposta data 20.07.2015 - 02:03
fonte
3

Se vuoi sempre evitare di giocare una nuova entità in una posizione che è già stata assegnata a qualcos'altro, puoi cambiare leggermente il tuo processo. Ciò garantirebbe posizioni uniche, ma richiede un po 'più di overhead. Ecco i passaggi:

  1. Imposta una raccolta di riferimenti a tutte le possibili posizioni sulla mappa (per la mappa 20x20, questa sarebbe 400 posizioni)
  2. Scegli una posizione a caso da questa raccolta di 400 (rand () funzionerebbe bene per questo)
  3. Rimuovi questa possibilità dalla raccolta delle posizioni possibili (quindi ora ha 399 possibilità)
  4. Ripeti fino a quando tutte le entità hanno una posizione specificata

Fino a quando rimuovi la posizione dal set da cui stai prelevando, non ci dovrebbe essere alcuna possibilità che una seconda entità riceva la stessa posizione (a meno che tu non stia scegliendo le posizioni da più di un thread contemporaneamente).

Un vero mondo analogo a questo sarebbe pescare una carta da un mazzo di carte. Al momento stai mescolando il mazzo, pescando una carta e annotandola, rimettendo la carta pescata nel mazzo, rimescolando e pescando di nuovo. L'approccio sopra riportato salta a rimettere la carta nel mazzo.

    
risposta data 20.07.2015 - 13:44
fonte
1

Pertinente al fatto che rand() % n è inferiore all'ideale

Il fare rand() % n ha una distribuzione non uniforme. Otterrai un numero sproporzionato di determinati valori perché il numero di valori non è un multiplo di 20

Successivamente, rand() è in genere un generatore congruenziale lineare (ci sono molti altri , solo questo è il più probabile implementato - e con parametri meno che ideali (ci sono molti modi per selezionare i parametri)). Il problema più grande è che spesso i bit bassi (quelli che si ottengono con un'espressione di tipo % 20 ) non sono casuali. Ricordo un rand() di anni fa in cui il bit più basso si è alternato da 1 a 0 con ogni chiamata a rand() - non era molto casuale.

Dalla rand (3) pagina man:

The  versions of rand() and srand() in the Linux C Library use the same
random number generator as random() and srandom(), so  the  lower-order
bits  should  be as random as the higher-order bits.  However, on older
rand() implementations, and on  current implementations  on  different
systems,  the  lower-order  bits  are much less random than the higher-
order bits.  Do not use this function in applications  intended to  be
portable when good randomness is needed.

Questo potrebbe essere ora relegato alla storia, ma è abbastanza probabile che tu abbia ancora un'implementazione povera di rand () che si nasconde da qualche parte nello stack. Nel qual caso, è ancora abbastanza applicabile.

La cosa da fare è usare effettivamente una buona libreria di numeri casuali (che dà buoni numeri casuali) e poi chiedere numeri casuali all'interno dell'intervallo desiderato.

Un esempio di un buon numero di bit di codice casuale (dalle 13:00 nel video collegato)

#include <iostream>
#include <random>
int main() {
    std::mt19937 mt(1729); // yes, this is a fixed seed
    std::uniform_int_distribution<int> dist(0, 99);
    for (int i = 0; i < 10000; i++) {
        std::cout << dist(mt) << " ";
    }
    std::cout << std::endl;
}

Confronta questo per:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
    srand(time(NULL));
    for (int i = 0; i < 10000; i++) {
        printf("%d ", rand() % 100);
    }
    printf("\n");
}

Esegui entrambi questi programmi e confronta la frequenza con cui alcuni numeri vengono visualizzati (o non vengono visualizzati) in quell'output.

Video correlato: rand () considerato dannoso

Alcuni aspetti storici di rand () che causano bug in Nethack che si dovrebbero osservare e considerare nelle proprie implementazioni:

  • Problema RNG di Nethack

    Rand() is a very fundamental function for Nethack's random number generation. The way Nethack uses it is buggy or it may be argued that lrand48() produces crappy pseudo-random numbers. (However, lrand48() is a library function using a defined PRNG method and any program that uses it should take into account the weaknesses of that method.)

    The bug is that Nethack relies (sometimes exclusively as is the case in rn(2)) on the lower bits of the results from lrand48(). Because of this, RNG in the whole game works bad. This is especially noticable before user actions introduce further randomness, i.e. in character generation and first level creation.

Anche se il precedente era del 2003, dovrebbe comunque essere tenuto presente poiché potrebbe non essere il caso che tutti i sistemi che eseguono il gioco previsto siano un sistema Linux aggiornato con una buona funzione rand (). / p>

Se stai facendo questo per te stesso, puoi verificare quanto è buono il tuo generatore di numeri casuali tramite scrivere un codice e testare l'output con ent .

Sulle proprietà dei numeri casuali

Ci sono altre interpretazioni di "random" che non sono esattamente casuali. In un flusso casuale di dati, è possibile ottenere lo stesso numero due volte. Se si lancia una moneta (casuale), è abbastanza possibile ottenere due teste di fila. Oppure lancia un dado due volte e ottieni lo stesso numero due volte di seguito. Oppure girare una ruota della roulette e ottenere lo stesso numero due volte lì.

La distribuzione dei numeri

Quando si riproduce un elenco di brani, le persone si aspettano che "random" significhi che la stessa canzone o artista non verrà riprodotto una seconda volta di seguito. Avere una playlist su The Beatles per due volte consecutive è considerata "non casuale" (anche se è casuale). La percezione che per una playlist di quattro brani suonasse un totale di otto volte:

1 3 2 4 1 2 4 3

è più "casuale" di:

1 3 3 2 1 4 4 2

Altro su questo per il "mescolamento" di canzoni: Come mescolare le canzoni?

Su valori ripetuti

Se non vuoi ripetere valori, c'è un approccio diverso che dovrebbe essere considerato. Genera tutti i valori possibili e mescolali.

Se stai chiamando rand() (o qualsiasi altro generatore di numeri casuali), lo stai chiamando con la sostituzione. Puoi sempre ottenere lo stesso numero due volte. Un'opzione è di buttare via i valori ancora e ancora finché non ne selezioni uno che soddisfi i tuoi requisiti. Farò notare che questo ha un runtime non deterministico ed è possibile che tu ti possa trovare in una situazione in cui c'è un ciclo infinito a meno che non inizi a fare un backtracing più complesso.

Elenca e seleziona

Un'altra opzione è quella di generare un elenco di tutti gli stati validi possibili e quindi selezionare un elemento casuale da quella lista. Trova tutti i punti vuoti (che soddisfano alcune regole) nella stanza e poi sceglierne uno casuale da quella lista. E poi fallo ancora e ancora finché non hai finito.

Shuffle

L'altro approccio è mescolare come se fosse un mazzo di carte. Inizia con tutti i punti vuoti nella stanza e poi inizia ad assegnarli distribuendo i punti vuoti, uno alla volta, a ogni regola / processo che richiede un posto vuoto. Hai finito quando finisci le carte o le cose smettono di chiederle.

    
risposta data 20.07.2015 - 01:47
fonte
0

La soluzione più semplice a questo problema è stata citata nelle risposte precedenti: è quella di creare un elenco di valori casuali accanto a ciascuna delle 400 celle, e quindi, per ordinare questo elenco casuale. Il tuo elenco di celle verrà ordinato come elenco casuale e in questo modo verrà mescolato.

Questo metodo ha il vantaggio di evitare totalmente la sovrapposizione di celle selezionate a caso.

Lo svantaggio è che devi calcolare un valore casuale in un elenco separato per ogni delle tue celle. Quindi, preferiresti non farlo mentre il gioco è iniziato.

Ecco un esempio di come puoi farlo:

#include <algorithm>
#include <iostream>
#include <vector>

#define NUMBER_OF_SPAWNS 20
#define WIDTH 20
#define HEIGHT 20

typedef struct _COORD
{
  int x;
  int y;
  _COORD() : x(0), y(0) {}
  _COORD(int xp, int yp) : x(xp), y(yp) {}
} COORD;

typedef struct _spawnCOORD
{
  float rndValue;
  COORD*coord;
  _spawnCOORD() : rndValue(0.) {}
} spawnCOORD;

struct byRndValue {
  bool operator()(spawnCOORD const &a, spawnCOORD const &b) {
    return a.rndValue < b.rndValue;
  }
};

int main(int argc, char** argv)
{
  COORD map[WIDTH][HEIGHT];
  std::vector<spawnCOORD>       rndSpawns(WIDTH * HEIGHT);

  for (int x = 0; x < WIDTH; ++x)
    for (int y = 0; y < HEIGHT; ++y)
      {
        map[x][y].x = x;
        map[x][y].y = y;
        rndSpawns[x + y * WIDTH].coord = &(map[x][y]);
        rndSpawns[x + y * WIDTH].rndValue = rand();
      }

  std::sort(rndSpawns.begin(), rndSpawns.end(), byRndValue());

  for (int i = 0; i < NUMBER_OF_SPAWNS; ++i)
    std::cout << "Case selected for spawn : " << rndSpawns[i].coord->x << "x"
              << rndSpawns[i].coord->y << " (rnd=" << rndSpawns[i].rndValue << ")\n";
  return 0;
}

Risultato:

root@debian6:/home/eh/testa# ./exe 
Case selected for spawn : 11x15 (rnd=6.93951e+06)
Case selected for spawn : 14x1 (rnd=7.68493e+06)
Case selected for spawn : 8x12 (rnd=8.93699e+06)
Case selected for spawn : 18x13 (rnd=1.16148e+07)
Case selected for spawn : 1x0 (rnd=3.50052e+07)
Case selected for spawn : 2x17 (rnd=4.29992e+07)
Case selected for spawn : 9x14 (rnd=7.60658e+07)
Case selected for spawn : 3x11 (rnd=8.43539e+07)
Case selected for spawn : 12x7 (rnd=8.77554e+07)
Case selected for spawn : 19x0 (rnd=1.05576e+08)
Case selected for spawn : 19x14 (rnd=1.10613e+08)
Case selected for spawn : 8x2 (rnd=1.11538e+08)
Case selected for spawn : 7x2 (rnd=1.12806e+08)
Case selected for spawn : 19x15 (rnd=1.14724e+08)
Case selected for spawn : 8x9 (rnd=1.16088e+08)
Case selected for spawn : 2x19 (rnd=1.35497e+08)
Case selected for spawn : 2x16 (rnd=1.37807e+08)
Case selected for spawn : 2x8 (rnd=1.49798e+08)
Case selected for spawn : 7x16 (rnd=1.50123e+08)
Case selected for spawn : 8x11 (rnd=1.55325e+08)

Modifica NUMBER_OF_SPAWNS per ottenere più o meno celle casuali, questo non cambierà il tempo di calcolo richiesto per l'attività.

    
risposta data 21.07.2015 - 17:05
fonte

Leggi altre domande sui tag