Skip to content

Commit 6533756

Browse files
committed
Restored code
1 parent 4ae7cbd commit 6533756

7 files changed

Lines changed: 571 additions & 0 deletions

File tree

dice_notation/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
Dice Notation Tools for Python
4+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5+
Dice notation tools
6+
:copyright: (c) 2016-2018 by Bernardo Martínez Garrido
7+
:license: MIT, see LICENSE for more details.
8+
"""
9+
10+
__version__ = '1.0.3'
11+
__license__ = 'MIT'

dice_notation/dice.py

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# -*- coding: utf-8 -*-
2+
3+
from abc import ABCMeta, abstractmethod
4+
from random import randint
5+
6+
"""
7+
Dice classes.
8+
9+
These are just plain dice, not dice notation, even thought they may be used
10+
for handling that too.
11+
12+
The classes in this file will allow creating and using dice and rollable
13+
entities, which are able to generate a random value.
14+
15+
There are two dice classes, the Dice is just the bare dice, while the
16+
RollableDice is an extension, which allows rolling the dice.
17+
"""
18+
19+
__author__ = 'Benardo Martínez Garrido'
20+
__license__ = 'MIT'
21+
22+
23+
class Rollable(object, metaclass=ABCMeta):
24+
"""
25+
Interface for rollable classes.
26+
27+
While rolling implies using dice to generate a random value, this interface
28+
just takes care of generating a random value.
29+
30+
This way it not only can support any kind of dice, but also more complex
31+
constructions such as dice notation expressions, where calling the roll
32+
method would execute the full expression.
33+
34+
As such, the value generated by rolling may be anything.
35+
"""
36+
37+
def __init__(self):
38+
pass
39+
40+
@abstractmethod
41+
def roll(self):
42+
"""
43+
Generates a random value.
44+
45+
This can be anything, the only expectation is that the output
46+
is randomized somehow.
47+
"""
48+
raise NotImplementedError('The roll method must be implemented')
49+
50+
51+
class Dice(object):
52+
"""
53+
A group of dice, all with the same number of sides. Such a group is just
54+
composed of a quantity of dice, and their number of sides.
55+
56+
Both the quantity and the number of sides are expected to be positive, as
57+
any other value would make no sense.
58+
59+
No other limitation is expected. In the real world the number of sides
60+
which a die may physically have are limited by the rules of geometry,
61+
but there is no reason to take care of that.
62+
"""
63+
64+
def __init__(self, quantity, sides):
65+
super(Dice, self).__init__()
66+
self._quantity = quantity
67+
self._sides = sides
68+
69+
def __str__(self):
70+
return '%sd%s' % (self.quantity, self.sides)
71+
72+
def __repr__(self):
73+
return '<class %s>(quantity=%r, sides=%r)' % \
74+
(self.__class__.__name__, self.quantity, self.sides)
75+
76+
@property
77+
def quantity(self):
78+
"""
79+
The number of dice which compose this group.
80+
81+
This is expected to be a positive value or zero.
82+
83+
:return: the number of dice
84+
"""
85+
return self._quantity
86+
87+
@quantity.setter
88+
def quantity(self, quantity):
89+
self._quantity = quantity
90+
91+
@property
92+
def sides(self):
93+
"""
94+
The number of sides each die has.
95+
96+
All the dice in the group have the same number of sides.
97+
98+
This is expected to be a positive value or zero.
99+
100+
:return: the number of sides
101+
"""
102+
return self._sides
103+
104+
@sides.setter
105+
def sides(self, sides):
106+
self._sides = sides
107+
108+
109+
class RollableDice(Dice, Rollable):
110+
"""
111+
A rollable dice group.
112+
113+
The result of calling the roll method will be an integer, which will be
114+
between 1 and the number of sides. Actually one number will be generated
115+
like that as many times as the value of the quantity field, and all those
116+
values will be added, and then returned.
117+
"""
118+
119+
def __init__(self, quantity, sides):
120+
super(RollableDice, self).__init__(quantity, sides)
121+
122+
def roll(self):
123+
result = 0
124+
125+
if self.quantity == 0 or self.sides == 0:
126+
result = 0
127+
elif self.quantity is None or self.sides is None:
128+
result = None
129+
elif self.quantity > 0 and self.sides > 0:
130+
for x in range(self.quantity):
131+
result += randint(1, self.sides)
132+
else:
133+
result = None
134+
135+
return result

dice_notation/parser/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# -*- coding: utf-8 -*-
2+
3+
from dice_notation.parser.dice import DiceParser
4+
5+
__all__ = ['DiceParser']

dice_notation/parser/common.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import os
4+
from abc import ABCMeta, abstractmethod
5+
6+
import ply.lex as lex
7+
import ply.yacc as yacc
8+
9+
"""
10+
Base classes for parsers.
11+
12+
It contains some interfaces, along a base parser for the parsing library being
13+
used.
14+
"""
15+
16+
__author__ = 'Bernardo Martínez Garrido'
17+
__license__ = 'MIT'
18+
19+
20+
class Parser(object, metaclass=ABCMeta):
21+
"""
22+
Interface for implementing parsers.
23+
24+
It just contains a single method, 'parse', which will receive a value
25+
and take care of parsing it into another.
26+
"""
27+
28+
def __init__(self):
29+
pass
30+
31+
@abstractmethod
32+
def parse(self, value):
33+
raise NotImplementedError('The parse method must be implemented')
34+
35+
36+
class PlyParser(Parser):
37+
"""
38+
Base class for a lexer/parser that has the rules defined as methods.
39+
40+
It makes use of Ply for the parsing.
41+
"""
42+
43+
tokens = ()
44+
precedence = ()
45+
46+
def __init__(self, **kw):
47+
super(PlyParser, self).__init__()
48+
self.debug = kw.get('debug', 0)
49+
self.names = {}
50+
try:
51+
modname = os.path.split(os.path.splitext(__file__)[0])[
52+
1] + "_" + self.__class__.__name__
53+
except Exception:
54+
modname = "parser" + "_" + self.__class__.__name__
55+
self.debugfile = modname + ".dbg"
56+
self.tabmodule = modname + "_" + "parsetab"
57+
# print self.debugfile, self.tabmodule
58+
59+
# Builds the lexer and parser
60+
lex.lex(module=self, debug=self.debug)
61+
yacc.yacc(module=self,
62+
debug=self.debug,
63+
debugfile=self.debugfile,
64+
tabmodule=self.tabmodule)
65+
66+
def parse(self, value):
67+
return yacc.parse(value)

dice_notation/parser/dice.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import logging
4+
from operator import add, sub
5+
6+
from dice_notation.parser.common import PlyParser
7+
from dice_notation.parser.notation import BinaryOperation, ConstantOperand, \
8+
DiceOperand
9+
10+
"""
11+
Dice notation parsers.
12+
"""
13+
14+
__author__ = 'Bernardo Martínez Garrido'
15+
__license__ = 'MIT'
16+
17+
18+
class DiceParser(PlyParser):
19+
"""
20+
Dice notation parser.
21+
22+
It handles the most common version of the dice notation.
23+
"""
24+
25+
tokens = (
26+
'DIGIT', 'DSEPARATOR', 'ADD', 'SUB'
27+
)
28+
29+
def __init__(self):
30+
super(DiceParser, self).__init__()
31+
self._logger = logging.getLogger("DiceParser")
32+
33+
# Tokens
34+
35+
t_ADD = r'\+'
36+
t_DSEPARATOR = r'(d|D)'
37+
t_SUB = r'-'
38+
39+
def t_DIGIT(self, t):
40+
r'\d+'
41+
try:
42+
t.value = int(t.value)
43+
except ValueError:
44+
self._logger.error("Integer value too large %s", t.value)
45+
t.value = 0
46+
# print "parsed number %s" % repr(t.value)
47+
return t
48+
49+
def t_error(self, t):
50+
self._logger.error("Illegal character '%s'", t.value[0])
51+
t.lexer.skip(1)
52+
53+
# Parsing rules
54+
55+
precedence = (
56+
('left', 'ADD', 'SUB'),
57+
)
58+
59+
def p_statement_expr(self, p):
60+
'statement : expression'
61+
p[0] = p[1]
62+
self._logger.debug("Statement %s", p[0])
63+
64+
def p_expression_dice(self, p):
65+
'expression : DIGIT DSEPARATOR DIGIT'
66+
p[0] = DiceOperand(p[1], p[3])
67+
self._logger.debug("Dice %s", p[0])
68+
69+
def p_expression_binop(self, p):
70+
"""
71+
expression : expression ADD expression
72+
| expression SUB expression
73+
"""
74+
# print [repr(p[i]) for i in range(0,4)]
75+
if p[2] == '+':
76+
p[0] = BinaryOperation(add, p[1], p[3])
77+
elif p[2] == '-':
78+
p[0] = BinaryOperation(sub, p[1], p[3])
79+
self._logger.debug("Binary operation %s", p[0])
80+
81+
def p_expression_digit(self, p):
82+
'expression : DIGIT'
83+
if isinstance(p[1], ConstantOperand):
84+
p[0] = p[1]
85+
else:
86+
p[0] = ConstantOperand(p[1])
87+
self._logger.debug("Constant %s", p[0])
88+
89+
def p_error(self, p):
90+
if p:
91+
self._logger.error("Syntax error at '%s'", p.value)
92+
else:
93+
self._logger.error("Syntax error at EOF")

0 commit comments

Comments
 (0)