33 votes

Programming Challenge: Dice Roller

Its been a while since we did one of these, which is a shame.

Create a program that takes is an input of the type: "d6 + 3" or "2d20 - 5", and return a valid roll.
The result should display both the actual rolls as well as the final result. The program should accept any valid roll of the type 'xdx'
Bonuses:

  • Multiplication "d6 * 3"
  • Division "d12 / 6"
  • Polish notation "4d6 * (5d4 - 3)"

As a side note, it would be really cool if weekly programming challenges became a thing

34 comments

  1. [12]
    mftrhu
    (edited )
    Link
    I usually write Python code, but Pascal was my first programming language - almost two decades ago now - and I have been using it a bit lately to solve some DMOJ problems. It's a decent fit for a...

    I usually write Python code, but Pascal was my first programming language - almost two decades ago now - and I have been using it a bit lately to solve some DMOJ problems. It's a decent fit for a lot of them, and it produces ridiculously lean programs to boot.

    The bonuses, though, will have to wait. Unless reverse polish notation is acceptable?

    Pascal (fpc 3.0.0)

    program DiceRoller;
    {$mode objfpc}
    uses
      SysUtils, RegExpr;
    var
      line : String;
    
    function DiceRoll(spec : String; show : Boolean = False) : Integer;
    var
      dice, faces, roll, i : Integer;
      re : TRegExpr;
    begin
      re := TRegExpr.Create('(\d*)\s*d\s*(\d+)\s*(([+-])\s*(\d+))?');
      if re.Exec(spec) then
      begin
        if Length(re.Match[1]) > 0 then
          dice := StrToInt(re.Match[1])
        else
          dice := 1;
        faces := StrToInt(re.Match[2]);
        if re.Match[4] = '+' then DiceRoll := + StrToInt(re.Match[5]);
        if re.Match[4] = '-' then DiceRoll := - StrToInt(re.Match[5]);
        for i := 1 to dice do
        begin
          roll := Random(faces) + 1;
          DiceRoll := DiceRoll + roll;
          if show then Write(roll, ' ');
        end;
      end;
      if show then WriteLn;
      re.Free;
    end;
    
    begin
      Randomize;
      repeat
        ReadLn(line);
        WriteLn(DiceRoll(line, True));
      until line = '';
    end.
    

    Python 3.x

    import re, sys, random, operator as op
    
    def dice_roll(spec, show=False):
        match = re.match(r"(\d*)d(\d+)(([+-])(\d+))?", spec)
        dice = int(match.group(1) or "1")
        faces = int(match.group(2))
        adds = [1, -1][match.group(4) == "-"] * int(match.group(5)) if match.group(3) else 0
        rolls = [random.randint(1, faces) for i in range(dice)]
        if show:
            print(" ".join(map(str, rolls)))
        return sum(rolls) + adds
    
    def rpn(line):
        operators = {"+": op.add, "-": op.sub, "*": op.mul, "/": op.truediv}
        stack = []
        for token in line.split():
            if "d" in token:
                stack.append(dice_roll(token))
            elif token in operators:
                b, a = stack.pop(-1), stack.pop(-1)
                stack.append(operators[token](a, b))
            else:
                try:
                    stack.append(int(token))
                except ValueError:
                    pass
        return(stack[-1])
    
    if __name__ == "__main__":
        for line in sys.stdin:
            print(rpn(line.strip()))
    

    Edit: s/dices/dice/g, s|(\d*)d(\d+)(([+-])(\d+))?|(\d*)\s*d\s*(\d+)\s*(([+-])\s*(\d+))?|

    6 votes
    1. [6]
      sniper24
      Link Parent
      The input "4d6 + 12" throws an error, as does any addition attempt

      The input "4d6 + 12" throws an error, as does any addition attempt

      2 votes
      1. [5]
        mftrhu
        Link Parent
        The Pascal one will just ignore the + 12, as I forgot to sprinkle around enough \s* in the regex (and (\d*)\s*d\s*(\d+)\s*(([+-])\s*(\d+))? should do the trick), but, as far as the Python version...

        The Pascal one will just ignore the + 12, as I forgot to sprinkle around enough \s* in the regex (and (\d*)\s*d\s*(\d+)\s*(([+-])\s*(\d+))? should do the trick), but, as far as the Python version is concerned, that's an user error.

        It's using reverse polish notation, not infix notation, so it should either be 4d6+12 (and thus a single "dice roll" token), or 4d6 12 +: 4d6 + 12 reads 4d6, reads +, tries to pop two elements from a single-element-tall stack, and throws up its hands in failure at the stack underflow it gets as a result.

        3 votes
        1. [4]
          sniper24
          (edited )
          Link Parent
          Ah I see, I misunderstood. A simple line.replace(" ", "") would fix that no? edit: indeed it does Edit 2: multiplication and division don't work, inputs of "2d1 * 2 1 1 2" and "3d5 / 5 4 5 1 10"...

          Ah I see, I misunderstood.
          A simple

           line.replace(" ", "") 
          

          would fix that no?
          edit: indeed it does

          Edit 2: multiplication and division don't work, inputs of
          "2d1 * 2
          1 1
          2" and "3d5 / 5
          4 5 1
          10" are wrong

          Ok this is bothering me.
          elif token in operators:
          b, a = stack.pop(-1), stack.pop(-1)
          stack.append(operators[token](a, b))
          print(stack)
          never executes

          OK for operators only entries of the form 3d6 12 * are accepted, but if its + or - , 3d6+12 works fine, I assume cuz the other function works with them

          1. [2]
            mftrhu
            Link Parent
            No, that would break the tokenizer. I'm following Forth's example and just splitting the stream on spaces. I need to distinguish between A and B in A B +, removing spaces would just make the...

            No, that would break the tokenizer. I'm following Forth's example and just splitting the stream on spaces. I need to distinguish between A and B in A B +, removing spaces would just make the program see C+ and break - this is not aiming at being smart, or parsing infix notation at all, and the fact that 3d6+12 works at all is a more-or-less happy mistake, as I copy-pasted the regex before deciding to write the RPN function.

            2 votes
            1. sniper24
              Link Parent
              Ignore me I was being a fool

              Ignore me I was being a fool

          2. sniper24
            Link Parent
            Ok so I'm just being an idiot, the rolling function can do addition and subtraction, but not the others, so infix worked for those, while multiplication and division only work for post fix

            Ok so I'm just being an idiot, the rolling function can do addition and subtraction, but not the others, so infix worked for those, while multiplication and division only work for post fix

            1 vote
    2. [2]
      teaearlgraycold
      Link Parent
      Looking at the Python code it seems like this requires inputs use postfix operators, right?

      Looking at the Python code it seems like this requires inputs use postfix operators, right?

      1 vote
      1. mftrhu
        Link Parent
        Yeah, reverse polish notation. I mentioned it at the beginning of my comment.

        Yeah, reverse polish notation. I mentioned it at the beginning of my comment.

        1 vote
    3. sniper24
      Link Parent
      Feel free to use any form of notation, the point is more to accept full mathematical formulas

      Feel free to use any form of notation, the point is more to accept full mathematical formulas

      1 vote
    4. [2]
      unknown user
      Link Parent
      Yo, just a heads-up: "dice" is already a plural of "die". Python code, line 5.

      Yo, just a heads-up: "dice" is already a plural of "die". Python code, line 5.

      1 vote
      1. mftrhu
        Link Parent
        Yeeeah, I always forget that. Thanks.

        Yeeeah, I always forget that. Thanks.

  2. Hvv
    Link
    This one was really fun to implement and a good reminder that I know how to do a tiny subset of things in C++ very well and many, many things very badly. Does 2.5/3 bonuses (takes expressions in...

    This one was really fun to implement and a good reminder that I know how to do a tiny subset of things in C++ very well and many, many things very badly.

    Does 2.5/3 bonuses (takes expressions in polish notation)
    C++

    #include <bits/stdc++.h>
    using namespace std;
    typedef vector<int> vi;
    typedef vector<vi> vvi;
    mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
    int roll(int n) {
    	return ((rng()%n)+n)%n+1;
    }
    bool isOp(char c) {
    	return (c == '+') || (c=='-') || (c=='*') || (c=='/');
    }
    vvi gentree(vector<string>& vs) {
    	stack<int> st;
    	vvi g;
    	g.assign(vs.size(),vi());
    	if(isOp(vs[0][0])) {st.push(0);st.push(0);}
    	for(int i=1;i<vs.size();i++) {
    		if(st.empty()) {return vvi();}
    		int id = st.top();st.pop();
    		if(isOp(vs[i][0])) {
    			st.push(i);
    			st.push(i);
    		}
    		g[id].push_back(i);
    	}
    	if(!st.empty()) {return vvi();}
    	return g;
    }
    int parseDice(string& s) {
    	int val = 0;
    	int pos = -1;
    	bool dc = false;
    	for(int i=0;i<s.size();i++) {
    		if(s[i] == 'd') {
    			dc = true;
    			pos = i;
    			break;
    		}
    		val *= 10;
    		val += s[i]-'0';
    	}
    	if(dc) {
    		val = max(val,1);
    		int num = 0;
    		int tot = 0;
    		for(int i=pos+1;i<s.size();i++) {
    			num *= 10;
    			num += s[i]-'0';
    		}
    		for(int i=0;i<val;i++) {
    			int r = roll(num);
    			tot += r;
    			cout << "d" << num << ": " << r << '\n';
    		}
    		return tot;
    	} else {
    		return val;
    	}
    }
    int eval(vvi& tr,vector<string>& vs, int u) {
    	if(tr[u].size() > 0) {
    		int lf = eval(tr,vs,tr[u][0]);
    		int rt = eval(tr,vs,tr[u][1]);
    		switch(vs[u][0]) {
    			case '+':
    				return lf+rt;
    			case '-':
    				return lf-rt;
    			case '*':
    				return lf*rt;
    			case '/':
    				return lf/rt;
    			default:
    				return -1;
    		}
    	} else {
    		return parseDice(vs[u]);
    	}
    }
    int main() {
    	while(1) {
    		string st;
    		vector<string> vs;
    		getline(cin,st);
    		stringstream ss(st);
    		string t;
    		while(getline(ss,t,' ')) {vs.push_back(t);}
    		if(vs.size() == 0) break;
    		vvi tr = gentree(vs);
    		if(tr.size() == 0) {
    			printf("Invalid dice expression\n");
    			continue;
    		}
    		int res = eval(tr,vs,0);
    		cout << "Result: " << res << '\n';
    	}
    }
    
    5 votes
  3. [2]
    telharmonium
    Link
    I know this isn't in keeping with the spirit of a challenge, but like @onyxleopard, I'd like to share one of these I wrote several years ago, in Python 2.7. It was part of an RPG project which...

    I know this isn't in keeping with the spirit of a challenge, but like @onyxleopard, I'd like to share one of these I wrote several years ago, in Python 2.7. It was part of an RPG project which never took off, but this feels like a good chance to dust it off and set it free, for whatever it's worth.

    It satisfies the main criteria and the 3 bonuses, and can also handle exponentiation and implied multiplication with parentheses, like 2d4(3d2+9)^2. It will even work with eldritch dice expressions in which the number of dice, the number of sides the dice have, and the exponent are themselves determined by dice rolls: (2d4)d((3d20)^2d20).

    Finally, it can output the result as a number, or it can return a dictionary listing the results of each individual roll, and how the dice expression was tokenized:

    > python roll.py -v "2d20 + 1d6 + 3d10"
    {'diceRolls': [{'rolls': [11, 1], 'sides': 20, 'sum': 12},
                   {'rolls': [5], 'sides': 6, 'sum': 5},
                   {'rolls': [2, 6, 6], 'sides': 10, 'sum': 14}],
     'error': False,
     'errorCode': False,
     'origString': '2d20 + 1d6 + 3d10',
     'result': 31,
     'tokenized': [{'tokType': 'number', 'value': 2},
                   {'thisRoll': {'rolls': [11, 1], 'sides': 20, 'sum': 12},
                    'tokType': 'operator',
                    'value': 'd'},
                   {'rollResult': {'rolls': [11, 1], 'sides': 20, 'sum': 12},
                    'tokType': 'number',
                    'value': 20},
                   {'tokType': 'operator', 'value': '+'},
                   {'tokType': 'number', 'value': 1},
                   {'thisRoll': {'rolls': [5], 'sides': 6, 'sum': 5},
                    'tokType': 'operator',
                    'value': 'd'},
                   {'rollResult': {'rolls': [5], 'sides': 6, 'sum': 5},
                    'tokType': 'number',
                    'value': 6},
                   {'tokType': 'operator', 'value': '+'},
                   {'tokType': 'number', 'value': 3},
                   {'thisRoll': {'rolls': [2, 6, 6], 'sides': 10, 'sum': 14},
                    'tokType': 'operator',
                    'value': 'd'},
                   {'rollResult': {'rolls': [2, 6, 6], 'sides': 10, 'sum': 14},
                    'tokType': 'number',
                    'value': 10}]}
    

    The original idea was that this structure could be used by other libraries, or marshaled to JSON. The code theoretically uses a Top down operator precedence parser or Pratt parser, but I wasn't a CS major, so it probably shouldn't be used as an exemplar of the form. All the same, I hope this code might be useful to someone, or at least entertaining.

    4 votes
    1. onyxleopard
      Link Parent
      It’s fascinating to me that the influence of tabletop games (which I personally haven’t even played much) have led to a de facto standard for dice rolling notation. I like this, and esp. the JSON...

      It’s fascinating to me that the influence of tabletop games (which I personally haven’t even played much) have led to a de facto standard for dice rolling notation. I like this, and esp. the JSON schema you created, but it also seems a bit like overkill. I think tabular data would be preferable, as the only structured info that is required for the output is very amenable to simple columns. The detailed explanation of the parsing works better as JSON, but is probably only useful for debugging (not a bad motivation for implementing it, but I’ve been on a kick lately to try to force myself to think harder about how to do things as simply as possible).

      2 votes
  4. [2]
    Litmus2336
    Link
    Extremely fun challenge! This is actually a lot like making a very simple compiler, which is always very fun. I will take a look at completing it tonight :)

    Extremely fun challenge! This is actually a lot like making a very simple compiler, which is always very fun. I will take a look at completing it tonight :)

    3 votes
    1. Litmus2336
      Link Parent
      Some SML for functional action. Might implement RPN in a bit: Note: Somehow I'm screwing up the random function so it is currently deterministic (*Given a list of strings, evaluate each string*)...

      Some SML for functional action. Might implement RPN in a bit:

      Note: Somehow I'm screwing up the random function so it is currently deterministic

      (*Given a list of strings, evaluate each string*)
      fun recurseProcessTokens (token::[]) =  (resolveToken(token)) :: []
      | recurseProcessTokens (token::tail) = (resolveToken(token) :: recurseProcessTokens(tail))
      | recurseProcessTokens ([]) = []
      
      (*Evaluated dice rolls recursively*)
      and evalDiceRoll(0,diceFaces) = 0
      | evalDiceRoll(num,diceFaces) = (Random.randRange (1,diceFaces) (Random.rand(1,5)) + evalDiceRoll(num-1,diceFaces))
      
      (*Resolves tokens, either with enum mapping for operands, converting to int for digits
      or solving dice rolls*)
      and resolveToken("+") = ~1 | resolveToken("-") = ~2 | resolveToken("*") = ~3 | resolveToken("/") = ~4 
      | resolveToken(token) : int= 
          if(List.all (Char.isDigit) (explode token))
              then (valOf(Int.fromString token))
          else (*Is dicer oll*)
              let val tok = (String.tokens (fn #"d" => true | _ => false) token)
                  val num = (valOf(Int.fromString (hd tok)))
                  val diceFaces = (valOf(Int.fromString (hd (tl tok))))
                  val p = print((hd tok) ^ "d" ^ (hd (tl tok) ^ "=" ))
                  val rollVal = evalDiceRoll(num,diceFaces)
                  val p2 = print((Int.toString rollVal) ^ "\n")
              in
                  rollVal
              end
      ;
      (*Recursively solve the list of evaluated tokens*)
      fun evaluate ([]) = 0 
      | evaluate (last :: []) = last 
      | evaluate(arg1::(~1)::arg2 :: tail) = evaluate( (arg1 + arg2) :: tail)
      | evaluate(arg1::(~2)::arg2 :: tail) = evaluate( (arg1 - arg2) :: tail)
      | evaluate(arg1::(~3)::arg2 :: tail) = evaluate( (arg1 * arg2) :: tail)
      | evaluate(arg1::(~4)::arg2 :: tail) = evaluate( (arg1 div arg2) :: tail)
      | evaluate(arg1::el::arg2 :: tail) = ~1 (*Error*)
      ;
      (*Main*)
      let val input = "2d4 + 1d2 + 5"
          val strArray = (String.tokens (fn #" " => true | _ => false) input)
          val intArray = recurseProcessTokens(strArray)
      in
          evaluate(intArray)
      end
      
      3 votes
  5. onyxleopard
    (edited )
    Link
    This is a Python3 dice rolling simulator (I wrote a few years ago). #!/usr/bin/env python3 """Roll N M-sided dice from the command line. N: number of dice to roll (implicitly N=1 if omitted) M:...

    This is a Python3 dice rolling simulator (I wrote a few years ago).

    #!/usr/bin/env python3
    """Roll N M-sided dice from the command line.
        
            N: number of dice to roll (implicitly N=1 if omitted)
            M: the maximum value on the die (must be at least 1)
         e.g.:   'd6' -> roll one six-sided die
               '3d20' -> roll three twenty-sided dice
           'd2 d3 d4' -> roll a two-, three-, and four-sided die
    """
    
    import random
    import sys
    
    from argparse import ArgumentError
    
    def die(value):
        """Parse a die string of the form (N)dM int a tuple of integers where:
            N: an optional number of dice (implicitly N=1 if omitted)
            M: the number of sides on the die
        
        E.g.:
            die('d6') -> (1, 6)
            die('1d6') -> (1, 6)
            die('2d4') -> (2, 4)
        """
        try:
            n, m = value.lower().split('d', 1)
            return int(n or 1), int(m)
        except:
            raise ArgumentError('invalid die string')
    
    def roll(n, m):
        """Generate n random integers in the range [1..m]"""
        for i in range(n):
            yield random.randint(1, m)
    
    if __name__ == '__main__':
        import argparse
        parser = argparse.ArgumentParser(
            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
            description=__doc__
        )
        parser.add_argument(
            'dice',
            nargs='+',
            type=die,
            metavar='[N]dM',
            help='Roll N M-sided dice',
        )
        args = parser.parse_args()
        for n, m in args.dice:
            print('{}d{}\t{}'.format(n, m, sum(roll(n, m))))
    
    $ ./rolldice -h
    usage: rolldice [-h] [N]dM [[N]dM ...]
    
    Roll N M-sided dice from the command line. N: number of dice to roll
    (implicitly N=1 if omitted) M: the maximum value on the die (must be at least
    1) e.g.: 'd6' -> roll one six-sided die '3d20' -> roll three twenty-sided dice
    'd2 d3 d4' -> roll a two-, three-, and four-sided die
    
    positional arguments:
      [N]dM       Roll N M-sided dice
    
    optional arguments:
      -h, --help  show this help message and exit
    $ ./rolldice d6
    1d6	5
    $ ./rolldice 3d6
    3d6	11
    $ ./rolldice {1..6}d6
    1d6	3
    2d6	10
    3d6	10
    4d6	10
    5d6	14
    6d6	26
    $ ./rolldice {1..3}d{1..6}
    1d1	1
    1d2	2
    1d3	2
    1d4	1
    1d5	3
    1d6	4
    2d1	2
    2d2	4
    2d3	4
    2d4	5
    2d5	6
    2d6	12
    3d1	3
    3d2	6
    3d3	5
    3d4	9
    3d5	6
    3d6	13
    # oh, and for modifiers
    ./rolldice {1..3}d{1..6} | awk '{print $1 "     " $2 +3}'
    1d1	4
    1d2	4
    1d3	5
    1d4	5
    1d5	8
    1d6	9
    2d1	5
    2d2	7
    2d3	7
    2d4	8
    2d5	9
    2d6	11
    3d1	6
    3d2	6
    3d3	9
    3d4	14
    3d5	14
    3d6	14
    $ ./rolldice {1..3}d{1..6} | awk '{print $1 "     " $2 -6}'
    1d1	-5
    1d2	-5
    1d3	-4
    1d4	-4
    1d5	-3
    1d6	-1
    2d1	-4
    2d2	-3
    2d3	-1
    2d4	-1
    2d5	1
    2d6	-1
    3d1	-3
    3d2	-1
    3d3	0
    3d4	-1
    3d5	5
    3d6	7
    # oh and you need to sum everything? https://www.gnu.org/software/datamash/
    $ ./rolldice {1..3}d{1..6} | awk '{print $1 "     " $2 -6}' | datamash sum 2
    -37
    # oh you’re Mr. fancy-pants?
    ./rolldice {1..3}d{1..6} | awk '{print $1 "     " $2 -6}' | datamash --header-out min 2 max 2 mean 2 sstdev 2 sum 2 | csvlook -It
    | min(field-2) | max(field-2) | mean(field-2) | sstdev(field-2) | sum(field-2) |
    | ------------ | ------------ | ------------- | --------------- | ------------ |
    | -5           | 4            | -1.5          | 2.8952293417235 | -27          |
    
    3 votes
  6. tomf
    (edited )
    Link
    I can't code.. but I'm alright with Sheets. I didn't know what 2d20 - 5 meant, but a friend said its 2x 20 sided dice minus 5 -- so I went with that. =QUERY( TRANSPOSE( ARRAYFORMULA( RANDBETWEEN(...

    I can't code.. but I'm alright with Sheets. I didn't know what 2d20 - 5 meant, but a friend said its 2x 20 sided dice minus 5 -- so I went with that.

    =QUERY(
     TRANSPOSE(
      ARRAYFORMULA(
       RANDBETWEEN(
        ARRAYFORMULA(ROW(A2:A3)-ROW(A1:A2)),20))),
     "select Col1+0, Col2+0, Col1+Col2-5 
     label Col1+0 'Die 1', Col2+0 'Die 2', Col1+Col2-5 'Result'")
    

    edit: this is overkill

    =QUERY({RANDBETWEEN(1,20),RANDBETWEEN(1,20)},
     "select Col1+0, Col2+0, Col1+Col2-5 
     label Col1+0 'Die 1', Col2+0 'Die 2', Col1+Col2-5 'Result'")
    
    3 votes
  7. cge
    (edited )
    Link
    These solutions seem very nice and well-written, but they're all enormous: the smallest of the two Python versions is almost an entire kilobyte, without even implementing parentheses in the input....

    These solutions seem very nice and well-written, but they're all enormous: the smallest of the two Python versions is almost an entire kilobyte, without even implementing parentheses in the input.

    The problem here is trying to figure out the parsing. That seems like too much work for my style of approaching these challenges, so I'll just avoid doing it entirely:

    import re
    from random import randint as ri
    i=int;z=[]
    def f(x):z.append([ri(1,i(x[2])+1)for _ in range(0,i(max('1',x[1])))]);return str(sum(z[-1]))
    print(eval(re.sub(r"(\d*)d(\d+)",f,input())),z)
    

    By avoiding every challenging part of this challenge, this code is only 196 bytes. It accepts as input any valid Python expression, where XdY can replace numbers, and will return first the final value, then all the rolls involved.

    Thus:

    • d6+3 ==> 21 [[7]]
    • 4d6 * (5d4 - 3) ==> 312 [[5, 7, 7, 7], [2, 3, 5, 3, 2]]
    • 4d6**2d4 ==> 7776 [[2, 2, 1, 1], [4, 1]]
    • 4d8 > 15 ==> True [[7, 4, 9, 1]]

    Obviously, this allows arbitrary code execution. But in this case, that's arguably a feature. For example:

    • __import__("math").exp(-(5d8+3))
    • __import__("numpy").average([3d7,8d8])
    • ["Failure","Success"][4d8 > 15]
    • (Disclaimer: don't run this one. Adapted from these redditors.) (__import__("time").sleep(4d8),[o.fork() for (o,i) in [(__import__('os'), __import__('itertools'))] for x in i.count()])

    There are two known issues:

    • Zero should never be the leading digit in X for XdY (0d5 and 05d5 will act as 1d5).
    • XdY handling is done as preprocessing. Thus, eg, sum(4d8 for _ in range(0,1000)) will not roll 4000 d8s. This could be handled, but the challenge would be to keep track of each roll.
    3 votes
  8. Diff
    (edited )
    Link
    Woo that was a trip. Never done anything like this before, so this a pretty horrific and fragile way to handle it. I started out with the idea of using a tree to represent the math-y witchcraft,...

    Woo that was a trip. Never done anything like this before, so this a pretty horrific and fragile way to handle it.

    I started out with the idea of using a tree to represent the math-y witchcraft, which seemed like a good idea at the time. Had a super hard time actually turning a string into a tree structure though.

    First attempt worked pretty okay, but didn't implement any order of operations. So 2d5+5 was fine but 5 + 2d5 was run as (5+2)d5. Figured that out after I fed it 2d5000000 + 5d5. It just kept spitting out dice rolls lol.

    Second version creates an "unfinished" flat list of tokens then uses a new per-operator Priority to gradually fold it into a tree. Then once the tree is made you ask the root node for its value and the request cascades down the tree until a result appears.

    Output:

    $ ./dice2 "2d8+2"
    Rolling 2d8: ( 2 4 )
    Adding 6 and 2...
    And your total is 8.
    
    $ ./dice2 "5+5d5+5*5+5d5"
    Rolling 5d5: ( 1 1 5 1 2 )
    Adding 5 and 10...
    Multiplying 5 and 5...Adding 15 and 25...
    Rolling 5d5: ( 3 5 1 3 4 )
    Adding 40 and 16...
    And your total is 56.
    
    package main
    
    import (
    	"fmt"
    	"math/rand"
    	"os"
    	"time"
    )
    
    func main() {
    	rand.Seed(time.Now().UnixNano())
    	
    	if len(os.Args) < 2 {
    		usage()
    		return
    	}
    
    	var input string
    	for i := 1; i < len(os.Args); i++ {
    		input += os.Args[i]
    	}
    
    	defer func() {
    		if r := recover(); r != nil {
    			fmt.Println(r)
    			usage()
    		}
    	}()
    
    	parser := Parser{}
    	value := parser.Parse(input).GetValue()
    	fmt.Printf("And your total is %v.\n", value)
    }
    
    var Operations = map[rune]func(int, int) int{
    	'd': DiceRollOp,
    	'+': AdditionOp,
    	'-': SubtractionOp,
    	'*': MultiplicationOp,
    	'/': DivisionOp,
    }
    
    var Priorities = map[rune]int{
    	'd': 2,
    	'+': 0,
    	'-': 0,
    	'*': 1,
    	'/': 1,
    }
    
    func usage() {
    	fmt.Println("Usage: dice <expression>")
    	fmt.Println("Expressions are made up of alternating numbers and operators.")
    	fmt.Println()
    	fmt.Println("Supported operators are 'd', '+', and '-'.")
    	fmt.Println("XdY rolls a die with Y sides X times.")
    	fmt.Println("I'll leave it up to you to guess what + and - do.")
    }
    
    type Parser struct{}
    
    func (p *Parser) Parse(input string) MathThing {
    	var tokens TokenString
    	var currentNum int
    	for _, char := range input {
    		if char >= '0' && char <= '9' {
    			currentNum *= 10
    			currentNum += int(char - '0')
    			continue
    		}
    
    		tokens = append(tokens, Number{currentNum})
    		currentNum = 0
    
    		switch char {
    		case ' ':
    			continue
    
    		case 'd', '+', '-', '*', '/':
    			tokens = append(tokens, Operation{Op: Operations[char], Priority: Priorities[char]})
    
    		default:
    			panic(fmt.Sprintf("Unknown character %c", char))
    		}
    	}
    
    	tokens = append(tokens, Number{currentNum})
    
    	for i := tokens.GetNext(); i >= 0; i = tokens.GetNext() {
    		left := tokens[i-1]
    		right := tokens[i+1]
    
    		if !left.IsComplete() || !right.IsComplete() {
    			panic("Syntax error. Somewhere. Good luck. If it helps, looks like there's too many operators and not enough numbers. Could be some weird order of operations crap idk.")
    		}
    
    		this := tokens[i].Complete(left, right)
    
    		tokens = append(tokens[:i], tokens[i+2:]...) // Consume 2 tokens.
    		tokens[i-1] = this                           // Replace the third token with our new one.
    	}
    
    	if len(tokens) != 1 {
    		panic("There's some left over bits. Or not enough left over bits. Good luck.")
    	}
    
    	return tokens[0]
    }
    
    type TokenString []MathThing
    
    func (t TokenString) GetNext() int {
    	highestToken := -1
    	highestPriority := -1
    
    	for i, token := range t {
    		if !token.IsComplete() && token.GetPriority() > highestPriority {
    			highestToken = i
    			highestPriority = token.GetPriority()
    		}
    	}
    
    	return highestToken
    }
    
    // Is a thing that has a value.
    type MathThing interface {
    	GetValue() int
    	IsComplete() bool
    	Complete(MathThing, MathThing) MathThing
    	GetPriority() int
    }
    
    
    
    // Number is just a constant value.
    type Number struct {
    	Value int
    }
    
    func (n Number) GetValue() int {
    	return n.Value
    }
    
    func (n Number) IsComplete() bool {
    	return true
    }
    
    func (n Number) Complete(_, _ MathThing) MathThing {
    	return n
    }
    
    func (n Number) GetPriority() int {
    	return -1
    }
    
    
    
    // OpFunc is a function that does some math magic to 2 values.
    type OpFunc func(int, int) int
    
    // Operation has a value that depends on 2 other values and some unspecified black magic.
    type Operation struct {
    	Left     MathThing
    	Right    MathThing
    	Op       OpFunc
    	Priority int
    }
    
    func (o Operation) GetValue() int {
    	return o.Op(o.Left.GetValue(), o.Right.GetValue())
    }
    
    func (o Operation) IsComplete() bool {
    	/*if o.Left == nil || o.Right == nil {
    		return false
    	}
    	return o.Left.IsComplete() && o.Right.IsComplete()*/
    	return o.Left != nil && o.Right != nil
    }
    
    func (o Operation) Complete(Left, Right MathThing) MathThing {
    	o.Left = Left
    	o.Right = Right
    
    	return o
    }
    
    func (o Operation) GetPriority() int {
    	return o.Priority
    }
    
    
    
    func DiceRollOp(diceCount, diceSize int) int {
    	fmt.Printf("Rolling %vd%v: (", diceCount, diceSize)
    	var total int
    	var roll int
    	for i := 0; i < diceCount; i++ {
    		roll = rand.Intn(diceSize) + 1
    		total += roll
    		fmt.Printf(" %v", roll)
    	}
    	fmt.Println(" )")
    	return total
    }
    
    func AdditionOp(a, b int) int {
    	fmt.Printf("Adding %v and %v...\n", a, b)
    	return a + b
    }
    
    func SubtractionOp(a, b int) int {
    	fmt.Printf("Subtracting %v from %v...\n", b, a)
    	return a - b
    }
    
    func DivisionOp(a, b int) int {
    	fmt.Printf("Dividing %v by %v...\n", a, b)
    	return a / b
    }
    
    func MultiplicationOp(a, b int) int {
    	fmt.Printf("Multiplying %v and %v...\n", a, b)
    	return a * b
    }
    

    I think it'd be really cool to try and print out the finished tree structure but I could not figure out how to pull that off.

    3 votes
  9. gpl
    Link
    I am a little late to this since I was travelling, but I wanted to share something a little different. I present the Quantum Dice Roller. This was written in Python 3.7 using IBM's qiskit...

    I am a little late to this since I was travelling, but I wanted to share something a little different. I present the Quantum Dice Roller. This was written in Python 3.7 using IBM's qiskit platform. In order to generate randomness, this program places a single quantum bit (qubit) into a superposition of 0 and 1 with equal probability, and then measures this state in order to return 0 or 1 with perfectly equal probability. It uses these random 0s and 1s to build a random string of 0s and 1s which is interpreted as a binary number in order to get the desired random result. So, for example, if you are rolling a d4 it will find the number of bits needed to represent "4" and create a string of that length. The only catch here is that sometimes the result will be greater than the number of sides. For example, for a d4 the program will create a bit string of length 3, which in principle could be "111" which is greater than 4. Thus, it checks the final result and tries again until a valid result is obtained.

    In principle, this program could be run on one of IBM's actual quantum computers. As it is written, it uses a simulated quantum processor (and thus probably some standard PRNG). However all it would take is changing the specified backend and making an IBM account to change this. I might do so later just for the novelty of it. I'm happy to answer questions if anyone has them

    import math
    from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister, execute, Aer
    
    def parse(string):
        params = [] #will hold relevant data
        if "+" in string:
            dice_info, modifier = string.split("+")
            dice_info = dice_info.strip()
            modifier = int(modifier.strip())
            nrolls = int(dice_info.split("d")[0])
            nsides = int(dice_info.split("d")[1])
            params=[nrolls, nsides, "+", modifier]
        elif "-" in string:
            dice_info, modifier = string.split("-")
            dice_info = dice_info.strip()
            modifier = int(modifier.strip())
            nrolls = int(dice_info.split("d")[0])
            nsides = int(dice_info.split("d")[1])
            params=[nrolls, nsides, "-", modifier]
        else:
            nrolls = int(string.split("d")[0])
            nsides = int(string.split("d")[1])
            params=[nrolls, nsides]
    
        return params
    
    def quantum_roll(nbits, nsides):
    
        max_flag = True
        
        while(max_flag):
            my_backend = Aer.get_backend("qasm_simulator")
            bit_string = ''
            for i in range(nbits):
                q = QuantumRegister(1)
                c = ClassicalRegister(1)
                qc = QuantumCircuit(q, c)
                qc.h(q[0])
                qc.measure(q, c)
    
                job_sim = execute(qc, my_backend, shots=1)
                sim_result = job_sim.result()
                counts = sim_result.get_counts(qc)
                bit = list(counts.keys())[0]
    
                bit_string += bit
            
            result = int(bit_string, 2) + 1
            if (result<=nsides):
                max_flag=False
    
        return result 
    
    
    def main():
        user_in = input("Enter a dice roll in the form _d__ + _ (e.g. 3d4+2): ")
    
        roll_params = parse(user_in)
        nrolls=roll_params[0]
        nsides=roll_params[1]
        
        #[0] is nrolls, [1] is nsides [2] is modifier if any
    
        nbits = nsides.bit_length()
    
        rolls = [] 
        for roll in range(nrolls):
            rolls.append(quantum_roll(nbits, nsides))
            
        total = sum(rolls)
    
        if len(roll_params) > 2:
            if roll_params[2] == "+":
                total += roll_params[3]
            elif roll_params[2] == "-":
                total -= roll_params[3]
            else:
                print("Invalid modifier, ignoring")
    
        roll_str = ",".join([str(x) for x in rolls])
        out = "You rolled the quantum dice {0} times, with results of {1}. This gave a total of {2}".format(nrolls, roll_str, total)
    
        if len(roll_params) > 2:
            out = out+", after a modifier of {} was applied.".format(roll_params[2]+str(roll_params[3]))
        else:
            out = out+"."
    
        print(out)
    
    if __name__=="__main__":
        main()
    
    3 votes
  10. [11]
    krg
    Link
    Cool. Trying this in Rust. I'm not a super adept programmer, so I'm having a bit of trouble splitting the input string by regular expression. If I gather number of dice, number of sides, and...

    Cool. Trying this in Rust. I'm not a super adept programmer, so I'm having a bit of trouble splitting the input string by regular expression. If I gather number of dice, number of sides, and modifiers separately it's a fairly trivial task, but of course that's not in the spirit of the challenge.

    Also, does anyone know how to extract the last digit of a number? I'm trying to be clever and print proper ordinal numbers, but what I have right now only works for the first ten digits.

    match (i + 1) {
    1 => println!("{}st roll: {}", (i + 1), rolls[i]),
    2 => println!("{}nd roll: {}", (i + 1), rolls[i]),
    3 => println!("{}rd roll: {}", (i + 1), rolls[i]), 
    _ => println!("{}th roll: {}", (i + 1), rolls[i]),
    }
    

    Also, there's a lot of repetition, so I imagine it can get even more clever.

    2 votes
    1. [8]
      mftrhu
      Link Parent
      What are you trying to do? Print the rolls as 1st, 2nd, 3rd, 40th? If so, use the remainder operator, but you might want to keep in mind the rules for forming ordinals in English: So you could...

      Also, does anyone know how to extract the last digit of a number? I'm trying to be clever and print proper ordinal numbers, but what I have right now only works for the first ten digits.

      What are you trying to do? Print the rolls as 1st, 2nd, 3rd, 40th?

      If so, use the remainder operator, but you might want to keep in mind the rules for forming ordinals in English:

      • If the tens digit of a number is 1, then "th" is written after the number. For example: 13th, 19th, 112th, 9,311th.
      • If the tens digit is not equal to 1, then the following table could be used:
      If the units digit is:01234-9
      This is written after the number:thstndrdth
      For example: 2nd, 7th, 20th, 23rd, 52nd, 135th, 301st.

      So you could write something like

      let tens = (i + 1) % 100 / 10;
      let units = (i + 1) % 10;
      if (tens == 1) { return "th"; }
      return match (units) {
          1 => "st",
          2 => "nd",
          3 => "rd",
          _ => "th"
      }
      
      3 votes
      1. [6]
        krg
        Link Parent
        Okay, so this also seems to work: match if (i + 1) < 20 {(i + 1)} else {(i + 1) % 10} { 1 => println!("{}st roll: {}", (i + 1), rolls[i]), 2 => println!("{}nd roll: {}", (i + 1), rolls[i]), 3 =>...

        Okay, so this also seems to work:

        	match if (i + 1) < 20 {(i + 1)} else {(i + 1) % 10} {
        		1 => println!("{}st roll: {}", (i + 1), rolls[i]),
        		2 => println!("{}nd roll: {}", (i + 1), rolls[i]),
        		3 => println!("{}rd roll: {}", (i + 1), rolls[i]),
        		11 ... 20 | _ => println!("{}th roll: {}", (i + 1), rolls[i]),
        	}
        

        but maybe it can be cleaner. I guess I can separate it into its own function.

        4 votes
        1. [5]
          mftrhu
          Link Parent
          match if (i + 1) < 20 {(i + 1)} else {(i + 1) % 10} { [...] 11 ... 20 | _ => println!("{}th roll: {}", (i + 1), rolls[i]), Oh, this is clever. You are matching the result of an if..else...
          match if (i + 1) < 20 {(i + 1)} else {(i + 1) % 10} {
          [...]
              11 ... 20 | _ => println!("{}th roll: {}", (i + 1), rolls[i]),
          

          Oh, this is clever. You are matching the result of an if..else expression, yes? On both i + 1 % 10 and i + 1, that is? I didn't know that Rust could do that.

          Still, I would definitely move it to its own function, which would also avoid the whole "having to repeat i + 1 seven times" thing.

          4 votes
          1. [4]
            krg
            (edited )
            Link Parent
            Yea, I wasn't quite sure if it'd evaluate, either..until I tried it! This is the function I ended up with: fn ordinal_append(n: u32) -> String { match if (n % 100) < 20 { n % 100 } else { n % 10 }...

            Yea, I wasn't quite sure if it'd evaluate, either..until I tried it!

            This is the function I ended up with:

            fn ordinal_append(n: u32) -> String {
            	match if (n % 100) < 20 { n % 100 } else { n % 10 } {
            		1 => "st",
            		2 => "nd",
            		3 => "rd",
            		11 ... 20 | _ => "th",
            	}.to_string()
            }
            

            The only pain being that Rust is strongly typed and anything that gets thrown into the function has to be cast as a u32. I initiated the dice values as u8, since that seems to be enough for dice and maybe I save a few bytes of memory?

            Edit: Ooo, also didn't consider anything past 100, so 111 shows as "111st" instead of "111th." Well, better fix that...

            EditEdit: Okay, fixed. I think.

            2 votes
            1. [3]
              mftrhu
              (edited )
              Link Parent
              Unless you are on a microcontroller or another such system where memory is measured in kBs, don't bother. Really. Hell, I'd go for u64 - you lose quite a bit by going down to u8 and, more...

              I initiated the dice values as u8, since that seems to be enough for dice and maybe I save a few bytes of memory?

              Unless you are on a microcontroller or another such system where memory is measured in kBs, don't bother. Really. Hell, I'd go for u64 - you lose quite a bit by going down to u8 and, more importantly, you are not really gaining anything. Unless you are dealing with an array of thousands of such elements, the 3 bytes you are gaining from that (and from other such premature optimizations) are not going to be significant.

              I have been doing a few DMOJ problems each day for a while, and I picked Pascal back up because I wanted to tackle one such problem with a fairly restrictive memory limit (I hit the time limit instead). I have been looking at the best solutions leaderboards for a bit, and - Pascal often uses as little as 4 kB, while Python 3 is usually around 8.9 MB. Python 2, 5.5 MB or so. C, around one MB (890 kB+ even for things as trivial as Hello, World!). C++, ~1.5 MB.

              Rust seems to use 2x to 1.1x more memory than C - 1.0 MB to 1.9 MB for Hello, World!, and any gains measured in bytes are going to be absolutely swamped by whatever it does setting up its environment.

              Edit: apparently DMOJ doesn't let you link to a particular submission, you need to be logged in and to have solved that problem to see it. In any case, here's the best submissions for Hello, World!, just to get an idea of how much base memory your program will end up using in different programming languages.

              3 votes
              1. [2]
                krg
                Link Parent
                Haha, yea. Figured it didn't really matter on any modern system. For some reason it just feels more satisfying using as small a variable as possible. I also want to be exposed to type errors so I...

                Haha, yea. Figured it didn't really matter on any modern system. For some reason it just feels more satisfying using as small a variable as possible. I also want to be exposed to type errors so I can figure out how to fix em. For example, ordinal_append() initially didn't have .to_string() attached, which gave me a type error.

                DMOJ looks like a cool spot for problems. Gotta practice my Rust syntax some more...

                2 votes
                1. mftrhu
                  Link Parent
                  Check also out Project Euler (heavy on mathematics) and Rosalind (bio-informatics, heavy on string manipulation) while you are at it.

                  DMOJ looks like a cool spot for problems. Gotta practice my Rust syntax some more...

                  Check also out Project Euler (heavy on mathematics) and Rosalind (bio-informatics, heavy on string manipulation) while you are at it.

                  2 votes
      2. krg
        Link Parent
        Ah, yea. Didn't consider that case. And I figured I'd use the remainder operator, but I must've forgot how it worked. Ha, that was going to be my go to solution but talked myself out of it.

        Ah, yea. Didn't consider that case. And I figured I'd use the remainder operator, but I must've forgot how it worked. Ha, that was going to be my go to solution but talked myself out of it.

        2 votes
    2. [2]
      Litmus2336
      Link Parent
      Hm, why do you need the last digit of a number? What I recommend is to split around the "d", separating it into the before the d (number of dice) and after the d (faces on each die).

      Hm, why do you need the last digit of a number? What I recommend is to split around the "d", separating it into the before the d (number of dice) and after the d (faces on each die).

      1 vote
      1. krg
        Link Parent
        Purely for nice looking ordinal printing. E.g., "1st roll, 2nd roll, 3rd roll" etc. My solution handles up to "10th", but adds "th" to each subsequent number. If I checked only the last digit I...

        Purely for nice looking ordinal printing. E.g., "1st roll, 2nd roll, 3rd roll" etc. My solution handles up to "10th", but adds "th" to each subsequent number. If I checked only the last digit I believe I'd handle every case. Just an aesthetics thing, not really part of thtw challenge, but a nice little challenge in itself. :)

        I looked up the documentation and some examples on Rust's regular expressions and I understand where I should split, but the syntax is a little confusing to me.

        1 vote
  11. 0d_billie
    Link
    I'm very much not a programmer, just someone with an interest in coding. Here's my attempt in python, which probably breaks an awful lot of conventions and rules: import re,random,argparse parser...

    I'm very much not a programmer, just someone with an interest in coding. Here's my attempt in python, which probably breaks an awful lot of conventions and rules:

    import re,random,argparse
    
    parser = argparse.ArgumentParser(description="Roll dice with or without additional operations")
    parser.add_argument("to_roll", metavar="to_roll", type=str, help="A string in the format 4d6+1")
    args = parser.parse_args()
    
    to_roll = args.to_roll
    input = re.match(r'(\d*)d(\d+)(([+-/\*])(\d+))?', to_roll)
    
    if input:
        if input.group(1):
            dice = int(input.group(1))
        else:
            dice = 1
        faces = int(input.group(2))
        if input.group(3):
            mod_op = input.group(4)
            mod_num = int(input.group(5))
    else:
        print("Enter calculation in the correct format ""4d6+1""")
    
    def dice_roll():
        roll_result = []
        for d in range(dice):
            roll = random.randint(1,faces)
            roll_result.append(roll)
    
        roll_total = 0
        for d in roll_result:
            roll_total+=d
    
        if input.group(3):
            if mod_op == "+":
                roll_total+=mod_num
            if mod_op == "-":
                roll_total+-mod_num
            if mod_op == "/":
                roll_total//=mod_num
            if mod_op == "*":
                roll_total*=mod_num
    
        print("Rolled:",*roll_result)
        print("Total:",roll_total)
    
    dice_roll()