Skip to content

Commit 9aeff67

Browse files
Add Rock Paper Scissors game implementation with CLI support
1 parent 4cdd2e8 commit 9aeff67

4 files changed

Lines changed: 425 additions & 0 deletions

File tree

cmd/rockpaperscissors.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// This file is an entrypoint for the Rock Paper Scissors game. It uses the cobra cmd to execute the game.
2+
// The game logic is in the internal/rockpaperscissors package.
3+
4+
// This file sets up the command line interface for the game. It should call the PlayGame function from the rockpaperscissors package.
5+
6+
package cmd
7+
8+
import (
9+
"os"
10+
11+
"github.com/chrisreddington/gh-game/internal/rockpaperscissors"
12+
userPrompt "github.com/cli/go-gh/v2/pkg/prompter"
13+
"github.com/spf13/cobra"
14+
)
15+
16+
// rootCmd represents the base command when called without any subcommands
17+
var rockPaperScissorsCmd = &cobra.Command{
18+
Use: "rockpaperscissors",
19+
Short: "A simple Rock Paper Scissors game",
20+
Long: `A simple Rock Paper Scissors game that allows you to play against the computer.
21+
You can choose from rock, paper, or scissors. The computer will randomly choose its move and the winner will be determined based on the rules of the game.`,
22+
Run: func(cmd *cobra.Command, args []string) {
23+
input := userPrompt.New(os.Stdin, os.Stdout, os.Stderr)
24+
rockpaperscissors.PlayGame(input)
25+
},
26+
}

