1 The Problem
We want a tiny game: the computer thinks of a number between 1 and 100, and the player keeps guessing. After each guess the computer says “too high” or “too low” until the player gets it right. Simple — but it teaches the three things every program does: ask, decide, and repeat.
2 How to Think About It
Before writing any code, picture the game as a loop with a decision inside it. You do not need to know Go yet — you just need to know the shape of what happens:
3 The Build — explained part by part
Here is the complete game. Read each part’s plain-English note below it — you should be able to understand the whole program from the notes alone, without knowing any Go in advance.
package main
import (
"bufio"
"fmt"
"math/rand"
"os"
"strconv"
"strings"
)
// playGame is a testable version of the game: give it the secret number and
// a list of guesses, and it returns how many tries it took (0 if the guesses
// never find it).
func playGame(secret int, guesses []int) int {
for i, guess := range guesses {
if guess == secret {
return i + 1
}
}
return 0 // never guessed it
}
func main() {
// The computer secretly picks a whole number from 1 to 100.
secretNumber := rand.Intn(100) + 1
// We count how many guesses the player takes.
guessesTaken := 0
reader := bufio.NewReader(os.Stdin)
fmt.Println("I am thinking of a number between 1 and 100.")
// Keep asking until the player guesses right.
for {
fmt.Print("Your guess: ")
line, err := reader.ReadString('\n')
if err != nil {
return // input closed (e.g. end of file) — stop cleanly
}
guess, err := strconv.Atoi(strings.TrimSpace(line))
if err != nil {
fmt.Println("Please type a whole number.")
continue
}
guessesTaken++
if guess < secretNumber {
fmt.Println("Too low. Try a bigger number.")
} else if guess > secretNumber {
fmt.Println("Too high. Try a smaller number.")
} else {
fmt.Printf("Correct! You got it in %d guesses.\n", guessesTaken)
break
}
}
}
Intn(100) gives 0–99, so we add 1 to shift the range to 1–100. Modern Go (1.20+) seeds this automatically, so you do not need to seed it by hand.bufio.NewReader(os.Stdin) — Go reads input in a more explicit way than Python. We wrap standard input in a buffered reader so we can read one full line at a time.
for { ... } — Go has only one loop keyword,
for. Written with no condition, it means “repeat forever” — the same job Python’s while True: does.reader.ReadString('\n') — read everything the player typed, up to and including the Enter key.
strconv.Atoi(strings.TrimSpace(line)) — the input arrives as text with a trailing newline attached.
TrimSpace removes it, and Atoi (“ASCII to integer”) turns the text into a number — and returns an error if it cannot, which we check immediately.if / else if / else — the decision. Go spells the middle case
else if (two words, no elif) but the logic is identical to Python’s.break — exits the
for loop the moment the guess is correct, exactly like Python.
strings.TrimSpace before strconv.Atoi — the leftover \n from ReadString makes every conversion fail.error that strconv.Atoi returns — a non-number crashes the program with a panic-free but useless zero value.err != nil and print a friendly message instead, like the code above does.rand.Intn(100) + 1 inside the loop — it picks a new secret every guess, so you can never win.if guess = secretNumber by mistake instead of ==.= where a comparison is expected, which catches the bug for you at build time.4 Test & Prove Each Part
How do we know the game works without playing it a hundred times by hand? We write small tests — each one checks one rule and proves it. Go’s built-in testing package is the standard way to do this, no extra install required.
package main
import (
"math/rand"
"testing"
)
func TestCorrectFirstTry(t *testing.T) {
if got := playGame(42, []int{42}); got != 1 {
t.Errorf("playGame(42, [42]) = %d; want 1", got)
}
}
func TestTakesThreeTries(t *testing.T) {
if got := playGame(50, []int{10, 90, 50}); got != 3 {
t.Errorf("playGame(50, [10 90 50]) = %d; want 3", got)
}
}
func TestNeverGuesses(t *testing.T) {
if got := playGame(7, []int{1, 2, 3}); got != 0 {
t.Errorf("playGame(7, [1 2 3]) = %d; want 0 (never found)", got)
}
}
func TestSecretIsInRange(t *testing.T) {
for i := 0; i < 1000; i++ {
n := rand.Intn(100) + 1
if n < 1 || n > 100 {
t.Fatalf("secret %d out of range 1-100", n)
}
}
}
Run them with go test -v ./.... Each test sets up a tiny situation and checks the result with a plain if and t.Errorf — Go’s tests are just functions named TestXxx that take a *testing.T. Green means the rule holds; a failing test prints exactly what it expected and what it got.
5 The Interface
Even a tiny program has an interface — the way a person interacts with it. Here is its contract, documented plainly, the same way a professional would describe any tool.
What it expects
A number like 42, typed and then Enter pressed.What it returns
"Too low. Try a bigger number."
"Too high. Try a smaller number."
"Correct! You got it in N guesses."6 Run It & Automate It
Save the code as guessing_game.go and run it with go run guessing_game.go — Go compiles and executes in one step, no separate build needed while you are experimenting.
go run guessing_game.goThen type a number, press Enter, and follow the “too high / too low” hints until you win.
A CI tool like Jenkins runs go test automatically whenever the code changes — every line below has a plain explanation.
I am thinking of a number between 1 and 100.
Your guess: 50
Too high. Try a smaller number.
Your guess: 25
Too low. Try a bigger number.
Your guess: 37
Too high. Try a smaller number.
Your guess: 31
Correct! You got it in 4 guesses.ReadString('\n') waits for the newline.break is missing or in the wrong branch. It must run in the else case, when the guess equals the secret.// Jenkinsfile — runs the tests automatically every time the code changes.
pipeline {
agent any // run on any available machine
stages {
stage('Get the code') {
steps { checkout scm } // download the latest code
}
stage('Set up Go') {
steps {
sh 'go version' // confirm Go is installed
sh 'test -f go.mod || go mod init guessing_game' // create a module if none exists
}
}
stage('Run the tests') {
steps {
sh 'go vet ./...' // catch obvious mistakes before running
sh 'go test -v ./...' // run every test, show each result
}
}
}
post {
success { echo 'All tests passed.' }
failure { echo 'A test failed — look above.' }
}
}
You have the working game. Now make it yours — each of these adds one new idea:
- Limit the guesses. Give the player only 7 tries, then reveal the answer. (Teaches: counting toward a limit.)
- Let them play again. After winning, ask “Play again? (y/n)” and restart if yes. (Teaches: an outer loop.)
- Make invalid input impossible to miss. Print the valid range again whenever
Atoifails. (Teaches: friendlier error handling.) - Make the computer guess your number. Flip the roles — the computer guesses and you say higher/lower. (Teaches: binary search thinking.)
bufio), decisions (if / else if / else), and loops (for + break). You also met Go’s error-checking style — instead of exceptions, functions like Atoi hand back an error value you check yourself — and how go test proves your code works. Related reference: Control Flow, Error Handling & the Standard Library.