In this tutorial, we’ll build NumMaze — a desktop Python app that generates arithmetic puzzles with automatic solutions and exports them as PDFs or JPG images.
It’s designed to be beginner-friendly and useful for:
Learning Python GUI basics
Practicing recursion and logic
Creating printable math worksheets
We’ll use:
tkinter – GUI framework
ttkbootstrap – modern themes
reportlab – PDF export
Pillow – JPG image export
By the end, you’ll have a complete puzzle generator.
✅ Step 1 — Install Dependencies
First, install the required libraries:
pip install ttkbootstrap reportlab pillow
tkinter comes bundled with most Python installs.
✅ Step 2 — Import Modules
Create a file called nummaze.py and start with imports:
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
What these do
tkinter → GUI
random + operator → puzzle logic
ttkbootstrap → modern styling
reportlab → PDF generation
Pillow → JPG images
✅ Step 3 — Create the App Class
We wrap everything inside a class:
class NumMaze:
APP_NAME = "NumMaze"
APP_VERSION = "2.2.0"
Define math operators:
OPERATORS = {
"+": operator.add,
"-": operator.sub,
"*": operator.mul,
"/": operator.floordiv
}
This lets us dynamically apply math later.
✅ Step 4 — Initialize the Window
Inside init:
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("1250x700")
We also define app state:
self.difficulty_var = tk.StringVar(value="Easy")
self.num_puzzles_var = tk.IntVar(value=1)
self.grid_numbers = []
self.target_number = None
self.solution_steps = []
self.rows = self.cols = 0
Finally:
self._build_ui()
✅ Step 5 — Build the User Interface
Create _build_ui():
def _build_ui(self):
tb.Label(self.root, text=self.APP_NAME,
font=("Segoe UI", 22, "bold")).pack(pady=10)
Subtitle:
tb.Label(
self.root,
text="Auto-Generated Arithmetic Puzzle",
font=("Segoe UI", 10, "italic")
).pack()
Options Panel
opts = tb.Labelframe(self.root, text="Options", padding=10)
opts.pack(fill="x", padx=10)
tb.Label(opts, text="Difficulty:").pack(side="left")
tb.Combobox(
opts,
values=["Easy","Medium","Hard"],
textvariable=self.difficulty_var,
width=10
).pack(side="left", padx=5)
Puzzle count:
tb.Label(opts, text="Number of Puzzles:").pack(side="left", padx=10)
tb.Spinbox(opts, from_=1, to=20,
textvariable=self.num_puzzles_var,
width=5).pack(side="left")
✅ Step 6 — Control Buttons
ctrl = tb.Frame(self.root)
ctrl.pack(fill="x", padx=10, pady=10)
tb.Button(ctrl, text="Generate Puzzle",
bootstyle="success",
command=self.generate_single_puzzle).pack(side="left")
You can add export buttons the same way:
tb.Button(ctrl, text="Combined PDF",
bootstyle="warning",
command=self.generate_multiple_combined_pdf).pack(side="left", padx=5)
Each button simply calls a method.
✅ Step 7 — Grid + Solution Panels
Puzzle grid:
self.grid_frame = tb.Labelframe(self.root, text="Puzzle Grid", padding=10)
self.grid_frame.pack(fill="x", padx=10)
Solution area:
sol = tb.Labelframe(self.root, text="Solution", padding=10)
sol.pack(fill="both", expand=True, padx=10)
self.solution_text = tk.Text(sol, height=10, font=("Consolas", 12))
self.solution_text.pack(fill="both", expand=True)
✅ Step 8 — Generate Puzzle Data
Difficulty controls how many numbers:
def create_puzzle_data(self):
d = self.difficulty_var.get()
if d == "Easy":
n, rows, cols = 4, 2, 2
elif d == "Medium":
n, rows, cols = 6, 2, 3
else:
n, rows, cols = 9, 3, 3
Generate random numbers:
numbers = [random.randint(1,15) for _ in range(n)]
Solve them:
expr, target, steps = self.recursive_solution(numbers)
return numbers, target, steps, rows, cols
✅ Step 9 — Recursive Solver
This repeatedly tries random combinations:
def recursive_solution(self, numbers):
for _ in range(5000):
nums = numbers[:]
random.shuffle(nums)
expr, val, steps = self.build_expr(nums)
if val is not None and val > 0:
return expr, val, steps
return str(numbers[0]), numbers[0], []
Build Expressions
def build_expr(self, nums):
if len(nums) == 1:
return str(nums[0]), nums[0], []
Split list and recurse:
for i in range(1, len(nums)):
left = nums[:i]
right = nums[i:]
Apply operators:
for op in self.OPERATORS:
try:
val = self.OPERATORS[op](left_val, right_val)
steps = left_steps + right_steps + [
f"{left_val} {op} {right_val} = {val}"
]
return expr, val, steps
except:
continue
This is the core logic engine.
✅ Step 10 — Display Grid
def display_grid(self):
for w in self.grid_frame.winfo_children():
w.destroy()
Create labels for each number:
for r in range(self.rows):
for c in range(self.cols):
tb.Label(self.grid_frame,
text=str(self.grid_numbers[idx]),
font=("Segoe UI",20,"bold")).grid(row=r,column=c)
Target:
tb.Label(self.grid_frame,
text=f"Target: {self.target_number}",
font=("Segoe UI",16,"bold")).grid(row=self.rows,columnspan=self.cols)
✅ Step 11 — Show Solution
def show_solution(self):
self.solution_text.delete("1.0", tk.END)
self.solution_text.insert(tk.END, "\n".join(self.solution_steps))
✅ Step 12 — Export PDFs and JPGs
Using:
reportlab → PDFs
Pillow → images
Each export function:
Generates puzzles
Draws grids
Writes solution text
Saves files
(See full repo for complete export implementations.)
✅ Step 13 — Run the App
At the bottom:
if __name__ == "__main__":
NumMaze().run()
🎉 Done!
You now have:
A themed Python GUI
Recursive arithmetic solver
Puzzle grids
Step-by-step solutions
PDF + JPG exports
🔗 Source Code
GitHub:
SOCIAL SHARE CARD GENERATOR