Expressions de lancer de dés complexes

23

Contexte

Je joue régulièrement à D&D avec des amis. Tout en parlant de la complexité de certains systèmes / versions lorsqu'il s'agit de lancer des dés et d'appliquer des bonus et des pénalités, nous avons proposé en plaisantant une certaine complexité supplémentaire pour les expressions de roulement de dés. Certains d'entre eux étaient trop scandaleux (comme l'extension d'expressions de dés simples comme 2d6aux arguments de matrice 1 ), mais les autres constituent un système intéressant.

Le défi

Étant donné une expression de dés complexe, évaluez-la selon les règles suivantes et sortez le résultat.

Règles d'évaluation de base

  • Chaque fois qu'un opérateur attend un entier mais reçoit une liste pour un opérande, la somme de cette liste est utilisée
  • Chaque fois qu'un opérateur attend une liste mais reçoit un entier pour un opérande, l'entier est traité comme une liste à un élément contenant cet entier

Les opérateurs

Tous les opérateurs sont des opérateurs d'infixes binaires. Aux fins d'explication, asera l'opérande gauche et bsera l'opérande droit. La notation de liste sera utilisée pour des exemples où les opérateurs peuvent prendre des listes comme opérandes, mais les expressions réelles ne sont constituées que d'entiers positifs et d'opérateurs.

  • d: sortie d' aentiers aléatoires uniformes indépendants dans la plage[1, b]
    • Priorité: 3
    • Les deux opérandes sont des entiers
    • Exemples: 3d4 => [1, 4, 3],[1, 2]d6 => [3, 2, 6]
  • t: prendre les bvaleurs les plus faibles dea
    • Priorité: 2
    • aest une liste, best un entier
    • Si b > len(a), toutes les valeurs sont retournées
    • Exemples: [1, 5, 7]t1 => [1], [5, 18, 3, 9]t2 => [3, 5],3t5 => [3]
  • T: prendre les bvaleurs les plus élevées dea
    • Priorité: 2
    • aest une liste, best un entier
    • Si b > len(a), toutes les valeurs sont retournées
    • Exemples: [1, 5, 7]T1 => [7], [5, 18, 3, 9]T2 => [18, 9],3T5 => [3]
  • r: si des éléments bsont adedans, relancez ces éléments, en utilisant la ddéclaration qui les a générés
    • Priorité: 2
    • Les deux opérandes sont des listes
    • Le relancement n'est effectué qu'une seule fois, il est donc possible d'avoir encore des éléments bdans le résultat
    • Exemples: 3d6r1 => [1, 3, 4] => [6, 3, 4], 2d4r2 => [2, 2] => [3, 2],3d8r[1,8] => [1, 8, 4] => [2, 2, 4]
  • R: si des éléments bsont dans a, relancez ces éléments à plusieurs reprises jusqu'à ce qu'aucun élément de bne soit présent, en utilisant la ddéclaration qui les a générés
    • Priorité: 2
    • Les deux opérandes sont des listes
    • Exemples: 3d6R1 => [1, 3, 4] => [6, 3, 4], 2d4R2 => [2, 2] => [3, 2] => [3, 1],3d8R[1,8] => [1, 8, 4] => [2, 2, 4]
  • +: ajouter aet bensemble
    • Priorité: 1
    • Les deux opérandes sont des entiers
    • Exemples: 2+2 => 4, [2]+[2] => 4,[3, 1]+2 => 6
  • -: soustraire bdea
    • Priorité: 1
    • Les deux opérandes sont des entiers
    • b sera toujours inférieur à a
    • Exemples: 2-1 => 1, 5-[2] => 3,[8, 3]-1 => 10
  • .: concaténer aet bensemble
    • Priorité: 1
    • Les deux opérandes sont des listes
    • Exemples: 2.2 => [2, 2], [1].[2] => [1, 2],3.[4] => [3, 4]
  • _: sortie aavec tous les éléments bsupprimés
    • Priorité: 1
    • Les deux opérandes sont des listes
    • Exemples: [3, 4]_[3] => [4], [2, 3, 3]_3 => [2],1_2 => [1]

