Ekspresi Dadu Bergulir Kompleks

23

Latar Belakang

Saya bermain D&D secara teratur dengan beberapa teman. Ketika berbicara tentang kerumitan beberapa sistem / versi ketika datang untuk melempar dadu dan menerapkan bonus dan penalti, kami bercanda menemukan beberapa kompleksitas tambahan untuk ekspresi dadu bergulir. Beberapa dari mereka terlalu keterlaluan (seperti memperluas ekspresi dadu sederhana seperti 2d6argumen matriks 1 ), tetapi sisanya membuat sistem yang menarik.

Tantangan

Dengan ekspresi dadu yang kompleks, evaluasilah sesuai dengan aturan berikut dan hasilkan hasilnya.

Aturan Evaluasi Dasar

  • Setiap kali operator mengharapkan bilangan bulat tetapi menerima daftar untuk operan, jumlah daftar itu digunakan
  • Setiap kali operator mengharapkan daftar tetapi menerima integer untuk operan, integer diperlakukan sebagai daftar satu elemen yang mengandung integer itu

Operator

Semua operator adalah operator infus biner. Untuk tujuan penjelasan, aakan menjadi operan kiri, dan bakan menjadi operan kanan. Notasi daftar akan digunakan untuk contoh di mana operator dapat mengambil daftar sebagai operan, tetapi ekspresi aktual hanya terdiri dari bilangan bulat positif dan operator.

  • d: output abilangan bulat acak seragam independen di kisaran[1, b]
    • Diutamakan: 3
    • Kedua operan adalah bilangan bulat
    • Contoh: 3d4 => [1, 4, 3],[1, 2]d6 => [3, 2, 6]
  • t: ambil nilai bterendah daria
    • Diutamakan: 2
    • aadalah daftar, badalah bilangan bulat
    • Jika b > len(a), semua nilai dikembalikan
    • Contoh: [1, 5, 7]t1 => [1], [5, 18, 3, 9]t2 => [3, 5],3t5 => [3]
  • T: ambil nilai btertinggi daria
    • Diutamakan: 2
    • aadalah daftar, badalah bilangan bulat
    • Jika b > len(a), semua nilai dikembalikan
    • Contoh: [1, 5, 7]T1 => [7], [5, 18, 3, 9]T2 => [18, 9],3T5 => [3]
  • r: Jika ada unsur-unsur di bdalam a, reroll elemen-elemen, menggunakan apa pun dpernyataan yang dihasilkan mereka
    • Diutamakan: 2
    • Kedua operan adalah daftar
    • Rerolling dilakukan hanya sekali, sehingga dimungkinkan untuk tetap memiliki elemen bdalam hasilnya
    • Contoh: 3d6r1 => [1, 3, 4] => [6, 3, 4], 2d4r2 => [2, 2] => [3, 2],3d8r[1,8] => [1, 8, 4] => [2, 2, 4]
  • R: Jika ada unsur-unsur di bdalam a, reroll elemen-elemen berulang kali sampai tidak ada unsur-unsur byang hadir, menggunakan apa pun dpernyataan yang dihasilkan mereka
    • Diutamakan: 2
    • Kedua operan adalah daftar
    • Contoh: 3d6R1 => [1, 3, 4] => [6, 3, 4], 2d4R2 => [2, 2] => [3, 2] => [3, 1],3d8R[1,8] => [1, 8, 4] => [2, 2, 4]
  • +: tambahkan adan bbersama - sama
    • Diutamakan: 1
    • Kedua operan adalah bilangan bulat
    • Contoh: 2+2 => 4, [2]+[2] => 4,[3, 1]+2 => 6
  • -: kurangi bdaria
    • Diutamakan: 1
    • Kedua operan adalah bilangan bulat
    • b akan selalu kurang dari a
    • Contoh: 2-1 => 1, 5-[2] => 3,[8, 3]-1 => 10
  • .: menyatukan adan bbersama - sama
    • Diutamakan: 1
    • Kedua operan adalah daftar
    • Contoh: 2.2 => [2, 2], [1].[2] => [1, 2],3.[4] => [3, 4]
  • _: output adengan semua elemen yang bdihapus
    • Diutamakan: 1
    • Kedua operan adalah daftar
    • Contoh: [3, 4]_[3] => [4], [2, 3, 3]_3 => [2],1_2 => [1]

