GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • About AskPython
  • Blog
  • Contact Me
  • Free Online Developer Tools Hub
  • Home
  • Meet the Team
  • Privacy Policy
  • Python
  • Python Course for Beginners Online FREE
  • Python Tutorial – Mega Index
  • Terms and Conditions
  • Try YTGrowAI Free
  • Aprataksh Anand
  • Python Programming Examples

This view

  • Original page
  • New address

GeezerVison is showing www.askpython.com — Create Mastermind Game in Python: A Terminal Tutorial - AskPython

Create Mastermind Game in Python: A Terminal Tutorial

Creating a game in Python can start with a terminal version of Mastermind. Python chooses a hidden four-letter color code and gives a clue after each valid guess, showing exact matches and colors in the wrong positions.

This article shows how to build and run the game, including how its clues score repeated colors.

TL;DR

This Python game chooses a hidden color code, checks each guess, and reports exact and misplaced matches.

  • Save the program as mastermind_game.py.
  • Run python3 mastermind_game.py to play, or add --demo for fixed output.
  • Repeated colors are allowed, and a secret color can match only once.

What Is a Mastermind Game in Python?

Mastermind is a code-breaking game in which one player chooses a hidden sequence and another submits guesses until the code is found or the turn limit runs out.

This terminal version assigns the codemaker role to Python and prints a clue after each valid guess. The person at the keyboard plays the codebreaker, without a second human or a physical board.

The code has four positions and uses the letters R, G, B, Y, O, and P for red, green, blue, yellow, orange, and purple. A clue reports exact positions and matching colors in different positions without identifying which colors earned either count.

ClueMeaningExample
ExactSame color in the same positionWith secret RGBY, guess RRRR has one exact
MisplacedColor is present in another positionWith secret RGBY, guess GRBY has two misplaced
No matchNo unused copy of that color remainsWith secret RGBY, guess PPPP has none

I use letter labels because they work in shells without terminal color support.

A unique-color variant changes both secret generation and guess validation.

Each valid guess follows the same path: parse the text, calculate the clue, then print the two values.

Data structures for the game variables

The game state is small enough to keep in ordinary Python values. The palette is a tuple of allowed symbols, the secret and each accepted guess are tuples, and the turn counter is an integer. This makes the rules visible instead of hiding them in a class hierarchy.

COLORS defines the input alphabet, CODE_LENGTH controls the number of positions, and MAX_GUESSES sets the limit. Changing CODE_LENGTH also requires updating the parser’s length message and the winning check. Keep those values consistent when you change the game size.

VariableTypePurpose
COLORSTuple of stringsAllowed code symbols
secretTuple of stringsHidden answer used for every turn
guessTuple of stringsValidated player input for one turn
turnIntegerNumber of valid guesses already scored
historyList of pairs, when addedAccepted guess and its feedback

I keep the secret as a tuple because the scoring function only needs an ordered sequence. A dictionary would add named fields without making this game’s three pieces of state clearer.

The game menu and round flow

The current program has one mode: play a round in the terminal. argparse adds a separate demo option for a repeatable example, while normal play generates a secret and repeatedly asks for guesses. That keeps the main path short and avoids a menu prompt before the first game.

A menu can present actions such as starting a round, reading rules, or quitting. The loop below dispatches those choices and handles an invalid selection. Keep this presentation loop outside the scoring function, which returns feedback to its caller.

while True:
    print("1. Play")
    print("2. Quit")
    choice = input("Choose an option: ").strip()
    if choice == "1":
        play_round()
    elif choice == "2":
        break
    else:
        print("Choose 1 or 2.")

This menu sketch assumes a play_round function that owns one round, so the outer loop handles choices while round logic handles guesses.

Apply sanity checks before scoring

A sanity check rejects input that cannot be a legal guess before it reaches score_guess. parse_guess applies two checks in order: the tuple must contain exactly four symbols, and every symbol must belong to COLORS.

Whitespace is normalized before validation, so a player can type R G B Y or RGBY. The conversion to uppercase accepts lowercase letters. An unknown symbol triggers a clear error rather than changing the intended guess.

InputResultReason
rgb yAccepted as RGBYSpaces are removed and letters normalized
RGBRejectedOnly three positions were supplied
RGBYXRejectedFive positions exceed the code length
RBGXRejectedX is outside the palette

The exception is caught in the input loop, printed to the player, and followed by another prompt. Since the turn count changes only after scoring, a rejected guess does not spend an attempt.

Store player moves separately from game state

For a game that displays earlier attempts, keep a history list. Append an entry only after parsing succeeds, and store the clue beside the guess. A pair keeps the accepted move and its feedback together, which makes a turn-by-turn display straightforward.

