Skip to content

Commit 783ab7d

Browse files
Merge pull request steam-bell-92#1096 from knoxiboy/feat/issue-967-cli-menu
feat: implement a CLI menu system for running games interactively
2 parents e927cad + 8c14ede commit 783ab7d

1 file changed

Lines changed: 208 additions & 0 deletions

File tree

main.py

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
"""
2+
🎮 Python Mini Projects — Interactive Launcher Menu
3+
===================================================
4+
A central, interactive CLI menu at the root level to browse,
5+
search, and launch all games, math utilities, and other tools.
6+
"""
7+
8+
import os
9+
import subprocess
10+
import sys
11+
import json
12+
13+
REGISTRY_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "projects_registry.json")
14+
15+
try:
16+
with open(REGISTRY_PATH, "r", encoding="utf-8") as f:
17+
PROJECTS = json.load(f)
18+
except Exception as e:
19+
print(f"Error loading project registry: {e}")
20+
PROJECTS = []
21+
22+
DIFFICULTY_BADGES = {
23+
"beginner": "🟢 Beg",
24+
"intermediate": "🟡 Int",
25+
"advanced": "🔴 Adv",
26+
}
27+
28+
CATEGORY_EMOJIS = {
29+
"games": "🎮",
30+
"math": "🔢",
31+
"utilities": "🔧",
32+
}
33+
34+
def print_header():
35+
print("\n" + "═" * 60)
36+
print(" 🚀 PYTHON MINI PROJECTS — INTERACTIVE LAUNCHER")
37+
print("═" * 60)
38+
39+
def print_footer():
40+
print("═" * 60)
41+
42+
def list_projects_by_category(category_name):
43+
filtered = [p for p in PROJECTS if p["category"] == category_name]
44+
filtered_sorted = sorted(filtered, key=lambda p: p["name"])
45+
return filtered_sorted
46+
47+
def launch_project(path):
48+
if not os.path.exists(path):
49+
print(f"\n❌ Error: File not found at '{path}'")
50+
input("\nPress Enter to return to menu...")
51+
return
52+
53+
print(f"\n🚀 Launching: {os.path.basename(path)}")
54+
print("─" * 60 + "\n")
55+
try:
56+
# Run with current python executable
57+
subprocess.run([sys.executable, path])
58+
except Exception as e:
59+
print(f"\n❌ Error executing script: {e}")
60+
print("\n" + "─" * 60)
61+
input("ℹ️ Script finished. Press Enter to return to the launcher...")
62+
63+
def main_menu():
64+
while True:
65+
os.system('cls' if os.name == 'nt' else 'clear')
66+
print_header()
67+
print(" Please select a category to browse:")
68+
print("\n [1] 🎮 Games")
69+
print(" [2] 🔢 Math Utilities")
70+
print(" [3] 🔧 General Utilities")
71+
print(" [4] 🔍 Search Projects by Keyword")
72+
print(" [5] 📋 List All Projects")
73+
print(" [6] ❌ Exit")
74+
print_footer()
75+
76+
choice = input("👉 Enter choice (1-6): ").strip()
77+
78+
if choice == "1":
79+
category_menu("games", "Games")
80+
elif choice == "2":
81+
category_menu("math", "Math Utilities")
82+
elif choice == "3":
83+
category_menu("utilities", "General Utilities")
84+
elif choice == "4":
85+
search_menu()
86+
elif choice == "5":
87+
list_all_menu()
88+
elif choice == "6":
89+
print("\n👋 Happy Coding! Goodbye.\n")
90+
break
91+
else:
92+
input("\n⚠️ Invalid selection. Press Enter to try again...")
93+
94+
def category_menu(category_key, category_title):
95+
while True:
96+
os.system('cls' if os.name == 'nt' else 'clear')
97+
print_header()
98+
print(f" 📂 Category: {category_title}")
99+
print("─" * 60)
100+
101+
items = list_projects_by_category(category_key)
102+
for idx, item in enumerate(items, start=1):
103+
difficulty = DIFFICULTY_BADGES.get(item["difficulty"], item["difficulty"])
104+
print(f" [{idx:2d}] {item['emoji']} {item['name']:30s} [{difficulty}]")
105+
print(f" {item['description']}")
106+
print()
107+
108+
print(f" [B] 🔙 Back to Main Menu")
109+
print_footer()
110+
111+
choice = input("👉 Select project number to launch (or 'b' to go back): ").strip().lower()
112+
if choice == 'b':
113+
break
114+
115+
try:
116+
val = int(choice)
117+
if 1 <= val <= len(items):
118+
launch_project(items[val - 1]["path"])
119+
else:
120+
input("\n⚠️ Number out of range. Press Enter to try again...")
121+
except ValueError:
122+
input("\n⚠️ Invalid input. Please enter a number or 'b'. Press Enter to try again...")
123+
124+
def search_menu():
125+
os.system('cls' if os.name == 'nt' else 'clear')
126+
print_header()
127+
print(" 🔍 Search Projects")
128+
print_footer()
129+
query = input("👉 Enter search keyword (e.g., game, solver, cipher): ").strip().lower()
130+
if not query:
131+
return
132+
133+
results = [
134+
p for p in PROJECTS
135+
if query in p["name"].lower()
136+
or query in p["description"].lower()
137+
or any(query in kw.lower() for kw in p["keywords"])
138+
]
139+
140+
if not results:
141+
input("\n⚠️ No matching projects found. Press Enter to return to main menu...")
142+
return
143+
144+
while True:
145+
os.system('cls' if os.name == 'nt' else 'clear')
146+
print_header()
147+
print(f" 🔍 Search Results for '{query}':")
148+
print("─" * 60)
149+
150+
for idx, item in enumerate(results, start=1):
151+
cat_emoji = CATEGORY_EMOJIS.get(item["category"], "")
152+
difficulty = DIFFICULTY_BADGES.get(item["difficulty"], item["difficulty"])
153+
print(f" [{idx:2d}] {cat_emoji} {item['name']:30s} [{difficulty}]")
154+
print(f" {item['description']}")
155+
print()
156+
157+
print(f" [B] 🔙 Back to Main Menu")
158+
print_footer()
159+
160+
choice = input("👉 Select project number to launch (or 'b' to go back): ").strip().lower()
161+
if choice == 'b':
162+
break
163+
164+
try:
165+
val = int(choice)
166+
if 1 <= val <= len(results):
167+
launch_project(results[val - 1]["path"])
168+
else:
169+
input("\n⚠️ Number out of range. Press Enter to try again...")
170+
except ValueError:
171+
input("\n⚠️ Invalid input. Please enter a number or 'b'. Press Enter to try again...")
172+
173+
def list_all_menu():
174+
while True:
175+
os.system('cls' if os.name == 'nt' else 'clear')
176+
print_header()
177+
print(" 📋 All Projects")
178+
print("─" * 60)
179+
180+
sorted_all = sorted(PROJECTS, key=lambda p: (p["category"], p["name"]))
181+
for idx, item in enumerate(sorted_all, start=1):
182+
cat_emoji = CATEGORY_EMOJIS.get(item["category"], "")
183+
difficulty = DIFFICULTY_BADGES.get(item["difficulty"], item["difficulty"])
184+
print(f" [{idx:2d}] {cat_emoji} {item['name']:30s} [{difficulty}]")
185+
print(f" {item['description']}")
186+
print()
187+
188+
print(f" [B] 🔙 Back to Main Menu")
189+
print_footer()
190+
191+
choice = input("👉 Select project number to launch (or 'b' to go back): ").strip().lower()
192+
if choice == 'b':
193+
break
194+
195+
try:
196+
val = int(choice)
197+
if 1 <= val <= len(sorted_all):
198+
launch_project(sorted_all[val - 1]["path"])
199+
else:
200+
input("\n⚠️ Number out of range. Press Enter to try again...")
201+
except ValueError:
202+
input("\n⚠️ Invalid input. Please enter a number or 'b'. Press Enter to try again...")
203+
204+
if __name__ == "__main__":
205+
try:
206+
main_menu()
207+
except KeyboardInterrupt:
208+
print("\n\n👋 Goodbye!")

0 commit comments

Comments
 (0)