需要可预测的随机生成器

我是一个网络游戏开发者,我对随机数字很有意见。假设一个玩家有20% 的几率用他的剑造成致命一击。也就是说,五分之一的命中率应该是至关重要的。问题是我在现实生活中得到了非常糟糕的结果,有时候玩家在5次命中中得到3次暴击,有时候在15次命中中没有一次。战斗相当短(3-10次命中) ,所以获得良好的随机分布很重要。

目前我使用的是 PHP mt_rand(),但是我们只是将代码移动到 C + + ,所以我想在我们游戏的新引擎中解决这个问题。

我不知道解决方案是否是某个均匀随机发生器,或者也许是为了记住以前的随机状态,以迫使适当的分布。

18260 次浏览

首先,定义“适当”分布。随机数是随机的——你看到的结果完全符合(伪)随机性。

在此基础上,我假设你想要的是一种“公平”的感觉,这样用户就不能100次都没有成功。如果是这样,我将跟踪自上次成功以来的失败次数,并对生成的结果进行权重。让我们假设您希望五卷中有一卷“成功”。你随机生成一个从1到5的数字,如果是5,很好。

如果没有,记录故障,下一次,生成一个从1到5的数字,但是加上,比如,floor (numail/2)。所以这一次,他们有五分之一的机会。如果他们失败了,下次获胜的间隔时间是4 还有5; 成功的概率是2/5。有了这些选择,在8次失败之后,他们一定会成功。

给价值加权怎么样?

例如,如果你有20% 的机会暴击,生成一个数字1到5代表一个暴击,或者一个数字1到100代表20个数字是暴击。

但是,只要你是随机或伪随机数生成器工作,就没有办法避免你现在看到的结果。这是随机性的本质。

不幸的是,您所要求的实际上是一个非随机数生成器——因为您希望在确定下一个数字时考虑以前的结果。恐怕这不是随机数生成器的工作原理。

如果你希望每5次命中中有1次是致命的,那么只需要在1到5之间选择一个数字,然后说那次命中将是致命的。

当然,任何随机数生成都有可能产生这样的运行?你不会得到一个足够大的样本集在3-10卷看到适当的百分比。

也许你想要的是一个仁慈的门槛... 记住最后10卷,如果他们没有得到一个致命的打击,给他们一个免费的。消除随机性的莎翁秘辛。

对于 C + + 随机数生成器,使用 Rand随机

每当一个玩家被击中,你只需检查[0; 1]中的随机数是否小于0.2。 这意味着某人不可能在15秒内得到致命一击,但这是不可能的。

这将根据 二项分布(p = 0.2)定律给出自然随机数

在如此少量的测试中,你应该期待这样的结果:

真正的随机性只有在一个巨大的集合中才可以预测,比如说,第一次掷硬币就有可能得到三个正面,但是超过几百万次的掷硬币,你最终将得到大约50-50个正面。

您的最佳解决方案可能是游戏测试与多个不同的 随机方案,并选择一个让球员最快乐。

你也可以在给定的遭遇中尝试相同数量的后退策略,例如,如果一个玩家在第一回合掷出一个 1就接受它。为了得到另一个 1,他们需要连续滚动两个 1。为了得到第三个 1,他们需要3个连续的,无穷无尽的。

把 mt _ rand ()替换成这样的东西怎么样?

XKCD comic (RFC 1149.5 specifies 4 as the standard IEEE-vetted random number.)

(RFC 1149.5指定4为经 IEEE 审核的标准随机数。)

来自 XKCD

你误解了“随机”的意思。

哪个更随机?

enter image description here enter image description here

虽然第二个图看起来更均匀分布,更 随机的实际上是第一个图。人类的大脑经常在随机性中看到模式,所以我们把第一个图中的团块看作是模式,但它们不是——它们只是随机选择的样本的一部分。

我不知道你想要什么。可以创建一个函数,这样在前5次调用它时,它会以随机顺序返回数字1-5。

但这不是随机的。玩家将会知道在接下来的5次进攻中他将会得到正好一个5分。但它可能是您想要的,在这种情况下,您只需要自己编写代码。(创建一个包含数字的数组,然后对它们进行洗牌)

或者,您可以继续使用当前的方法,并假设当前的结果是由于错误的随机生成器造成的。请注意,您当前的数据可能没有任何问题。随机值是随机的。有时你会在一行中得到2,3或8个相同的值。因为它们是随机的。一个好的随机生成器只是保证平均来说,所有的数字都会被同样频繁地返回。

