Take the 2-minute tour ×
Programming Puzzles & Code Golf Stack Exchange is a question and answer site for programming puzzle enthusiasts and code golfers. It's 100% free, no registration required.

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!

share|improve this question
1  
Is 02d05+073 a valid input? –  MT0 Apr 7 at 7:23
1  
The 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 Apr 7 at 9:13
1  
@PeterTaylor: I think the + sign should only be added if the modifier p is provided. –  ProgramFOX Apr 7 at 9:39
1  
@MT0 Yes; 02 is a number >= 1. –  Doorknob Apr 7 at 12:11
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 Apr 7 at 15:48

13 Answers 13

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.

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.

share|improve this answer
    
I 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 Apr 7 at 5:59
    
You can use 1..$1||1 instead of 1..$1?$1:1, 2 chars shorter –  Hasturkun Apr 7 at 7:25
2  
Can 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 Apr 7 at 9:02
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. –  sudo Apr 7 at 13:41
    
@Hasturkun: Thanks. Parentheses and curly brackets... Not sure what I was thinking. This is Perl! –  sudo Apr 7 at 13:43

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'
share|improve this answer
    
@m.buettner Thanks! I don't know why I thought it had to be >0. –  Chron Apr 7 at 22:45

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.

share|improve this answer
1  
s="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. –  sudo Apr 7 at 14:28
2  
According to the comments on the question, this is an incorrect answer because it rejects the input 02d05+073. –  Peter Taylor Apr 7 at 15:57

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.

share|improve this answer

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

share|improve this answer
    
I'm wondering why you don't use n./? Maybe also 10,n* for one character less. –  Howard Apr 7 at 17:35
    
@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 Apr 7 at 17:55

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.

share|improve this answer
    
Does that work for 01d01+01? –  Cees Timmerman Apr 8 at 12:24
    
@CeesTimmerman My bad. It does now. –  algorithmshark Apr 8 at 18:40

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.

share|improve this answer

Python 3, 185 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.

share|improve this answer
    
I misunderstood the BNF, though. This page helps. –  Cees Timmerman Apr 8 at 10:37
    
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 Apr 8 at 10:56
1  
There'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 Apr 8 at 11:03

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.

share|improve this answer

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')
share|improve this answer
    
This is so similar to Snack's answer –  ace Apr 7 at 10:09
    
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. –  Mig Apr 7 at 11:33
    
According to the comments on the question, this is an incorrect answer because it rejects the input 02d05+073. –  Peter Taylor Apr 7 at 15:58

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.

share|improve this answer

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+)?

share|improve this answer
    
According to the clarified question, this is an incorrect answer because it accepts the input 3d20+. –  Peter Taylor Apr 7 at 16:00
    
Good point! #filler –  ali0sha Apr 7 at 16:10
    
And not 01d01+01. –  Cees Timmerman Apr 8 at 12:13

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?

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.