Skip to content

Commit 9baaad6

Browse files
Refactor cointoss logic into separate package and add unit tests
1 parent 735a8d5 commit 9baaad6

4 files changed

Lines changed: 81 additions & 40 deletions

File tree

.github/workflows/ci.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,7 @@ jobs:
1717
go-version: '1.23' # You can change this to your desired Go version
1818

1919
- name: Build
20-
run: go build -v ./...
20+
run: go build -v ./...
21+
22+
- name: Test
23+
run: go test -v ./...

cmd/cointoss.go

Lines changed: 5 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,47 +2,13 @@ package cmd
22

33
import (
44
"fmt"
5-
"math/rand"
65
"strings"
7-
"time"
6+
7+
"github.com/chrisreddington/gh-game/internal/cointoss" // adjust import path as needed
88

99
"github.com/spf13/cobra"
1010
)
1111

12-
// Helper functions
13-
func tossCoin() string {
14-
rand.Seed(time.Now().UnixNano())
15-
if rand.Float32() < 0.5 {
16-
return "heads"
17-
}
18-
return "tails"
19-
}
20-
21-
func validateGuess(guess string) error {
22-
guess = strings.ToLower(strings.TrimSpace(guess))
23-
if guess != "heads" && guess != "tails" {
24-
return fmt.Errorf("guess must be either 'heads' or 'tails'")
25-
}
26-
return nil
27-
}
28-
29-
func getNextGuess() (string, bool) {
30-
fmt.Print("Play again? Enter 'heads' or 'tails' (or 'quit' to end): ")
31-
var answer string
32-
fmt.Scanln(&answer)
33-
34-
if strings.ToLower(strings.TrimSpace(answer)) == "quit" {
35-
return "", false
36-
}
37-
38-
if err := validateGuess(answer); err != nil {
39-
fmt.Println(err)
40-
return getNextGuess()
41-
}
42-
43-
return strings.ToLower(strings.TrimSpace(answer)), true
44-
}
45-
4612
var cointossCmd = &cobra.Command{
4713
Use: "cointoss [guess]",
4814
Short: "Toss a coin",
@@ -51,22 +17,22 @@ var cointossCmd = &cobra.Command{
5117
if len(args) != 1 {
5218
return fmt.Errorf("requires exactly 1 argument (guess)")
5319
}
54-
return validateGuess(args[0])
20+
return cointoss.ValidateGuess(args[0])
5521
},
5622
Run: func(cmd *cobra.Command, args []string) {
5723
guess := strings.ToLower(strings.TrimSpace(args[0]))
5824
streak := 0
5925
keepPlaying := true
6026

6127
for keepPlaying {
62-
result := tossCoin()
28+
result := cointoss.TossCoin()
6329
fmt.Printf("The coin shows: %s!\n", strings.Title(result))
6430

6531
if guess == result {
6632
streak++
6733
fmt.Printf("Correct! Streak: %d\n", streak)
6834
var continuePlay bool
69-
guess, continuePlay = getNextGuess()
35+
guess, continuePlay = cointoss.GetNextGuess()
7036
keepPlaying = continuePlay
7137
} else {
7238
fmt.Printf("Game Over! Final streak: %d\n", streak)

internal/cointoss/cointoss.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package cointoss
2+
3+
import (
4+
"fmt"
5+
"math/rand"
6+
"strings"
7+
"time"
8+
)
9+
10+
func TossCoin() string {
11+
rand.Seed(time.Now().UnixNano())
12+
if rand.Float32() < 0.5 {
13+
return "heads"
14+
}
15+
return "tails"
16+
}
17+
18+
func ValidateGuess(guess string) error {
19+
guess = strings.ToLower(strings.TrimSpace(guess))
20+
if guess != "heads" && guess != "tails" {
21+
return fmt.Errorf("guess must be either 'heads' or 'tails'")
22+
}
23+
return nil
24+
}
25+
26+
func GetNextGuess() (string, bool) {
27+
fmt.Print("Play again? Enter 'heads' or 'tails' (or 'quit' to end): ")
28+
var answer string
29+
fmt.Scanln(&answer)
30+
31+
if strings.ToLower(strings.TrimSpace(answer)) == "quit" {
32+
return "", false
33+
}
34+
35+
if err := ValidateGuess(answer); err != nil {
36+
fmt.Println(err)
37+
return GetNextGuess()
38+
}
39+
40+
return strings.ToLower(strings.TrimSpace(answer)), true
41+
}

internal/cointoss/cointoss_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package cointoss
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestValidateGuess(t *testing.T) {
8+
validGuesses := []string{"heads", "heads ", " HEADS", "tails", "TAILS", " tails "}
9+
invalidGuesses := []string{"", "foo", "123", "heds", "taol"}
10+
11+
for _, guess := range validGuesses {
12+
if err := ValidateGuess(guess); err != nil {
13+
t.Errorf("ValidateGuess(%q) returned error: %v, expected nil", guess, err)
14+
}
15+
}
16+
17+
for _, guess := range invalidGuesses {
18+
if err := ValidateGuess(guess); err == nil {
19+
t.Errorf("ValidateGuess(%q) did not return error, expected error", guess)
20+
}
21+
}
22+
}
23+
24+
func TestTossCoin(t *testing.T) {
25+
for i := 0; i < 100; i++ {
26+
result := TossCoin()
27+
if result != "heads" && result != "tails" {
28+
t.Errorf("TossCoin() returned unexpected value %q", result)
29+
}
30+
}
31+
}

0 commit comments

Comments
 (0)