Skip to content

Commit 30f9948

Browse files
Refactor coin toss game logic to improve structure and testability; add PlayGame function and corresponding tests
1 parent a350074 commit 30f9948

File tree

3 files changed

+81
-21
lines changed

3 files changed

+81
-21
lines changed

cmd/cointoss.go

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package cmd
33
import (
44
"fmt"
55
"os"
6-
"strings"
76

87
"github.com/chrisreddington/gh-game/internal/cointoss"
98
userPrompt "github.com/cli/go-gh/v2/pkg/prompter"
@@ -21,24 +20,11 @@ var cointossCmd = &cobra.Command{
2120
return cointoss.ValidateGuess(args[0])
2221
},
2322
Run: func(cmd *cobra.Command, args []string) {
24-
game := cointoss.NewGame()
25-
prompter := userPrompt.New(os.Stdin, os.Stdout, os.Stderr)
26-
guess := strings.ToLower(strings.TrimSpace(args[0]))
27-
streak := 0
28-
keepPlaying := true
29-
30-
for keepPlaying {
31-
game.Play(guess)
32-
fmt.Println(game.GetResult())
33-
34-
if game.PlayerGuess == game.Result {
35-
streak++
36-
fmt.Printf("Streak: %d\n", streak)
37-
guess, keepPlaying = cointoss.GetPlayerGuess(prompter)
38-
} else {
39-
fmt.Printf("Game Over! Final streak: %d\n", streak)
40-
keepPlaying = false
41-
}
42-
}
23+
input := userPrompt.New(os.Stdin, os.Stdout, os.Stderr)
24+
cointoss.PlayGame(input, args[0])
4325
},
4426
}
27+
28+
func init() {
29+
rootCmd.AddCommand(cointossCmd)
30+
}

internal/cointoss/cointoss.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ func NewGame() *Game {
2525
}
2626
}
2727

28-
func TossCoin() string {
28+
// TossCoin is a variable so it can be replaced in tests
29+
var TossCoin = func() string {
2930
rand.Seed(time.Now().UnixNano())
3031
if rand.Float32() < 0.5 {
3132
return "heads"
@@ -73,3 +74,25 @@ func (g *Game) GetResult() string {
7374
}
7475
return fmt.Sprintf("You guessed %s but the coin landed on %s. You lose!", g.PlayerGuess, g.Result)
7576
}
77+
78+
// PlayGame handles the main game loop
79+
func PlayGame(p prompter, initialGuess string) {
80+
game := NewGame()
81+
streak := 0
82+
keepPlaying := true
83+
guess := strings.ToLower(strings.TrimSpace(initialGuess))
84+
85+
for keepPlaying {
86+
game.Play(guess)
87+
fmt.Println(game.GetResult())
88+
89+
if game.PlayerGuess == game.Result {
90+
streak++
91+
fmt.Printf("Streak: %d\n", streak)
92+
guess, keepPlaying = GetPlayerGuess(p)
93+
} else {
94+
fmt.Printf("Game Over! Final streak: %d\n", streak)
95+
keepPlaying = false
96+
}
97+
}
98+
}

internal/cointoss/cointoss_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,3 +175,54 @@ func TestGame_GetResult(t *testing.T) {
175175
func contains(s, substr string) bool {
176176
return strings.Contains(s, substr)
177177
}
178+
179+
func TestPlayGame(t *testing.T) {
180+
tests := []struct {
181+
name string
182+
selectAnswer int
183+
selectError error
184+
initialGuess string
185+
results []string // sequence of coin flip results to test
186+
}{
187+
{
188+
name: "win first round then quit",
189+
selectAnswer: 2, // quit
190+
initialGuess: "heads",
191+
results: []string{"heads"},
192+
},
193+
{
194+
name: "lose first round",
195+
initialGuess: "heads",
196+
results: []string{"tails"},
197+
},
198+
{
199+
name: "win twice then lose",
200+
selectAnswer: 0, // heads
201+
initialGuess: "heads",
202+
results: []string{"heads", "heads", "tails"},
203+
},
204+
}
205+
206+
for _, tt := range tests {
207+
t.Run(tt.name, func(t *testing.T) {
208+
mockP := &mockPrompter{
209+
selectAnswer: tt.selectAnswer,
210+
selectError: tt.selectError,
211+
}
212+
213+
// Override TossCoin for deterministic testing
214+
resultIndex := 0
215+
oldTossCoin := TossCoin
216+
TossCoin = func() string {
217+
result := tt.results[resultIndex]
218+
if resultIndex < len(tt.results)-1 {
219+
resultIndex++
220+
}
221+
return result
222+
}
223+
defer func() { TossCoin = oldTossCoin }()
224+
225+
PlayGame(mockP, tt.initialGuess)
226+
})
227+
}
228+
}

0 commit comments

Comments
 (0)