当然,如果您使用的是一个糟糕的随机生成器,那么结果可能会有偏差,如果是这样的话,只需切换到一个更好的随机生成器就可以解决这个问题。(看看 Boost。更好的生成器的随机库)

或者,您可以记住随机函数返回的最后 N 个值,并根据这些值对结果进行权重。(一个简单的例子是,“对于每次出现的新结果,有50% 的可能性我们应该丢弃该值并得到一个新值”

如果要我猜的话,我会说坚持“实际”的随机性是你最好的选择。确保你使用一个好的随机生成器,然后继续你现在做的方式。

根据你要求的行为,我认为你随机化了错误的变量。

而不是随机 这个命中是否将是关键,尝试随机的转数,直到下一个关键命中发生。例如,只要选择一个数字之间的2和9每次玩家得到一个关键,然后给他们的下一个关键后,许多回合已经过去。你也可以使用骰子方法来接近正态分布——例如,你将在2D4回合中得到你的下一个临界值。

我相信这个技巧也被用在火箭筒中,在地球上也有随机遭遇——你随机选择一个步数计数器,在那么多步之后,你再次被击中。这让人感觉更公平,因为你几乎从来没有连续两次遭遇——如果这种情况发生一次,玩家就会变得易怒。

Mt _ rand ()是基于 梅森旋转算法实现的,这意味着它生成的是可以得到的最佳随机分布之一。

显然,您想要的根本不是随机性,因此您应该从指定您想要的确切内容开始。你可能会意识到你有相互矛盾的期望-结果应该是真正的随机和不可预测的,但同时他们不应该表现出局部变化从陈述的概率-但然后它变得可预测。如果你连续设置了10个非暴击点,那么你只是告诉玩家“如果你连续有9个非暴击点,下一个将是100% 肯定的关键”——你可能根本不会为随机性而烦恼。

您可以创建一个包含从1到5的数字的列表,并让它们按随机性排序。然后浏览你创建的列表。你可以保证每个数字都至少碰到一次... ... 当你碰到前5个数字时,再创建另外5个数字... ..。

static int crit = 0;


public bool isCritical()
{
crit = crit++ % 5;
return (crit==0);
}

如果你仍然想要一些随机性,改变模数使用另一个静态变量每次临界点执行。改变它将等于概率从3到7应该保持平均时间之间的暴击在1/5,但从来没有少于3或超过7命中他们之间。

希望这篇文章能帮助你: Http://web.archive.org/web/20090103063439/http://www.gamedev.net:80/reference/design/features/randomness/

这种生成“随机数”的方法在 rpg/mmorpg 游戏中很常见。

它解决的问题是:

一只蜘蛛在你的喉咙上。一击不中。它再次击中,你再次失手。一次又一次,直到你身无分文。你死了,还有一只两吨重的蜘蛛在你尸体上幸灾乐祸。不可能?没有。不太可能?是的。但是如果有足够多的球员和足够多的时间,这种不可能性几乎变得确定无疑。不是因为蜘蛛硬,只是运气不好。真令人沮丧。这足以让一个球员想要退出。

也就是说,五分之一的命中率应该是至关重要的。问题是我在现实生活中得到了非常糟糕的结果——有时候玩家在5次命中中得到3次暴击,有时候在15次命中中没有一次。

你需要的是一个 洗牌袋。它解决的问题,真正的随机是太随机的游戏。

算法大概是这样的: 你把一个关键点和4个非关键点放在一个袋子里。然后你随机把他们的顺序放进袋子里,一次一个地挑出来。当袋子是空的时候,你再次用相同的值填充它并随机化它。这样,你将获得平均每5次命中1暴击,并最多2暴击和8非暴击在一个连续。增加袋子里物品的数量以增加随机性。

下面是我前段时间编写的 一种实现方式(Java 语言)和 它的测试案例的示例。

预先计算一个随机暴击 每个人播放器。

// OBJECT
//...
// OnAttack()
//...
c_h = c_h -1;
if ( c_h == 0 ) {
// Yes, critical hit!
c_h = random(5) + 1 // for the next time
// ...
}

如何使临界概率取决于最后 N 次攻击。一个简单的方案是某种马尔可夫链: http://en.wikipedia.org/wiki/Markov_chain,但是代码非常简单。


IF 从最后一刻开始转动 < M THEN
危险 = 虚假
转动 _ 自 _ 最后 _ 临界 + + ;
还有
临界 = 临界(机会) ;
如果很危险的话
自最后一次临界值 = 0;
还有
转到 _ since _ last _ Crita + + ;
如果结束;
如果结束;

当然,你必须做好数学运算,因为一旦你知道自上次以来已经有足够多的回合,那么一个关键回合的概率就会低于一个关键回合的概率

你想要的不是随机的数字,而是在人类看来是随机的数字。其他人已经提出了单独的算法,这可以帮助你,像洗牌袋。

有关这个领域的详细和广泛的分析,请参阅 人工智能游戏编程智慧2。对于任何游戏开发者来说,整本书都是值得一读的,“看似随机的数字”这个概念在下面的章节中得到了阐述:

人工智能决策和博弈逻辑的过滤随机性 :

摘要: 传统智慧认为随机数生成器越好,你的游戏就越不可预测。然而,根据心理学研究,短期内真正的随机性对于人类来说往往看起来非常不随机。本文展示了如何使随机人工智能决策和游戏逻辑看起来更随机的球员,同时仍然保持强大的统计随机性。

你可能还会发现另一章很有趣:

随机数的统计

摘要: 随机数是人工智能和游戏中使用最多的数字。忽视他们的潜力只会让游戏变得可预测和无聊。不正确地使用它们可能和完全忽视它们一样糟糕。了解随机数是如何产生的,它们的局限性和能力,可以消除在游戏中使用它们的许多困难。本文提供了对随机数的深入了解,它们的产生,以及区分好数和坏数的方法。

反应: “问题是我在现实生活中得到了非常糟糕的结果——有时候玩家在5次命中中得到3次暴击,有时候在15次命中中没有暴击。”

你有3% 到4% 的几率在15次点击中一无所获。

我建议采用以下“随机延迟回放死亡”:

  • 维护两个数组,一个(in-array)最初填充从0到 n-1的值,另一个(out-array)为空
  • 当要求得到结果时:
    • in-array中的所有 定义值中返回一个随机值
    • 将此值从 in-array移动到 out-array
    • 将一个随机的(通过 所有元素,包括未定义的!)元素从 out-array移回到 in-array

这种特性就是,N越大,它的“反应”就越慢。例如,如果你想要一个20% 的概率,设置 N到5,然后选择一个0,比设置 N到10,然后选择一个0或1要“不随机”,在一个小样本中,使1000中的0到199几乎和真正的随机性没有什么区别。您将不得不调整 N到您的样本大小。

操作室,

基本上,如果你希望它公平,它不会是随机的。

你的游戏的问题是实际的比赛长度。匹配时间越长,你看到的随机性就越少(暴击率通常为20%) ,并且它会接近你的预期值。

你有两个选择,根据之前的掷骰预先计算攻击。每5次攻击你将获得一次暴击(基于你的20%) ,但是你可以让它发生的顺序是随机的。

攻击列表 = {命中,命中,命中,未命中,暴击} ;

这就是你想要的模式,所以让它从列表中随机选择,直到它为空,他们重新创建它。

这是我为我的游戏创建的模式,它工作得很好,为我想要它做的。

你的第二个选择是,增加暴击的几率,你可能会在所有攻击结束时看到一个更加均匀的数字(假设你的比赛结束得很快)。机会越小,你就越容易搞砸。

我看到许多答案建议跟踪先前生成的数字或者对所有可能的值进行洗牌。

就我个人而言,我不同意,连续3次暴击是不好的。我也不同意连续15次非暴击是不好的。

我会解决这个问题,通过修改暴击几率本身,每个数字后。 例子(演示这个想法) :

int base_chance = 20;
int current_chance = base_chance;


int hit = generate_random_number(0, 100) + 1; // anything from 1 to 100
if(hit < current_chance)//Or whatever method you use to check
{
//crit!
if(current_chance > base_chance)
current_chance = base_chance; // reset the chance.
else
current_chance *= 0.8; // decrease the crit chance for the NEXT hit.
}
else
{
//no crit.
if(current_chance < base_chance)
current_chance = base_chance; // reset the chance.
else
current_chance *= 1.1; // increase the crit chance for the NEXT hit.
//raise the current_chance
}

你得不到暴击的时间越长-你下一个行动的暴击几率就越高。我包含的重置是完全可选的,它需要测试来判断是否需要。在一个较长的非暴击行为链之后,对于连续的多个行为给出较高的暴击概率可能是可取的,也可能是不可取的。

我只是说说我的看法。

我同意早期的答案,即在一些小规模运行的游戏中,真正的随机性是不可取的——对于某些用例来说,这似乎太不公平了。

我用 Ruby 编写了一个类似 Shuffle Bag 的简单实现,并进行了一些测试:

  • 如果它仍然看起来公平,或者我们还没有达到最小卷的门槛,它返回一个公平的命中基于正常的概率。
  • 如果从过去的卷中观察到的概率使它看起来不公平,它返回一个“公平的”命中。

基于边界概率,它被认为是不公平的。例如,对于20% 的概率,您可以将10% 设置为下界,将40% 设置为上界。

使用这些界限,我发现运行的10个命中,14.2% 的情况下,真正的伪随机实现会产生超出这些界限的结果。大约11% 的情况下,在10次尝试中,0次暴击得分。3.3% 的情况下,10次中有5次或更多的致命一击命中。自然地,使用这个算法(最小卷数为5) ,一个小得多的数量(0.03%)的“ Fairish”运行是出界的。即使下面的实现不合适(当然,还可以做更多聪明的事情) ,值得注意的是,用户经常会觉得使用真正的伪随机解决方案是不公平的。

下面是我用 Ruby 编写的 FairishBag的主要部分。整个实现和快速的蒙特卡罗模拟 在这里可以买到(要点)

def fire!
hit = if @rolls >= @min_rolls && observed_probability > @unfair_high
false
elsif @rolls >= @min_rolls && observed_probability < @unfair_low
true
else
rand <= @probability
end
@hits += 1 if hit
@rolls += 1
return hit
end


def observed_probability
@hits.to_f / @rolls
end

更新: 使用此方法确实增加了获得致命一击的总体概率,使用上面的界限可以增加到22% 左右。您可以通过设置它的“实际”概率低一点来抵消这一点。一个17.5% 的概率与公平的修改产生一个观察到的长期概率约20% ,并保持短期运行感觉公平。

前面的几个答案是很好的解释,所以我只关注一个算法,它可以让你在 永远不会成为确定性的时候控制“坏条纹”的概率。我觉得你应该这么做:

与其指定 P,不如指定伯努利分布的参数,即临界命中的概率,指定 B,指定 Β分布的参数,指定伯努利分布的“共轭先验”。你需要跟踪 AB,到目前为止的关键和非关键命中的数量。

现在,要指定 B,请确保 a/(a + b) = p,即临界命中的几率。整洁的事情是(a + b)量化你想要 A/(A + B)在一般情况下与 p 的接近程度。

你这样做你的抽样:

让 abc 0成为概率密度函数的 Β分布。它在许多地方都可以找到,但是您可以在 GSL 中以 GSL _ ran _ beta _ pdf 的形式找到它。

S = A+B+1
p_1 = p((A+1)/S)
p_2 = p(A/S)

从概率 p _ 1/(p _ 1 + p _ 2)的伯努利分布中选择一个临界点

如果你发现随机数有太多的“坏条纹”,放大 B,但在限制,因为 B去无穷大,你将有洗牌袋方法前面描述。

如果你实现了这一点,请让我知道它是如何进行的!

如果需要阻止重复值的发行版,可以使用一个简单的重复拒绝算法。

例如:。

int GetRand(int nSize)
{
return 1 + (::rand() % nSize);
}
int GetDice()
{
static int nPrevious=-1;
while (1) {
int nValue = GetRand(6);
// only allow repeat 5% of the time
if (nValue==nPrevious && GetRand(100)<95)
continue;
nPrevious = nValue;
return nValue;
}
}

这个代码在95% 的情况下拒绝重复值,使得重复不太可能,但并非不可能。 从统计学上来说,这有点难看,但它可能会产生你想要的结果。当然,它不会阻止像“54545”这样的发行版。你可以变得更花哨,在60% 的时间里拒绝倒数第二名,在30% 的时间里拒绝倒数第三名。

我不是推荐这个作为好的游戏设计。只是简单的建议如何达到你想要的。

我想你可能用错了随机分布函数。 你可能不希望这些数字是均匀分布的。尝试一个正态分布代替,这样的重大命中变得更加不常见的比’正常’命中。

我用 Java 工作,所以我不确定在哪里可以找到一些 C + + 的东西,给你一个正态分布的随机数,但是一定有一些东西在那里。

正如许多人所说,这实际上是一个什么是“随机”的问题。你得到的结果是随机的,无论你如何制作游戏,一些玩家会觉得你的计数器是不公平的,不是随机的。;)

