Roll Dungeons and Dragons dice

20

7

I want to play Dungeons and Dragons, but I don't have any dice! Your challenge is to roll some D&D dice.

The input format specification in Backus-Naur form is:

<valid-input>  ::= <opt-integer> "d" <integer> <opt-modifier>
<opt-integer>  ::= | <integer>
<opt-modifier> ::= | "+" <integer>
<integer>      ::= "0" | "1" | "2" | "3" | "4" | "5" |
                   "6" | "7" | "8" | "9" | <integer> <integer>

The optional integer before the d is the number of dice to roll; it must be at least 1, and defaults to 1 if not supplied.

The required integer immediately after the d is the number of sides each die has; it must be at least 1. The sides of each die are distinct consecutive positive integers starting at 1.

The optional modifier may be +0, and it defaults to +0 if not specified.

For example, for the input 2d10+5, you generate two random numbers from 1 to 10 inclusive, add them together, and add 5. Then you will output the result.

If you receive invalid input, such as 2d, d20+, 0d4, 2d5+1+2, 2+2, or anything else that doesn't fit this format, you must output "Invalid input". Otherwise, you must output only a single random integer, weighted according to the input. For example, 3d6 should produce more 10s than 4s.

Test cases

Input      Minimum possible output    Maximum possible output
d1         1                          1
d6         1                          6
d1+3       4                          4
d20+3      4                          23
2d1        2                          2
2d6+2      4                          14
d01        1                          1
d01+0      1                          1
01d01+01   2                          2
3d20+10    13                         70

d          Invalid input
d0         Invalid input
d+0        Invalid input
d0+0       Invalid input
0d1        Invalid input
0d1+1      Invalid input
d1+        Invalid input
1d         Invalid input
1d1+       Invalid input
1d+1       Invalid input
2d+2d      Invalid input
d2+d2      Invalid input
d2+2+2     Invalid input
d2-1       Invalid input
-d2        Invalid input
-2d2       Invalid input
4*3        Invalid input
4*d2       Invalid input

This is , so the shortest code in bytes will win!

Doorknob

Posted 2014-04-07T02:31:33.930

Reputation: 68 138

Is 2d10-5 a valid input? -- Oops, I missed the part where you said p is 0 or more. Nevermind. – undergroundmonorail – 2014-04-07T02:37:21.327

1Is 02d05+073 a valid input? – MT0 – 2014-04-07T07:23:27.437

Can you throw a 13 sided die? Or a 17 sided die? – Averroes – 2014-04-07T08:29:35.613

2The hard part about this question is validating the input, but the paragraph which describes the validation rules is self-contradictory. It describes n and p as optional, but input which choose not to include them (d20+) as invalid. – Peter Taylor – 2014-04-07T09:13:50.680

1@PeterTaylor: I think the + sign should only be added if the modifier p is provided. – ProgramFOX – 2014-04-07T09:39:00.037

1@MT0 Yes; 02 is a number >= 1. – Doorknob – 2014-04-07T12:11:31.467

@Averroes Of course, why not? – Doorknob – 2014-04-07T12:11:59.873

@PeterTaylor Edited post to clarify ("...and preceded by a + sign.") – Doorknob – 2014-04-07T12:12:58.947

4@Doorknob, Well, because d13 and d17 aren't dice used in D&D. D&D uses d4, d6, d8, d10, d12, and d20. Also, there are certainly cases where a roll would include different types of dice (eg, 1d4+1d6 for a Rogue sneak attacking with a dagger) or having a negative p (eg, 1d20-1 for a skill check with no ranks/training and a negative ability modifier). – Brian S – 2014-04-07T15:48:46.337

@BrianS well you also sometimes see the odd d3 which is done using a normal die and dividing by 2 (rounding up). I'm not entirely sure but I may have seen d2 as well which is just flipping a coin (or using a similar method as above). But yes d13 and d17 are a bit unusual. – Martin Ender – 2014-04-07T16:23:15.120

@m.buettner, I think 3.5/PF might use d2/d3 for whips, but the standard is to simulate those with d4/d6 (particularly when considering a "standard set" available at hobby shops). 4e doesn't have dice below d4, although it does have things like Weakened condition/half damage. – Brian S – 2014-04-07T16:28:09.103

