Skip to content

About

A Python desktop application to play Mastermind, the classic code-breaking game, featuring an intuitive graphical interface

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Mastermind

Version: 1.0.0 Date: Oct. 11, 2026 Author: Matteo P. URL: https://github.com/pmatteo68/mastermind


Abstract

A desktop implementation of the classic Mastermind code-breaking game, built with Python. Two opponents -- a human player and a computer -- each hide a secret code and take turns trying to crack each other's code within a fixed number of attempts. Feedback after each guess (black pegs for correct color in the correct position, white pegs for correct color in the wrong position) progressively narrows the solution space. The computer opponent uses the Knuth minimax algorithm, which guarantees an optimal worst-case strategy.

The game runs fully offline with no dependency on external services.


Setup

1. Create the virtual environment

Run create_env script once to create the virtual environment and install dependencies. If the environment already exists the script simply activates it.

2. Launch the game

Run play script every time to activate the virtual environment and start the game.


How to Play

Start screen

Before the game begins you choose:

  • Who guesses first -- you or the machine. This determines which player opens the first round.
  • Secret codes -- whether both rounds share the same secret code (what the machine generated is what you both try to crack), or each player hides a different code. Note: the same-code option is only available when you go first; if the machine goes first you would have seen all the feedback and the game would be trivial.
  • Number of colors -- how many distinct colors are available to build a code. More colors make the code harder to guess.
  • Number of pegs -- how long each code is. Longer codes are harder to crack.
  • Attempts per round -- the maximum number of guesses each player gets. Fewer is harder.
  • Repetition -- whether the same color can appear more than once in a code. Allowing repetition expands the solution space considerably.

Once you are happy with the settings click Start Game. To resume a previously saved game click Load Game right next to it.

Playing your round

When it is your turn to guess you interact with the keyboard panel on the right:

  • Double-click a color circle to place it in the next empty slot of your current guess.
  • Drag-and-drop a color circle directly onto any specific slot of the current-guess strip to place it there. You can fill slots in any order this way.
  • Clear removes all pegs from your current guess so you can start over.
  • Confirm submits your current guess (all slots must be filled). The row is committed to the board and feedback pegs appear immediately.
  • Get AI Hint asks the computer engine to compute its best suggestion given everything that has been guessed so far. The suggestion animates into the current-guess strip peg by peg. You are free to accept it (click Confirm) or clear it and choose your own.

Feedback is shown to the right of each committed guess row:

  • Black peg -- the right color in the right position.
  • White peg -- the right color but in the wrong position.

Your goal is to get all-black feedback (every peg correct) in as few attempts as possible.

The machine's round

When it is the machine's turn (either as the first guesser or after your round), you watch it place pegs automatically, one at a time, with a brief animated blink for each peg. The machine's secret code appears in a banner above the board so you can follow its reasoning. After the machine finishes its round a summary dialog appears and then play continues.

Defining a code for the machine (different-code mode)

In different-code mode you also play the role of the code setter: after one round ends you are asked to define the secret code the machine will try to crack in the next round. Use the same keyboard panel (double-click or drag-and-drop) to place your pegs, then confirm. The machine will not see your code until it has finished guessing (or run out of attempts).

Winning

Each player is scored by how many attempts they needed. Fewer is better. If one player cracks the code and the other does not, the one who cracked it wins. If both crack it, the one who used fewer attempts wins. Equal attempts is a draw.

At game end a summary window appears with:

  • The winner and each player's attempt count.
  • An efficiency percentage (how many attempts were saved versus the maximum allowed).
  • Total game duration and average AI compute time per guess.
  • A trajectory chart (requires matplotlib) showing how each player's match score evolved over their guesses. The score accounts for both exact matches (black pegs) and partial matches (white pegs), with white pegs counting as a fractional bonus so that a guess with one black and one white always plots slightly above a guess with one black only.

Saving and loading

Use the Game menu to save the current game state to a JSON file at any point during play. You can load it later via the same menu or via the Load Game button on the start screen. The loaded game resumes exactly where it was left off, with all previously committed guesses already shown on the board.


Configuration

All user-tunable parameters are isolated in dedicated files under src/. No other file needs to be edited for typical customisation.

File Purpose
src/config_game.py Gameplay rules: board dimension bounds and defaults, AI algorithm tuning, all animation timing, autoplay mode
src/config_ui.py Active skin selection; global layout overrides that apply on top of the skin
src/config_text.py Every user-visible string: button labels, menu items, status messages, dialog text, chart axis labels
src/log_config.py Logging: output file path, console echo toggle, global log level

Gameplay knobs (config_game.py)

  • Board dimension bounds -- the minimum, maximum, and default values offered on the start screen for number of colors, number of pegs, and number of attempts. Raising the maximum for pegs or colors beyond their current ceilings requires corresponding updates to every skin file (see Tech Notes below for the constraints).
  • AI algorithm tuning -- the threshold below which the engine uses exhaustive minimax and above which it switches to a fast random-sample heuristic, plus the heuristic sample size. Lowering the threshold makes the AI slower but more precise; raising it makes it faster but less optimal for large code spaces.
  • Animation timing -- independent controls for the delay before the AI starts each guess, the blink speed during peg placement, the pause between successive pegs, the pause between guess rows, and the timing of the end-of-round secret reveal. All values are in milliseconds and can be set to zero for instant rendering.
  • Autoplay mode -- when enabled, both the human and the AI round run automatically without any interaction. Useful for quickly reaching the end-of-game stats. See the Debugging section below.

