"""Arcane Purge - A text-based exorcist game using Bible verses as spell mechanics.""" import os import subprocess import sys import time if __name__ == "__main__": _here = os.path.dirname(os.path.abspath(__file__)) if _here not in sys.path: sys.path.insert(0, _here) from data import load_verses, normalize_text # Demon encounter definitions with escalating difficulty DEMONS = [ {"name": "Shade of Whispers", "health": 30, "weaknesses": ["Binding"], "damage": 5}, {"name": "Gloom Wraith", "health": 45, "weaknesses": ["Healing"], "damage": 8}, {"name": "Shadow Stalker", "health": 60, "weaknesses": ["Exorcism", "Binding"], "damage": 10}, { "name": "Abyssal Phantom", "health": 80, "weaknesses": ["Protection", "Exorcism"], "damage": 15, }, {"name": "Dread Incubus", "health": 100, "weaknesses": ["Binding", "Healing"], "damage": 18}, { "name": "Nightmare Sovereign", "health": 130, "weaknesses": ["Judgment", "Exorcism"], "damage": 22, }, { "name": "Void Archfiend", "health": 160, "weaknesses": ["Judgment", "Protection"], "damage": 28, }, { "name": "Abyssal Lord", "health": 200, "weaknesses": ["Binding", "Judgment", "Exorcism"], "damage": 35, }, ] def clear_screen() -> None: """Clear the terminal screen.""" cmd = "cls" if os.name == "nt" else "clear" subprocess.run(cmd.split(), check=False) def show_status(player: dict, demon: dict, verses: list, unlocked: list) -> None: """Display the current game status including player stats, demon info, and available verses. Args: player: Player state dictionary with health, max_health, score, mana. demon: Current demon dictionary with name, health, max_health, weaknesses. verses: Full list of all verses. unlocked: List of verse IDs that are unlocked. """ clear_screen() print("=" * 50) print(" ARCANED PURGE") print("=" * 50) print() print(f"Player Health: {player['health']}/{player['max_health']}") print(f"Score: {player['score']}") print(f"Demons Vanquished: {player['demons_defeated']}") print() print(f"Encounter: {demon['name']}") print(f"Demon Health: {demon['health']}/{demon['max_health']}") print(f"Weaknesses: {', '.join(demon['weaknesses'])}") print() print("Available Verses:") for i, verse in enumerate(verses): if verse["id"] in unlocked: marker = ">>" if verse["category"] in demon["weaknesses"] else " " print( f" {marker} {i + 1}) {verse['id']} ({verse['category']}, " f"Difficulty: {verse['difficulty']})" ) print() print("-" * 50) def calculate_similarity(text1: str, text2: str) -> float: """Calculate similarity between two strings using character matching ratio. Args: text1: First normalized text string. text2: Second normalized text string. Returns: Float between 0.0 and 1.0 representing similarity. """ if not text1 and not text2: return 1.0 if not text1 or not text2: return 0.0 words1 = set(text1.split()) words2 = set(text2.split()) matches = len(words1 & words2) max_len = max(len(words1), len(words2)) return matches / max_len if max_len > 0 else 0.0 def get_hint(verse_text: str) -> str: """Generate a hint showing the first letter of each word. Args: verse_text: The full verse text. Returns: String with first letter of each word separated by spaces. """ words = verse_text.split() return " ".join(w[0] if w else "?" for w in words) def cast_verse(player: dict, demon: dict, verse: dict) -> tuple[bool, int, float]: """Execute the verse casting phase of combat. Player types the verse text. If correct, damage is dealt to demon. If wrong, player takes damage. Supports hints and partial credit. Args: player: Player state dictionary. demon: Current demon dictionary. verse: Selected verse dictionary. Returns: Tuple of (success: bool, damage_dealt: int, elapsed_time: float). """ print(f"\n--- Casting {verse['id']} ({verse['category']}) ---") print(f"Reference: {verse['reference']}") print(f'\nVerse to type:\n"{verse["text"]}"') print("\nType the verse above (or press H for hint):") start_time = time.time() typed = input("> ") elapsed = time.time() - start_time # Handle hint requests during input while typed.upper() == "H": print(f"\nHint: {get_hint(verse['text'])}") print("Type the verse (or press H for hint again):") typed = input("> ") typed_normalized = normalize_text(typed) verse_normalized = normalize_text(verse["text"]) if typed_normalized == verse_normalized: damage = verse["difficulty"] * 5 if verse["category"] in demon["weaknesses"]: damage = damage * 2 return True, damage, elapsed similarity = calculate_similarity(typed_normalized, verse_normalized) if similarity >= 0.8: print(f"\nPartial match ({similarity:.0%} correct). Retry allowed.") print(f'Verse: "{verse["text"]}"') print("Try again:") typed = input("> ") typed_normalized = normalize_text(typed) elapsed = time.time() - start_time if typed_normalized == verse_normalized: damage = verse["difficulty"] * 3 if verse["category"] in demon["weaknesses"]: damage = damage * 2 return True, damage, elapsed # Failed cast player["health"] -= demon["damage"] player["score"] -= verse["difficulty"] * 2 print(f"\nSpell failed! ({similarity:.0%} match)") print(f"Demon attacks! You take {demon['damage']} damage.") if player["health"] < 0: player["health"] = 0 return False, 0, elapsed def memory_test(player: dict, demon: dict, verse: dict) -> tuple[bool, int]: """Execute the memory test phase after successful casting. Player must recall and type the verse from memory. Success defeats demon. Failure deals damage to player with retry chances. Args: player: Player state dictionary. demon: Current demon dictionary. verse: Verse that was successfully cast. Returns: Tuple of (success: bool, bonus_damage: int). """ print("\n--- Memory Test ---") print("Type the verse from memory (do not look at it!):") print("(Press H for hint if needed)") attempts = 3 max_iterations = 10 iteration = 0 while attempts > 0 and iteration < max_iterations: iteration += 1 typed = input("> ") if typed.upper() == "H": print(f"\nHint: {get_hint(verse['text'])}") print("Type the verse from memory:") continue typed_normalized = normalize_text(typed) verse_normalized = normalize_text(verse["text"]) if typed_normalized == verse_normalized: bonus = verse["difficulty"] * 10 if verse["category"] in demon["weaknesses"]: bonus = bonus * 2 print("\nMemory test passed! Powerful strike!") return True, bonus similarity = calculate_similarity(typed_normalized, verse_normalized) if similarity >= 0.8: attempts -= 1 print(f"\nClose ({similarity:.0%} correct). One more retry.") print("Try again:") continue attempts -= 1 player["health"] -= demon["damage"] player["score"] -= 5 if player["health"] < 0: player["health"] = 0 if attempts > 0: print(f"\nMemory test failed! Demon attacks! ({attempts} attempts left)") print("Try again (press H for hint):") print("\nMemory test failed! The demon resists.") return False, 0 def unlock_new_verses(player: dict, all_verses: list, unlocked: list) -> list: """Unlock new verses based on demons defeated count. Progression: - Start: difficulty 1 verses (4 verses) - After 1 demon: unlock difficulty 2 - After 2 demons: unlock difficulty 3 - After 3 demons: unlock difficulty 4 - After 4 demons: unlock difficulty 5 Args: player: Player state dictionary. all_verses: Full list of all verses. unlocked: Current list of unlocked verse IDs. Returns: Updated list of unlocked verse IDs. """ defeated = player["demons_defeated"] new_unlocks = [] if defeated >= 1: max_diff = 2 elif defeated >= 2: max_diff = 3 elif defeated >= 3: max_diff = 4 elif defeated >= 4: max_diff = 5 else: max_diff = 1 for verse in all_verses: if verse["difficulty"] <= max_diff and verse["id"] not in unlocked: unlocked.append(verse["id"]) new_unlocks.append(verse["id"]) if new_unlocks: print(f"\n*** New verses unlocked: {', '.join(new_unlocks)} ***") return unlocked def run_encounter(player: dict, all_verses: list, unlocked: list, demon_index: int) -> list: """Run a full demon encounter including casting and memory test phases. Args: player: Player state dictionary. all_verses: Full list of all verses. unlocked: List of unlocked verse IDs. demon_index: Index into DEMONS list for this encounter. Returns: Updated list of unlocked verse IDs. """ demon_data = DEMONS[demon_index % len(DEMONS)] cycle = demon_index // len(DEMONS) scale = 1 + cycle * 0.25 demon = { "name": demon_data["name"], "health": int(demon_data["health"] * scale), "max_health": int(demon_data["health"] * scale), "weaknesses": demon_data["weaknesses"], "damage": int(demon_data["damage"] * scale), } available = [v for v in all_verses if v["id"] in unlocked] print(f"\nA {demon['name']} emerges from the darkness!") time.sleep(1) while demon["health"] > 0 and player["health"] > 0: show_status(player, demon, all_verses, unlocked) if not available: print("No verses available! Game Over.") player["health"] = 0 break print("Select a verse to cast (enter number):") choice = input("> ").strip() try: idx = int(choice) - 1 if idx < 0 or idx >= len(available): print("Invalid selection. Try again.") continue selected = available[idx] except ValueError: print("Please enter a number.") continue success, damage, elapsed = cast_verse(player, demon, selected) if player["health"] <= 0: break if success: demon["health"] -= damage time_bonus = 5 if elapsed < 10 else (3 if elapsed < 20 else (1 if elapsed < 30 else 0)) player["score"] += selected["difficulty"] * 10 + time_bonus print(f"\nSpell successful! Dealt {damage} damage.") print(f"Time: {elapsed:.1f}s (+{time_bonus} bonus)") if demon["health"] <= 0: demon["health"] = 0 print(f"\nThe {demon['name']} is weakened!") if player.get("memory_mode"): mem_success, bonus = memory_test(player, demon, selected) if mem_success: demon["health"] -= bonus player["score"] += bonus if demon["health"] < 0: demon["health"] = 0 if player["health"] <= 0: break if demon["health"] <= 0: break input("\nPress Enter to continue...") if demon["health"] <= 0: player["demons_defeated"] += 1 player["score"] += 50 print(f"\n*** {demon['name']} has been vanquished! ***") print(f"Score: {player['score']}") if player["demons_defeated"] >= 5 and not player.get("memory_mode"): player["memory_mode"] = True print("*** Memory Mode unlocked! Memory tests now active. ***") unlocked = unlock_new_verses(player, all_verses, unlocked) input("\nPress Enter for next encounter...") elif player["health"] <= 0: print(f"\nThe {demon['name']} overwhelms you...") return unlocked def game_over(player: dict) -> None: """Display the game over screen with final statistics. Args: player: Final player state dictionary. """ clear_screen() print("=" * 50) print(" GAME OVER") print("=" * 50) print() print(f"Final Score: {player['score']}") print(f"Demons Vanquished: {player['demons_defeated']}") verses_known = player.get("verses_unlocked", 0) print(f"Verses Learned: {verses_known}") memory_active = "Yes" if player.get("memory_mode") else "No" print(f"Memory Mode: {memory_active}") print() print("The darkness recedes... for now.") print("May your faith remain unbroken.") print() print("=" * 50) def main() -> None: """Main entry point for the Arcane Purge game. Initializes the game state, loads verses, and runs the main game loop through demon encounters until the player is defeated. """ all_verses = load_verses() unlocked = [v["id"] for v in all_verses if v["difficulty"] == 1] player = { "health": 100, "max_health": 100, "score": 0, "demons_defeated": 0, "verses_unlocked": len(unlocked), "memory_mode": False, } clear_screen() print("=" * 50) print(" ARCANED PURGE") print("=" * 50) print() print("You are an exorcist armed with the power of Scripture.") print("Type Bible verses to cast spells against demonic forces.") print("Match the verse exactly to deal damage.") print("Pass memory tests to deliver the final blow.") print() print(f"You start with {len(unlocked)} verses unlocked.") print("Defeat demons to unlock more powerful verses.") print() input("Press Enter to begin...") demon_index = 0 while player["health"] > 0: clear_screen() unlocked = run_encounter(player, all_verses, unlocked, demon_index) player["verses_unlocked"] = len(unlocked) demon_index += 1 game_over(player) input("\nPress Enter to exit...") if __name__ == "__main__": main()