Skip to content

How to Code Hangman in Go

Published:

Hangman is a classic word-guessing game. In this tutorial we will create it in Go.

How to play

In case you don’t know how to play hangman, here is how it works:

  1. The computer picks a random word.
  2. The word is hidden as underscores.
  3. Guess one letter at a time.
  4. Correct guesses reveal letters.
  5. Incorrect guesses draw the hangman.
  6. Win by guessing the word before the hangman is fully drawn.

Building the game

Initialize the Project

Start by creating a new Go project:

go mod init hangman

Create a main.go file:

package main
func main() {}

Set Up the Game

Add the following code to the main.go file.

package main

import (
	"fmt"
)

func main() {
	word := "golang"
    attempts := 6
	currentWordState := initializeCurrentWordState(word)

    fmt.Println("Welcome to Hangman!")
	displayCurrentState(currentWordState, attempts)
}

Here we have the word we will be guessing for now it’s hardcoded to “golang”. Later we will read this from a file. Other variables we have is the number of attempts, the current state of the word. We also display a welcome message and a function to display the current state of the word with remaining attempts.

Next we will create a function to initialize the current word state.

func initializeCurrentWordState(word string) []string {
	currentWordState := make([]string, len(word))
	for i := range currentWordState {
		currentWordState[i] = "_"
	}
	return currentWordState
}

This function creates a slice of the same length as the word and fills it with underscores.

After that we will create a function to display the current state of the word and the number of attempts left.

func displayCurrentState(currentWordState []string, attempts int) {
	fmt.Println("Current word state:", strings.Join(currentWordState, " "))
	fmt.Println("Attempts left:", attempts)
}

Dont forget to import the strings and fmt packages at the top of the file.

Our code should now look like this.

import (
	"fmt"
	"strings"
)
package main

func main() {
	word := "golang"
	attempts := 6
    currentWordState := initializeCurrentWordState(word)


	fmt.Println("Welcome to Hangman!")
	displayCurrentState(currentWordState, attempts)
}

func displayCurrentState(currentWordState []string, attempts int) {
	fmt.Println("Current word state:", strings.Join(currentWordState, " "))
	fmt.Println("Attempts left:", attempts)
}

func initializeCurrentWordState(word string) []string {
	currentWordState := make([]string, len(word))
	for i := range currentWordState {
		currentWordState[i] = "_"
	}
	return currentWordState
}

if we run this.

go run ./

we will get the following output.

Welcome to Hangman!
Current word state: _ _ _ _ _ _
Attempts left: 6

Read User Input & Game Loop

Add bufio and os to the import at the top of the file.

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

Declare a new scanner variable under the other variables.

word := "golang"
attempts := 6
currentWordState := initializeCurrentWordState(word)
scanner := bufio.NewScanner(os.Stdin)

Next we will create a function to get user input.

func getUserInput(scanner *bufio.Scanner) string {
	scanner.Scan()
	return scanner.Text()
}

This function will read the user input from the scanner and return it.

After that lets create our game loop and function to validate user input. Add it to the main function.

for attempts > 0 {
    displayCurrentState(currentWordState, attempts)
    userInput := getUserInput(scanner)
    if !isValidInput(userInput) {
        fmt.Println("Invalid input. Please enter a single letter.")
        continue
    }
}

It will continue until the user either wins or loses.

Lets create the isValidInput function and import unicode/utf8 at the top of the file.

import (
	"bufio"
	"fmt"
	"os"
	"strings"
    "unicode/utf8"
)
func isValidInput(input string) bool {
	return utf8.RuneCountInString(input) == 1
}

this function will check if the input is a single letter.

Check User Input

Create a new variable called guessedLetters and initialize it.

word := "golang"
attempts := 6
currentWordState := initializeCurrentWordState(word)
scanner := bufio.NewScanner(os.Stdin)
guessedLetters := make(map[string]bool)

This will be a map to store the guessed letters.

Next we will check if the user has already guessed the letter. If it’s already guessed we continue the loop. Else we add it to the guessed letters.

if guessedLetters[userInput] {
    fmt.Println("You've already guessed that letter.")
    continue
}

guessedLetters[userInput] = true

After that we will check if the guess is correct and update the current word state.

correctGuess := updateGuessed(word, currentWordState, userInput)

Lets create the updateGuessed function.

