繁体   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