rand () küçük bir aralık için aynı sayıları tekrar verir


9

20x20'lik bir ızgaram olan bir oyun yapmaya çalışıyorum ve bir oyuncu (P), bir hedef (T) ve üç düşman (X) sergiliyorum. Tüm bunların bir X ve bir Y koordinatı vardır rand(). Sorun şu ki, oyunda daha fazla puan elde etmeye çalışırsam (enerji doldurma vb.), Aralığın küçük olması (1 ila 20 dahil) olduğu için bir veya daha fazla puanla çakışırlar.

Bunlar benim değişkenler ve onları atıyorum nasıl değerleri: ( COORDa ise structsadece bir X ve bir Y ile)

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;
}

Oyuna daha fazla şey eklemek istiyorum, ancak bunu yaptığımda çakışma olasılığı artıyor. Bunu düzeltmenin bir yolu var mı?


8
rand () kötü bir RNG
cırcır ucube

3
rand()acımasız bir RNG'dir ve yine de bu kadar küçük bir aralıkta, sadece çarpışma beklemek zorunda değilsiniz , neredeyse garanti edilirler.
Deduplicator

1
rand()Berbat bir RNG olduğu doğru olsa da, muhtemelen tek kişilik bir oyun için uygundur ve RNG kalitesi burada sorun değildir.
Robot Gort

13
Kalitesinden bahsetmek rand()burada önemsiz görünüyor. Herhangi bir kriptografi yoktur ve herhangi bir RNG muhtemelen bu kadar küçük bir haritada çarpışma verecektir.
Tom Cornebize

2
Gördüğünüz şey Doğum Günü Sorunu olarak bilinir. Rastgele sayılarınız PRNG'nin doğal aralığından daha küçük bir aralığa dönüştürülüyorsa, aynı sayının iki örneğini alma olasılığı düşündüğünüzden çok daha yüksektir. Bir süre önce buraya
ConcernedOfTunbridgeWells

Yanıtlar:


40

Daha rand()iyi RNG'lerden şikayet eden ve öneren kullanıcılar rastgele sayıların kalitesi konusunda haklı olsalar da, daha büyük resmi kaçırırlar. Rastgele sayıların akışlarındaki kopyalardan kaçınılamaz, bunlar hayatın bir gerçeğidir. Bu doğum günü sorununun dersidir .

