20
\$\begingroup\$

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!

\$\endgroup\$
15
  • 1
    \$\begingroup\$ Is 02d05+073 a valid input? \$\endgroup\$
    – MT0
    Apr 7, 2014 at 7:23
  • 2
    \$\begingroup\$ 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. \$\endgroup\$ Apr 7, 2014 at 9:13
  • 1
    \$\begingroup\$ @PeterTaylor: I think the + sign should only be added if the modifier p is provided. \$\endgroup\$
    – ProgramFOX
    Apr 7, 2014 at 9:39
  • 4
    \$\begingroup\$ @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). \$\endgroup\$
    – Brian S
    Apr 7, 2014 at 15:48
  • 2
    \$\begingroup\$ You're going to play dnd without the usecase of 2d8 + 1d6 + 4? You're gonna have a bad time \$\endgroup\$
    – corsiKa
    Apr 7, 2014 at 20:19

14 Answers 14

12
\$\begingroup\$

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.

\$\endgroup\$
11
  • 1
    \$\begingroup\$ 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. \$\endgroup\$ Apr 7, 2014 at 5:59
  • 2
    \$\begingroup\$ 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 \$\endgroup\$
    – Hasturkun
    Apr 7, 2014 at 9:02
  • 1
    \$\begingroup\$ @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. \$\endgroup\$
    – Dennis
    Apr 7, 2014 at 13:41
  • 1
    \$\begingroup\$ @Vynce I assume you haven't either. I use |0 to cast to int, since rand returns a pseudo-randomly chosen float. \$\endgroup\$
    – Dennis
    Dec 7, 2015 at 20:23
  • 1
    \$\begingroup\$ @Vynce I've added a permalink to the question (ideone.com/gLJfhO). -e would be problematic here, unless you replace the single quotes with double quotes. \$\endgroup\$
    – Dennis
    Dec 7, 2015 at 20:28
6
\$\begingroup\$

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.

\$\endgroup\$
1
  • \$\begingroup\$ The basic logic is cool, but this does not handle d 1 correctly (nor several other inputs), nor is the output random. Also no need for ; at the end. Examples \$\endgroup\$
    – roblogic
    Nov 7, 2022 at 16:36
4
\$\begingroup\$

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'
\$\endgroup\$
1
  • \$\begingroup\$ @m.buettner Thanks! I don't know why I thought it had to be >0. \$\endgroup\$ Apr 7, 2014 at 22:45
3
\$\begingroup\$

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

\$\endgroup\$
2
  • \$\begingroup\$ I'm wondering why you don't use n./? Maybe also 10,n* for one character less. \$\endgroup\$
    – Howard
    Apr 7, 2014 at 17:35
  • \$\begingroup\$ @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. \$\endgroup\$ Apr 7, 2014 at 17:55
2
\$\begingroup\$

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?

\$\endgroup\$
2
\$\begingroup\$

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.

\$\endgroup\$
2
  • \$\begingroup\$ Does that work for 01d01+01? \$\endgroup\$ Apr 8, 2014 at 12:24
  • \$\begingroup\$ @CeesTimmerman My bad. It does now. \$\endgroup\$ Apr 8, 2014 at 18:40
2
\$\begingroup\$

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.

\$\endgroup\$
2
\$\begingroup\$

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.

\$\endgroup\$
1
\$\begingroup\$

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.

\$\endgroup\$
1
\$\begingroup\$

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).

\$\endgroup\$
3
  • \$\begingroup\$ I misunderstood the BNF, though. This page helps. \$\endgroup\$ Apr 8, 2014 at 10:37
  • \$\begingroup\$ 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. \$\endgroup\$ Apr 8, 2014 at 10:56
  • 1
    \$\begingroup\$ 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. \$\endgroup\$ Apr 8, 2014 at 11:03
1
\$\begingroup\$

Vyxal V, 89 bytes

