Learn the Go programming language by building a guessing game

In this tutorial, we’ll tackle a classic beginner programming problem: a “Guess the number” game. In this game, the program will generate a random integer between 1 and 100, and then prompt the player to enter a guess.

After a guess is entered, the program will notify the player if the guess was higher or lower than the random number, and prompt the player for another guess. Otherwise, if the guess is correct, a congratulatory message will be printed on the screen and the program will exit.

The objective here is to give you your first bit of experience with writing a Go program, and for you to gain a basic understanding of what writing a Go program involves.

At the end of this tutorial, you should have a program that works in a similar manner to the demo below:

Prerequisites

This tutorial assumes no prior experience with Go. However, you must be familiar with at least one programming language to grasp the concepts that will be discussed herein. Basic familiarity with the command line is also assumed.

You also need to have Go installed on your computer. You can visit the official Golang website to view installation instructions for your operating system. The version I used while building this project is v1.12.4.

You can view the version of Go that you have installed by running the following command in your terminal:

$ go version

Set up your environment

This section describes how to set up your environment for the first time. Feel free to skip ahead if you already know the standard project structure used for Golang projects.

The first step is to check the current value of $GOPATH in your environment. This value of this variable defines your Go workspace directory. Your Go workspace is directory on your system where Go looks for source code files, manages dependency packages and build distribution binary files.

$ go env "GOPATH"

This should be set to $HOME/go on Unix systems and %USERPROFILE%\go on Windows. If your $GOPATH is not set or if you want to use a custom location as your workspace, you can set the GOPATH environment variable in your .bashrc or .zshrc as shown below:

export GOPATH="/location/of/your/gopath/directory"

If you’re using Fish shell, set your $GOPATH by adding the line below to your config.fish file:

set -gx GOPATH_HOME "/location/of/your/gopath/directory"

Inside your Go workspace, create the following 3 folders if they are not present already:

mkdir bin pkg src

bin is where executable binaries created by compiling your Go program are stored, pkg contains package objects made by libraries, while src is where all your Go source code should be written in.

Getting started

The convention for Go projects is to create the same directory structure as where your remote respository lies. For a project hosted at “https://github.com/ayoisaiah/guessing-game”, the location on my computer should be $GOPATH/src/github.com/ayoisaiah/guessthenumber.

Even if you do not plan to publish your code, it’s still a good habit to organise it as if you will do so in the future. With that said, within the src folder of your Go workspace, create the following directory structure shown below:

$ cd src
$ mkdir -p github.com/<your user name>/guessthenumber

The guessthenumber folder is your project directory where all the code for this program will be written in. Don’t forget to replace <your user name> with your actual Github username in the command above.

Write your first Go program

Create a new main.go file in your project directory, containing the following Go code:

package main

import (
	"fmt"
)

func main() {
	fmt.Println("Hello, World!")
}

Save the file, then compile and run the program in a single step with the command below. This should print the text “Hello, World!” to your terminal.

$ go run main.go
Hello, World!

Let’s review the above “Hello, World!” program in detail. Here’s the first line:

package main

Every Go source file belongs to a package. A package is nothing more than a directory containing one or more source files or other Go packages. To declare a source file as part of a package, we use the package <packagename> which must be the first line in the file.

The main package is special in Go. It indicates that the program should be compiled as an executable, not a shared library, and this package serves as the entry point for every Go executable program.

Inside the main package, a main function must be declared that takes no arguments and returns no value. This function will be invoked automatically when the program runs.

func main() {

}

The above code defines a function in Go. The func keyword is used to create a new function with a name (main in this case) and any parameters within the parenthesis (). The function body is also wrapped in curly braces {}. Go requires that the opening curly brace must be on the same line as the function declaration, otherwise the code will not compile.

Inside the main function, we have the following code:

fmt.Println("Hello World!")

The fmt package comes from Go’s standard library, and it exports several methods related to formatting and printing output or reading input from various I/O sources. The Println method for example, prints a provided string to the screen.

Before we can use methods from the fmt package, we need to bring it into scope. That is what the following lines are about:

import (
	"fmt"
)

Read user input from the terminal

Let’s return to the guessing game program. The first aspect to building a guessing game is to ask for user input and verify that the provided input is in the expected form. Let’s go ahead and prompt the user to input a guess. Modify the main.go file shown below:

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	fmt.Println("Guess a number between 1 and 100")
	fmt.Println("Please input your guess")

	reader := bufio.NewReader(os.Stdin)
	input, _ := reader.ReadString('\n')
	input = strings.TrimSuffix(input, "\n")

	guess, err := strconv.Atoi(input)

	if err != nil {
		fmt.Println("Invalid input. Please enter an integer value")
		return
	}

	fmt.Println("Your guess is", guess)
}