20 * 20 = 400 olası yumurtlama pozisyonu ızgarasında, sadece 24 varlık yumurtlarken bile yinelenen bir yumurtlama noktası beklenmelidir (% 50 olasılık). 50 varlık ile (hala tüm şebekenin sadece% 12.5'i), kopya olasılığı% 95'in üzerindedir. Çarpışmalarla uğraşmak zorundasınız.

Bazen tüm örnekleri bir kerede çizebilir, daha sonra garantili farklı öğeler çizmek için bir karıştırma algoritması kullanabilirsiniz n. Sadece tüm olasılıkların listesini oluşturmanız gerekir. Olasılıkların tam listesi depolanamayacak kadar büyükse, şimdi yaptığınız gibi (sadece daha iyi bir RNG ile) yumurtlama pozisyonları oluşturabilir ve bir çarpışma meydana geldiğinde yeniden oluşturabilirsiniz. Sahip olsa da bazı çarpışmalara muhtemeldir, üst üste birçok çarpışmalar ızgaranın en doldurulur bile katlanarak olası değildir.


Bir çarpışma durumunda yeniden doğmayı düşündüm, ancak daha fazla eşyam varsa, istediğim gibi, bir çarpışma arayışı karmaşıklaşacaktı. Oyuna bir puan eklenmesi veya oyundan çıkarılması durumunda da çekleri düzenlemem gerekir. Oldukça tecrübesizim, bu yüzden bir çözüm varsa, göremedim.
Rabeez Riaz

7
20x20 sürekli (gerçek) bir XY düzleminin aksine 20x20 dama tahtası varsa, çarpışmaları kontrol etmek için 400 hücreli bir arama tablonuz var. Bu TRIVIAL.
John R. Strohm

@RabeezRiaz Daha büyük bir haritanız varsa, bazı ızgara tabanlı veri yapınız olacaktır (bazı hücre alanlarından oluşan bir ızgara ve o hücrenin içindeki her öğe bir listede saklanır). Haritanız daha büyükse, rect-tree uygulayacaksınız.
rwong

2
@RabeezRiaz: arama çok karmaşıksa, ilk önerisini kullanın: 400 olası başlangıç ​​noktasının tümünü bir liste oluşturun, rastgele bir sırada olmaları için karıştırın (algoritmayı arayın) ve ardından ihtiyaç duyduğunuzda önden konumları kullanmaya başlayın bir şeyler oluşturmak için (zaten kaç tane kullandığınızı takip edin). Çarpışma yok.
RemcoGerlich

2
@RabeezRiaz Tüm listeyi karıştırmanıza gerek yok, sadece az sayıda rastgele değere ihtiyacınız varsa, sadece ihtiyacınız olan parçayı karıştırın (olduğu gibi, 1..400 listesinden rastgele bir değer alın, kaldırın ve tekrarlayın yeterli elemanınız var). Aslında bir karıştırma algoritması bu şekilde çalışır.
Dorus

3

Zaten başka bir şeye tahsis edilmiş bir konumda yeni bir varlığı oynatmaktan kaçınmak istiyorsanız, işleminizin etrafında biraz değişiklik yapabilirsiniz. Bu, benzersiz konumları garanti eder, ancak biraz daha fazla ek yük gerektirir. İşte adımlar:

  1. Haritadaki tüm olası konumlara bir referans koleksiyonu oluşturun (20x20 haritası için bu 400 konum olacaktır)
  2. Bu 400 koleksiyonundan rastgele bir yer seçin (rand () bunun için iyi çalışır)
  3. Bu olasılığı olası konumlar koleksiyonundan kaldırın (şimdi 399 olasılık var)
  4. Tüm varlıklar belirtilen bir konuma sahip olana kadar tekrarlayın

Konumu, seçtiğiniz kümeden kaldırdığınız sürece, aynı konumu alan ikinci bir varlığın hiç şansı olmamalıdır (konumları aynı anda birden fazla iş parçacığından seçmiyorsanız).

Buna gerçek bir dünya analogu bir kart destesinden bir kart çekmek olurdu. Şu anda desteyi karıştırıyorsunuz, bir kart çekiyorsunuz ve işaretliyorsunuz, çizilmiş kartı tekrar desteye koyuyorsunuz, tekrar karıştırıyorsunuz ve tekrar çiziyorsunuz. Yukarıdaki yaklaşım kartı tekrar desteye koyarak atlar.


1

İlişkin rand() % nideale daha az olması

Yapmanın rand() % nhomojen olmayan bir dağılımı vardır. Değerlerin sayısı 20'nin katı olmadığından orantısız sayıda belirli değer elde edersiniz

Daha sonra rand(), tipik olarak doğrusal bir konjügasyon jeneratörüdür ( diğerleri vardır , sadece bu büyük olasılıkla uygulanır - ideal parametrelerden daha azı ile (parametreleri seçmenin birçok yolu vardır). Bununla ilgili en büyük sorun, genellikle içindeki düşük bitlerin (bir % 20tür ifadesiyle elde ettikleriniz ) o kadar rasgele olmamasıdır. Bir tane hatırlamak rand()gelen dönüşümlü düşük bit nerede yıl önce 1için 0yapılan her çağrı ile rand()çok tesadüfi değildi -.

Gönderen Rand (3) kılavuz sayfasında:

Linux C Library'deki rand () ve srand () sürümleri aynı
rastgele () ve srandom () gibi rasgele sayı üreteci, bu yüzden alt sıra
bitler yüksek dereceli bitler kadar rastgele olmalıdır. Ancak, daha eski
rand () uygulamaları ve farklı
sistemleri, düşük mertebeden bitler yüksek mertebeden çok daha az rastgele
sipariş bitleri. Bu işlevi olması amaçlanan uygulamalarda kullanmayın
iyi rasgelelik gerektiğinde taşınabilir.

Bu artık tarihe düşmüş olabilir, ancak yığının herhangi bir yerinde saklanan hala kötü bir rand () uygulamasına sahip olmanız mümkündür. Bu durumda, hala oldukça uygulanabilir.

Yapılacak şey aslında iyi bir rasgele sayı kütüphanesi (iyi rasgele sayılar verir) kullanmak ve daha sonra istediğiniz aralıkta rasgele sayılar istemektir.

İyi bir rastgele sayı biti kodu örneği (bağlantılı videoda 13: 00'dan itibaren)

#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;
}

Bunu şununla karşılaştır:

#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");
}

Bu programların her ikisini de çalıştırın ve bu çıktıda belirli sayıların ne sıklıkta ortaya çıktığını (veya çıkmadığını) karşılaştırın.

İlgili video: rand () zararlı olarak kabul edildi

Rand () 'ın Nethack'ta kendi uygulamalarında izlemesi ve göz önünde bulundurması gereken hatalara neden olan bazı tarihsel yönleri:

  • Nethack RNG Sorunu

    Rand (), Nethack'ın rasgele sayı üretimi için çok temel bir işlevdir. Nethack'in kullandığı yol buggy veya lrand48 () 'nin crappy sözde rasgele sayılar ürettiği iddia edilebilir. (Ancak, lrand48 (), tanımlı bir PRNG yöntemi kullanan bir kitaplık işlevidir ve bunu kullanan herhangi bir program, bu yöntemin zayıflıklarını dikkate almalıdır.)

    Hata, Nethack'ın (bazen sadece rn (2) durumunda olduğu gibi) lrand48 () 'in sonuçlarının alt bitlerine dayanmasıdır. Bu nedenle, tüm oyundaki RNG kötü çalışıyor. Bu, özellikle kullanıcı eylemleri daha fazla rasgele ortaya çıkmadan önce, yani karakter oluşturma ve birinci seviye oluşturmada fark edilir.

Yukarıdakiler 2003'teyken, amaçlanan oyununuzu çalıştıran tüm sistemlerin iyi bir rand () işlevine sahip güncel bir Linux sistemi olacağı düşünülmeyebilir.

Bunu sadece kendiniz için yapıyorsanız, bazı kod yazıp çıkışı ent ile test ederek rastgele sayı üretecinizin ne kadar iyi olduğunu test edebilirsiniz .


Rasgele sayıların özellikleri hakkında

'Rasgele' nin tam olarak rasgele olmayan başka yorumları da vardır. Rasgele bir veri akışında, aynı sayıyı iki kez elde etmek oldukça mümkündür. Bir bozuk para (rastgele) çevirirseniz, arka arkaya iki kafa almak oldukça mümkündür. Ya da iki kez bir zar atın ve aynı sayıyı arka arkaya iki kez alın. Ya da bir rulet tekerleğini döndürmek ve orada aynı sayıyı iki kez almak.

Sayıların dağılımı

Bir şarkı listesi çalarken, insanlar 'rastgele'nin aynı şarkının veya sanatçının arka arkaya ikinci kez çalınmayacağı anlamına gelmesini bekler. Bir çalma listesi olarak kabul edilir arka arkaya iki kez Beatles oynamak Having (olsa 'rastgele değil' olduğu rastgele). Dört şarkının çalma listesinin toplam sekiz kez çalındığı algısı:

1 3 2 4 1 2 4 3

şundan daha 'rastgele':

1 3 3 2 1 4 4 2

Şarkıların 'karıştırılması' için daha fazlası: Şarkıları karıştırmak nasıl?

Tekrarlanan değerlerde

Değerleri tekrarlamak istemiyorsanız, dikkate alınması gereken farklı bir yaklaşım vardır. Olası tüm değerleri oluşturun ve karıştırın.

Eğer rand()(veya başka herhangi bir rastgele sayı üreteci) arıyorsanız, onu yedek ile çağırıyorsunuz. Aynı numarayı her zaman iki kez alabilirsiniz. Seçeneklerden biri, gereksinimlerinizi karşılayana kadar değerleri tekrar tekrar atmaktır. Bunun belirleyici olmayan bir çalışma süresine sahip olduğunu ve daha karmaşık bir geri izleme yapmaya başlamadığınız sürece kendinizi sonsuz bir döngü olduğu bir durumda bulabileceğinizi göstereceğim.

Liste ve Seçim

Diğer bir seçenek, olası tüm geçerli durumların bir listesini oluşturmak ve ardından bu listeden rastgele bir öğe seçmektir. Odadaki tüm boş noktaları (bazı kurallara uyan) bulun ve ardından bu listeden rastgele bir nokta seçin. Ve sonra bitene kadar tekrar tekrar yapın.

Karıştır

Diğer yaklaşım sanki bir kart destesi gibi karıştırmaktır. Odadaki tüm boş noktalar ile başlayın ve ardından her bir kurala / sürece boş noktaları dağıtarak bunları boş bir noktaya atayarak başlayın. Kartınız bittiğinde işiniz bittiğinde veya kart istemeyi bıraktığınız zaman.


3
Next, rand() is typically a linear congruential generatorBu artık birçok platformda geçerli değil. Linux'un rand (3) man sayfasından: "Linux C Library'deki rand () ve srand () sürümleri random (3) ve srandom (3) ile aynı rasgele sayı üretecini kullanır, bu nedenle alt sıra bitleri üst düzey bitler kadar rastgele olmalıdır. " Ayrıca @delnan'ın belirttiği gibi, PRNG'nin kalitesi burada gerçek bir sorun değildir.
Charles E. Grant

4
Bunu küçümsüyorum çünkü asıl sorunu çözmüyor.
user253751

@immibis Öyleyse diğer cevap gerçek sorunu “çözmez” ve indirilmemelidir. Bence soru "kodumu düzelt" değil, "neden yinelenen rasgele sayılar alıyorum?" İkinci soruya, sorunun cevaplandığına inanıyorum.
Neil

4
RAND_MAX32767'nin en küçük değeriyle bile fark, 1638'in diğerleri için 1639'a karşı bazı sayıları almanın olası yollarıdır. OP'de çok pratik bir fark yaratma olasılığı düşük görünüyor.
Martin Smith

@Neil "Kodumu düzelt" bir soru değil.
Orbit'te Hafiflik Yarışları

0

Bu sorunun en basit çözümü önceki yanıtlarda alıntılanmıştır: 400 hücrenizin her birinin yanında rastgele değerlerin bir listesini yapmak ve ardından bu rastgele listeyi sıralamaktır. Hücre listeniz rastgele liste olarak sıralanacak ve bu şekilde karıştırılacaktır.

Bu yöntem, rastgele seçilen hücrelerin üst üste binmesinden tamamen kaçınma avantajına sahiptir.

Dezavantajı, hücrelerinizin her biri için ayrı bir listede rastgele bir değer hesaplamanız gerektiğidir . Yani, oyun başlarken bunu yapmamayı tercih edersiniz.

İşte bunu nasıl yapabileceğinize bir örnek:

#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;
}

Sonuç:

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)

Daha fazla veya daha az rastgele hücre elde etmek için NUMBER_OF_SPAWNS değerini değiştirmeniz yeterlidir, bu görev için gereken hesaplama süresini değiştirmez.


"ve sonra, hepsini sıralamak için" - Ben "shuffle" demek istediğinizi düşünüyorum

Açıklamamı biraz tamamladım. Şimdi daha açık olmalı.
KwentRell
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.