Visual themes (config_ui.py)

Set the active skin to any of the names listed in the Available Skins section below, or use "random" to pick a different skin on every launch. If a named skin cannot be loaded the application falls back to the default skin automatically.

Text and labels (config_text.py)

Every word the user sees -- button labels, status bar messages, dialog titles, round headers, chart axis labels, feedback strings -- lives here. Edit this file to translate the game, rebrand it, or adjust any label without touching the rest of the code.


Tech Notes

  • Language / GUI: Python 3.8+, tkinter (bundled with CPython). No installation required for tkinter on Windows; on Linux install python3-tk via the package manager.

  • AI engine: Knuth minimax. Ref. details in Available AI engines section.

  • Board dimension ceilings: the number of colors and the number of pegs are not infinitely scalable. Each skin defines a fixed peg color palette, and the board layout allocates a fixed amount of space per row; beyond certain values both constraints are violated and the result is visual corruption or a crash. The current ceilings are documented as comments at the top of src/config_game.py, together with the reasoning behind each limit. Before raising any maximum beyond its current value, read those comments first.

  • Trajectory chart: an optional matplotlib figure embedded in the end-of-game stats window. If matplotlib is not installed the chart section is simply omitted and the rest of the stats window is unaffected. The chart can also be disabled in the UI configuration file even when matplotlib is present.

  • Hint and AI engine isolation: the AI hint and the autoplay human turn each run on a background thread so the UI stays responsive while the engine thinks. They use an independent engine instance so that hints do not corrupt the state of the AI's own guessing round.

  • Save / load format: game state is stored as a human-readable JSON file. The format version is embedded in the file; a mismatch raises a clear error at load time.


AI Engine Plugin System

The AI logic is fully encapsulated behind a plugin interface. The active engine is selected with a single line in src/config_game.py:

AI_ENGINE = "knuth"

The value is the name of a module inside src/ai_engines/. The facade src/ai_engine.py loads it at runtime. The rest of the codebase never references a specific engine directly.

Contract

Every engine module must contain a class that subclasses MastermindAIEngine (from ai_engines/base.py) and implements the required methods, plus a module-level factory function.

Method What it must do
get_name() Return a short human-readable name (shown on the start screen and in logs)
_compute_next_guess(history, num_colors, num_pegs) Given the history of all prior guesses and their feedback, return the next guess as a list of color indices
reset() Reset all internal state so the engine is ready for a fresh round

Plus a module-level factory:

def create():
    return MyEngine()

The history parameter

history is a list of (guess, feedback) pairs representing every guess made so far in the current round:

[
    ([2, 0, 1, 3], (1, 0)),   # guess, (black_pegs, white_pegs)
    ([0, 2, 1, 3], (2, 1)),
    ...
]

Each guess is a list of integer color indices (0-based, length == num_pegs). Each feedback is a tuple of two integers: the number of black pegs (right color, right position) and the number of white pegs (right color, wrong position). An empty history means it is the first guess of the round.

What the base class handles automatically

The abstract base class provides the public next_guess() method, which you must NOT override. It calls your _compute_next_guess(), then verifies that the returned code was not already tried in this round. If it was (which should not happen in a correct implementation but is guarded against), it silently substitutes the first valid untried code and logs a warning. This guarantee applies to every engine automatically.

Writing a new engine -- step by step

  1. Create src/ai_engines/my_engine.py.

  2. Import the base class and subclass it:

    from .base import MastermindAIEngine
    
    class MyEngine(MastermindAIEngine):
        ...
  3. Implement get_name():

    def get_name(self):
        return "My Engine"
  4. Implement reset() -- clear all internal data structures that accumulate state across guesses (e.g. candidate lists, memo tables, tried-code sets):

    def reset(self):
        self._candidates = None
        # ... any other state
  5. Implement _compute_next_guess(history, num_colors, num_pegs):

    • On the first call (history is empty or your internal state is not yet initialised), build the full list of all possible codes:
      import itertools
      all_codes = list(itertools.product(range(num_colors), repeat=num_pegs))
    • On subsequent calls, filter your candidate list: keep only the codes whose feedback against each past guess matches the recorded feedback. This is the key correctness invariant -- a code that contradicts any prior feedback can never be the secret and must never be returned:
      for guess, fb in history[self._processed:]:
          self._candidates = [
              c for c in self._candidates
              if compute_feedback(c, tuple(guess)) == tuple(fb)
          ]
    • Choose a guess from the remaining candidates using whatever strategy you prefer (random choice, minimax, entropy maximisation, etc.) and return it as a list of integers.
    • Never return a code that is not in your filtered candidate list unless the list is empty (an impossible edge case caused by inconsistent feedback). The base class will catch any repeated guess, but returning a code inconsistent with prior feedback is a logical error your engine should prevent.
  6. Add the module-level factory at the bottom:

    def create():
        return MyEngine()
  7. Set AI_ENGINE = "my_engine" in src/config_game.py. No other file needs to change. Your engine name from get_name() will appear on the start screen automatically.