一个可能的选择可能是保证每 n 次命中,并且在每次命中后,在特定的边界内随机生成 n。这完全取决于在游戏测试中什么样的“感觉”是正确的。

好吧,如果你对数学有一点兴趣,你可以试试 指数分布

例如,如果 lambda = 0.5,期望值是2(去读那篇文章吧!),意味着你很有可能每2回合就打击/暴击/无论什么(像50% ,是吧?).但是有了这样的概率分布,你肯定会在第0回合(事件已经发生,回合计数器已经重置)错过(或者做相反的事情) ,有大约40% 的机会打到下一回合,大约65% 的机会打到第2回合(下一回合之后) ,大约80% 的机会打到第3回合,以此类推。

这种分布的全部目的是,如果一个人有50% 的命中机会,而且他连续3次失手,他将必然命中(好吧,超过80% 的机会,并且每次回合都会增加)。它导致更“公平”的结果,保持50% 的机会不变。

考虑到你20% 的暴击几率,你做到了

  • 第一回合17%
  • 32% 到第二回合暴击,如果之前所有回合都没有暴击。
  • 45% 到第三回合爆击,如果之前所有回合都没有爆击。
  • 54% 到第四回合爆击,如果之前所有回合都没有爆击。
  • ...
  • 80% 到暴击第8回合,如果没有暴击发生在所有以前的。

