fixed an issue with calculating lowest and highest dice roll numbers

This commit is contained in:
Grimsace
2026-02-12 09:22:11 -06:00
parent 7badd1c02f
commit 94adcb152c
2 changed files with 65 additions and 15 deletions
+20 -8
View File
@@ -44,9 +44,9 @@ func expandDiceNotation(expression string) (string, string, error) {
result := expression result := expression
diceRollsStr := expression diceRollsStr := expression
// Pattern to match dice notation: [count]d[sides][H|L] // Pattern to match dice notation: [H|L]?[count]d[sides][H|L]?
// Examples: d20, 2d6, 3d6H, 4d8L, dx (where x is placeholder) // Examples: d20, 2d6, 3d6H, 4d8L, h2d20, l3d6, dx (where x is placeholder)
dicePattern := regexp.MustCompile(`(\d+)?d(\d+|x)([HL])?`) dicePattern := regexp.MustCompile(`([HL])?(\d+)?d(\d+|x)([HL])?`)
// Process all dice matches // Process all dice matches
matches := dicePattern.FindAllStringSubmatchIndex(result, -1) matches := dicePattern.FindAllStringSubmatchIndex(result, -1)
@@ -58,14 +58,26 @@ func expandDiceNotation(expression string) (string, string, error) {
end := match[1] end := match[1]
// Extract components // Extract components
countStr := "" prefixModifier := ""
if match[2] != -1 { if match[2] != -1 {
countStr = result[match[2]:match[3]] prefixModifier = result[match[2]:match[3]]
} }
sidesStr := result[match[4]:match[5]] countStr := ""
if match[4] != -1 {
countStr = result[match[4]:match[5]]
}
sidesStr := result[match[6]:match[7]]
suffixModifier := ""
if match[8] != -1 {
suffixModifier = result[match[8]:match[9]]
}
// Determine which modifier to use (priority: suffix > prefix)
modifier := "" modifier := ""
if match[6] != -1 { if suffixModifier != "" {
modifier = result[match[6]:match[7]] modifier = suffixModifier
} else if prefixModifier != "" {
modifier = prefixModifier
} }
// Determine count (default is 1) // Determine count (default is 1)
+45 -7
View File
@@ -85,14 +85,20 @@ type Term struct {
} }
// parseTerms parses a dice expression into terms // parseTerms parses a dice expression into terms
// parseTerms parses a dice expression into terms, handling H/L flexibly
func parseTerms(expression string) ([]Term, error) { func parseTerms(expression string) ([]Term, error) {
var terms []Term var terms []Term
// Split by + and -, keeping the operators // Remove spaces
expression = strings.TrimSpace(expression)
// Split by + and - while keeping the operators
parts := regexp.MustCompile(`([+\-])`).Split(expression, -1) parts := regexp.MustCompile(`([+\-])`).Split(expression, -1)
currentOp := "+" currentOp := "+"
dicePattern := regexp.MustCompile(`^(\d*)d(\d+)([HL])?$`) pendingModifier := "" // Store H or L to apply to the next dice roll
// Pattern to match: optional H/L prefix, count, d, sides, optional H/L suffix
dicePattern := regexp.MustCompile(`^([HL])?(\d*)d(\d+)([HL])?$`)
for _, part := range parts { for _, part := range parts {
part = strings.TrimSpace(part) part = strings.TrimSpace(part)
@@ -107,19 +113,32 @@ func parseTerms(expression string) ([]Term, error) {
continue continue
} }
// Check for standalone H or L modifier
if part == "H" || part == "L" {
pendingModifier = part
continue
}
// Try to match dice notation // Try to match dice notation
matches := dicePattern.FindStringSubmatch(part) matches := dicePattern.FindStringSubmatch(part)
if matches != nil { if matches != nil {
// Extract components
prefixModifier := matches[1] // H or L before the dice
countStr := matches[2]
sidesStr := matches[3]
suffixModifier := matches[4] // H or L after the dice
count := 1 count := 1
if matches[1] != "" { if countStr != "" {
c, err := strconv.Atoi(matches[1]) c, err := strconv.Atoi(countStr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
count = c count = c
} }
sides, err := strconv.Atoi(matches[2]) sides, err := strconv.Atoi(sidesStr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -128,7 +147,15 @@ func parseTerms(expression string) ([]Term, error) {
return nil, fmt.Errorf("invalid dice: %dd%d", count, sides) return nil, fmt.Errorf("invalid dice: %dd%d", count, sides)
} }
modifier := matches[3] // Determine which modifier to use (priority: suffix > prefix > pending)
modifier := ""
if suffixModifier != "" {
modifier = suffixModifier
} else if prefixModifier != "" {
modifier = prefixModifier
} else if pendingModifier != "" {
modifier = pendingModifier
}
terms = append(terms, Term{ terms = append(terms, Term{
isDice: true, isDice: true,
@@ -137,9 +164,15 @@ func parseTerms(expression string) ([]Term, error) {
modifier: modifier, modifier: modifier,
op: currentOp, op: currentOp,
}) })
currentOp = "+" currentOp = "+"
pendingModifier = ""
} else { } else {
// Try to parse as constant // Try to parse as constant (but reset pending modifier if it was set)
if pendingModifier != "" {
return nil, fmt.Errorf("modifier %s can only be applied to dice rolls", pendingModifier)
}
val, err := strconv.Atoi(part) val, err := strconv.Atoi(part)
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid term: %s", part) return nil, fmt.Errorf("invalid term: %s", part)
@@ -154,6 +187,11 @@ func parseTerms(expression string) ([]Term, error) {
} }
} }
// If we ended with a pending modifier, that's an error
if pendingModifier != "" {
return nil, fmt.Errorf("modifier %s at end of expression with no dice roll to apply to", pendingModifier)
}
return terms, nil return terms, nil
} }