twitch-bot-tools/dice/main.go
Knut Ahlers f8a616e284
Add unique-mode for dice
Signed-off-by: Knut Ahlers <knut@ahlers.me>
2024-02-01 18:48:41 +01:00

146 lines
3.6 KiB
Go

package main
import (
"bytes"
"crypto/rand"
"encoding/json"
"fmt"
"math/big"
"os"
"text/template"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/Luzifer/rconfig/v2"
"github.com/Luzifer/twitch-bot/v3/plugins"
)
var (
cfg = struct {
BotRespond bool `flag:"bot-respond,b" default:"false" description:"Wrap output in a respond directive for twitch-bot"`
Count int64 `flag:"count,c" default:"1" description:"How many dice to throw?"`
LogLevel string `flag:"log-level" default:"info" description:"Log level (debug, info, warn, error, fatal)"`
Sides int64 `flag:"sides,s" default:"6" description:"How many sides does the dice have?"`
Template string `flag:"template" vardefault:"tpl" description:"Template to format the result with"`
Type string `flag:"type,t" default:"dice" description:"What to throw (coin, dice)"`
Unique bool `flag:"unique,u" default:"false" description:"Cannot roll the same number twice"`
VersionAndExit bool `flag:"version" default:"false" description:"Prints current version and exits"`
}{}
version = "dev"
)
func initApp() error {
rconfig.AutoEnv(true)
rconfig.SetVariableDefaults(map[string]string{
"tpl": `I threw {{.count}}x {{if eq .type "coin"}}coin{{else}}W{{.sides}}{{end}} for you and got:{{ range .results}} {{ if eq $.type "coin" }}{{ if eq . 1 }}Head{{ else }}Number{{ end }}{{ else }}{{ . }}{{ end }}{{ end }}`,
})
if err := rconfig.ParseAndValidate(&cfg); err != nil {
return errors.Wrap(err, "parsing cli options")
}
l, err := logrus.ParseLevel(cfg.LogLevel)
if err != nil {
return errors.Wrap(err, "parsing log-level")
}
logrus.SetLevel(l)
return nil
}
func main() {
var (
err error
results []int64
sum int64
)
if err = initApp(); err != nil {
logrus.WithError(err).Fatal("initializing app")
}
if cfg.VersionAndExit {
logrus.WithField("version", version).Info("twitch-bot-tools/dice")
os.Exit(0)
}
switch cfg.Type {
case "coin":
results, sum = getResults(2, cfg.Count) //nolint:gomnd // Makes no sense to extract
case "dice":
if cfg.Sides == 0 {
// There is no [0..0] dice.
logrus.Fatal("there is no 0-sided dice")
}
results, sum = getResults(cfg.Sides, cfg.Count)
default:
logrus.WithField("type", cfg.Type).Fatal("don't know how to throw that")
}
t, err := template.New("output").Parse(cfg.Template)
if err != nil {
logrus.WithError(err).Fatal("parsing template")
}
text := new(bytes.Buffer)
if err = t.Execute(text, map[string]interface{}{
"count": cfg.Count,
"results": results,
"sides": cfg.Sides,
"sum": sum,
"type": cfg.Type,
}); err != nil {
logrus.WithError(err).Fatal("executing template")
}
if !cfg.BotRespond {
fmt.Println(text.String()) //nolint:forbidigo // This is an expected stdout output
return
}
output := []plugins.RuleAction{
{
Type: "respond",
Attributes: plugins.FieldCollectionFromData(map[string]any{
"message": text.String(),
}),
},
}
if err = json.NewEncoder(os.Stdout).Encode(output); err != nil {
logrus.WithError(err).Fatal("encoding bot response")
}
}
func containsNum(s []int64, n int64) bool {
for _, sn := range s {
if sn == n {
return true
}
}
return false
}
func getRandomNumber(sides int64) int64 {
n, _ := rand.Int(rand.Reader, big.NewInt(sides))
return n.Int64()
}
func getResults(sides, count int64) (res []int64, sum int64) {
for len(res) < int(count) {
v := getRandomNumber(sides) + 1
if cfg.Unique && containsNum(res, v) {
continue
}
res = append(res, v)
sum += v
}
return res, sum
}