Règles supplémentaires

  • Si la valeur finale d'une expression est une liste, elle est additionnée avant la sortie
  • L'évaluation des termes ne produira que des entiers positifs ou des listes d'entiers positifs - toute expression qui aboutit à un entier non positif ou une liste contenant au moins un entier non positif verra ces valeurs remplacées par 1s
  • Les parenthèses peuvent être utilisées pour regrouper les termes et spécifier l'ordre d'évaluation
  • Les opérateurs sont évalués par ordre de priorité la plus élevée à la priorité la plus basse, l'évaluation se déroulant de gauche à droite dans le cas d'une priorité liée (ainsi 1d4d4serait évalué comme (1d4)d4)
  • L'ordre des éléments dans les listes n'a pas d'importance - il est parfaitement acceptable pour un opérateur qui modifie une liste de la renvoyer avec ses éléments dans un ordre relatif différent
  • Les termes qui ne peuvent pas être évalués ou qui entraîneraient une boucle infinie (comme 1d1R1ou 3d6R[1, 2, 3, 4, 5, 6]) ne sont pas valides

Cas de test

Format: input => possible output

1d20 => 13
2d6 => 8
4d6T3 => 11
2d20t1 => 13
5d8r1 => 34
5d6R1 => 20
2d6d6 => 23
3d2R1d2 => 3
(3d2R1)d2 => 11
1d8+3 => 10
1d8-3 => 4
1d6-1d2 => 2
2d6.2d6 => 12
3d6_1 => 8
1d((8d20t4T2)d(6d6R1r6)-2d4+1d2).(1d(4d6_3d3)) => 61

Tous, sauf le dernier cas de test, ont été générés avec l'implémentation de référence.

Exemple travaillé

Expression: 1d((8d20t4T2)d(6d6R1r6)-2d4+1d2).(1d(4d6_3d3))

  1. 8d20t4T2 => [19, 5, 11, 6, 19, 15, 4, 20]t4T2 => [4, 5, 6, 11]T2 => [11, 6](plein: 1d(([11, 6])d(6d6R1r6)-2d4+1d2).(1d(4d6_3d3)))
  2. 6d6R1r6 => [2, 5, 1, 5, 2, 3]r1R6 => [2, 5, 3, 5, 2, 3]R6 => [2, 5, 3, 5, 2, 3]( 1d([11, 6]d[2, 5, 3, 5, 2, 3]-2d4+1d2).(1d(4d6_3d3)))
  3. [11, 6]d[2, 5, 3, 5, 2, 3] => 17d20 => [1, 6, 11, 7, 2, 8, 15, 3, 4, 18, 11, 11, 1, 10, 8, 6, 11]( 1d([1, 6, 11, 7, 2, 8, 15, 3, 4, 18, 11, 11, 1, 10, 8, 6, 11]-2d4+1d2).(1d(4d6_3d3)))
  4. 2d4 => 7( 1d([1, 6, 11, 7, 2, 8, 15, 3, 4, 18, 11, 11, 1, 10, 8, 6, 11]-7+1d2).(1d(4d6_3d3)))
  5. 1d2 => 2( 1d([1, 6, 11, 7, 2, 8, 15, 3, 4, 18, 11, 11, 1, 10, 8, 6, 11]-7+2).(1d(4d6_3d3)))
  6. [1, 6, 11, 7, 2, 8, 15, 3, 4, 18, 11, 11, 1, 10, 8, 6, 11]-7+2 => 133-7+2 => 128( 1d128).(1d(4d6_3d3)))
  7. 4d6_3d3 => [1, 3, 3, 6]_[3, 2, 2] => [1, 3, 3, 6, 3, 2, 2]( 1d128).(1d[1, 3, 3, 6, 3, 2, 2]))
  8. 1d[1, 3, 3, 6, 3, 2, 2] => 1d20 => 6( 1d128).(6))
  9. 1d128 => 55( 55.6)
  10. 55.6 => [55, 6]( [55, 6])
  11. [55, 6] => 61 (terminé)

Implémentation de référence

Cette implémentation de référence utilise la même graine constante ( 0) pour évaluer chaque expression pour des sorties cohérentes testables. Il attend une entrée sur STDIN, avec des retours à la ligne séparant chaque expression.

#!/usr/bin/env python3

import re
from random import randint, seed
from collections import Iterable
from functools import total_ordering

def as_list(x):
    if isinstance(x, Iterable):
        return list(x)
    else:
        return [x]

def roll(num_sides):
    return Die(randint(1, num_sides), num_sides)

def roll_many(num_dice, num_sides):
    num_dice = sum(as_list(num_dice))
    num_sides = sum(as_list(num_sides))
    return [roll(num_sides) for _ in range(num_dice)]

def reroll(dice, values):
    dice, values = as_list(dice), as_list(values)
    return [die.reroll() if die in values else die for die in dice]

def reroll_all(dice, values):
    dice, values = as_list(dice), as_list(values)
    while any(die in values for die in dice):
        dice = [die.reroll() if die in values else die for die in dice]
    return dice

def take_low(dice, num_values):
    dice = as_list(dice)
    num_values = sum(as_list(num_values))
    return sorted(dice)[:num_values]

def take_high(dice, num_values):
    dice = as_list(dice)
    num_values = sum(as_list(num_values))
    return sorted(dice, reverse=True)[:num_values]

def add(a, b):
    a = sum(as_list(a))
    b = sum(as_list(b))
    return a+b

def sub(a, b):
    a = sum(as_list(a))
    b = sum(as_list(b))
    return max(a-b, 1)

def concat(a, b):
    return as_list(a)+as_list(b)

def list_diff(a, b):
    return [x for x in as_list(a) if x not in as_list(b)]

@total_ordering
class Die:
    def __init__(self, value, sides):
        self.value = value
        self.sides = sides
    def reroll(self):
        self.value = roll(self.sides).value
        return self
    def __int__(self):
        return self.value
    __index__ = __int__
    def __lt__(self, other):
        return int(self) < int(other)
    def __eq__(self, other):
        return int(self) == int(other)
    def __add__(self, other):
        return int(self) + int(other)
    def __sub__(self, other):
        return int(self) - int(other)
    __radd__ = __add__
    __rsub__ = __sub__
    def __str__(self):
        return str(int(self))
    def __repr__(self):
        return "{} ({})".format(self.value, self.sides)

class Operator:
    def __init__(self, str, precedence, func):
        self.str = str
        self.precedence = precedence
        self.func = func
    def __call__(self, *args):
        return self.func(*args)
    def __str__(self):
        return self.str
    __repr__ = __str__

ops = {
    'd': Operator('d', 3, roll_many),
    'r': Operator('r', 2, reroll),
    'R': Operator('R', 2, reroll_all),
    't': Operator('t', 2, take_low),
    'T': Operator('T', 2, take_high),
    '+': Operator('+', 1, add),
    '-': Operator('-', 1, sub),
    '.': Operator('.', 1, concat),
    '_': Operator('_', 1, list_diff),
}

def evaluate_dice(expr):
    return max(sum(as_list(evaluate_rpn(shunting_yard(tokenize(expr))))), 1)

def evaluate_rpn(expr):
    stack = []
    while expr:
        tok = expr.pop()
        if isinstance(tok, Operator):
            a, b = stack.pop(), stack.pop()
            stack.append(tok(b, a))
        else:
            stack.append(tok)
    return stack[0]

