treerazer/dice/dice.go
Malachy Byrne 5cf90653bf
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
continuous-integration/drone/tag Build is passing
Convert to lowercase when checking prefix
Return flat modifier in dice roll
Use log.Print instead of log.Fatal where makes sense
2025-04-01 22:36:32 +01:00

176 lines
3.9 KiB
Go

package dice
import (
"errors"
"fmt"
"math/rand/v2"
"regexp"
"strconv"
)
type DieExp struct {
Size int
Count int
Add int
KeepHigh int
KeepLow int
Explode bool
}
type DieResult struct {
Size int
Result int
}
func (d DieExp) Roll() ([]DieResult, int, error) {
results := make([]DieResult, 0)
var err error
for i := 0; i < d.Count; i++ {
results, err = addDice(d, results)
if err != nil {
return nil, 0, err
}
}
if d.KeepHigh != 0 {
highResults := make([]DieResult, 0)
for i := 0; i < d.KeepHigh; i++ {
var high int
for j, val := range results {
if results[high].Result > val.Result {
fmt.Printf("Result %v higher than %v\n", results[high].Result, results[j].Result)
high = j
} else {
fmt.Printf("Result %v lower than %v\n", results[high].Result, results[j].Result)
}
}
highResults = append(highResults, results[high])
results = append(results[:high], results[high+1:]...)
}
results = highResults
}
if d.KeepLow > len(results) {
return nil, 0, errors.New("keep low higher than remaining results")
}
if d.KeepLow != 0 {
lowResults := make([]DieResult, 0)
for i := 0; i < d.KeepLow; i++ {
var low int
for j, val := range results {
if results[low].Result < val.Result {
fmt.Printf("Result %v lower than %v\n", results[low].Result, results[j].Result)
low = j
} else {
fmt.Printf("Result %v higher than %v\n", results[low].Result, results[j].Result)
}
}
lowResults = append(lowResults, results[low])
results = append(results[:low], results[low+1:]...)
}
results = lowResults
}
if d.Explode {
var neededExplosions int
for _, result := range results {
if result.Result == result.Size {
neededExplosions++
}
}
for explosions := 0; explosions < neededExplosions; explosions++ {
alreadyRolled := len(results)
results, err = addDice(d, results)
if err != nil {
return nil, 0, err
}
for _, result := range results[alreadyRolled:] {
if result.Result == result.Size {
neededExplosions++
}
}
}
}
return results, d.Add, nil
}
func CreateFromExp(exp string) (DieExp, error) {
d := DieExp{
Count: 1,
KeepHigh: 0,
KeepLow: 0,
Explode: false,
}
match := regexp.MustCompile("[0-9]*d[0-9]+([hl][0-9]+)*e?([+-][0-9]+)?")
countMatch := regexp.MustCompile("^[0-9]*")
sizeMatch := regexp.MustCompile("d[0-9]+")
highMatch := regexp.MustCompile("h[0-9]+")
lowMatch := regexp.MustCompile("l[0-9]+")
explodeMatch := regexp.MustCompile("e")
addMatch := regexp.MustCompile("[+\\-][0-9]+")
match.Longest()
countMatch.Longest()
sizeMatch.Longest()
highMatch.Longest()
lowMatch.Longest()
matched := match.FindString(exp)
if len(matched) != len(exp) {
return DieExp{}, errors.New("dice expression invalid")
}
countString := countMatch.FindString(exp)
if len(countString) != 0 {
count, _ := strconv.Atoi(countString)
d.Count = count
}
sizeString := sizeMatch.FindString(exp)
if len(sizeString) != 0 {
size, _ := strconv.Atoi(sizeString[1:])
d.Size = size
}
highString := highMatch.FindString(exp)
if len(highString) != 0 {
high, _ := strconv.Atoi(highString[1:])
d.KeepHigh = high
}
lowString := lowMatch.FindString(exp)
if len(lowString) != 0 {
low, _ := strconv.Atoi(lowString[1:])
d.KeepLow = low
}
if explodeMatch.MatchString(exp) {
d.Explode = true
}
addString := addMatch.FindString(exp)
if len(addString) != 0 {
add, _ := strconv.Atoi(addString[1:])
if addString[0] == '-' {
add = 0 - add
}
d.Add = add
}
return d, nil
}
func addDice(d DieExp, results []DieResult) ([]DieResult, error) {
if d.Size < 2 {
return nil, errors.New("dice size is too small. Should be at least 2")
}
if len(results) > 1000 {
return nil, errors.New("too many dice rolled. This may be caused by a dice explosion that got out of hand")
}
result := DieResult{
Size: d.Size,
Result: rand.IntN(d.Size) + 1,
}
return append(results, result), nil
}