🎮 Python Hangman Guide

A type-along manual: install nothing but Python, write the game in five small steps with the whole file shown at each step, run it like a real command, make it your own, and learn to read Python's error messages.

Video: Python Hangman Guide (Sysadmin video)
Prefer to watch? This short video runs this page's main commands in a real lab, with on-screen captions and no voiceover. Click to play, or watch it on YouTube.
cmd-hangman: guess the Linux command before the stick figure is finished
python3 hangman.py
./hangman.py

No programming experience is needed. You type a short Python program in a text editor, save it, run it, and add a little more each step. By the end you have a 71-line game that picks a random Linux command (ls, grep, chmod, systemctl, journalctl, rsync...), shows its meaning as a hint and lets you guess it letter by letter.

It is also the first part of a series. The same file is turned into a .deb package for Ubuntu and Debian, and into an .rpm package for Rocky Linux, RHEL and Fedora, so you learn how software is packaged with something fun to install at the end. The other two guides: Build a .deb Package for Ubuntu and Debian, Build an .rpm Package for Rocky Linux.

Every example ran for real, on Ubuntu 24.04 with Python 3.12.3. The finished game was also run on Rocky Linux 9.8 with Python 3.9.25; it uses nothing that is not in the standard Python that comes with both systems. The random word in the examples is fixed (grep, unless another is named) so that the page can show exactly what happens; on your machine the word is random, so guess the letters of your own word.

Install and check Python

A tool is only fun if it runs, so we start by checking the one thing the game needs: Python 3. Every current Ubuntu and Rocky Linux has it. If you did the Ubuntu install with the defaults, it is already there:

$ python3 --version
Python 3.12.3
$ which python3
/usr/bin/python3
$ grep PRETTY_NAME /etc/os-release
PRETTY_NAME="Ubuntu 24.04.5 LTS"

If the first command says "command not found", install it. On Ubuntu and Debian:

$ sudo apt install python3

On Rocky Linux, Fedora and RHEL:

$ sudo dnf install python3

Nothing else is needed. The game uses only what comes with Python, which is the reason it packages so simply later: the package will say "needs python3" and nothing more.

Make a folder for the project and go into it. Everything in this guide happens in this folder:

$ mkdir ~/hangman
$ cd ~/hangman
$ pwd
/home/tc/hangman

To write the code you need a text editor. nano is the easiest one in a terminal: type nano hangman.py, type your text, press Ctrl+O then Enter to save and Ctrl+X to leave. Any editor works (vim, VS Code, gedit), but the file has to be plain text and has to be called hangman.py. Python cares about spaces at the start of a line, so type the lines exactly as shown, with the same number of spaces.

Step 1: your first program

A program is a text file of instructions that Python reads from the top, one line at a time. Open the file in your editor:

$ nano hangman.py

and type these two lines. The first starts with a # sign: Python ignores everything after a # on a line, so it is a note to humans (a comment). The second prints text to the screen:

$ cat hangman.py
# cmd-hangman - guess the Linux command before the stick figure is finished
print("=== cmd-hangman by TechCommune ===")

Save it and run it by giving the file to python3:

$ python3 hangman.py
=== cmd-hangman by TechCommune ===

If you see the title line, your editor, Python and the folder all work, and you have written your first program. If you get an error, it will say which line, and the section "When Python complains" near the end shows the common ones.

Step 2: a list of commands, and one at random

The game needs a list of Linux commands, each with a hint. Python has a type for that, a dictionary: curly braces, with "key": "value" pairs separated by commas. Here the key is the command and the value is its hint.

