Conditionals, loops, lists, dicts, sets, and functions — all working together. Build 4 programs that feel genuinely useful.
# ══════════════════════════════════════════════ # Number Guessing Game — Phase 2 Project 1 # ══════════════════════════════════════════════ import random LEVELS = { "easy" : {"range": (1, 50), "attempts": 10}, "medium": {"range": (1, 100), "attempts": 7}, "hard" : {"range": (1, 200), "attempts": 5}, } high_scores = {"easy": 0, "medium": 0, "hard": 0} def get_hint(guess, secret): """Return hot/warm/cold hint based on distance.""" diff = abs(guess - secret) if diff == 0: return "🎯 CORRECT!" elif diff <= 5: return "🔥 Very hot!" elif diff <= 15: return "♨️ Warm" elif diff <= 30: return "❄️ Cold" else: return "🧊 Freezing!" def calc_score(attempts_used, max_attempts): """Score based on how few attempts were used.""" return max(0, (max_attempts - attempts_used + 1) * 10) def play_round(level): """Play one round and return score (0 if lost).""" cfg = LEVELS[level] lo, hi = cfg["range"] secret = random.randint(lo, hi) max_att= cfg["attempts"] att = 0 print(f"\n Guess a number between {lo} and {hi}. ({max_att} attempts)\n") while att < max_att: att += 1 try: guess = int(input(f" Attempt {att}/{max_att}: ")) except ValueError: print(" Enter a whole number!"); att -= 1; continue hint = get_hint(guess, secret) print(f" {hint}") if guess == secret: score = calc_score(att, max_att) print(f" 🏆 You got it in {att} attempts! Score: {score}") return score print(f" 😞 Out of attempts. Answer was {secret}.") return 0 # ── Main game loop ──────────────────────────── print("=" * 42) print(" 🎯 NUMBER GUESSING GAME") print("=" * 42) while True: print("\n Difficulty: easy | medium | hard") lvl = input(" Choose: ").strip().lower() if lvl not in LEVELS: print(" ❌ Invalid level."); continue score = play_round(lvl) if score > high_scores[lvl]: high_scores[lvl] = score print(f" ⭐ New high score on {lvl}: {score}!") print(f" High scores: {high_scores}") if input("\n Play again? (y/n): ").lower() != "y": print(" Thanks for playing! 👋") break
time.time() to penalise slow guessing. Add binary search hint: tell the user if their guess is too high or too low.# ══════════════════════════════════════════════ # Expense Tracker — Phase 2 Project 2 # ══════════════════════════════════════════════ expenses = [] def add_expense(amount, category, desc): """Add an expense record to the list.""" expenses.append({ "amount" : float(amount), "category": category.strip().title(), "desc" : desc.strip() }) def view_all(): """Print all expenses in a table.""" if not expenses: print(" No expenses yet."); return print(f"\n {'#':4} {'Category':14} {'Amount':>10} Description") print(" " + "─" * 50) for i, e in enumerate(expenses, 1): print(f" {i:4} {e['category']:14} PKR {e['amount']:>7,.0f} {e['desc']}") print(f"\n Total: PKR {sum(e['amount'] for e in expenses):,.2f}") def summary_by_category(): """Group and total expenses by category.""" grouped = {} for e in expenses: grouped.setdefault(e["category"], 0) grouped[e["category"]] += e["amount"] ranked = sorted(grouped.items(), key=lambda x: x[1], reverse=True) max_amt= ranked[0][1] if ranked else 1 print(f"\n {'Category':16} {'Total':>10} Chart") print(" " + "─" * 50) for cat, amt in ranked: bar = "█" * int(amt / max_amt * 20) print(f" {cat:16} PKR {amt:>7,.0f} {bar}") def filter_by_category(cat): """Show expenses matching a category.""" found = [e for e in expenses if e["category"].lower() == cat.lower()] if not found: print(" No expenses in that category."); return for e in found: print(f" PKR {e['amount']:,.0f} — {e['desc']}") print(f" Subtotal: PKR {sum(e['amount'] for e in found):,.2f}") # ── Menu ───────────────────────────────────── print("═" * 44) print(" 💰 EXPENSE TRACKER") print("═" * 44) while True: print("\n 1) Add 2) View all 3) Summary 4) Filter 5) Quit") ch = input(" Choice: ").strip() if ch == "1": amt = input(" Amount (PKR): ") cat = input(" Category : ") desc = input(" Description: ") add_expense(amt, cat, desc) print(" ✅ Added.") elif ch == "2": view_all() elif ch == "3": summary_by_category() elif ch == "4": filter_by_category(input(" Category: ")) elif ch == "5": print(" Bye! 👋"); break
"█" * int(amount / max_amount * 20) — normalising each amount to a 0–20 scale so the largest bar always fills 20 blocks. This scales correctly regardless of the actual currency amounts.# ══════════════════════════════════════════════ # Caesar Cipher — Phase 2 Project 3 # ══════════════════════════════════════════════ def shift_char(ch, shift): """Shift a single letter by shift positions (preserves case).""" if ch.isalpha(): base = ord('A') if ch.isupper() else ord('a') return chr((ord(ch) - base + shift) % 26 + base) return ch # non-letters unchanged def encrypt(text, shift): """Encrypt text with Caesar cipher.""" return "".join(shift_char(c, shift) for c in text) def decrypt(text, shift): """Decrypt by shifting backwards.""" return encrypt(text, -shift) def crack(ciphertext): """Try all 26 shifts and highlight most likely.""" # English letter frequencies — 'e' is most common freq = {c: ciphertext.lower().count(c) for c in "abcdefghijklmnopqrstuvwxyz"} most_common = max(freq, key=freq.get) likely_shift= (ord(most_common) - ord('e')) % 26 print(f"\n Most common letter: '{most_common}' → likely shift: {likely_shift}\n") for s in range(26): marker = " ← likely" if s == likely_shift else "" print(f" [{s:2}] {decrypt(ciphertext, s)[:50]}{marker}") # ── Menu ───────────────────────────────────── print("=" * 44) print(" 🔐 CAESAR CIPHER") print("=" * 44) while True: print("\n 1) Encrypt 2) Decrypt 3) Crack 4) Quit") ch = input(" Choice: ") if ch == "1": text = input(" Text to encrypt: ") shift = int(input(" Shift (1-25) : ")) print(f" 🔒 Encrypted: {encrypt(text, shift)}") elif ch == "2": text = input(" Encrypted text : ") shift = int(input(" Shift : ")) print(f" 🔓 Decrypted: {decrypt(text, shift)}") elif ch == "3": text = input(" Ciphertext to crack: ") crack(text) elif ch == "4": print(" Encrypting goodbye... Hmm 👋"); break
ord('A') gives 65. Subtracting base converts a letter to 0–25. Adding shift, modulo 26, then adding base back converts back to a character. The % 26 handles wrap-around (Z+1 = A). This is all standard ASCII arithmetic.# ══════════════════════════════════════════════ # Reusable Quiz Engine — Phase 2 Project 4 # ══════════════════════════════════════════════ import random, time QUESTIONS = [ {"q": "What does type('42') return?", "opts": ["int", "str", "float", "None"], "ans": "b", "cat": "Types"}, {"q": "What does 17 % 5 equal?", "opts": ["3", "3.4", "2", "0"], "ans": "c", "cat": "Operators"}, {"q": "What is the output of 'Python'[::-1]?", "opts": ["Python", "nohtyP", "Pyt", "Error"], "ans": "b", "cat": "Strings"}, {"q": "Which creates an empty set?", "opts": ["{}", "set()", "[]", "dict()"], "ans": "b", "cat": "Collections"}, {"q": "What does *args collect?", "opts": ["dict", "tuple", "list", "set"], "ans": "b", "cat": "Functions"}, {"q": "LEGB stands for…?", "opts": ["Local-Enclosing-Global-Built-in", "Loop-Else-Get-Break", "Lambda-Enum-Global-Block", "None of these"], "ans": "a", "cat": "Functions"}, ] def run_quiz(questions, time_limit=15): """Run the quiz, return results dict.""" random.shuffle(questions) results = {"correct": 0, "wrong": 0, "categories": {}} for i, q in enumerate(questions, 1): print(f"\n Q{i}/{len(questions)} [{q['cat']}] {q['q']}") for j, opt in zip("abcd", q["opts"]): print(f" {j}) {opt}") t0 = time.time() ans= input(" Answer (a/b/c/d): ").strip().lower() elapsed = time.time() - t0 cat = q["cat"] results["categories"].setdefault(cat, {"correct": 0, "total": 0}) results["categories"][cat]["total"] += 1 if ans == q["ans"] and elapsed <= time_limit: print(f" ✅ Correct! ({elapsed:.1f}s)") results["correct"] += 1 results["categories"][cat]["correct"] += 1 elif elapsed > time_limit: print(f" ⏰ Too slow ({elapsed:.1f}s). Answer: {q['ans']}") results["wrong"] += 1 else: print(f" ❌ Wrong. Answer: {q['ans']}") results["wrong"] += 1 return results def show_report(results, total): """Print analytics report.""" pct = results["correct"] / total * 100 weak = [c for c, s in results["categories"].items() if s["correct"] / s["total"] < 0.5] print(f""" ╔═══════════════════════════════╗ QUIZ REPORT Score : {results['correct']}/{total} ({pct:.0f}%) ───────────────────────────── Category Breakdown:""") for c, s in results["categories"].items(): print(f" {c:15} {s['correct']}/{s['total']}") if weak: print(f"\n ⚠️ Study more: {', '.join(weak)}") print(" ╚═══════════════════════════════╝") # ── Run ────────────────────────────────────── print("═" * 44 + "\n 🎓 QUIZ ENGINE (15s per question)\n" + "═" * 44) while True: res = run_quiz(QUESTIONS.copy()) show_report(res, len(QUESTIONS)) if input("\n Play again? (y/n): ").lower() != "y": break
QUESTIONS list for a JSON file or database means zero code changes. The engine is fully reusable for any subject just by changing the question data. This separation of data from logic is fundamental software design.