Aturan tambahan

  • Jika nilai akhir ekspresi adalah daftar, itu dijumlahkan sebelum output
  • Evaluasi istilah hanya akan menghasilkan bilangan bulat positif atau daftar bilangan bulat positif - ekspresi yang menghasilkan integer non-positif atau daftar yang berisi setidaknya satu bilangan bulat non-positif akan memiliki nilai-nilai digantikan oleh 1s
  • Tanda kurung dapat digunakan untuk mengelompokkan istilah dan menentukan urutan evaluasi
  • Operator dievaluasi dalam urutan prioritas tertinggi ke prioritas terendah, dengan evaluasi berjalan dari kiri ke kanan dalam kasus diutamakan terikat (sehingga 1d4d4akan dievaluasi sebagai (1d4)d4)
  • Urutan elemen dalam daftar tidak masalah - sangat dapat diterima untuk operator yang memodifikasi daftar untuk mengembalikannya dengan elemen-elemennya dalam urutan relatif berbeda
  • Istilah yang tidak dapat dievaluasi atau akan menghasilkan loop tak terbatas (suka 1d1R1atau 3d6R[1, 2, 3, 4, 5, 6]) tidak valid

Uji Kasus

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

Semua kecuali kasus uji terakhir dihasilkan dengan implementasi referensi.

Contoh yang berhasil

Ekspresi: 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](penuh 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 (selesai)

Implementasi Referensi

Implementasi referensi ini menggunakan seed konstan yang sama ( 0) untuk mengevaluasi setiap ekspresi untuk hasil yang konsisten dan dapat diuji. Ia mengharapkan input pada STDIN, dengan baris baru memisahkan setiap ekspresi.

#!/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]: Definisi kami adbuntuk argumen matriks adalah untuk menggulung AdXuntuk masing-masing Xdalam a * b, di mana A = det(a * b). Jelas itu terlalu absurd untuk tantangan ini.

Mego
sumber
Dengan jaminan -itu bakan selalu kurang dari yang asaya lihat tidak ada cara untuk mendapatkan bilangan bulat non-positif, sehingga aturan tambahan kedua tampaknya tidak ada gunanya. OTOH, _dapat menghasilkan daftar kosong, yang tampaknya berguna dalam kasus yang sama tetapi apa artinya ketika integer diperlukan? Biasanya saya katakan jumlahnya adalah 0...
Christian Sievers
@ChristianSievers 1) Saya menambahkan catatan tambahan tentang bilangan bulat non-positif untuk kejelasan. 2) Jumlah daftar kosong adalah 0. Dengan aturan no-non-positif, itu akan dievaluasi sebagai 1.
Mego
Oke, tetapi apakah itu oke sebagai hasil antara? Begitu [1,2]_([1]_[1])juga [1,2]?
Christian Sievers
@ChristianSievers No. Itu akan menghasilkan [2], karena [1]_[1] -> [] -> 0 -> 1 -> [1].
Mego

Jawaban:

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 terima kasih kepada Mr.Xcoder

-5 byte lebih banyak berkat NGN

-tentang 40 byte berkat Jonathan French

Yuck, benar-benar tolol! Ini bekerja dengan menggunakan ekspresi reguler untuk membungkus semua angka di kkelas saya , dan mengubah semua operator menjadi operator python mengerti, kemudian menggunakan metode ajaib kkelas untuk menangani matematika. The +-dan +--pada akhir untuk .dan _adalah hack untuk menjaga diutamakan yang benar. Demikian juga, saya tidak dapat menggunakan **operator untuk d karena hal itu akan membuat 1d4d4diuraikan sebagai 1d(4d4). Sebagai gantinya, saya membungkus semua angka dalam seperangkat parens tambahan dan melakukan apa pun .j, karena pemanggilan metode memiliki prioritas lebih tinggi daripada operator. Baris terakhir mengevaluasi sebagai fungsi anonim yang mengevaluasi ekspresi.