Let’s go over each line in the main function so as to understand what the code does.

fmt.Println("Guess a number between 1 and 100")
fmt.Println("Please input your guess")

The above lines prints text to the screen that introduces the game and prompts the player to enter a guess.

reader := bufio.NewReader(os.Stdin)
input, _ := reader.ReadString('\n')
input = strings.TrimSuffix(input, "\n")

The next step is to accept input from the player. The bufio package is what we’ll be using to capture the user’s input. The above line of code declares a new variable called reader and initialises it to the return value of bufio.NewReader(os.Stdin).

The NewReader() method of bufio takes an io Reader which in this case is os.Stdin. Stdin is an open File that points to the standard input file descriptor. This method returns a reader struct which has a method called ReadString() which takes in a delimiter (the newline character \n in this case).

The ReadString() method reads the player’s input until the first occurrence of delimiter in the string, and returns two values: a string containing the data up to and including the delimiter, and an error. The former is stored in the input variable while the latter value (the error) is discarded as a result of assigning it to _. The underscore _ is a placeholder that essentially means “I don’t care about this particular return value.”

Due to the fact that ReadString() includes the delimiter in the return string, it means that the value of input will contain the newline character when the player presses Enter. For example, if the player types 30 and presses Enter, the value of input will be 30\n. To eliminate this \n character, we can use the TrimSuffix() method from the strings package and pass it the string and the trailing suffix. This method returns provided the string back without the suffix, so 30\n will become just 30.

// [..]
guess, err := strconv.Atoi(input)

if err != nil {
	fmt.Println("Invalid input. Please enter an integer value")
	return
}

fmt.Println("Your guess is", guess)

Next, we need to convert the input string to an integer so that we can compare it numerically to the random integer which we’ll be generating later. We can use the Atoi() method from the strconv package for this purpose. This method attempts to convert the provided string into an integer, and it will return an integer and an error which we are assigning to guess and err respectively.

The usage of Atoi() can easily result in an error. If, for example, the player’s input contained an alphabet, there would be no way to convert it to an integer. Therefore we need to handle the error and remind the player to provide an integer input. But if there is no error, the player’s guess will be printed on the screen.

Try it out. Save the file and run the program using go run main.go. You should get a similar output to the one below:

$ go run main.go
Guess a number between 1 and 100
Please input your guess
20
Your guess is 20

Generate a random integer

Now that we’re able to receive input from the player, let’s go ahead and generate a random integer that we’ll be comparing the player’s guess to.

First, import the math/rand and time packages:

package main

import (
	"bufio"
	"fmt"
	"math/rand"
	"os"
	"strconv"
	"strings"
	"time"
)

// [..]

Then create a new generateRandomInteger function below the main function as shown below:

func generateRandomInteger(min, max int) int {
	rand.Seed(time.Now().Unix())
	return rand.Intn(max-min) + min
}

This function takes two parameters (min, and max) and returns an integer so we need to annotate the types of the parameters within the parenthesis, and the return value int before the opening curly brace.

The Intn() method of the rand package is responsible for generating a random integer between the min and max integers. To improve the randomness of the operation, you need to provide a unique seed for your program. A commonly used seed is the current time which can be converted to an int64 value by the Unix() method with rand.Seed(time.Now().Unix()).

Finally, call the generateRandomInteger() function in the main function as shown below:

func main() {
	fmt.Println("Guess a number between 1 and 100")
	fmt.Println("Please input your guess")

	secretNumber := generateRandomInteger(1, 100)
	fmt.Println("The secret number is", secretNumber)

  // [..]
}

Here, the return value of generateRandomInteger() is stored in a new secretNumber variable. Try running the program a few times. It should output different random numbers all between 1 and 100.

$ go run main.go
Guess a number between 1 and 100
Please input your guess
The secret number is 93
37
Your guess is 37
$ go run main.go
Guess a number between 1 and 100
Please input your guess
The secret number is 5
23
Your guess is 23

Compare the player’s guess to the secret number

Now that we’ve got both the player’s guess, and the secret number, it’s time to compare them. We’ll provide feedback to the player if the guess is higher or lower than the secret number. If the guess is equal to the secret number, we’ll print out a congratulatory message and exit the program.

Update the main function with the following code:

