好吧,我真的不知道如何正确地提出这个问题,因为我几乎不知道如何用一句话来描述我想要什么,我很抱歉。
让我开门见山,你可以跳过剩下的,因为我只是想表明我已经尝试了一些东西,而不是一时兴起来这里问问题。
我需要一个产生6个随机数的算法,在这个序列中,它可能不会产生超过2个连续数。
示例:3 3 4 4 2 1
^很好。
示例:3 3 4 4 2
^否不错误!
很明显,我不知道如何做到这一点而不经常绊倒自己。
有STL或Boost功能可以做到这一点吗?或者这里有人知道如何为它炮制一个算法。那太棒了。
我想做的和我已经尝试过的(可以跳过的部分)
这是用C++编写的。我正在尝试制作一个Panel de Pon/俄罗斯方块攻击/益智联盟的任何克隆来练习。游戏有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枚举描述的随机块类型分配给block对象的构造函数来构造行(block对象具有BlockType和位置)。
然后我使用RowCheck函数来查看一行中是否有3个连续的块类型,并且我对所有块类型都这样做。*_match变量是具有相同块类型的3个块对象的阵列。如果我发现有3个连续的块类型,那么我只需将第一个值减去一。然而,如果我这样做的话,我可能会无意中产生另一个3匹配,所以我只需要确保块类型按顺序从大到小。
好吧,它很糟糕,很复杂,而且不起作用!这就是为什么我需要你的帮助。
只需记录前两个值,并在新生成的值与前两个值相匹配时循环即可。
对于任意的运行长度,动态调整历史缓冲区的大小并在循环中进行比较是有意义的。但这应该接近您的要求。
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;
}
第一个想法
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.
第二个想法需要大量内存,但速度很快。第一个也不慢,因为while循环迭代不止一到两次的可能性非常小。HTH
迄今为止看到的大多数解决方案都涉及潜在的无限循环。我可以建议一种不同的方法吗?
// 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;
}
有趣的部分是其他块。如果最后两个数字相等,那么只有5个不同的数字可供选择,所以我使用rand() % 5
而不是rand() % 6
。这个调用仍然可以产生相同的数字,但也不能产生6,所以我只是将该数字映射到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.
然后你知道最后两个元素不同。。。当您继续检查一行中的2个元素时,后者将占据第一个位置。。。。
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个放在一行中。你可能需要投入更多。
I认为最好将随机数生成隐藏在方法或函数后面。它可以是一个同时返回三个随机数的方法或函数,确保输出中至少有两个不同的数字。它也可以是一个流生成器,确保它永远不会连续输出三个相同的数字。
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]
(三个中的中间一个),你就永远不会因为更改而连续出现三个,但输出也不会是随机:X Y X
比X X Y
发生得更频繁,因为它可能是随机发生的,并且在X X X
的情况下是强制的。
首先对上述解决方案发表一些评论。
-
如果一个随机值不令人满意,那么拒绝它的技术并没有错。这是一种广泛使用的技术——拒绝采样的一个例子。例如,用于生成随机高斯的几种算法涉及拒绝采样。一种是极性拒绝方法,它包括从U(-1,1)中重复绘制一对数字,直到它们都为非零并且不在单位圆之外。这就投出了超过21%的双人组。在找到一个令人满意的对之后,一个简单的变换产生一对高斯偏差。(极性抑制方法现在已经失宠,取而代之的是ziggurat算法。它也使用了抑制采样。)
-
rand() % 6
有很大问题。不要这样做。曾经来自随机数生成器的低阶比特,即使是一个好的随机数生成器,也不像高阶比特那样"随机"。 -
rand()
出现了很大问题。大多数编译器编写者显然不知道如何生成随机数。不要使用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个数组,一个数组有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
),没有无限循环,并且考虑到我们每次洗牌的数字数量很少,这应该是相当有效的。
注意,由于使用了statics,这是不线程安全的,这对于您的使用来说可能很好,如果不是,那么您可能希望将其封装在一个对象中,每个对象都有自己的状态。