Skip to content

Commit a350074

Browse files
Refactor coin toss game logic and enhance player interaction with structured game state management
1 parent 86c4691 commit a350074

3 files changed

Lines changed: 124 additions & 30 deletions

File tree

cmd/cointoss.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ package cmd
22

33
import (
44
"fmt"
5+
"os"
56
"strings"
67

7-
"github.com/chrisreddington/gh-game/internal/cointoss" // adjust import path as needed
8-
8+
"github.com/chrisreddington/gh-game/internal/cointoss"
9+
userPrompt "github.com/cli/go-gh/v2/pkg/prompter"
910
"github.com/spf13/cobra"
1011
)
1112

@@ -20,20 +21,20 @@ var cointossCmd = &cobra.Command{
2021
return cointoss.ValidateGuess(args[0])
2122
},
2223
Run: func(cmd *cobra.Command, args []string) {
24+
game := cointoss.NewGame()
25+
prompter := userPrompt.New(os.Stdin, os.Stdout, os.Stderr)
2326
guess := strings.ToLower(strings.TrimSpace(args[0]))
2427
streak := 0
2528
keepPlaying := true
2629

2730
for keepPlaying {
28-
result := cointoss.TossCoin()
29-
fmt.Printf("The coin shows: %s!\n", strings.Title(result))
31+
game.Play(guess)
32+
fmt.Println(game.GetResult())
3033

31-
if guess == result {
34+
if game.PlayerGuess == game.Result {
3235
streak++
33-
fmt.Printf("Correct! Streak: %d\n", streak)
34-
var continuePlay bool
35-
guess, continuePlay = cointoss.GetNextGuess()
36-
keepPlaying = continuePlay
36+
fmt.Printf("Streak: %d\n", streak)
37+
guess, keepPlaying = cointoss.GetPlayerGuess(prompter)
3738
} else {
3839
fmt.Printf("Game Over! Final streak: %d\n", streak)
3940
keepPlaying = false

internal/cointoss/cointoss.go

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,28 @@ package cointoss
33
import (
44
"fmt"
55
"math/rand"
6-
"os"
76
"strings"
87
"time"
9-
10-
userPrompt "github.com/cli/go-gh/v2/pkg/prompter"
118
)
129

10+
// Game represents the state of a coin toss game
11+
type Game struct {
12+
PlayerGuess string
13+
Result string
14+
IsOver bool
15+
}
16+
1317
// prompter interface allows us to mock the prompt functionality in tests
1418
type prompter interface {
1519
Select(prompt string, defaultValue string, options []string) (int, error)
1620
}
1721

22+
func NewGame() *Game {
23+
return &Game{
24+
IsOver: false,
25+
}
26+
}
27+
1828
func TossCoin() string {
1929
rand.Seed(time.Now().UnixNano())
2030
if rand.Float32() < 0.5 {
@@ -31,11 +41,8 @@ func ValidateGuess(guess string) error {
3141
return nil
3242
}
3343

34-
func GetNextGuess() (string, bool) {
35-
return GetNextGuessWithPrompter(userPrompt.New(os.Stdin, os.Stdout, os.Stderr))
36-
}
37-
38-
func GetNextGuessWithPrompter(p prompter) (string, bool) {
44+
// GetPlayerGuess gets the player's next guess using the provided prompter
45+
func GetPlayerGuess(p prompter) (string, bool) {
3946
options := []string{"Heads", "Tails", "Quit"}
4047

4148
answer, err := p.Select("What's your next guess? Heads, Tails or Quit?", "Heads", options)
@@ -51,3 +58,18 @@ func GetNextGuessWithPrompter(p prompter) (string, bool) {
5158

5259
return answerLower, true
5360
}
61+
62+
// Play executes a round of the coin toss game
63+
func (g *Game) Play(guess string) {
64+
g.PlayerGuess = guess
65+
g.Result = TossCoin()
66+
g.IsOver = true
67+
}
68+
69+
// GetResult returns the game result message
70+
func (g *Game) GetResult() string {
71+
if g.PlayerGuess == g.Result {
72+
return fmt.Sprintf("You guessed %s and the coin landed on %s. You win!", g.PlayerGuess, g.Result)
73+
}
74+
return fmt.Sprintf("You guessed %s but the coin landed on %s. You lose!", g.PlayerGuess, g.Result)
75+
}

internal/cointoss/cointoss_test.go

Lines changed: 84 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package cointoss
22

33
import (
44
"errors"
5+
"strings"
56
"testing"
67
)
78

@@ -41,7 +42,7 @@ func TestTossCoin(t *testing.T) {
4142
}
4243
}
4344

44-
func TestGetNextGuessWithPrompter(t *testing.T) {
45+
func TestGetPlayerGuess(t *testing.T) {
4546
tests := []struct {
4647
name string
4748
selectAnswer int
@@ -77,13 +78,6 @@ func TestGetNextGuessWithPrompter(t *testing.T) {
7778
expectedGuess: "",
7879
expectedCont: false,
7980
},
80-
{
81-
name: "error during selection prints error message",
82-
selectAnswer: 0,
83-
selectError: errors.New("test error"),
84-
expectedGuess: "",
85-
expectedCont: false,
86-
},
8781
}
8882

8983
for _, tt := range tests {
@@ -92,15 +86,92 @@ func TestGetNextGuessWithPrompter(t *testing.T) {
9286
selectAnswer: tt.selectAnswer,
9387
selectError: tt.selectError,
9488
}
95-
96-
guess, cont := GetNextGuessWithPrompter(mockP)
97-
89+
guess, cont := GetPlayerGuess(mockP)
9890
if guess != tt.expectedGuess {
99-
t.Errorf("GetNextGuessWithPrompter() guess = %v, want %v", guess, tt.expectedGuess)
91+
t.Errorf("GetPlayerGuess() guess = %v, want %v", guess, tt.expectedGuess)
10092
}
10193
if cont != tt.expectedCont {
102-
t.Errorf("GetNextGuessWithPrompter() cont = %v, want %v", cont, tt.expectedCont)
94+
t.Errorf("GetPlayerGuess() cont = %v, want %v", cont, tt.expectedCont)
10395
}
10496
})
10597
}
10698
}
99+
100+
func TestGame_Play(t *testing.T) {
101+
game := NewGame()
102+
103+
// Test initial state
104+
if game.IsOver {
105+
t.Error("New game should not be over")
106+
}
107+
108+
// Play a round
109+
game.Play("heads")
110+
111+
// Test that game state is updated
112+
if !game.IsOver {
113+
t.Error("Game should be over after playing")
114+
}
115+
if game.PlayerGuess != "heads" {
116+
t.Errorf("PlayerGuess = %v, want heads", game.PlayerGuess)
117+
}
118+
if game.Result != "heads" && game.Result != "tails" {
119+
t.Errorf("Result = %v, want either heads or tails", game.Result)
120+
}
121+
}
122+
123+
func TestGame_GetResult(t *testing.T) {
124+
tests := []struct {
125+
name string
126+
playerGuess string
127+
result string
128+
wantWin bool
129+
}{
130+
{
131+
name: "player wins with heads",
132+
playerGuess: "heads",
133+
result: "heads",
134+
wantWin: true,
135+
},
136+
{
137+
name: "player wins with tails",
138+
playerGuess: "tails",
139+
result: "tails",
140+
wantWin: true,
141+
},
142+
{
143+
name: "player loses with heads",
144+
playerGuess: "heads",
145+
result: "tails",
146+
wantWin: false,
147+
},
148+
{
149+
name: "player loses with tails",
150+
playerGuess: "tails",
151+
result: "heads",
152+
wantWin: false,
153+
},
154+
}
155+
156+
for _, tt := range tests {
157+
t.Run(tt.name, func(t *testing.T) {
158+
game := &Game{
159+
PlayerGuess: tt.playerGuess,
160+
Result: tt.result,
161+
IsOver: true,
162+
}
163+
got := game.GetResult()
164+
if tt.wantWin && !contains(got, "You win!") {
165+
t.Errorf("GetResult() = %v, want win message", got)
166+
}
167+
if !tt.wantWin && !contains(got, "You lose!") {
168+
t.Errorf("GetResult() = %v, want lose message", got)
169+
}
170+
})
171+
}
172+
}
173+
174+
// Helper function to check if a string contains a substring
175+
func contains(s, substr string) bool {
176+
return strings.Contains(s, substr)
177+
}

0 commit comments

Comments
 (0)