I'll create a comprehensive quiz app called "Quarix" with all the features you
requested. This will be a multi-file application with an awesome GUI
interface.
# main.py
import customtkinter as ctk
import tkinter as tk
from tkinter import messagebox
import pygame
import json
import os
import threading
import time
import random
from datetime import datetime, timedelta
import requests
import google.generativeai as genai
from PIL import Image, ImageTk
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import sqlite3
from dataclasses import dataclass
from typing import List, Dict, Optional
import webbrowser
# Initialize pygame for sounds
pygame.mixer.init()
# Configure Gemini AI (You'll need to add your API key)
genai.configure(api_key="YOUR_GEMINI_API_KEY_HERE")
@dataclass
class Question:
question: str
options: List[str]
correct_answer: int
explanation: str
class DatabaseManager:
def __init__(self):
self.db_path = "quarix_data.db"
self.init_database()
def init_database(self):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# Users table
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE,
email TEXT,
password TEXT,
coins INTEGER DEFAULT 0,
gems INTEGER DEFAULT 0,
streak_freezes INTEGER DEFAULT 0,
current_streak INTEGER DEFAULT 0,
last_activity DATE,
total_questions INTEGER DEFAULT 0,
correct_answers INTEGER DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
# Quiz history table
cursor.execute('''
CREATE TABLE IF NOT EXISTS quiz_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER,
class_level TEXT,
subject TEXT,
topic TEXT,
score REAL,
total_questions INTEGER,
correct_answers INTEGER,
date_taken TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users (id)
)
''')
# Daily activity table
cursor.execute('''
CREATE TABLE IF NOT EXISTS daily_activity (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER,
date DATE,
questions_answered INTEGER DEFAULT 0,
correct_answers INTEGER DEFAULT 0,
FOREIGN KEY (user_id) REFERENCES users (id)
)
''')
conn.commit()
conn.close()
class SoundManager:
def __init__(self):
self.sounds = {}
self.load_sounds()
def load_sounds(self):
# Create default sounds if files don't exist
try:
# You can replace these with actual sound files
self.sounds['correct'] =
pygame.mixer.Sound("sounds/correct.wav") if
os.path.exists("sounds/correct.wav") else None
self.sounds['wrong'] =
pygame.mixer.Sound("sounds/wrong.wav") if
os.path.exists("sounds/wrong.wav") else None
self.sounds['celebration'] =
pygame.mixer.Sound("sounds/celebration.wav") if
os.path.exists("sounds/celebration.wav") else None
except:
self.sounds = {'correct': None, 'wrong': None,
'celebration': None}
def play_sound(self, sound_type):
if self.sounds.get(sound_type):
self.sounds[sound_type].play()
class GeminiQuestionGenerator:
def __init__(self):
self.model = genai.GenerativeModel('gemini-pro')
def generate_questions(self, class_level, subject, topic,
num_questions):
try:
if topic.lower() == "random":
prompt = f"""
Generate {num_questions} multiple choice questions for
{class_level} level {subject}.
Cover various topics in {subject} appropriate for
{class_level}.
Format each question as:
Question: [question text]
A) [option 1]
B) [option 2]
C) [option 3]
D) [option 4]
Correct Answer: [A/B/C/D]
Explanation: [brief explanation]
---
"""
else:
prompt = f"""
Generate {num_questions} multiple choice questions
about {topic} in {subject} for {class_level} level.
Format each question as:
Question: [question text]
A) [option 1]
B) [option 2]
C) [option 3]
D) [option 4]
Correct Answer: [A/B/C/D]
Explanation: [brief explanation]
---
"""
response = self.model.generate_content(prompt)
return self.parse_questions(response.text)
except Exception as e:
print(f"Error generating questions: {e}")
return self.get_fallback_questions(num_questions)
def parse_questions(self, response_text):
questions = []
question_blocks = response_text.split('---')
for block in question_blocks:
if not block.strip():
continue
lines = [line.strip() for line in block.strip().split('\
n') if line.strip()]
if len(lines) >= 7:
question_text = lines[0].replace('Question:',
'').strip()
options = []
correct_answer = 0
explanation = ""
for i, line in enumerate(lines[1:5]):
option = line[3:].strip() if len(line) > 3 else
line
options.append(option)
for line in lines[5:]:
if 'Correct Answer:' in line:
answer_letter = line.split(':')
[1].strip().upper()
correct_answer = ord(answer_letter) - ord('A')
elif 'Explanation:' in line:
explanation = line.replace('Explanation:',
'').strip()
if question_text and len(options) == 4:
questions.append(Question(question_text, options,
correct_answer, explanation))
return questions if questions else
self.get_fallback_questions(3)
def get_fallback_questions(self, num_questions):
fallback = [
Question(
"What is 2 + 2?",
["3", "4", "5", "6"],
1,
"2 + 2 = 4 is basic addition."
),
Question(
"What is the capital of Nigeria?",
["Lagos", "Abuja", "Kano", "Port Harcourt"],
1,
"Abuja is the capital city of Nigeria."
),
Question(
"Which planet is closest to the Sun?",
["Venus", "Mercury", "Earth", "Mars"],
1,
"Mercury is the closest planet to the Sun."
)
]
return fallback[:num_questions]
class SplashScreen:
def __init__(self, parent, callback):
self.parent = parent
self.callback = callback
self.create_splash()
def create_splash(self):
# Create splash window
self.splash = ctk.CTkToplevel(self.parent)
self.splash.title("")
self.splash.geometry("800x600")
self.splash.configure(fg_color=("#1a1a2e", "#16213e"))
# Center the window
self.splash.transient(self.parent)
self.splash.grab_set()
# Create canvas for animation
self.canvas = tk.Canvas(
self.splash,
width=800,
height=600,
bg="#1a1a2e",
highlightthickness=0
)
self.canvas.pack(fill="both", expand=True)
# Start animation
self.animate_text()
def animate_text(self):
text = "Quarix Quiz"
x_start = 200
y_pos = 250
def write_letter(index):
if index < len(text):
letter = text[index]
x_pos = x_start + (index * 30)
self.canvas.create_text(
x_pos, y_pos,
text=letter,
font=("Arial", 36, "bold"),
fill="#00d4ff",
tags="title"
)
self.splash.after(200, lambda: write_letter(index +
1))
else:
self.splash.after(1000, self.show_logo)
write_letter(0)
def show_logo(self):
# Create a placeholder logo (you can replace with your actual
logo)
logo_frame = ctk.CTkFrame(
self.splash,
width=200,
height=200,
corner_radius=100,
fg_color=("#00d4ff", "#0099cc")
)
logo_frame.place(relx=0.5, rely=0.6, anchor="center")
logo_label = ctk.CTkLabel(
logo_frame,
text="Q",
font=("Arial", 72, "bold"),
text_color="white"
)
logo_label.place(relx=0.5, rely=0.5, anchor="center")
# Animate logo appearance
self.animate_logo_scale(logo_frame, 0.1)
def animate_logo_scale(self, widget, scale):
if scale <= 1.0:
# Simple scaling effect
self.splash.after(50, lambda:
self.animate_logo_scale(widget, scale + 0.1))
else:
self.splash.after(1500, self.finish_splash)
def finish_splash(self):
self.splash.destroy()
self.callback()
class QuarixApp:
def __init__(self):
# Initialize main window
ctk.set_appearance_mode("dark")
ctk.set_default_color_theme("blue")
self.root = ctk.CTk()
self.root.title("Quarix Quiz")
self.root.geometry("1200x800")
self.root.configure(fg_color=("#0f0f23", "#1a1a2e"))
# Initialize managers
self.db_manager = DatabaseManager()
self.sound_manager = SoundManager()
self.question_generator = GeminiQuestionGenerator()
# App state
self.current_user = None
self.current_questions = []
self.current_question_index = 0
self.quiz_score = 0
self.quiz_answers = []
# Hide main window initially
self.root.withdraw()
# Show splash screen
self.splash = SplashScreen(self.root, self.show_main_app)
# Class and subject data
self.class_data = {
"Primary": ["Nursery 1", "Nursery 2", "Primary 1",
"Primary 2", "Primary 3", "Primary 4", "Primary 5", "Primary 6"],
"Secondary": ["JSS 1", "JSS 2", "JSS 3", "SS 1", "SS 2",
"SS 3"],
"University": ["100 Level", "200 Level", "300 Level", "400
Level", "500 Level", "600 Level"]
}
self.subjects = [
"Mathematics", "English Language", "Physics", "Chemistry",
"Biology",
"Geography", "History", "Economics", "Government",
"Literature",
"Agricultural Science", "Computer Science", "Further
Mathematics",
"Technical Drawing", "Fine Arts", "Music", "Physical
Education",
"Home Economics", "Business Studies", "CRS"
]
def show_main_app(self):
self.root.deiconify()
self.create_main_interface()
def create_main_interface(self):
# Create main container
self.main_container = ctk.CTkFrame(self.root,
fg_color="transparent")
self.main_container.pack(fill="both", expand=True, padx=20,
pady=20)
# Create navigation
self.create_navigation()
# Create content area
self.content_frame = ctk.CTkFrame(self.main_container)
self.content_frame.pack(fill="both", expand=True, pady=(20,
0))
# Show home screen by default
self.show_home_screen()
def create_navigation(self):
nav_frame = ctk.CTkFrame(self.main_container, height=80)
nav_frame.pack(fill="x")
nav_frame.pack_propagate(False)
# Navigation buttons
nav_buttons = [
("🏠 Home", self.show_home_screen),
("🎁 Rewards", self.show_rewards_screen),
("🔥 Streak", self.show_streak_screen),
("👤 Profile", self.show_profile_screen),
("🏆 Leaderboard", self.show_leaderboard_screen)
]
for i, (text, command) in enumerate(nav_buttons):
btn = ctk.CTkButton(
nav_frame,
text=text,
command=command,
width=200,
height=50,
font=("Arial", 16, "bold"),
corner_radius=25
)
btn.pack(side="left", padx=10, pady=15)
def clear_content(self):
for widget in self.content_frame.winfo_children():
widget.destroy()
def show_home_screen(self):
self.clear_content()
# Title with streak
title_frame = ctk.CTkFrame(self.content_frame)
title_frame.pack(fill="x", padx=20, pady=20)
title_label = ctk.CTkLabel(
title_frame,
text="Welcome to Quarix Quiz! 🎯",
font=("Arial", 32, "bold"),
text_color=("#00d4ff", "#ffffff")
)
title_label.pack(pady=20)
# Streak display
streak_frame = ctk.CTkFrame(title_frame)
streak_frame.pack(pady=10)
streak_label = ctk.CTkLabel(
streak_frame,
text=f"🔥 Current Streak: {self.get_user_streak()} days",
font=("Arial", 18, "bold"),
text_color=("#ff6b35", "#ff8c42")
)
streak_label.pack(padx=20, pady=10)
# Main action buttons
actions_frame = ctk.CTkFrame(self.content_frame)
actions_frame.pack(fill="both", expand=True, padx=20, pady=20)
# Start Quiz Button
quiz_btn = ctk.CTkButton(
actions_frame,
text="🚀 Start Quiz",
command=self.start_quiz_setup,
width=300,
height=80,
font=("Arial", 24, "bold"),
corner_radius=40,
fg_color=("#00d4ff", "#0099cc"),
hover_color=("#0099cc", "#007399")
)
quiz_btn.pack(pady=30)
# Read Topic Button
topic_btn = ctk.CTkButton(
actions_frame,
text="📚 Read a Topic",
command=self.show_topics,
width=300,
height=80,
font=("Arial", 24, "bold"),
corner_radius=40,
fg_color=("#4ecdc4", "#45b7aa"),
hover_color=("#45b7aa", "#3da58a")
)
topic_btn.pack(pady=20)
# Chat with Quarix Button
chat_btn = ctk.CTkButton(
actions_frame,
text="💬 Chat with Quarix",
command=self.show_chat,
width=300,
height=80,
font=("Arial", 24, "bold"),
corner_radius=40,
fg_color=("#ff6b6b", "#e55555"),
hover_color=("#e55555", "#cc4444")
)
chat_btn.pack(pady=20)
def start_quiz_setup(self):
self.clear_content()
# Quiz setup title
title_label = ctk.CTkLabel(
self.content_frame,
text="📝 Quiz Setup",
font=("Arial", 28, "bold"),
text_color=("#00d4ff", "#ffffff")
)
title_label.pack(pady=30)
# Setup frame
setup_frame = ctk.CTkFrame(self.content_frame)
setup_frame.pack(fill="both", expand=True, padx=50, pady=20)
# Education Level Selection
level_label = ctk.CTkLabel(setup_frame, text="Select Education
Level:", font=("Arial", 18, "bold"))
level_label.pack(pady=(30, 10))
self.level_var = ctk.StringVar(value="Primary")
level_menu = ctk.CTkOptionMenu(
setup_frame,
values=list(self.class_data.keys()),
variable=self.level_var,
command=self.update_class_options,
width=300,
height=40,
font=("Arial", 16)
)
level_menu.pack(pady=10)
# Class Selection
class_label = ctk.CTkLabel(setup_frame, text="Select Class:",
font=("Arial", 18, "bold"))
class_label.pack(pady=(20, 10))
self.class_var =
ctk.StringVar(value=self.class_data["Primary"][0])
self.class_menu = ctk.CTkOptionMenu(
setup_frame,
values=self.class_data["Primary"],
variable=self.class_var,
width=300,
height=40,
font=("Arial", 16)
)
self.class_menu.pack(pady=10)
# Subject Selection
subject_label = ctk.CTkLabel(setup_frame, text="Select
Subject:", font=("Arial", 18, "bold"))
subject_label.pack(pady=(20, 10))
self.subject_var = ctk.StringVar(value=self.subjects[0])
subject_menu = ctk.CTkOptionMenu(
setup_frame,
values=self.subjects,
variable=self.subject_var,
width=300,
height=40,
font=("Arial", 16)
)
subject_menu.pack(pady=10)
# Topic Selection
topic_label = ctk.CTkLabel(setup_frame, text="Specific Topic
(or leave as Random):", font=("Arial", 18, "bold"))
topic_label.pack(pady=(20, 10))
self.topic_var = ctk.StringVar(value="Random")
topic_entry = ctk.CTkEntry(
setup_frame,
textvariable=self.topic_var,
width=300,
height=40,
font=("Arial", 16)
)
topic_entry.pack(pady=10)
# Number of Questions
questions_label = ctk.CTkLabel(setup_frame, text="Number of
Questions:", font=("Arial", 18, "bold"))
questions_label.pack(pady=(20, 10))
self.questions_var = ctk.StringVar(value="10")
questions_menu = ctk.CTkOptionMenu(
setup_frame,
values=["5", "10", "15", "20", "25", "30"],
variable=self.questions_var,
width=300,
height=40,
font=("Arial", 16)
)
questions_menu.pack(pady=10)
# Start Quiz Button
start_btn = ctk.CTkButton(
setup_frame,
text="🎯 Generate Quiz",
command=self.generate_quiz,
width=300,
height=60,
font=("Arial", 20, "bold"),
corner_radius=30,
fg_color=("#00d4ff", "#0099cc")
)
start_btn.pack(pady=40)
# Back Button
back_btn = ctk.CTkButton(
setup_frame,
text="← Back to Home",
command=self.show_home_screen,
width=200,
height=40,
font=("Arial", 16),
fg_color=("#666666", "#555555")
)
back_btn.pack(pady=10)
def update_class_options(self, selected_level):
classes = self.class_data[selected_level]
self.class_menu.configure(values=classes)
self.class_var.set(classes[0])
def generate_quiz(self):
# Show loading screen
self.show_loading_screen()
# Generate questions in a separate thread
def generate_questions_thread():
try:
class_level = self.class_var.get()
subject = self.subject_var.get()
topic = self.topic_var.get()
num_questions = int(self.questions_var.get())
self.current_questions =
self.question_generator.generate_questions(
class_level, subject, topic, num_questions
)
self.current_question_index = 0
self.quiz_score = 0
self.quiz_answers = []
# Switch to quiz screen on main thread
self.root.after(0, self.start_quiz)
except Exception as e:
self.root.after(0, lambda:
messagebox.showerror("Error", f"Failed to generate quiz: {str(e)}"))
self.root.after(0, self.show_home_screen)
threading.Thread(target=generate_questions_thread,
daemon=True).start()
def show_loading_screen(self):
self.clear_content()
loading_frame = ctk.CTkFrame(self.content_frame)
loading_frame.pack(fill="both", expand=True)
loading_label = ctk.CTkLabel(
loading_frame,
text="🤖 Generating your quiz...",
font=("Arial", 24, "bold")
)
loading_label.pack(expand=True)
# Add a progress bar
self.progress_bar = ctk.CTkProgressBar(loading_frame,
width=400)
self.progress_bar.pack(pady=20)
self.progress_bar.set(0)
# Animate progress bar
self.animate_progress()
def animate_progress(self):
current_value = self.progress_bar.get()
if current_value < 1.0:
self.progress_bar.set(current_value + 0.1)
self.root.after(200, self.animate_progress)
def start_quiz(self):
if not self.current_questions:
messagebox.showerror("Error", "No questions available!")
self.show_home_screen()
return
self.show_question()
def show_question(self):
if self.current_question_index >= len(self.current_questions):
self.show_quiz_results()
return
self.clear_content()
question = self.current_questions[self.current_question_index]
# Question header
header_frame = ctk.CTkFrame(self.content_frame)
header_frame.pack(fill="x", padx=20, pady=20)
progress_text = f"Question {self.current_question_index + 1}
of {len(self.current_questions)}"
progress_label = ctk.CTkLabel(
header_frame,
text=progress_text,
font=("Arial", 16, "bold")
)
progress_label.pack(pady=10)
# Progress bar
progress = (self.current_question_index + 1) /
len(self.current_questions)
progress_bar = ctk.CTkProgressBar(header_frame, width=400)
progress_bar.pack(pady=10)
progress_bar.set(progress)
# Question frame
question_frame = ctk.CTkFrame(self.content_frame)
question_frame.pack(fill="both", expand=True, padx=20,
pady=20)
# Question text
question_label = ctk.CTkLabel(
question_frame,
text=question.question,
font=("Arial", 20, "bold"),
wraplength=800,
justify="left"
)
question_label.pack(pady=30, padx=30)
# Answer options
self.selected_answer = ctk.IntVar(value=-1)
options_frame = ctk.CTkFrame(question_frame)
options_frame.pack(fill="both", expand=True, padx=30, pady=20)
for i, option in enumerate(question.options):
option_btn = ctk.CTkRadioButton(
options_frame,
text=f"{chr(65+i)}) {option}",
variable=self.selected_answer,
value=i,
font=("Arial", 16),
radiobutton_width=20,
radiobutton_height=20
)
option_btn.pack(pady=15, padx=20, anchor="w")
# Submit button
submit_btn = ctk.CTkButton(
question_frame,
text="Submit Answer",
command=self.submit_answer,
width=200,
height=50,
font=("Arial", 18, "bold"),
corner_radius=25
)
submit_btn.pack(pady=30)
def submit_answer(self):
if self.selected_answer.get() == -1:
messagebox.showwarning("Warning", "Please select an
answer!")
return
question = self.current_questions[self.current_question_index]
selected = self.selected_answer.get()
is_correct = selected == question.correct_answer
# Store answer
self.quiz_answers.append({
'question': question.question,
'selected': selected,
'correct': question.correct_answer,
'is_correct': is_correct,
'explanation': question.explanation
})
if is_correct:
self.quiz_score += 1
self.sound_manager.play_sound('correct')
else:
self.sound_manager.play_sound('wrong')
# Show answer feedback
self.show_answer_feedback(is_correct, question)
def show_answer_feedback(self, is_correct, question):
self.clear_content()
feedback_frame = ctk.CTkFrame(self.content_frame)
feedback_frame.pack(fill="both", expand=True, padx=20,
pady=20)
# Feedback icon and text
if is_correct:
feedback_text = "✅ Correct!"
feedback_color = "#4CAF50"
else:
feedback_text = "❌ Incorrect!"
feedback_color = "#F44336"
feedback_label = ctk.CTkLabel(
feedback_frame,
text=feedback_text,
font=("Arial", 32, "bold"),
text_color=feedback_color
)
feedback_label.pack(pady=30)
# Show correct answer
correct_answer_text = f"Correct Answer: {chr(65 +
question.correct_answer)})
{question.options[question.correct_answer]}"
correct_label = ctk.CTkLabel(
feedback_frame,
text=correct_answer_text,
font=("Arial", 18, "bold"),
wraplength=800
)
correct_label.pack(pady=20, padx=30)
# Show explanation
if question.explanation:
explanation_label = ctk.CTkLabel(
feedback_frame,
text=f"Explanation: {question.explanation}",
font=("Arial", 16),
wraplength=800,
justify="left"
)
explanation_label.pack(pady=20, padx=30)
# Next button
next_btn = ctk.CTkButton(
feedback_frame,
text="Next Question →",
command=self.next_question,
width=200,
height=50,
font=("Arial", 18, "bold")
)
next_btn.pack(pady=30)
# Auto-advance after 3 seconds
self.root.after(3000, self.next_question)
def next_question(self):
self.current_question_index += 1
self.show_question()
def show_quiz_results(self):
self.clear_content()
# Play celebration sound
self.sound_manager.play_sound('celebration')
# Calculate results
total_questions = len(self.current_questions)
correct_answers = self.quiz_score
percentage = (correct_answers / total_questions) * 100
# Save quiz to database
self.save_quiz_results(percentage, total_questions,
correct_answers)
# Results frame
results_frame = ctk.CTkFrame(self.content_frame)
results_frame.pack(fill="both", expand=True, padx=20, pady=20)
# Celebration header
celebration_label = ctk.CTkLabel(
results_frame,
text="🎉 Quiz Complete! 🎉",
font=("Arial", 36, "bold"),
text_color=("#FFD700", "#FFA500")
)
celebration_label.pack(pady=30)
# Score display
score_frame = ctk.CTkFrame(results_frame)
score_frame.pack(pady=30, padx=50, fill="x")
# Percentage score
percentage_label = ctk.CTkLabel(
score_frame,
text=f"{percentage:.1f}%",
font=("Arial", 72, "bold"),
text_color=self.get_score_color(percentage)
)
percentage_label.pack(pady=20)
# Detailed results
details_text = f"Correct: {correct_answers} | Wrong:
{total_questions - correct_answers} | Total: {total_questions}"
details_label = ctk.CTkLabel(
score_frame,
text=details_text,
font=("Arial", 20, "bold")
)
details_label.pack(pady=10)
# Performance message
performance_msg = self.get_performance_message(percentage)
performance_label = ctk.CTkLabel(