Evaluate dice rolling notation strings

前端 未结 14 1425
甜味超标
甜味超标 2021-01-30 14:55

Rules

Write a function that accepts string as a parameter, returning evaluated value of expression in dice notation, including addition and multiplication.

To

相关标签:
14条回答
  • 2021-01-30 15:09

    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)
    
    0 讨论(0)
  • 2021-01-30 15:11

    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.

    0 讨论(0)
  • 2021-01-30 15:12

    C# class. It evaluates recursively for addition and multiplication, left-to-right for chained die rolls

    Edits:

    • Removed .Replace(" ","") on each call
    • Added .Trim() on int.TryParse instead
    • All work is now done in single method
    • If die face count is not specified, assumes 6 (see Wiki article)
    • Refactored redundant call to parse left side of "d"
    • Refactored unnecessary if statement

    Minified: (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));
        }
    
    0 讨论(0)
  • 2021-01-30 15:12

    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

    0 讨论(0)
  • 2021-01-30 15:20

    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));}
    
    0 讨论(0)
  • 2021-01-30 15:20

    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.

    0 讨论(0)
提交回复
热议问题