import tkinter as tk
from tkinter import messagebox, ttk
from PIL import Image, ImageTk
import random
import os
class SplashScreen(tk.Toplevel):
def __init__(self, parent, on_close):
super().__init__(parent)
self.parent = parent
self.on_close = on_close
self.overrideredirect(True)
self.geometry("500x200+500+300")
self.configure(bg="#2c3e50")
self.label = tk.Label(self, text="Welcome to Escape Python
Advanced",
font=("Helvetica", 18, "bold"), fg="white",
bg="#2c3e50")
self.label.pack(pady=30)
self.progress = ttk.Progressbar(self, orient="horizontal", length=400,
mode="determinate")
self.progress.pack(pady=20)
self.progress_value = 0
self.after(100, self.update_progress)
def update_progress(self):
if self.progress_value < 100:
self.progress_value += 2
self.progress['value'] = self.progress_value
self.after(60, self.update_progress)
else:
self.destroy()
self.on_close()
class InstructionScreen(tk.Toplevel):
def __init__(self, parent, on_start):
super().__init__(parent)
self.on_start = on_start
self.title("Escape Python Advanced - How to Play")
self.geometry("700x700")
self.configure(bg="#000000")
instruction_text = (
"Welcome to the Game - Escape Python Advanced!\n\n"
"In this game, you will go through 3 levels, each based on topics
from your Python Advanced course.\n\n"
"- Each level has 3 questions.\n"
"- Correctly answering a question gives you 2 characters of a 6-
character code.\n"
"- The questions are shuffled — use your logic to arrange the code
in the order of topics taught.\n"
"- Enter the correct code to unlock the next level.\n\n"
"Good luck!"
)
label = tk.Label(self, text=instruction_text, font=("Arial", 18),
justify="left", wraplength=550,
bg="#000000", fg="#ffffff")
label.pack(pady=20)
self.selected = tk.StringVar(value="Medium")
diff_label = tk.Label(self, text="Select Difficulty:", font=("Arial", 20,
"bold"), bg="#000000", fg="white")
diff_label.pack(pady=(10, 5))
options = [("Easy (Unlimited Time)", "Easy"),
("Medium (10 minutes)", "Medium"),
("Hard (5 minutes)", "Hard"),
("Impossible (3 minutes)", "Impossible")]
for text, mode in options:
rb = tk.Radiobutton(self, text=text, variable=self.selected,
value=mode,
font=("Arial", 16), bg="#000000", fg="white",
selectcolor="#333333",
activebackground="#222222", activeforeground="white")
rb.pack(anchor="w", padx=60)
start_btn = tk.Button(self, text="Start Game", font=("Arial", 20),
bg="#ffffff", fg="#000000",
command=self.start_game)
start_btn.pack(pady=20)
# Admin button
admin_btn = tk.Button(self, text="Administrator", font=("Arial", 16),
bg="#ffffff", fg="#000000",
command=self.open_admin_screen)
admin_btn.pack(pady=(0, 5))
# Exit button
quit_btn = tk.Button(self, text="Exit", font=("Arial", 16), bg="#ffffff",
fg="#000000",
command=self.quit_game)
quit_btn.pack(pady=(0, 10))
def open_admin_login(self):
self.withdraw()
AdminLoginScreen(self)
def open_admin_screen(self):
AdminAccessScreen(self)
def start_game(self):
difficulty = self.selected.get()
self.destroy()
self.on_start(difficulty)
def quit_game(self):
self.destroy()
self.master.destroy()
class AdminAccessScreen(tk.Toplevel):
def __init__(self, parent):
super().__init__(parent)
self.title("Admin Access")
self.geometry("400x300")
self.configure(bg="#1a1a1a")
tk.Label(self, text="Admin Access", font=("Arial", 20, "bold"),
bg="#1a1a1a", fg="white").pack(pady=20)
tk.Button(self, text="Create Admin Account", font=("Arial", 14),
command=self.create_admin, bg="#ffffff",
fg="#000000").pack(pady=10)
tk.Button(self, text="Login as Admin", font=("Arial", 14),
command=self.login_admin, bg="#ffffff",
fg="#000000").pack(pady=10)
def create_admin(self):
AdminCreateScreen(self)
def login_admin(self):
AdminLoginScreen(self)
class AdminCreateScreen(tk.Toplevel):
def __init__(self, parent):
super().__init__(parent)
self.title("Create Admin Account")
self.geometry("400x300")
self.configure(bg="#1a1a1a")
tk.Label(self, text="Create Admin Account", font=("Arial", 18, "bold"),
bg="#1a1a1a", fg="white").pack(pady=20)
tk.Label(self, text="Username:", font=("Arial", 14), bg="#1a1a1a",
fg="white").pack()
self.username_entry = tk.Entry(self, font=("Arial", 14))
self.username_entry.pack(pady=5)
tk.Label(self, text="Password:", font=("Arial", 14), bg="#1a1a1a",
fg="white").pack()
self.password_entry = tk.Entry(self, font=("Arial", 14), show="*")
self.password_entry.pack(pady=5)
tk.Button(self, text="Create", font=("Arial", 14),
command=self.save_credentials, bg="#ffffff",
fg="#000000").pack(pady=15)
tk.Button(self, text="Back", command=self.go_back, bg="#dc3545",
fg="white").pack(pady=5)
def save_credentials(self):
username = self.username_entry.get().strip()
password = self.password_entry.get().strip()
if not username or not password:
messagebox.showwarning("Input Error", "Username and password
cannot be empty.")
return
with open("admin_credentials.txt", "w") as f:
f.write(f"{username}:{password}\n")
messagebox.showinfo("Success", "Admin account created
successfully.")
self.destroy()
def go_back(self):
self.destroy()
self.parent.deiconify()
class AdminLoginScreen(tk.Toplevel):
def __init__(self, parent):
super().__init__(parent)
self.title("Admin Login")
self.geometry("400x250")
self.configure(bg="#1a1a1a")
tk.Label(self, text="Admin Login", font=("Arial", 18, "bold"),
bg="#1a1a1a", fg="white").pack(pady=20)
tk.Label(self, text="Username:", font=("Arial", 14), bg="#1a1a1a",
fg="white").pack()
self.username_entry = tk.Entry(self, font=("Arial", 14))
self.username_entry.pack(pady=5)
tk.Label(self, text="Password:", font=("Arial", 14), bg="#1a1a1a",
fg="white").pack()
self.password_entry = tk.Entry(self, font=("Arial", 14), show="*")
self.password_entry.pack(pady=5)
tk.Button(self, text="Login", font=("Arial", 14),
command=self.verify_credentials, bg="#ffffff",
fg="#000000").pack(pady=15)
tk.Button(self, text="Back", command=self.go_back, bg="#dc3545",
fg="white").pack(pady=10)
def verify_credentials(self):
username = self.username_entry.get().strip()
password = self.password_entry.get().strip()
try:
with open("admin_credentials.txt", "r") as f:
stored = f.readline().strip()
if stored == f"{username}:{password}":
messagebox.showinfo("Login Successful", "Welcome,
Admin!")
self.destroy()
AdminPanel(self.master)
else:
messagebox.showerror("Login Failed", "Incorrect
credentials.")
except FileNotFoundError:
messagebox.showerror("Error", "No admin credentials found.
Please create one.")
def open_create_screen(self):
self.withdraw()
AdminCreateScreen(self)
def go_back(self):
self.destroy()
self.parent.deiconify()
class AdminPanel(tk.Toplevel):
def __init__(self, parent):
super().__init__(parent)
self.title("Admin Panel")
self.geometry("500x400")
self.configure(bg="#1a1a1a")
tk.Label(self, text="Admin Panel - Question Manager", font=("Arial",
16, "bold"),
bg="#1a1a1a", fg="white").pack(pady=20)
tk.Button(self, text="Add Question", font=("Arial", 14),
command=self.add_question).pack(pady=10)
tk.Button(self, text="Delete Question", font=("Arial", 14),
command=self.delete_question).pack(pady=10)
tk.Button(self, text="Modify Question", font=("Arial", 14),
command=self.modify_question).pack(pady=10)
tk.Button(self, text="Logout", width=20, bg="#dc3545", fg="white",
command=self.logout).pack(pady=30)
def add_question(self):
QuestionEditor(self, mode="add")
def delete_question(self):
QuestionEditor(self, mode="delete")
def modify_question(self):
selection_window = tk.Toplevel(self)
selection_window.title("Select Question to Modify")
selection_window.geometry("500x400")
selection_window.configure(bg="#1a1a1a")
tk.Label(selection_window, text="Select a Question", font=("Arial",
16), bg="#1a1a1a", fg="white").pack(pady=10)
# Load questions from the .docx or your question source
try:
from docx import Document
doc = Document("questions.docx")
questions = [para.text for para in doc.paragraphs if
para.text.strip()]
except:
questions = []
listbox = tk.Listbox(selection_window, font=("Arial", 12), width=60)
for i, q in enumerate(questions):
listbox.insert(tk.END, f"{i + 1}. {q[:80]}...") # Short preview
listbox.pack(pady=10)
def open_editor():
selected_index = listbox.curselection()
if not selected_index:
tk.messagebox.showwarning("No Selection", "Please select a
question to modify.")
return
QuestionEditor(self, mode="modify",
question_index=selected_index[0])
selection_window.destroy()
tk.Button(selection_window, text="Edit Selected Question",
command=open_editor, font=("Arial", 12),
bg="#28a745", fg="white").pack(pady=10)
def logout(self):
self.destroy()
self.instruction_screen.deiconify()
class QuestionEditor(tk.Toplevel):
def __init__(self, parent, mode):
super().__init__(parent)
self.title(f"{mode.capitalize()} Question")
self.geometry("600x500")
self.configure(bg="#1a1a1a")
self.mode = mode
self.level_var = tk.StringVar()
tk.Label(self, text=f"{mode.capitalize()} Question", font=("Arial", 16,
"bold"),
bg="#1a1a1a", fg="white").pack(pady=10)
level_frame = tk.Frame(self, bg="#1a1a1a")
tk.Label(level_frame, text="Level:", font=("Arial", 14),
bg="#1a1a1a", fg="white").pack(side=tk.LEFT)
level_dropdown = ttk.Combobox(level_frame,
textvariable=self.level_var, values=["1", "2", "3"], width=5)
level_dropdown.pack(side=tk.LEFT, padx=10)
level_dropdown.current(0)
level_frame.pack(pady=10)
if self.mode == "add":
self.create_add_ui()
else:
self.create_modify_delete_ui()
def create_add_ui(self):
tk.Label(self, text="Question:", bg="#1a1a1a", fg="white",
font=("Arial", 12)).pack()
self.question_text = tk.Text(self, height=4, width=60)
self.question_text.pack(pady=5)
tk.Label(self, text="Answer:", bg="#1a1a1a", fg="white",
font=("Arial", 12)).pack()
self.answer_entry = tk.Entry(self, width=60)
self.answer_entry.pack(pady=5)
tk.Button(self, text="Add Question",
command=self.add_question).pack(pady=15)
def create_modify_delete_ui(self):
self.question_listbox = tk.Listbox(self, width=80, height=10)
self.question_listbox.pack(pady=10)
tk.Button(self, text="Load Questions",
command=self.load_questions).pack()
if self.mode == "delete":
tk.Button(self, text="Delete Selected",
command=self.delete_selected).pack(pady=10)
elif self.mode == "modify":
tk.Label(self, text="New Question:", bg="#1a1a1a",
fg="white").pack()
self.new_question_entry = tk.Text(self, height=4, width=60)
self.new_question_entry.pack()
tk.Label(self, text="New Answer:", bg="#1a1a1a",
fg="white").pack()
self.new_answer_entry = tk.Entry(self, width=60)
self.new_answer_entry.pack()
tk.Button(self, text="Modify Selected",
command=self.modify_selected).pack(pady=10)
def load_questions(self):
self.question_listbox.delete(0, tk.END)
level = self.level_var.get()
try:
with open("questions.txt", "r") as file:
lines = file.readlines()
for i in range(0, len(lines), 3):
if lines[i].strip() == f"Level: {level}":
question = lines[i + 1].strip().replace("Question: ", "")
answer = lines[i + 2].strip().replace("Answer: ", "")
self.question_listbox.insert(tk.END, f"Q: {question} | A:
{answer}")
except FileNotFoundError:
messagebox.showerror("Error", "questions.txt not found.")
def add_question(self):
level = self.level_var.get()
question = self.question_text.get("1.0", tk.END).strip()
answer = self.answer_entry.get().strip()
if not question or not answer:
messagebox.showwarning("Input Error", "Question and answer
cannot be empty.")
return
with open("questions.txt", "a") as file:
file.write(f"Level: {level}\n")
file.write(f"Question: {question}\n")
file.write(f"Answer: {answer}\n")
messagebox.showinfo("Success", "Question added successfully.")
self.destroy()
def delete_selected(self):
selected = self.question_listbox.curselection()
if not selected:
messagebox.showwarning("Selection Error", "No question
selected.")
return
to_delete = self.question_listbox.get(selected[0])
level = self.level_var.get()
with open("questions.txt", "r") as file:
lines = file.readlines()
new_lines = []
i=0
while i < len(lines):
if lines[i].strip() == f"Level: {level}":
question = lines[i + 1].strip().replace("Question: ", "")
answer = lines[i + 2].strip().replace("Answer: ", "")
full = f"Q: {question} | A: {answer}"
if full == to_delete:
i += 3
continue
new_lines.append(lines[i])
i += 1
with open("questions.txt", "w") as file:
file.writelines(new_lines)
messagebox.showinfo("Success", "Question deleted successfully.")
self.load_questions()
def modify_selected(self):
selected = self.question_listbox.curselection()
if not selected:
messagebox.showwarning("Selection Error", "No question
selected.")
return
old = self.question_listbox.get(selected[0])
level = self.level_var.get()
new_q = self.new_question_entry.get("1.0", tk.END).strip()
new_a = self.new_answer_entry.get().strip()
if not new_q or not new_a:
messagebox.showwarning("Input Error", "New question and
answer cannot be empty.")
return
with open("questions.txt", "r") as file:
lines = file.readlines()
new_lines = []
i=0
while i < len(lines):
if lines[i].strip() == f"Level: {level}":
question = lines[i + 1].strip().replace("Question: ", "")
answer = lines[i + 2].strip().replace("Answer: ", "")
full = f"Q: {question} | A: {answer}"
if full == old:
new_lines.extend([
f"Level: {level}\n",
f"Question: {new_q}\n",
f"Answer: {new_a}\n"
])
i += 3
continue
new_lines.append(lines[i])
i += 1
with open("questions.txt", "w") as file:
file.writelines(new_lines)
messagebox.showinfo("Success", "Question modified successfully.")
self.load_questions()
def load_questions_from_file(level):
filename = "questions.txt"
questions = []
current_level = None
if not os.path.exists(filename):
messagebox.showerror("Missing File", f"{filename} not found.")
return []
with open(filename, "r", encoding="utf-8") as f:
lines = f.readlines()
question_data = {}
current_key = None
buffer = []
def flush_buffer():
if current_key and buffer:
question_data[current_key]="\n".join(buffer).strip()
for line in lines:
line = line.rstrip("\n")
if not line.strip():
flush_buffer()
if question_data and current_level == level:
questions.append(question_data)
question_data = {}
current_key = None
buffer = []
continue
if line.startswith("# Level"):
flush_buffer()
try:
current_level = int(line.split("Level")[1].strip())
except ValueError:
current_level = None
question_data = {}
current_key = None
buffer = []
continue
if current_level == level:
if line.startswith("QUESTION:"):
flush_buffer()
current_key = "question"
buffer = [line[len("QUESTION:"):].strip()]
elif line.startswith("ANSWER:"):
flush_buffer()
current_key = "answer"
buffer = [line[len("ANSWER:"):].strip()]
elif line.startswith("HINT:"):
flush_buffer()
current_key = "hint"
buffer = [line[len("HINT:"):].strip()]
elif line.startswith("CODE:"):
flush_buffer()
current_key = "code"
buffer = [line[len("CODE:"):].strip()]
elif line.startswith("ORDER:"):
flush_buffer()
current_key = "order"
try:
question_data["order"] = int(line[len("ORDER:"):].strip())
except ValueError:
question_data["order"] = 0
current_key = None
buffer = []
else:
buffer.append(line)
flush_buffer()
if question_data and current_level == level:
questions.append(question_data)
return questions
class Game:
def __init__(self, root, difficulty):
self.root = root
self.root.title("Escape Python Advanced")
self.root.geometry("800x600")
self.level = 1
self.root.protocol("WM_DELETE_WINDOW", self.cleanup_and_exit)
self.style = ttk.Style(self.root)
self.style.theme_use('default')
self.style.configure("Green.Horizontal.TProgressbar",
foreground="#006400", background="#006400")
if not os.path.exists("background_fullscreen.png"):
messagebox.showerror("Missing File", "Required background
image not found.")
self.root.destroy()
return
self.bg_image = Image.open("background_fullscreen.png")
self.bg_photo = ImageTk.PhotoImage(self.bg_image)
self.canvas = tk.Canvas(self.root)
self.canvas.pack(fill="both", expand=True)
self.canvas.create_image(0, 0, image=self.bg_photo, anchor="nw")
self.frame = None
self.levels = {
1: self.get_level1_questions,
2: self.get_level2_questions,
3: self.get_level3_questions
self.difficulty = difficulty
self.time_left = self.set_time_for_difficulty(difficulty)
self.timer_id = None
self.timer_label = tk.Label(self.root, text="", font=("Arial", 16,
"bold"),
bg="#000000", fg="#ffffff")
self.timer_label.place(x=650, y=50)
self.collected_codes = []
self.start_level()
if self.time_left is not None:
self.start_timer()
else:
self.timer_label.config(text="Unlimited Time")
def confirm_quit(self):
answer = messagebox.askyesno("Quit Game", "Are you sure you
want to quit?")
if answer:
self.root.destroy()
def add_quit_button(self):
quit_btn = tk.Button(self.root, text="Quit", font=("Arial", 14),
command=self.confirm_quit, bg="#ff4d4d", fg="white")
quit_btn.place(x=720, y=550) # Adjust position as needed
def set_time_for_difficulty(self, difficulty):
return {
"Easy": None,
"Medium": 10 * 60,
"Hard": 5 * 60,
"Impossible": 3 * 60
}.get(difficulty, None)
def clear_window(self):
if self.frame is not None:
self.frame.destroy()
def start_level(self):
self.clear_window()
self.collected_codes = []
self.questions = self.levels[self.level]()
self.shuffled_questions = self.questions.copy()
random.shuffle(self.shuffled_questions)
self.questions = self.shuffled_questions
self.current_question = 0
self.hint_count = 0
self.max_hints = 3
self.hint_window = None
self.show_question()
self.add_quit_button()
def show_question(self):
self.clear_window()
self.frame = tk.Frame(self.canvas, bg="#000000", bd=0)
self.frame.place(x=20, y=40)
self.progress_bar = ttk.Progressbar(self.canvas, orient="horizontal",
length=760,
mode="determinate",
style="Green.Horizontal.TProgressbar")
self.progress_bar.place(x=20, y=20)
self.progress_bar["maximum"] = len(self.questions)
self.progress_bar["value"] = self.current_question
if self.current_question < len(self.questions):
q = self.questions[self.current_question]
tk.Label(self.frame, text=f"Level {self.level} - Question
{self.current_question + 1}",
font=("Arial", 20, "bold"), bg="#000000",
fg="#0000ff").pack(anchor="w", pady=(0, 5))
tk.Label(self.frame, text=q['question'], wraplength=1000,
justify="left",
font=("Arial", 18), bg="#000000",
fg="#ffffff").pack(anchor="w", pady=(0, 10))
self.answer_entry = tk.Entry(self.frame, width=30, font=("Arial",
16))
self.answer_entry.pack(anchor="w", pady=(0, 10))
tk.Button(self.frame, text="Submit", command=self.check_answer,
font=("Arial", 16)).pack(anchor="w", pady=(0, 5))
self.hint_btn = tk.Button(self.frame, text="Hint",
command=self.show_hint,
font=("Arial", 16))
self.hint_btn.pack(anchor="w")
self.hint_counter_label = tk.Label(self.frame,
text=f"Hints used:
{self.hint_count}/{self.max_hints}",
font=("Arial", 14), bg="#000000",
fg="#ffcc00")
self.hint_counter_label.pack(anchor="w", pady=(5, 0))
else:
self.prompt_code_entry()
def check_answer(self):
user_answer = self.answer_entry.get().strip()
correct_answer = self.questions[self.current_question]["answer"]
if user_answer.lower() == correct_answer.lower():
messagebox.showinfo("Correct!", f"Code segment received: "
f"{self.questions[self.current_question]
['code']}")
self.collected_codes.append({
'code': self.questions[self.current_question]['code'],
'order': self.questions[self.current_question]['order']
})
self.current_question += 1
self.progress_bar["value"] = self.current_question
self.show_question()
else:
messagebox.showwarning("Try Again", "Incorrect answer. Please
try again.")
def show_hint(self):
if self.hint_count >= self.max_hints:
messagebox.showwarning("Hint Limit Reached", "You have used all
your hints for this level.")
return
if self.hint_window and self.hint_window.winfo_exists():
return
self.hint_count += 1
hint = self.questions[self.current_question]["hint"]
self.hint_window = tk.Toplevel(self.root)
self.hint_window.title("Hint")
self.hint_window.geometry("400x250") # Increased from 300x200
self.hint_window.configure(bg="#000000")
tk.Label(self.hint_window, text=f"HINT
({self.hint_count}/{self.max_hints})",
font=("Arial", 18, "bold"), bg="#000000",
fg="white").pack(pady=(20, 10))
tk.Label(self.hint_window, text=hint, wraplength=350,
justify="center",
font=("Arial", 16), bg="#000000", fg="white").pack(pady=(0,
20))
tk.Button(self.hint_window, text="Got it",
command=self.hint_window.destroy,
font=("Arial", 16), bg="white", fg="black").pack()
self.hint_counter_label.config(text=f"Hints used:
{self.hint_count}/{self.max_hints}")
def prompt_code_entry(self):
if self.timer_id:
self.root.after_cancel(self.timer_id)
self.clear_window()
self.frame = tk.Frame(self.canvas, bg="#000000", bd=0)
self.frame.place(x=20, y=40)
tk.Label(self.frame, text=f"Level {self.level} Complete!\n\n"
f"Enter the 6-character access code to continue:",
font=("Arial", 20), bg="#000000",
fg="#ffffff").pack(anchor="w", pady=(0, 10))
self.code_entry = tk.Entry(self.frame, width=20, font=("Arial", 18))
self.code_entry.pack(anchor="w", pady=(0, 10))
tk.Button(self.frame, text="Enter Code", command=self.verify_code,
font=("Arial", 18)).pack(anchor="w")
self.add_quit_button()
def verify_code(self):
entered_code = self.code_entry.get().strip()
self.code_segments = [c['code'] for c in sorted(self.collected_codes,
key=lambda x: x['order'])]
correct_code = ''.join(self.code_segments)
if entered_code.lower() == correct_code.lower():
if self.level == 3:
messagebox.showinfo("🏁 Game Complete!", "You have escaped
for now!\nYour project is next...")
self.root.destroy()
else:
messagebox.showinfo("Access Granted", f"Welcome to Level
{self.level + 1}!")
self.level += 1
self.start_level()
else:
messagebox.showerror("Access Denied", "Incorrect code. Please
try again.")
def start_timer(self):
self.update_timer()
def update_timer(self):
if self.time_left is None:
return
mins, secs = divmod(self.time_left, 60)
self.timer_label.config(text=f"Time left: {mins:02d}:{secs:02d}")
if self.time_left <= 0:
messagebox.showinfo("Time's Up", "You have run out of time!
Game over.")
self.root.destroy()
return
self.time_left -= 1
self.timer_id = self.root.after(1000, self.update_timer)
def cleanup_and_exit(self):
if self.timer_id:
self.root.after_cancel(self.timer_id)
self.root.destroy()
# Question methods unchanged...
def get_level1_questions(self):
return load_questions_from_file(1)
def get_level2_questions(self):
return load_questions_from_file(2)
def get_level3_questions(self):
return load_questions_from_file(3)
if __name__ == "__main__":
root = tk.Tk()
root.withdraw()
def start_game(difficulty):
root.deiconify()
Game(root, difficulty)
def show_instructions():
InstructionScreen(root, on_start=start_game)
splash = SplashScreen(root, on_close=show_instructions)
splash.mainloop()