Spaces:
Running
Running
Update backend/agents/puzzle_agent.py
Browse files- backend/agents/puzzle_agent.py +56 -32
backend/agents/puzzle_agent.py
CHANGED
|
@@ -31,7 +31,16 @@ class PuzzleAgent:
|
|
| 31 |
}
|
| 32 |
|
| 33 |
self.high_rated_levels = []
|
| 34 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
def _generate_maze_grid(self, size: int) -> list:
|
| 36 |
"""Generates a perfect, guaranteed solvable maze using Recursive Backtracking."""
|
| 37 |
# Force size to be odd for perfect wall alignment
|
|
@@ -82,10 +91,22 @@ class PuzzleAgent:
|
|
| 82 |
if len(self.high_rated_levels) > 50: self.high_rated_levels.pop(0)
|
| 83 |
self.high_rated_levels.append(level_data)
|
| 84 |
|
| 85 |
-
def _get_system_prompt(self, difficulty: str, specific_topic: str, generated_grids: list) -> str:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
base_prompt = f"""
|
| 87 |
You are the Lead Game Designer for 'CodeCracker', an educational coding game for ages 10-14.
|
| 88 |
-
Your task is to generate a UNIQUE
|
|
|
|
|
|
|
| 89 |
|
| 90 |
CURRENT SETTINGS:
|
| 91 |
- Mode: maze
|
|
@@ -100,43 +121,45 @@ class PuzzleAgent:
|
|
| 100 |
Grid 4: {json.dumps(generated_grids[3])}
|
| 101 |
Grid 5: {json.dumps(generated_grids[4])}
|
| 102 |
|
|
|
|
| 103 |
- 0 = Path
|
| 104 |
- 1 = Wall
|
| 105 |
-
- 2 = Start
|
| 106 |
-
- 3 = Goal
|
| 107 |
-
- 4 = Hazard
|
|
|
|
|
|
|
| 108 |
|
| 109 |
-
|
| 110 |
-
|
|
|
|
|
|
|
| 111 |
|
| 112 |
DYNAMIC PROCEDURAL UI INSTRUCTION (THEME PALETTE):
|
| 113 |
-
You must act as a UI Designer.
|
| 114 |
-
It MUST be returned in the root JSON.
|
| 115 |
-
|
| 116 |
-
OUTPUT FORMAT: RAW JSON ONLY (No Markdown).
|
| 117 |
|
| 118 |
REQUIRED JSON STRUCTURE:
|
| 119 |
{{
|
| 120 |
"story_arc_title": "Epic Title",
|
| 121 |
"theme_palette": {{
|
| 122 |
-
"background_gradient": ["#
|
| 123 |
-
"wall_color": "#
|
| 124 |
-
"path_color": "#
|
| 125 |
-
"hazard_glow": "#
|
| 126 |
}},
|
| 127 |
"levels": [
|
| 128 |
{{
|
| 129 |
"type": "maze",
|
| 130 |
-
"level_id": "
|
| 131 |
-
"title": "Level
|
| 132 |
-
"story": "
|
| 133 |
-
"concept_tutorial": "
|
| 134 |
-
"goal_description": "
|
| 135 |
-
"grid_layout": [[...]], // MUST USE
|
| 136 |
"allowed_blocks": ["move_forward", "turn_left", "turn_right", "jump"],
|
| 137 |
"optimal_steps": 10,
|
| 138 |
-
"hint_1": "
|
| 139 |
-
"hint_2": "
|
| 140 |
}}
|
| 141 |
]
|
| 142 |
}}
|
|
@@ -144,18 +167,19 @@ class PuzzleAgent:
|
|
| 144 |
return base_prompt
|
| 145 |
|
| 146 |
def generate_level(self, difficulty: str, specific_topic: Optional[str] = None) -> Dict[str, Any]:
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
|
|
|
|
|
|
|
|
|
| 150 |
|
| 151 |
# Generate 5 progressively harder mathematical grids
|
| 152 |
-
base_size = 5
|
| 153 |
-
if difficulty == "Intermediate": base_size = 7
|
| 154 |
-
elif difficulty == "Expert": base_size = 9
|
| 155 |
|
| 156 |
perfect_grids = []
|
| 157 |
for i in range(5):
|
| 158 |
-
size = base_size + (2 if i >= 3 else 0)
|
| 159 |
perfect_grids.append(self._generate_maze_grid(size))
|
| 160 |
|
| 161 |
cache_key = f"maze_{difficulty}_{specific_topic}"
|
|
@@ -164,7 +188,7 @@ class PuzzleAgent:
|
|
| 164 |
if time.time() - entry['timestamp'] < self.cache_duration:
|
| 165 |
return entry['data']
|
| 166 |
|
| 167 |
-
prompt = self._get_system_prompt(difficulty, specific_topic, perfect_grids)
|
| 168 |
|
| 169 |
try:
|
| 170 |
if not self.model: raise Exception("No API Key")
|
|
|
|
| 31 |
}
|
| 32 |
|
| 33 |
self.high_rated_levels = []
|
| 34 |
+
|
| 35 |
+
# Load the 500 Logic Scenarios
|
| 36 |
+
self.scenarios_path = os.path.join(os.getcwd(), "data", "maze_scenarios_500.json")
|
| 37 |
+
self.scenarios = []
|
| 38 |
+
try:
|
| 39 |
+
with open(self.scenarios_path, 'r') as f:
|
| 40 |
+
data = json.load(f)
|
| 41 |
+
self.scenarios = data.get("scenarios", [])
|
| 42 |
+
except Exception as e:
|
| 43 |
+
print(f"WARNING: Could not load maze_scenarios_500.json: {e}")
|
| 44 |
def _generate_maze_grid(self, size: int) -> list:
|
| 45 |
"""Generates a perfect, guaranteed solvable maze using Recursive Backtracking."""
|
| 46 |
# Force size to be odd for perfect wall alignment
|
|
|
|
| 91 |
if len(self.high_rated_levels) > 50: self.high_rated_levels.pop(0)
|
| 92 |
self.high_rated_levels.append(level_data)
|
| 93 |
|
| 94 |
+
def _get_system_prompt(self, difficulty: str, specific_topic: str, generated_grids: list, scenario_context: Dict = None) -> str:
|
| 95 |
+
mission_context = ""
|
| 96 |
+
if scenario_context:
|
| 97 |
+
mission_context = f"""
|
| 98 |
+
MISSION SCENARIO:
|
| 99 |
+
- Goal: {scenario_context.get('title')}
|
| 100 |
+
- Objective: {scenario_context.get('story')}
|
| 101 |
+
- Logic Steps: {', '.join(scenario_context.get('required_steps', []))}
|
| 102 |
+
- Objective: You MUST incorporate these logic steps into the 5-level story arc.
|
| 103 |
+
"""
|
| 104 |
+
|
| 105 |
base_prompt = f"""
|
| 106 |
You are the Lead Game Designer for 'CodeCracker', an educational coding game for ages 10-14.
|
| 107 |
+
Your task is to generate a UNIQUE MISSION ARC of EXACTLY 5 interconnected game levels in JSON format.
|
| 108 |
+
|
| 109 |
+
{mission_context}
|
| 110 |
|
| 111 |
CURRENT SETTINGS:
|
| 112 |
- Mode: maze
|
|
|
|
| 121 |
Grid 4: {json.dumps(generated_grids[3])}
|
| 122 |
Grid 5: {json.dumps(generated_grids[4])}
|
| 123 |
|
| 124 |
+
TILE ID SCHEMA:
|
| 125 |
- 0 = Path
|
| 126 |
- 1 = Wall
|
| 127 |
+
- 2 = Start (Captain Cody)
|
| 128 |
+
- 3 = Goal / Final Mission Point
|
| 129 |
+
- 4 = Hazard (☠️ Skull) - Must be avoided/jumped!
|
| 130 |
+
- 5 = Mission Item (Locker/Key/Terminal) - Must be visited!
|
| 131 |
+
- 8 = NPC / Expert (Manager/Scientist) - Must be visited!
|
| 132 |
|
| 133 |
+
LOGIC RULES:
|
| 134 |
+
1. Level 1 must introduce the mission.
|
| 135 |
+
2. Progressively place Tile IDs 5 and 8 in the grids for later levels to represent mission steps.
|
| 136 |
+
3. You MUST assign Grid 1 to Level 1, Grid 2 to Level 2, etc.
|
| 137 |
|
| 138 |
DYNAMIC PROCEDURAL UI INSTRUCTION (THEME PALETTE):
|
| 139 |
+
You must act as a UI Designer. Generate a modern Glassmorphism hex color palette based on the {specific_topic} and {scenario_context.get('theme') if scenario_context else 'Space'} story.
|
|
|
|
|
|
|
|
|
|
| 140 |
|
| 141 |
REQUIRED JSON STRUCTURE:
|
| 142 |
{{
|
| 143 |
"story_arc_title": "Epic Title",
|
| 144 |
"theme_palette": {{
|
| 145 |
+
"background_gradient": ["#HEX", "#HEX"],
|
| 146 |
+
"wall_color": "#HEX",
|
| 147 |
+
"path_color": "#HEX",
|
| 148 |
+
"hazard_glow": "#HEX"
|
| 149 |
}},
|
| 150 |
"levels": [
|
| 151 |
{{
|
| 152 |
"type": "maze",
|
| 153 |
+
"level_id": "UUID",
|
| 154 |
+
"title": "Level Name",
|
| 155 |
+
"story": "Mission-driven story based on the scenario steps.",
|
| 156 |
+
"concept_tutorial": "Explain the logic concept.",
|
| 157 |
+
"goal_description": "Objective (e.g. 'Go to the Manager (8) first, then the Locker (5)')",
|
| 158 |
+
"grid_layout": [[...]], // MUST USE PROVIDED GRIDS
|
| 159 |
"allowed_blocks": ["move_forward", "turn_left", "turn_right", "jump"],
|
| 160 |
"optimal_steps": 10,
|
| 161 |
+
"hint_1": "Logic hint",
|
| 162 |
+
"hint_2": "Hazard hint"
|
| 163 |
}}
|
| 164 |
]
|
| 165 |
}}
|
|
|
|
| 167 |
return base_prompt
|
| 168 |
|
| 169 |
def generate_level(self, difficulty: str, specific_topic: Optional[str] = None) -> Dict[str, Any]:
|
| 170 |
+
# Pick a random Logic Scenario context from the JSON
|
| 171 |
+
scenario = {}
|
| 172 |
+
if self.scenarios:
|
| 173 |
+
filtered = [s for s in self.scenarios if s.get('difficulty', '').lower() == difficulty.lower()]
|
| 174 |
+
scenario = random.choice(filtered if filtered else self.scenarios)
|
| 175 |
+
specific_topic = scenario.get('theme', specific_topic)
|
| 176 |
|
| 177 |
# Generate 5 progressively harder mathematical grids
|
| 178 |
+
base_size = scenario.get('grid_size', 5) if scenario else 5
|
|
|
|
|
|
|
| 179 |
|
| 180 |
perfect_grids = []
|
| 181 |
for i in range(5):
|
| 182 |
+
size = base_size + (2 if i >= 3 else 0)
|
| 183 |
perfect_grids.append(self._generate_maze_grid(size))
|
| 184 |
|
| 185 |
cache_key = f"maze_{difficulty}_{specific_topic}"
|
|
|
|
| 188 |
if time.time() - entry['timestamp'] < self.cache_duration:
|
| 189 |
return entry['data']
|
| 190 |
|
| 191 |
+
prompt = self._get_system_prompt(difficulty, specific_topic, perfect_grids, scenario)
|
| 192 |
|
| 193 |
try:
|
| 194 |
if not self.model: raise Exception("No API Key")
|