Feedback function reference

from collections import Counter

def compute_feedback(secret, guess):
    """Return (black_pegs, white_pegs) for a (secret, guess) pair."""
    blacks = sum(s == g for s, g in zip(secret, guess))
    s_cnt  = Counter(secret)
    g_cnt  = Counter(guess)
    total  = sum(min(s_cnt[c], g_cnt[c]) for c in s_cnt)
    return blacks, total - blacks

Both secret and guess are tuples (or lists) of integer color indices.

Available AI engines

Name Module Strategy
Knuth Minimax knuth Exhaustive minimax for small code spaces (up to ~2000 candidates); random-sample heuristic for larger spaces. Always picks the guess that minimises the worst-case number of remaining candidates after the feedback is received. Guarantees cracking any 4-peg / 6-color code in at most 5 guesses. For larger spaces it falls back to a random-sample heuristic that is fast but no longer strictly optimal. The engine maintains a list of codes that are still consistent with every feedback received so far; it never guesses a code it has already tried, and it never guesses a code that contradicts any prior feedback.

Skin Plugin System

The visual appearance is fully encapsulated behind a skin plugin interface. The active skin is selected with a single line in src/config_ui.py:

SKIN = "default"

The value is the name of a module inside src/skins/. If the module cannot be loaded the default skin is used automatically.

Writing a new skin

  1. Copy src/skins/default.py to src/skins/my_skin.py.
  2. Change the skin name constant near the top to a short descriptive label.
  3. Edit colors, fonts, and dimension values to taste. Every constant in the default skin must be present with the same name; do not remove any.
  4. Set SKIN = "my_skin" in src/config_ui.py. No other file needs to change.

Available skins

Name Module Description
Default default Warm cream background, classic vivid peg palette.
Midnight midnight Near-black canvas, neon electric pegs, dark-mode feel.
Sunset sunset Amber and coral warm tones, gold accents.
Ocean ocean Deep navy canvas, cyan and teal pegs.
Forest forest Mossy greens, bark browns, earthy palette.
Candy candy Soft blush-white canvas, pastel bubblegum palette.
Vivid-1 vivid_1 Full-saturation primaries on white, high energy.
Vivid-2 vivid_2 Electric cobalt and cyan on pale blue.
Mint-Gray mint_gray Cool gray canvas, mint-green accents.
Black-and-White black_and_white Pure monochrome grayscale, no color anywhere.
Random "random" Picks a different skin on every launch.

Debugging

Log levels

Logging is configured in src/log_config.py. Set the level to DEBUG to see full per-guess AI candidate counts, engine filtering steps, hint computation, and animation lifecycle events. Set it to INFO to see only the compact game-summary block that is logged at the end of every game (winner, attempts, efficiency, trajectory lists).

Autoplay mode

The autoplay flag in src/config_game.py (default off) runs the entire game without any human interaction, useful for quickly reaching the end-of-game stats window or stress-testing an AI engine:

  • When enabled, the game starts immediately from the start screen without waiting for any button click.
  • The human's round is played automatically: the AI hint engine computes the best guess and submits it on the human's behalf.
  • Between rounds, instead of a blocking dialog requiring a click, a brief auto-dismissing splash screen appears for a configurable duration and then the next round begins.
  • All statistics are collected normally, so the stats window at game end is fully populated.

Set it back to off for normal interactive play. The hint and autoplay human turn use a separate engine instance from the AI's own guessing round, so they do not interfere with each other.


Changelog

1.0.0 - 2026-10

  • First release. Full two-round Mastermind: human vs. AI.
  • Start screen with configurable colors, pegs, attempts, repetition, first-guesser, and code-mode.
  • Drag-and-drop and double-click color placement on the keyboard panel.
  • Get AI Hint button: engine computes best guess from current history, animates it into the guess strip peg by peg; human accepts or discards.
  • Animated AI guessing: peg-by-peg blink placement, inter-guess pause, configurable timing throughout.
  • End-of-round secret reveal animation in the mystery banner.
  • Save / Load game (JSON); Load Game button on start screen and in Game menu.
  • End-of-game statistics window: winner, attempts, efficiency, game duration, AI compute time, trajectory chart (requires matplotlib).
  • Trajectory chart accounts for both black and white pegs: score = blacks + whites / (num_pegs + 1), so white-peg contributions are visible as a fractional advantage when blacks are equal.
  • 10 visual skins plus random rotation; skin plugin system for adding new themes.
  • AI engine plugin system for replacing or adding solver strategies (currently available: Knuth minimax).
  • Autoplay mode for unattended end-to-end runs.
  • Strict 7-bit ASCII policy throughout all source files.

About

A Python desktop application to play Mastermind, the classic code-breaking game, featuring an intuitive graphical interface

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages