Espressioni a rotazione complesse di dadi


23

sfondo

Gioco a D&D regolarmente con alcuni amici. Mentre parliamo della complessità di alcuni sistemi / versioni quando si tratta di lanciare dadi e applicare bonus e penalità, scherzosamente abbiamo trovato un'ulteriore complessità per le espressioni di lancio dei dadi. Alcuni di loro erano troppo scandalosi (come estendere semplici espressioni di dadi come 2d6matrici di argomenti 1 ), ma il resto crea un sistema interessante.

La sfida

Data un'espressione complessa dei dadi, valutala secondo le seguenti regole e genera il risultato.

Regole di valutazione di base

  • Ogni volta che un operatore si aspetta un numero intero ma riceve un elenco per un operando, viene utilizzata la somma di tale elenco
  • Ogni volta che un operatore si aspetta un elenco ma riceve un numero intero per un operando, l'intero viene trattato come un elenco di un elemento contenente tale numero intero

operatori

Tutti gli operatori sono operatori di infissione binaria. Ai fini della spiegazione, asarà l'operando di sinistra e bsarà l'operando di destra. La notazione elenco verrà utilizzata per esempi in cui gli operatori possono prendere elenchi come operandi, ma le espressioni effettive sono costituite solo da numeri interi e operatori positivi.

  • d: output ainteri casuali uniformi indipendenti nell'intervallo[1, b]
    • Precedenza: 3
    • Entrambi gli operandi sono numeri interi
    • Esempi: 3d4 => [1, 4, 3],[1, 2]d6 => [3, 2, 6]
  • t: prende i bvalori più bassi daa
    • Precedenza: 2
    • aè un elenco, bè un numero intero
    • Se b > len(a)vengono restituiti tutti i valori
    • Esempi: [1, 5, 7]t1 => [1], [5, 18, 3, 9]t2 => [3, 5],3t5 => [3]
  • T: prende i bvalori più alti daa
    • Precedenza: 2
    • aè un elenco, bè un numero intero
    • Se b > len(a)vengono restituiti tutti i valori
    • Esempi: [1, 5, 7]T1 => [7], [5, 18, 3, 9]T2 => [18, 9],3T5 => [3]
  • r: Se qualche elemento in bsono a, ritirare quegli elementi, usando qualsiasi ddichiarazione li ha generati
    • Precedenza: 2
    • Entrambi gli operandi sono elenchi
    • Il rilancio viene eseguito una sola volta, quindi è possibile avere ancora elementi bnel risultato
    • Esempi: 3d6r1 => [1, 3, 4] => [6, 3, 4], 2d4r2 => [2, 2] => [3, 2],3d8r[1,8] => [1, 8, 4] => [2, 2, 4]
  • R: Se qualche elemento in bsono a, ritirare quegli elementi più volte fino a quando non gli elementi di bsono presenti, utilizzando qualsiasi ddichiarazione li ha generati
    • Precedenza: 2
    • Entrambi gli operandi sono elenchi
    • Esempi: 3d6R1 => [1, 3, 4] => [6, 3, 4], 2d4R2 => [2, 2] => [3, 2] => [3, 1],3d8R[1,8] => [1, 8, 4] => [2, 2, 4]
  • +: aggiungi ae binsieme
    • Precedenza: 1
    • Entrambi gli operandi sono numeri interi
    • Esempi: 2+2 => 4, [2]+[2] => 4,[3, 1]+2 => 6
  • -: sottrarre bdaa
    • Precedenza: 1
    • Entrambi gli operandi sono numeri interi
    • b sarà sempre inferiore a a
    • Esempi: 2-1 => 1, 5-[2] => 3,[8, 3]-1 => 10
  • .: concatenati ae binsieme
    • Precedenza: 1
    • Entrambi gli operandi sono elenchi
    • Esempi: 2.2 => [2, 2], [1].[2] => [1, 2],3.[4] => [3, 4]
  • _: output acon tutti gli elementi brimossi
    • Precedenza: 1
    • Entrambi gli operandi sono elenchi
    • Esempi: [3, 4]_[3] => [4], [2, 3, 3]_3 => [2],1_2 => [1]

Regole aggiuntive

  • Se il valore finale di un'espressione è un elenco, viene sommato prima dell'output
  • La valutazione dei termini comporterà solo numeri interi positivi o elenchi di numeri interi positivi - qualsiasi espressione che dia luogo a un numero intero non positivo o a un elenco contenente almeno un numero intero non positivo avrà quei valori sostituiti da 1s
  • Le parentesi possono essere utilizzate per raggruppare i termini e specificare l'ordine di valutazione
  • Gli operatori vengono valutati in ordine di precedenza dal più basso al precedente, con la valutazione che procede da sinistra a destra in caso di precedenza legata (quindi 1d4d4sarebbe valutata come (1d4)d4)
  • L'ordine degli elementi negli elenchi non ha importanza: è perfettamente accettabile per un operatore che modifica un elenco restituirlo con i suoi elementi in un diverso ordine relativo
  • I termini che non possono essere valutati o risulterebbero in un ciclo infinito (come 1d1R1o 3d6R[1, 2, 3, 4, 5, 6]) non sono validi

Casi test

Formato: 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

Tutti tranne l'ultimo caso di test sono stati generati con l'implementazione di riferimento.

Esempio lavorato