def shunting_yard(tokens):
    outqueue = []
    opstack = []
    for tok in tokens:
        if isinstance(tok, int):
            outqueue = [tok] + outqueue
        elif tok == '(':
            opstack.append(tok)
        elif tok == ')':
            while opstack[-1] != '(':
                outqueue = [opstack.pop()] + outqueue
            opstack.pop()
        else:
            while opstack and opstack[-1] != '(' and opstack[-1].precedence > tok.precedence:
                outqueue = [opstack.pop()] + outqueue
            opstack.append(tok)
    while opstack:
        outqueue = [opstack.pop()] + outqueue
    return outqueue

def tokenize(expr):
    while expr:
        tok, expr = expr[0], expr[1:]
        if tok in "0123456789":
            while expr and expr[0] in "0123456789":
                tok, expr = tok + expr[0], expr[1:]
            tok = int(tok)
        else:
            tok = ops[tok] if tok in ops else tok
        yield tok

if __name__ == '__main__':
    import sys
    while True:
        try:
            dice_str = input()
            seed(0)
            print("{} => {}".format(dice_str, evaluate_dice(dice_str)))
        except EOFError:
            exit()

[1]: Notre définition des adbarguments pour la matrice était de rouler AdXpour chacun Xdans a * b, où A = det(a * b). De toute évidence, c'est trop absurde pour ce défi.

Mego
la source
Avec la garantie, -ce bsera toujours moins que aje ne vois aucun moyen d'obtenir des entiers non positifs, donc la deuxième règle supplémentaire semble inutile. OTOH, _pourrait entraîner une liste vide, ce qui semble utile dans les mêmes cas, mais qu'est-ce que cela signifie lorsqu'un entier est nécessaire? Normalement, je dirais que la somme est 0...
Christian Sievers
@ChristianSievers 1) J'ai ajouté la note supplémentaire sur les entiers non positifs pour plus de clarté. 2) La somme d'une liste vide est 0. Selon la règle du non-positif, il serait évalué comme a 1.
Mego
D'accord, mais est-ce correct comme résultat intermédiaire? Alors [1,2]_([1]_[1])est [1,2]?
Christian Sievers
@ChristianSievers Non. Cela se traduirait par [2], car [1]_[1] -> [] -> 0 -> 1 -> [1].
Mego

Réponses:

9

Python 3, 803 788 753 749 744 748 745 740 700 695 682 octets

exec(r'''from random import*
import re
class k(int):
 p=0;j=Xl([d(randint(1,int(b)),b)Zrange(a)]);__mul__=Xl(sorted(Y)[:b]);__matmul__=Xl(sorted(Y)[-b:]);__truediv__=Xl([d(randint(1,int(_.i)),_.i)if _==b else _ ZY]);__sub__=Xk(max(1,int.__sub__(a,b)))
 def __mod__(a,b):
  x=[]
  while x!=Y:x=Y;a=a/b
  Wl(x)
 def V:
  if b.p:p=b.p;del b.p;Wl(Y+b.l)if~-p else l([_ZY if~-(_ in b.l)])
  Wk(int.V)
 def __neg__(a):a.p+=1;Wa
def l(x):a=k(sum(x)or 1);Y=x;Wa
def d(v,i):d=k(v);d.i=i;Wd
lambda a:eval(re.sub("(\d+)","(k(\\1))",a).translate({100:".j",116:"*",84:"@",114:"/",82:"%",46:"+--",95:"+-"}))'''.translate({90:" for _ in ",89:"a.l",88:"lambda a,b:",87:"return ",86:"__add__(a,b)"}))

-5 octets grâce à Mr.Xcoder

-5 octets de plus grâce à NGN

-environ 40 octets grâce à Jonathan French

Beurk, quelle blague! Cela fonctionne en utilisant une expression régulière pour encapsuler tous les nombres de ma kclasse, et en convertissant tous les opérateurs en opérateurs que python comprend, puis en utilisant les méthodes magiques de la kclasse pour gérer les mathématiques. Le +-et +--à la fin de .et _sont un hack pour garder la priorité correcte. De même, je ne peux pas utiliser l' **opérateur pour d car cela ferait 1d4d4être analysé comme 1d(4d4). Au lieu de cela, j'encapsule tous les nombres dans un ensemble supplémentaire de parens et fais d as .j, car les appels de méthode ont une priorité plus élevée que les opérateurs. La dernière ligne est évaluée comme une fonction anonyme qui évalue l'expression.

