From 7a2646ddcef1f2f93bb778f782ac5b3678e4cec8 Mon Sep 17 00:00:00 2001 From: Vaclav Lunak Date: Sat, 18 Nov 2023 13:29:38 +0100 Subject: [PATCH 1/2] extend coin flip syntax to support multiple flips --- dice.go | 57 ++++++++++++++++++++++++++++++++++++++++++++++++---- dice_test.go | 2 +- main.go | 6 +++--- utils.go | 1 + 4 files changed, 58 insertions(+), 8 deletions(-) diff --git a/dice.go b/dice.go index 0a7f7c2..cbffbc1 100644 --- a/dice.go +++ b/dice.go @@ -118,9 +118,58 @@ func validateDice(input string, dice []string) (Roll, error) { return wellFormedDice, nil } -func flipCoin() string { - coin := []string{"Heads", "Tails"} - side := coin[rand.Intn(len(coin))] +func flipCoin(input string) string { + coinCount, err := parseCoinCount(input) + if err != nil { + return err.Error() + } + + flips := make([]int, coinCount) + for i := 0; i < coinCount; i++ { + flips[i] = rand.Intn(2) + } + return formatFlips(flips, coinCount) +} + +func parseCoinCount(input string) (int, error) { + coinMaximum := 50 + + coins := coinRegex.FindStringSubmatch(input) + if coins[1] == "" { + // no number specified - implicit one flip + return 1, nil + } + + numCoins, err := strconv.Atoi(coins[1]) + if err != nil { + return 0, errors.New("malformed coin toss") + } - return fmt.Sprintf("%s.", side) + if numCoins > coinMaximum { + return 0, errors.New("malformed coin toss (max count is 50)") + } + if numCoins < 1 { + return 0, errors.New("malformed coin toss (min count is 1)") + } + return numCoins, nil } + +func formatFlips(flips []int, count int) string { + prefix := "" + coinNames := []string{"Heads", "Tails"} + joiner := ", " + if count > 5 { + prefix = fmt.Sprintf("%d coins: ", count) + coinNames = []string{"H", "T"} + joiner = "" + } + + formatted := make([]string, len(flips)) + for i := 0; i < len(formatted); i++ { + formatted[i] = coinNames[flips[i]] + } + + return fmt.Sprintf("%s%s.", prefix, strings.Join(formatted, joiner)) +} + + diff --git a/dice_test.go b/dice_test.go index 902b16b..2f4ba26 100644 --- a/dice_test.go +++ b/dice_test.go @@ -8,7 +8,7 @@ import ( func TestFlipCoin(t *testing.T) { for i := 1; i < 10; i++ { - result := flipCoin() + result := flipCoin("coin") validOutput := regexp.MustCompile(`(?:Heads|Tails).`) if !(validOutput.MatchString(result)) { t.Errorf(`FAIL: Expected heads or tails in output, but result was \"%s\"`, result) diff --git a/main.go b/main.go index cbfea29..4b969a0 100644 --- a/main.go +++ b/main.go @@ -161,7 +161,7 @@ func printHelp() string { ret = append(ret, "!uncard/vanguard/plane/scheme to bring up normally filtered out cards") ret = append(ret, "!url to bring up the links to policy documents") ret = append(ret, "!roll to roll X-sided die; !roll to roll X Y-sided dice") - ret = append(ret, "!coin to flip a coin (heads/tails)") + ret = append(ret, "!coin to flip a coin (heads/tails); !coin to flip X coins") ret = append(ret, "https://github.com/Fryyyyy/Fryatog/issues for bugs & feature requests") return strings.Join(ret, " ยท ") } @@ -434,9 +434,9 @@ func handleCommand(params *fryatogParams, c chan string) { c <- rollDice(message) return - case message == "coin": + case coinRegex.MatchString(message): log.Debug("Coin flip") - c <- flipCoin() + c <- flipCoin(message) return case ruleRegexp.MatchString(message), diff --git a/utils.go b/utils.go index 62196cb..9571101 100644 --- a/utils.go +++ b/utils.go @@ -28,6 +28,7 @@ var ( wordStartingWithBang = regexp.MustCompile(`\s+! *\S+`) diceRegex = regexp.MustCompile(`^(?:roll )?\s*(.*?)d(\d+)([+-]\d+)?`) + coinRegex = regexp.MustCompile(`^coin(?:\s+(\d+))?`) cardMetadataRegex = regexp.MustCompile(`(?i)^(?:rulings?|reminder|flavou?r) `) From b2c09057c5ef744fe3493662d2dcf3aaa2690793 Mon Sep 17 00:00:00 2001 From: Vaclav Lunak Date: Tue, 21 Nov 2023 18:18:22 +0100 Subject: [PATCH 2/2] added coin flip tests --- dice_test.go | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/dice_test.go b/dice_test.go index 2f4ba26..828f316 100644 --- a/dice_test.go +++ b/dice_test.go @@ -7,13 +7,41 @@ import ( ) func TestFlipCoin(t *testing.T) { + validOutput := regexp.MustCompile(`(?:Heads|Tails).`) for i := 1; i < 10; i++ { result := flipCoin("coin") - validOutput := regexp.MustCompile(`(?:Heads|Tails).`) if !(validOutput.MatchString(result)) { t.Errorf(`FAIL: Expected heads or tails in output, but result was \"%s\"`, result) } } + + validOutput = regexp.MustCompile(`(?:(?:Heads|Tails), ){2}(?:Heads|Tails)\.`) + for i := 1; i < 10; i++ { + result := flipCoin("coin 3") + if !(validOutput.MatchString(result)) { + t.Errorf(`FAIL: Expected list of heads or tails in output, but result was \"%s\"`, result) + } + } + + validOutput = regexp.MustCompile(`10 coins: [HT]{10}\.`) + for i := 1; i < 10; i++ { + result := flipCoin("coin 10") + if !(validOutput.MatchString(result)) { + t.Errorf(`FAIL: Expected list of H or T in output, but result was \"%s\"`, result) + } + } + + errorMsg := "malformed coin toss (max count is 50)" + result := flipCoin("coin 99") + if result != errorMsg { + t.Errorf(`FAIL: Expected count exceeded error, got "%s"`, result) + } + + errorMsg = "malformed coin toss (min count is 1)" + result = flipCoin("coin 0") + if result != errorMsg { + t.Errorf(`FAIL: Expected count subceeded error, got "%s"`, result) + } } func TestRollDice(t *testing.T) {