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

Top comments (0)