pppery
sumber
def __mod__(a, b)... Mengapa ruang antara a,dan b?
Tn. Xcoder
744 bytes
Mr. Xcoder
@ Mr.Xcoder Saya pikir Anda dapat menyimpan byte dengan menghapus ruang yang tidak perlu: ; __sub__. Dan mungkin juga di sini: lambda a,b: l(.
Jonathan Frech
1
Anda dapat menyimpan beberapa byte dengan membungkus seluruh kode Anda dalam sebuah exec("""...""".replace("...","..."))pernyataan dan mengganti string yang sering muncul (seperti return ) dengan satu karakter. Namun, bagi saya exec-strategy selalu tampak agak tidak menarik ...
Jonathan Frech
tubuh __mod__dan __add__tidak perlu bahwa banyak indent
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

Cobalah online!

Ini adalah algoritma halaman shunting dari implementasi referensi digabung dengan evaluate_dice(), tanpa omong kosong dan berorientasi objek omong kosong. Hanya dua tumpukan yang digunakan: huntuk operator dan vuntuk nilai. Parsing dan evaluasi saling terkait.

Hasil antara direpresentasikan sebagai matriks 2 × N di mana baris pertama adalah nilai acak dan baris kedua adalah jumlah sisi pada dadu yang menghasilkannya. Ketika hasil tidak dihasilkan oleh operator "d" melempar dadu, baris kedua berisi angka acak. Nilai acak tunggal adalah matriks 2 × 1 dan dengan demikian tidak dapat dibedakan dari daftar 1-elemen.

ngn
sumber
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)

Titik masuknya adalah E. Ini berlaku persamaan reguler berulang. Pertama itu mengganti semua bilangan bulat xdengan tuple daftar tunggal [(x,0)]. Kemudian ekspresi reguler pertama melakukan doperasi, dengan mengganti semua [(x,0)]d[(b,0)]dengan representasi string dari array tuple seperti [(1,b),(2,b),(3,b)]. Elemen kedua dari setiap tuple adalah operan kedua d. Kemudian, ekspresi reguler berikutnya melakukan masing-masing operator lainnya. Ada regex khusus untuk menghapus parens dari ekspresi yang dihitung penuh.

rekursif
sumber
3

Clojure, 731 720 byte

(saat baris baru dihapus)

Pembaruan: implementasi yang lebih singkat dari 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 "))))))

Ini terdiri dari empat bagian utama:

  • N: memaksa daftar menjadi angka
  • g: mengevaluasi struktur sintaksis abstrak (ekspresi-S dengan 3 item)
  • F: mengonversi infix AST ke notasi awalan (S-expressions), juga menerapkan prioritas urutan operan
  • f: digunakan read-stringuntuk mengubah string menjadi urutan angka dan simbol (infiks AST), pipa mereka melalui F -> g -> N, mengembalikan nomor hasil.

Saya tidak yakin bagaimana menguji secara menyeluruh ini, mungkin melalui uji statistik terhadap implementasi referensi? Setidaknya AST dan evaluasinya relatif mudah diikuti.

Contoh S-ekspresi dari 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))))

Kurang bermain golf dengan hasil dan tes internasional:

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

Juru bahasa yang dibangun menggunakan tatsu, perpustakaan parser PEG. Argumen pertama tatsu.parser()adalah tata bahasa PEG.

class D(untuk Die) subkelas tipe bawaan int. Nilainya adalah hasil dari roll. Atributnya .sadalah jumlah sisi pada die.

class S memiliki tindakan semantik untuk parser, dan mengimplementasikan penerjemah.

RootTwo
sumber