cmd/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@ func init() {
1818
rootCmd.AddCommand(whoamiCmd)
1919
rootCmd.AddCommand(cointossCmd)
2020
rootCmd.AddCommand(tictactoeCmd)
21+
rootCmd.AddCommand(rockPaperScissorsCmd)
2122
}
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
package rockpaperscissors
2+
3+
import (
4+
"fmt"
5+
"math/rand"
6+
"time"
7+
)
8+
9+
// Game options
10+
var (
11+
options = []string{"rock", "paper", "scissors", "exit"}
12+
)
13+
14+
// Game represents a single game of Rock Paper Scissors.
15+
type Game struct {
16+
// PlayerChoice is the choice made by the player.
17+
PlayerChoice string
18+
// ComputerChoice is the choice made by the computer.
19+
ComputerChoice string
20+
// Winner is the winner of the current round.
21+
Winner string
22+
// PlayerScore tracks the player's wins
23+
PlayerScore int
24+
// ComputerScore tracks the computer's wins
25+
ComputerScore int
26+
// BestOf determines how many games to play (e.g., best of 3, 5, 7)
27+
BestOf int
28+
// GameOver is a flag to indicate if the game is over.
29+
GameOver bool
30+
// GameOverMessage is the message to display when the game is over.
31+
GameOverMessage string
32+
// GamesPlayed tracks the number of games played
33+
GamesPlayed int
34+
}
35+
36+
// Prompter defines an interface for getting user input
37+
type Prompter interface {
38+
Select(prompt, defaultValue string, options []string) (int, error)
39+
}
40+
41+
func NewGame(bestOf int) *Game {
42+
if bestOf%2 == 0 {
43+
bestOf++ // Ensure we have an odd number for "best of"
44+
}
45+
return &Game{
46+
PlayerChoice: "",
47+
ComputerChoice: "",
48+
Winner: "",
49+
GamesPlayed: 0,
50+
PlayerScore: 0,
51+
ComputerScore: 0,
52+
BestOf: bestOf,
53+
GameOver: false,
54+
GameOverMessage: "",
55+
}
56+
}
57+
58+
// Play plays a single round of Rock Paper Scissors.
59+
func (g *Game) Play(playerChoice string) {
60+
if playerChoice == "exit" {
61+
g.GameOver = true
62+
g.GameOverMessage = "Game ended by player"
63+
return
64+
}
65+
66+
g.PlayerChoice = playerChoice
67+
g.ComputerChoice = g.getComputerChoice()
68+
g.Winner = g.getWinner()
69+
g.updateScore()
70+
g.GamesPlayed++
71+
g.GameOver = g.isGameOver()
72+
if g.GameOver {
73+
g.GameOverMessage = g.getGameOverMessage()
74+
}
75+
}
76+
77+
// getComputerChoice returns the choice made by the computer.
78+
func (g *Game) getComputerChoice() string {
79+
rand.Seed(time.Now().UnixNano())
80+
// Only use the game options excluding "exit"
81+
choices := options[:len(options)-1]
82+
return choices[rand.Intn(len(choices))]
83+
}
84+
85+
// getWinner returns the winner of the current round.
86+
func (g *Game) getWinner() string {
87+
if g.PlayerChoice == g.ComputerChoice {
88+
return "draw"
89+
}
90+
if (g.PlayerChoice == "rock" && g.ComputerChoice == "scissors") ||
91+
(g.PlayerChoice == "paper" && g.ComputerChoice == "rock") ||
92+
(g.PlayerChoice == "scissors" && g.ComputerChoice == "paper") {
93+
return "player"
94+
}
95+
return "computer"
96+
}
97+
98+
// updateScore updates the score based on the round winner
99+
func (g *Game) updateScore() {
100+
if g.Winner == "player" {
101+
g.PlayerScore++
102+
} else if g.Winner == "computer" {
103+
g.ComputerScore++
104+
}
105+
}
106+
107+
// isGameOver returns true if the game is over.
108+
func (g *Game) isGameOver() bool {
109+
winsNeeded := (g.BestOf / 2) + 1
110+
111+
// Game is over if:
112+
// 1. Either player has reached the required wins, or
113+
// 2. We've played all games in the series
114+
return g.PlayerScore >= winsNeeded ||
115+
g.ComputerScore >= winsNeeded ||
116+
g.GamesPlayed >= g.BestOf
117+
}
118+
119+
// getGameOverMessage returns the message to display when the game is over.
120+
func (g *Game) getGameOverMessage() string {
121+
if g.PlayerScore > g.ComputerScore {
122+
return fmt.Sprintf("You win the match! Final score: Player %d - Computer %d", g.PlayerScore, g.ComputerScore)
123+
} else if g.ComputerScore > g.PlayerScore {
124+
return fmt.Sprintf("Computer wins the match! Final score: Player %d - Computer %d", g.PlayerScore, g.ComputerScore)
125+
}
126+
return fmt.Sprintf("The match is a draw! Final score: Player %d - Computer %d", g.PlayerScore, g.ComputerScore)
127+
}
128+
129+
// PlayGame plays a game of Rock Paper Scissors.
130+
func PlayGame(prompter Prompter) {
131+
// Get the number of rounds from the user
132+
roundOptions := []string{"3", "5", "7", "9"}
133+
roundIndex, err := prompter.Select("How many rounds would you like to play (best of)?", "3", roundOptions)
134+
if err != nil {
135+
fmt.Printf("Error getting number of rounds: %v\n", err)
136+
return
137+
}
138+
bestOf := 3 // Default value
139+
if roundIndex >= 0 && roundIndex < len(roundOptions) {
140+
bestOf = parseInt(roundOptions[roundIndex])
141+
}
142+
143+
game := NewGame(bestOf)
144+
fmt.Printf("Playing best of %d games\n", bestOf)
145+
146+
for !game.GameOver {
147+
fmt.Printf("\nCurrent score - Player: %d, Computer: %d\n", game.PlayerScore, game.ComputerScore)
148+
149+
// Get player choice using prompter
150+
playerChoiceIndex, err := prompter.Select("Choose your move", "rock", options)
151+
if err != nil {
152+
fmt.Printf("Error getting player choice: %v\n", err)
153+
return
154+
}
155+
playerChoice := options[playerChoiceIndex]
156+
157+
game.Play(playerChoice)
158+
if playerChoice == "exit" {
159+
break
160+
}
161+
162+
fmt.Printf("You chose: %s\n", game.PlayerChoice)
163+
fmt.Printf("Computer chose: %s\n", game.ComputerChoice)
164+
fmt.Printf("Round result: %s wins!\n", game.Winner)
165+
}
166+
fmt.Println(game.GameOverMessage)
167+
}
168+
169+
// parseInt safely converts a string to an integer
170+
func parseInt(s string) int {
171+
val := 3 // Default value
172+
fmt.Sscanf(s, "%d", &val)
173+
return val
174+
}

0 commit comments

Comments
 (0)