DEV Community

Mate Technologies
Mate Technologies

Posted on

🧩 Building MazeMath: A Python App That Turns Arithmetic Into Maze Puzzles

In this tutorial, we’ll build MazeMath — a desktop app that generates arithmetic maze puzzles, shows step-by-step solutions, and exports worksheets as PDF or JPG.

We’ll use:

Python

Tkinter + ttkbootstrap (GUI)

ReportLab (PDF export)

Pillow (JPG export)

By the end, you’ll have a working educational puzzle generator.

👉 Final project: https://github.com/rogers-cyber/MazeMath

✅ What MazeMath Does

Generates math mazes (Easy / Medium / Hard)

Guarantees a solvable path

Shows step-by-step arithmetic

Highlights the solution visually

Exports puzzles as PDFs or JPGs

Supports multiple puzzles at once

Great for classrooms, tutoring, or learning projects.

  1. Installing Dependencies

First, install the required libraries:

pip install ttkbootstrap reportlab pillow

Tkinter usually comes bundled with Python.

  1. Importing Libraries

Start by importing everything we’ll need:

import tkinter as tk
from tkinter import messagebox, filedialog
import random
import operator
import ttkbootstrap as tb
from ttkbootstrap.constants import *

from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4

from PIL import Image, ImageDraw, ImageFont
from pathlib import Path
Enter fullscreen mode Exit fullscreen mode

Why these?

tkinter → base GUI

ttkbootstrap → modern dark theme

random + operator → puzzle math

reportlab → PDF generation

Pillow → JPG images

  1. Creating the Main App Class

We wrap everything in a class called MazeMath.

class MazeMath:
    APP_NAME = "MazeMath"
    APP_VERSION = "2.1"

    OPERATORS = {
        "+": operator.add,
        "-": operator.sub,
        "*": operator.mul,
        "/": operator.floordiv
    }
Enter fullscreen mode Exit fullscreen mode

What’s happening?

APP_NAME and APP_VERSION are just labels

OPERATORS maps math symbols to Python functions

  1. Initializing the Window

Inside init, we set up the main window and variables:

def __init__(self):
    self.root = tk.Tk()
    tb.Style(theme="darkly")

    self.root.title(f"{self.APP_NAME} v{self.APP_VERSION}")
    self.root.geometry("1100x680")

    self.difficulty_var = tk.StringVar(value="Easy")
    self.num_puzzles_var = tk.IntVar(value=1)

    self.grid_numbers = []
    self.grid_ops = []
    self.solution_path = []
    self.target_number = None

    self.rows = self.cols = 0

    self._build_ui()
Enter fullscreen mode Exit fullscreen mode

We store:

Difficulty

Number of puzzles

Grid values

Solution path

Target result

  1. Building the User Interface

Now we create buttons, dropdowns, and panels:

def _build_ui(self):
    tb.Label(self.root, text=self.APP_NAME,
             font=("Segoe UI", 22, "bold")).pack(pady=10)

    opts = tb.Labelframe(self.root, text="Options", padding=10)
    opts.pack(fill="x", padx=10)

    tb.Combobox(opts,
        values=["Easy","Medium","Hard"],
        textvariable=self.difficulty_var,
        width=10).pack(side="left", padx=5)

    tb.Spinbox(opts, from_=1, to=20,
        textvariable=self.num_puzzles_var,
        width=5).pack(side="left", padx=5)
Enter fullscreen mode Exit fullscreen mode

Then we add control buttons:

tb.Button(ctrl, text="Generate",
          command=self.generate_single_puzzle).pack(side="left")

tb.Button(ctrl, text="PDF",
          command=self.generate_multiple_combined_pdf).pack(side="left")

tb.Button(ctrl, text="JPG",
          command=self.generate_multiple_jpgs).pack(side="left")
Enter fullscreen mode Exit fullscreen mode

And finally:

A grid area for the puzzle

A text box for the solution steps

  1. Generating a Maze Path

We use Depth-First Search to ensure every puzzle is solvable.

def generate_maze(self, rows, cols):
    visited = [[False]*cols for _ in range(rows)]
    path = []

    def dfs(r, c):
        visited[r][c] = True
        path.append((r,c))

        dirs = [(0,1),(1,0),(0,-1),(-1,0)]
        random.shuffle(dirs)

        for dr, dc in dirs:
            nr, nc = r+dr, c+dc
            if 0 <= nr < rows and 0 <= nc < cols and not visited[nr][nc]:
                dfs(nr, nc)

    dfs(0,0)
    return path
Enter fullscreen mode Exit fullscreen mode

This gives us a guaranteed path from start to finish.

  1. Creating Puzzle Data

Now we place numbers and operators along that path:

def create_puzzle_data(self):
    diff = self.difficulty_var.get()

    if diff == "Easy":
        self.rows = self.cols = 3
    elif diff == "Medium":
        self.rows = self.cols = 4
    else:
        self.rows = self.cols = 5
Enter fullscreen mode Exit fullscreen mode

Then:

path = self.generate_maze(self.rows, self.cols)

current = random.randint(1,9)
steps = [f"Start: {current}"]
Enter fullscreen mode Exit fullscreen mode

For every next cell, we safely apply math:

for r,c in path[1:]:
    op = random.choice(list(self.OPERATORS.keys()))
    num = random.randint(1,9)

    if op == "/" and current % num != 0:
        continue

    next_val = self.OPERATORS[op](current, num)
    steps.append(f"{current} {op} {num} = {next_val}")
    current = next_val
Enter fullscreen mode Exit fullscreen mode

This guarantees:

No negative results

No broken division

Always solvable

  1. Displaying the Grid

We draw labels for each cell:

for r in range(self.rows):
    for c in range(self.cols):
        text = f"{op}{num}"
        bg = "#4caf50" if (r,c) in self.solution_path else "#222"

        tb.Label(self.grid_frame,
            text=text,
            background=bg).grid(row=r,column=c)
Enter fullscreen mode Exit fullscreen mode

Green cells show the solution path.

  1. Showing Step-by-Step Solutions
def show_solution(self):
    self.solution_text.delete("1.0", tk.END)
    self.solution_text.insert(tk.END, "\n".join(steps))
Enter fullscreen mode Exit fullscreen mode

This prints:

Start: 5
5 + 3 = 8
8 × 2 = 16
...

  1. Exporting to PDF

Using ReportLab:

c = canvas.Canvas("puzzle.pdf", pagesize=A4)
c.drawString(50, 800, "MazeMath Puzzle")
Enter fullscreen mode Exit fullscreen mode

Then we draw:

Grid

Target

Solution steps

Each puzzle becomes a printable worksheet.

  1. Exporting to JPG

With Pillow:

img = Image.new("RGB", (900, 800), (34,34,34))
draw = ImageDraw.Draw(img)
Enter fullscreen mode Exit fullscreen mode

We draw:

Title

Maze grid

Highlighted path

Target

Solution text

Finally:

img.save("Puzzle_1.jpg")

  1. Running the App

Finish with:

if __name__ == "__main__":
    MazeMath().run()
Enter fullscreen mode Exit fullscreen mode

Run it:

python mazemath.py

🎉 Done!

🚀 Final Thoughts

MazeMath combines:

Algorithms (DFS)

GUI design

Math logic

File export

It’s a great example of how Python can be used for real educational tools, not just scripts.

If you enjoyed this project:

⭐ Star the repo
🐛 Open issues
🤝 Submit PRs

GitHub: https://github.com/rogers-cyber/MazeMath

Top comments (0)