summaryrefslogtreecommitdiff
path: root/preroll.go
blob: ab3d0a05d2786fbc8a8f7cf6bc3c975d41c46aac (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
/* Copyright (C) 2011, 2013-2014, 2017, 2019 Luke Shumaker <lukeshu@sbcglobal.net> */
package main

import (
	"math"
	"fmt"
	"os"
	"regexp"
	"strconv"
	"strings"
)

func usage() {
	fmt.Printf("Usage: %s <SPECSLIST>...\n", os.Args[0])
	fmt.Println("SPECLIST = [-]SPEC[<<+|->SPEC>...]")
	fmt.Println("SPEC = [<COUNT>]d<SIZE> | <MOD>")
}

var parser = regexp.MustCompile("^(([0-9]*)d)?([0-9]+)$")

func rollSpec(spec string) (mean float64, variance float64) {
	parts := parser.FindStringSubmatch(spec)
	if len(parts) < 4 {
		usage()
		os.Exit(1)
	}

	if parts[1] == "" {
		mean, _ := strconv.Atoi(parts[3])
		return float64(mean), 0
	} else {
		dice, _ := strconv.Atoi(parts[2])
		die_size, _ := strconv.Atoi(parts[3])
		if dice < 1 {
			dice = 1
		}

		// - Let 'd' be the die_size
		// - Let 'X' be the the uniform random variable that is the result of a 1d${d} roll
		// - E(X)   = (1+d)/2
		mean = float64(1 + die_size)/2.0
		// - Var(X) = E(X²)-E(X)²
		// - E(X²)  = (Σi² for i=1..d) / d
		//          = ¹/₆d(d+1)(2d+1)  / d
		//          =    d(d+1)(2d+1)  / (6d)
		// ∴ Var(X) = (d(d+1)(2d+1) / (6d)) - ((1+d)/2)²
		//          = ¹/₁₂(d+1)(d-1)
		variance = float64((die_size+1)*(die_size-1))/12.0

		mean *= float64(dice)
		variance *= float64(dice)

		return
	}
}

func roll(speclist string) {
	var mean, variance float64

	neg := strings.HasPrefix(speclist, "-")
	speclist = strings.TrimPrefix(speclist, "-")
	for {
		sep := strings.IndexAny(speclist, "+-")
		var spec string
		if sep < 0 {
			spec = speclist
		} else {
			spec = speclist[:sep]
		}

		_mean, _variance := rollSpec(spec)
		if neg {
			_mean = -_mean
		}
		mean += _mean
		variance += _variance

		if sep < 0 {
			break
		}
		neg = speclist[sep] == '-'
		speclist = speclist[sep+1:]
	}

	fmt.Printf("µ=%v σ=%.2v\n", mean, math.Sqrt(variance))
}

func main() {
	if len(os.Args) < 2 {
		usage()
	}
	for _, arg := range os.Args[1:] {
		roll(arg)
	}
}