Skip to content

Commit 0ec52bf

Browse files
committed
Improve dice roll parsing
1 parent 282f32c commit 0ec52bf

File tree

3 files changed

+93
-65
lines changed

3 files changed

+93
-65
lines changed

pkg/posting/formatting.go

Lines changed: 34 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package posting
22

33
import (
4+
"errors"
45
"fmt"
56
"html/template"
67
"math/rand"
@@ -18,7 +19,7 @@ var (
1819
msgfmtr MessageFormatter
1920
urlRE = regexp.MustCompile(`https?://(\S+)`)
2021
unsetBBcodeTags = []string{"center", "color", "img", "quote", "size"}
21-
diceRoller = regexp.MustCompile(`(?i)(\S*)\[(\d*)d(\d+)(?:([+-])(\d+))?\](\S*)`)
22+
diceRollRE = regexp.MustCompile(`\[(\d*)d(\d+)(?:([+-])(\d+))?\]`)
2223
)
2324

2425
// InitPosting prepares the formatter and the temp post pruner
@@ -134,50 +135,42 @@ func FormatMessage(message string, boardDir string) (template.HTML, error) {
134135
return template.HTML(strings.Join(postLines, "<br />")), nil // skipcq: GSC-G203
135136
}
136137

137-
func ApplyDiceRoll(p *gcsql.Post) (rollSum int, err error) {
138-
words := strings.Split(string(p.Message), " ")
139-
for w, word := range words {
140-
roll := diceRoller.FindStringSubmatch(word)
141-
if len(roll) == 0 {
142-
continue
143-
}
138+
func diceRoller(numDice int, diceSides int, modifier int) int {
139+
rollSum := 0
140+
for i := 0; i < numDice; i++ {
141+
rollSum += rand.Intn(diceSides) + 1 // skipcq: GSC-G404
142+
}
143+
return rollSum + modifier
144+
}
145+
146+
func ApplyDiceRoll(p *gcsql.Post) error {
147+
var err error
148+
result := diceRollRE.ReplaceAllStringFunc(string(p.Message), func(roll string) string {
149+
rollMatch := diceRollRE.FindStringSubmatch(roll)
144150
numDice := 1
145-
if roll[2] != "" {
146-
numDice, err = strconv.Atoi(roll[2])
147-
if err != nil {
148-
return 0, err
149-
}
151+
if rollMatch[1] != "" {
152+
numDice, _ = strconv.Atoi(rollMatch[1])
150153
}
151-
dieSize, err := strconv.Atoi(roll[3])
152-
if err != nil {
153-
return 0, err
154+
if numDice < 1 {
155+
err = errors.New("number of dice must be at least 1")
156+
return roll
154157
}
155-
if numDice < 1 || dieSize < 1 {
156-
return 0, fmt.Errorf("dice roll too small")
158+
dieSize, _ := strconv.Atoi(rollMatch[2])
159+
if dieSize <= 1 {
160+
err = errors.New("die size must be greater than 1")
161+
return roll
157162
}
158-
for i := 0; i < numDice; i++ {
159-
rollSum += rand.Intn(dieSize) + 1 // skipcq: GSC-G404
160-
switch roll[4] {
161-
case "+":
162-
mod, err := strconv.Atoi(roll[5])
163-
if err != nil {
164-
return 0, err
165-
}
166-
rollSum += mod
167-
case "-":
168-
mod, err := strconv.Atoi(roll[5])
169-
if err != nil {
170-
return 0, err
171-
}
172-
rollSum -= mod
173-
}
163+
modifierIsNegative := rollMatch[3] == "-"
164+
modifier, _ := strconv.Atoi(rollMatch[4])
165+
if modifierIsNegative {
166+
modifier = -modifier
174167
}
175-
words[w] = fmt.Sprintf(`%s<span class="dice-roll">%dd%d`, roll[1], numDice, dieSize)
176-
if roll[4] != "" {
177-
words[w] += roll[4] + roll[5]
178-
}
179-
words[w] += fmt.Sprintf(" = %d</span>%s", rollSum, roll[6])
168+
rollSum := diceRoller(numDice, dieSize, modifier)
169+
return fmt.Sprintf(`<span class="dice-roll">%dd%d%s%s = %d</span>`, numDice, dieSize, rollMatch[3], rollMatch[4], rollSum)
170+
})
171+
if err != nil {
172+
return err
180173
}
181-
p.Message = template.HTML(strings.Join(words, " ")) // skipcq: GSC-G203
182-
return
174+
p.Message = template.HTML(result) // skipcq: GSC-G203
175+
return nil
183176
}

pkg/posting/formatting_test.go

Lines changed: 58 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -39,31 +39,31 @@ end)`
3939
var (
4040
diceTestCases = []diceRollerTestCase{
4141
{
42-
desc: "[1d6]",
42+
desc: "[2d6]",
4343
post: gcsql.Post{
44-
MessageRaw: "before [1d6] after",
44+
MessageRaw: "[2d6]",
4545
},
46-
matcher: regexp.MustCompile(`before <span class="dice-roll">1d6 = \d</span> after`),
47-
expectMin: 1,
48-
expectMax: 6,
46+
matcher: regexp.MustCompile(`<span class="dice-roll">2d6 = \d{1,2}</span>`),
47+
expectMin: 2,
48+
expectMax: 12,
4949
},
5050
{
51-
desc: "[1d6+1]",
51+
desc: "[2d6+1]",
5252
post: gcsql.Post{
53-
MessageRaw: "before [1d6+1] after",
53+
MessageRaw: "[2d6+1]",
5454
},
55-
matcher: regexp.MustCompile(`before <span class="dice-roll">1d6\+1 = \d</span> after`),
56-
expectMin: 2,
57-
expectMax: 7,
55+
matcher: regexp.MustCompile(`<span class="dice-roll">2d6\+1 = \d{1,2}</span>`),
56+
expectMin: 3,
57+
expectMax: 13,
5858
},
5959
{
60-
desc: "[1d6-1]",
60+
desc: "[2d6-1]",
6161
post: gcsql.Post{
62-
MessageRaw: "before [1d6-1] after",
62+
MessageRaw: "[2d6-1]",
6363
},
64-
matcher: regexp.MustCompile(`before <span class="dice-roll">1d6-1 = \d</span> after`),
65-
expectMin: 0,
66-
expectMax: 5,
64+
matcher: regexp.MustCompile(`<span class="dice-roll">2d6-1 = \d{1,2}</span>`),
65+
expectMin: 1,
66+
expectMax: 11,
6767
},
6868
{
6969
desc: "[d8]",
@@ -88,10 +88,48 @@ var (
8888
post: gcsql.Post{
8989
MessageRaw: `<script>alert("lol")</script>[1d6]<script>alert("lmao")</script>`,
9090
},
91-
expectError: false,
92-
matcher: regexp.MustCompile(`&lt;script&gt;alert\(&#34;lol&#34;\)&lt;/script&gt;<span class="dice-roll">1d6 = \d</span>&lt;script&gt;alert\(&#34;lmao&#34;\)&lt;/script&gt;`),
93-
expectMin: 1,
94-
expectMax: 6,
91+
matcher: regexp.MustCompile(`&lt;script&gt;alert\(&#34;lol&#34;\)&lt;/script&gt;<span class="dice-roll">1d6 = \d</span>&lt;script&gt;alert\(&#34;lmao&#34;\)&lt;/script&gt;`),
92+
expectMin: 1,
93+
expectMax: 6,
94+
},
95+
{
96+
desc: "two dice rolls, no space",
97+
post: gcsql.Post{
98+
MessageRaw: "[d6][2d6]",
99+
},
100+
matcher: regexp.MustCompile(`<span class="dice-roll">1d6 = \d</span><span class="dice-roll">2d6 = \d{1,2}</span>`),
101+
expectMin: 0,
102+
expectMax: 7,
103+
},
104+
{
105+
desc: "multiple dice rolls, no space",
106+
post: gcsql.Post{
107+
MessageRaw: "[d6][2d20-2][3d8+1]",
108+
},
109+
matcher: regexp.MustCompile(`<span class="dice-roll">1d6 = \d</span><span class="dice-roll">2d20-2 = \d{1,2}</span><span class="dice-roll">3d8\+1 = \d{1,2}</span>`),
110+
expectMin: 0,
111+
expectMax: 38,
112+
},
113+
{
114+
desc: "invalid number of dice",
115+
post: gcsql.Post{
116+
MessageRaw: "[0d6]",
117+
},
118+
expectError: true,
119+
},
120+
{
121+
desc: "invalid die size",
122+
post: gcsql.Post{
123+
MessageRaw: "[1d0]",
124+
},
125+
expectError: true,
126+
},
127+
{
128+
desc: "invalid modifier",
129+
post: gcsql.Post{
130+
MessageRaw: "[1d6+]",
131+
},
132+
matcher: regexp.MustCompile(`\[1d6\+\]`),
95133
},
96134
}
97135
)
@@ -148,15 +186,12 @@ func diceRollRunner(t *testing.T, tC *diceRollerTestCase) {
148186
var err error
149187
tC.post.Message, err = FormatMessage(tC.post.MessageRaw, "")
150188
assert.NoError(t, err)
151-
result, err := ApplyDiceRoll(&tC.post)
189+
err = ApplyDiceRoll(&tC.post)
152190
if tC.expectError {
153191
assert.Error(t, err)
154-
assert.Equal(t, 0, result)
155192
} else {
156193
assert.NoError(t, err)
157194
assert.Regexp(t, tC.matcher, tC.post.Message)
158-
assert.GreaterOrEqual(t, result, tC.expectMin)
159-
assert.LessOrEqual(t, result, tC.expectMax)
160195
}
161196
if t.Failed() {
162197
t.FailNow()

pkg/posting/post.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ func doFormatting(post *gcsql.Post, board *gcsql.Board, request *http.Request, e
226226
errEv.Err(err).Caller().Msg("Unable to format message")
227227
return errors.New("unable to format message")
228228
}
229-
if _, err = ApplyDiceRoll(post); err != nil {
229+
if err = ApplyDiceRoll(post); err != nil {
230230
errEv.Err(err).Caller().Msg("Error applying dice roll")
231231
return err
232232
}

0 commit comments

Comments
 (0)