SOURCE CODE
1. [Link]
# [Link]
import sys, os
ROOT_DIR = [Link]([Link](__file__))
if ROOT_DIR not in [Link]: [Link](0, ROOT_DIR)
for sub in ["learning_centre","quiz_engine","minigames","ui"]:
p=[Link](ROOT_DIR,sub)
2. ui/[Link]
import pygame
# --- Colour palette ---
PINK = (255, 140, 180)
LIGHT_PINK = (255, 182, 193)
DARK_PINK = (199, 21, 133)
WHITE = (255, 255, 255)
SHADOW = (235, 170, 190)
# --- Helper ---
def safe_color(value):
"""Return a valid RGB tuple for pygame."""
presets = {
"pink": PINK,
"light_pink": LIGHT_PINK,
"dark_pink": DARK_PINK,
"white": WHITE,
if isinstance(value, tuple) and len(value) == 3:
return tuple(int(max(0, min(255, c))) for c in value)
if isinstance(value, str):
return [Link]([Link](), WHITE)
return WHITE
# --- Button class ---
class Button:
def __init__(self, text, x, y, w, h,
color=PINK, hover=LIGHT_PINK,
text_color=WHITE, font_name="segoeui", font_size=28,
action=None):
[Link] = text
[Link] = [Link](x, y, w, h)
[Link] = safe_color(color)
[Link] = safe_color(hover)
self.text_color = safe_color(text_color)
[Link] = action
[Link] = h // 2
[Link] = [Link](font_name, font_size, bold=True)
self.shadow_offset = 4
def draw(self, screen):
mouse_pos = [Link].get_pos()
hovered = [Link](mouse_pos)
base_col = [Link] if hovered else [Link]
shadow_rect = [Link]()
shadow_rect.x += self.shadow_offset
shadow_rect.y += self.shadow_offset
[Link](screen, SHADOW, shadow_rect, border_radius=[Link])
[Link](screen, base_col, [Link], border_radius=[Link])
[Link](screen, (255, 220, 230), [Link], width=2,
border_radius=[Link])
try:
txt = [Link]([Link], True, self.text_color)
except Exception:
txt = [Link]("arial", 24).render(str([Link]), True, WHITE)
[Link](
txt,
([Link] - txt.get_width() // 2,
[Link] - txt.get_height() // 2)
def click(self, pos):
if [Link](pos):
print(f"🩷 CLICKED BUTTON: {[Link]}")
if [Link]:
[Link]()
3. ui/simple_scene.py
# F:\hello kitty detectibve gsame\ui\simple_scene.py
import pygame
class SceneBase:
def __init__(self, screen, width, height):
[Link] = screen
[Link] = width
[Link] = height
self.next_scene = None
def handle_event(self, event):
"""Handle pygame events."""
pass
def update(self, dt=0):
"""Update logic per frame. Return scene key if switching."""
return self.next_scene
def draw(self):
"""Draw everything."""
Pass
4. Kuromi Command Center (kuromi_chaos_center_pygame.py)
# kuromi_chaos_center_pygame.py
# One-window Kuromi Command Center (Pygame)
# - Main Menu with two big buttons
# - Progress HQ scene (monitor players + time tracking to daily_activity)
# - Lessons Manager scene (add/view/edit/delete lessons by grade & subject)
# Palette: black + purples + white only
import pygame
import [Link]
import time
from datetime import datetime
# ───────────────────────── CONFIG
─────────────────────────
DB_NAME = "CASE_FILE_0X67"
WIDTH, HEIGHT = 1200, 720
FPS = 60
# Colors (purple/black/white only)
BLACK_PURPLE = (10, 0, 15)
MID_PURPLE = (30, 0, 45)
ROYAL_PURPLE = (80, 0, 110)
GLOW_PURPLE = (140, 80, 190)
LAVENDER = (210, 190, 240)
WHITE = (255, 255, 255)
MUTED = (180, 160, 200)
# ─────────────────────── DB HELPERS
───────────────────────
def db_conn():
try:
return [Link](
host="localhost", user="root", password="Tikku17@#$",
database=DB_NAME, autocommit=True
except Exception as e:
print("DB error:", e)
return None
def ensure_daily_activity():
con = db_conn()
if not con: return
cur = [Link]()
[Link]("""
CREATE TABLE IF NOT EXISTS daily_activity (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(100) NOT NULL,
seconds INT NOT NULL DEFAULT 0,
activity_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
""")
[Link](); [Link]()
def fetch_players():
con = db_conn()
if not con: return []
cur = [Link](dictionary=True, buffered=True)
[Link]("SELECT username, coins, completed_levels, last_played FROM
quiz_progress ORDER BY coins DESC, username ASC")
rows = [Link]() or []
[Link](); [Link]()
return rows
def fetch_player(username):
con = db_conn()
if not con: return None
cur = [Link](dictionary=True, buffered=True)
[Link]("SELECT * FROM quiz_progress WHERE username=%s", (username,))
row = [Link]()
[Link](); [Link]()
return row
def update_player(username, **fields):
if not fields: return
parts = [f"{k}=%s" for k in [Link]()]
vals = list([Link]()) + [username]
sql = f"UPDATE quiz_progress SET {', '.join(parts)}, last_played=NOW() WHERE
username=%s"
con = db_conn()
if not con: return
cur = [Link]()
[Link](sql, tuple(vals))
[Link]()
[Link](); [Link]()
def add_activity_seconds(username, seconds):
if not username or seconds <= 0: return
con = db_conn()
if not con: return
cur = [Link]()
[Link]("INSERT INTO daily_activity (username, seconds) VALUES (%s,%s)",
(username, int(seconds)))
[Link]()
[Link](); [Link]()
def minutes_total(username):
con = db_conn()
if not con: return 0
cur = [Link](dictionary=True, buffered=True)
[Link]("SELECT COALESCE(SUM(seconds),0) AS s FROM daily_activity
WHERE username=%s", (username,))
r = [Link]()
[Link](); [Link]()
s = int(r["s"] or 0)
return s // 60
def active_days(username):
con = db_conn()
if not con: return 0
cur = [Link](dictionary=True, buffered=True)
[Link]("SELECT COUNT(DISTINCT DATE(activity_time)) AS d FROM
daily_activity WHERE username=%s", (username,))
r = [Link]()
[Link](); [Link]()
return int(r["d"] or 0)
# Lessons/Grades/Subjects helpers
def fetch_grades():
con = db_conn()
if not con: return []
cur = [Link](dictionary=True, buffered=True)
[Link]("SELECT id, name FROM grades ORDER BY id")
rows = [Link]() or []
[Link](); [Link]()
return rows
def fetch_subjects_by_grade(grade_id):
con = db_conn()
if not con: return []
cur = [Link](dictionary=True, buffered=True)
[Link]("SELECT id, name FROM subjects WHERE grade_id=%s ORDER BY
id", (grade_id,))
rows = [Link]() or []
[Link](); [Link]()
return rows
def fetch_lessons_by_grade(grade_name):
con = db_conn()
if not con: return []
cur = [Link](dictionary=True, buffered=True)
[Link]("""
SELECT [Link], [Link], [Link] AS subject
FROM lessons l
JOIN subjects s ON l.subject_id = [Link]
JOIN grades g ON s.grade_id = [Link]
WHERE [Link]=%s
ORDER BY [Link] DESC
""", (grade_name,))
rows = [Link]() or []
[Link](); [Link]()
return rows
def fetch_lesson(lesson_id):
con = db_conn()
if not con: return None
cur = [Link](dictionary=True, buffered=True)
[Link]("SELECT * FROM lessons WHERE id=%s", (lesson_id,))
row = [Link]()
[Link](); [Link]()
return row
def insert_lesson(title, desc, content, subject_id):
con = db_conn()
if not con: return
cur = [Link]()
[Link]("INSERT INTO lessons (title, description, content, subject_id) VALUES
(%s,%s,%s,%s)",
(title, desc, content, subject_id))
[Link]()
[Link](); [Link]()
def update_lesson(lesson_id, title, desc, content):
con = db_conn()
if not con: return
cur = [Link]()
[Link]("UPDATE lessons SET title=%s, description=%s, content=%s WHERE
id=%s",
(title, desc, content, lesson_id))
[Link]()
[Link](); [Link]()
def delete_lesson(lesson_id):
con = db_conn()
if not con: return
cur = [Link]()
[Link]("DELETE FROM lessons WHERE id=%s", (lesson_id,))
[Link]()
[Link](); [Link]()
# ─────────────────────── UI COMPONENTS
───────────────────────
class PillButton:
def __init__(self, rect, text, font, fill=ROYAL_PURPLE, fg=WHITE):
[Link] = [Link](rect)
[Link] = text
[Link] = font
[Link] = fill
[Link] = fg
[Link] = False
def draw(self, surf):
color = [Link] if not [Link] else tuple(min(255, c+35) for c in [Link])
[Link](surf, color, [Link], border_radius=40)
t = [Link]([Link], True, [Link])
[Link](t, ([Link] - t.get_width()//2, [Link] - t.get_height()//2))
def hit(self, pos): return [Link](pos)
class TextInput:
# simple single-line input
def __init__(self, rect, font, text="", placeholder="", bg=MID_PURPLE, fg=WHITE):
[Link] = [Link](rect)
[Link] = font
[Link] = text
[Link] = placeholder
[Link] = fg
[Link] = bg
[Link] = False
[Link] = len(text)
[Link] = 0
def handle(self, e):
if [Link] == [Link]:
[Link] = [Link]([Link])
if [Link] and [Link] == [Link]:
if [Link] == pygame.K_BACKSPACE and [Link] > 0:
[Link] = [Link][:[Link]-1] + [Link][[Link]:]
[Link] -= 1
elif [Link] == pygame.K_DELETE and [Link] < len([Link]):
[Link] = [Link][:[Link]] + [Link][[Link]+1:]
elif [Link] == pygame.K_LEFT:
[Link] = max(0, [Link]-1)
elif [Link] == pygame.K_RIGHT:
[Link] = min(len([Link]), [Link]+1)
elif [Link] == pygame.K_HOME:
[Link] = 0
elif [Link] == pygame.K_END:
[Link] = len([Link])
elif [Link] == pygame.K_RETURN:
pass
else:
if [Link] and [Link]():
[Link] = [Link][:[Link]] + [Link] + [Link][[Link]:]
[Link] += 1
def draw(self, surf):
[Link](surf, [Link], [Link], border_radius=20)
show = [Link] if [Link] else [Link]
color = [Link] if [Link] else MUTED
t = [Link](show, True, color)
[Link](t, ([Link].x+14, [Link].y+10))
# cursor
[Link] = ([Link] + 1) % 60
if [Link] and [Link] < 30:
cx = [Link].x + 14 + [Link]([Link][:[Link]])[0]
[Link](surf, WHITE, (cx, [Link].y+8), (cx,
[Link].y+[Link]-8), 2)
class TextArea:
# multi-line naive text area (Enter inserts newline)
def __init__(self, rect, font, text="", placeholder="", bg=MID_PURPLE, fg=WHITE):
[Link] = [Link](rect)
[Link] = font
[Link] = [Link]("\n") if text else [""]
[Link] = placeholder
[Link] = fg
[Link] = bg
[Link] = False
self.cursor_row = 0
self.cursor_col = 0
[Link] = 0
def get_text(self): return "\n".join([Link])
def set_text(self, t):
[Link] = [Link]("\n") if t else [""]
self.cursor_row = min(self.cursor_row, len([Link])-1)
self.cursor_col = min(self.cursor_col, len([Link][self.cursor_row]))
def handle(self, e):
if [Link] == [Link]:
[Link] = [Link]([Link])
if [Link] and [Link] == [Link]:
if [Link] == pygame.K_BACKSPACE:
if self.cursor_col > 0:
line = [Link][self.cursor_row]
[Link][self.cursor_row] = line[:self.cursor_col-1] + line[self.cursor_col:]
self.cursor_col -= 1
elif self.cursor_row > 0:
prev_len = len([Link][self.cursor_row-1])
[Link][self.cursor_row-1] += [Link][self.cursor_row]
[Link](self.cursor_row)
self.cursor_row -= 1
self.cursor_col = prev_len
elif [Link] == pygame.K_DELETE:
line = [Link][self.cursor_row]
if self.cursor_col < len(line):
[Link][self.cursor_row] = line[:self.cursor_col] + line[self.cursor_col+1:]
elif self.cursor_row < len([Link])-1:
[Link][self.cursor_row] += [Link][self.cursor_row+1]
[Link](self.cursor_row+1)
elif [Link] == pygame.K_RETURN:
line = [Link][self.cursor_row]
new_line = line[self.cursor_col:]
[Link][self.cursor_row] = line[:self.cursor_col]
[Link](self.cursor_row+1, new_line)
self.cursor_row += 1
self.cursor_col = 0
elif [Link] == pygame.K_LEFT:
if self.cursor_col > 0:
self.cursor_col -= 1
elif self.cursor_row > 0:
self.cursor_row -= 1
self.cursor_col = len([Link][self.cursor_row])
elif [Link] == pygame.K_RIGHT:
if self.cursor_col < len([Link][self.cursor_row]):
self.cursor_col += 1
elif self.cursor_row < len([Link])-1:
self.cursor_row += 1
self.cursor_col = 0
elif [Link] == pygame.K_UP:
if self.cursor_row > 0:
self.cursor_row -= 1
self.cursor_col = min(self.cursor_col, len([Link][self.cursor_row]))
elif [Link] == pygame.K_DOWN:
if self.cursor_row < len([Link])-1:
self.cursor_row += 1
self.cursor_col = min(self.cursor_col, len([Link][self.cursor_row]))
else:
if [Link] and [Link]():
line = [Link][self.cursor_row]
[Link][self.cursor_row] = line[:self.cursor_col] + [Link] +
line[self.cursor_col:]
self.cursor_col += 1
def draw(self, surf):
[Link](surf, [Link], [Link], border_radius=16)
y = [Link].y + 8
if len(self.get_text().strip()) == 0 and not [Link]:
t = [Link]([Link], True, MUTED)
[Link](t, ([Link].x+10, y))
else:
for line in [Link]:
t = [Link](line, True, WHITE)
[Link](t, ([Link].x+10, y))
y += [Link].get_height()+4
# cursor blink
[Link] = ([Link] + 1) % 60
if [Link] and [Link] < 30:
cx = [Link].x + 10 + [Link]([Link][self.cursor_row][:self.cursor_col])
[0]
cy = [Link].y + 8 + self.cursor_row*([Link].get_height()+4)
[Link](surf, WHITE, (cx, cy), (cx, cy+[Link].get_height()), 2)
# ─────────────────────── SCENE SYSTEM
───────────────────────
class SceneBase:
def __init__(self, screen):
[Link] = screen
def handle(self, e): pass
def update(self): pass
def draw(self): pass
# ─────────────────────── MAIN MENU SCENE
────────────────────
class MainMenu(SceneBase):
def __init__(self, screen, goto_progress, goto_lessons):
super().__init__(screen)
[Link]()
[Link] = [Link]("Comic Sans MS", 56, bold=True)
[Link] = [Link]("Comic Sans MS", 26, bold=True)
self.btn_progress = PillButton((WIDTH//2-220, HEIGHT//2-30, 440, 60), "💀
Monitor Player Progress", [Link], ROYAL_PURPLE)
self.btn_lessons = PillButton((WIDTH//2-220, HEIGHT//2+60, 440, 60), "📚
Manage Lessons", [Link], ROYAL_PURPLE)
self.goto_progress = goto_progress
self.goto_lessons = goto_lessons
def handle(self, e):
if [Link] == [Link]:
self.btn_progress.hover = self.btn_progress.hit([Link])
self.btn_lessons.hover = self.btn_lessons.hit([Link])
if [Link] == [Link]:
if self.btn_progress.hit([Link]): self.goto_progress()
if self.btn_lessons.hit([Link]): self.goto_lessons()
def draw(self):
[Link](BLACK_PURPLE)
[Link]([Link], MID_PURPLE, (0, 0, WIDTH, 100))
[Link]([Link], GLOW_PURPLE, (0, 98, WIDTH, 2))
t = [Link]("KUROMI CHAOS CENTER 🖤", True, LAVENDER)
[Link](t, (WIDTH//2 - t.get_width()//2, 24))
# card
[Link]([Link], MID_PURPLE, (WIDTH//2-300, HEIGHT//2-120,
600, 280), border_radius=30)
self.btn_progress.draw([Link])
self.btn_lessons.draw([Link])
hint = [Link]("Choose your domain of mischief…", True, LAVENDER)
[Link](hint, (WIDTH//2 - hint.get_width()//2, HEIGHT//2-150))
# ─────────────────────── PROGRESS HQ SCENE
──────────────────
class ProgressHQ(SceneBase):
def __init__(self, screen, go_back):
super().__init__(screen)
ensure_daily_activity()
self.go_back = go_back
[Link] = [Link]("Comic Sans MS", 40, bold=True)
[Link] = [Link]("Comic Sans MS", 28, bold=True)
[Link] = [Link]("Comic Sans MS", 22)
[Link] = fetch_players()
[Link] = None
[Link] = 0
[Link] = 0 # 0 Progress, 1 Coins, 2 Time, 3 Achievements, 4 Deep
[Link] = []
self.btn_back = PillButton((30, 24, 140, 42), "← Back", [Link],
ROYAL_PURPLE)
# time tracking
self.view_start_ts = None # start time viewing current user
# utility
def _start_tracking(self):
if [Link]:
self.view_start_ts = [Link]()
def _flush_tracking(self):
if [Link] and self.view_start_ts:
secs = int([Link]() - self.view_start_ts)
add_activity_seconds([Link], secs)
self.view_start_ts = None
def handle(self, e):
if [Link] == [Link]:
self.btn_back.hover = self.btn_back.hit([Link])
for _, b in [Link]: [Link] = [Link]([Link])
if [Link] == [Link]:
[Link] = max(0, [Link] - e.y*40)
if [Link] == [Link]:
if self.btn_back.hit([Link]):
self._flush_tracking()
self.go_back()
return
# select player
y = 170 - [Link]
for row in [Link]:
r = [Link](40, y, 330, 50)
if [Link]([Link]):
# flush old user time
self._flush_tracking()
[Link] = row["username"]
self._start_tracking()
return
y += 60
# tabs
if hasattr(self, "tab_rects"):
for r, idx in self.tab_rects:
if [Link]([Link]):
[Link] = idx
return
# actions
if [Link]:
for key, b in [Link]:
if [Link]([Link]):
self._act(key)
return
def _act(self, key):
p = fetch_player([Link])
if not p: return
c = int([Link]("coins",0)); l = int([Link]("completed_levels",0))
t = int([Link]("tools_unlocked",0)); f = int([Link]("finale_unlocked",0))
if key == "lvl+": update_player([Link], completed_levels=min(7, l+1))
if key == "lvl-": update_player([Link], completed_levels=max(0, l-1))
if key == "c+10": update_player([Link], coins=c+10)
if key == "c-10": update_player([Link], coins=max(0, c-10))
if key == "c+100": update_player([Link], coins=c+100)
if key == "c-100": update_player([Link], coins=max(0, c-100))
if key == "toggle_tools": update_player([Link], tools_unlocked=0 if t else 1)
if key == "toggle_finale": update_player([Link], finale_unlocked=0 if f else 1)
if key == "reset": update_player([Link], coins=0, clues_unlocked=0,
tools_unlocked=0, finale_unlocked=0, completed_levels=0)
[Link] = fetch_players()
def draw(self):
[Link](BLACK_PURPLE)
[Link]([Link], MID_PURPLE, (0,0,WIDTH,90))
title = [Link]("💀 KUROMI PROGRESS HQ — Chaos Observatory",
True, LAVENDER)
[Link](title, (WIDTH//2 - title.get_width()//2, 22))
[Link]([Link], GLOW_PURPLE, (0, 88, WIDTH, 2))
self.btn_back.draw([Link])
# left panel: players
[Link]([Link], MID_PURPLE, (20, 110, 380, HEIGHT-140),
border_radius=24)
[Link]([Link]("🎭 Players", True, GLOW_PURPLE), (40, 120))
y = 170 - [Link]
for row in [Link]:
r = [Link](40, y, 340, 50)
col = GLOW_PURPLE if [Link] == row["username"] else
ROYAL_PURPLE
[Link]([Link], col, r, border_radius=40)
t = [Link](f"{row['username']} 💰{row['coins']}", True, WHITE)
[Link](t, (r.x+16, r.y+10))
y += 60
# right panel
[Link]([Link], MID_PURPLE, (420, 110, WIDTH-440, HEIGHT-
140), border_radius=24)
# tabs
self.tab_rects = []
x = 440
tabs = ["🪪 Progress","💰 Coins"," Time","🏆 Achievements","🔮 Deep"]
for i, name in enumerate(tabs):
r = [Link](x, 120, 150, 36)
[Link]([Link], GLOW_PURPLE if [Link]==i else
ROYAL_PURPLE, r, border_radius=30)
tt = [Link](name, True, WHITE)
[Link](tt, ([Link]-tt.get_width()//2, [Link]-tt.get_height()//2))
self.tab_rects.append((r, i))
x += 160
# inner content
inner = [Link](440, 165, WIDTH-480, HEIGHT-200)
[Link]([Link], BLACK_PURPLE, inner, border_radius=20)
if not [Link]:
hint = [Link]("Pick a player to start spying ", True, LAVENDER)
[Link](hint, ([Link] - hint.get_width()//2, [Link] - 10))
return
p = fetch_player([Link])
if not p:
[Link]([Link]("⚠️Player not found", True, WHITE),
(inner.x+20, inner.y+20))
return
if [Link] == 0: self._tab_progress(inner, p)
elif [Link] == 1: self._tab_coins(inner, p)
elif [Link] == 2: self._tab_time(inner, p)
elif [Link] == 3: self._tab_achievements(inner, p)
elif [Link] == 4: self._tab_deep(inner, p)
def _tab_progress(self, area, p):
x, y = area.x+20, area.y+20
lines = [
f"🪪 {p['username']}",
f"✅ Levels: {p['completed_levels']}/7",
f"🧩 Clues: {p['clues_unlocked']}",
f"🪄 Tools: {'ON' if p['tools_unlocked'] else 'OFF'}",
f"🌟 Finale: {'Unlocked' if p['finale_unlocked'] else 'Locked'}",
f"🕓 Last Played: {str(p['last_played'])[:19]}",
for line in lines:
[Link]([Link](line, True, LAVENDER), (x, y)); y += 42
[Link] = [
("lvl+", PillButton((x, y+10, 140, 44), "+ Level", [Link])),
("lvl-", PillButton((x+150, y+10, 140, 44), "- Level", [Link])),
("toggle_tools", PillButton((x+300, y+10, 180, 44), "Toggle Tools", [Link])),
("toggle_finale", PillButton((x+490, y+10, 200, 44), "Toggle Finale", [Link])),
("reset", PillButton((x, y+70, 220, 44), "💀 Reset Progress", [Link])),
for _, b in [Link]: [Link]([Link])
def _tab_coins(self, area, p):
x, y = area.x+20, area.y+30
[Link]([Link](f"💰 {int(p['coins'])} Coins", True, LAVENDER),
(x, y))
y += 70
[Link] = [
("c+10", PillButton((x, y, 130, 44), "+10", [Link])),
("c-10", PillButton((x+140, y, 130, 44), "-10", [Link])),
("c+100", PillButton((x+280, y, 130, 44), "+100", [Link])),
("c-100", PillButton((x+420, y, 130, 44), "-100", [Link])),
for _, b in [Link]: [Link]([Link])
def _tab_time(self, area, p):
x, y = area.x+20, area.y+20
mins = minutes_total([Link])
hrs, mm = mins//60, mins%60
days = active_days([Link])
lines = [
" TIME SPENT",
f"Total: {hrs}h {mm}m ({mins} minutes)",
f"Active Days: {days}",
"Time auto-logs while this player's page is open."
cols = [LAVENDER, WHITE, WHITE, MUTED]
for i, line in enumerate(lines):
[Link]([Link](line, True, cols[i]), (x, y)); y += 48
# progress bar (10h full)
barw = min(640, max(20, int((mins/600.0)*640)))
[Link]([Link], ROYAL_PURPLE, (x, y+10, 640, 20),
border_radius=10)
[Link]([Link], GLOW_PURPLE, (x, y+10, barw, 20),
border_radius=10)
def _tab_achievements(self, area, p):
x, y = area.x+20, area.y+20
mins = minutes_total([Link]); days = active_days([Link])
ach = [
("🎀 Clue Hunter", p["clues_unlocked"]>=1),
(" Toolsmith", p["tools_unlocked"]==1),
("💀 Apprentice", p["completed_levels"]>=1),
("👑 Grade Crusher", p["completed_levels"]>=4),
("🌟 Finale Finder", p["finale_unlocked"]==1),
("💰 Rich Kitty", p["coins"]>=100),
(" Loyal Detective", days>=7),
("📚 Study Buddy", mins>=120),
("🧠 Marathon Mind", mins>=600),
for name, ok in ach:
mark = "✅" if ok else "▫"
col = LAVENDER if ok else MUTED
[Link]([Link](f"{mark} {name}", True, col), (x, y))
y += 38
def _tab_deep(self, area, p):
x, y = area.x+20, area.y+20
keys =
["username","coins","clues_unlocked","tools_unlocked","finale_unlocked","completed_l
evels","last_played"]
for k in keys:
v = [Link](k)
[Link]([Link](f"{k}: {v}", True, LAVENDER), (x, y)); y += 36
# ───────────────────── LESSONS MANAGER SCENE
───────────────────
class LessonsManager(SceneBase):
def __init__(self, screen, go_back):
super().__init__(screen)
self.go_back = go_back
[Link] = [Link]("Comic Sans MS", 40, bold=True)
[Link] = [Link]("Comic Sans MS", 28, bold=True)
[Link] = [Link]("Comic Sans MS", 22)
[Link] = [Link]("Comic Sans MS", 20)
# data
[Link] = fetch_grades()
self.selected_grade_idx = 0 if [Link] else -1
[Link] = []
[Link] = []
self.selected_lesson_id = None
# controls
self.btn_back = PillButton((30, 24, 140, 42), "← Back", [Link],
ROYAL_PURPLE)
self.btn_refresh = PillButton((WIDTH-180, 24, 140, 42), "↻ Refresh", [Link],
ROYAL_PURPLE)
self._load_grade_related()
# modals
[Link] = None # ("add", widgets) or ("edit", lesson_id, widgets)
def _load_grade_related(self):
if self.selected_grade_idx < 0 or self.selected_grade_idx >= len([Link]):
[Link], [Link] = [], []
return
g = [Link][self.selected_grade_idx]
[Link] = fetch_subjects_by_grade(g["id"])
[Link] = fetch_lessons_by_grade(g["name"])
self.selected_lesson_id = None
def handle(self, e):
if [Link]:
self._handle_modal(e); return
if [Link] == [Link]:
self.btn_back.hover = self.btn_back.hit([Link])
self.btn_refresh.hover = self.btn_refresh.hit([Link])
if [Link] == [Link]:
if self.btn_back.hit([Link]): self.go_back(); return
if self.btn_refresh.hit([Link]): self._load_grade_related(); return
# grade chips
gx, gy = 40, 120
for i, g in enumerate([Link]):
r = [Link](gx, gy, 140, 36)
if [Link]([Link]):
self.selected_grade_idx = i
self._load_grade_related()
return
gx += 150
if gx > WIDTH-200: gx, gy = 40, gy+46
# lessons list click
area = [Link](30, 200, 500, HEIGHT-230)
if [Link]([Link]):
rel_y = [Link][1] - area.y
idx = rel_y // 48
if 0 <= idx < len([Link]):
self.selected_lesson_id = [Link][idx]["id"]
# action buttons
ax = 560; ay = 200
btns = [
("add", [Link](ax, ay, 180, 44)),
("edit", [Link](ax+200, ay, 180, 44)),
("delete", [Link](ax+400, ay, 180, 44)),
for k, r in btns:
if [Link]([Link]):
if k == "add": self._open_add_modal()
if k == "edit" and self.selected_lesson_id:
self._open_edit_modal(self.selected_lesson_id)
if k == "delete" and self.selected_lesson_id:
delete_lesson(self.selected_lesson_id); self._load_grade_related()
return
def draw(self):
[Link](BLACK_PURPLE)
[Link]([Link], MID_PURPLE, (0,0,WIDTH,90))
t = [Link]("📚 KUROMI LESSONS LAB — Rewrite Reality", True,
LAVENDER)
[Link](t, (WIDTH//2 - t.get_width()//2, 22))
[Link]([Link], GLOW_PURPLE, (0, 88, WIDTH, 2))
self.btn_back.draw([Link]); self.btn_refresh.draw([Link])
# grade chips
[Link]([Link]("🎓 Grades", True, GLOW_PURPLE), (30, 100))
gx, gy = 40, 140
for i, g in enumerate([Link]):
r = [Link](gx, gy, 140, 36)
[Link]([Link], GLOW_PURPLE if i==self.selected_grade_idx
else ROYAL_PURPLE, r, border_radius=30)
tt = [Link](g["name"], True, WHITE)
[Link](tt, ([Link]-tt.get_width()//2, [Link]-tt.get_height()//2))
gx += 150
if gx > WIDTH-200: gx, gy = 40, gy+46
# lessons list
list_area = [Link](30, 200, 500, HEIGHT-230)
[Link]([Link], MID_PURPLE, list_area, border_radius=16)
[Link]([Link]("🧾 Lessons", True, LAVENDER), (40, 210))
y = 250
for row in [Link]:
line = f"#{row['id']} — {row['title']} | {row['subject']}"
col = GLOW_PURPLE if self.selected_lesson_id == row["id"] else WHITE
[Link]([Link](line, True, col), (40, y)); y += 48
# action buttons
ax, ay = 560, 200
for label, off in [("➕ Add Lesson",0), ("✏️Edit Lesson",200), (" Delete
Lesson",400)]:
[Link]([Link], ROYAL_PURPLE, (ax+off, ay, 180, 44),
border_radius=30)
tt = [Link](label, True, WHITE)
[Link](tt, (ax+off+90-tt.get_width()//2, ay+22-tt.get_height()//2))
# subject hint
if self.selected_grade_idx >= 0 and [Link]:
g = [Link][self.selected_grade_idx]
subs = ", ".join(s["name"] for s in [Link]) if [Link] else "No subjects
(add in DB)"
[Link]([Link](f"📂 Subjects in {g['name']}: {subs}", True,
MUTED), (560, 260))
# modal
if [Link]: self._draw_modal()
# ── Modals (Add/Edit) ──
def _open_add_modal(self):
if self.selected_grade_idx < 0: return
g = [Link][self.selected_grade_idx]
subs = [Link]
if not subs: return
[Link] = ("add", {
"title": TextInput((150, 240, 500, 44), [Link], "", "Title"),
"desc": TextArea((150, 300, 500, 100), [Link], "", "Short description"),
"content": TextArea((150, 410, 500, 180), [Link], "", "Lesson content (quiz
source)"),
"subject_index": 0,
"subjects": subs,
"grade": g
})
def _open_edit_modal(self, lesson_id):
row = fetch_lesson(lesson_id)
if not row: return
# find subject index within current grade list
subj_idx = 0
subs = [Link]
for i, s in enumerate(subs):
if s["id"] == row["subject_id"]:
subj_idx = i; break
[Link] = ("edit", lesson_id, {
"title": TextInput((150, 240, 500, 44), [Link], row["title"], "Title"),
"desc": TextArea((150, 300, 500, 100), [Link], row["description"], "Short
description"),
"content": TextArea((150, 410, 500, 180), [Link], row["content"], "Lesson
content (quiz source)"),
"subject_index": subj_idx,
"subjects": subs
})
def _handle_modal(self, e):
kind = [Link][0]
if kind == "add":
widgets = [Link][1]
for w in (widgets["title"], widgets["desc"], widgets["content"]): [Link](e)
if [Link] == [Link]:
# subject dropdown simple cycling on click area
drop = [Link](150, 205, 300, 28)
if [Link]([Link]):
widgets["subject_index"] = (widgets["subject_index"] + 1) %
max(1,len(widgets["subjects"]))
# buttons
save = [Link](150, 610, 180, 44)
cancel = [Link](370, 610, 180, 44)
if [Link]([Link]):
title = widgets["title"].[Link]()
desc = widgets["desc"].get_text().strip()
content = widgets["content"].get_text().strip()
if title and desc and content and widgets["subjects"]:
subject_id = widgets["subjects"][widgets["subject_index"]]["id"]
insert_lesson(title, desc, content, subject_id)
[Link] = None
self._load_grade_related()
if [Link]([Link]):
[Link] = None
elif kind == "edit":
lesson_id, widgets = [Link][1], [Link][2]
for w in (widgets["title"], widgets["desc"], widgets["content"]): [Link](e)
if [Link] == [Link]:
drop = [Link](150, 205, 300, 28)
if [Link]([Link]):
widgets["subject_index"] = (widgets["subject_index"] + 1) %
max(1,len(widgets["subjects"]))
save = [Link](150, 610, 180, 44)
cancel = [Link](370, 610, 180, 44)
if [Link]([Link]):
title = widgets["title"].[Link]()
desc = widgets["desc"].get_text().strip()
content = widgets["content"].get_text().strip()
# Note: we keep subject the same grade scope; if needed you can also update
subject_id here
if title and desc and content:
update_lesson(lesson_id, title, desc, content)
[Link] = None
self._load_grade_related()
if [Link]([Link]):
[Link] = None
def _draw_modal(self):
# overlay
overlay = [Link]((WIDTH, HEIGHT), [Link])
[Link]((0,0,0,160))
[Link](overlay, (0,0))
box = [Link](120, 120, WIDTH-240, HEIGHT-200)
[Link]([Link], MID_PURPLE, box, border_radius=24)
[Link]([Link], GLOW_PURPLE, box, 2, border_radius=24)
kind = [Link][0]
title = "➕ Add Lesson" if kind=="add" else "✏️Edit Lesson"
tt = [Link](title, True, LAVENDER)
[Link](tt, ([Link] - tt.get_width()//2, 140))
if kind == "add":
widgets = [Link][1]
# subject chooser
subs = widgets["subjects"]; idx = widgets["subject_index"]
subj_name = subs[idx]["name"] if subs else "No subjects"
tag = [Link](f"Subject: {subj_name}", True, LAVENDER)
[Link]([Link], ROYAL_PURPLE, (150, 205, 300, 28),
border_radius=14)
[Link](tag, (160, 206))
widgets["title"].draw([Link])
widgets["desc"].draw([Link])
widgets["content"].draw([Link])
# buttons
for (x,label) in [(150,"Save"), (370,"Cancel")]:
[Link]([Link], ROYAL_PURPLE, (x, 610, 180, 44),
border_radius=30)
t = [Link](label, True, WHITE)
[Link](t, (x+90 - t.get_width()//2, 632 - t.get_height()//2))
elif kind == "edit":
lesson_id, widgets = [Link][1], [Link][2]
subs = widgets["subjects"]; idx = widgets["subject_index"]
subj_name = subs[idx]["name"] if subs else "No subjects"
tag = [Link](f"Subject: {subj_name}", True, LAVENDER)
[Link]([Link], ROYAL_PURPLE, (150, 205, 300, 28),
border_radius=14)
[Link](tag, (160, 206))
widgets["title"].draw([Link])
widgets["desc"].draw([Link])
widgets["content"].draw([Link])
for (x,label) in [(150,"Save"), (370,"Cancel")]:
[Link]([Link], ROYAL_PURPLE, (x, 610, 180, 44),
border_radius=30)
t = [Link](label, True, WHITE)
[Link](t, (x+90 - t.get_width()//2, 632 - t.get_height()//2))
# ─────────────────────── APP BOOTSTRAP
───────────────────────
class App:
def __init__(self):
[Link]()
[Link] = [Link].set_mode((WIDTH, HEIGHT))
[Link].set_caption("💀 Kuromi Chaos Center — One Window of Cute
Doom")
[Link] = [Link]()
[Link] = None
self.main_menu()
def main_menu(self):
[Link] = MainMenu(
[Link],
goto_progress=lambda: self.progress_hq(),
goto_lessons=lambda: self.lessons_lab()
def progress_hq(self):
[Link] = ProgressHQ([Link], go_back=self.main_menu)
def lessons_lab(self):
[Link] = LessonsManager([Link], go_back=self.main_menu)
def run(self):
running = True
while running:
for e in [Link]():
if [Link] == [Link]: running = False
else: [Link](e)
[Link]()
[Link]()
[Link](FPS)
[Link]()
if __name__ == "__main__":
ensure_daily_activity()
App().run()kuromi_chaos_center_pygame.py CODE HERE>
5. Quiz UI (quiz_ui.py)
import pygame
import random
import time
import [Link]
import pathfix
from ui.simple_scene import SceneBase
from learning_centre.ending import EndingScene # 🩷 cinematic finale
# ───────────────────────────────────────────────
# HELLO KITTY DETECTIVE QUIZ — Sequential Unlock Build
# ───────────────────────────────────────────────
GRADE_ORDER = [
{"name": "My Melody", "grade": 6, "color": (255,182,193)},
{"name": "Keroppi", "grade": 7, "color": (152,251,152)},
{"name": "Pochacco", "grade": 8, "color": (255,255,153)},
{"name": "Badtz-Maru", "grade": 9, "color": (176,196,222)},
{"name": "Cinnamoroll", "grade": 10, "color": (173,216,230)},
{"name": "Kuromi", "grade": 11, "color": (216,191,216)},
{"name": "Chococat", "grade": 12, "color": (139,69,19)},
FINALE = {"name": "Hello Kitty", "grade": "Finale", "color": (255,192,203)}
CLUE_THRESHOLD = 10
TOOLS_THRESHOLD = 25
FINALE_THRESHOLD = 40
class QuizUI(SceneBase):
def __init__(self, screen, width, height, username):
super().__init__(screen, width, height)
[Link] = screen
[Link] = width
[Link] = height
[Link] = username
[Link]()
[Link] = [Link]("Comic Sans MS", 36)
[Link] = [Link]("Comic Sans MS", 24)
[Link] = [Link]("Comic Sans MS", 18)
# DB setup (buffered cursors avoid unread-result errors; ping to auto-reconnect)
[Link] = None
self._db_connect()
self._ensure_tables()
self._load_progress()
# State
[Link] = "hub"
[Link] = None
self.active_host = None
[Link] = []
self.q_index = 0
self.option_rects = []
self.disabled_options = set()
self.hint_used_this_q = False
self.ending_scene = None # 🎬 holds the cinematic finale scene
# UI
self.back_button = [Link](40, 40, 160, 50)
self.hint_button = [Link]([Link] - 200, 40, 140, 50)
self.hub_cards = []
# Animation
self.anim_t0 = 0
self.anim_dur = 1.5
self.anim_text = ""
# ───────────────────────────────────────────────
# DATABASE UTILITIES
# ───────────────────────────────────────────────
def _db_connect(self):
try:
[Link] = [Link](
host="localhost",
user="root",
password="Tikku17@#$",
database="CASE_FILE_0X67",
autocommit=True,
connection_timeout=8,
raise_on_warnings=False,
except [Link] as e:
# Graceful offline mode; game still runs
[Link] = None
[Link] = (f"DB offline: {[Link] if hasattr(e,'msg') else str(e)}", (200,60,60))
def _cursor(self):
"""Fresh buffered cursor every query. Handles reconnects."""
if [Link] is None:
return None
try:
[Link](reconnect=True, attempts=1, delay=0)
return [Link](dictionary=True, buffered=True)
except Exception:
# Try one reconnect
self._db_connect()
if [Link] is None:
return None
return [Link](dictionary=True, buffered=True)
# ───────────────────────────────────────────────
# DATABASE SETUP + PROGRESS
# ───────────────────────────────────────────────
def _ensure_tables(self):
cur = self._cursor()
if not cur:
return
try:
[Link]("""
CREATE TABLE IF NOT EXISTS quiz_progress (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(100) UNIQUE,
coins INT DEFAULT 0,
clues_unlocked INT DEFAULT 0,
tools_unlocked BOOLEAN DEFAULT FALSE,
finale_unlocked BOOLEAN DEFAULT FALSE,
completed_levels INT DEFAULT 0,
last_played DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
""")
finally:
[Link]()
def _load_progress(self):
"""Load or create per-user quiz progress with safe defaults."""
cur = self._cursor()
if not cur:
# Offline defaults
[Link] = 0
[Link] = 0
[Link] = False
[Link] = False
self.completed_levels = 0
return
try:
[Link]("SELECT * FROM quiz_progress WHERE username=%s",
([Link],))
r = [Link]()
if not r:
# --- NEW USER: start clean, from Grade 6 only ---
[Link]("INSERT INTO quiz_progress (username) VALUES (%s)",
([Link],))
[Link] = 0
[Link] = 0
[Link] = False
[Link] = False
self.completed_levels = 0
else:
# --- RETAINED: existing progress load ---
[Link] = int([Link]("coins", 0))
[Link] = int([Link]("clues_unlocked", 0))
[Link] = bool([Link]("tools_unlocked", 0))
[Link] = bool([Link]("finale_unlocked", 0))
self.completed_levels = int([Link]("completed_levels", 0))
# --- NEW: clamp completed_levels so it's always in valid range ---
if self.completed_levels < 0:
self.completed_levels = 0
if self.completed_levels > len(GRADE_ORDER):
self.completed_levels = len(GRADE_ORDER)
finally:
[Link]()
def _save_progress(self):
cur = self._cursor()
if not cur:
return
try:
[Link]("""
UPDATE quiz_progress
SET coins=%s, clues_unlocked=%s, tools_unlocked=%s,
finale_unlocked=%s, completed_levels=%s, last_played=NOW()
WHERE username=%s
""", ([Link], [Link], int([Link]), int([Link]),
self.completed_levels, [Link]))
try:
[Link]()
except Exception:
pass
finally:
[Link]()
# ───────────────────────────────────────────────
# EVENT HANDLING
# ───────────────────────────────────────────────
def handle_event(self, event):
if [Link] == "ending" and self.ending_scene:
self.ending_scene.handle_event(event)
return
if [Link] == [Link]:
mx, my = [Link].get_pos()
# Back button
if self.back_button.collidepoint(mx, my):
self._return_to_hub("Returned to quiz plaza 🩷")
return
# Hint button
if [Link] in ("quiz", "finale") and [Link] and
self.hint_button.collidepoint(mx, my):
self._use_hint()
return
# Hub click logic
if [Link] == "hub":
for rect, kind, idx in self.hub_cards:
if [Link](mx, my):
if kind == "level":
# --- UPDATED: strict unlock by index ---
# Only levels with index <= completed_levels are playable.
if idx <= self.completed_levels:
self.start_quiz(idx)
else:
[Link] = ("Level locked. Complete previous level first!",
(200,60,60))
elif kind == "finale" and [Link]:
self.start_finale()
return
# Quiz options
elif [Link] == "quiz":
if 0 <= self.q_index < len([Link]):
for i, r in enumerate(self.option_rects):
if [Link](mx, my) and i not in self.disabled_options:
self._answer(i)
return
# Finale options
elif [Link] == "finale":
if self.q_index >= len([Link]):
self._return_to_hub("✨ The town sparkles again! Case closed.")
return
for i, r in enumerate(self.option_rects):
if [Link](mx, my):
self._answer(i)
return
# ───────────────────────────────────────────────
# QUIZ FLOW
# ───────────────────────────────────────────────
def start_quiz(self, level_index):
# safety clamps
level_index = max(0, min(level_index, len(GRADE_ORDER) - 1))
self.active_host = GRADE_ORDER[level_index]
[Link] = self._generate_mcqs(self.active_host["grade"])
if not [Link]:
# Always ensure at least 1 question exists
[Link] = [{"q": "Which language is used to write Python programs?",
"options": ["Python", "C++", "HTML", "Ruby"], "answer": 0}]
self.q_index = 0
self.disabled_options.clear()
self.hint_used_this_q = False
[Link] = "quiz"
[Link] = (f"{self.active_host['name']}’s quiz begins!", (90,50,120))
def start_finale(self):
self.active_host = FINALE
[Link] = [
{"q": "What restores sparkle to Sugarvale?", "options": ["Magic", "Kindness",
"Coins", "Dreams"], "answer": 1},
{"q": "What was Hello Kitty’s real goal?", "options": ["Teamwork", "Treasure",
"Revenge", "Fun"], "answer": 0},
{"q": "What’s the detective’s true tool?", "options": ["Curiosity", "Speed",
"Anger", "Luck"], "answer": 0},
self.q_index = 0
self.disabled_options.clear()
self.hint_used_this_q = False
[Link] = "finale"
[Link] = ("🌟 Finale unlocked — answer from the heart!", (180,70,180))
def _answer(self, choice_idx):
if not (0 <= self.q_index < len([Link])):
return
q = [Link][self.q_index]
correct = (choice_idx == q["answer"])
payout = 2 + min(self._current_level_index(), 3)
if correct:
[Link] += payout
[Link] = (f"✨ Correct! +{payout} coins", (80,150,80))
else:
[Link] = ("❌ Wrong! Try again!", (200,60,60))
self.q_index += 1
self.disabled_options.clear()
self.hint_used_this_q = False
# Handle endings
if self.q_index >= len([Link]):
if [Link] == "quiz":
self._on_quiz_complete()
elif [Link] == "finale":
self.start_finale_ending() # 🎬 trigger cinematic ending
self._handle_unlocks()
self._save_progress()
def start_finale_ending(self):
"""Launch the ending cinematic after the Hello Kitty finale quiz."""
self.ending_scene = EndingScene(
[Link],
[Link],
[Link],
on_complete=lambda: self._return_to_hub("💫 New mystery awaits…")
[Link] = "ending"
# ───────────────────────────────────────────────
# HELPERS + UNLOCKS
# ───────────────────────────────────────────────
def _return_to_hub(self, msg):
[Link] = "hub"
self.active_host = None
[Link]()
self.option_rects = []
self.q_index = 0
self.disabled_options.clear()
self.hint_used_this_q = False
[Link] = (msg, (120,70,120))
def _current_level_index(self):
if not self.active_host or self.active_host is FINALE:
return 0
for i, c in enumerate(GRADE_ORDER):
if c["grade"] == self.active_host["grade"]:
return i
return 0
def _on_quiz_complete(self):
idx = self._current_level_index()
# --- RETAINED: only advance if this level was the "current frontier" ---
if idx == self.completed_levels:
self.completed_levels += 1
if self.completed_levels >= len(GRADE_ORDER):
[Link] = True
self._save_progress()
self._play_unlock_animation(self._next_unlock_name())
else:
self._return_to_hub("✅ Level complete!")
def _next_unlock_name(self):
nxt = self.completed_levels
if nxt < len(GRADE_ORDER):
ch = GRADE_ORDER[nxt]
return f"{ch['name']} — Grade {ch['grade']}"
return "Finale: Hello Kitty"
def _handle_unlocks(self):
updated = False
if [Link] >= CLUE_THRESHOLD and [Link] < 1:
[Link] = 1
[Link] = ("🧩 First clue found!", (255,105,180))
updated = True
if [Link] >= TOOLS_THRESHOLD and not [Link]:
[Link] = True
[Link] = ("🪄 Detective Tools unlocked!", (255,182,193))
updated = True
if ([Link] >= FINALE_THRESHOLD or self.completed_levels >=
len(GRADE_ORDER)) and not [Link]:
[Link] = True
[Link] = ("🌟 Finale unlocked — Hello Kitty awaits!", (255,192,203))
updated = True
if updated:
self._save_progress()
def _use_hint(self):
"""Remove one wrong option once per question when Tools are ON."""
if self.hint_used_this_q or not (0 <= self.q_index < len([Link])):
return
q = [Link][self.q_index]
wrongs = [i for i in range(len(q["options"])) if i != q["answer"] and i not in
self.disabled_options]
if wrongs:
to_disable = [Link](wrongs)
self.disabled_options.add(to_disable)
self.hint_used_this_q = True
[Link] = ("💡 Hint used! One wrong option removed.", (120,60,120))
# ───────────────────────────────────────────────
# QUESTION GENERATION
# ───────────────────────────────────────────────
def _generate_mcqs(self, grade_num):
lessons = []
cur = self._cursor()
if cur:
try:
[Link]("""
SELECT [Link], [Link], [Link], [Link] AS subject
FROM lessons l
JOIN subjects s ON l.subject_id = [Link]
JOIN grades g ON s.grade_id = [Link]
WHERE [Link] = %s
""", (f"Grade {grade_num}",))
lessons = [Link]() or []
except Exception:
lessons = []
finally:
[Link]()
if not lessons:
# Safe fallback bank
return [
{"q": "Which language is used to write Python programs?",
"options": ["Python", "C++", "HTML", "Ruby"], "answer": 0},
{"q": "What does SQL stand for?",
"options": ["Structured Query Language", "Simple Quick Loop", "Super Query
Line", "System Queue Logic"], "answer": 0},
{"q": "Sum of angles in a triangle?",
"options": ["180°", "360°", "90°", "120°"], "answer": 0},
{"q": "CPU stands for…",
"options": ["Central Processing Unit", "Core Program Utility", "Computer
Power Unit", "Central Print Unit"], "answer": 0},
qs = []
for row in lessons:
subj = ([Link]("subject") or "").lower()
q = self._subject_to_question(subj)
if q:
[Link](q)
[Link](qs)
# Always guarantee at least 2 qs
if len(qs) < 2:
[Link]([
{"q": "Which language is used to write Python programs?",
"options": ["Python", "C++", "HTML", "Ruby"], "answer": 0},
{"q": "What does SQL stand for?",
"options": ["Structured Query Language", "Simple Quick Loop", "Super Query
Line", "System Queue Logic"], "answer": 0},
])
return qs[:8]
def _subject_to_question(self, subject):
banks = {
"math": [
("What is 12 × 8?", ["96", "108", "84", "112"], 0),
("Sum of angles in a triangle?", ["180°", "360°", "90°", "120°"], 0)
],
"science": [
("What gas do plants release?", ["Oxygen", "Nitrogen", "CO2", "Hydrogen"],
0),
("H₂O is the formula of…", ["Water", "Oxygen", "Salt", "Acid"], 0)
],
"english": [
("Which is a verb?", ["Run", "Cat", "Bright", "Fast"], 0),
("What punctuation ends a question?", ["?", "!", ".", ","], 0)
],
"history": [
("Who led India’s non-violence movement?", ["Gandhi", "Nehru", "Tagore",
"Bose"], 0),
("When did India gain independence?", ["1947", "1857", "1950", "1939"], 0)
],
"computer": [
("CPU stands for…", ["Central Processing Unit", "Core Program Utility",
"Computer Power Unit", "Central Print Unit"], 0),
("HTML is used for…", ["Web structure", "Drawing", "Maths", "Games"], 0)
if subject in banks:
qtext, opts, ans = [Link](banks[subject])
return {"q": qtext, "options": opts, "answer": ans}
return None
# ───────────────────────────────────────────────
# UPDATE + DRAW
# ───────────────────────────────────────────────
def update(self):
if [Link] == "anim":
if [Link]() - self.anim_t0 >= self.anim_dur:
[Link] = "hub"
elif [Link] == "ending" and self.ending_scene:
self.ending_scene.update()
return self.next_scene
def draw(self):
if [Link] == "ending" and self.ending_scene:
self.ending_scene.draw()
return
[Link]((255,240,245))
title = [Link]("🎀 Quiz Adventure 🎀", True, (255,105,180))
[Link](title, ([Link]//2 - title.get_width()//2, 40))
# Back
[Link]([Link], (255,182,193), self.back_button, border_radius=15)
[Link]([Link]("Back", True, (0,0,0)), (self.back_button.x+35,
self.back_button.y+10))
# HUD
y = 110
[Link]([Link](f"💰 Coins: {[Link]}", True, (90,0,90)), (50, y))
[Link]([Link](f"✅ Levels: {self.completed_levels}/7", True,
(90,0,90)), (230, y))
if [Link]: [Link]([Link]("🧩 Clues", True, (120,60,120)), (420,
y))
if [Link]: [Link]([Link]("🪄 Tools ON", True, (120,60,120)),
(520, y))
if [Link]: [Link]([Link]("🌟 Finale Unlocked", True,
(150,60,150)), (660, y))
if [Link] and [Link] in ("quiz","finale"):
[Link]([Link], (255,223,247), self.hint_button, border_radius=15)
[Link]([Link]("Hint", True, (0,0,0)), (self.hint_button.x+42,
self.hint_button.y+10))
if [Link] == "hub":
self._draw_hub_two_columns()
elif [Link] == "quiz":
self._draw_quiz()
elif [Link] == "finale":
self._draw_finale()
elif [Link] == "anim":
self._draw_unlock_anim()
if [Link]:
msg, col = [Link]
surf = [Link](msg, True, col)
[Link](surf, ([Link]//2 - surf.get_width()//2, [Link] - 60))
def _draw_hub_two_columns(self):
self.hub_cards = []
left_x, right_x = [Link]//2 - 480, [Link]//2 + 40
y0, gap = 150, 90
left, right = 0, 0
# --- UPDATED: only show levels up to completed_levels (strict, no +1) ---
max_index = 0
if self.completed_levels <= 0:
max_index = 0 # new user: only Level 6 visible
else:
max_index = min(self.completed_levels, len(GRADE_ORDER) - 1)
for idx in range(max_index + 1):
ch = GRADE_ORDER[idx]
x, y = (left_x, y0 + left*gap) if idx % 2 == 0 else (right_x, y0 + right*gap)
if idx % 2 == 0: left += 1
else: right += 1
rect = [Link](x, y, 440, 70)
locked = idx > self.completed_levels
color = (220,200,200) if locked else ch["color"]
[Link]([Link], color, rect, border_radius=16)
text = f"{idx+1}. {ch['name']} — Grade {ch['grade']}"
[Link]([Link](text, True, (0,0,0)), (rect.x+20, rect.y+20))
self.hub_cards.append((rect, "level", idx))
# Finale card (unchanged)
if self.completed_levels >= len(GRADE_ORDER) or [Link] >=
FINALE_THRESHOLD:
rect = [Link]([Link]//2 - 220, y0 + max(left,right)*gap + 30, 440, 70)
[Link]([Link], FINALE["color"], rect, border_radius=16)
label = "Finale: Hello Kitty (Unlocked)" if [Link] else "Finale: Hello Kitty
(Locked)"
[Link]([Link](label, True, (0,0,0)), (rect.x+20, rect.y+20))
self.hub_cards.append((rect, "finale", -1))
def _draw_quiz(self):
if not (0 <= self.q_index < len([Link])):
empty = [Link]("No questions found.", True, (120,60,120))
[Link](empty, ([Link]//2 - empty.get_width()//2, 220))
return
q = [Link][self.q_index]
qsurf = [Link](q["q"], True, (0,0,0))
[Link](qsurf, ([Link]//2 - qsurf.get_width()//2, 180))
self.option_rects = []
for i, opt in enumerate(q["options"]):
r = [Link]([Link]//2 - 300, 240 + i*70, 600, 50)
fill = (220,220,220) if i in self.disabled_options else self.active_host["color"]
[Link]([Link], fill, r, border_radius=14)
[Link]([Link](opt, True, (0,0,0)), (r.x+16, r.y+12))
self.option_rects.append(r)
ptxt = [Link](f"Q {self.q_index+1}/{len([Link])}", True,
(90,70,110))
[Link](ptxt, ([Link] - 130, 115))
def _draw_finale(self):
q_done = self.q_index >= len([Link])
area = [Link](70, 160, [Link]-140, [Link]-260)
[Link]([Link], (255,230,240), area, border_radius=24)
[Link]([Link], FINALE["color"], area, 4, border_radius=24)
if q_done:
msg = [Link]("✨ Finale complete! Case closed!", True, (120,60,160))
[Link](msg, ([Link] - msg.get_width()//2, [Link]))
return
q = [Link][self.q_index]
qsurf = [Link](q["q"], True, (60,30,60))
[Link](qsurf, ([Link]//2 - qsurf.get_width()//2, area.y + 50))
self.option_rects = []
for i, opt in enumerate(q["options"]):
r = [Link]([Link]//2 - 250, area.y + 120 + i*70, 500, 50)
fill = (220,220,220) if i in self.disabled_options else FINALE["color"]
[Link]([Link], fill, r, border_radius=14)
[Link]([Link](opt, True, (0,0,0)), (r.x+20, r.y+10))
self.option_rects.append(r)
def _play_unlock_animation(self, next_name):
[Link] = "anim"
self.anim_t0 = [Link]()
self.anim_text = f"✨ Unlocked: {next_name}!"
def _draw_unlock_anim(self):
overlay = [Link](([Link], [Link]), [Link])
[Link]((0,0,0,120))
[Link](overlay, (0,0))
box = [Link]([Link]//2 - 260, [Link]//2 - 60, 520, 120)
[Link]([Link], (255,255,255), box, border_radius=20)
[Link]([Link], (255,182,193), box, 6, border_radius=20)
msg = [Link]("Unlocked!", True, (200,80,140))
sub = [Link](self.anim_text, True, (80,40,80))
[Link](msg, ([Link] - msg.get_width()//2, box.y + 18))
[Link](sub, ([Link] - sub.get_width()//2, box.y + 70))
6. Learning Centre — learning_ui.py
# learning_centre/learning_ui.py
# ------------------------------------------------------------
# Pastel Learning Centre — with Progress Dashboard + Coins
# ------------------------------------------------------------
# Notes:
# - Existing screens kept intact. New features are marked with "# --- NEW:"
# - Uses DB: CASE_FILE_0X67 (tables: grades, subjects, lessons, players, progress)
# - No emojis to avoid missing-font rectangles
# ------------------------------------------------------------
import pygame
import time
import [Link]
from [Link] import Error
from [Link] import Button, WHITE, PINK, LIGHT_PINK, DARK_PINK
# ------------------------------------------------------------
# DB connection (UNCHANGED)
# ------------------------------------------------------------
def get_connection():
try:
return [Link](
host="localhost",
user="root",
password="Tikku17@#$",
database="CASE_FILE_0X67",
autocommit=True,
except Error as e:
print(f"DB error: {e}")
return None
class LearningUI:
def __init__(self, screen, width, height, save=None):
[Link] = screen
[Link] = width
[Link] = height
# fonts
self.font_big = [Link]("comicsansms", 38, bold=True)
self.font_small = [Link]("comicsansms", 22)
# navigation state (UNCHANGED)
[Link] = "menu" # menu | grade | subject | topic | lesson | done | progress
self.selected_grade = None
self.selected_subject = None
self.selected_lesson = None
[Link] = []
[Link] = True
self.db_error = None
# --- NEW: username & coins header
[Link] = "Player1"
if save:
try:
# accept dict-like or SaveData
[Link] = [Link]("username", "Player1") if isinstance(save, dict) \
else getattr(save, "load", lambda: {})().get("username", "Player1")
except Exception:
[Link] = "Player1"
self.session_start_ts = None # lesson timer
# ------------------------------------------------------------
# DB helpers (UNCHANGED APIs)
# ------------------------------------------------------------
def query(self, sql, params=None, dictionary=True):
conn = cur = None
try:
conn = get_connection()
if not conn:
raise Error("No DB connection")
cur = [Link](dictionary=dictionary, buffered=True)
[Link](sql, params or ())
rows = [Link]()
self.db_error = None
return rows
except Error as e:
self.db_error = str(e)
print("SQL error:", e)
return []
finally:
if cur: [Link]()
if conn: [Link]()
def execute(self, sql, params=None):
conn = cur = None
try:
conn = get_connection()
if not conn:
raise Error("No DB connection")
cur = [Link](buffered=True)
[Link](sql, params or ())
[Link]()
self.db_error = None
return [Link]
except Error as e:
self.db_error = str(e)
print("Exec error:", e)
return 0
finally:
if cur: [Link]()
if conn: [Link]()
# --- NEW: player coins helpers ------------------------------------------
def ensure_player_row(self):
# create row if not exists
got = [Link]("SELECT id FROM players WHERE username=%s",
([Link],))
if not got:
[Link]("INSERT INTO players (username) VALUES (%s)",
([Link],))
def get_player_coins(self):
self.ensure_player_row()
row = [Link]("SELECT coins FROM players WHERE username=%s",
([Link],))
return (row[0]["coins"] if row else 0)
def add_player_coins(self, delta):
self.ensure_player_row()
[Link]("UPDATE players SET coins = coins + %s WHERE username=%s",
(delta, [Link]))
# ------------------------------------------------------------
# drawing
# ------------------------------------------------------------
def draw_bg(self):
# pastel vertical gradient
for y in range([Link]):
r = 255
g = max(225, 248 - y // 20)
b = max(235, 252 - y // 25)
[Link]([Link], (r, g, b), (0, y), ([Link], y))
# --- NEW: small header strip with username + coins
def draw_header(self):
bar_h = 52
[Link]([Link], (255, 230, 240), (0, 0, [Link], bar_h))
name_txt = self.font_small.render(f"User: {[Link]}", True, (120, 40, 80))
coins = self.get_player_coins()
coins_txt = self.font_small.render(f"Coins: {coins}", True, (120, 40, 80))
[Link](name_txt, (20, 14))
[Link](coins_txt, ([Link] - coins_txt.get_width() - 20, 14))
# events
def handle_event(self, event):
if [Link] == [Link]:
[Link] = False
elif [Link] == [Link]:
pos = [Link].get_pos()
for b in [Link]:
[Link](pos)
# main draw dispatcher
def draw(self):
self.draw_bg()
self.draw_header() # --- NEW
if [Link] == "menu":
self.draw_main_learning_menu()
elif [Link] == "grade":
self.draw_grade()
elif [Link] == "subject":
self.draw_subject()
elif [Link] == "topic":
self.draw_topic()
elif [Link] == "lesson":
self.draw_lesson()
elif [Link] == "done":
self.draw_done()
elif [Link] == "progress":
self.draw_progress()
[Link]()
# ------------------------------------------------------------
# MAIN MENU (UNCHANGED + small polish)
# ------------------------------------------------------------
def draw_main_learning_menu(self):
[Link]()
title = self.font_big.render("Learning Centre", True, DARK_PINK)
[Link](title, ([Link] // 2 - title.get_width() // 2, 100))
btn1 = Button("Select Grade", [Link] // 2 - 160, 250, 320, 70,
color=PINK, action=lambda: self.change_stage("grade"))
btn2 = Button("View Progress", [Link] // 2 - 160, 350, 320, 70,
color=LIGHT_PINK, action=lambda: self.change_stage("progress"))
back = Button("Main Menu", [Link] // 2 - 140, 480, 280, 60,
color=LIGHT_PINK, action=self.quit_to_menu)
for b in [btn1, btn2, back]:
[Link](b); [Link]([Link])
def change_stage(self, s):
[Link] = s
# ------------------------------------------------------------
# GRADE (UNCHANGED)
# ------------------------------------------------------------
def draw_grade(self):
[Link]()
title = self.font_big.render("Select Your Grade", True, DARK_PINK)
[Link](title, ([Link] // 2 - title.get_width() // 2, 80))
grades = [Link]("SELECT id, name FROM grades ORDER BY id ASC")
if not grades:
self._draw_error("No grades found.")
else:
for i, g in enumerate(grades):
btn = Button(g["name"], [Link] // 2 - 150, 180 + i * 72, 300, 56,
color=PINK, action=lambda g=g: self.select_grade(g))
[Link](btn); [Link]([Link])
back = Button("Back", 40, [Link] - 80, 200, 56,
color=LIGHT_PINK, action=lambda: self.change_stage("menu"))
[Link](back); [Link]([Link])
def select_grade(self, grade_row):
self.selected_grade = grade_row
[Link] = "subject"
# ------------------------------------------------------------
# SUBJECT (UNCHANGED)
# ------------------------------------------------------------
def draw_subject(self):
[Link]()
header = f"{self.selected_grade['name']} — Subjects"
title = self.font_big.render(header, True, DARK_PINK)
[Link](title, ([Link] // 2 - title.get_width() // 2, 70))
subjects = [Link]("SELECT id, name FROM subjects WHERE grade_id=%s
ORDER BY name ASC",
(self.selected_grade["id"],))
if not subjects:
self._draw_error("No subjects found.")
else:
for i, s in enumerate(subjects):
btn = Button(s["name"], [Link] // 2 - 150, 180 + i * 72, 300, 56,
color=PINK, action=lambda s=s: self.select_subject(s))
[Link](btn); [Link]([Link])
back = Button("Back", 40, [Link] - 80, 200, 56,
color=LIGHT_PINK, action=self.go_back_grade)
[Link](back); [Link]([Link])
def go_back_grade(self):
[Link] = "grade"
def select_subject(self, subject_row):
self.selected_subject = subject_row
[Link] = "topic"
# ------------------------------------------------------------
# TOPIC (UNCHANGED)
# ------------------------------------------------------------
def draw_topic(self):
[Link]()
header = f"{self.selected_subject['name']} — Topics"
title = self.font_big.render(header, True, DARK_PINK)
[Link](title, ([Link] // 2 - title.get_width() // 2, 60))
lessons = [Link](
"SELECT id, title, description, content FROM lessons WHERE subject_id=%s
ORDER BY id ASC",
(self.selected_subject["id"],),
y = 140
if not lessons:
self._draw_error("No topics found.")
else:
for l in lessons:
label = f"{l['title']} — {l['description']}"
btn = Button(label, 80, y, [Link] - 160, 56, color=PINK,
action=lambda l=l: self.select_topic(l))
[Link](btn); [Link]([Link])
y += 68
back = Button("Back", 40, [Link] - 80, 200, 56,
color=LIGHT_PINK, action=self.go_back_subject)
[Link](back); [Link]([Link])
def go_back_subject(self):
[Link] = "subject"
def select_topic(self, lesson_row):
self.selected_lesson = lesson_row
self.session_start_ts = [Link]() # --- NEW: start timer
[Link] = "lesson"
# ------------------------------------------------------------
# LESSON (UNCHANGED + completion writes)
# ------------------------------------------------------------
def draw_lesson(self):
[Link]()
if not self.selected_lesson:
self._draw_error("No lesson selected.")
return
title = self.font_big.render(self.selected_lesson['title'], True, DARK_PINK)
[Link](title, ([Link] // 2 - title.get_width() // 2, 60))
desc = self.font_small.render(self.selected_lesson["description"], True, (70, 70, 70))
[Link](desc, (80, 118))
self._draw_wrapped_text(self.selected_lesson["content"], 80, 160, self.font_small,
[Link] - 160)
complete = Button("Mark Topic Completed", [Link] // 2 - 200, [Link] - 150,
400, 56,
color=PINK, action=self.complete_topic)
back = Button("Back", 40, [Link] - 80, 200, 56,
color=LIGHT_PINK, action=self.go_back_to_topics)
for b in [complete, back]:
[Link](b); [Link]([Link])
def complete_topic(self):
# --- NEW: write/aggregate progress + award coins
elapsed = 0
if self.session_start_ts:
elapsed = max(0, int([Link]() - self.session_start_ts))
self.session_start_ts = None
grade_name = self.selected_grade["name"]
subject_name = self.selected_subject["name"]
topic_title = self.selected_lesson["title"]
# Try UPDATE first
updated = [Link]("""
UPDATE progress
SET completed=1, time_spent=time_spent + %s, date_completed=NOW()
WHERE username=%s AND grade=%s AND subject=%s AND lesson_title=%s
""", (elapsed, [Link], self._grade_number(grade_name), subject_name,
topic_title))
if updated == 0:
# Insert if no existing row
[Link]("""
INSERT INTO progress (username, grade, subject, lesson_title, completed,
time_spent)
VALUES (%s, %s, %s, %s, 1, %s)
""", ([Link], self._grade_number(grade_name), subject_name, topic_title,
elapsed))
# Coins: +5 per topic (tweak if you like)
self.add_player_coins(5)
# Subject-complete bonus: if all topics of the subject are completed at least once
if self._is_subject_fully_completed(self.selected_grade["id"],
self.selected_subject["id"]):
self.add_player_coins(20)
[Link] = "done"
def go_back_to_topics(self):
[Link] = "topic"
# --- NEW: helper to detect subject full completion
def _is_subject_fully_completed(self, grade_id, subject_id):
total = [Link]("SELECT COUNT(*) AS c FROM lessons WHERE subject_id=
%s", (subject_id,))
if not total or total[0]["c"] == 0:
return False
# subject name + grade num
srow = [Link]("SELECT name FROM subjects WHERE id=%s", (subject_id,))
grow = [Link]("SELECT name FROM grades WHERE id=%s", (grade_id,))
if not srow or not grow:
return False
subj = srow[0]["name"]
grdnum = self._grade_number(grow[0]["name"])
done = [Link]("""
SELECT COUNT(*) AS c
FROM progress
WHERE username=%s AND grade=%s AND subject=%s AND completed=1
""", ([Link], grdnum, subj))
return (done and done[0]["c"] == total[0]["c"])
# --- NEW: map "Grade 6" -> 6
def _grade_number(self, grade_name):
try:
return int(str(grade_name).split()[-1])
except Exception:
return 0
# ------------------------------------------------------------
# DONE (UNCHANGED + encouragement)
# ------------------------------------------------------------
def draw_done(self):
[Link]()
msg = self.font_big.render("Topic Completed", True, DARK_PINK)
[Link](msg, ([Link] // 2 - msg.get_width() // 2, 200))
# short encouragement, based on total completions
total = [Link]("""
SELECT COUNT(*) AS c FROM progress
WHERE username=%s AND completed=1
""", ([Link],))
c = total[0]["c"] if total else 0
if c == 0:
tip = "Great start. Keep going!"
elif c < 5:
tip = "Nice momentum. You're getting there!"
elif c < 15:
tip = "Strong streak. Proud of you!"
else:
tip = "Amazing consistency. You're unstoppable!"
tip_s = self.font_small.render(tip, True, (90, 90, 90))
[Link](tip_s, ([Link] // 2 - tip_s.get_width() // 2, 260))
done = Button("Back to Topics", [Link] // 2 - 190, 360, 380, 56, color=PINK,
action=self.go_back_to_topics)
home = Button("Main Menu", [Link] // 2 - 160, 430, 320, 56,
color=LIGHT_PINK, action=self.quit_to_menu)
for b in [done, home]:
[Link](b); [Link]([Link])
# ------------------------------------------------------------
# # ------------------------------------------------------------
# 🌸 PROGRESS DASHBOARD (UPDATED WITH CLEAN ALIGNMENT)
# ------------------------------------------------------------
def draw_progress(self):
[Link]()
title = self.font_big.render("📊 Your Learning Progress", True, DARK_PINK)
[Link](title, ([Link] // 2 - title.get_width() // 2, 50))
progress_data = [Link]("""
SELECT subject, COUNT(*) AS completed
FROM progress
WHERE username=%s AND completed=1
GROUP BY subject
ORDER BY subject
""", ([Link],))
total = sum(p["completed"] for p in progress_data)
total_msg = self.font_small.render(f"Total Topics Completed: {total}", True, (70,
70, 70))
[Link](total_msg, (80, 120))
# 🎨 Bar chart area setup
bar_x, bar_y = 100, 170
bar_width = 600
max_bar_height = 35
gap = 20
if not progress_data:
tip = self.font_small.render("No progress yet. Start your journey! 🌟", True, (130,
130, 130))
[Link](tip, ([Link] // 2 - tip.get_width() // 2, [Link] // 2))
else:
max_completed = max(p["completed"] for p in progress_data)
total_height = len(progress_data) * (max_bar_height + gap)
y_offset = ([Link] - total_height) // 2 # Center vertically
# Alternate soft pastel backgrounds for readability
row_colors = [(255, 240, 245), (255, 230, 250)] # pastel pinks
for i, p in enumerate(progress_data):
subject = p["subject"]
completed = p["completed"]
percentage = int((completed / max_completed) * 100) if max_completed else 0
bg_rect = [Link](bar_x - 40, y_offset + i * (max_bar_height + gap) - 5,
bar_width + 300, max_bar_height + 10)
[Link]([Link], row_colors[i % 2], bg_rect, border_radius=10)
# Bar length
filled_width = int((completed / max_completed) * bar_width)
bar_rect = [Link](bar_x, y_offset + i * (max_bar_height + gap),
filled_width, max_bar_height)
[Link]([Link], (255, 182, 193), bar_rect, border_radius=10)
# Subject name
subj_text = self.font_small.render(f"{subject}", True, (80, 0, 60))
[Link](subj_text, (bar_x - 90, y_offset + i * (max_bar_height + gap)))
# Completion %
pct_text = self.font_small.render(f"{percentage}%", True, (100, 50, 100))
[Link](pct_text, (bar_x + bar_width + 20, y_offset + i *
(max_bar_height + gap)))
# 💰 Coins Display
player_data = [Link]("SELECT coins FROM players WHERE username=%s",
([Link],))
coins = player_data[0]["coins"] if player_data else 0
coin_text = self.font_small.render(f"Current Coins: {coins}", True, (100, 70, 90))
[Link](coin_text, (80, [Link] - 140))
# 🩷 Encouragement Message
if total == 0:
msg = "Let's start learning together!"
elif total < 5:
msg = "You’re doing great! Keep going!"
elif total < 10:
msg = "Impressive progress 🌟"
else:
msg = "Super learner alert! 🚀"
tip = self.font_small.render(msg, True, (90, 90, 90))
[Link](tip, (80, [Link] - 100))
# Back button
back = Button("⬅ Back", 50, [Link] - 70, 200, 50,
color=LIGHT_PINK, action=lambda: self.change_stage("menu"))
[Link](back)
[Link]([Link])
# ------------------------------------------------------------
# helpers (UNCHANGED)
# ------------------------------------------------------------
def _draw_error(self, message, y=150):
surf = self.font_small.render(message, True, (120, 0, 0))
[Link](surf, (80, y))
def _draw_wrapped_text(self, text, x, y, font, max_width):
words = (text or "").split()
lines, current = [], ""
for w in words:
test = (current + " " + w).strip()
if [Link](test)[0] <= max_width:
current = test
else:
if current: [Link](current)
current = w
if current: [Link](current)
for i, line in enumerate(lines):
surf = [Link](line, True, (60, 60, 60))
[Link](surf, (x, y + i * 28))
# ------------------------------------------------------------
# main loop (UNCHANGED but returns a value for SceneManager)
# ------------------------------------------------------------
def run(self):
clock = [Link]()
[Link] = True
result = "exit"
while [Link]:
for event in [Link]():
self.handle_event(event)
[Link]()
[Link](60)
# when quit_to_menu called, we jump back to main menu
return result
def quit_to_menu(self):
# SceneManager will set the menu after this returns
[Link] = False
7. Intro System (run_intro + all scenes)
import pygame, cv2, sys, time, [Link], tkinter as tk, os
from tkinter import messagebox
from PIL import Image, ImageTk
from [Link] import *
# -------------------
# CONFIG
# -------------------
DB_NAME = "CASE_FILE_0X67"
WIDTH, HEIGHT = 1280, 720
FPS = 30
# Media files
BONANZA_BG = "bonanza_bg.mp4"
OFFICE_BG = "[Link]"
HKITTY_IMG = "[Link]"
SCARY_VIDEO = "scary.mp4"
SUSPECTS_BG = "suspects_bg.png"
# -------------------
# FONT HELPER
# -------------------
def get_font(size, bold=False):
return [Link]("Times New Roman", size, bold)
# -------------------
# DATABASE
# -------------------
def db_connect():
try:
conn = [Link](
host="localhost",
user="root",
passwd="Tikku17@#$",
database=DB_NAME,
auth_plugin="caching_sha2_password"
return conn
except Exception as e:
print("❌ Database error:", e)
return None
def ensure_users_table():
conn = db_connect()
if not conn:
return
cur = [Link]()
[Link]("""
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(100) UNIQUE,
password_hash VARCHAR(255),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
""")
[Link]()
[Link]()
def save_user(username, password):
conn = db_connect()
if not conn:
return False
try:
cur = [Link]()
[Link]("INSERT IGNORE INTO users (username, password_hash) VALUES
(%s, %s)", (username, password))
[Link]()
[Link]()
return True
except Exception as e:
print("DB insert error:", e)
return False
# -------------------
# HELPERS
# -------------------
def play_video_with_overlay(filename, title_text=None, middle_text=None,
duration=None):
cap = [Link](filename)
if not [Link]():
print("⚠ Cannot open video:", filename)
return
screen = [Link].get_surface()
clock = [Link]()
start_time = [Link]()
title_font = get_font(68, True)
font = get_font(36)
while [Link]():
ret, frame = [Link]()
if not ret:
break
frame = [Link](frame, (WIDTH, HEIGHT))
frame = [Link](frame, cv2.COLOR_BGR2RGB)
surf = [Link].make_surface([Link](0, 1))
[Link](surf, (0, 0))
# overlay text
if title_text:
title = title_font.render(title_text, True, (255, 255, 255))
[Link](title, (WIDTH // 2 - title.get_width() // 2, 80))
if middle_text:
msg = [Link](middle_text, True, (255, 255, 255))
[Link](msg, (WIDTH // 2 - msg.get_width() // 2, HEIGHT // 2))
[Link]()
for e in [Link]():
if [Link] == QUIT:
[Link]()
if duration and [Link]() - start_time > duration:
break
[Link](FPS)
[Link]()
def typewriter(surface, text, pos, font, color, speed=35, line_spacing=40):
x, y = pos
for line in [Link]("\n"):
rendered = ""
for ch in line:
rendered += ch
txt = [Link](rendered, True, color)
[Link](txt, (x, y))
[Link]()
[Link](speed)
y += line_spacing
# -------------------
# SCENES
# -------------------
def scene1_system_connection(screen):
play_video_with_overlay(
BONANZA_BG,
title_text="BRAINSTORM BONANZA",
middle_text="Connecting to SANRIO DETECTIVE NETWORK...",
duration=8
def scene2_office_check(screen):
bg = [Link](OFFICE_BG).convert()
bg = [Link](bg, (WIDTH, HEIGHT))
[Link](bg, (0, 0))
font = get_font(36, True)
top_text = "SUCCESSFULLY CONNECTED — CHECKING MISSING
REPORTS..."
overlay = [Link]((WIDTH, 60))
[Link]((0, 0, 0))
overlay.set_alpha(180)
[Link](overlay, (0, 0))
text_surf = [Link](top_text, True, (255, 255, 255))
[Link](text_surf, (WIDTH // 2 - text_surf.get_width() // 2, 10))
[Link]()
[Link](1500)
def scene3_kitty_alert(screen):
bg = [Link](HKITTY_IMG).convert_alpha()
bg = [Link](bg, (WIDTH, HEIGHT))
clock = [Link]()
font = get_font(40, True)
button_font = get_font(30, True)
button = [Link](WIDTH // 2 - 160, HEIGHT - 120, 320, 70)
flash = True
while True:
for e in [Link]():
if [Link] == QUIT:
[Link]()
if [Link] == MOUSEBUTTONDOWN and [Link]([Link]):
return True # ✅ Return something to continue
[Link](bg, (0, 0))
if flash:
alert = [Link]("⚠ ALERT! HELLO KITTY REPORTED MISSING ⚠",
True, (255, 0, 0))
[Link](alert, (WIDTH // 2 - alert.get_width() // 2, 80))
flash = not flash
[Link](screen, (255, 182, 193), button, border_radius=35)
txt = button_font.render("Investigate Case 🩷", True, (0, 0, 0))
[Link](txt, (button.x + 45, button.y + 20))
[Link]()
[Link](2)
def scene4_suspects(screen):
clock = [Link]()
font_title = get_font(48, True)
font_btn = get_font(26, True)
font_hint = get_font(26)
small = get_font(22)
# Load background safely
try:
bg = [Link]("suspects_bg.png").convert()
bg = [Link](bg, (WIDTH, HEIGHT))
except Exception as e:
print("⚠ Background load failed:", e)
bg = None
# Suspect definitions (name, short description)
suspects = [
("Chococat", "Smart and shy, loves science!"),
("Kuromi", "Cheeky troublemaker, but fun!"),
("Cinnamoroll", "Loves to fly and is super cuddly!"),
("Pompompurin", "Chill and pudding-loving pup!"),
("Hello Kitty", "Kind-hearted and curious!"),
("My Melody", "Always sweet and gentle!"),
("Badtz-Maru", "Sassy and cool penguin!"),
("Pochacco", "Sporty dog who loves carrots!"),
("Keroppi", "Happy frog flying the kite!"),
]
# Create a grid of buttons (3x3)
cols, rows = 3, 3
grid_w, grid_h = 900, 420
grid_x = (WIDTH - grid_w) // 2
grid_y = 230 # shifted slightly down for spacing
pad = 20
btn_w = (grid_w - pad * (cols - 1)) // cols
btn_h = (grid_h - pad * (rows - 1)) // rows
buttons = []
for i, (name, desc) in enumerate(suspects):
r = i // cols
c = i % cols
x = grid_x + c * (btn_w + pad)
y = grid_y + r * (btn_h + pad)
[Link]({"rect": [Link](x, y, btn_w, btn_h), "name": name, "desc":
desc})
# Continue button
cont_rect = [Link](WIDTH // 2 - 200, HEIGHT - 100, 400, 60)
# Toast message (shows when clicking a suspect)
toast = ""
toast_timer = 0
while True:
for e in [Link]():
if [Link] == QUIT:
[Link]()
[Link]()
if [Link] == MOUSEBUTTONDOWN:
pos = [Link].get_pos()
for b in buttons:
if b["rect"].collidepoint(pos):
toast = f"{b['name']}: {b['desc']}"
toast_timer = 1800
break
if cont_rect.collidepoint(pos):
return True
# Draw background
if bg:
[Link](bg, (0, 0))
veil = [Link]((WIDTH, HEIGHT), [Link])
[Link]((0, 0, 0, 80))
[Link](veil, (0, 0))
else:
[Link]((255, 240, 246))
# Title
title = font_title.render("Suspect Board ♀️", True, (255, 255, 255))
title_shadow = font_title.render("Suspect Board ♀️", True, (199, 21, 133))
[Link](title_shadow, (WIDTH // 2 - title.get_width() // 2 + 2, 95 + 2))
[Link](title, (WIDTH // 2 - title.get_width() // 2, 95))
# Hint (aligned right under title)
hint = font_hint.render(
"Tap a suspect to view their vibe, then start your investigation!",
True,
(255, 255, 255)
hint_bg = [Link]((hint.get_width() + 40, hint.get_height() + 10),
[Link])
hint_bg.fill((0, 0, 0, 120))
hint_x = WIDTH // 2 - hint.get_width() // 2
hint_y = 165
[Link](hint_bg, (hint_x - 20, hint_y - 5))
[Link](hint, (hint_x, hint_y))
# Buttons (suspects)
mouse = [Link].get_pos()
for b in buttons:
hovered = b["rect"].collidepoint(mouse)
base = (255, 182, 193) if hovered else (255, 160, 190)
[Link](screen, base, b["rect"], border_radius=20)
[Link](screen, (255, 110, 160), b["rect"], width=2, border_radius=20)
label = font_btn.render(b["name"], True, (60, 20, 40))
[Link](label, (b["rect"].centerx - label.get_width() // 2,
b["rect"].centery - label.get_height() // 2))
# Continue button
[Link](screen, (255, 105, 180), cont_rect, border_radius=28)
[Link](screen, (255, 255, 255), cont_rect, width=3, border_radius=28)
cont_lbl = font_btn.render("Start Investigation ➜", True, (255, 255, 255))
[Link](cont_lbl, (cont_rect.centerx - cont_lbl.get_width() // 2,
cont_rect.centery - cont_lbl.get_height() // 2))
# Toast popup when a suspect is clicked
dt = [Link](60)
if toast_timer > 0:
toast_timer -= dt
tw = min(900, WIDTH - 80)
tx = (WIDTH - tw) // 2
ty = grid_y - 70
[Link](screen, (0, 0, 0, 200), (tx, ty, tw, 50), border_radius=16)
txt = [Link](toast, True, (255, 255, 255))
[Link](txt, (tx + 20, ty + 14))
[Link]()
def scene5_scary_transition(screen):
play_video_with_overlay(SCARY_VIDEO, title_text="⚠ WARNING ⚠",
middle_text="UNKNOWN PRESENCE DETECTED...", duration=5)
def scene6_terms(screen):
[Link]((0, 0, 0))
font = get_font(30)
typewriter(screen,
"👻 Beware, young detective...\nOnce you start, there's no going back.\nSolve
the quiz, or the truth stays buried...",
(200, 250), font, (255, 0, 0))
[Link](500)
green_btn = [Link](WIDTH // 2 - 220, HEIGHT - 180, 200, 70)
red_btn = [Link](WIDTH // 2 + 40, HEIGHT - 180, 200, 70)
font_b = get_font(26, True)
while True:
for e in [Link]():
if [Link] == QUIT:
[Link]()
if [Link] == MOUSEBUTTONDOWN:
if green_btn.collidepoint([Link]): return True
if red_btn.collidepoint([Link]):
[Link]()
[Link]()
[Link](screen, (0, 180, 0), green_btn, border_radius=30)
[Link](screen, (180, 0, 0), red_btn, border_radius=30)
[Link](font_b.render("Accept & Continue", True, (255, 255, 255)), (green_btn.x
+ 15, green_btn.y + 20))
[Link](font_b.render("Decline & Run Away", True, (255, 255, 255)), (red_btn.x
+ 10, red_btn.y + 20))
[Link]()
def scene7_login(screen):
ensure_users_table()
username, password = "", ""
font = get_font(30)
active = "user"
while True:
[Link]((0, 0, 0))
title = [Link]("🔐 DETECTIVE LOGIN REQUIRED", True, (255, 182, 193))
[Link](title, (WIDTH // 2 - title.get_width() // 2, 150))
user_rect = [Link](WIDTH // 2 - 150, 300, 300, 50)
pwd_rect = [Link](WIDTH // 2 - 150, 380, 300, 50)
[Link](screen, (255, 255, 255), user_rect, 2)
[Link](screen, (255, 255, 255), pwd_rect, 2)
[Link]([Link]("Username:", True, (255, 255, 255)), (user_rect.x - 150,
305))
[Link]([Link]("Password:", True, (255, 255, 255)), (pwd_rect.x - 150, 385))
[Link]([Link](username, True, (255, 255, 255)), (user_rect.x + 10, 305))
[Link]([Link]("*" * len(password), True, (255, 255, 255)), (pwd_rect.x +
10, 385))
btn = [Link](WIDTH // 2 - 100, 480, 200, 60)
[Link](screen, (255, 182, 193), btn, border_radius=25)
[Link]([Link]("Login", True, (0, 0, 0)), (btn.x + 70, btn.y + 15))
[Link]()
for e in [Link]():
if [Link] == QUIT:
[Link]()
if [Link] == MOUSEBUTTONDOWN:
if user_rect.collidepoint([Link]): active = "user"
elif pwd_rect.collidepoint([Link]): active = "pass"
elif [Link]([Link]):
if username and password:
save_user(username, password)
return True
if [Link] == KEYDOWN:
if [Link] == K_TAB:
active = "pass" if active == "user" else "user"
elif [Link] == K_BACKSPACE:
if active == "user": username = username[:-1]
else: password = password[:-1]
elif [Link] == K_RETURN:
if username and password:
save_user(username, password)
return True
else:
if active == "user": username += [Link]
else: password += [Link]
def scene8_main_menu(screen):
import main
[Link]() # ✅ Connects cleanly
# -------------------
# MAIN FLOW
# -------------------
def run_intro():
[Link]()
screen = [Link].set_mode((WIDTH, HEIGHT))
[Link].set_caption("Case 0X67: Kitty Missing")
sequence = [
scene1_system_connection,
scene2_office_check,
scene3_kitty_alert,
scene4_suspects,
scene5_scary_transition,
scene6_terms,
scene7_login,
scene8_main_menu
for func in sequence:
print(f"🎬 Running: {func.__name__}")
res = func(screen)
if res is False or res == "quit":
break
[Link]()
if __name__ == "__main__":
run_intro()
8. Ending Cinematic — [Link]
import pygame
import cv2
import time
import random
class EndingScene:
def __init__(self, screen, width, height, on_complete=None):
[Link] = screen
[Link] = width
[Link] = height
self.on_complete = on_complete
# 🎬 Video config
self.video_path = "assets/videos/ending.mp4"
[Link] = None
self.frame_surf = None
[Link] = True
self.glitch_phase = False
[Link] = False
self.start_time = [Link]()
# Fonts
[Link]()
self.font_big = [Link]("Impact", 72)
self.font_small = [Link]("Comic Sans MS", 32)
# Timing
self.glitch_trigger_time = 12.0 # seconds to start glitch
self.teaser_display_time = 5.0 # seconds after glitch
# Video setup
try:
[Link] = [Link](self.video_path)
except Exception:
[Link] = None
# ─────────────────────────────
# Update Loop
# ─────────────────────────────
def update(self):
if [Link]:
return
now = [Link]()
elapsed = now - self.start_time
# Glitch time!
if not self.glitch_phase and elapsed > self.glitch_trigger_time:
self.glitch_phase = True
self._trigger_glitch()
# End completely
if self.glitch_phase and elapsed > (self.glitch_trigger_time +
self.teaser_display_time):
[Link] = True
if self.on_complete:
self.on_complete()
# ─────────────────────────────
# Draw Loop
# ─────────────────────────────
def draw(self):
if [Link] and [Link] and not self.glitch_phase:
ret, frame = [Link]()
if not ret:
[Link] = False
else:
frame = [Link](frame, ([Link], [Link]))
frame = [Link](frame, cv2.COLOR_BGR2RGB)
frame = [Link].make_surface([Link](0, 1))
self.frame_surf = frame
if self.frame_surf:
[Link](self.frame_surf, (0, 0))
else:
[Link]((255, 200, 230))
if self.glitch_phase:
self._draw_glitch_overlay()
[Link]()
# ─────────────────────────────
# Event Handling
# ─────────────────────────────
def handle_event(self, event):
if [Link] == [Link] or [Link] ==
[Link]:
if not self.glitch_phase:
# skip to glitch manually
self.glitch_phase = True
self._trigger_glitch()
elif [Link]:
if self.on_complete:
self.on_complete()
# ─────────────────────────────
# Glitch Visual
# ─────────────────────────────
def _trigger_glitch(self):
self.glitch_sound_played = False
self.glitch_start = [Link]()
def _draw_glitch_overlay(self):
t = [Link]() - self.glitch_start
# Flash effect
flash = (abs(int(255 * (0.5 + 0.5 * ((t * 20) % 1)))), 0, 0)
overlay = [Link](([Link], [Link]))
[Link](flash)
[Link](overlay, (0, 0), special_flags=pygame.BLEND_RGBA_ADD)
# Static noise
for _ in range(400):
x = int(([Link]) * (0.5 + 0.5 * ([Link].get_ticks() % 100 / 100)))
y = int(([Link]) * (0.5 + 0.5 * ([Link].get_ticks() % 200 / 200)))
color = ([Link](150, 255), 0, 0)
[Link]([Link], color, (x, y), 2)
# Blood red text
msg = "CASE 0X69"
sub = "KUROMI KIDNAPPED"
next_ = "COMING SOON..."
surf1 = self.font_big.render(msg, True, (255, 0, 0))
surf2 = self.font_big.render(sub, True, (255, 20, 20))
surf3 = self.font_small.render(next_, True, (255, 80, 80))
[Link](surf1, ([Link]//2 - surf1.get_width()//2, [Link]//2 - 140))
[Link](surf2, ([Link]//2 - surf2.get_width()//2, [Link]//2 - 60))
[Link](surf3, ([Link]//2 - surf3.get_width()//2, [Link]//2 + 40))
9. [Link] (launcher)
# [Link] — FINAL CLEAN VERSION (no minigames, fully synced)
import pathfix
import pygame, cv2, sys, json
# ---------------------------------------------------------
# Imports for main components ONLY (minigames removed)
# ---------------------------------------------------------
from learning_centre.learning_ui import LearningUI
from quiz_engine.quiz_ui import QuizUI
from ui.simple_scene import SceneBase
from [Link] import Button
SCREEN_WIDTH, SCREEN_HEIGHT = 1200, 800
FPS = 60
# ---------------------------------------------------------
# Save system (unchanged)
# ---------------------------------------------------------
class SaveData:
FILE = "data/[Link]"
def load(self):
try:
with open([Link], "r") as f:
return [Link](f)
except Exception:
return {"username": "Player1"}
def save(self, data):
with open([Link], "w") as f:
[Link](data, f)
# Shim so LearningUI can read [Link]["username"]
class _SaveShim:
def __init__(self, data: dict):
[Link] = data
# ---------------------------------------------------------
# MENU SCENE — minigames removed
# ---------------------------------------------------------
class MenuScene(SceneBase):
def __init__(self, screen, width, height, save):
super().__init__(screen, width, height)
[Link] = save
[Link] = [Link]("comicsansms", 40, bold=True)
self.next_scene = None
[Link] = []
# Safe background video loader
try:
[Link] = [Link]("menu_bg.mp4")
if not [Link]():
[Link] = None
except:
[Link] = None
self.create_buttons()
def create_buttons(self):
y_start, gap = 260, 95
# ⭐⭐ ONLY LEARNING + QUIZ NOW ⭐⭐
labels = [
("Learning Centre", "learning"),
("Quiz Adventure", "quiz"),
[Link]()
for i, (label, key) in enumerate(labels):
btn = Button(
label,
[Link]//2 - 180, y_start + i * gap,
360, 65,
(255, 140, 180), (255, 182, 193),
action=lambda k=key: self.set_scene(k)
)
[Link](btn)
def set_scene(self, key):
print(f"🌸 Switching to: {key}")
self.next_scene = key
def handle_event(self, event):
if [Link] == [Link]:
pos = [Link].get_pos()
for b in [Link]:
[Link](pos)
def update(self):
return self.next_scene
def draw(self):
# Draw background video or pastel fallback
if [Link]:
ret, frame = [Link]()
if not ret:
[Link](cv2.CAP_PROP_POS_FRAMES, 0)
ret, frame = [Link]()
if ret:
frame = [Link](frame, cv2.COLOR_BGR2RGB)
frame = [Link].make_surface([Link](0, 1))
frame = [Link](frame, ([Link], [Link]))
[Link](frame, (0, 0))
else:
[Link]((255, 248, 252))
# Title
title = [Link]("HELLO KITTY CASE 0X67 💖", True, (199, 21, 133))
[Link](title, ([Link]//2 - title.get_width()//2, 100))
# Buttons
for b in [Link]:
[Link]([Link])
# ---------------------------------------------------------
# SCENE MANAGER (clean & stable)
# ---------------------------------------------------------
class SceneManager:
def __init__(self, screen):
[Link] = screen
[Link] = SaveData()
[Link] = {}
[Link] = None
self.create_scenes()
self.change_scene("menu")
def create_scenes(self):
username = [Link]().get("username", "Player1")
[Link]["menu"] = MenuScene([Link], SCREEN_WIDTH,
SCREEN_HEIGHT, [Link])
[Link]["quiz"] = QuizUI([Link], SCREEN_WIDTH, SCREEN_HEIGHT,
username)
# ⭐ Learning Centre is special — launched manually, not registered here ⭐
def change_scene(self, key):
if key in [Link]:
print(f"🎀 SceneManager switched to: {key}")
[Link] = [Link][key]
else:
print(f"⚠️Scene '{key}' not found")
def handle_event(self, event):
if not [Link]:
return
[Link].handle_event(event)
next_scene = [Link]()
# Normal scene switching
if next_scene and next_scene in [Link]:
self.change_scene(next_scene)
# ⭐ OPEN LEARNING CENTRE ⭐
elif next_scene == "learning":
username = [Link]().get("username", "Player1")
shim = _SaveShim({"username": username})
ui = LearningUI([Link], SCREEN_WIDTH, SCREEN_HEIGHT, shim)
[Link]() # blocking
# Return to menu
self.change_scene("menu")
def draw(self):
if [Link]:
[Link]()
# ---------------------------------------------------------
# MAIN LOOP
# ---------------------------------------------------------
def main():
[Link]()
screen = [Link].set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
[Link].set_caption("Hello Kitty Detective 💖")
clock = [Link]()
manager = SceneManager(screen)
running = True
while running:
for event in [Link]():
if [Link] == [Link]:
running = False
manager.handle_event(event)
[Link]()
[Link]()
[Link](FPS)
[Link]()
[Link]()
if __name__ == "__main__":
main()
MYSQL DATABASE COMMANDS (FULL)
========================================
1. CREATE DATABASE
========================================
CREATE DATABASE IF NOT EXISTS CASE_FILE_0X67;
USE CASE_FILE_0X67;
========================================
2. SHOW ALL DATABASES
========================================
SHOW DATABASES;
========================================
3. SHOW ALL TABLES IN THE DATABASE
========================================
SHOW TABLES;
========================================
4. DESCRIBE EACH TABLE
========================================
DESCRIBE badges;
DESCRIBE daily_activity;
DESCRIBE grades;
DESCRIBE lessons;
DESCRIBE players;
DESCRIBE progress;
DESCRIBE quiz_progress;
DESCRIBE streaks;
DESCRIBE subjects;
DESCRIBE users;
========================================
5. SHOW CREATE TABLE (FULL STRUCTURE)
========================================
SHOW CREATE TABLE badges;
SHOW CREATE TABLE daily_activity;
SHOW CREATE TABLE grades;
SHOW CREATE TABLE lessons;
SHOW CREATE TABLE players;
SHOW CREATE TABLE progress;
SHOW CREATE TABLE quiz_progress;
SHOW CREATE TABLE streaks;
SHOW CREATE TABLE subjects;
SHOW CREATE TABLE users;
========================================
6. SHOW COLUMN DETAIL FOR ALL TABLES
========================================
SELECT
TABLE_NAME,
COLUMN_NAME,
COLUMN_TYPE,
IS_NULLABLE,
COLUMN_KEY,
EXTRA
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'CASE_FILE_0X67'
ORDER BY TABLE_NAME, ORDINAL_POSITION;