![](/img/trans.png)
[英]Calling a random number generating member function doesn't produce entirely random numbers
[英]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
事件中被迫發生的。
首先對上述解決方案進行一些評論。
如果隨機值不令人滿意,則拒絕隨機值的技術沒有任何問題。 這是拒絕抽樣的一個例子,這是一種廣泛使用的技術。 例如,用於生成隨機高斯的幾種算法都涉及拒絕采樣。 一種是極坐標拒絕法,它涉及從 U(-1,1) 重復繪制一對數字,直到兩者都不為零且不在單位圓之外。 這拋出了超過 21% 的對。 找到滿意的對后,簡單的變換會產生一對高斯偏差。 (極坐標拒絕方法現在已經失寵,被 ziggurat 算法所取代。這也使用了拒絕采樣。)
rand() % 6
有很多問題。 不要這樣做。 曾經。 來自隨機數發生器的低位比特,即使是一個好的隨機數發生器,也不像高位比特那樣“隨機”。
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.