我知道这是问题,但总体而言,目的是帮助人们学习,所以我想我会尝试分享一些代码并征求反馈意见。
我正在寻找一个依赖于随机数的程序,特别是骰子。这些将以“2D6”,“4D10 + 3”,“2D2 + 3D3”等形式呈现,依此类推。因此,我开始创建一个能够接受这种形式的输入的骰子滚轮模块。
它适用于所需的内容,但对于可能不需要的内容有一个错误(文件开头的文档字符串应该解释)。我感兴趣的是人们对我的代码的看法以及是否有人能够看到改进它的方法。
它仍然是WIP,我还没有开始进行单元测试。
#!/usr/bin/env python3
"""
Created by Teifion Jordan
http://woarl.com
Notes: The roller does not correctly apply * and / signs:
A + B * C is worked out as (A + B) * C, not A + (B * C) as would be correct
"""
import random
import re
import math
class Roller_dict (object):
"""A 'dictionary' that stores rollers, if it's not got that roller it'll make a new one"""
def __init__(self, generator=random.randint):
super(Roller_dict, self).__init__()
self.rollers = {}
# Generator is used to supply a "rigged" random function for testing purposes
self.generator = generator
def __call__(self, constructor):
constructor = constructor.replace(" ", "")
if constructor not in self.rollers:
self.rollers[constructor] = Roller(constructor, self.generator)
return self.rollers[constructor]()
# Regular expressions used by the Roller class
# Compiled here to save time if we need to make lots of Roller objects
pattern_split = re.compile(r"(\+|-|\*|/)")
pattern_constant = re.compile(r"([0-9]*)")
pattern_die = re.compile(r"([0-9]*)[Dd]([0-9]*)")
pattern_sign = re.compile(r"^(\+|-|\*|/)")
class Roller (object):
def __call__(self):
return self.roll()
def __init__(self, constructor, generator=random.randint):
super(Roller, self).__init__()
self.items = []
self.rebuild(constructor)
self.generator = generator
def rebuild(self, constructor):
"""Builds the Roller from a new constructor string"""
# First we need to split it up
c = pattern_split.split(constructor.replace(" ", ""))
# Check for exceptions
if len(c) == 0:
raise Exception('String "%s" did not produce any splits' % constructor)
# Stitch signs back into their sections
parts = []
last_p = ""
for p in c:
if p in "+-*/":
last_p = p
continue
if last_p != "":
p = "%s%s" % (last_p, p)
last_p = ""
parts.append(p)
# We have the parts, now we need to evaluate them into items
for p in parts:
# Look for a sign, default to positive
sign = pattern_sign.search(p)
if sign == None: sign = "+"
else: sign = sign.groups()[0]
# Strip out the sign, we're left with just the pure value
body = p.replace(sign, "")
# Now we find out what our main body is
# Die
value = pattern_die.search(body)
if value != None:
# Sign, Number, Sides
self.items.append(("die", sign, int(value.groups()[0]), int(value.groups()[1])))
continue
# Constant
value = pattern_constant.search(body)
if value != None:
self.items.append(("constant", sign, int(value.groups()[0])))
continue
# No matches
raise Exception('The part string "%s" had no matches' % body)
def roll(self):
"""Rolls the die/dice and returns the result"""
result = 0
for i in self.items:
# Get value
if i[0] == "die": value = self._derive_die(i[2], i[3])
elif i[0] == "constant": value = self._derive_constant(i[2])
else: raise Exception('No handler for item type "%s"' % i[0])
# Apply sign
if i[1] == "+": result += value
elif i[1] == "-": result -= value
elif i[1] == "*": result *= value
elif i[1] == "/": result /= value
return result
def _derive_die(self, number, sides):
result = 0
for n in range(0, number):
result += self.generator(0, sides)
return result
def _derive_constant(self, value):
return value
# Useful for running the tests to make sure that it uses "random" numbers
false_numbers = (int(math.cos(x)*5)+5 for x in range(0,1000))
def false_numbers_func(*args):
return false_numbers.next()
# If it's main, run unit tests?
if __name__ == '__main__':
r = Roller_dict(false_numbers_func)
print(r("2D6"))
print(r("2D6"))
print(r("2D6"))
答案 0 :(得分:3)
我认为你的方法会带来很多复杂性:你在尝试解决难题(解析输入)的同时解决难度较小的问题(做骰子滚动)。如果你把问题分开,那就更容易了。
滚动骰子的类相对容易编写。我正在做的两件事你不是:将符号映射到操作(使用映射意味着不必编写逻辑,再加上它可重用),并让Roller
个对象在一个简单的链表中链接在一起,所以在列表的头部调用roll
会滚动所有这些并总结结果。
import random
R = random.Random()
class Roller(object):
# map signs to operations
op = { "+" : lambda a,b: a+b,
"-" : lambda a,b: a-b,
"*" : lambda a,b: a*b,
"/" : lambda a,b: a/b }
def __init__(self, dice, sides, sign=None, modifier=0):
self.dice = dice
self.sides = sides
self.sign = sign
self.modifier = modifier
self.next_sign = None
self.next_roller = None
def roll(self):
self.dice_rolled = [R.randint(1, self.sides) for n in range(self.dice)]
result = sum(dice_rolled)
if self.sign:
result = self.op[self.sign](result, self.modifier)
if self.next_sign and self.next_roller:
result = self.op[self.next_sign](result, self.next_roller.roll())
return result
测试它相对容易。请注意,dice_rolled
将保存为属性,以便您可以更轻松地编写单元测试。
下一步是弄清楚如何解析输入。这种作品:
>>> p = """
(?P<next_sign>[-+*/])?
(?P<dice>[\d]+)
[\s]*D[\s]*
(?P<sides>[\d]+)
# trailing sign and modifier are optional, but if one is present both must be
([\s]*(?P<sign>[-+/*])[\s]*(?P<modifier>[\d]+))?"""
>>> r = re.compile(p, re.VERBOSE+re.IGNORECASE)
>>> m=r.match('2 d 20 +1')
>>> m.group('dice'), m.group('sides'), m.group('sign'), m.group('modifier')
('2', '20', '+', '1')
>>> r.findall('3D6*2-1D4+1*2D6-1')
[('', '3', '6', '*2', '*', '2'), ('-', '1', '4', '+1', '+', '1'), ('*', '2', '6', '-1', '-', '1')]
语法允许的词汇含糊不清 - 2D6+1D4
被解析为2D6+1
后跟无法匹配的D4
,而且我不清楚如何在正则表达式中解决这个问题。也许这可以用负前瞻断言来解决。
无论如何,一旦正则表达式得到修复,剩下的唯一事情就是处理r.findall
的结果来创建一个Roller
个对象链。如果你真的挖掘封装,那就把它作为一个类方法。
答案 1 :(得分:1)
表面上看,PEP08;特别是使用4个空格进行缩进与使用制表符。
在这里看起来你也有很多偶然的复杂性,但是我必须更多地去理解它。这似乎是一个简单的想法,它不应该像看起来那样花费太多的努力。
答案 2 :(得分:1)
pyparsing examples page包含类似的dice expression parser and roller,包括以下测试用例:
D5+2d6*3-5.5+4d6
D5+2d6*3-5.5+4d6.takeHighest(3)
2d6*3-5.5+4d6.minRoll(2).takeHighest(3)
脚本的前30行左右包含解析器,其余包含一个评估器,包括显示正在滚动的卷的调试代码。
我意识到这更像是一个“银盘”答案,而不是对你发布的代码的反馈 - 罗伯特罗斯尼的答案有一个共同点就是解析与滚动的明确分离。也许在这个和罗伯特的样本之间,你可以为自己的骰子收集一些花絮。