Random Combination Quiz App – A Fun Desktop Learning Tool Built in Python

 

Random Combination Quiz App – A Fun Desktop Learning Tool Built in Python

Learning becomes more enjoyable when interactivity and surprise are added to the process. That is exactly what the Random Combination Quiz App, built using Python and Tkinter, is designed to do. This lightweight desktop application helps users test their memory, sharpen their focus, and improve recall skills through quick randomized quizzes. Whether you are a student, teacher, or someone who simply enjoys brain games, this app provides a simple yet effective way to boost mental agility.


What Is the Random Combination Quiz App?

The Random Combination Quiz App is a Python-based desktop program that allows users to create quizzes from any list of items. The app shows a random combination of items for a few seconds, hides them, and then asks you to select the correct items from a full list. This makes it a perfect tool for:

  • Memory training

  • Study revision

  • Classroom activities

  • Brain exercises

  • Quick entertainment

Since the app is fully customizable, you can use it for any subject or theme—fruits, countries, vocabulary words, programming terms, or even study notes.


Key Features

1. Simple Input System

Users can type or paste any set of items into the “Items” box. Just separate items with commas, and the app will automatically clean and organize the list.

2. Choose Your Quiz Settings

The app allows you to select:

  • Combination Size (k): How many items will appear at once

  • Number of Questions: Total questions in your quiz

  • Reveal Time: How long the combination stays visible

These options make the quiz fully flexible and user-controlled.

3. Randomized Questions

The app uses Python’s random module to pick different combinations each time. This keeps every quiz session fresh and unpredictable.

4. Memory Test with Checkboxes

Once the shown items are hidden, the user sees a full list of checkboxes. Each question challenges you to recall exactly which items were displayed.

5. Scoring System

The app gives:

  • 1 point for every perfectly correct answer

  • A summary of missing or extra selections when answers are wrong

  • A final score at the end of the quiz

This encourages continuous improvement.

6. Clean and User-Friendly Interface

Built using Tkinter, the app has:

  • Organized frames

  • Clear labels

  • Easy-to-use buttons

  • A responsive layout

It is beginner-friendly and works smoothly on any Windows desktop.


How to Use the App

  1. Enter your items in the text box (comma-separated).

  2. Choose the combination size (k).

  3. Choose the number of questions.

  4. Select the reveal time.

  5. Click Start Quiz.

  6. Memorize the items displayed.

  7. Select the correct ones using checkboxes.

  8. View your score at the end.

That’s it—simple, interactive, and fun!


Who Can Use This App?

This app is useful for:

🎓 Students

Memorize formulas, vocabulary, or concepts through quick repetition.

👨‍🏫 Teachers

Create classroom quizzes or memory games for students.

🧠 Brain Training Enthusiasts

Boost your recall speed and short-term memory with daily practice.

👥 General Users

Have fun with friends or challenge yourself during free time.


Why This App Is Worth Trying

The Random Combination Quiz App stands out because of its simplicity and flexibility. You can turn any topic into an engaging quiz without writing any code or using complicated tools. Since the app is written entirely in Python, it is also a great example for learners who want to understand GUI programming with Tkinter.

If you love brain games, want to study smarter, or simply enjoy building Python projects, this app is a perfect choice.


Final Thoughts

The Random Combination Quiz App is a small but powerful tool that blends learning with entertainment. With customizable settings, a clean interface, and instant feedback, it can help sharpen your memory and make learning more interactive. You can expand it further by adding timers, leaderboard features, or exporting results—Python gives you limitless possibilities.

"""

Random Combination Quiz App

Single-file tkinter desktop application.


Run: save this file as random_combination_quiz.py and run `python random_combination_quiz.py`.

"""

import tkinter as tk

from tkinter import ttk, messagebox

import random