h\d=[1p|]⁽±Ḋ→a⟨←aL5=|←a⌊±T⟨0|2|4⟩=|←a1i\d=|←a3i\+=⟩A[←a0i⌊ʁ(←a2i⌊℅⅛)¾∑←a4i⌊+,|«¢q⁰∷Ṗ-←¥«,

Try it Online!

Ties Perl

h\d=[1p|]         # Checks if the first character is a d, and prepends a 1 
        ⁽±Ḋ→a     # Splits the string on non-numeric values, so consecutive digits stay grouped, then casts the list to variable "a"

     ⟨←aL5=|           # is it Length 5 (3 numbers, the "+" and the "d")
       ←a⌊±T⟨0|2|4⟩=|  # convert the numbers-as-strings to numbers and check if the numbers are in positions 1 3 and 5 (0 indexed)
         ←a1i\d=|     # is the second item in the list  "d"
           ←a3i\+=⟩    # is the fourth item "+"
                A     # are all of these checks true?

  [←a0i⌊ʁ               # If all the checks are true, create a range from 0-a[0]
        (←a2i⌊℅⅛)       #  for i in said range, generate a random number 0-a[2] and push to global array
             ¾∑←a4i⌊+,  # push global array, sum the values, and add a[4] 

            |«¢q⁰∷Ṗ-←¥«, # If any of the checks are false, print "invalid input"
```
\$\endgroup\$
1
\$\begingroup\$

Zsh, 205 223 bytes

set -e;e=Invalid\ input;trap '<<<$e' ERR
echo "^0d\n+$\nd+\n+d\n+.+\n[-|*]">p;[[ `grep -cfp<<<$1`>0 ]]&&<<<$e&&exit
q=(${(s: :)${1//(#s)d/1d}//[^0-9]/ });for i ({1..$q[1]})((s+=RANDOM%q[2]+1))
<<<$[s+q[3]]

Try it Online (with all test cases)

Writing the validation code was a chore. Without this guff, the solution would be a svelte 89 bytes:

q=(${(s: :)${1//(#s)d/1d}//[^0-9]/ });for i ({1..$q[1]})((s+=RANDOM%q[2]+1))
<<<$[s+q[3]]
\$\endgroup\$
0
\$\begingroup\$

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

\$\endgroup\$
3
  • \$\begingroup\$ According to the clarified question, this is an incorrect answer because it accepts the input 3d20+. \$\endgroup\$ Apr 7, 2014 at 16:00
  • \$\begingroup\$ Good point! #filler \$\endgroup\$
    – alyx-brett
    Apr 7, 2014 at 16:10
  • \$\begingroup\$ And not 01d01+01. \$\endgroup\$ Apr 8, 2014 at 12:13
0
\$\begingroup\$

Go, unseeded, 366 bytes

import(."regexp";."strconv";."math/rand")
func f(s string)int{k:=MustCompile(`^(\d*)d(\d+)(\+\d+)?$`).FindAllStringSubmatch(s,-1)
if len(k)<1||k[0][2]==""{return-1}
r:=k[0]
var n,l,f,m,t int
l=len(r)
n,_=Atoi(r[1])
if n<1&&r[1]==""{n=1}else if n<1{return-1}
if l>2{f,_=Atoi(r[2])}
if f<1{return-1}
if l>3{m,_=Atoi(r[3])}
for i:=0;i<n;i++{t+=Intn(f)+1}
t+=m
return t}

Attempt This Online!

Go, seeded, 393 bytes

import(."regexp";."strconv";."math/rand";."time")
func f(s string)int{k:=MustCompile(`^(\d*)d(\d+)(\+\d+)?$`).FindAllStringSubmatch(s,-1)
if len(k)<1||k[0][2]==""{return-1}
r:=k[0]
var n,l,f,m,t int
l=len(r)
n,_=Atoi(r[1])
if n<1&&r[1]==""{n=1}else if n<1{return-1}
if l>2{f,_=Atoi(r[2])}
if f<1{return-1}
if l>3{m,_=Atoi(r[3])}
Seed(Now().Unix())
for i:=0;i<n;i++{t+=Intn(f)+1}
t+=m
return t}

Attempt This Online!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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