2You're going to play dnd without the usecase of 2d8 + 1d6 + 4? You're gonna have a bad time – corsiKa – 2014-04-07T20:19:02.743

12d6+2 4 12 is wrong; max should be 14. – Cees Timmerman – 2014-04-08T09:56:24.423

@CeesTimmerman, concur. Fixed. – Peter Taylor – 2014-04-08T10:53:54.880

Answers

12

Perl, 109 95 93 96 89 bytes

s/^d/1d/;/^(\d+)d(\d+)(\+\d+)?$/;$d+=1+rand$2|0for
1..$1;$_=$1*$2?$d+$3:'Invalid input'

Requires the -p switch, which accounts for two of the bytes. Try it online on Ideone.

How it works

  • Because of the -p switch, a line is read from STDIN and stored in $_.

  • The command s/^d/1d/ prepends a 1 to $_ if it begins with a d, i. e., if the number of dice has not been specified.

  • The regular expression /^(\d+)d(\d+)(\+\d+)?/ checks if the line consists of a number, a literal d, another number and, optionally, a third number preceded by a + sign.

    If there is a match, the numbers will be saved in $1, $2 and $3.

    In this case, the input will be valid if and only if $1 and $2 are both positive.

  • $d += 1 + rand $2 | 0 adds a pseudo-randomly chosen integer from 1 to the specified number of sides to $d (initially treated as zero).

  • for 1 .. $1 does the above once for every integer between 1 and the number of dice.

  • The command $_ = $1 * $2 ? $d + $3 : 'Invalid input' does the following:

    • If $1 * $2 is zero, it sets $_ to Invalid input.

    • Otherwise, the input is valid and it sets $_ to the sum of the dice rolls and the modifier.

  • Because of the -p switch, Perl prints the contents of $_.

  • Since there a no further input lines, the script exits.

Dennis

Posted 2014-04-07T02:31:33.930

Reputation: 196 637

I assume you haven't tested this. |0 is not the same as ||0. – Vynce – 2015-12-07T20:19:59.850

1@Vynce I assume you haven't either. I use |0 to cast to int, since rand returns a pseudo-randomly chosen float. – Dennis – 2015-12-07T20:23:19.853

Actually, you're right -- but for the wrong reason. ( : I noticed that rand gave floats, and was trying to test the program, and was wondering how you were getting around it; i'm trying to run it and getting lots of syntax errors on the command line.

ETA: ah. I'm used to perl golfing a -e and this has to be actually a file to work as written – Vynce – 2015-12-07T20:24:01.550

1

@Vynce I've added a permalink to the question (http://ideone.com/gLJfhO). -e would be problematic here, unless you replace the single quotes with double quotes.

– Dennis – 2015-12-07T20:28:07.300

1I believe that, in general, extra command line parameters are considered to be worth a byte each but the hyphen is free. In this case, -p would only cost you one, making this a 108 byte solution. – undergroundmonorail – 2014-04-07T05:59:58.713

You can use 1..$1||1 instead of 1..$1?$1:1, 2 chars shorter – Hasturkun – 2014-04-07T07:25:47.143

2Can be made 96 chars, /^([1-9]\d*)?d([1-9]\d*)(\+\d+)?$/||die"Invalid input$/";$a+=1+int rand$2for(1..$1||1);$_=$a+$3 – Hasturkun – 2014-04-07T09:02:59.340

1@undergroundmonorail: I've seen people counting a single command-line switch as one, two and even three (counting the whitespace) bytes. I'd rather count it as one, but two bytes seems fair to me. – Dennis – 2014-04-07T13:41:34.433

@Hasturkun: Thanks. Parentheses and curly brackets... Not sure what I was thinking. This is Perl! – Dennis – 2014-04-07T13:43:46.273

Your count is too high: the current program is only 87 chars. I assume you've counted a Windows newline and an EOF. – Peter Taylor – 2014-04-08T08:18:44.450

@PeterTaylor: I've counted the -p switch as two bytes. – Dennis – 2014-04-08T13:54:10.747

4

Ruby, 116

Alternative Ruby version. I was trying to find a way to do it without the regular expressions, but the validation you have to do is a lot harder without them.

