dice.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. '''
  2. '''
  3. import random
  4. import re
  5. _ALLOWED = {'abs': abs}
  6. def roll_d(pattern):
  7. """ Parse, roll and evaluate the pattern.
  8. 'pattern' can be any mathematics expression and include dice notations ('xDx').
  9. Eg: '1d4+2', '10d6+2-1d20', '3*(1d6+2)'...Etc
  10. Returns tuple of score (integer) and detailed result (string)
  11. """
  12. # Parse the pattern
  13. parsed = _parse(pattern)
  14. raw = _forcejoin(parsed)
  15. return _secured_eval(raw), raw
  16. def roll(pattern):
  17. """ Similar to roll_d(), but only return the numeric results (integer) """
  18. return roll_d(pattern)[0]
  19. def _secured_eval(raw):
  20. """ securely evaluate the incoming raw string """
  21. return eval(raw, {"__builtins__":None}, _ALLOWED)
  22. def _forcejoin(obj_list):
  23. """ force-join the objects of the list by forcing string conversion of its items """
  24. return "".join(map(str, obj_list))
  25. def _parse(pattern):
  26. """ split the members and symbols of the pattern and roll them when it is possible """
  27. return [_roll_if_u_can(m) for m in _split_members(pattern)]
  28. def _normalize(pattern):
  29. """ normalize the incoming string to a lower string without spaces"""
  30. return str(pattern).replace(" ", "").lower()
  31. def _split_members(pattern):
  32. """ split a string by blocks of numeric / symbols / dice notations
  33. eg: '1d6+2' becomes ['1d6', '+', '2']
  34. """
  35. return re.findall(r"[\w']+|[\W']+", _normalize(pattern))
  36. def _roll_if_u_can(member):
  37. """ try to interpret member as a dice notation and roll it.
  38. If it can not, member is returned as it was."""
  39. try:
  40. return Dice.parse(member).roll()
  41. except ValueError:
  42. return member
  43. class Dice():
  44. """ Dice(sides, amount=1)
  45. Set of dice. Use roll() to get a score.
  46. """
  47. def __init__(self, sides, amount=1):
  48. self._sides = 1
  49. self._amount = 0
  50. self.sides = sides
  51. self.amount = amount
  52. @property
  53. def sides(self):
  54. return self._sides
  55. @sides.setter
  56. def sides(self, sides):
  57. try:
  58. if not int(sides) >= 1:
  59. raise ValueError()
  60. except (TypeError, ValueError):
  61. raise TypeError("Invalid value for sides (given: '{}')".format(sides))
  62. self._sides = sides
  63. @property
  64. def amount(self):
  65. return self._amount
  66. @amount.setter
  67. def amount(self, amount):
  68. try:
  69. if not int(amount) >= 0:
  70. raise ValueError()
  71. except (TypeError, ValueError):
  72. raise ValueError("Invalid value for amount (given: '{}')".format(amount))
  73. self._amount = amount
  74. def roll(self):
  75. """ Role the dice and return a Score object """
  76. return Score([random.randint(1, self._sides) for _ in range(self._amount)])
  77. @classmethod
  78. def parse(cls, pattern):
  79. """ parse a pattern of the form 'xdx', where x are positive integers """
  80. # normalize
  81. pattern = str(pattern).replace(" ", "").lower()
  82. # parse
  83. amount, faces = (int(x) for x in pattern.split("d"))
  84. # instanciate
  85. return Dice(faces, amount)
  86. class Score(int):
  87. """ Score is a subclass of integer.
  88. Then you can manipulate it as you would do with an integer.
  89. It also provides an access to the detail with the property 'results'.
  90. 'results' is the list of the scores obtained by each dice.
  91. Score class can also be used as an iterable, to walk trough the individual scores.
  92. eg:
  93. >>> s = Score([1,2,3])
  94. >>> print(s)
  95. 6
  96. >>> s + 1
  97. 7
  98. >>> list(s)
  99. [1,2,3]
  100. """
  101. def __new__(cls, results):
  102. score = super(Score, cls).__new__(cls, sum(results))
  103. score._results = results
  104. return score
  105. @property
  106. def results(self):
  107. return self._results
  108. def __repr__(self):
  109. return "<<{} ({})>>".format(int(self), self.results)
  110. def __contains__(self, value):
  111. return self.results.__contains__(value)
  112. def __iter__(self):
  113. return self.results.__iter__()
  114. # print(roll("2*(2d1+1d1-1d1-3)//2"))