dos2unix dice/*

This commit is contained in:
Brian S. Stephan 2019-06-22 11:45:09 -05:00
parent 649a148209
commit 6b8dd1a645
3 changed files with 308 additions and 308 deletions

View File

@ -1,264 +1,264 @@
"""Dice rollers used by the views, bots, etc.""" """Dice rollers used by the views, bots, etc."""
import logging import logging
import random import random
import ply.lex as lex import ply.lex as lex
import ply.yacc as yacc import ply.yacc as yacc
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class DiceRoller(object): class DiceRoller(object):
tokens = ['NUMBER', 'TEXT', 'ROLLSEP'] tokens = ['NUMBER', 'TEXT', 'ROLLSEP']
literals = ['#', '/', '+', '-', 'd'] literals = ['#', '/', '+', '-', 'd']
t_TEXT = r'\s+[^;]+' t_TEXT = r'\s+[^;]+'
t_ROLLSEP = r';\s*' t_ROLLSEP = r';\s*'
def build(self): def build(self):
lex.lex(module=self) lex.lex(module=self)
yacc.yacc(module=self) yacc.yacc(module=self)
def t_NUMBER(self, t): def t_NUMBER(self, t):
r'\d+' r'\d+'
t.value = int(t.value) t.value = int(t.value)
return t return t
def t_error(self, t): def t_error(self, t):
t.lexer.skip(1) t.lexer.skip(1)
precedence = ( precedence = (
('left', 'ROLLSEP'), ('left', 'ROLLSEP'),
('left', '+', '-'), ('left', '+', '-'),
('right', 'd'), ('right', 'd'),
('left', '#'), ('left', '#'),
('left', '/') ('left', '/')
) )
output = "" output = ""
def roll_dice(self, keep, dice, size): def roll_dice(self, keep, dice, size):
"""Takes the parsed dice string for a single roll (eg 3/4d20) and performs """Takes the parsed dice string for a single roll (eg 3/4d20) and performs
the actual roll. Returns a string representing the result. the actual roll. Returns a string representing the result.
""" """
a = list(range(dice)) a = list(range(dice))
for i in range(dice): for i in range(dice):
a[i] = random.randint(1, size) a[i] = random.randint(1, size)
if keep != dice: if keep != dice:
b = sorted(a, reverse=True) b = sorted(a, reverse=True)
b = b[0:keep] b = b[0:keep]
else: else:
b = a b = a
total = sum(b) total = sum(b)
outstr = "[" + ",".join(str(i) for i in a) + "]" outstr = "[" + ",".join(str(i) for i in a) + "]"
return (total, outstr) return (total, outstr)
def process_roll(self, trials, mods, comment): def process_roll(self, trials, mods, comment):
"""Processes rolls coming from the parser. """Processes rolls coming from the parser.
This generates the inputs for the roll_dice() command, and returns This generates the inputs for the roll_dice() command, and returns
the full string representing the whole current dice string (the part the full string representing the whole current dice string (the part
up to a semicolon or end of line). up to a semicolon or end of line).
""" """
rolls = mods[0][0][1] if mods[0][0][1] else 1 rolls = mods[0][0][1] if mods[0][0][1] else 1
if trials: if trials:
assert trials <= 10, "Too many rolls (max: 10)." assert trials <= 10, "Too many rolls (max: 10)."
assert rolls <= 50, "Too many dice (max: 50) in roll." assert rolls <= 50, "Too many dice (max: 50) in roll."
output = "" output = ""
repeat = 1 repeat = 1
if trials != None: if trials != None:
repeat = trials repeat = trials
for i in range(repeat): for i in range(repeat):
mode = 1 mode = 1
total = 0 total = 0
curr_str = "" curr_str = ""
if i > 0: if i > 0:
output += ", " output += ", "
for m in mods: for m in mods:
keep = 0 keep = 0
dice = 1 dice = 1
res = 0 res = 0
# if m is a tuple, then it is a die roll # if m is a tuple, then it is a die roll
# m[0] = (keep, num dice) # m[0] = (keep, num dice)
# m[1] = num faces on the die # m[1] = num faces on the die
if type(m) == tuple: if type(m) == tuple:
if m[0] != None: if m[0] != None:
if m[0][0] != None: if m[0][0] != None:
keep = m[0][0] keep = m[0][0]
dice = m[0][1] dice = m[0][1]
size = m[1] size = m[1]
if keep > dice or keep == 0: if keep > dice or keep == 0:
keep = dice keep = dice
if size < 1: if size < 1:
output = "# of sides for die is incorrect: %d" % size output = "# of sides for die is incorrect: %d" % size
return output return output
if dice < 1: if dice < 1:
output = "# of dice is incorrect: %d" % dice output = "# of dice is incorrect: %d" % dice
return output return output
res = self.roll_dice(keep, dice, size) res = self.roll_dice(keep, dice, size)
curr_str += "%d%s" % (res[0], res[1]) curr_str += "%d%s" % (res[0], res[1])
res = res[0] res = res[0]
elif m == "+": elif m == "+":
mode = 1 mode = 1
curr_str += "+" curr_str += "+"
elif m == "-": elif m == "-":
mode = -1 mode = -1
curr_str += "-" curr_str += "-"
else: else:
res = m res = m
curr_str += str(m) curr_str += str(m)
total += mode * res total += mode * res
if repeat == 1: if repeat == 1:
if comment != None: if comment != None:
output = "%d %s (%s)" % (total, comment.strip(), curr_str) output = "%d %s (%s)" % (total, comment.strip(), curr_str)
else: else:
output = "%d (%s)" % (total, curr_str) output = "%d (%s)" % (total, curr_str)
else: else:
output += "%d (%s)" % (total, curr_str) output += "%d (%s)" % (total, curr_str)
if i == repeat - 1: if i == repeat - 1:
if comment != None: if comment != None:
output += " (%s)" % (comment.strip()) output += " (%s)" % (comment.strip())
return output return output
def p_roll_r(self, p): def p_roll_r(self, p):
# Chain rolls together. # Chain rolls together.
# General idea I had when creating this grammar: A roll string is a chain # General idea I had when creating this grammar: A roll string is a chain
# of modifiers, which may be repeated for a certain number of trials. It can # of modifiers, which may be repeated for a certain number of trials. It can
# have a comment that describes the roll # have a comment that describes the roll
# Multiple roll strings can be chained with semicolon # Multiple roll strings can be chained with semicolon
'roll : roll ROLLSEP roll' 'roll : roll ROLLSEP roll'
global output global output
p[0] = p[1] + "; " + p[3] p[0] = p[1] + "; " + p[3]
output = p[0] output = p[0]
def p_roll(self, p): def p_roll(self, p):
# Parse a basic roll string. # Parse a basic roll string.
'roll : trial modifier comment' 'roll : trial modifier comment'
global output global output
mods = [] mods = []
if type(p[2]) == list: if type(p[2]) == list:
mods = p[2] mods = p[2]
else: else:
mods = [p[2]] mods = [p[2]]
p[0] = self.process_roll(p[1], mods, p[3]) p[0] = self.process_roll(p[1], mods, p[3])
output = p[0] output = p[0]
def p_roll_no_trials(self, p): def p_roll_no_trials(self, p):
# Parse a roll string without trials. # Parse a roll string without trials.
'roll : modifier comment' 'roll : modifier comment'
global output global output
mods = [] mods = []
if type(p[1]) == list: if type(p[1]) == list:
mods = p[1] mods = p[1]
else: else:
mods = [p[1]] mods = [p[1]]
p[0] = self.process_roll(None, mods, p[2]) p[0] = self.process_roll(None, mods, p[2])
output = p[0] output = p[0]
def p_comment(self, p): def p_comment(self, p):
# Parse a comment. # Parse a comment.
'''comment : TEXT '''comment : TEXT
|''' |'''
if len(p) == 2: if len(p) == 2:
p[0] = p[1] p[0] = p[1]
else: else:
p[0] = None p[0] = None
def p_modifier(self, p): def p_modifier(self, p):
# Parse a modifier on a roll string. # Parse a modifier on a roll string.
'''modifier : modifier "+" modifier '''modifier : modifier "+" modifier
| modifier "-" modifier''' | modifier "-" modifier'''
# Use append to prevent nested lists (makes dealing with this easier) # Use append to prevent nested lists (makes dealing with this easier)
if type(p[1]) == list: if type(p[1]) == list:
p[1].append(p[2]) p[1].append(p[2])
p[1].append(p[3]) p[1].append(p[3])
p[0] = p[1] p[0] = p[1]
elif type(p[3]) == list: elif type(p[3]) == list:
p[3].insert(0, p[2]) p[3].insert(0, p[2])
p[3].insert(0, p[1]) p[3].insert(0, p[1])
p[0] = p[3] p[0] = p[3]
else: else:
p[0] = [p[1], p[2], p[3]] p[0] = [p[1], p[2], p[3]]
def p_die(self, p): def p_die(self, p):
# Return the left side before the "d", and the number of faces. # Return the left side before the "d", and the number of faces.
'modifier : left NUMBER' 'modifier : left NUMBER'
p[0] = (p[1], p[2]) p[0] = (p[1], p[2])
def p_die_num(self, p): def p_die_num(self, p):
'modifier : NUMBER' 'modifier : NUMBER'
p[0] = p[1] p[0] = p[1]
def p_left(self, p): def p_left(self, p):
# Parse the number of dice we are rolling, and how many we are keeping. # Parse the number of dice we are rolling, and how many we are keeping.
'left : keep dice' 'left : keep dice'
if p[1] == None: if p[1] == None:
p[0] = [None, p[2]] p[0] = [None, p[2]]
else: else:
p[0] = [p[1], p[2]] p[0] = [p[1], p[2]]
def p_left_all(self, p): def p_left_all(self, p):
'left : dice' 'left : dice'
p[0] = [None, p[1]] p[0] = [None, p[1]]
def p_left_e(self, p): def p_left_e(self, p):
'left :' 'left :'
p[0] = None p[0] = None
def p_total(self, p): def p_total(self, p):
'trial : NUMBER "#"' 'trial : NUMBER "#"'
if len(p) > 1: if len(p) > 1:
p[0] = p[1] p[0] = p[1]
else: else:
p[0] = None p[0] = None
def p_keep(self, p): def p_keep(self, p):
'keep : NUMBER "/"' 'keep : NUMBER "/"'
if p[1] != None: if p[1] != None:
p[0] = p[1] p[0] = p[1]
else: else:
p[0] = None p[0] = None
def p_dice(self, p): def p_dice(self, p):
'dice : NUMBER "d"' 'dice : NUMBER "d"'
p[0] = p[1] p[0] = p[1]
def p_dice_one(self, p): def p_dice_one(self, p):
'dice : "d"' 'dice : "d"'
p[0] = 1 p[0] = 1
def p_error(self, p): def p_error(self, p):
"""Raise ValueError on unparseable strings.""" """Raise ValueError on unparseable strings."""
raise ValueError("Error occurred in parser") raise ValueError("Error occurred in parser")
def get_result(self): def get_result(self):
global output global output
return output return output
def do_roll(self, dicestr): def do_roll(self, dicestr):
""" """
Roll some dice and get the result (with broken out rolls). Roll some dice and get the result (with broken out rolls).
Keyword arguments: Keyword arguments:
dicestr - format: dicestr - format:
N#X/YdS+M label N#X/YdS+M label
N#: do the following roll N times (optional) N#: do the following roll N times (optional)
X/: take the top X rolls of the Y times rolled (optional) X/: take the top X rolls of the Y times rolled (optional)
Y : roll the die specified Y times (optional, defaults to 1) Y : roll the die specified Y times (optional, defaults to 1)
dS: roll a S-sided die dS: roll a S-sided die
+M: add M to the result (-M for subtraction) (optional) +M: add M to the result (-M for subtraction) (optional)
""" """
self.build() self.build()
yacc.parse(dicestr) yacc.parse(dicestr)
return self.get_result() return self.get_result()

View File

@ -1,8 +1,8 @@
"""URL patterns for the dice views.""" """URL patterns for the dice views."""
from django.urls import path from django.urls import path
from dice.views import rpc_roll_dice from dice.views import rpc_roll_dice
urlpatterns = [ urlpatterns = [
path('rpc/roll/', rpc_roll_dice, name='dice_rpc_roll_dice'), path('rpc/roll/', rpc_roll_dice, name='dice_rpc_roll_dice'),
] ]

View File

@ -1,36 +1,36 @@
"""Views for rolling dice.""" """Views for rolling dice."""
import json import json
import logging import logging
from rest_framework.authentication import BasicAuthentication from rest_framework.authentication import BasicAuthentication
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.decorators import api_view, authentication_classes, permission_classes from rest_framework.decorators import api_view, authentication_classes, permission_classes
from dice.roller import DiceRoller from dice.roller import DiceRoller
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
roller = DiceRoller() roller = DiceRoller()
@api_view(['POST']) @api_view(['POST'])
@authentication_classes((BasicAuthentication, )) @authentication_classes((BasicAuthentication, ))
@permission_classes((IsAuthenticated, )) @permission_classes((IsAuthenticated, ))
def rpc_roll_dice(request): def rpc_roll_dice(request):
"""Get a dice string from the client, roll the dice, and give the result.""" """Get a dice string from the client, roll the dice, and give the result."""
if request.method != 'POST': if request.method != 'POST':
return Response({'detail': "Supported method: POST."}, status=405) return Response({'detail': "Supported method: POST."}, status=405)
try: try:
roll_data = json.loads(request.body) roll_data = json.loads(request.body)
dice_str = roll_data['dice'] dice_str = roll_data['dice']
except (json.decoder.JSONDecodeError, KeyError): except (json.decoder.JSONDecodeError, KeyError):
return Response({'detail': "Request must be JSON with a 'dice' parameter."}, status=400) return Response({'detail': "Request must be JSON with a 'dice' parameter."}, status=400)
try: try:
result_str = roller.do_roll(dice_str) result_str = roller.do_roll(dice_str)
return Response({'dice': dice_str, 'result': result_str}) return Response({'dice': dice_str, 'result': result_str})
except AssertionError as aex: except AssertionError as aex:
return Response({'detail': f"Could not roll dice: {aex}"}, status=400) return Response({'detail': f"Could not roll dice: {aex}"}, status=400)
except ValueError: except ValueError:
return Response({'detail': f"Could not parse requested dice '{dice_str}'."}, status=400) return Response({'detail': f"Could not parse requested dice '{dice_str}'."}, status=400)