在接下来的5个回合中,仍然有0.2% (相对于那5%)的几率出现3次暴击 + 2次非暴击。 而且有14% 的机会4个随后的非暴击,5% 的5,1.5% 的6,0.3% 的7,0.07% 的8个随后的非暴击。我打赌它比41% ,32% ,26% ,21% 和16% “更公平”。

希望你还没无聊死。

我建议像暴雪这样的累进百分比系统使用: Http://www.shacknews.com/onearticle.x/57886

一般来说,先滚动一个 RNG,然后将其与一个值进行比较,以确定是否成功。这可能看起来像:

if ( randNumber <= .2 ) {
//Critical
} else {
//Normal
}

你需要做的就是逐渐增加基本概率。

if (randNumber <= .2 + progressiveChance ) {
progressiveChance = 0;
//Critical
} else {
progressiveChance += CHANCE_MODIFIER;
//Normal hit
}

如果你需要它更花哨,添加更多是很容易的。您可以限制累进机会可以得到避免100% 的关键机会或重置它在某些事件。您还可以使每次增加的累进机会增加更小的数量,比如累进机会 + = (1-累进机会) * SCALE,其中 SCALE < 1。

这就是它应该是什么样子,它的概率,你不应该期望一个暴击每5场战斗1分。.用硬币或骰子试试看。但那只是我。 GB

您所看到的是一个线性分布,当您可能需要一个正态分布时。

如果你还记得在你年轻的时候玩龙与地下城,你被要求掷多个 n 面骰子,然后总结结果。

例如,滚动4 x 6面骰子不同于滚动1 x 24面骰子。

alt text

这个真的是可以预测的... 但你永远不能确定。

我也试图解决这个问题。我能想到的最好办法就是动态地改变概率,如果这些数字是基于它们在不久的过去出现的频率。类似(对于骰子,在 matlab 中) :

probabilities = [1 1 1 1 1 1];
unrandomness = 1;
while true
cumprob = cumsum(probabilities) ./ sum(probabilities);
roll = find(cumprob >= rand, 1)
probabilities = probabilities + unrandomness;
probabilities(roll) = probabilities(roll) - 6*unrandomness;
if min(probabilities) < 0
probabilities = probabilities - min(probabilities);
end
end

抱歉没有缩进。非随机性参数可以根据需要调整。真随机输出(非随机性 = 0) :

231141345152611113561426351162564355555444555443412

非随机性 = 1:

324562624151643142322653533416534216541436543331553535553553555355355355535535535535553555355553555535555535555555

我觉得看起来更好,特别是如果你把数字间的差距画出来的话。

笨蛋的问题在于,它有可能永远不能完成。

我认为你需要从一个泊松分佈中生成随机数。

英雄之城实际上有一个叫做“断路器”的机械师,正好解决了这个问题。它的工作原理是,在一个长度与该字符串中最低命中概率相关的错过字符串之后,下一次攻击肯定会命中。例如,如果你错过了一次命中率超过90% 的攻击,那么你的下一次攻击将会自动命中,但是如果你的命中率比较低,比如60% ,那么你需要连续几次错过才能触发“断路器”(我不知道确切的数字)