func updateGuessed(word string, guessed []string, letter string) bool {
	correctGuess := false
	for i, char := range word {
		if string(char) == letter {
			guessed[i] = letter
			correctGuess = true
		}
	}
	return correctGuess
}

The updateGuessed function checks if a guessed letter is in the word, replaces underscores with the letter at the correct positions if found, and returns true or false based on whether the letter is found.

If the guess is incorrect we will decrement the attempts. Add this to the end of the game loop.

if !correctGuess {
    attempts--
}

Display Hangman State

Next we will display the hangman state. After the if statement that checks if the guess is correct add the following code.

displayHangman(6 - attempts)

Create a new file called hangman_states.go and add the following code.

package main

var hangmanStates = []string{
	`
  +---+
  |   |
      |
      |
      |
      |
=========
`,
	`
  +---+
  |   |
  O   |
      |
      |
      |
=========
`,
	`
  +---+
  |   |
  O   |
  |   |
      |
      |
=========
`,
	`
  +---+
  |   |
  O   |
 /|   |
      |
      |
=========
`,
	`
  +---+
  |   |
  O   |
 /|\  |
      |
      |
=========
`,
	`
  +---+
  |   |
  O   |
 /|\  |
 /    |
      |
=========
`,
	`
  +---+
  |   |
  O   |
 /|\  |
 / \  |
      |
=========
`}

This file containt a slice of strings representing the hangman states.

Next create the displayHangman function. It will be a simple function that prints the hangman state.

func displayHangman(incorrectGuesses int) {
	if incorrectGuesses >= 0 && incorrectGuesses < len(hangmanStates) {
		fmt.Println(hangmanStates[incorrectGuesses])
	}
}

We check if the incorrect guesses is within the range of the hangman states and print the corresponding state.

Or code in main.go should now look like this.

package main

func main() {
    word := "golang"
    currentWordState := initializeCurrentWordState(word)
    attempts := 6
    guessedLetters := make(map[string]bool)
    scanner := bufio.NewScanner(os.Stdin)

    fmt.Println("Welcome to Hangman!")

    for attempts > 0 {
        displayCurrentState(currentWordState, attempts)
        userInput := getUserInput(scanner)

        if !isValidInput(userInput) {
            fmt.Println("Invalid input. Please enter a single letter.")
            continue
        }

        if guessedLetters[userInput] {
            fmt.Println("You've already guessed that letter.")
            continue
        }

        guessedLetters[userInput] = true

        correctGuess := updateGuessed(word, currentWordState, userInput)

        if !correctGuess {
            attempts--
        }

        displayHangman(6 - attempts)
    }
}

Lets run the code and see what happens. We can now see the current state of the word and the hangman state is updated after each guess. We also get a message if we don’t enter a valid input and if we have guessed a letter before.

Check if the word is guessed and if the user has lost

Now we need to check if the word is guessed or if the user has lost.

if isWordGuessed(currentWordState, word) {
    fmt.Println("Congratulations! You've guessed the word:", word)
    return
}

if attempts == 0 {
    fmt.Println("Game over! The word was:", word)
    return
}

Add this to the end of the game loop.

Lets create the isWordGuessed function.

func isWordGuessed(guessed []string, word string) bool {
	return strings.Join(guessed, "") == word
}

This function will check if the word is guessed by joining the guessed letters and comparing it to the word.

Generate a random word

The only thing left do is generate a random word from a file.

Download this file words.txt and add it to your project.

Replace word := "golang" with this.

word, err := getRandomWord("words.txt")
if err != nil {
    fmt.Println("Error reading word file:", err)
    return
}

In case we get an error we will print it else we will use the random word.

import math/rand at the top of the file.

import (
	"bufio"
	"fmt"
	"math/rand"
	"os"
	"strings"
	"unicode/utf8"
)

Lets create the getRandomWord function.

func getRandomWord(filename string) (string, error) {
	data, err := os.ReadFile(filename)
	if err != nil {
		return "", err
	}
	words := strings.Split(string(data), "\n")
	return words[rand.Intn(len(words))], nil
}

This function will read the file and split each line into words in a slice. Then it will return a random word from the slice.

We now have a complete hangman game. Feel free to play it and try to guess the word.

Conclusion

You’ve built a complete Hangman game in Go! This project taught you file handling, randomization, and user interaction. Feel free to expand and enhance the game further. I hope you enjoyed this tutorial and learned something from it.

Full source code can be found here Github