Write a function that accepts string as a parameter, returning evaluated value of expression in dice notation, including addition and multiplication.
To
JAVASCRIPT, 1399 chars, no eval
old post, i know. but i try to contribute
Roll = window.Roll || {};
Roll.range = function (str) {
var rng_min, rng_max, str_split,
delta, value;
str = str.replace(/\s+/g, "");
str_split = str.split("-");
rng_min = str_split[0];
rng_max = str_split[1];
rng_min = parseInt(rng_min) || 0;
rng_max = Math.max(parseInt(rng_max), rng_min) || rng_min;
delta = (rng_max - rng_min + 1);
value = Math.random() * delta;
value = parseInt(value);
return value + rng_min;
};
Roll.rollStr = function (str) {
var check,
qta, max, dice, mod_opts, mod,
rng_min, rng_max,
rolls = [], value = 0;
str = str.replace(/\s+/g, "");
check = str.match(/(?:^[-+]?(\d+)?(?:\/(\d+))?[dD](\d+)(?:([-+])(\d+)\b)?$|^(\d+)\-(\d+)$)/);
if (check == null) {return "ERROR"}
qta = check[1];
max = check[2];
dice = check[3];
mod_opts = check[4];
mod = check[5];
rng_min = check[6];
rng_max = check[7];
check = check[0];
if (rng_min && rng_max) {return Roll.range(str)}
dice = parseInt(dice);
mod_opts = mod_opts || "";
mod = parseInt(mod) || 0;
qta = parseInt(qta) || 1;
max = Math.max(parseInt(max), qta) || qta;
for (var val; max--;) {
val = Math.random() * dice;
val = Math.floor(val) + 1;
rolls.push(val);
}
if (max != qta) {
rolls.sort(function (a, b) {return a < b});
rolls.unshift(rolls.splice(0, qta));
}
while (rolls[0][0]) {value += rolls[0].shift();}
if (mod_opts == "-") {value -= mod;}
else {value += mod;}
return value
};
if (!window.diceRoll) {window.diceRoll= Roll.rollStr;}
it's a single dice roll, like "2d8+2" or "4-18" "3/4d6" (best 3 of 4 d6)
diceRoll("2d8+2");
diceRoll("4-18");
diceRoll("3/4d6");
to check cumulative rolls, better loop on matched result ove rthe input string like
r = "2d8+2+3/4d6"
r.match(/([-+])?(\d+)?(?:\/(\d+))?[dD](\d+)(?:([-+])(\d+)\b)?/g);
// => ["2d8+2", "+3/4d6"]
// a program can manage the "+" or "-" on the second one (usually is always an addiction)
Clojure, 854 characters as is, 412 shrunk
Just run "(roll-dice "input-string")".
(defn roll-dice
[string]
(let [parts (. (. (. string replace "-" "+-") replaceAll "\\s" "") split "\\+")
dice-func (fn [d-notation]
(let [bits (. d-notation split "d")]
(if (= 1 (count bits))
(Integer/parseInt (first bits)) ; Just a number, like 12
(if (zero? (count (first bits)))
(inc (rand-int (Integer/parseInt (second bits)))) ; Just d100 or some such
(if (. (first bits) contains "*")
(* (Integer/parseInt (. (first bits) replace "*" ""))
(inc (rand-int (Integer/parseInt (second bits)))))
(reduce +
(map #(+ 1 %)
(map rand-int
(repeat
(Integer/parseInt (first bits))
(Integer/parseInt (second bits)))))))))))]
(reduce + (map dice-func parts))))
To shrink, I made variables 1 letter, moved the (first bits)/(second bits) into variables, made dice-func an anonymous function, made a wrapper for Integer.parseInt called 'i', and stripped out comments and extra whitespace.
This should work on anything valid, with or without whitespace. Just don't go asking it for "15dROBERT", it will throw an exception.
They way it works is by splitting up the string into dice (that's the 3rd line, the let). So "5d6+2*d4-17" becomes "5d6","2*d4","-17".
Each of those is then processed by the function dice-func, and the results are added up (this is the map/reduce on the last line)
Dice-func takes a little dice string (such a "5d6") and splits it on the "d". If there is only one part left, it was a simple number (6, -17, etc).
If the first part contains a *, we multiply that number by a random interger, 1 to (number after d), inclusive.
If the first part doesn't contain a *, we take first number random rolls (just like previous line) and add them up (this is the map/reduce in the middle).
This was fun little challenge.
C# class. It evaluates recursively for addition and multiplication, left-to-right for chained die rolls
Edits:
.Replace(" ","")
on each call.Trim()
on int.TryParse
insteadif
statementMinified: (411 bytes)
class D{Random r=new Random();public int R(string s){int t=0;var a=s.Split('+');if(a.Count()>1)foreach(var b in a)t+=R(b);else{var m=a[0].Split('*');if(m.Count()>1){t=1;foreach(var n in m)t*=R(n);}else{var d=m[0].Split('d');if(!int.TryParse(d[0].Trim(),out t))t=0;int f;for(int i=1;i<d.Count();i++){if(!int.TryParse(d[i].Trim(),out f))f=6;int u=0;for(int j=0;j<(t== 0?1:t);j++)u+=r.Next(1,f);t+=u;}}}return t;}}
Expanded form:
class D
{
/// <summary>Our Random object. Make it a first-class citizen so that it produces truly *random* results</summary>
Random r = new Random();
/// <summary>Roll</summary>
/// <param name="s">string to be evaluated</param>
/// <returns>result of evaluated string</returns>
public int R(string s)
{
int t = 0;
// Addition is lowest order of precedence
var a = s.Split('+');
// Add results of each group
if (a.Count() > 1)
foreach (var b in a)
t += R(b);
else
{
// Multiplication is next order of precedence
var m = a[0].Split('*');
// Multiply results of each group
if (m.Count() > 1)
{
t = 1; // So that we don't zero-out our results...
foreach (var n in m)
t *= R(n);
}
else
{
// Die definition is our highest order of precedence
var d = m[0].Split('d');
// This operand will be our die count, static digits, or else something we don't understand
if (!int.TryParse(d[0].Trim(), out t))
t = 0;
int f;
// Multiple definitions ("2d6d8") iterate through left-to-right: (2d6)d8
for (int i = 1; i < d.Count(); i++)
{
// If we don't have a right side (face count), assume 6
if (!int.TryParse(d[i].Trim(), out f))
f = 6;
int u = 0;
// If we don't have a die count, use 1
for (int j = 0; j < (t == 0 ? 1 : t); j++)
u += r.Next(1, f);
t += u;
}
}
}
return t;
}
}
Test cases:
static void Main(string[] args)
{
var t = new List<string>();
t.Add("2d6");
t.Add("2d6d6");
t.Add("2d8d6 + 4d12*3d20");
t.Add("4d12");
t.Add("4*d12");
t.Add("4d"); // Rolls 4 d6
D d = new D();
foreach (var s in t)
Console.WriteLine(string.Format("{0}\t{1}", d.R(s), s));
}
perl, no evals, 144 chars, works multiple times, supports multiple dice rolls
sub e{($c=pop)=~y/+* /PT/d;$b='(\d+)';map{$a=0while$c=~s!$b?$_$b!$d=$1||1;$a+=1+int rand$2for 1..$d;$e=$2;/d/?$a:/P/?$d+$e:$d*$e!e}qw(d T P);$c}
Expanded version, with comments
sub f {
($c = pop); #assign first function argument to $c
$c =~ tr/+* /PT/d; #replace + and * so we won't have to escape them later.
#also remove spaces
#for each of 'd','T' and 'P', assign to $_ and run the following
map {
#repeatedly replace in $c the first instance of <number> <operator> <number> with
#the result of the code in second part of regex, capturing both numbers and
#setting $a to zero after every iteration
$a=0 while $c =~ s[(\d+)?$_(\d+)][
$d = $1 || 1; #save first parameter (or 1 if not defined) as later regex
#will overwrite it
#roll $d dice, sum in $a
for (1..$d)
{
$a += 1 + int rand $2;
}
$e = $2; #save second parameter, following regexes will overwrite
#Code blocks return the value of their last statement
if (/d/)
{
$a; #calculated dice throw
}
elsif (/P/)
{
$d + $e;
}
else
{
$d * $e;
}
]e;
} qw(d T P);
return $c;
}
EDIT cleaned up, updated explanation to latest version
JavaScript solution, 340 chars when compressed (no eval, supports prefixed multiplicator and suffixed addition):
function comp (s, m, n, f, a) {
m = parseInt( m );
if( isNaN( m ) ) m = 1;
n = parseInt( n );
if( isNaN( n ) ) n = 1;
f = parseInt( f );
a = typeof(a) == 'string' ? parseInt( a.replace(/\s/g, '') ) : 0;
if( isNaN( a ) ) a = 0;
var r = 0;
for( var i=0; i<n; i++ )
r += Math.floor( Math.random() * f );
return r * m + a;
};
function parse( de ) {
return comp.apply( this, de.match(/(?:(\d+)\s*\*\s*)?(\d*)d(\d+)(?:\s*([\+\-]\s*\d+))?/i) );
}
Test code:
var test = ["3d6 + 12", "4*d12 + 3", "d100"];
for(var i in test)
alert( test[i] + ": " + parse(test[i]) );
Compressed version (pretty sure you can do shorter):
function c(s,m,n,f,a){m=parseInt(m);if(isNaN(m))m=1;n=parseInt(n);if(isNaN(n))n=1;f=parseInt(f);a=typeof(a)=='string'?parseInt(a.replace(/\s/g,'')):0;if(isNaN(a))a=0;var r=0;for(var i=0;i<n;i++)r+=Math.floor(Math.random()*f);return r*m+a;};function p(d){return c.apply(this,d.match(/(?:(\d+)\s*\*\s*)?(\d*)d(\d+)(?:\s*([\+\-]\s*\d+))?/i));}
PHP, 147 symbols, no eval:
preg_match('/(\d+)?d(\d+)[\s+]?([\+\*])?[\s+]?(\d+)?/',$i,$a);$d=rand(1,$a[2])*((!$a[1])?1:$a[1]);$e['+']=$d+$a[4];$e['*']=$d*$a[4];print$e[$a[3]];
$i
contains input string.
Edit: oops, forgot about prefixed operation. brb.