gets=~/^(\d+)?d(\d+)(\+\d+)?$/
a=$1||?1
puts$~&&a>?0?eval("r=#{$3||0};#{a}.times{r+=rand(#$2)+1};r"):'Invalid input'

This one is 112, using Dennis' clever Perl algorithm:

$p='(\d*[1-9]\d*)'
puts~/^#$p?d#$p(\+\d+)?$/?eval("r=#{$3||0};#{$1||1}.times{r+=rand(#$2)+1};r"):'Invalid input'

Paul Prestidge

Posted 2014-04-07T02:31:33.930

Reputation: 2 390

@m.buettner Thanks! I don't know why I thought it had to be >0. – Paul Prestidge – 2014-04-07T22:45:13.500

4

Fortran: 145

character(1)a;read(*,*)s,a,j,a,k;n=0;if(k<0.or.a=="-")then;print*,"error k<0";stop;endif;do l=1,int(s);n=n+int(s*rand(0)+1);enddo;print*,n+k;end;

Abuses implicit typing (i-n are all integers, everything else a real). Minor caveat: input must be space separated, so 2d10+5 must be entered as 2 d 10 + 5, otherwise you'll get an input conversion error.

Kyle Kanos

Posted 2014-04-07T02:31:33.930

Reputation: 4 270

3

Javascipt, 158

m=prompt().match(/^([1-9]\d*)?d([1-9]\d*)(\+\d+)?$/);if(!m)alert("Invalid input");else{for(s=+m[3]|0,i=0;i<(+m[1]||1);i++)s+=Math.random()*+m[2]+1|0;alert(s)}

Can't golf better than this. It's time to get back to work.

Snack

Posted 2014-04-07T02:31:33.930

Reputation: 2 142

1s="Invalid input";if(m=prompt().match(/^([1-9]\d*)?d([1-9]\d*)(\+\d+)?$/))for(s=m[3]|0,i=0;i<(m[1]||1);i++)s+=Math.random()*m[2]+1|0;alert(s) has only 137 bytes. – Dennis – 2014-04-07T14:28:24.337

2According to the comments on the question, this is an incorrect answer because it rejects the input 02d05+073. – Peter Taylor – 2014-04-07T15:57:27.587

3

GolfScript (120 106 bytes)

.100?!1`*\+.43?)!'+0'*+.10,'d+':^*-!*.10,''*-^=*^1/{/n*}/~].,3=*3,or:x~;*{x~\{rand)+}+@*}'Invalid input'if

This is not only shorter than the first version, but also more elegant. The part which actually does the die rolling is

\{rand)+}+@*

The rest is mainly input validation, and a few characters for parsing.

# Start by converting valid inputs into valid inputs with all optional bits.
# Prepend a '1' if the string starts with 'd'.
.100?!1`*\+
# Append '+0' if there's no '+' in the string.
.43?)!'+0'*+
# Now we start knocking out the invalid inputs.
# If it contains a character other than [0-9d+], replace the string with ''.
.10,'d+':^*-!*
# If it doesn't contain exactly one 'd', exactly one '+', and the 'd' before the '+',
# replace the string with ''.
.10,''*-^=*
# Now we either have a valid string, an empty string, or a string which is almost valid
# but has some empty substrings which should be integers, or a forbidden 0 integer value.
# Replace the 'd' and '+' with newlines, eval the result, and gather into an array.
^1/{/n*}/~]
# If we had any empty parts, we'll have fewer than 3 items on the stack.
# In that case, replace with integer values which will fail the final validation step.
.,3=*3,or
# Final validation: number of dice * number of sides per die != 0.
:x~;*
# If we pass, do the actual die rolling. Otherwise give the error message.
{x~\{rand)+}+@*}'Invalid input'if

Online demo with test framework

Peter Taylor

Posted 2014-04-07T02:31:33.930

Reputation: 41 901

I'm wondering why you don't use n./? Maybe also 10,n* for one character less. – Howard – 2014-04-07T17:35:01.313

@Howard, to the first, because it was a last minute hack to pass some test cases and I didn't think about golfing it. To the second, that would make it accept some invalid input. – Peter Taylor – 2014-04-07T17:55:18.563

2

