簡體   English   中英

如何生成不產生超過 X 個連續元素的隨機數序列

[英]How to produce a random number sequence that doesn't produce more than X consecutive elements

好的,我真的不知道如何正確地提出問題,因為我幾乎不知道如何用一句話來描述我想要的東西,我很抱歉。

讓我直奔主題,您可以跳過 rest 因為我只是想表明我已經嘗試過一些東西,而不是一時興起來這里問問題。

我需要一個產生 6 個隨機數的算法,它可能不會在該序列中產生超過 2 個連續數字。

示例:3 3 4 4 2 1

^很好。

示例:3 3 3 4 4 2

^不! 不! 錯誤的!

顯然,如果不經常絆倒自己,我不知道如何做到這一點。

是否有 STL 或 Boost 功能可以做到這一點? 或者也許這里有人知道如何為它設計一個算法。 那將是真棒。

我正在嘗試做的事情和我嘗試過的事情。 (可以跳過的部分)

這是在 C++ 中。 我正在嘗試制作一個 Panel de Pon/Tetris Attack/Puzzle League 任何克隆進行練習。 游戲有一個 6 塊行和 3 個或更多匹配塊將破壞塊。 這是一個視頻,以防您不熟悉。

當一個新行從底部出來時,它一定不能有 3 個水平匹配塊,否則它會自動消失。 我不想要水平的東西。 不過豎版也不錯。

我試圖做到這一點,但似乎我無法做到這一點。 當我開始游戲時,缺少塊塊,因為它在不應該檢測到匹配時。 如您所見,我的方法很可能過於笨拙且過於復雜。

enum BlockType {EMPTY, STAR, UP_TRIANGLE, DOWN_TRIANGLE, CIRCLE, HEART, DIAMOND};
vector<Block> BlockField::ConstructRow()
{
    vector<Block> row;

    int type = (rand() % 6)+1;

    for (int i=0;i<6;i++)
    {
        row.push_back(Block(type));
        type = (rand() % 6) +1;
    }

    // must be in order from last to first of the enumeration
    RowCheck(row, diamond_match);
    RowCheck(row, heart_match);
    RowCheck(row, circle_match);
    RowCheck(row, downtriangle_match);
    RowCheck(row, uptriangle_match);
    RowCheck(row, star_match);

    return row;
}

void BlockField::RowCheck(vector<Block> &row, Block blockCheckArray[3])
{
    vector<Block>::iterator block1 = row.begin();
    vector<Block>::iterator block2 = row.begin()+1;
    vector<Block>::iterator block3 = row.begin()+2;
    vector<Block>::iterator block4 = row.begin()+3;
    vector<Block>::iterator block5 = row.begin()+4;
    vector<Block>::iterator block6 = row.begin()+5;

    int bt1 = (*block1).BlockType();
    int bt2 = (*block2).BlockType();
    int bt3 = (*block3).BlockType();
    int bt4 = (*block4).BlockType();
    int type = 0;

    if (equal(block1, block4, blockCheckArray)) 
    {
        type = bt1 - 1;
        if (type <= 0) type = 6;
        (*block1).AssignBlockType(type);
    }
    else if (equal(block2, block5, blockCheckArray)) 
    {
        type = bt2 - 1;
        if (type <= 0) type = 6;
        (*block2).AssignBlockType(type);
    }
    else if (equal(block3, block6, blockCheckArray)) 
    {
        type = bt3 - 1;
        if (type == bt3) type--;
        if (type <= 0) type = 6;
        (*block3).AssignBlockType(type);
    }
    else if (equal(block4, row.end(), blockCheckArray)) 
    {
        type = bt4 - 1;
        if (type == bt3) type--;
        if (type <= 0) type = 6;

        (*block4).AssignBlockType(type);
    }
}

嘆息,我不確定這是否有助於展示這一點......至少它表明我已經嘗試過一些東西。

基本上,我通過將 BlockType 枚舉描述的隨機塊類型分配給塊對象的構造函數來構造行(塊 object 具有塊類型和位置)。

然后我使用 RowCheck function 來查看一行中是否有 3 個連續的塊類型,並且我對所有塊類型都這樣做了。 *_match 變量是具有相同塊類型的 3 個塊對象的 arrays。 如果我確實發現有 3 種連續的塊類型,我只需將第一個值減一即可。 但是,如果我這樣做,我可能最終會無意中產生另一個 3 匹配,所以我只是確保塊類型按從大到小的順序排列。

好吧,這很糟糕,很復雜,而且不起作用。 這就是為什么我需要你的幫助。

想法1。