history = []

# After parse_guess and score_guess succeed:
history.append((guess, (exact, misplaced)))

for number, (old_guess, clue) in enumerate(history, start=1):
    exact, misplaced = clue
    print(number, "".join(old_guess), exact, misplaced)

The sample stores validated tuples in history. That keeps displayed moves in the same normalized form accepted by the parser, and the clue stays paired with the move that produced it. The current game only checks the latest guess, so history is an optional extension.

How to Create a Mastermind Game in Python Step by Step

This section assembles the scorer and input parser into a complete command-line game.

Step 1: Count exact and misplaced matches

Counter stores the remaining symbol frequencies, and its intersection keeps the smaller count for a color present in both unmatched sequences.

With secret RRGY and guess GRRR, the second slot is exact. The unmatched secret has R, G, and Y, while the guess has G and two Rs. The function returns one exact and two misplaced.

Step 2: Validate the four-letter guess

parse_guess also verifies the length and palette before returning a tuple, raising ValueError for a wrong length or unknown symbol.

Step 3: Assemble and run the complete program

The loop scores only parsed guesses. It increments the turn after feedback and stops when the code is solved or the guess limit is reached.

Save the file in the folder you plan to use for the game. The script uses Python’s standard library and creates no data files. Run it from that directory or provide its path if the terminal is elsewhere.

The demo flag selects a fixed secret and two guesses for a repeatable transcript. Demo mode bypasses input, scores RRRR and RGBY, and exits after the winning guess.

import argparse
import random
from collections import Counter

COLORS = ("R", "G", "B", "Y", "O", "P")
CODE_LENGTH = 4
MAX_GUESSES = 8


def score_guess(secret, guess):
    exact = sum(s == g for s, g in zip(secret, guess))
    left = Counter(s for s, g in zip(secret, guess) if s != g)
    right = Counter(g for s, g in zip(secret, guess) if s != g)
    return exact, sum((left & right).values())


def parse_guess(raw):
    guess = tuple("".join(raw.strip().upper().split()))
    if len(guess) != CODE_LENGTH:
        raise ValueError("Enter exactly four letters.")
    if set(guess) - set(COLORS):
        raise ValueError("Use only R, G, B, Y, O, or P.")
    return guess


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--demo", action="store_true")
    args = parser.parse_args()
    secret = tuple("RGBY") if args.demo else tuple(
        random.choice(COLORS) for _ in range(CODE_LENGTH)
    )
    print("Mastermind: enter four letters from R G B Y O P.")
    if args.demo:
        for turn, raw in enumerate(("RRRR", "RGBY"), 1):
            exact, misplaced = score_guess(secret, parse_guess(raw))
            print(f"Guess {turn}: {raw} -> {exact} exact, {misplaced} misplaced")
            if exact == CODE_LENGTH:
                print("You cracked the code.")
                break
        print(f"Demo secret: {' '.join(secret)}")
        return

    turn = 0
    while turn < MAX_GUESSES:
        try:
            guess = parse_guess(input(f"Guess {turn + 1}/{MAX_GUESSES}: "))
        except ValueError as error:
            print(error)
            continue
        exact, misplaced = score_guess(secret, guess)
        print(f"Clue: {exact} exact, {misplaced} misplaced")
        if exact == CODE_LENGTH:
            print(f"You cracked the code in {turn + 1} guesses.")
            return
        turn += 1
    print("No guesses left. The code was", " ".join(secret))


if __name__ == "__main__":
    main()

Run the fixed example to see one partial clue followed by a solved code:

python3 mastermind_game.py --demo
Mastermind: enter four letters from R G B Y O P.
Guess 1: RRRR -> 1 exact, 0 misplaced
Guess 2: RGBY -> 4 exact, 0 misplaced
You cracked the code.
Demo secret: R G B Y
Mastermind demo showing two guesses and exact-match feedback
A partial clue appears before the solved code.

The first clue counts only the first R because the other guessed copies have no matching secret position. The final guess matches every slot, so the game reports success. The terminal image records the complete command output rather than a hand-written transcript.

Handle Repeated Colors and Invalid Guesses

These cases cover the points where a guess, a clue, or the turn loop can take a different path.

CaseWhat happensWhat to check
Guess repeats a color more often than the secretOnly available copies earn exact or misplaced matchesRGBY against RRRR returns one exact and zero misplaced
Guess has fewer or more than four lettersValueError prints the required length and prompts againConfirm the turn count does not change
Guess contains a character outside the paletteThe parser rejects it before scoringTry a guess containing X
All eight valid guesses missThe loop prints the hidden code and endsTest with a fixed secret and eight non-winning guesses
Guess matches every positionThe game prints a win message and returnsCheck that no later prompt appears