J - 130 (45?) char

This challenge seems to be a little biased towards regular expressions, especially with having to differentiate invalid input. J has a POSIX regex library, so it's not that bad, but it's not integrated as it is with Perl, so J fares no better than other languages.

+/@,`(1+?@#~)/`('Invalid input'"_)@.(0 e.$)0 1 1>.".>|.}.((,'?d','(\+[0-9]+)?$',~}.)'^([0-9]*[1-9][0-9]*)')(rxmatch rxfrom])1!:1]1

If you're just implementing the logic for valid expressions, like the Python/PHP solutions appear to, it's the more reasonable 45 chars:

+/,(1+[:?@#/1>.".;._2@,&'d')`".;._1'+',1!:1]1

Notable bits:

  • 1!:1]1 is the input, and (rxmatch rxfrom]) is the logic that returns the subexpression matches.

  • Whether or not the input was legal is handled by the regex matching, so we can set the defaults for n and p with 0 1 1>.. It looks backwards (n is 1 by default and p is 0) because we had to reverse (|.) the list earlier, so that the logic at the end executes in the right order.

  • @. is the Agenda conjunction, essentially a J-ish switch statement. If the matches are empty (if 0 is an e.lement of the $hape: 0 e.$), we emit the error message, else we go through with rolling the dice: #~ to set out the dice, 1+? to roll, and +/@, to add the modifier p and sum.

algorithmshark

Posted 2014-04-07T02:31:33.930

Reputation: 8 144

Does that work for 01d01+01? – Cees Timmerman – 2014-04-08T12:24:54.957

@CeesTimmerman My bad. It does now. – algorithmshark – 2014-04-08T18:40:23.583

2

TinyMUSH, 239

@dig/t +
@op d=+
@lo d=d
@fail d=Invalid input
@cr .
@set .=com
&d .=$*:\ifelse(regmatch(%0,^(\\\\d+)?d(\\\\d+)(\\\\+\\\\d+)?$,0 1 2 3),ifzero(and(or(not(strlen(%q1)),%q1),%q2),Invalid input,add(die(usetrue(%q1,1),%q2),%q3)),Invalid input)

The first four lines deal with the fact that "d" is an alias for the universal "down" exit with a built-in failure message when it doesn't exist; exits are scanned before user-defined commands. The remaining lines create an object with a user-defined command making use of the built-in die() function.

Muqo

Posted 2014-04-07T02:31:33.930

Reputation: 499

2

PHP, 129

<?eval(preg_filter(~Сף›ÔÖÀ›×£›Ö×£Ô£›ÔÖÀÛÐ,~ÛžÂÝÛÎÝÀÅÎÄ™×ÄÛ–ÔÔÃÛžÄیԞ‘›×ÎÓÛÍÖÖÄšœ—ÛŒÛÌÄ,$_GET[0])?:~šœ—ݶ‘‰ž“–›ß–‘Š‹ÝÄ);

Uses a regex to create a expression which PHP then evaluates. Input is fed in via url: ?0=argument. Make sure you urlencode the + to %2b. Here's what it looks like in a more readable form:

eval(preg_filter('/^(\\d)?d(\\d)(\\+\\d)?$/','$a="$1"?:1;for(;$i++<$a;$s+=rand(1,$2));echo$s$3;',$_GET[0])?:'echo"Invalid input";');

Bitwise inverting the strings using ~ not only saves a character because you don't need quotes (PHP assumes they are strings) but also saves characters because you don't have to escape the backslashes in the regular expression.

The ?: operator is a special form of the ternary operator. $foo = $a ? $a : $b is the same as $foo = $a ?: $b.

Tryth

Posted 2014-04-07T02:31:33.930

Reputation: 750

1

Python 3, 184 bytes

import random,re
try:a,b,c=re.findall("^(\d*)d(\d+)(\+\d+)?$",input())[0];t=int(c or 0)+(sum(random.randint(1,int(b))for i in range(int(a or 1)))or q)
except:t="Invalid input"
print(t)

Passes all tests. If zero dice were allowed, it would be 6 bytes shorter by leaving out (or q).

Cees Timmerman

Posted 2014-04-07T02:31:33.930

Reputation: 625

I misunderstood the BNF, though. This page helps.