Espressione: 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](completo 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 (fatto)

Implementazione di riferimento

Questa implementazione di riferimento utilizza lo stesso seed costante ( 0) per valutare ogni espressione per output verificabili e coerenti. Si aspetta input su STDIN, con le nuove righe che separano ogni espressione.

#!/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]: La nostra definizione di adbper gli argomenti di matrice era di rollare AdXper ciascuno Xin a * b, dove A = det(a * b). Chiaramente è troppo assurdo per questa sfida.



Con la garanzia -che bsarà sempre inferiore a anon vedo alcun modo per ottenere numeri interi non positivi, quindi la seconda regola aggiuntiva sembra inutile. OTOH, _potrebbe risultare in un elenco vuoto, che sembra utile negli stessi casi, ma cosa significa quando è necessario un numero intero? Normalmente direi che la somma è 0...
Christian Sievers,

@ChristianSievers 1) Ho aggiunto la nota aggiuntiva sugli interi non positivi per chiarezza. 2) La somma di un elenco vuoto è 0. In base alla regola dei non positivi, sarebbe valutata come a 1.
Mego,

Va bene, ma va bene come risultato intermedio? Così [1,2]_([1]_[1])è [1,2]?
Christian Sievers,

@ChristianSievers No. Ciò comporterebbe [2], perché [1]_[1] -> [] -> 0 -> 1 -> [1].
Mego,

Risposte:


9

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

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 byte grazie a Mr.Xcoder

-5 byte in più grazie a NGN

-circa 40 byte grazie a Jonathan French

Che schifo! Funziona usando un'espressione regolare per racchiudere tutti i numeri nella mia kclasse e convertendo tutti gli operatori in operatori comprensibili da Python, quindi usando i metodi magici della kclasse per gestire la matematica. Il +-e +--alla fine per .e _sono un trucco per mantenere corretta la precedenza. Allo stesso modo, non posso usare l' **operatore per d perché farlo verrebbe 1d4d4analizzato come 1d(4d4). Invece, avvolgo tutti i numeri in un set extra di parentesi e faccio come .j, poiché le chiamate al metodo hanno una precedenza maggiore rispetto agli operatori. L'ultima riga viene valutata come una funzione anonima che valuta l'espressione.


def __mod__(a, b)... Perché lo spazio tra a,e b?
Mr. Xcoder,


@ Mr.Xcoder Penso che è possibile salvare un byte rimuovendo uno spazio inutile: ; __sub__. E forse anche qui: lambda a,b: l(.
Jonathan Frech,

1
È possibile salvare alcuni byte racchiudendo l'intero codice in exec("""...""".replace("...","..."))un'istruzione e sostituendo le stringhe che si verificano spesso (come return ) con un singolo carattere. Tuttavia, per me la execstrategia sembra sempre un po 'poco elegante ...
Jonathan Frech,

i corpi di __mod__e __add__non hanno bisogno di così tanto trattino
ngn

3

APL (Dyalog Classic) , 367 byte

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

Provalo online!

Questo è l'algoritmo di shunting yard dall'implementazione di riferimento unita evaluate_dice(), senza il non senso e l'inversione orientata agli oggetti. Vengono utilizzate solo due pile: hper gli operatori e vper i valori. L'analisi e la valutazione sono intercalate.

I risultati intermedi sono rappresentati come matrici 2 × N in cui la prima riga sono i valori casuali e la seconda riga è il numero di lati sui dadi che li hanno prodotti. Quando un risultato non viene prodotto dall'operatore "d" che lancia i dadi, la seconda riga contiene numeri arbitrari. Un singolo valore casuale è una matrice 2 × 1 e quindi non distinguibile da un elenco di 1 elemento.


3

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

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)

Il punto di ingresso è E. Questo applica espressioni regolari in modo iterativo. Innanzitutto sostituisce tutti i numeri interi xcon una tupla di elenco singleton [(x,0)]. Quindi la prima espressione regolare esegue l' doperazione, sostituendo tutto [(x,0)]d[(b,0)]con la rappresentazione in stringa di una matrice di tuple come [(1,b),(2,b),(3,b)]. Il secondo elemento di ogni tupla è il secondo operando a d. Quindi, le successive espressioni regolari eseguono ciascuno degli altri operatori. Esiste una regex speciale per rimuovere le parentesi dalle espressioni completamente calcolate.


3

Clojure, 731 720 byte

(quando vengono rimosse le nuove righe)

Aggiornamento: un'implementazione più breve di 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 "))))))

Questo è composto da quattro parti principali:

  • N: costringe un elenco in un numero
  • g: valuta un albero di sintassi astratto (espressioni S con 3 elementi)
  • F: converte un AST infisso in notazione prefisso (espressioni S), applica anche la precedenza dell'ordine degli operandi
  • f: usa read-stringper convertire una stringa in una sequenza nidificata di numeri e simboli (infetti AST), li instrada attraverso F -> g -> N, restituendo il numero del risultato.

Non sono sicuro di come testarlo a fondo, magari tramite test statistici contro un'implementazione di riferimento? Almeno l'AST e la sua valutazione sono relativamente facili da seguire.

Esempio di espressione S da 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))))

Meno golfato con risultati e test interni:

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

2

Python 3, 695 byte

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 interprete creato usando tatsu, una libreria di parser PEG. Il primo argomento tatsu.parser()è la grammatica PEG.

class D(per Die) subclasse il inttipo incorporato . Il suo valore è il risultato di un lancio. L'attributo .sè il numero di lati sul dado.

class S ha le azioni semantiche per il parser e implementa l'interprete.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.