I ran assertions for RRGY against GRRR and RGBY against RRRR, then checked spaced input, an unknown letter, and a winning sequence. The returned scores and parser behavior matched the expected results.

This program leaves KeyboardInterrupt and EOFError visible during development, though a version for repeated use can catch EOFError around input and print an exit message.

Check for the loss condition

The round has two endings: a correct code ends it early, while using all allowed guesses ends it as a loss. The loop’s else path prints the secret only after the limit expires. Keep that message outside the loop so it does not appear after a winning return.

OutcomeControl flowPlayer sees
Correct guessWinning condition returns from mainWin message and number of guesses
Limit reachedLoop completes without returningLoss message and hidden code
Invalid inputException handler asks againError message, same turn number

That distinction matters when adding a replay option. Start a new round only after the current round has reached one of its two terminal outcomes, and generate a fresh secret for that new round.

When to use a class or a graphics library

A small terminal game can keep its state in local variables and functions. Object-oriented programming becomes useful when several rounds must retain separate state, or when game entities have behavior that belongs with their data. A Game class could own the secret, turn count, and history, while score_guess remains a standalone rule that is simple to test.

Pygame addresses a different need: drawing a window, handling input events, and updating a visual game loop. It adds a library dependency and a display loop, so it is a poor first step when the goal is to learn code generation, parsing, and scoring. The command-line version keeps those rules independent, which gives a later graphical version the same scorer to call.

Rock, Paper, Scissors is another suitable terminal project, but its result rule differs. It compares one player choice with one computer choice and resolves a fixed set of winning pairs. Mastermind instead keeps a secret sequence across turns and computes partial feedback, so reusing its score function for a different game would mix unrelated rules.

What to Do Next

Once the game runs, extend one rule at a time and check that the scoring function still matches the chosen rules.

I would add replay before adding a graphical interface because replay exercises the round boundary without changing how a clue is calculated. Put one round in a function that returns whether the player won, then let the outer loop ask whether to start another round. Generate a new secret inside that function so a replay does not reuse the previous answer.

Random generation makes ordinary play vary from run to run, which is expected for a guessing game. For a repeatable test, pass a known secret into a round or test score_guess directly with fixed tuples. That separates a bug in clue calculation from a guess sequence that simply did not find the randomly selected answer.

ChangeFunction or value affectedCheck after editing
More colorsCOLORSParser accepts each symbol and generation can choose each one
Longer codeCODE_LENGTH and matching checksInput length, win condition, and clue total agree
More attemptsMAX_GUESSESFinal valid miss reaches the loss message
No repeated colorsSecret generation and parse_guessNeither generated codes nor accepted guesses contain duplicates

Keep configuration changes together. If repeated colors are disabled only during secret generation, a player can still submit a duplicate guess. The chosen rule must be enforced at both boundaries, and the clue function should continue to count exact positions before counting remaining colors.

# Record only a parsed guess.
guess = parse_guess(raw)
history.append((guess, score_guess(secret, guess)))

# Enforce unique colors in both places.
secret = tuple(random.sample(COLORS, CODE_LENGTH))
if len(set(guess)) != CODE_LENGTH:
    raise ValueError("Use four different colors.")

Conclusion: Keep the Clue Rule Testable

Before adding history or another mode, decide which value changes after a valid guess and which stays fixed.

ExtensionState to addFirst check
Guess historyAccepted guesses paired with cluesEach row shows the same feedback as score_guess
Unique-color modeA flag controlling secret and guess rulesGenerated secrets and accepted guesses follow one policy

For the library behavior, see the Python docs for collections.Counter and random. AskPython’s Python games roundup has other project ideas, and its command-line number guessing game is another example of input and turn control.

FAQ

These answers cover the main implementation choices.

Can I make a game in Python without installing a library?

Yes. This terminal Mastermind game uses only Python’s standard library, including random and collections.Counter.

How does Mastermind score repeated colors?

Count exact positions first, then compare remaining color frequencies. Each secret copy can contribute only once.

How many guesses does this Python game allow?

The program allows eight valid guesses. Invalid entries show an error and do not consume a turn.

Aprataksh Anand
Aprataksh Anand

A Computer Science Engineer, content writing is Aprataksh's first rodeo. A passionate coder along with a love for gaming, he can be found passing his free time with friends in a CS:GO lobby.

Articles: 13

Related Posts

ReLU Function: Python Examples and Use

ReLU Function: Python Examples and Use

  • November 11, 2026
Python Predict() Function: How It Works

Python Predict() Function: How It Works

  • November 11, 2026
One Hot Encoding in Python

One Hot Encoding in Python

  • November 10, 2026
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.