随机滚动表是用于桌面游戏等中随机生成的表。虽然它们可以非常简单,但也可以向复杂的方向发展。
随机滚动表的一个不寻常的例子是“财富”式游戏的轮子,在这种游戏中,某些类别可能有更高的发生几率。
我正在为D&D &D 5e魔术项目构建一个随机生成器(参见此处:发电机/),但我决定对表进行加权,以便在特定的时间选择特定的结果。
一个非常简单的表可能如下所示:
(1d6)
1: First result
2: Second result
3: Third result
4: Fourth result
5: Fifth result
6: Sixth result
通过使用列表和1到6之间的随机化,可以很容易地解决这个问题。
一个稍微复杂一些(但仍然基于1d6的)表可能如下所示:
(1d6)
1-3: First result
4-5: Second result
6: Third result
我的桌子看起来更像:
(1d20)
1-5: First result
6-15: Second result
16-17: Third result
18: Fourth result
19: Fifth result
20: Sixth result
这些表对某些类别具有较高的weighting
,以便更频繁地选择这些类别。在我的魔术项目示例中,剑应该有6-15作为metal
刃,而1-5则是bone
刃,而20将是非常不寻常的东西,如darkness
刀片或light
刀片。
一种可能的选择是在每个类别中添加一个weight
,并对每个滚动执行一个计算,通过将先前的权重相加来查看它的重量,但是这感觉非常笨重。
我尝试在PHP中实现这个项目生成器,并且我使用了一个用比较运算符来解决这个问题的开关情况,但是很明显,这在Python中是行不通的。
对于这个加权的表实现,建议的Python设计是什么?或者有人对实现有任何想法吗?我愿意完全重写我的数据结构,以充分利用任何想法。
发布于 2019-07-26 10:51:10
选项1:在你的模具上使用一个每个数字都有一个元素的list
--它的优点是算法映射非常接近于滚动骰子和从表中查找结果的行为。不利之处在于,如果用一万面的骰子,它的规模会很差。但这可能不关你的事。
roll_results = ([first_result] * 5
+ [second_result] * 10
+ [third_result] * 2
+ [fourth_result,
fifth_result,
sixth_result])
roll = random.randint(0, 20)
result = roll_results[roll]
选项2:使用民防 --这是一种有效的数学方法。它的规模会很大。但是,如果您不知道统计数据,并且没有明显地映射到用例,那么理解起来就更复杂了。
from bisect import bisect_left
import numpy as np
options = ["a", "b", "c", "d", "e", "f"]
weights = np.array([5, 10, 2, 1, 1, 1])
pdf = weight/np.sum(weights)
cdf = np.cumsum(pdf)
def get_item(cdf, options):
roll = np.random.random()
idx = bisect_left(cdf, roll) # This uses a binary search to find the point on the CDF (F) such that F(roll) == P(X <= roll)
return options[idx]
选项3:完全按照您在PhP中所做的做:
options = ["a", "b", "c", "d", "e", "f"]
roll = random.randint(1, 21)
if 1 <= roll <= 5:
return option[0]
elif 6 <= roll <= 15
return option[1]
elif 16 <= roll <= 17
return option[2]
elif roll == 18
return option[3]
elif roll == 19
return option[4]
elif roll == 20
return option[5]
如果您有许多这样的表,我建议使用选项二,但将其封装在类或闭包中:
class OptionsWeightsLengthMismatchException(BaseException):
"""Raises when the number of elements in the options list doesn't match the number of elements in the weight list."""
pass
# Using an class (OOP approach):
class RollTable:
def __init__(self, options: List[Any], weights: np.array) -> "RollTable":
self.options = options
self.weights = weights
self._validate_inputs()
pdf = weights/np.sum(weights)
self.cdf = np.cumsum(pdf)
def _validate_inputs(self) -> None:
if len(self.options) != self.weights.size:
raise OptionsWeightsLengthMismatchException(f"options and weights must have the same number of elements.")
if max(self.weights.shape) != np.prod(self.weights.shape): # i.e. make sure it's 1D
raise ValueError("Weights must be 1D, i.e. only one non-singular dimension.")
def get_item(self):
roll = np.random.random()
idx = bisect_left(self.cdf, roll)
return self.options[idx]
#Use it like:
table_1 = RollTable(["a", "b"], np.array([3, 2]))
for _ in range(10):
print(table_1.get_item())
# =============================================================================
# using a closure (functional programming approach)
def make_roll_table(options: List[Any], weights: np.array) -> Callable[[None], Any]:
# Do the input validation here
pdf = weights/np.sum(weights)
cdf = np.cumsum(pdf)
def get_item():
roll = np.random.random()
idx = bisect_left(cdf, roll)
return options[idx]
return get_item
# Use it like
get_item_from_table_2 = make_roll_table(["a", "b", "c"], np.array([3, 1, 1]))
for _ in range(10):
print(get_item_from_table_2())
发布于 2019-07-26 10:47:15
一个简单的解决方案就是简单地使用random.choice
。第二个例子就是:
>>> random.choice([1, 1, 1, 2, 2, 3])
你的第三个问题很简单:
>>> choices = [1]*5 + [2]*10 + [3]*2 + [4, 5, 6]
>>> random.choice(choices)
https://stackoverflow.com/questions/57217923
复制相似问题