Replace the file with this. New lines this step: import random at the top (loads Python's random-number tools), the WORDS dictionary, and a function called play. A function is a named block of code; def starts it and the indented lines below it are its body. Nothing inside runs until the last line, play(), calls it:

$ cat hangman.py
# cmd-hangman - guess the Linux command before the stick figure is finished
import random

WORDS = {
    "ls": "list directory contents",
    "cd": "change directory",
    "pwd": "print the current directory",
    "grep": "search text for a pattern",
    "chmod": "change file permissions",
    "chown": "change file owner",
    "mkdir": "make a directory",
    "tail": "show the end of a file",
    "systemctl": "control system services",
    "journalctl": "read the system journal",
    "rsync": "copy files efficiently",
    "ssh": "log in to another machine",
    "crontab": "schedule repeating jobs",
    "kill": "stop a process",
    "df": "show free disk space",
}


def play():
    word = random.choice(list(WORDS))
    hint = WORDS[word]
    guessed = set()
    shown = " ".join(c if c in guessed else "_" for c in word)
    print("Command:", shown)
    print("Hint:   ", hint)


play()

What the lines in play() do:

word = random.choice(list(WORDS))   list(WORDS) is the list of keys (the commands); random.choice picks one
hint = WORDS[word]                  look the hint up by its key
guessed = set()                     a set is a bag of items with no duplicates; it starts empty. It will hold the letters tried
shown = " ".join(...)               builds the text on the screen: for each letter c in the word, show c if it was guessed, else an underscore, and join them with spaces

Run it. The word is random, so on your machine it will probably be a different one (and a different one each time you run it). The examples in this guide were run with a fixed starting point for the random numbers, so that the page is repeatable (the second run below is a different starting point, to show a different word); the commands you type are exactly the ones shown:

$ python3 hangman.py
Command: _ _ _ _
Hint:    search text for a pattern
$ python3 hangman.py
Command: _ _ _ _ _ _ _ _ _ _
Hint:    read the system journal

The lines of a function body are indented by four spaces. That is not decoration: Python uses the indentation to know where the function ends.

Step 3: guessing letters

Now the game part. Replace the play() function and keep everything above it as it is (the comment line, import random and the WORDS dictionary). The complete file after this step:

$ cat hangman.py
# cmd-hangman - guess the Linux command before the stick figure is finished
import random

WORDS = {
    "ls": "list directory contents",
    "cd": "change directory",
    "pwd": "print the current directory",
    "grep": "search text for a pattern",
    "chmod": "change file permissions",
    "chown": "change file owner",
    "mkdir": "make a directory",
    "tail": "show the end of a file",
    "systemctl": "control system services",
    "journalctl": "read the system journal",
    "rsync": "copy files efficiently",
    "ssh": "log in to another machine",
    "crontab": "schedule repeating jobs",
    "kill": "stop a process",
    "df": "show free disk space",
}


def play():
    word = random.choice(list(WORDS))
    hint = WORDS[word]
    guessed = set()
    while True:
        shown = " ".join(c if c in guessed else "_" for c in word)
        print("\nCommand:", shown)
        print("Hint:   ", hint)
        if all(c in guessed for c in word):
            print("\nYou got it! The command was:", word)
            return
        letter = input("Guess a letter: ").strip().lower()
        guessed.add(letter)


play()

New in this step:

while True:         repeat the indented block again and again, until something says stop
all(c in guessed for c in word)    true when every letter of the word is in the guessed set: the player has won
return              leave the function (this ends the round)
input("...")        show the question and wait for the player to type a line
.strip().lower()    remove stray spaces around the answer and make it lower case, so "G " counts as "g"
guessed.add(letter) put the letter into the set

Play it. In this run the word is grep, so typing g, r, e, p wins in four guesses (your word will differ, so guess the letters of your own word):

$ python3 hangman.py
Command: _ _ _ _
Hint:    search text for a pattern
Guess a letter: g

Command: g _ _ _
Hint:    search text for a pattern
Guess a letter: r

Command: g r _ _
Hint:    search text for a pattern
Guess a letter: e

Command: g r e _
Hint:    search text for a pattern
Guess a letter: p

Command: g r e p
Hint:    search text for a pattern

You got it! The command was: grep

Nothing punishes a wrong letter yet, and there is no picture. That is the next step.

Step 4: the stick figure and the misses

A round should end in a loss as well as in a win. The pictures are a list of seven strings (STAGES), from an empty gallows to the finished figure. Each wrong guess moves one picture further. Add the STAGES list below the WORDS dictionary, and change play() as shown:

$ cat hangman.py
# cmd-hangman - guess the Linux command before the stick figure is finished
import random

WORDS = {
    "ls": "list directory contents",
    "cd": "change directory",
    "pwd": "print the current directory",
    "grep": "search text for a pattern",
    "chmod": "change file permissions",
    "chown": "change file owner",
    "mkdir": "make a directory",
    "tail": "show the end of a file",
    "systemctl": "control system services",
    "journalctl": "read the system journal",
    "rsync": "copy files efficiently",
    "ssh": "log in to another machine",
    "crontab": "schedule repeating jobs",
    "kill": "stop a process",
    "df": "show free disk space",
}

STAGES = [
    "\n\n\n\n\n_______",
    "\n |\n |\n |\n |\n_|_____",
    " ______\n |\n |\n |\n |\n_|_____",
    " ______\n |    |\n |    O\n |\n |\n_|_____",
    " ______\n |    |\n |    O\n |    |\n |\n_|_____",
    " ______\n |    |\n |    O\n |   /|\\\n |\n_|_____",
    " ______\n |    |\n |    O\n |   /|\\\n |   / \\\n_|_____",
]


def play():
    word = random.choice(list(WORDS))
    hint = WORDS[word]
    guessed = set()
    misses = 0
    while misses < len(STAGES) - 1:
        print(STAGES[misses])
        shown = " ".join(c if c in guessed else "_" for c in word)
        print("\nCommand:", shown)
        print("Hint:   ", hint)
        if all(c in guessed for c in word):
            print("\nYou got it! The command was:", word)
            return
        letter = input("Guess a letter: ").strip().lower()
        if letter in word:
            guessed.add(letter)
        else:
            guessed.add(letter)
            misses += 1
            print("Nope!")
    print(STAGES[misses])
    print("\nGame over. The command was:", word)


play()

What changed:

misses = 0               counts the wrong guesses
while misses < len(STAGES) - 1    keep playing until the last picture (number 6) has been reached; len() is the length of the list
print(STAGES[misses])    show the picture for the current number of misses (the first one is number 0)
if letter in word: ... else: ...   a right guess is remembered; a wrong guess is also remembered (so it is not counted twice) and adds one to misses
the last two print lines   run after the loop ends without a win: the player lost

The backslashes in the figure's arms are written twice ("\\"): in Python text a single backslash starts a special code such as \n for a new line, so a real backslash has to be doubled.

A round with two wrong guesses and a win:

$ python3 hangman.py
_______

Command: _ _ _ _
Hint:    search text for a pattern
Guess a letter: g





_______

Command: g _ _ _
Hint:    search text for a pattern
Guess a letter: z
Nope!

 |
 |
 |
 |
_|_____

Command: g _ _ _
Hint:    search text for a pattern
Guess a letter: r

 |
 |
 |
 |
_|_____

Command: g r _ _
Hint:    search text for a pattern
Guess a letter: q
Nope!
 ______
 |
 |
 |
 |
_|_____

Command: g r _ _
Hint:    search text for a pattern
Guess a letter: e
 ______
 |
 |
 |
 |
_|_____

Command: g r e _
Hint:    search text for a pattern
Guess a letter: p
 ______
 |
 |
 |
 |
_|_____

Command: g r e p
Hint:    search text for a pattern

You got it! The command was: grep

And a loss. The word is grep again; six letters that are not in it end the game (only the end of the output is shown):

$ python3 hangman.py
(earlier screens left out)
Hint:    search text for a pattern
Guess a letter: f
Nope!
 ______
 |    |
 |    O
 |   /|\
 |
_|_____

Command: _ _ _ _
Hint:    search text for a pattern
Guess a letter: h
Nope!
 ______
 |    |
 |    O
 |   /|\
 |   / \
_|_____

Game over. The command was: grep

Step 5: the finished game

Two things are missing: the game believes anything the player types (a number, two letters, the same letter twice), and it ends after one round. This final version fixes both. The input checks go into play(), and a second function, main(), repeats rounds while the player answers y. The last two lines are the usual way to start a Python program: they run main() only when the file is run, not when another program imports it.

$ cat hangman.py
# cmd-hangman - guess the Linux command before the stick figure is finished
import random

WORDS = {
    "ls": "list directory contents",
    "cd": "change directory",
    "pwd": "print the current directory",
    "grep": "search text for a pattern",
    "chmod": "change file permissions",
    "chown": "change file owner",
    "mkdir": "make a directory",
    "tail": "show the end of a file",
    "systemctl": "control system services",
    "journalctl": "read the system journal",
    "rsync": "copy files efficiently",
    "ssh": "log in to another machine",
    "crontab": "schedule repeating jobs",
    "kill": "stop a process",
    "df": "show free disk space",
}

STAGES = [
    "\n\n\n\n\n_______",
    "\n |\n |\n |\n |\n_|_____",
    " ______\n |\n |\n |\n |\n_|_____",
    " ______\n |    |\n |    O\n |\n |\n_|_____",
    " ______\n |    |\n |    O\n |    |\n |\n_|_____",
    " ______\n |    |\n |    O\n |   /|\\\n |\n_|_____",
    " ______\n |    |\n |    O\n |   /|\\\n |   / \\\n_|_____",
]


def play():
    word = random.choice(list(WORDS))
    hint = WORDS[word]
    guessed = set()
    misses = 0
    while misses < len(STAGES) - 1:
        print(STAGES[misses])
        shown = " ".join(c if c in guessed else "_" for c in word)
        print("\nCommand:", shown)
        print("Hint:   ", hint)
        if all(c in guessed for c in word):
            print("\nYou got it! The command was:", word)
            return
        letter = input("Guess a letter: ").strip().lower()
        if len(letter) != 1 or not letter.isalpha():
            print("Type a single letter.")
        elif letter in guessed:
            print("You already tried that one.")
        elif letter in word:
            guessed.add(letter)
        else:
            guessed.add(letter)
            misses += 1
            print("Nope!")
    print(STAGES[misses])
    print("\nGame over. The command was:", word)


def main():
    print("=== cmd-hangman by TechCommune ===")
    while True:
        play()
        if input("\nPlay again? (y/n) ").strip().lower() != "y":
            print("Happy hacking!")
            break


if __name__ == "__main__":
    main()

Count the lines: it is 71 lines, and you wrote all of them. New in this step:

len(letter) != 1 or not letter.isalpha()   not exactly one character, or not a letter: ask again
elif letter in guessed                      already tried: say so, no penalty
def main():                                 the outer loop: play a round, ask "Play again?", stop unless the answer is y

Try the bad input first (a number, two letters, a repeated letter), then win and say n. The word is grep again:

$ python3 hangman.py
=== cmd-hangman by TechCommune ===





_______

Command: _ _ _ _
Hint:    search text for a pattern
Guess a letter: 7
Type a single letter.





_______

Command: _ _ _ _
Hint:    search text for a pattern
Guess a letter: gr
Type a single letter.





_______

Command: _ _ _ _
Hint:    search text for a pattern
Guess a letter: g





_______

Command: g _ _ _
Hint:    search text for a pattern
Guess a letter: g
You already tried that one.





_______

Command: g _ _ _
Hint:    search text for a pattern
Guess a letter: r





_______

Command: g r _ _
Hint:    search text for a pattern
Guess a letter: e





_______

Command: g r e _
Hint:    search text for a pattern
Guess a letter: p





_______

Command: g r e p
Hint:    search text for a pattern

You got it! The command was: grep

Play again? (y/n) n
Happy hacking!

Run it like a real program

python3 hangman.py works, but a real command is typed by name. Two changes make that possible. First, the very first line of the file can tell the system which interpreter runs it. That line is called the shebang (#! and a path). Put it above the comment line:

$ sed -i '1i #!/usr/bin/env python3' hangman.py
$ head -3 hangman.py
#!/usr/bin/env python3
# cmd-hangman - guess the Linux command before the stick figure is finished
import random

(/usr/bin/env python3 finds python3 wherever it is installed, which matters because the path differs between systems.) Second, mark the file as executable, then run it with ./ in front of the name, which means "the file in this folder":

$ chmod +x hangman.py
$ ls -l hangman.py
-rwxr-xr-x 1 tc tc 2185 Oct 30 22:10 hangman.py

Run it by name and play a round (the word is grep again; you type your own guesses):

$ ./hangman.py
=== cmd-hangman by TechCommune ===





_______

Command: _ _ _ _
Hint:    search text for a pattern
Guess a letter: g





_______

Command: g _ _ _
Hint:    search text for a pattern
Guess a letter: r





_______

Command: g r _ _
Hint:    search text for a pattern
Guess a letter: e





_______

Command: g r e _
Hint:    search text for a pattern
Guess a letter: p





_______

Command: g r e p
Hint:    search text for a pattern

You got it! The command was: grep

Play again? (y/n) n
Happy hacking!

Play it for real now with ./hangman.py, for as long as you like. This final file is exactly the program the next two guides turn into a .deb and an .rpm, so keep it. If you would rather not type it all, the finished file is the one printed in step 5 with the shebang line on top.

Make it yours

The WORDS dictionary is the part to change. Add a line (do not forget the comma at the end of the line before it, and at the end of your new line):

    "sudo": "run a command as another user",
    "tar": "bundle files into an archive",

Rules the game relies on: use only lower-case letters in the command (no dashes or digits, so apt-get or python3 would not work as written), and keep each hint short. How the game could grow:

MAX_MISSES     the number of pictures decides it: remove a picture for an easier game, add one for a harder one
a score        keep a wins counter in main() and print it after each round (a good first exercise)
categories     one dictionary per topic (networking, files, services) and a menu to choose

Whatever you change, run it again and play a round before you move on: the quickest way to find a typing mistake is to run it straight after you make it.

When Python complains

Typing along always produces a few mistakes. Python's messages name the file and the line, so they are easy to follow. The five you are most likely to meet, each produced here on purpose:

A missing quote:

$ cat bad1.py
print("hello)
$ python3 bad1.py
  File "/home/tc/hangman/bad1.py", line 1
    print("hello)
          ^
SyntaxError: unterminated string literal (detected at line 1)

Indentation that does not line up (the body of play() must be indented):

$ cat bad2.py
def play():
print("hello")
play()
$ python3 bad2.py
  File "/home/tc/hangman/bad2.py", line 2
    print("hello")
    ^
IndentationError: expected an indented block after function definition on line 1

A missing colon at the end of a def, if or while line:

$ cat bad3.py
def play()
    print("hello")
play()
$ python3 bad3.py
  File "/home/tc/hangman/bad3.py", line 1
    def play()
              ^
SyntaxError: expected ':'

A name typed wrongly (here pritn instead of print):

$ cat bad4.py
pritn("hello")
$ python3 bad4.py
Traceback (most recent call last):
  File "/home/tc/hangman/bad4.py", line 1, in <module>
    pritn("hello")
    ^^^^^
NameError: name 'pritn' is not defined. Did you mean: 'print'?

A missing comma in the dictionary (the commonest typing error of all in this game):

$ cat bad5.py
WORDS = {
    "ls": "list directory contents"
    "cd": "change directory",
}
$ python3 bad5.py
  File "/home/tc/hangman/bad5.py", line 3
    "cd": "change directory",
        ^
SyntaxError: invalid syntax

The rule that fixes nearly all of them: read the last line of the message, then look at the line it points to and the line just above it. Python often notices a problem one line after the real mistake.

The finished file, to compare

If something still does not work, compare your file with the real one by its checksum. The checksum of the finished file with the shebang line (see "Run it like a real program") is:

$ sha256sum hangman.py
ca34b1f9bb0ba7df711bdec7cd90729407b79e3add7578b0dd894e6ae83b770b  hangman.py
$ wc -l hangman.py
72 hangman.py

Same line count and checksum means identical text. Different checksum: print both with cat -A to see stray tabs or trailing spaces, which are invisible but count as differences.

Next in the series: Build a .deb package for Ubuntu and Debian, and Build an .rpm package for Rocky Linux, Fedora and RHEL. Both start from this hangman.py.