func main() {
  // [..]
	fmt.Println("Your guess is", guess)

	if guess > secretNumber {
		fmt.Println("Your guess is bigger than the secret number. Try again")
	} else if guess < secretNumber {
		fmt.Println("Your guess is smaller than the secret number. Try again")
	} else {
		fmt.Println("Correct, you Legend!")
	}
}

Try it out. Run the program a few times to verify that the code works as expected.

$ go run main.go
Guess a number between 1 and 100
Please input your guess
The secret number is 72
65
Your guess is 65
Your guess is smaller than the secret number. Try again
$ go run main.go
Guess a number between 1 and 100
Please input your guess
The secret number is 85
93
Your guess is 93
Your guess is bigger than the secret number. Try again
$ go run main.go
Guess a number between 1 and 100
Please input your guess
The secret number is 78
78
Your guess is 78
Correct, you Legend!

Our program mostly works but at this time, the player can only input one guess, and the program exits regardless of whether the guess is correct or not. What we really want is for the player to be able to continue guessing until the correct number is guessed. We also want to inform the player of the number of attempts made before winning the game.

We can implement the desired functionality by moving most of the code into an infinite loop as shown below:

func main() {
	fmt.Println("Guess a number between 1 and 100")

	secretNumber := generateRandomInteger(1, 100)
	fmt.Println("The secret number is", secretNumber)

	var attempts int
	for {
		attempts++
		fmt.Println("Please input your guess")
		reader := bufio.NewReader(os.Stdin)
		input, _ := reader.ReadString('\n')
		input = strings.TrimSuffix(input, "\n")

		guess, err := strconv.Atoi(input)

		if err != nil {
			fmt.Println("Invalid input. Please enter an integer value")
			continue
		}

		fmt.Println("Your guess is", guess)
		if guess > secretNumber {
			fmt.Println("Your guess is bigger than the secret number. Try again")
		} else if guess < secretNumber {
			fmt.Println("Your guess is smaller than the secret number. Try again")
		} else {
			fmt.Printf("Correct, you Legend! You guessed right after %d attempts", attempts)
			break
		}
	}
}

The for loop construct might look strange if you’re coming to Go from another programming languague because it does not have any conditions. In Go, using a for loop without a loop condition creates an infinite loop, and the only way to break out of this sort of loop is by using the break keyword which we’re doing when the player guesses correctly. This also causes the program to exit.

Notice the attempts variable declaration just before the loop. This variable is initialised to 0 and we’re incrementing its value by 1 on each iteration of the loop so as to keep track of how many attempts the player has made. When the player wins the game, the number of attempts is printed in the congratulatory message.

Also notice that while handling the error from the Atoi() method, the return keyword has been changed to continue so that the program goes to the next iteration of the loop when an invalid input is received instead of exiting.

$ go run main.go
Guess a number between 1 and 100
The secret number is 20
Please input your guess
26
Your guess is 26
Your guess is bigger than the secret number. Try again
Please input your guess
22
Your guess is 22
Your guess is bigger than the secret number. Try again
Please input your guess
20
Your guess is 20
Correct, you Legend! You guessed right after 3 attempts

Let’s finish off the game by deleting the line that prints out the secretNumber to the screen. The final code is shown below:

package main

import (
	"bufio"
	"fmt"
	"math/rand"
	"os"
	"strconv"
	"strings"
	"time"
)

func main() {
	fmt.Println("Guess a number between 1 and 100")

	secretNumber := generateRandomInteger(1, 100)

	var attempts int
	for {
		attempts++
		fmt.Println("Please input your guess")
		reader := bufio.NewReader(os.Stdin)
		input, _ := reader.ReadString('\n')
		input = strings.TrimSuffix(input, "\n")

		guess, err := strconv.Atoi(input)

		if err != nil {
			fmt.Println("Invalid input. Please enter an integer value")
			continue
		}

		fmt.Println("Your guess is", guess)
		if guess > secretNumber {
			fmt.Println("Your guess is bigger than the secret number. Try again")
		} else if guess < secretNumber {
			fmt.Println("Your guess is smaller than the secret number. Try again")
		} else {
			fmt.Printf("Correct, you Legend! You guessed right after %d attempts", attempts)
			break
		}
	}
}

func generateRandomInteger(min, max int) int {
	rand.Seed(time.Now().Unix())
	return rand.Intn(max-min) + min
}

Wrap up

Congratulations! You’ve successfully created a guessing game and learnt many new Go concepts such as variables, loops, functions, control flow, error handling and more along the way.

I hope this tutorial was helpful to you. If you have any questions, please leave a comment below and I’ll get back to you.

Thanks for reading!

To get notified whenever I publish a new article or tutorial about Golang on this blog, please subscribe using the newsletter form below.