pppery
la source
def __mod__(a, b)... Pourquoi l'espace entre a,et b?
M. Xcoder
744 octets
M. Xcoder
@ Mr.Xcoder Je pense que vous pouvez enregistrer un octet en supprimant un espace inutile: ; __sub__. Et peut - être aussi ici: lambda a,b: l(.
Jonathan Frech
1
Vous pouvez enregistrer quelques octets en enveloppant l'intégralité de votre code dans une exec("""...""".replace("...","..."))instruction et en remplaçant les chaînes qui se produisent souvent (comme return ) par un seul caractère. Cependant, pour moi la exec-stratégie semble toujours un peu peu élégante ...
Jonathan Frech
les corps de __mod__et __add__ne ont pas besoin que tiret beaucoup
ngn
3

APL (Dyalog Classic) , 367 octets

d←{(⊢⍪⍨1+?)⍉⍪⊃⍴/⊃¨+/¨⍺⍵}⋄r←{z←⊣⌿⍺⋄((m×?n)+z×~m←z∊⊣⌿⍵)⍪n←⊢⌿⍺}⋄R←{⍬≡⊃∩/⊣⌿¨⍺⍵:⍺⋄⍵∇⍨⍺r⍵}
u←{⍺[;((⊃+/⍵)⌊≢⍉⍺)↑⍺⍺⊣⌿⍺]}⋄t←⍋u⋄T←⍒u⋄A←+/,⋄S←+/,∘-⋄C←,⋄D←{⍺/⍨~⊃∊/⊣⌿¨⍺⍵}
hv←⍬⋄o'drRtT+-._'f←{8<io⍳⊃⍵:0v⊢←(¯2v),(⍎i'drRtTASCD')/¯2v}
{⊃⍵∊⎕d:v,←⊂⍪2↑⍎⍵⋄'('=⍵:h,←⍵⋄')'=⍵:h↑⍨←i-1f¨⌽h↓⍨i←+/∨\⌽h='('⋄h,←⍵⊣h↓⍨←-i⊣f¨⌽h↑⍨-i←+/\⌽≤/(1 4 4 1/⌽⍳4)[o⍳↑⍵,¨h]}¨'\d+' '.'s'&'⊢⍞
f¨⌽h1⌈+/⊣⌿⊃v

Essayez-le en ligne!

Il s'agit de l'algorithme de triage de l'implémentation de référence fusionné evaluate_dice(), sans le non-sens cru et orienté objet. Seules deux piles sont utilisées: hpour les opérateurs et vpour les valeurs. L'analyse et l'évaluation sont entrelacées.

Les résultats intermédiaires sont représentés sous la forme de matrices 2 × N où la première ligne représente les valeurs aléatoires et la deuxième ligne le nombre de côtés sur les dés qui les ont produites. Lorsqu'un résultat n'est pas produit par l'opérateur "d" qui lance des dés, la deuxième ligne contient des nombres arbitraires. Une seule valeur aléatoire est une matrice 2 × 1 et donc indiscernable d'une liste à 1 élément.

ngn
la source
3

Python 3: 723 722 714 711 707 675 653 665 octets

import re
from random import*
S=re.subn
e=eval
r=randint
s=lambda a:sum(g(e(a)))or 1
g=lambda a:next(zip(*a))
def o(m):a,o,b=m.groups();A=sorted(e(a));t=g(e(b));return str(o in"rR"and([([v,(r(1,d)*(o>"R")or choice([n+1for n in range(d)if~-(n+1in t)]))][v in t],d)for(v,d)in A])or{"t":A[:s(b)],"T":A[-s(b):],"+":[(s(a)+s(b),0)],"-":[(s(a)-s(b),0)],".":e(a)+e(b),"_":[t for t in e(a)if~-(t[0]in g(e(b)))]}[o])
def d(m):a,b=map(s,m.groups());return str([(r(1,b),b)for _ in" "*a])
p=r"(\[[^]]+\])"
def E(D):
 D,n=S(r"(\d+)",r"[(\1,0)]",D)
 while n:
  n=0
  for e in[("\(("+p+")\)",r"\1"),(p+"d"+p,d),(p+"([tTrR])"+p,o),(p+"(.)"+p,o)]:
   if n<1:D,n=S(*e,D)
 return s(D)

Le point d'entrée est E. Cela applique les expressions régulières de manière itérative. Tout d'abord, il remplace tous les entiers xpar un tuple de liste singleton [(x,0)]. Ensuite, la première expression régulière effectue l' dopération, en remplaçant all [(x,0)]d[(b,0)]par la représentation sous forme de chaîne d'un tableau de tuples comme [(1,b),(2,b),(3,b)]. Le deuxième élément de chaque tuple est le deuxième opérande de d. Ensuite, les expressions régulières suivantes exécutent chacun des autres opérateurs. Il existe une expression rationnelle spéciale pour supprimer les parens des expressions entièrement calculées.

récursif
la source
3

Clojure, 731 720 octets

(lorsque les sauts de ligne sont supprimés)

Mise à jour: une implémentation plus courte de F.

(defn N[i](if(seq? i)(apply + i)i))
(defn g[i](let[L(fn[i](let[v(g i)](if(seq? v)v(list v))))R remove T take](if(seq? i)(let[[o a b :as A]i](if(some symbol? A)(case o d(repeatedly(N(g a))(fn[](inc(rand-int(N(g b))))))t(T(N(g b))(sort(g a)))T(T(N(g b))(sort-by -(g a)))r(for[i(L a)](if((set(L b))i)(nth(L a)0)i))R(T(count(L a))(R(set(L b))(for[_(range)i(L a)]i)))+(+(N(g a))(N(g b)))-(-(N(g a))(N(g b))).(into(L a)(L b))_(R(set(L b))(g a)))A))i)))
(defn F[T](if(seq? T)(if(next T)(loop[[s & S]'[_ . - + R r T t d]](let[R reverse[b a](map R(split-with(comp not #{s})(R T)))a(butlast a)](if a(cons s(map F[a b]))(recur S))))(F(first T)))T))
(defn f[i](N(g(F(read-string(clojure.string/replace(str"("i")")#"[^0-9]"" $0 "))))))

Il se compose de quatre parties principales:

  • N: contraint une liste en un nombre
  • g: évalue un arbre de syntaxe abstraite (expressions S avec 3 éléments)
  • F: convertit un infixe AST en notation de préfixe (expressions S), applique également la priorité de l'ordre des opérandes
  • f: utilise read-stringpour convertir une chaîne en une séquence imbriquée de nombres et de symboles (infixe AST), les redirige vers F -> g -> N, renvoyant le numéro de résultat.

Je ne sais pas comment tester cela à fond, peut-être via des tests statistiques contre une implémentation de référence? Au moins l'AST et son évaluation sont relativement faciles à suivre.

Exemple d'expression S de 1d((8d20t4T2)d(6d6R1r6)-2d4+1d2).(1d(4d6_3d3)):

(. (d 1 (- (d (T (t (d 8 20) 4) 2)
              (R (d 6 6) (r 1 6)))
           (+ (d 2 4)
              (d 1 2))))
   (d 1 (_ (d 4 6) (d 3 3))))

Moins de golf avec des résultats et des tests internes:

(def f #(read-string(clojure.string/replace(str"("%")")#"[^0-9]"" $0 ")))

(defn F [T]
  (println {:T T})
  (cond
    (not(seq? T))T
    (symbol?(first T))T
    (=(count T)1)(F(first T))
    1(loop [[s & S] '[_ . - + R r T t d]]
      (let[[b a](map reverse(split-with(comp not #{s})(reverse T)))
           _ (println [s a b])
           a(butlast a)]
        (cond
          a(do(println {:s s :a a :b b})(cons s(map F[a b])))
          S(recur S))))))


(->> "3d6" f F)
(->> "3d6t2" f F)
(->> "3d2R1" f F)
(->> "1d4d4" f F)
(->> "2d6.2d6" f F)
(->> "(3d2R1)d2" f F)
(->> "1d((8d20t4T2)d(6d6R1r6)-2d4+1d2).(1d(4d6_3d3))" f F)

(defn N[i](if(seq? i)(apply + i)i))

(defn g[i]
  (let[L(fn[i](let[v(g i)](if(seq? v)v(list v))))]
    (if(seq? i)
      (let[[o a b :as A] i]
        (println {:o o :a a :b b :all A})
        (if(every? number? A)(do(println{:A A})A)
           (case o
            d (repeatedly(N (g a))(fn[](inc(rand-int(N (g b))))))
            t (take(N (g b))(sort(g a)))
            T (take(N (g b))(sort-by -(g a)))
            r (for[i(L a)](if((set(L b))i)(nth(L a)0)i))
            R (take(count(g a))(remove(set(L b))(for[_(range)i(g a)]i)))
            + (+(N (g a))(N (g b)))
            - (-(N (g a))(N (g b)))
            . (into(L a)(L b))
            _ (remove(set(L b))(g a)))))
      (do(println {:i i})i))))


(g '(. (d 3 5) (d 4 3)))
(g '(. 1 (2 3)))
(g '(+ 1 (2 3)))
(g '(R (d 10 5) (d 1 3)))
(g '(T (d 5 20) 3))
(g '(t (d 5 20) 3))
(g '(d (d 3 4) 10))
(g '(d 4 3))
(g '(_ (d 4 6) (d 3 3)))

(->> "1d(4d6_3d3)" f F g)
(->> "1r6" f F g)
(->> "(8d20t4T2)d(6d6R1r6)" f F g)
(->> "(8d20t4T2)d(6d6R1r6)-2d4+1d2).(1d(4d6_3d3)" f F g)
(->> "1d((8d20t4T2)d(6d6R1r6)-2d4+1d2).(1d(4d6_3d3))" f F g))
NikoNyrh
la source
2

Python 3, 695 octets

import random,tatsu
A=lambda x:sum(x)or 1
H=lambda x:[[d,D(d.s)][d in x[2]]for d in x[0]]
R=lambda x:R([H(x)]+x[1:])if{*x[0]}&{*x[2]}else x[0]
class D(int):
 def __new__(cls,s):o=super().__new__(cls,random.randint(1,s));o.s = s;return o
class S:
 o=lambda s,x:{'+':[A(x[0])+A(x[2])],'-':[A(x[0])-A(x[2])],'.':x[0]+x[2],'_':[d for d in x[0]if d not in x[2]]}[x[1]]
 f=lambda s,x:{'r':H(x),'R':R(x),'t':sorted(x[0])[:A(x[2])],'T':sorted(x[0])[-A(x[2]):]}[x[1]]
 d=lambda s,x:[D(A(x[2]))for _ in' '*A(x[0])]
 n=lambda s,x:[int(x)]
 l=lambda s,x:sum(x,[])
lambda i:tatsu.parse("s=e$;e=o|t;o=e/[-+._]/t;t=f|r;f=t/[rRtT]/r;r=d|a;d=r/d/a;a=n|l|p;n=/\d+/;l='['@:','.{n}']';p='('@:e')';",i,semantics=S())

Un interpréteur construit à l'aide d' tatsuune bibliothèque d'analyseur PEG. Le premier argument de tatsu.parser()est la grammaire PEG.

class D(pour Die) sous-classe le inttype intégré . Sa valeur est le résultat d'un rouleau. L'attribut .sest le nombre de faces du dé.

class S a les actions sémantiques pour l'analyseur et implémente l'interpréteur.

RootTwo
la source