while(sequence doesn't satisfy you)
      generate a new sequence 

想法2。

Precalculate all allowable sequences (there are about ~250K of them) 
randomly choose an index and take that element.

第二個想法需要很多 memory,但速度很快。 第一個也不慢,因為您的 while 循環迭代不止一次或兩次的可能性很小。 HTH

記錄前兩個值就足夠了,並在新生成的值與前兩個值匹配時循環。

對於任意運行長度,動態調整歷史緩沖區的大小並在循環中進行比較是有意義的。 但這應該接近滿足您的要求。

int type, type_old, type_older;

type_older = (rand() % 6)+1;
row.push_back(Block(type_older));

type_old = (rand() % 6)+1;
row.push_back(Block(type_old));

for (int i=2; i<6; i++)
{
    type = (rand() % 6) +1;
    while ((type == type_old) && (type == type_older)) {
        type = (rand() % 6) +1;
    }

    row.push_back(Block(type));
    type_older = type_old;
    type_old = type;
}

到目前為止看到的大多數解決方案都涉及潛在的無限循環。 我可以建議不同的方法嗎?

// generates a random number between 1 and 6
// but never the same number three times in a row
int dice()
{
    static int a = -2;
    static int b = -1;
    int c;
    if (a != b)
    {
        // last two were different, pick any of the 6 numbers
        c = rand() % 6 + 1;
    }
    else
    {
        // last two were equal, so we need to choose from 5 numbers only
        c = rand() % 5;
        // prevent the same number from being generated again
        if (c == b) c = 6;
    }
    a = b;
    b = c;
    return c;
}

有趣的部分是 else 塊。 如果最后兩個數字相等,則只有 5 個不同的數字可供選擇,因此我使用rand() % 5而不是rand() % 6 這個調用仍然可以產生相同的數字,它也不能產生 6,所以我簡單地將 map 那個數字改為 6。

使用簡單的 do-while 循環的解決方案(對於大多數情況來說已經足夠了):

vector<Block> row;

int type = (rand() % 6) + 1, new_type;
int repetition = 0;

for (int i = 0; i < 6; i++)
{
    row.push_back(Block(type));
    do {
        new_type = (rand() % 6) + 1;
    } while (repetition == MAX_REPETITION && new_type == type);

    repetition = new_type == type ? repetition + 1 : 0;
    type = new_type;
}

沒有循環的解決方案(對於那些不喜歡先前解決方案的不確定性的人):

vector<Block> row;

int type = (rand() % 6) + 1, new_type;
int repetition = 0;

for (int i = 0; i < 6; i++)
{
    row.push_back(Block(type));

    if (repetition != MAX_REPETITION)
        new_type = (rand() % 6) + 1;
    else
    {
        new_type = (rand() % 5) + 1;
        if (new_type >= type)
            new_type++;
    }

    repetition = new_type == type ? repetition + 1 : 0;
    type = new_type;
}

在這兩種解決方案中,對於您的情況,MAX_REPETITION 都等於 1。

如何將一個六元素數組初始化為[1, 2, 3, 4, 5, 6]並隨機交換它們一段時間? 保證沒有重復。

很多答案說“一旦你連續檢測到 X,重新計算最后一個,直到你沒有得到 X”......實際上,對於這樣的游戲,這種方法比你需要的要快數百萬倍“實時”的人際互動,所以就去做吧!

但是,你顯然對它感到不舒服,並尋找更本質上“有界”和優雅的東西。 因此,假設您從 1..6 生成數字,當您檢測到 2 個 X 時,您已經知道下一個 X 可能是重復的,因此只有 5 個有效值:生成一個從 1 到 5 的隨機數,如果是>= X,再增加一。

這有點像這樣:

1..6 -> 3
1..6 -> 3
"oh no, we've got two 3s in a row"
1..5 -> ?
        < "X"/3   i.e. 1, 2       use as is
        >= "X"         3, 4, 5,   add 1 to produce 4, 5 or 6.

然后你知道最后兩個元素不同......當你繼續檢查連續兩個元素時,后者將占據第一個位置......

vector<BlockType> constructRow()
{
    vector<BlockType> row;

    row.push_back(STAR); row.push_back(STAR);
    row.push_back(UP_TRIANGLE); row.push_back(UP_TRIANGLE);
    row.push_back(DOWN_TRIANGLE); row.push_back(DOWN_TRIANGLE);
    row.push_back(CIRCLE); row.push_back(CIRCLE);
    row.push_back(HEART); row.push_back(HEART);
    row.push_back(DIAMOND); row.push_back(DIAMOND);

    do
    {
        random_shuffle(row.begin(), row.end());
    }while(rowCheckFails(row));

    return row;
}

這個想法是在這里使用random_shuffle() 您需要實現滿足要求的rowCheckFails()

編輯

我可能無法正確理解您的要求。 這就是為什么我將每種塊類型中的 2 個放在行中。 你可能需要放更多。

認為最好將隨機數生成隱藏在方法或 function 后面。 它可以是一個方法或 function 一次返回三個隨機數,確保 output 中至少有兩個不同的數字。 它也可以是一個 stream 生成器,確保它永遠不會連續輸出三個相同的數字。

int[] get_random() {
    int[] ret;
    ret[0] = rand() % 6 + 1;
    ret[1] = rand() % 6 + 1;
    ret[2] = rand() % 6 + 1;

    if (ret[0] == ret[1] && ret[1] == ret[2]) {
        int replacement;
        do {
            replacement = rand() % 6 + 1;
        } while (replacement == ret[0]);
        ret[rand() % 3] = replacement;
    }
    return ret;
}

如果你想要六個隨機數(這對我來說有點難以分辨,而且視頻只是令人費解:) 那么生成if條件會更加努力:

for (int i=0; i<4; i++) {
    if (ret[i] == ret[i+1] && ret[i+1] == ret[i+2])
        /* three in a row */

如果您總是更改ret[1] (三者的中間),那么您將永遠不會因為更改而獲得三合一,但 output 也不會是隨機的: XYX將比XXY因為它可能是隨機發生的,也可能是在XXX事件被迫發生的。

首先對上述解決方案進行一些評論。

  1. 如果隨機值不令人滿意,則拒絕隨機值的技術沒有任何問題。 這是拒絕抽樣的一個例子,這是一種廣泛使用的技術。 例如,用於生成隨機高斯的幾種算法都涉及拒絕采樣。 一種是極坐標拒絕法,它涉及從 U(-1,1) 重復繪制一對數字,直到兩者都不為零且不在單位圓之外。 這拋出了超過 21% 的對。 找到滿意的對后,簡單的變換會產生一對高斯偏差。 (極坐標拒絕方法現在已經失寵,被 ziggurat 算法所取代。這也使用了拒絕采樣。)

  2. rand() % 6有很多問題。 不要這樣做。 曾經。 來自隨機數發生器的低位比特,即使是一個好的隨機數發生器,也不像高位比特那樣“隨機”。

  3. rand()有很多問題,句號。 大多數編譯器編寫者顯然不知道有關生成隨機數的 bean。 不要使用rand()

現在一個使用 Boost 隨機數庫的解決方案:

vector<Block> BlockField::ConstructRow(
  unsigned int max_run) // Maximum number of consecutive duplicates allowed
{
  // The Mersenne Twister produces high quality random numbers ...
  boost::mt19937 rng;

  // ... but we want numbers between 1 and 6 ...
  boost::uniform_int<> six(1,6);

  // ... so we need to glue the rng to our desired output.
  boost::variate_generator<boost::mt19937&, boost::uniform_int<> >
    roll_die(rng, six);

  vector<Block> row;

  int prev = 0;
  int run_length = 0;

  for (int ii=0; ii<6; ++ii) {
    int next;
    do {
      next = roll_die();
      run_length = (next == prev) ? run_length+1 : 0;
    } while (run_length > max_run);
    row.push_back(Block(next));
    prev = next;
  }

  return row;
}

我知道這已經有很多答案了,但是我突然想到了一個想法。 您可能有 7 個 arrays,一個包含所有 6 個數字,每個缺少一個給定數字。 像這樣:

int v[7][6] = {
    {1, 2, 3, 4, 5, 6 },
    {2, 3, 4, 5, 6, 0 }, // zeros in here to make the code simpler, 
    {1, 3, 4, 5, 6, 0 }, // they are never used
    {1, 2, 4, 5, 6, 0 },
    {1, 2, 3, 5, 6, 0 },
    {1, 2, 3, 4, 6, 0 },
    {1, 2, 3, 4, 5, 0 }
};

然后你可以有一個2級歷史。 最后生成一個數字,如果您的匹配歷史小於最大值,則隨機播放 v[0] 並取 v[0][0]。 否則,將 v[n] 中的前 5 個值打亂並取 v[n][0]。 像這樣的東西:

#include <algorithm>

int generate() {
    static int prev         = -1;
    static int repeat_count = 1;

    static int v[7][6] = {
        {1, 2, 3, 4, 5, 6 },
        {2, 3, 4, 5, 6, 0 }, // zeros in here to make the code simpler, 
        {1, 3, 4, 5, 6, 0 }, // they are never used
        {1, 2, 4, 5, 6, 0 },
        {1, 2, 3, 5, 6, 0 },
        {1, 2, 3, 4, 6, 0 },
        {1, 2, 3, 4, 5, 0 }
    };

    int r;

    if(repeat_count < 2) {
        std::random_shuffle(v[0], v[0] + 6);
        r = v[0][0];
    } else {
        std::random_shuffle(v[prev], v[prev] + 5);
        r = v[prev][0];
    }

    if(r == prev) {
        ++repeat_count;
    } else {
        repeat_count = 1;
    }

    prev = r;   
    return r;
}

這應該會導致良好的隨機性(不依賴於rand() % N ),沒有無限循環,並且考慮到我們每次洗牌的少量數字應該相當有效。

請注意,由於使用了靜態,這不是線程安全的,這對您的使用可能沒問題,如果不是,那么您可能希望將其包裝在 object 中,每個都有自己的 state。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM