我是一名网络游戏开发者,我遇到了随机数问题.假设玩家有20%的几率用他的剑获得重击.这意味着,5次点击中的1次应该是至关重要的.问题是我的现实生活成绩非常糟糕 - 有时候球员在5次安打中得到3次降落,有时15次命中都没有.战斗相当短(3-10次命中),因此获得良好的随机分布非常重要.
目前我使用PHP mt_rand()
,但我们只是将代码移动到C++,所以我想在游戏的新引擎中解决这个问题.
我不知道解决方案是否是一些统一的随机生成器,或者可能记住以前的随机状态以强制正确分配.
这意味着,5次点击中的1次应该是至关重要的.问题是我的现实生活成绩非常糟糕 - 有时候球员在5次安打中得到3次降落,有时15次命中都没有.
你需要的是一个洗牌袋.它解决了游戏中随机过于随机的问题.
该算法大致如下:您将1个关键点和4个非关键点击放入包中.然后你将他们的订单随机化,并一次挑出一个.当行李是空的时,再次使用相同的值填充它并随机化.通过这种方式,您每5次点击平均可获得1次重击,最多可连续获得2次关键命中和8次非关键命中.增加包中的商品数量以获得更多随机性.
下面是我之前写的一个实现(在Java中)及其测试用例的示例.
你对随机意味着什么有误解.
哪个更随机?
虽然第二个图看起来分布更均匀,但实际上第一个图更随机.人类的思维常常看到随机性的模式,所以我们将第一个图中的团块视为模式,但它们不是 - 它们只是随机选择的样本的一部分.
考虑到你要求的行为,我认为你正在随机化错误的变量.
而不是随机化此命中是否至关重要,尝试随机化转数,直到发生下一次重击.例如,每次玩家获得一个关键时,只需在2和9之间选择一个数字,然后在经过多轮之后给他们下一个关键数字.您还可以使用骰子方法来接近正态分布 - 例如,您将在2D4轮次中获得下一个关键.
我相信这种技术也可用于在世界范围内随机遭遇的RPG中 - 你随机化一个步数计数器,然后经过很多步骤,你再次被击中.感觉更加公平,因为你几乎不会连续遭遇两次遭遇 - 如果这种情况发生一次,那么球员就会变得烦躁.
首先,定义"正确"的分配.随机数是随机的 - 您看到的结果与(伪)随机性完全一致.
在此基础上,我假设你想要的是一种"公平"的感觉,所以用户不能没有成功就转100圈.如果是这样,我将跟踪自上次成功以来的失败次数,并对生成的结果进行加权.让我们假设你想要五分之一的卷"成功".所以你随机生成一个从1到5的数字,如果它是5,那就太好了.
如果没有,记录失败,下次生成一个从1到5的数字,但加上说,floor(numFailures/2).所以这一次,他们又有五分之一的机会.如果失败,下次获胜间隔为4 和 5; 成功的机会是五分之二.有了这些选择,经过8次失败后,他们肯定会成功.
我同意早先的答案,即一些游戏的小规模运行中的真实随机性是不可取的 - 对某些用例来说似乎太不公平了.
我在Ruby中编写了一个简单的Shuffle Bag,并进行了一些测试.实施这样做:
如果它看起来仍然公平或者我们还没有达到最小滚动的阈值,它会根据正常概率返回一个公平的命中.
如果从过去的滚动中观察到的概率使其看起来不公平,则会返回"公平的"命中.
基于边界概率,这被认为是不公平的.例如,对于20%的概率,您可以将10%设置为下限,将40%设置为上限.
使用这些边界,我发现在10次点击运行中,14.2%的时间真正的伪随机实现产生的结果超出了这些界限.大约11%的时间,0次关键命中在10次尝试中得分.3.3%的时间,5个或更多的关键命中率从10个中降落.当然,使用这种算法(最小滚数为5),"Fairish"运行的数量少得多(0.03%)超出界限.即使下面的实现不合适(当然可以做更聪明的事情),值得注意的是,通常你的用户会觉得它对真正的伪随机解决方案是不公平的.
这是我FairishBag
用Ruby编写的.此处提供了整个实施和快速蒙特卡罗模拟(要点).
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%,并使短期运行保持公平.
如何用这样的东西替换mt_rand()?
(RFC 1149.5将4指定为标准的IEEE审查随机数.)
来自XKCD.
希望本文能为您提供帮助:http://web.archive.org/web/20090103063439/http : //www.gamedev.net :80/reference/design/features/randomness /
这种生成"随机数"的方法在rpg/mmorpg游戏中很常见.
它解决的问题是这个(摘录):
刀片蜘蛛在你的喉咙.它打了你想念.它再次击中,你再次错过.一次又一次,直到你没有任何东西可以击中.你已经死了,还有一个两吨重的蜘蛛在你的尸体上幸灾乐祸.不可能?不可以吗?是.但是如果给予足够多的球员并给予足够的时间,那么不可能的几乎可以肯定.这不是刀片蜘蛛很难,这只是运气不好.多么令人沮丧.这足以让玩家想要退出.
你想要的不是随机数,而是人类看似随机的数字.其他人已经提出了个人算法,可以帮助你,比如Shuffle Bad.
有关此域的详细而广泛的分析,请参阅AI Game Programming Wisdom 2.对于任何游戏开发者来说,整本书值得一读,"貌似随机数"的概念在章节中处理:
AI决策和游戏逻辑的过滤随机性:
摘要:传统观点认为,随机数生成器越好,你的游戏就越难以预测.然而,根据心理学研究,短期内的真正随机性往往对人类来说显然是不随意的.本文展示了如何使随机AI决策和游戏逻辑看起来更随机,同时仍保持强大的统计随机性.
您可能还会发现另一个有趣的章节:
随机数统计
摘要:人工智能和游戏一般使用随机数最多.忽视他们的潜力是让游戏变得可预测和无聊.错误地使用它们可能就像完全忽略它们一样糟糕.了解随机数如何生成,它们的局限性和能力,可以消除在游戏中使用它们的许多困难.本文提供了对随机数,它们的生成以及将好的和坏的分开的方法的见解.
当然,任何随机数生成都有可能产生这样的运行?您不会在3-10卷中获得足够大的样本集来查看适当的百分比.
也许你想要的是一个怜悯的门槛......记住最后10卷,如果他们没有受到重击,给他们一个免费赠品.平滑随机的吊索和箭头.
您最好的解决方案可能是使用多种不同的非随机方案进行游戏测试,并选择让玩家最开心的方案.
您也可以尝试在给定遭遇中针对相同数字的退避策略,例如,如果玩家1
在第一个回合中滚动接受它.要获得另一个,1
他们需要1
连续滚动2 秒.要获得第三名,1
他们需要连续3 次,无限制.
不幸的是,你要求的实际上是一个非随机数字生成器 - 因为你想要在确定下一个数字时考虑以前的结果.这不是随机数生成器工作的方式,我担心.
如果您希望每5次点击中有1次是关键,那么只需选择一个介于1和5之间的数字,并说该命中将是一个关键.
mt_rand()基于Mersenne Twister实现,这意味着它可以获得您可以获得的最佳随机分布之一.
显然你想要的并不是随机性,所以你应该开始准确地指出你想要的东西.你可能会意识到你有相互矛盾的期望 - 结果应该是真正随机的而且不可预测的,但同时它们不应该表现出来自所述概率的局部变化 - 但随后它变得可预测.如果你连续设置了最多10个非crits,那么你刚刚告诉玩家"如果你连续有9个非crit,那么下一个将是100%确定的关键" - 你可能会根本没有随意性的麻烦.
在如此少量的测试中,你应该期待这样的结果:
真正的随机性只能在巨大的设定尺寸上预测,因此很可能第一次翻转硬币并连续三次获得头部,但是在几百万次翻转时,你最终将大约50-50.
我看到很多答案建议跟踪以前生成的数字或者洗牌所有可能的值.
就个人而言,我不同意,连续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 }
你没有得到暴击的时间越长 - 你下一次暴击行动的可能性就越大.我包含的重置是完全可选的,它需要测试来判断它是否需要.在长期非暴击动作链之后,连续多次动作的暴击概率可能更高或可能不理想.
只要投入我的2美分......
最重要的几个答案是很好的解释,所以我只关注一个算法,让你可以控制"坏条纹"的可能性,同时永远不会变得确定.这是我认为你应该做的:
不是指定p,伯努利分布的参数,即临界命中的概率,指定a和b,β分布的参数,伯努利分布的"共轭先验".你需要跟踪A和B,到目前为止关键和非关键命中的数量.
现在,要指定a和b,确保a /(a + b)= p,是一个致命一击的机会.巧妙的是,(a + b)量化了你希望A /(A + B)与p一般接近的程度.
你做这样的采样:
让p(x)
是所述β分布的概率密度函数.它可以在很多地方使用,但您可以在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)从bernoulli分布中抽样来选择一个致命一击
如果您发现随机数有太多"坏条纹",请按比例放大a和b,但在极限情况下,当a和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%的时间内拒绝重复值,重复不太可能但不是不可能.从统计上来说它有点难看,但它可能会产生你想要的结果.当然,它不会阻止像"5 4 5 4 5"这样的分发.你可以得到更好的并且拒绝倒数第二(比如说)60%的时间和第三名(比如说)30%.
我不推荐这个好游戏设计.只是建议如何实现你想要的.