class RandomCombinationQuizApp(tk.Tk):

    def __init__(self):

        super().__init__()

        self.title("Random Combination Quiz")

        # a reasonable default size; window is resizable

        self.geometry("900x600")

        self.minsize(700, 480)

        self.resizable(True, True)


        # State

        self.items = []

        self.k = 2

        self.num_questions = 5

        self.current_question = 0

        self.score = 0

        self.questions = []  # list of sets

        self.reveal_time_ms = 2500  # milliseconds to reveal the combination


        self._build_ui()


    def _build_ui(self):

        # Use grid on root so resizing works nicely

        self.columnconfigure(0, weight=1)

        self.rowconfigure(1, weight=1)  # make question row expand


        # Top frame - configuration

        cfg_frame = ttk.LabelFrame(self, text="Configuration", padding=8)

        cfg_frame.grid(row=0, column=0, sticky="nsew", padx=8, pady=6)

        # let the cfg_frame expand horizontally but not take too much vertical space

        cfg_frame.columnconfigure(0, weight=1)

        cfg_frame.columnconfigure(1, weight=0)

        cfg_frame.columnconfigure(2, weight=0)

        cfg_frame.columnconfigure(3, weight=0)

        cfg_frame.rowconfigure(1, weight=1)


        ttk.Label(cfg_frame, text="Items (comma separated):").grid(row=0, column=0, columnspan=4, sticky="w")

        self.items_text = tk.Text(cfg_frame, height=4, wrap="word")

        self.items_text.grid(row=1, column=0, columnspan=4, sticky="nsew", pady=6)


        ttk.Label(cfg_frame, text="Combination size (k):").grid(row=2, column=0, sticky="w", pady=(4,0))

        self.k_spin = ttk.Spinbox(cfg_frame, from_=1, to=100, width=6, command=self._on_k_change)

        self.k_spin.set(self.k)

        self.k_spin.grid(row=2, column=1, sticky="w", pady=(4,0), padx=(6,0))


        ttk.Label(cfg_frame, text="Number of questions:").grid(row=2, column=2, sticky="w", pady=(4,0))

        self.num_q_spin = ttk.Spinbox(cfg_frame, from_=1, to=2000, width=6)

        self.num_q_spin.set(self.num_questions)

        self.num_q_spin.grid(row=2, column=3, sticky="w", pady=(4,0), padx=(6,0))


        ttk.Label(cfg_frame, text="Reveal time (seconds):").grid(row=3, column=0, sticky="w", pady=(6,0))

        self.reveal_spin = ttk.Spinbox(cfg_frame, from_=1, to=60, width=6)

        self.reveal_spin.set(int(self.reveal_time_ms/1000))

        self.reveal_spin.grid(row=3, column=1, sticky="w", pady=(6,0), padx=(6,0))


        start_btn = ttk.Button(cfg_frame, text="Start Quiz", command=self.start_quiz)

        start_btn.grid(row=3, column=3, sticky="e", padx=(0,6), pady=(6,0))


        # Middle frame - question display (this expands)

        q_frame = ttk.LabelFrame(self, text="Question", padding=8)

        q_frame.grid(row=1, column=0, sticky="nsew", padx=8, pady=6)

        q_frame.columnconfigure(0, weight=1)

        q_frame.rowconfigure(0, weight=1)


        self.question_label = ttk.Label(q_frame, text="Press 'Start Quiz' to begin.",

                                        font=(None, 14), wraplength=760, justify="center")

        self.question_label.grid(row=0, column=0, sticky="nsew", padx=6, pady=6)


        # update wraplength when question frame resizes

        def update_wrap(event):

            # subtract some padding so text doesn't touch edges

            new_wrap = max(200, event.width - 40)

            self.question_label.configure(wraplength=new_wrap)

        q_frame.bind("<Configure>", update_wrap)


        # Bottom frame - answer area and controls

        ans_frame = ttk.LabelFrame(self, text="Answer / Controls", padding=8)

        ans_frame.grid(row=2, column=0, sticky="nsew", padx=8, pady=6)

        ans_frame.columnconfigure(0, weight=1)

        ans_frame.columnconfigure(1, weight=0)


        # Scrollable checkbox area on the left

        checkbox_outer = ttk.Frame(ans_frame)

        checkbox_outer.grid(row=0, column=0, sticky="nsew")

        checkbox_outer.rowconfigure(0, weight=1)

        checkbox_outer.columnconfigure(0, weight=1)


        # Canvas + scrollbar

        self.checkbox_canvas = tk.Canvas(checkbox_outer, highlightthickness=0)

        self.checkbox_canvas.grid(row=0, column=0, sticky="nsew")

        vsb = ttk.Scrollbar(checkbox_outer, orient="vertical", command=self.checkbox_canvas.yview)

        vsb.grid(row=0, column=1, sticky="ns")

        self.checkbox_canvas.configure(yscrollcommand=vsb.set)


        # Inner frame that will hold checkbuttons

        self.checkbox_container = ttk.Frame(self.checkbox_canvas)

        self.checkbox_window = self.checkbox_canvas.create_window((0,0), window=self.checkbox_container, anchor="nw")


        def _on_checkbox_config(event):

            # update canvas scrollregion

            self.checkbox_canvas.configure(scrollregion=self.checkbox_canvas.bbox("all"))

        self.checkbox_container.bind("<Configure>", _on_checkbox_config)


        def _on_canvas_config(event):

            # keep the inner frame width in sync with canvas width

            canvas_width = event.width

            try:

                self.checkbox_canvas.itemconfigure(self.checkbox_window, width=canvas_width)

            except Exception:

                pass

        self.checkbox_canvas.bind("<Configure>", _on_canvas_config)


        # Controls on the right

        control_frame = ttk.Frame(ans_frame)

        control_frame.grid(row=0, column=1, sticky="ne", padx=(8,0))


        self.submit_btn = ttk.Button(control_frame, text="Submit Answer", command=self.submit_answer, state="disabled")

        self.submit_btn.pack(padx=4, pady=4, fill="x")


        self.next_btn = ttk.Button(control_frame, text="Next", command=self.next_question, state="disabled")

        self.next_btn.pack(padx=4, pady=4, fill="x")


        self.score_label = ttk.Label(control_frame, text=f"Score: {self.score}/{self.num_questions}")

        self.score_label.pack(padx=4, pady=8)


        reset_btn = ttk.Button(control_frame, text="Reset", command=self.reset_quiz)

        reset_btn.pack(padx=4, pady=4, fill="x")


        # some padding for a nicer look

        for child in cfg_frame.winfo_children():

            try:

                child.grid_configure(padx=6, pady=3)

            except Exception:

                pass


    def _on_k_change(self):

        try:

            self.k = int(self.k_spin.get())

        except Exception:

            self.k = 2


    def parse_items(self):

        raw = self.items_text.get("1.0", "end").strip()

        if not raw:

            return []

        # split by comma or newline, strip whitespace, remove empty

        parts = [p.strip() for p in raw.replace('\n', ',').split(',')]

        parts = [p for p in parts if p]

        # remove duplicates while preserving order

        seen = set()

        items = []

        for p in parts:

            if p not in seen:

                seen.add(p)

                items.append(p)

        return items


    def start_quiz(self):

        self.items = self.parse_items()

        if len(self.items) < 1:

            messagebox.showwarning("No items", "Please enter at least one item to quiz on.")

            return

        try:

            self.k = int(self.k_spin.get())

            self.num_questions = int(self.num_q_spin.get())

            self.reveal_time_ms = int(self.reveal_spin.get()) * 1000

        except Exception:

            messagebox.showerror("Invalid input", "Please make sure numeric fields are valid integers.")

            return


        if self.k < 1 or self.k > len(self.items):

            messagebox.showwarning("Invalid combination size", f"Combination size k must be between 1 and {len(self.items)}.")

            return


        # Generate questions as random combinations (allow repeated questions if combinations fewer than num_questions)

        from itertools import combinations

        all_combs = list(combinations(self.items, self.k))

        if not all_combs:

            messagebox.showerror("Error", "Unable to create any combinations with the given items and k.")

            return


        # Shuffle and pick

        random.shuffle(all_combs)

        if len(all_combs) >= self.num_questions:

            chosen = all_combs[:self.num_questions]

        else:

            # allow repeats if not enough unique combinations

            chosen = [random.choice(all_combs) for _ in range(self.num_questions)]


        # Convert to set for easier checking

        self.questions = [set(c) for c in chosen]


        # Reset counters

        self.current_question = 0

        self.score = 0

        self.update_score_label()


        # Disable config controls while quiz is running

        self.submit_btn.config(state="disabled")

        self.next_btn.config(state="disabled")


        self._show_current_question()


    def _show_current_question(self):

        qset = self.questions[self.current_question]

        # Display the combination as a bullet list

        display_text = "\n".join(f"• {item}" for item in qset)

        header = f"Question {self.current_question+1} of {self.num_questions}\n\n"

        self.question_label.config(text=header + display_text)


        # After reveal_time_ms hide the text and show the checkboxes

        self.after(self.reveal_time_ms, self._hide_and_show_checkboxes)


        # Ensure checkboxes area is cleared

        for child in self.checkbox_container.winfo_children():

            child.destroy()


        # disable submit/next until reveal period is over

        self.submit_btn.config(state="disabled")

        self.next_btn.config(state="disabled")


        # Reset scroll to top

        try:

            self.checkbox_canvas.yview_moveto(0)

        except Exception:

            pass


    def _hide_and_show_checkboxes(self):

        # Hide question text

        self.question_label.config(text="Select the items that were shown in the combination.")


        # Create checkboxes for all items inside the inner frame

        self.checkbox_vars = {}

        for child in self.checkbox_container.winfo_children():

            child.destroy()


        # We'll arrange checkboxes in a single column for clarity (scrollable)

        for i, item in enumerate(self.items):

            var = tk.BooleanVar(value=False)

            cb = ttk.Checkbutton(self.checkbox_container, text=item, variable=var)

            cb.grid(row=i, column=0, sticky="w", padx=6, pady=2)

            self.checkbox_vars[item] = var


        # Ensure scrollbar region updated

        self.checkbox_canvas.configure(scrollregion=self.checkbox_canvas.bbox("all"))


        # Enable submit button

        self.submit_btn.config(state="normal")

        # Next remains disabled until after submission

        self.next_btn.config(state="disabled")


    def submit_answer(self):

        # Collect selected items

        selected = {item for item, var in self.checkbox_vars.items() if var.get()}

        correct = self.questions[self.current_question]


        # Compute score for this question: full points only if exact match

        if selected == correct:

            self.score += 1

            message = "Correct!"

        else:

            # give feedback: which were missing and which were extra

            missing = correct - selected

            extra = selected - correct

            parts = []

            if missing:

                parts.append("Missing: " + ", ".join(sorted(missing)))

            if extra:

                parts.append("Incorrect selections: " + ", ".join(sorted(extra)))

            message = "Not quite. " + "; ".join(parts)


        messagebox.showinfo("Result", message)

        self.update_score_label()


        # Disable submit, enable next (or finish)

        self.submit_btn.config(state="disabled")

        if self.current_question + 1 < self.num_questions:

            self.next_btn.config(state="normal")

        else:

            # Quiz finished

            self._finish_quiz()


    def next_question(self):

        self.current_question += 1

        # Clear checkboxes

        for child in self.checkbox_container.winfo_children():

            child.destroy()

        self._show_current_question()


    def update_score_label(self):

        self.score_label.config(text=f"Score: {self.score}/{self.num_questions}")


    def _finish_quiz(self):

        # Clear checkboxes

        for child in self.checkbox_container.winfo_children():

            child.destroy()


        self.question_label.config(text=f"Quiz finished. Your score: {self.score}/{self.num_questions}")

        self.next_btn.config(state="disabled")

        self.submit_btn.config(state="disabled")


    def reset_quiz(self):

        self.items_text.delete("1.0", "end")

        self.k_spin.set(2)

        self.num_q_spin.set(5)

        self.reveal_spin.set(3)

        self.question_label.config(text="Press 'Start Quiz' to begin.")

        for child in self.checkbox_container.winfo_children():

            child.destroy()

        self.score = 0

        self.num_questions = 5

        self.update_score_label()



if __name__ == "__main__":

    app = RandomCombinationQuizApp()

    app.mainloop()

https://github.com/gagandeep44489/DiscreteStrucutreAndAlgoApp/blob/main/Random%20Combination%20Quiz%20App.py

Comments

Popular posts from this blog

NAND / NOR Logic Simulator: A Python Desktop App for Understanding Universal Logic Gates

Subset Sum Problem Visualizer Using Python (Dynamic Programming GUI Tool)

String Matching Algorithm Trainer (KMP & Rabin-Karp) – Python Desktop App