– Cees Timmerman – 2014-04-08T10:37:26.413

For the benefit of anyone else who wonders why the regex is anchored at one end but not the other: Python's re.match implicitly anchors at the start but not at the end. I'm not aware of any other regex library which does that. – Peter Taylor – 2014-04-08T10:56:54.187

1There's a small saving by initialising t=int(c or 0); and it might be possible to combine your answer with the existing Python one (which uses less whitespace) to save a couple more. – Peter Taylor – 2014-04-08T11:03:11.310

1

Java, 378

Just wanted to try a solution with Java far from the best solution. But hey: Java isn't a golfing language in any case!

It gets the input from command line. First parameter args[0] is the input value.

class A{public static void main(String[]s){System.out.print(s[0].matches(
"(0+\\d+|[1-9]\\d*|)d(0+\\d+|[1-9]\\d*)(\\+\\d+)?")?z(s[0]):"Invalid input");}static int
z(String s){String[]a=s.split("d");String[]b=a[1].split("\\+");int c=a[0].isEmpty()?1:Byte.
decode(a[0]);int d=b.length<2?0:Byte.decode(b[1]);while(c-->0)d+=new java.util.Random().
nextInt(Byte.decode(b[0]))+1;return d;}}

Did you know, that decode is shorter than valueOf?

bobbel

Posted 2014-04-07T02:31:33.930

Reputation: 1 037

0

Ruby, 167 147

/^(\d+)?d(\d+)(\+\d+)?$/.match gets
abort'Invalid input'if !$~||$1==?0||!$2||$2==?0
p eval(([0]*($1||1).to_i).map{rand($2.to_i)+1}*?+)+($3||0).to_i

Uses a regexp to do all the work. Since I'm using \d+, the only things I need to check for invalid input are that there was a match, that neither n nor m were 0, and that there was an m. If any of those are found, it aborts with a message ('Invalid input'). Then it just prints the result, since it would have aborted by now if the input was invalid.

The result-printing isn't that interesting, but...

([0]*($1||1).to_i)    # create an array of n elements (1 if there is no n)
.map{rand($2.to_i)+1} # fill it up with random numbers, where the number x is 1 < x < m+1
.inject(:+)           # add them all up
+($3||0).to_i         # and finally add the modifier (0 if there is none)

I later changed .inject(:+) to eval(...*?+), but the idea is the same.

Doorknob

Posted 2014-04-07T02:31:33.930

Reputation: 68 138

0

JavaScript 134

m=prompt().match(/^((?!0)\d*)d((?!0)\d+)(\+\d+)?$/);alert(m?eval('for(o=m[3]|0,i=m[1]||1;i--;)o+=m[2]*Math.random()+1|0'):'Invalid input')

Michael M.

Posted 2014-04-07T02:31:33.930

Reputation: 12 173

This is so similar to Snack's answer

– user12205 – 2014-04-07T10:09:56.617

Well there are similarities, this is the same language/algorithm... But I thought there is enough differences in my code (and the regex) to post a different answer. – Michael M. – 2014-04-07T11:33:50.777

According to the comments on the question, this is an incorrect answer because it rejects the input 02d05+073. – Peter Taylor – 2014-04-07T15:58:03.843

0

Python3, 204B

Mine beats the existing Python answer by adding in the required error handling and reading d20 as 1d20 rather than 0d20 :)

import random,re
try:a,b,c=re.findall('^([1-9]\d*)?d(\d+)(\+\d+)?$',input())[0];I=int;R=sum(random.randrange(I(b))+1for x in[0]*(1if a==''else I(a)))+(0if c==''else I(c))
except:R='Invalid input'
print(R)

edited to fix 2 typos: I(x) => I(c), Invalid Input => Invalid input

edited to fix regex: \+?(\d*) => (\+\d+)?

alexander-brett

Posted 2014-04-07T02:31:33.930

Reputation: 1 485

According to the clarified question, this is an incorrect answer because it accepts the input 3d20+. – Peter Taylor – 2014-04-07T16:00:23.217

Good point! #filler – alexander-brett – 2014-04-07T16:10:48.307

And not 01d01+01. – Cees Timmerman – 2014-04-08T12:13:32.337