WebashalarForML commited on
Commit
3d3703f
Β·
verified Β·
1 Parent(s): 638b051

Upload 24 files

Browse files
utils/action_node.py ADDED
@@ -0,0 +1,452 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import uuid
3
+ import logging
4
+ from typing import Dict, Any, List
5
+
6
+ # Assume GameState is a TypedDict or similar for clarity
7
+ # from typing import TypedDict
8
+ # class GameState(TypedDict):
9
+ # description: str
10
+ # project_json: Dict[str, Any]
11
+ # action_plan: Dict[str, Any]
12
+ # sprite_initial_positions: Dict[str, Any]
13
+
14
+ # Placeholder for actual GameState in your application
15
+ GameState = Dict[str, Any]
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ # --- Mock Agent for demonstration ---
20
+ class MockAgent:
21
+ def invoke(self, payload: Dict[str, Any]) -> Dict[str, Any]:
22
+ """
23
+ Mocks an LLM agent invocation. In a real scenario, this would call your
24
+ actual LLM API (e.g., through LangChain, LlamaIndex, etc.).
25
+ """
26
+ user_message = payload["messages"][-1]["content"]
27
+ print(f"\n--- Mock Agent Received Prompt (partial) ---\n{user_message[:500]}...\n------------------------------------------")
28
+
29
+ # Simplified mock responses for demonstration purposes
30
+ # In a real scenario, the LLM would generate actual Scratch block JSON
31
+ if "Propose a high-level action flow" in user_message:
32
+ return {
33
+ "messages": [{
34
+ "content": json.dumps({
35
+ "action_overall_flow": {
36
+ "Sprite1": {
37
+ "description": "Basic movement and interaction",
38
+ "plans": [
39
+ {
40
+ "event": "when flag clicked",
41
+ "logic": "forever loop: move 10 steps, if touching Edge then turn 15 degrees"
42
+ },
43
+ {
44
+ "event": "when space key pressed",
45
+ "logic": "say Hello! for 2 seconds"
46
+ }
47
+ ]
48
+ },
49
+ "Ball": {
50
+ "description": "Simple bouncing behavior",
51
+ "plans": [
52
+ {
53
+ "event": "when flag clicked",
54
+ "logic": "move 5 steps, if on edge bounce"
55
+ }
56
+ ]
57
+ }
58
+ }
59
+ })
60
+ }]
61
+ }
62
+ elif "You are an AI assistant generating Scratch 3.0 block JSON" in user_message:
63
+ # This mock response is highly simplified. A real LLM would generate
64
+ # valid Scratch blocks based on the provided relevant catalog and plan.
65
+ # We're just demonstrating the *mechanism* of filtering the catalog.
66
+ if "Sprite1" in user_message:
67
+ return {
68
+ "messages": [{
69
+ "content": json.dumps({
70
+ f"block_id_{generate_block_id()}": {
71
+ "opcode": "event_whenflagclicked",
72
+ "next": f"block_id_{generate_block_id()}_forever",
73
+ "parent": None,
74
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True, "x": 100, "y": 100
75
+ },
76
+ f"block_id_{generate_block_id()}_forever": {
77
+ "opcode": "control_forever",
78
+ "next": None,
79
+ "parent": f"block_id_{generate_block_id()}",
80
+ "inputs": {
81
+ "SUBSTACK": [2, f"block_id_{generate_block_id()}_move"]
82
+ }, "fields": {}, "shadow": False, "topLevel": False
83
+ },
84
+ f"block_id_{generate_block_id()}_move": {
85
+ "opcode": "motion_movesteps",
86
+ "next": f"block_id_{generate_block_id()}_if",
87
+ "parent": f"block_id_{generate_block_id()}_forever",
88
+ "inputs": {
89
+ "STEPS": [1, [4, "10"]]
90
+ }, "fields": {}, "shadow": False, "topLevel": False
91
+ },
92
+ f"block_id_{generate_block_id()}_if": {
93
+ "opcode": "control_if",
94
+ "next": None,
95
+ "parent": f"block_id_{generate_block_id()}_forever",
96
+ "inputs": {
97
+ "CONDITION": [2, f"block_id_{generate_block_id()}_touching"],
98
+ "SUBSTACK": [2, f"block_id_{generate_block_id()}_turn"]
99
+ }, "fields": {}, "shadow": False, "topLevel": False
100
+ },
101
+ f"block_id_{generate_block_id()}_touching": {
102
+ "opcode": "sensing_touchingobject",
103
+ "next": None,
104
+ "parent": f"block_id_{generate_block_id()}_if",
105
+ "inputs": {
106
+ "TOUCHINGOBJECTMENU": [1, f"block_id_{generate_block_id()}_touching_menu"]
107
+ }, "fields": {}, "shadow": False, "topLevel": False
108
+ },
109
+ f"block_id_{generate_block_id()}_touching_menu": {
110
+ "opcode": "sensing_touchingobjectmenu",
111
+ "next": None,
112
+ "parent": f"block_id_{generate_block_id()}_touching",
113
+ "inputs": {},
114
+ "fields": {"TOUCHINGOBJECTMENU": ["_edge_", None]},
115
+ "shadow": True, "topLevel": False
116
+ },
117
+ f"block_id_{generate_block_id()}_turn": {
118
+ "opcode": "motion_turnright",
119
+ "next": None,
120
+ "parent": f"block_id_{generate_block_id()}_if",
121
+ "inputs": {
122
+ "DEGREES": [1, [4, "15"]]
123
+ }, "fields": {}, "shadow": False, "topLevel": False
124
+ },
125
+ f"block_id_{generate_block_id()}_say": {
126
+ "opcode": "looks_sayforsecs",
127
+ "next": None,
128
+ "parent": None, # This block would typically be part of a separate script
129
+ "inputs": {
130
+ "MESSAGE": [1, [10, "Hello!"]],
131
+ "SECS": [1, [4, "2"]]
132
+ }, "fields": {}, "shadow": False, "topLevel": True, "x": 300, "y": 100
133
+ }
134
+ })
135
+ }]
136
+ }
137
+ elif "Ball" in user_message:
138
+ return {
139
+ "messages": [{
140
+ "content": json.dumps({
141
+ f"block_id_{generate_block_id()}": {
142
+ "opcode": "event_whenflagclicked",
143
+ "next": f"block_id_{generate_block_id()}_moveball",
144
+ "parent": None,
145
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True, "x": 100, "y": 100
146
+ },
147
+ f"block_id_{generate_block_id()}_moveball": {
148
+ "opcode": "motion_movesteps",
149
+ "next": f"block_id_{generate_block_id()}_edgebounce",
150
+ "parent": f"block_id_{generate_block_id()}",
151
+ "inputs": {
152
+ "STEPS": [1, [4, "5"]]
153
+ }, "fields": {}, "shadow": False, "topLevel": False
154
+ },
155
+ f"block_id_{generate_block_id()}_edgebounce": {
156
+ "opcode": "motion_ifonedgebounce",
157
+ "next": None,
158
+ "parent": f"block_id_{generate_block_id()}_moveball",
159
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": False
160
+ }
161
+ })
162
+ }]
163
+ }
164
+ return {"messages": [{"content": "[]"}]} # Default empty response
165
+
166
+ agent = MockAgent()
167
+
168
+ # Helper function to generate a unique block ID
169
+ def generate_block_id():
170
+ return str(uuid.uuid4())[:10].replace('-', '') # Shorten for readability, ensure uniqueness
171
+
172
+ # Placeholder for your extract_json_from_llm_response function
173
+ def extract_json_from_llm_response(response_string):
174
+ try:
175
+ # Assuming the LLM response is ONLY the JSON string within triple backticks
176
+ json_match = response_string.strip().replace("```json", "").replace("```", "").strip()
177
+ return json.loads(json_match)
178
+ except json.JSONDecodeError as e:
179
+ logger.error(f"Failed to decode JSON from LLM response: {e}")
180
+ logger.error(f"Raw response: {response_string}")
181
+ raise ValueError("Invalid JSON response from LLM")
182
+
183
+ # --- GLOBAL CATALOG OF ALL SCRATCH BLOCKS ---
184
+ # This is where you would load your block_content.json
185
+ # For demonstration, I'm using your provided snippets and adding some common ones.
186
+ # In a real application, you'd load this once at startup.
187
+ ALL_SCRATCH_BLOCKS_CATALOG = {
188
+ "motion_movesteps": {
189
+ "opcode": "motion_movesteps", "next": None, "parent": None,
190
+ "inputs": {"STEPS": [1, [4, "10"]]}, "fields": {}, "shadow": False, "topLevel": True, "x": 464, "y": -416
191
+ },
192
+ "motion_turnright": {
193
+ "opcode": "motion_turnright", "next": None, "parent": None,
194
+ "inputs": {"DEGREES": [1, [4, "15"]]}, "fields": {}, "shadow": False, "topLevel": True, "x": 467, "y": -316
195
+ },
196
+ "motion_ifonedgebounce": {
197
+ "opcode": "motion_ifonedgebounce", "next": None, "parent": None,
198
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True, "x": 467, "y": -316
199
+ },
200
+ "event_whenflagclicked": {
201
+ "opcode": "event_whenflagclicked", "next": None, "parent": None,
202
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True, "x": 10, "y": 10
203
+ },
204
+ "event_whenkeypressed": {
205
+ "opcode": "event_whenkeypressed", "next": None, "parent": None,
206
+ "inputs": {}, "fields": {"KEY_OPTION": ["space", None]}, "shadow": False, "topLevel": True, "x": 10, "y": 10
207
+ },
208
+ "control_forever": {
209
+ "opcode": "control_forever", "next": None, "parent": None,
210
+ "inputs": {"SUBSTACK": [2, "some_id"]}, "fields": {}, "shadow": False, "topLevel": True, "x": 10, "y": 10
211
+ },
212
+ "control_if": {
213
+ "opcode": "control_if", "next": None, "parent": None,
214
+ "inputs": {"CONDITION": [2, "some_id"], "SUBSTACK": [2, "some_id_2"]}, "fields": {}, "shadow": False, "topLevel": True, "x": 10, "y": 10
215
+ },
216
+ "looks_sayforsecs": {
217
+ "opcode": "looks_sayforsecs", "next": None, "parent": None,
218
+ "inputs": {"MESSAGE": [1, [10, "Hello!"]], "SECS": [1, [4, "2"]]}, "fields": {}, "shadow": False, "topLevel": True, "x": 10, "y": 10
219
+ },
220
+ "looks_say": {
221
+ "opcode": "looks_say", "next": None, "parent": None,
222
+ "inputs": {"MESSAGE": [1, [10, "Hello!"]]}, "fields": {}, "shadow": False, "topLevel": True, "x": 10, "y": 10
223
+ },
224
+ "sensing_touchingobject": {
225
+ "opcode": "sensing_touchingobject", "next": None, "parent": None,
226
+ "inputs": {"TOUCHINGOBJECTMENU": [1, "some_id"]}, "fields": {}, "shadow": False, "topLevel": True, "x": 10, "y": 10
227
+ },
228
+ "sensing_touchingobjectmenu": {
229
+ "opcode": "sensing_touchingobjectmenu", "next": None, "parent": None,
230
+ "inputs": {}, "fields": {"TOUCHINGOBJECTMENU": ["_mouse_", None]}, "shadow": True, "topLevel": True, "x": 10, "y": 10
231
+ },
232
+ # Add more blocks from your block_content.json here...
233
+ }
234
+
235
+ # --- Heuristic-based block selection ---
236
+ def get_relevant_blocks_for_plan(action_plan: Dict[str, Any], all_blocks_catalog: Dict[str, Any]) -> Dict[str, Any]:
237
+ """
238
+ Analyzes the natural language action plan and selects relevant Scratch blocks
239
+ from the comprehensive catalog. This is a heuristic approach and might need
240
+ to be refined based on your specific use cases and LLM capabilities.
241
+ """
242
+ relevant_opcodes = set()
243
+
244
+ # Always include common event blocks
245
+ relevant_opcodes.add("event_whenflagclicked")
246
+ relevant_opcodes.add("event_whenkeypressed") # Could be more specific if key is mentioned
247
+
248
+ # Keyword to opcode mapping (can be expanded)
249
+ keyword_map = {
250
+ "move": "motion_movesteps",
251
+ "steps": "motion_movesteps",
252
+ "turn": "motion_turnright",
253
+ "rotate": "motion_turnright",
254
+ "bounce": "motion_ifonedgebounce",
255
+ "edge": "motion_ifonedgebounce",
256
+ "forever": "control_forever",
257
+ "loop": "control_forever",
258
+ "if": "control_if",
259
+ "condition": "control_if",
260
+ "say": "looks_say",
261
+ "hello": "looks_say", # Simple example, might need more context
262
+ "touching": "sensing_touchingobject",
263
+ "mouse pointer": "sensing_touchingobjectmenu",
264
+ "edge": "sensing_touchingobjectmenu", # For touching edge
265
+ }
266
+
267
+ # Iterate through the action plan to find keywords
268
+ for sprite_name, sprite_actions in action_plan.get("action_overall_flow", {}).items():
269
+ for plan in sprite_actions.get("plans", []):
270
+ event_logic = plan.get("event", "").lower() + " " + plan.get("logic", "").lower()
271
+
272
+ # Check for direct opcode matches (if the LLM somehow outputs opcodes in its plan)
273
+ for opcode in all_blocks_catalog.keys():
274
+ if opcode in event_logic:
275
+ relevant_opcodes.add(opcode)
276
+
277
+ # Check for keywords
278
+ for keyword, opcode in keyword_map.items():
279
+ if keyword in event_logic:
280
+ relevant_opcodes.add(opcode)
281
+ # Add associated shadow blocks if known
282
+ if opcode == "sensing_touchingobject":
283
+ relevant_opcodes.add("sensing_touchingobjectmenu")
284
+ if opcode == "event_whenkeypressed":
285
+ relevant_opcodes.add("event_whenkeypressed") # It's already there but good to be explicit
286
+
287
+ # Construct the filtered catalog
288
+ relevant_blocks_catalog = {
289
+ opcode: all_blocks_catalog[opcode]
290
+ for opcode in relevant_opcodes if opcode in all_blocks_catalog
291
+ }
292
+ return relevant_blocks_catalog
293
+
294
+ # --- New Action Planning Node ---
295
+ def plan_sprite_actions(state: GameState):
296
+ logger.info("--- Running PlanSpriteActionsNode ---")
297
+
298
+ planning_prompt = (
299
+ f"You are an AI assistant tasked with planning Scratch 3.0 block code for a game. "
300
+ f"The game description is: '{state['description']}'.\n\n"
301
+ f"Here are the sprites currently in the project: {', '.join(target['name'] for target in state['project_json']['targets'] if not target['isStage']) if len(state['project_json']['targets']) > 1 else 'None'}.\n"
302
+ f"Initial positions: {json.dumps(state.get('sprite_initial_positions', {}), indent=2)}\n\n"
303
+ f"Consider the main actions and interactions required for each sprite. "
304
+ f"Think step-by-step about what each sprite needs to *do*, *when* it needs to do it (events), "
305
+ f"and if any actions need to *repeat* or depend on *conditions*.\n\n"
306
+ f"Propose a high-level action flow for each sprite in the following JSON format. "
307
+ f"Do NOT generate Scratch block JSON yet. Only describe the logic using natural language or simplified pseudo-code.\n\n"
308
+ f"Example format:\n"
309
+ f"```json\n"
310
+ f"{{\n"
311
+ f" \"action_overall_flow\": {{\n"
312
+ f" \"Sprite1\": {{\n"
313
+ f" \"description\": \"Main character actions\",\n"
314
+ f" \"plans\": [\n"
315
+ f" {{\n"
316
+ f" \"event\": \"when flag clicked\",\n"
317
+ f" \"logic\": \"forever loop: move 10 steps, if on edge bounce\"\n"
318
+ f" }},\n"
319
+ f" {{\n"
320
+ f" \"event\": \"when space key pressed\",\n"
321
+ f" \"logic\": \"change y by 10, wait 0.1 seconds, change y by -10\"\n"
322
+ f" }}\n"
323
+ f" ]\n"
324
+ f" }},\n"
325
+ f" \"Ball\": {{\n"
326
+ f" \"description\": \"Projectile movement\",\n"
327
+ f" \"plans\": [\n"
328
+ f" {{\n"
329
+ f" \"event\": \"when I start as a clone\",\n"
330
+ f" \"logic\": \"glide 1 sec to random position, if touching Sprite1 then stop this script\"\n"
331
+ f" }}\n"
332
+ f" ]\n"
333
+ f" }}\n"
334
+ f" }}\n"
335
+ f"}}\n"
336
+ f"```\n\n"
337
+ f"Return ONLY the JSON object for the action overall flow."
338
+ )
339
+
340
+ try:
341
+ response = agent.invoke({"messages": [{"role": "user", "content": planning_prompt}]})
342
+ raw_response = response["messages"][-1].content
343
+ print("Raw response from LLM [PlanSpriteActionsNode]:", raw_response)
344
+ action_plan = extract_json_from_llm_response(raw_response)
345
+ logger.info("Sprite action plan generated by PlanSpriteActionsNode.")
346
+ return {"action_plan": action_plan}
347
+ except Exception as e:
348
+ logger.error(f"Error in PlanSpriteActionsNode: {e}")
349
+ raise
350
+
351
+ # --- Updated Action Node Builder (to consume the plan and build blocks) ---
352
+ def build_action_nodes(state: GameState):
353
+ logger.info("--- Running ActionNodeBuilder ---")
354
+
355
+ action_plan = state.get("action_plan", {})
356
+ if not action_plan:
357
+ raise ValueError("No action plan found in state. Run PlanSpriteActionsNode first.")
358
+
359
+ # Convert the Scratch project JSON to a mutable Python object
360
+ project_json = state["project_json"]
361
+ targets = project_json["targets"]
362
+
363
+ # We need a way to map sprite names to their actual target objects in project_json
364
+ sprite_map = {target["name"]: target for target in targets if not target["isStage"]}
365
+
366
+ # --- NEW: Get only the relevant blocks for the entire action plan ---
367
+ relevant_scratch_blocks_catalog = get_relevant_blocks_for_plan(action_plan, ALL_SCRATCH_BLOCKS_CATALOG)
368
+ logger.info(f"Filtered {len(relevant_scratch_blocks_catalog)} relevant blocks out of {len(ALL_SCRATCH_BLOCKS_CATALOG)} total.")
369
+
370
+
371
+ # Iterate through the planned actions for each sprite
372
+ for sprite_name, sprite_actions in action_plan.get("action_overall_flow", {}).items():
373
+ if sprite_name in sprite_map:
374
+ current_sprite_target = sprite_map[sprite_name]
375
+ # Ensure 'blocks' field exists for the sprite
376
+ if "blocks" not in current_sprite_target:
377
+ current_sprite_target["blocks"] = {}
378
+
379
+ # Generate block JSON based on the detailed action plan for this sprite
380
+ # This is where the LLM's role becomes crucial: translating logic to blocks
381
+ llm_block_generation_prompt = (
382
+ f"You are an AI assistant generating Scratch 3.0 block JSON based on a provided plan. "
383
+ f"The current sprite is '{sprite_name}'.\n"
384
+ f"Its planned actions are:\n"
385
+ f"```json\n{json.dumps(sprite_actions, indent=2)}\n```\n\n"
386
+ f"Here is a **curated catalog of only the most relevant Scratch 3.0 blocks** for this plan:\n"
387
+ f"```json\n{json.dumps(relevant_scratch_blocks_catalog, indent=2)}\n```\n\n"
388
+ f"Current Scratch project JSON (for context, specifically this sprite's existing blocks if any):\n"
389
+ f"```json\n{json.dumps(current_sprite_target, indent=2)}\n```\n\n"
390
+ f"**Instructions:**\n"
391
+ f"1. For each planned event and its associated logic, generate the corresponding Scratch 3.0 block JSON.\n"
392
+ f"2. **Generate unique block IDs** for every new block. Use a format like 'block_id_abcdef12'.\n" # Updated ID format hint
393
+ f"3. Properly link blocks using `next` and `parent` fields to form execution stacks. Hat blocks (`topLevel: true`, `parent: null`).\n"
394
+ f"4. Correctly fill `inputs` and `fields` based on the catalog and the plan's logic (e.g., specific values for motion, keys for events, conditions for controls).\n"
395
+ f"5. For C-blocks (like `control_repeat`, `control_forever`, `control_if`), use the `SUBSTACK` input to link to the first block inside its loop/conditional.\n"
396
+ f"6. If the plan involves operators (e.g., 'if touching Sprite1'), use the appropriate operator blocks from the catalog and link them correctly as `CONDITION` inputs.\n"
397
+ f"7. Ensure that any shadow blocks (e.g., for dropdowns like `motion_goto_menu`, `sensing_touchingobjectmenu`) are generated with `shadow: true` and linked correctly as inputs to their parent block.\n"
398
+ f"8. Return ONLY the **updated 'blocks' dictionary** for this specific sprite. Do NOT return the full project JSON. ONLY the `blocks` dictionary."
399
+ )
400
+
401
+ try:
402
+ response = agent.invoke({"messages": [{"role": "user", "content": llm_block_generation_prompt}]})
403
+ raw_response = response["messages"][-1].content
404
+ print(f"Raw response from LLM [ActionNodeBuilder - {sprite_name}]:", raw_response)
405
+ generated_blocks = extract_json_from_llm_response(raw_response)
406
+ current_sprite_target["blocks"].update(generated_blocks) # Merge new blocks
407
+ logger.info(f"Action blocks added for sprite '{sprite_name}' by ActionNodeBuilder.")
408
+ except Exception as e:
409
+ logger.error(f"Error generating blocks for sprite '{sprite_name}': {e}")
410
+ # Depending on robustness needed, you might continue or re-raise
411
+ raise
412
+
413
+ return {"project_json": project_json}
414
+
415
+ # --- Example Usage (to demonstrate the flow) ---
416
+ if __name__ == "__main__":
417
+ # Initialize a mock game state
418
+ initial_game_state = {
419
+ "description": "A simple game where a sprite moves and says hello.",
420
+ "project_json": {
421
+ "targets": [
422
+ {"isStage": True, "name": "Stage", "blocks": {}},
423
+ {"isStage": False, "name": "Sprite1", "blocks": {}},
424
+ {"isStage": False, "name": "Ball", "blocks": {}}
425
+ ]
426
+ },
427
+ "sprite_initial_positions": {}
428
+ }
429
+
430
+ # Step 1: Plan Sprite Actions
431
+ try:
432
+ state_after_planning = plan_sprite_actions(initial_game_state)
433
+ initial_game_state.update(state_after_planning)
434
+ print("\n--- Game State After Planning ---")
435
+ print(json.dumps(initial_game_state, indent=2))
436
+ except Exception as e:
437
+ print(f"Planning failed: {e}")
438
+ exit()
439
+
440
+ # Step 2: Build Action Nodes (Generate Blocks)
441
+ try:
442
+ state_after_building = build_action_nodes(initial_game_state)
443
+ initial_game_state.update(state_after_building)
444
+ print("\n--- Game State After Building Blocks ---")
445
+ # Print only the blocks for a specific sprite to keep output manageable
446
+ for target in initial_game_state["project_json"]["targets"]:
447
+ if not target["isStage"]:
448
+ print(f"\nBlocks for {target['name']}:")
449
+ print(json.dumps(target.get('blocks', {}), indent=2))
450
+
451
+ except Exception as e:
452
+ print(f"Building blocks failed: {e}")
utils/agent.py ADDED
@@ -0,0 +1,1647 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #─── Basic imports ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
2
+ import os
3
+ import math
4
+ import sqlite3
5
+ import fitz # PyMuPDF for PDF parsing
6
+ import re
7
+
8
+ from dotenv import load_dotenv
9
+ # Load environment variables from .env file
10
+ load_dotenv() # This line ensures .env variables are loaded
11
+
12
+ from langgraph.graph import START, StateGraph, MessagesState, END
13
+ from langgraph.prebuilt import tools_condition
14
+ from langgraph.prebuilt import ToolNode
15
+ from langgraph.constants import START
16
+ from langchain_core.tools import tool
17
+ from langchain.schema import SystemMessage
18
+ #from langchain.chat_models import init_chat_model
19
+ #from langgraph.prebuilt import create_react_agent
20
+
21
+ from langchain.embeddings import HuggingFaceEmbeddings
22
+ #from langchain.vectorstores import Pinecone
23
+ from langchain.tools.retriever import create_retriever_tool
24
+ #import pinecone
25
+ #from pinecone import Pinecone as PineconeClient, ServerlessSpec
26
+ #from pinecone import Index # the blocking‐call client constructor
27
+ #from pinecone import Pinecone as PineconeClient, ServerlessSpec
28
+ from langchain.embeddings import HuggingFaceEmbeddings
29
+ from langchain_community.vectorstores.pinecone import Pinecone as LC_Pinecone
30
+
31
+ # ─── Langchain Frameworks ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
32
+ #from langchain.tools import Tool
33
+ from langchain.chat_models import ChatOpenAI
34
+ from langchain_groq import ChatGroq
35
+ from langchain_mistralai import ChatMistralAI
36
+ from langchain.agents import initialize_agent, AgentType
37
+ from langchain.schema import Document
38
+ from langchain.chains import RetrievalQA
39
+ from langchain.embeddings import OpenAIEmbeddings
40
+ from langchain_community.embeddings import HuggingFaceEmbeddings
41
+ from langchain.vectorstores import FAISS
42
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
43
+ from langchain.prompts import PromptTemplate
44
+ from langchain_community.document_loaders import TextLoader, PyMuPDFLoader
45
+ from langchain_community.document_loaders.wikipedia import WikipediaLoader
46
+ from langchain_community.document_loaders.arxiv import ArxivLoader
47
+ from langchain_experimental.tools.python.tool import PythonREPLTool
48
+
49
+
50
+ # ─── Memory ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
51
+ from langchain.agents import initialize_agent, AgentType
52
+ from langchain.tools import Tool
53
+ from typing import List, Callable
54
+ from langchain.schema import BaseMemory, AIMessage, HumanMessage, SystemMessage
55
+ from langchain.schema import HumanMessage, SystemMessage
56
+ from langchain.llms.base import LLM
57
+ from langchain.memory.chat_memory import BaseChatMemory
58
+ from pydantic import PrivateAttr
59
+ from langchain_core.messages import get_buffer_string
60
+
61
+ # ─── Image Processing ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
62
+
63
+ from PIL import Image
64
+ import pytesseract
65
+ from transformers import pipeline
66
+ from groq import Groq
67
+ import requests
68
+ from io import BytesIO
69
+ from transformers import pipeline, TrOCRProcessor, VisionEncoderDecoderModel
70
+ import requests
71
+ import base64
72
+ from PIL import UnidentifiedImageError
73
+
74
+ # ─── Browser var ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
75
+ from typing import List, Dict
76
+ import json
77
+ from io import BytesIO
78
+ #from langchain.tools import tool # or langchain_core.tools
79
+ from playwright.sync_api import sync_playwright
80
+ from duckduckgo_search import DDGS
81
+ import time
82
+ import random
83
+ import logging
84
+ from functools import lru_cache, wraps
85
+ import requests
86
+ from playwright.sync_api import sync_playwright
87
+ from bs4 import BeautifulSoup
88
+ import tenacity
89
+ from tenacity import retry, stop_after_attempt, wait_exponential
90
+
91
+ # Initialize logger
92
+ logger = logging.getLogger(__name__)
93
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
94
+
95
+ # Additional imports for new functionality
96
+ import pandas as pd
97
+ from PyPDF2 import PdfReader
98
+ import docx
99
+ import pytesseract
100
+ import speech_recognition as sr
101
+ from pydub import AudioSegment
102
+ from pytube import YouTube
103
+ from newspaper import Article
104
+ from langchain.document_loaders import ArxivLoader
105
+ from langchain_community.document_loaders.youtube import YoutubeLoader, TranscriptFormat
106
+
107
+ from playwright.sync_api import sync_playwright
108
+ # Attempt to import Playwright for dynamic page rendering
109
+ try:
110
+ from playwright.sync_api import sync_playwright
111
+ _playwright_available = True
112
+ except ImportError:
113
+ _playwright_available = False
114
+
115
+ # Define forbidden keywords for basic NSFW filtering
116
+ _forbidden = ["porn", "sex", "xxx", "nude", "erotic"]
117
+
118
+ # ─── LLM Setup ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
119
+ # Load OpenAI API key from environment (required for LLM and embeddings)
120
+
121
+ # API Keys from .env file
122
+ os.environ.setdefault("OPENAI_API_KEY", "<YOUR_OPENAI_KEY>") # Set your own key or env var
123
+ os.environ["GROQ_API_KEY"] = os.getenv("GROQ_API_KEY", "default_key_or_placeholder")
124
+ os.environ["MISTRAL_API_KEY"] = os.getenv("MISTRAL_API_KEY", "default_key_or_placeholder")
125
+
126
+ # Tavily API Key
127
+ TAVILY_API_KEY = os.getenv("TAVILY_API_KEY", "default_key_or_placeholder")
128
+ _forbidden = ["nsfw", "porn", "sex", "explicit"]
129
+ _playwright_available = True # set False to disable Playwright
130
+
131
+ # Globals for RAG system
132
+ vector_store = None
133
+ rag_chain = None
134
+ DB_PATH = None # will be set when a .db is uploaded
135
+ DOC_PATH = None # will be set when a document is uploaded
136
+ IMG_PATH = None # will be set when an image is uploaded
137
+ OTH_PATH = None # will be set when an other file is uploaded
138
+
139
+
140
+ # ─── LLMS ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
141
+ #llm = ChatOpenAI(model_name="gpt-3.5-turbo", streaming=True, temperature=0)
142
+ from tenacity import retry, stop_after_attempt, wait_exponential
143
+
144
+ # Import the RetryingChatGroq client
145
+ from retry_groq import RetryingChatGroq
146
+
147
+ # Use the retrying version instead
148
+ llm = RetryingChatGroq(model="deepseek-r1-distill-llama-70b", streaming=False, temperature=0)
149
+ #llm = ChatMistralAI(model="mistral-large-latest", streaming=True, temperature=0)
150
+
151
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
152
+ # ─────────────────────────────────────────────── Tool for multiply ──────────────────────────────────────────────────────────────────────
153
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
154
+ @tool(parse_docstring=True)
155
+ def multiply(a: int, b: int) -> int:
156
+ """
157
+ Multiply two numbers.
158
+
159
+ Args:
160
+ a (int): The first factor.
161
+ b (int): The second factor.
162
+
163
+ Returns:
164
+ int: The product of a and b.
165
+ """
166
+ try:
167
+ # Direct calculation without relying on LangChain handling
168
+ result = a * b
169
+ return result
170
+ except Exception as e:
171
+ return f"Error in multiplication: {str(e)}"
172
+
173
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
174
+ # ─────────────────────────────────────────────── Tool for add ──────────────────────────────────────────────────────────────────────────
175
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
176
+ @tool(parse_docstring=True)
177
+ def add(a: int, b: int) -> int:
178
+ """
179
+ Add two numbers.
180
+
181
+ Args:
182
+ a (int): The first factor.
183
+ b (int): The second factor.
184
+
185
+ Returns:
186
+ int: The addition of a and b.
187
+ """
188
+ try:
189
+ # Direct calculation without relying on LangChain handling
190
+ result = a + b
191
+ return result
192
+ except Exception as e:
193
+ return f"Error in addition: {str(e)}"
194
+
195
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
196
+ # ─────────────────────────────────────────────── Tool for subtract ──────────────────────────────────────────────────────────────────────
197
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
198
+ @tool(parse_docstring=True)
199
+ def subtract(a: int, b: int) -> int:
200
+ """
201
+ Subtract two numbers.
202
+
203
+ Args:
204
+ a (int): The first factor.
205
+ b (int): The second factor.
206
+
207
+ Returns:
208
+ int: The subtraction of a and b.
209
+ """
210
+ try:
211
+ # Direct calculation without relying on LangChain handling
212
+ result = a - b
213
+ return result
214
+ except Exception as e:
215
+ return f"Error in subtraction: {str(e)}"
216
+
217
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
218
+ # ─────────────────────────────────────────────── Tool for divide ──────────────────────────────────────────────────────────────────────
219
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
220
+ @tool(parse_docstring=True)
221
+ def divide(a: int, b: int) -> int:
222
+ """
223
+ Divide two numbers.
224
+
225
+ Args:
226
+ a (int): The numerator.
227
+ b (int): The denominator.
228
+
229
+ Returns:
230
+ float: The result of a divided by b.
231
+
232
+ Raises:
233
+ ValueError: If b is zero.
234
+ """
235
+ try:
236
+ if b == 0:
237
+ return "Error: Cannot divide by zero."
238
+ # Direct calculation without relying on LangChain handling
239
+ result = a / b
240
+ return result
241
+ except Exception as e:
242
+ return f"Error in division: {str(e)}"
243
+
244
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────���───────────────────────────────────────
245
+ # ─────────────────────────────────────────────── Tool for modulus ──────────────────────────────────────────────────────────────────────
246
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
247
+ @tool(parse_docstring=True)
248
+ def modulus(a: int, b: int) -> int:
249
+ """
250
+ Get the modulus (remainder) of two numbers.
251
+
252
+ Args:
253
+ a (int): The dividend.
254
+ b (int): The divisor.
255
+
256
+ Returns:
257
+ int: The remainder when a is divided by b.
258
+ """
259
+ try:
260
+ if b == 0:
261
+ return "Error: Cannot calculate modulus with zero divisor."
262
+ # Direct calculation without relying on LangChain handling
263
+ result = a % b
264
+ return result
265
+ except Exception as e:
266
+ return f"Error in modulus calculation: {str(e)}"
267
+
268
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
269
+ # ─────────────────────────────────────────────── Tool for browsing ──────────────────────────────────────────────────────────────────────
270
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
271
+ def with_retry(max_attempts: int = 3, backoff_base: int = 2):
272
+ """
273
+ Decorator for retrying a function with exponential backoff on exception.
274
+ """
275
+ def decorator(fn):
276
+ @wraps(fn)
277
+ def wrapper(*args, **kwargs):
278
+ for attempt in range(max_attempts):
279
+ try:
280
+ return fn(*args, **kwargs)
281
+ except Exception as e:
282
+ wait = backoff_base ** attempt + random.uniform(0, 1)
283
+ logger.warning(f"{fn.__name__} failed (attempt {attempt+1}/{max_attempts}): {e}")
284
+ if attempt < max_attempts - 1:
285
+ time.sleep(wait)
286
+ logger.error(f"{fn.__name__} failed after {max_attempts} attempts.")
287
+ return []
288
+ return wrapper
289
+ return decorator
290
+
291
+ @with_retry()
292
+ @lru_cache(maxsize=128)
293
+ def tavily_search(query: str, top_k: int = 3) -> List[Dict]:
294
+ """Call Tavily API and return a list of result dicts."""
295
+ if not TAVILY_API_KEY:
296
+ logger.info("[Tavily] No API key set. Skipping Tavily search.")
297
+ return []
298
+ url = "https://api.tavily.com/search"
299
+ headers = {
300
+ "Authorization": f"Bearer {TAVILY_API_KEY}",
301
+ "Content-Type": "application/json",
302
+ }
303
+ payload = {"query": query, "num_results": top_k}
304
+ resp = requests.post(url, headers=headers, json=payload, timeout=10)
305
+ resp.raise_for_status()
306
+ data = resp.json()
307
+ results = []
308
+ for item in data.get("results", []):
309
+ results.append({
310
+ "title": item.get("title", ""),
311
+ "url": item.get("url", ""),
312
+ "content": item.get("content", "")[:200],
313
+ "source": "Tavily"
314
+ })
315
+ return results
316
+
317
+ @with_retry()
318
+ @lru_cache(maxsize=128)
319
+ def duckduckgo_search(query: str, top_k: int = 3) -> List[Dict]:
320
+ """Query DuckDuckGo and return up to top_k raw SERP hits."""
321
+ results = []
322
+ try:
323
+ with DDGS(timeout=15) as ddgs: # Increase timeout from default
324
+ for hit in ddgs.text(query, safesearch="On", max_results=top_k, timeout=15):
325
+ results.append({
326
+ "title": hit.get("title", ""),
327
+ "url": hit.get("href") or hit.get("url", ""),
328
+ "content": hit.get("body", ""),
329
+ "source": "DuckDuckGo"
330
+ })
331
+ if len(results) >= top_k:
332
+ break
333
+ except Exception as e:
334
+ logger.warning(f"DuckDuckGo search failed: {e}")
335
+ # Don't re-raise - just return empty results to allow fallbacks to work
336
+
337
+ return results
338
+
339
+ # Additional fallback search alternative
340
+ def simple_google_search(query: str, top_k: int = 3) -> List[Dict]:
341
+ """Simplified Google search as a fallback when other methods fail."""
342
+ try:
343
+ # Encode the query
344
+ import urllib.parse
345
+ import bs4
346
+
347
+ encoded_query = urllib.parse.quote(query)
348
+ url = f"https://www.google.com/search?q={encoded_query}"
349
+
350
+ headers = {
351
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36",
352
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
353
+ "Accept-Language": "en-US,en;q=0.5",
354
+ "Referer": "https://www.google.com/",
355
+ "Connection": "keep-alive",
356
+ }
357
+
358
+ response = requests.get(url, headers=headers, timeout=20)
359
+ response.raise_for_status()
360
+
361
+ soup = bs4.BeautifulSoup(response.text, "html.parser")
362
+ results = []
363
+
364
+ # Extract search results
365
+ for result in soup.select("div.g")[:top_k]:
366
+ title_elem = result.select_one("h3")
367
+ link_elem = result.select_one("a")
368
+ snippet_elem = result.select_one("div.VwiC3b")
369
+
370
+ if title_elem and link_elem and snippet_elem and "href" in link_elem.attrs:
371
+ href = link_elem["href"]
372
+ if href.startswith("/url?q="):
373
+ href = href.split("/url?q=")[1].split("&")[0]
374
+
375
+ if href.startswith("http"):
376
+ results.append({
377
+ "title": title_elem.get_text(),
378
+ "url": href,
379
+ "content": snippet_elem.get_text(),
380
+ "source": "Google"
381
+ })
382
+
383
+ return results
384
+
385
+ except Exception as e:
386
+ logger.warning(f"Simple Google search failed: {e}")
387
+ return []
388
+
389
+ def hybrid_search(query: str, top_k: int = 3) -> List[Dict]:
390
+ """Combine multiple search sources with fallbacks."""
391
+ # Try primary search methods first
392
+ results = []
393
+
394
+ # Start with Tavily if API key is available
395
+ if TAVILY_API_KEY and TAVILY_API_KEY != "default_key_or_placeholder":
396
+ try:
397
+ tavily_results = tavily_search(query, top_k)
398
+ results.extend(tavily_results)
399
+ logger.info(f"Retrieved {len(tavily_results)} results from Tavily")
400
+ except Exception as e:
401
+ logger.warning(f"Tavily search failed: {e}")
402
+
403
+ # If we don't have enough results, try DuckDuckGo
404
+ if len(results) < top_k:
405
+ try:
406
+ ddg_results = duckduckgo_search(query, top_k - len(results))
407
+ results.extend(ddg_results)
408
+ logger.info(f"Retrieved {len(ddg_results)} results from DuckDuckGo")
409
+ except Exception as e:
410
+ logger.warning(f"DuckDuckGo search failed: {e}")
411
+
412
+ # If we still don't have enough results, try Google
413
+ if len(results) < top_k:
414
+ try:
415
+ google_results = simple_google_search(query, top_k - len(results))
416
+ results.extend(google_results)
417
+ logger.info(f"Retrieved {len(google_results)} results from Google")
418
+ except Exception as e:
419
+ logger.warning(f"Google search failed: {e}")
420
+
421
+ # If all search methods failed, return a dummy result
422
+ if not results:
423
+ results.append({
424
+ "title": "Search Failed",
425
+ "url": "",
426
+ "content": f"Sorry, I couldn't find results for '{query}'. Please try refining your search terms or check your internet connection.",
427
+ "source": "No results"
428
+ })
429
+
430
+ return results[:top_k] # Ensure we only return top_k results
431
+
432
+ def format_search_docs(search_docs: List[Dict]) -> Dict[str, str]:
433
+ """
434
+ Turn a list of {source, page, content} dicts into one big
435
+ string with <Document ...>…</Document> entries separated by `---`.
436
+ """
437
+ formatted_search_docs = "\n\n---\n\n".join(
438
+ [
439
+ f'<Document source="{doc["source"]}" page="{doc.get("page", "")}"/>\n'
440
+ f'{doc.get("content", "")}\n'
441
+ f'</Document>'
442
+ for doc in search_docs
443
+ ]
444
+ )
445
+ return {"web_results": formatted_search_docs}
446
+
447
+
448
+ @tool(parse_docstring=True)
449
+ def web_search(query: str, top_k: int = 3) -> Dict[str, str]:
450
+ """
451
+ Perform a hybrid web search combining multiple search engines with robust fallbacks.
452
+
453
+ Args:
454
+ query: The search query string to look up.
455
+ top_k: The maximum number of search results to return (default is 3).
456
+
457
+ Returns:
458
+ A dictionary mapping result indices to XML-like <Document> blocks, each containing:
459
+ - source: The URL of the webpage.
460
+ - page: Placeholder for page identifier (empty string by default).
461
+ - content: The first 200 words of the page text, cleaned of HTML tags.
462
+ """
463
+ try:
464
+ # Use our robust hybrid search to get initial results
465
+ search_results = hybrid_search(query, top_k)
466
+ results = []
467
+
468
+ # Process each search result to get better content
469
+ for hit in search_results:
470
+ url = hit.get("url")
471
+ if not url:
472
+ continue
473
+
474
+ # Start with the snippet from search
475
+ content = hit.get("content", "")
476
+ title = hit.get("title", "")
477
+
478
+ # Try to scrape additional content if possible
479
+ try:
480
+ # Use a random user agent to avoid blocking
481
+ headers = {
482
+ "User-Agent": random.choice([
483
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36",
484
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15",
485
+ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36",
486
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36 Edg/97.0.1072.62"
487
+ ]),
488
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
489
+ "Accept-Language": "en-US,en;q=0.5",
490
+ "Referer": "https://www.google.com/",
491
+ "DNT": "1",
492
+ "Connection": "keep-alive"
493
+ }
494
+
495
+ # Higher timeout for better reliability
496
+ resp = requests.get(url, timeout=15, headers=headers)
497
+
498
+ # Only process if successful
499
+ if resp.status_code == 200:
500
+ soup = BeautifulSoup(resp.text, "html.parser")
501
+
502
+ # Try to find main content
503
+ main_content = soup.find('main') or soup.find('article') or soup.find('div', class_='content')
504
+
505
+ # If we found main content, use it
506
+ if main_content:
507
+ extracted_text = main_content.get_text(separator=" ", strip=True)
508
+ # Take first 200 words
509
+ content = " ".join(extracted_text.split()[:200])
510
+ else:
511
+ # Otherwise use all text
512
+ all_text = soup.get_text(separator=" ", strip=True)
513
+ content = " ".join(all_text.split()[:200])
514
+
515
+ # Use content from page only if it's substantial
516
+ if len(content) < 50:
517
+ content = hit.get("content", "")[:200]
518
+
519
+ # Random delay between 0.5-1.5 seconds to avoid rate limits
520
+ time.sleep(0.5 + random.random())
521
+
522
+ except requests.exceptions.HTTPError as e:
523
+ logger.warning(f"HTTP error when scraping {url}: {e}")
524
+ # Keep the search snippet as a fallback
525
+ except requests.exceptions.RequestException as e:
526
+ logger.warning(f"Request error when scraping {url}: {e}")
527
+ # Keep the search snippet as a fallback
528
+ except Exception as e:
529
+ logger.warning(f"Unexpected error when scraping {url}: {e}")
530
+ # Keep the search snippet as a fallback
531
+
532
+ # Filter out inappropriate content
533
+ if any(f in content.lower() for f in _forbidden):
534
+ continue
535
+
536
+ # Add to results
537
+ results.append({
538
+ "source": url,
539
+ "page": "",
540
+ "content": content
541
+ })
542
+
543
+ # Return formatted search docs
544
+ return format_search_docs(results[:top_k])
545
+ except Exception as e:
546
+ logger.error(f"Web search failed: {e}")
547
+ # Return a helpful error message
548
+ return format_search_docs([{
549
+ "source": "Error",
550
+ "page": "",
551
+ "content": f"Search failed with error: {e}. Please try again with different search terms."
552
+ }])
553
+
554
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
555
+ # ─────────────────────────────────────────────── Tool for File System ───────────────────────────────────────────────────────────────────
556
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
557
+ @tool(parse_docstring=True)
558
+ def download_file(url: str, dest_path: str) -> str:
559
+ """
560
+ Download a file from a given URL and save it locally.
561
+
562
+ Args:
563
+ url: The direct URL of the file to download.
564
+ dest_path: The local path to save the downloaded file.
565
+
566
+ Returns:
567
+ The destination path where the file was saved.
568
+ """
569
+ r = requests.get(url, stream=True)
570
+ r.raise_for_status()
571
+ with open(dest_path, 'wb') as f:
572
+ for chunk in r.iter_content(8192):
573
+ f.write(chunk)
574
+ return dest_path
575
+
576
+ @tool(parse_docstring=True)
577
+ def process_excel_to_text(file_path: str) -> str:
578
+ """
579
+ Convert an Excel file into CSV-formatted text.
580
+
581
+ Args:
582
+ file_path: Path to the Excel (.xlsx) file.
583
+
584
+ Returns:
585
+ A string of CSV-formatted content extracted from the Excel file.
586
+ """
587
+ try:
588
+ # Check if file exists
589
+ import os
590
+ if not os.path.exists(file_path):
591
+ return f"Error: Excel file '{file_path}' does not exist."
592
+
593
+ # Try different engines
594
+ engines = ['openpyxl', 'xlrd', None]
595
+
596
+ for engine in engines:
597
+ try:
598
+ # For engine=None, pandas will try to auto-detect
599
+ if engine:
600
+ df = pd.read_excel(file_path, engine=engine)
601
+ else:
602
+ df = pd.read_excel(file_path)
603
+ return df.to_csv(index=False)
604
+ except Exception as e:
605
+ print(f"Excel engine {engine} failed: {e}")
606
+ last_error = e
607
+ continue
608
+
609
+ # If we got here, all engines failed
610
+ return f"Error processing Excel file: {str(last_error)}"
611
+ except Exception as e:
612
+ return f"Error with Excel file: {str(e)}"
613
+
614
+ @tool(parse_docstring=True)
615
+ def read_text_from_pdf(file_path: str, question: str = None) -> str:
616
+ """
617
+ Extract text from a PDF file, chunking large documents if needed.
618
+
619
+ Args:
620
+ file_path: Path to the PDF file.
621
+ question: Optional question to help retrieve relevant parts of long documents.
622
+
623
+ Returns:
624
+ The extracted text content, potentially chunked if the document is large.
625
+ """
626
+ try:
627
+ # Check if file exists
628
+ import os
629
+ if not os.path.exists(file_path):
630
+ return f"Error: PDF file '{file_path}' does not exist."
631
+
632
+ reader = PdfReader(file_path)
633
+ full_text = "\n".join([page.extract_text() or "" for page in reader.pages])
634
+
635
+ # If a question is provided, use retrieval to get relevant parts
636
+ if question and len(full_text) > 5000: # Only chunk if text is large
637
+ return process_large_document(full_text, question)
638
+
639
+ return full_text
640
+ except Exception as e:
641
+ return f"Error reading PDF: {str(e)}"
642
+
643
+ @tool(parse_docstring=True)
644
+ def read_text_from_docx(file_path: str, question: str = None) -> str:
645
+ """
646
+ Extract text from a DOCX (Word) document, chunking large documents if needed.
647
+
648
+ Args:
649
+ file_path: Path to the DOCX file.
650
+ question: Optional question to help retrieve relevant parts of long documents.
651
+
652
+ Returns:
653
+ The extracted text, potentially chunked if the document is large.
654
+ """
655
+ try:
656
+ # Check if file exists
657
+ import os
658
+ if not os.path.exists(file_path):
659
+ return f"Error: File '{file_path}' does not exist."
660
+
661
+ try:
662
+ doc = docx.Document(file_path)
663
+ full_text = "\n".join([para.text for para in doc.paragraphs])
664
+ except Exception as docx_err:
665
+ # Handle "Package not found" error specifically
666
+ if "Package not found" in str(docx_err):
667
+ # Try to read raw text if possible
668
+ try:
669
+ import zipfile
670
+ from xml.etree.ElementTree import XML
671
+
672
+ WORD_NAMESPACE = '{http://schemas.openxmlformats.org/wordprocessingml/2006/main}'
673
+ PARA = WORD_NAMESPACE + 'p'
674
+ TEXT = WORD_NAMESPACE + 't'
675
+
676
+ with zipfile.ZipFile(file_path) as docx_file:
677
+ with docx_file.open('word/document.xml') as document:
678
+ tree = XML(document.read())
679
+ paragraphs = []
680
+ for paragraph in tree.iter(PARA):
681
+ texts = [node.text for node in paragraph.iter(TEXT) if node.text]
682
+ if texts:
683
+ paragraphs.append(''.join(texts))
684
+ full_text = '\n'.join(paragraphs)
685
+ except Exception as e:
686
+ return f"Error reading DOCX file: {str(e)}"
687
+ else:
688
+ return f"Error reading DOCX file: {str(docx_err)}"
689
+
690
+ # If a question is provided, use retrieval to get relevant parts
691
+ if question and len(full_text) > 5000: # Only chunk if text is large
692
+ return process_large_document(full_text, question)
693
+
694
+ return full_text
695
+ except Exception as e:
696
+ return f"Error reading DOCX file: {str(e)}"
697
+
698
+
699
+ @tool(parse_docstring=True)
700
+ def transcribe_audio(file_path: str) -> str:
701
+ """
702
+ Transcribe speech from a local audio file to text.
703
+
704
+ Args:
705
+ file_path: Path to the audio file.
706
+
707
+ Returns:
708
+ Transcribed text using Google Web Speech API.
709
+ """
710
+ try:
711
+ # Check if file exists
712
+ import os
713
+ if not os.path.exists(file_path):
714
+ return f"Error: Audio file '{file_path}' does not exist."
715
+
716
+ # For non-WAV files, convert to WAV first
717
+ if not file_path.lower().endswith('.wav'):
718
+ try:
719
+ from pydub import AudioSegment
720
+ temp_wav = os.path.splitext(file_path)[0] + "_temp.wav"
721
+ audio = AudioSegment.from_file(file_path)
722
+ audio.export(temp_wav, format="wav")
723
+ file_path = temp_wav
724
+ except Exception as e:
725
+ return f"Failed to convert audio to WAV format: {str(e)}"
726
+
727
+ recognizer = sr.Recognizer()
728
+ with sr.AudioFile(file_path) as src:
729
+ audio = recognizer.record(src)
730
+ return recognizer.recognize_google(audio)
731
+ except Exception as e:
732
+ if "Audio file could not be read" in str(e):
733
+ return f"Error: Audio format not supported. Try converting to WAV, MP3, OGG, or FLAC."
734
+ return f"Error transcribing audio: {str(e)}"
735
+
736
+ @tool(parse_docstring=True)
737
+ def youtube_audio_processing(youtube_url: str) -> str:
738
+ """
739
+ Download and transcribe audio from a YouTube video.
740
+
741
+ Args:
742
+ youtube_url: URL of the YouTube video.
743
+
744
+ Returns:
745
+ Transcription text extracted from the video's audio.
746
+ """
747
+ yt = YouTube(youtube_url)
748
+ audio_stream = yt.streams.filter(only_audio=True).first()
749
+ out_file = audio_stream.download(output_path='.', filename='yt_audio')
750
+ wav_path = 'yt_audio.wav'
751
+ AudioSegment.from_file(out_file).export(wav_path, format='wav')
752
+ return transcribe_audio(wav_path)
753
+
754
+ @tool(parse_docstring=True)
755
+ def extract_article_text(url: str, question: str = None) -> str:
756
+ """
757
+ Download and extract the main article content from a webpage, chunking large articles if needed.
758
+
759
+ Args:
760
+ url: The URL of the article to extract.
761
+ question: Optional question to help retrieve relevant parts of long articles.
762
+
763
+ Returns:
764
+ The article's textual content, potentially chunked if large.
765
+ """
766
+ try:
767
+ art = Article(url)
768
+ art.download()
769
+ art.parse()
770
+ full_text = art.text
771
+
772
+ # If a question is provided, use retrieval to get relevant parts
773
+ if question and len(full_text) > 5000: # Only chunk if text is large
774
+ return process_large_document(full_text, question)
775
+
776
+ return full_text
777
+ except Exception as e:
778
+ return f"Error extracting article: {str(e)}"
779
+
780
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
781
+ # ───────────────────────────────────────────────────────────── Tool for ArXiv ────────────────────────────────────────────────────────────
782
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
783
+
784
+ @tool(parse_docstring=True)
785
+ def arvix_search(query: str) -> Dict[str, str]:
786
+ """
787
+ Search for academic papers on ArXiv.
788
+
789
+ Args:
790
+ query: The search term to look for in ArXiv.
791
+
792
+ Returns:
793
+ A dictionary of up to 3 relevant paper entries in JSON format.
794
+ """
795
+ papers = ArxivLoader(query=query, load_max_docs=3).load()
796
+ results = []
797
+ for doc in papers:
798
+ try:
799
+ # Handle different metadata formats that might be returned
800
+ source = doc.metadata.get("source", "ArXiv")
801
+ doc_id = doc.metadata.get("id", doc.metadata.get("entry_id", ""))
802
+ result = {
803
+ "source": source,
804
+ "id": doc_id,
805
+ "summary": doc.page_content[:1000] if hasattr(doc, "page_content") else str(doc)[:1000],
806
+ }
807
+ results.append(result)
808
+ except Exception as e:
809
+ # Add error information as a fallback
810
+ results.append({
811
+ "source": "ArXiv Error",
812
+ "id": "error",
813
+ "summary": f"Error processing paper: {str(e)}"
814
+ })
815
+
816
+ return {"arvix_results": json.dumps(results)}
817
+
818
+ @tool(parse_docstring=True)
819
+ def answer_youtube_video_question(
820
+ youtube_url: str,
821
+ question: str,
822
+ chunk_size_seconds: int = 30
823
+ ) -> str:
824
+ """
825
+ Answer a question based on a YouTube video's transcript.
826
+
827
+ Args:
828
+ youtube_url: URL of the YouTube video.
829
+ question: The question to be answered using video content.
830
+ chunk_size_seconds: Duration of each transcript chunk.
831
+
832
+ Returns:
833
+ The answer to the question generated from the video transcript.
834
+ """
835
+ loader = YoutubeLoader.from_youtube_url(
836
+ youtube_url,
837
+ add_video_info=True,
838
+ transcript_format=TranscriptFormat.CHUNKS,
839
+ chunk_size_seconds=chunk_size_seconds,
840
+ )
841
+ documents = loader.load()
842
+ embeddings = HuggingFaceEmbeddings(model_name='sentence-transformers/all-mpnet-base-v2')
843
+ vectorstore = FAISS.from_documents(documents, embeddings)
844
+ llm = RetryingChatGroq(model="deepseek-r1-distill-llama-70b", streaming=False)
845
+ qa_chain = RetrievalQA.from_chain_type(llm=llm, retriever=vectorstore.as_retriever())
846
+ return qa_chain.run(question)
847
+
848
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
849
+ # ───────────────────────────────────────────────────────────── Tool for Python REPL tool ────────────────────────────────────────────────
850
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
851
+
852
+ python_repl = PythonREPLTool()
853
+
854
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
855
+ # ───────────────────────────────────────────────────────────── Tool for Wiki ────────────────────────────────────────────────────────────
856
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
857
+
858
+ @tool(parse_docstring=True)
859
+ def wiki_search(query: str) -> str:
860
+ """
861
+ Search Wikipedia for information on a given topic.
862
+
863
+ Args:
864
+ query: The search term for Wikipedia.
865
+
866
+ Returns:
867
+ A JSON string with up to 3 summary results.
868
+ """
869
+ # load up to top_k pages
870
+ pages = WikipediaLoader(query=query, load_max_docs=3).load()
871
+ results: List[Dict] = []
872
+ for doc in pages:
873
+ results.append({
874
+ "source": doc.metadata["source"],
875
+ "page": doc.metadata.get("page", ""),
876
+ "content": doc.page_content[:1000], # truncate if you like
877
+ })
878
+ return {"wiki_results": format_search_docs(results)}
879
+
880
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
881
+ # ───────────────────────────────────── Tool for Image (understading, captioning & classification) ─────────────────────────────────────────
882
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
883
+
884
+ def _load_image(img_path: str, resize_to=(512, 512)) -> Image.Image:
885
+ """
886
+ Load, verify, convert, and resize an image.
887
+ Raises ValueError on failure.
888
+ """
889
+ if not img_path:
890
+ raise ValueError("No image path provided.")
891
+ try:
892
+ with Image.open(img_path) as img:
893
+ img.verify()
894
+ img = Image.open(img_path).convert("RGB")
895
+ img = img.resize(resize_to)
896
+ return img
897
+ except UnidentifiedImageError:
898
+ raise ValueError(f"File at {img_path} is not a valid image.")
899
+ except Exception as e:
900
+ raise ValueError(f"Failed to load image at {img_path}: {e}")
901
+
902
+ def _encode_image_to_base64(img_path: str) -> str:
903
+ """
904
+ Load an image, save optimized PNG into memory, and base64‑encode it.
905
+ """
906
+ img = _load_image(img_path)
907
+ buffer = BytesIO()
908
+ img.save(buffer, format="PNG", optimize=True)
909
+ return base64.b64encode(buffer.getvalue()).decode("utf-8")
910
+
911
+ @tool
912
+ def image_processing(prompt: str, img_path: str) -> str:
913
+ """Process an image using a vision LLM, with OCR fallback.
914
+
915
+ Args:
916
+ prompt: Instruction or question related to the image.
917
+ img_path: Path to the image file.
918
+
919
+ Returns:
920
+ The model's response or fallback OCR result.
921
+ """
922
+ try:
923
+ import os
924
+ # Check if file exists
925
+ if not os.path.exists(img_path):
926
+ return f"Error: Image file '{img_path}' does not exist."
927
+
928
+ try:
929
+ b64 = _encode_image_to_base64(img_path)
930
+ # Build a single markdown string with inline base64 image
931
+ md = f"{prompt}\n\n![](data:image/png;base64,{b64})"
932
+ message = HumanMessage(content=md)
933
+ # Use RetryingChatGroq with Llama 4 Maverick for vision
934
+ llm = RetryingChatGroq(model="meta-llama/llama-4-maverick-17b-128e-instruct", streaming=False, temperature=0)
935
+ try:
936
+ resp = llm.invoke([message])
937
+ if hasattr(resp, 'content'):
938
+ return resp.content.strip()
939
+ elif isinstance(resp, str):
940
+ return resp.strip()
941
+ else:
942
+ # Handle dictionary or other response types
943
+ return str(resp)
944
+ except Exception as invoke_err:
945
+ print(f"[LLM invoke error] {invoke_err}")
946
+ # Fall back to OCR
947
+ raise ValueError("LLM invocation failed")
948
+ except Exception as llama_err:
949
+ print(f"[LLM vision failed] {llama_err}")
950
+ try:
951
+ img = _load_image(img_path)
952
+ return pytesseract.image_to_string(img).strip()
953
+ except Exception as ocr_err:
954
+ print(f"[OCR fallback failed] {ocr_err}")
955
+ return "Unable to process the image. Please check the file and try again."
956
+ except Exception as e:
957
+ # Catch any other errors
958
+ print(f"[image_processing error] {e}")
959
+ return f"Error processing image: {str(e)}"
960
+
961
+ python_repl_tool = PythonREPLTool()
962
+
963
+ @tool
964
+ def echo(text: str) -> str:
965
+ """Echo back the input text.
966
+
967
+ Args:
968
+ text: The string to be echoed.
969
+
970
+ Returns:
971
+ The same text that was provided as input.
972
+ """
973
+ return text
974
+
975
+ # ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────��───────────────────
976
+ # ─────────────────────────────────────────────── Langgraph Agent ───────────────────────────────────────────────────────────────────────
977
+ # ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
978
+
979
+
980
+ # Build graph function
981
+ from langchain_core.tools import tool
982
+ from langchain.chat_models import ChatOpenAI
983
+ from langgraph.prebuilt.chat_agent_executor import create_react_agent, AgentState
984
+ from langchain.chat_models import init_chat_model
985
+
986
+
987
+
988
+ def build_graph(provider: str = "groq"):
989
+ """Construct and compile the multi‑agent GAIA workflow StateGraph.
990
+
991
+ This graph wires together three React‑style agents into a streamlined pipeline:
992
+ PerceptionAgent β†’ ActionAgent β†’ EvaluationAgent (with appropriate entry/exit points)
993
+
994
+ The agents have the following responsibilities:
995
+ - PerceptionAgent: Handles web searches, Wikipedia, ArXiv, and image processing
996
+ - ActionAgent: Performs calculations, file operations, and code analysis
997
+ - EvaluationAgent: Reviews results and ensures the final answer is properly formatted
998
+
999
+ Args:
1000
+ provider: The name of the LLM provider. Must be "groq".
1001
+
1002
+ Returns:
1003
+ CompiledGraph: A compiled LangGraph state machine ready for invocation.
1004
+
1005
+ Raises:
1006
+ ValueError: If `provider` is anything other than "groq".
1007
+ """
1008
+ try:
1009
+ if provider != "groq":
1010
+ raise ValueError("Invalid provider. Expected 'groq'.")
1011
+
1012
+ # Initialize LLM
1013
+ try:
1014
+ logger.info("Initializing LLM with model: deepseek-r1-distill-llama-70b")
1015
+ api_key = os.getenv("GROQ_API_KEY")
1016
+ if not api_key or api_key == "default_key_or_placeholder":
1017
+ logger.error("GROQ_API_KEY is not set or is using placeholder value")
1018
+ raise ValueError("GROQ_API_KEY environment variable is not set properly. Please set a valid API key.")
1019
+
1020
+ llm = RetryingChatGroq(model="deepseek-r1-distill-llama-70b", temperature=0)
1021
+ logger.info("LLM initialized successfully")
1022
+ except Exception as e:
1023
+ logger.error(f"Error initializing LLM: {str(e)}")
1024
+ raise
1025
+
1026
+ # General system message for agents
1027
+ sys_msg = SystemMessage(content="""
1028
+ You are a general AI assistant. I will ask you a question. Report your thoughts, and finish your answer with the following template:
1029
+
1030
+ FINAL ANSWER: [YOUR FINAL ANSWER]
1031
+
1032
+ YOUR FINAL ANSWER should be a number OR as few words as possible OR a comma-separated list of numbers and/or strings.
1033
+
1034
+ If you are asked for a number, don't use commas or units (e.g., $, %, kg) unless specified otherwise.
1035
+
1036
+ If you are asked for a string, don't use articles (a, an, the), and don't use abbreviations (e.g., for states).
1037
+
1038
+ If you are asked for a comma-separated list, apply the above rules to each element in the list.
1039
+ """.strip())
1040
+
1041
+ # Special system message for the evaluation agent with stricter formatting requirements
1042
+ eval_sys_msg = SystemMessage(content="""
1043
+ You are a specialized evaluation agent. Your job is to review the work done by other agents
1044
+ and provide a final, properly formatted answer.
1045
+
1046
+ IMPORTANT: You MUST ALWAYS format your answer using this exact template:
1047
+
1048
+ FINAL ANSWER: [concise answer]
1049
+
1050
+ Rules for formatting the answer:
1051
+ 1. The answer must be extremely concise - use as few words as possible
1052
+ 2. For numeric answers, provide only the number without units unless units are specifically requested
1053
+ 3. For text answers, avoid articles (a, an, the) and unnecessary words
1054
+ 4. For list answers, use a comma-separated format
1055
+ 5. NEVER explain your reasoning in the FINAL ANSWER section
1056
+ 6. NEVER skip the "FINAL ANSWER:" prefix
1057
+
1058
+ Example good answers:
1059
+ FINAL ANSWER: 42
1060
+ FINAL ANSWER: Paris
1061
+ FINAL ANSWER: 1912, 1945, 1989
1062
+
1063
+ Example bad answers (don't do these):
1064
+ - Based on my analysis, the answer is 42.
1065
+ - I think it's Paris because that's the capital of France.
1066
+ - The years were 1912, 1945, and 1989.
1067
+
1068
+ Remember: ALWAYS include "FINAL ANSWER:" followed by the most concise answer possible.
1069
+ """.strip())
1070
+
1071
+ # Define tools for each agent
1072
+ logger.info("Setting up agent tools")
1073
+ perception_tools = [web_search, wiki_search, news_article_search, arvix_search, image_processing, echo]
1074
+ execution_tools = [
1075
+ multiply, add, subtract, divide, modulus,
1076
+ download_file, process_excel_to_text,
1077
+ read_text_from_pdf, read_text_from_docx,
1078
+ transcribe_audio, youtube_audio_processing,
1079
+ extract_article_text, answer_youtube_video_question,
1080
+ python_repl_tool, analyze_code, read_code_file, analyze_python_function
1081
+ ]
1082
+
1083
+ # ─────────────── Agent Creation ───────────────
1084
+ logger.info("Creating agents")
1085
+ try:
1086
+ # Create agents with proper error handling
1087
+ PerceptionAgent = create_react_agent(
1088
+ model=llm,
1089
+ tools=perception_tools,
1090
+ prompt=sys_msg,
1091
+ state_schema=AgentState,
1092
+ name="PerceptionAgent"
1093
+ )
1094
+ logger.info("Created PerceptionAgent successfully")
1095
+
1096
+ # Combined Planning and Execution agent for better efficiency
1097
+ ActionAgent = create_react_agent(
1098
+ model=llm,
1099
+ tools=execution_tools, # Has access to all execution tools
1100
+ prompt=sys_msg,
1101
+ state_schema=AgentState,
1102
+ name="ActionAgent"
1103
+ )
1104
+ logger.info("Created ActionAgent successfully")
1105
+
1106
+ # Evaluation agent with stricter prompt
1107
+ EvaluationAgent = create_react_agent(
1108
+ model=llm,
1109
+ tools=[], # No tools needed for evaluation
1110
+ prompt=eval_sys_msg, # Use the specialized evaluation prompt
1111
+ state_schema=AgentState,
1112
+ name="EvaluationAgent"
1113
+ )
1114
+ logger.info("Created EvaluationAgent successfully")
1115
+ except Exception as e:
1116
+ logger.error(f"Error creating agent: {str(e)}")
1117
+ import traceback
1118
+ logger.error(f"Traceback: {traceback.format_exc()}")
1119
+ raise
1120
+
1121
+ # Build the StateGraph
1122
+ logger.info("Building StateGraph")
1123
+ try:
1124
+ builder = StateGraph(AgentState)
1125
+
1126
+ # Add agent nodes first
1127
+ builder.add_node("PerceptionAgent", PerceptionAgent)
1128
+ builder.add_node("ActionAgent", ActionAgent)
1129
+ builder.add_node("EvaluationAgent", EvaluationAgent)
1130
+
1131
+ # Define the flow with a starting edge
1132
+ builder.set_entry_point("PerceptionAgent")
1133
+
1134
+ # Add the edges for the simpler linear flow
1135
+ builder.add_edge("PerceptionAgent", "ActionAgent")
1136
+ builder.add_edge("ActionAgent", "EvaluationAgent")
1137
+
1138
+ # Set EvaluationAgent as the end node
1139
+ builder.set_finish_point("EvaluationAgent")
1140
+
1141
+ logger.info("Compiling StateGraph")
1142
+ return builder.compile()
1143
+ except Exception as e:
1144
+ logger.error(f"Error building graph: {str(e)}")
1145
+ import traceback
1146
+ logger.error(f"Traceback: {traceback.format_exc()}")
1147
+ raise
1148
+ except Exception as e:
1149
+ logger.error(f"Overall error in build_graph: {str(e)}")
1150
+ import traceback
1151
+ logger.error(f"Traceback: {traceback.format_exc()}")
1152
+ raise
1153
+
1154
+ def get_final_answer(text):
1155
+ """Extract just the FINAL ANSWER from the model's response.
1156
+
1157
+ Args:
1158
+ text: The full text response from the LLM
1159
+
1160
+ Returns:
1161
+ str: The extracted answer without the "FINAL ANSWER:" prefix
1162
+ """
1163
+ # Log the raw text for debugging if needed
1164
+ logger.debug(f"Extracting answer from: {text[:200]}...")
1165
+
1166
+ if not text:
1167
+ logger.warning("Empty response received")
1168
+ return "No answer provided."
1169
+
1170
+ # Method 1: Look for "FINAL ANSWER:" with most comprehensive pattern matching
1171
+ pattern = r'(?:^|\n)FINAL ANSWER:\s*(.*?)(?:\n\s*$|$)'
1172
+ match = re.search(pattern, text, re.DOTALL | re.IGNORECASE)
1173
+ if match:
1174
+ # Return just the answer part, cleaned up
1175
+ logger.debug("Found answer using pattern 1")
1176
+ return match.group(1).strip()
1177
+
1178
+ # Method 2: Try looking for variations on the final answer format
1179
+ for variant in ["FINAL ANSWER:", "FINAL_ANSWER:", "Final Answer:", "Answer:"]:
1180
+ lines = text.split('\n')
1181
+ for i, line in enumerate(reversed(lines)):
1182
+ if variant in line:
1183
+ # Extract everything after the variant text
1184
+ logger.debug(f"Found answer using variant: {variant}")
1185
+ answer = line[line.find(variant) + len(variant):].strip()
1186
+ if answer:
1187
+ return answer
1188
+ # If the answer is on the next line, return that
1189
+ if i > 0:
1190
+ next_line = lines[len(lines) - i]
1191
+ if next_line.strip():
1192
+ return next_line.strip()
1193
+
1194
+ # Method 3: Look for phrases that suggest an answer
1195
+ for phrase in ["The answer is", "The result is", "We get", "Therefore,", "In conclusion,"]:
1196
+ phrase_pos = text.find(phrase)
1197
+ if phrase_pos != -1:
1198
+ # Try to extract everything after the phrase until the end of the sentence
1199
+ sentence_end = text.find(".", phrase_pos)
1200
+ if sentence_end != -1:
1201
+ logger.debug(f"Found answer using phrase: {phrase}")
1202
+ return text[phrase_pos + len(phrase):sentence_end].strip()
1203
+
1204
+ # Method 4: Fall back to taking the last paragraph with actual content
1205
+ paragraphs = text.strip().split('\n\n')
1206
+ for para in reversed(paragraphs):
1207
+ para = para.strip()
1208
+ if para and not para.startswith("I ") and not para.lower().startswith("to "):
1209
+ logger.debug("Using last meaningful paragraph")
1210
+ # If paragraph is very long, try to extract a concise answer
1211
+ if len(para) > 100:
1212
+ sentences = re.split(r'[.!?]', para)
1213
+ for sentence in reversed(sentences):
1214
+ sent = sentence.strip()
1215
+ if sent and len(sent) > 5 and not sent.startswith("I "):
1216
+ return sent
1217
+ return para
1218
+
1219
+ # Method 5: Last resort - just return the last line with content
1220
+ lines = text.strip().split('\n')
1221
+ for line in reversed(lines):
1222
+ line = line.strip()
1223
+ if line and len(line) > 3:
1224
+ logger.debug("Using last line with content")
1225
+ return line
1226
+
1227
+ # If everything fails, warn and return the truncated response
1228
+ logger.warning("Could not find a properly formatted answer")
1229
+ return text[:100] + "..." if len(text) > 100 else text
1230
+
1231
+ # test
1232
+ if __name__ == "__main__":
1233
+ question = "When was a picture of St. Thomas Aquinas first added to the Wikipedia page on the Principle of double effect?"
1234
+ # Build the graph
1235
+ graph = build_graph(provider="groq")
1236
+ # Run the graph
1237
+ messages = [HumanMessage(content=question)]
1238
+ messages = graph.invoke({"messages": messages})
1239
+ for m in messages["messages"]:
1240
+ m.pretty_print()
1241
+
1242
+ # ─────────────────────────────────────────────── Tool for Code Analysis ───────────────────────────────────────────────────────────────
1243
+ @tool
1244
+ def analyze_code(code_string: str) -> str:
1245
+ """Analyze a string of code to understand its structure, functionality, and potential issues.
1246
+
1247
+ Args:
1248
+ code_string: The code to analyze as a string.
1249
+
1250
+ Returns:
1251
+ A structured analysis of the code including functions, classes, and key operations.
1252
+ """
1253
+ try:
1254
+ import ast
1255
+
1256
+ # Try to parse with Python's AST module
1257
+ try:
1258
+ parsed = ast.parse(code_string)
1259
+
1260
+ # Extract functions and classes
1261
+ functions = [node.name for node in ast.walk(parsed) if isinstance(node, ast.FunctionDef)]
1262
+ classes = [node.name for node in ast.walk(parsed) if isinstance(node, ast.ClassDef)]
1263
+ imports = [node.names[0].name for node in ast.walk(parsed) if isinstance(node, ast.Import)]
1264
+ imports.extend([f"{node.module}.{name.name}" if node.module else name.name
1265
+ for node in ast.walk(parsed) if isinstance(node, ast.ImportFrom)
1266
+ for name in node.names])
1267
+
1268
+ # Count various node types for complexity assessment
1269
+ num_loops = len([node for node in ast.walk(parsed)
1270
+ if isinstance(node, (ast.For, ast.While))])
1271
+ num_conditionals = len([node for node in ast.walk(parsed)
1272
+ if isinstance(node, (ast.If, ast.IfExp))])
1273
+
1274
+ analysis = {
1275
+ "language": "Python",
1276
+ "functions": functions,
1277
+ "classes": classes,
1278
+ "imports": imports,
1279
+ "complexity": {
1280
+ "functions": len(functions),
1281
+ "classes": len(classes),
1282
+ "loops": num_loops,
1283
+ "conditionals": num_conditionals
1284
+ }
1285
+ }
1286
+ return str(analysis)
1287
+ except SyntaxError:
1288
+ # If not valid Python, try some simple pattern matching
1289
+ if "{" in code_string and "}" in code_string:
1290
+ if "function" in code_string or "=>" in code_string:
1291
+ language = "JavaScript/TypeScript"
1292
+ elif "func" in code_string or "struct" in code_string:
1293
+ language = "Go or Rust"
1294
+ elif "public" in code_string or "private" in code_string or "class" in code_string:
1295
+ language = "Java/C#/C++"
1296
+ else:
1297
+ language = "Unknown C-like language"
1298
+ elif "<" in code_string and ">" in code_string and ("/>" in code_string or "</"):
1299
+ language = "HTML/XML/JSX"
1300
+ else:
1301
+ language = "Unknown"
1302
+
1303
+ return f"Non-Python code detected ({language}). Basic code structure analysis not available."
1304
+ except Exception as e:
1305
+ return f"Error analyzing code: {str(e)}"
1306
+
1307
+ @tool
1308
+ def read_code_file(file_path: str) -> str:
1309
+ """Read a code file and return its contents with proper syntax detection.
1310
+
1311
+ Args:
1312
+ file_path: Path to the code file.
1313
+
1314
+ Returns:
1315
+ The file contents and detected language.
1316
+ """
1317
+ try:
1318
+ # Check if file exists
1319
+ import os
1320
+ if not os.path.exists(file_path):
1321
+ return f"Error: File '{file_path}' does not exist."
1322
+
1323
+ with open(file_path, 'r', encoding='utf-8') as f:
1324
+ content = f.read()
1325
+
1326
+ # Try to detect language from extension
1327
+ ext = os.path.splitext(file_path)[1].lower()
1328
+
1329
+ language_map = {
1330
+ '.py': 'Python',
1331
+ '.js': 'JavaScript',
1332
+ '.ts': 'TypeScript',
1333
+ '.html': 'HTML',
1334
+ '.css': 'CSS',
1335
+ '.java': 'Java',
1336
+ '.c': 'C',
1337
+ '.cpp': 'C++',
1338
+ '.cs': 'C#',
1339
+ '.go': 'Go',
1340
+ '.rs': 'Rust',
1341
+ '.php': 'PHP',
1342
+ '.rb': 'Ruby',
1343
+ '.sh': 'Shell',
1344
+ '.bat': 'Batch',
1345
+ '.ps1': 'PowerShell',
1346
+ '.sql': 'SQL',
1347
+ '.json': 'JSON',
1348
+ '.xml': 'XML',
1349
+ '.yaml': 'YAML',
1350
+ '.yml': 'YAML',
1351
+ }
1352
+
1353
+ language = language_map.get(ext, 'Unknown')
1354
+
1355
+ return f"File content ({language}):\n\n{content}"
1356
+ except Exception as e:
1357
+ return f"Error reading file: {str(e)}"
1358
+
1359
+ @tool
1360
+ def analyze_python_function(function_name: str, code_string: str) -> str:
1361
+ """Extract and analyze a specific function from Python code.
1362
+
1363
+ Args:
1364
+ function_name: The name of the function to analyze.
1365
+ code_string: The complete code containing the function.
1366
+
1367
+ Returns:
1368
+ Analysis of the function including parameters, return type, and docstring.
1369
+ """
1370
+ try:
1371
+ import ast
1372
+ import inspect
1373
+ from types import CodeType, FunctionType
1374
+
1375
+ # Parse the code string
1376
+ parsed = ast.parse(code_string)
1377
+
1378
+ # Find the function definition
1379
+ function_def = None
1380
+ for node in ast.walk(parsed):
1381
+ if isinstance(node, ast.FunctionDef) and node.name == function_name:
1382
+ function_def = node
1383
+ break
1384
+
1385
+ if not function_def:
1386
+ return f"Function '{function_name}' not found in the provided code."
1387
+
1388
+ # Extract parameters
1389
+ params = []
1390
+ for arg in function_def.args.args:
1391
+ param_name = arg.arg
1392
+ # Get annotation if it exists
1393
+ if arg.annotation:
1394
+ if isinstance(arg.annotation, ast.Name):
1395
+ param_type = arg.annotation.id
1396
+ elif isinstance(arg.annotation, ast.Attribute):
1397
+ param_type = f"{arg.annotation.value.id}.{arg.annotation.attr}"
1398
+ else:
1399
+ param_type = "complex_type"
1400
+ params.append(f"{param_name}: {param_type}")
1401
+ else:
1402
+ params.append(param_name)
1403
+
1404
+ # Extract return type if it exists
1405
+ return_type = None
1406
+ if function_def.returns:
1407
+ if isinstance(function_def.returns, ast.Name):
1408
+ return_type = function_def.returns.id
1409
+ elif isinstance(function_def.returns, ast.Attribute):
1410
+ return_type = f"{function_def.returns.value.id}.{function_def.returns.attr}"
1411
+ else:
1412
+ return_type = "complex_return_type"
1413
+
1414
+ # Extract docstring
1415
+ docstring = ast.get_docstring(function_def)
1416
+
1417
+ # Create a summary
1418
+ summary = {
1419
+ "function_name": function_name,
1420
+ "parameters": params,
1421
+ "return_type": return_type,
1422
+ "docstring": docstring,
1423
+ "decorators": [d.id if isinstance(d, ast.Name) else "complex_decorator" for d in function_def.decorator_list],
1424
+ "line_count": len(function_def.body)
1425
+ }
1426
+
1427
+ # Create a more explicit string representation that ensures key terms are included
1428
+ result = f"Function '{function_name}' analysis:\n"
1429
+ result += f"- Parameters: {', '.join(params)}\n"
1430
+ result += f"- Return type: {return_type or 'None specified'}\n"
1431
+ result += f"- Docstring: {docstring or 'None'}\n"
1432
+ result += f"- Line count: {len(function_def.body)}"
1433
+
1434
+ return result
1435
+ except Exception as e:
1436
+ return f"Error analyzing function: {str(e)}"
1437
+
1438
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
1439
+ # ─────────────────────────────────────────────── Tool for News Article Retrieval ──────────────────────────────────────────────────────────────────────
1440
+ # ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
1441
+
1442
+ @tool
1443
+ def news_article_search(query: str, top_k: int = 3) -> Dict[str, str]:
1444
+ """Search for and retrieve news articles with robust error handling for news sites.
1445
+
1446
+ Args:
1447
+ query: The news topic or keywords to search for.
1448
+ top_k: Maximum number of articles to retrieve.
1449
+
1450
+ Returns:
1451
+ A dictionary with search results formatted as XML-like document entries.
1452
+ """
1453
+ # First, get URLs from DuckDuckGo with "news" focus
1454
+ results = []
1455
+ news_sources = [
1456
+ "bbc.com", "reuters.com", "apnews.com", "nasa.gov",
1457
+ "space.com", "universetoday.com", "nature.com", "science.org",
1458
+ "scientificamerican.com", "nytimes.com", "theguardian.com"
1459
+ ]
1460
+
1461
+ # Find news from reliable sources
1462
+ try:
1463
+ with DDGS() as ddgs:
1464
+ search_query = f"{query} site:{' OR site:'.join(news_sources)}"
1465
+ for hit in ddgs.text(search_query, safesearch="On", max_results=top_k*2):
1466
+ url = hit.get("href") or hit.get("url", "")
1467
+ if not url:
1468
+ continue
1469
+
1470
+ # Add the search snippet first as a fallback
1471
+ result = {
1472
+ "source": url,
1473
+ "page": "",
1474
+ "content": hit.get("body", "")[:250],
1475
+ "title": hit.get("title", "")
1476
+ }
1477
+
1478
+ # Try to get better content via a more robust method
1479
+ try:
1480
+ headers = {
1481
+ "User-Agent": random.choice([
1482
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36",
1483
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Safari/605.1.15",
1484
+ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36"
1485
+ ]),
1486
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
1487
+ "Accept-Language": "en-US,en;q=0.5",
1488
+ "Referer": "https://www.google.com/",
1489
+ "DNT": "1",
1490
+ "Connection": "keep-alive",
1491
+ "Upgrade-Insecure-Requests": "1"
1492
+ }
1493
+
1494
+ # Add a short delay between requests
1495
+ time.sleep(1 + random.random())
1496
+
1497
+ # Try to use newspaper3k for more reliable article extraction
1498
+ from newspaper import Article
1499
+ article = Article(url)
1500
+ article.download()
1501
+ article.parse()
1502
+
1503
+ # If we got meaningful content, update the result
1504
+ if article.text and len(article.text) > 100:
1505
+ # Get a summary - first paragraph + some highlights
1506
+ paragraphs = article.text.split('\n\n')
1507
+ first_para = paragraphs[0] if paragraphs else ""
1508
+ summary = first_para[:300]
1509
+ if len(paragraphs) > 1:
1510
+ summary += "... " + paragraphs[1][:200]
1511
+
1512
+ result["content"] = summary
1513
+ if article.title:
1514
+ result["title"] = article.title
1515
+
1516
+ except Exception as article_err:
1517
+ logger.warning(f"Article extraction failed for {url}: {article_err}")
1518
+ # Fallback to simple requests-based extraction
1519
+ try:
1520
+ resp = requests.get(url, timeout=12, headers=headers)
1521
+ resp.raise_for_status()
1522
+ soup = BeautifulSoup(resp.text, "html.parser")
1523
+
1524
+ # Try to get main content
1525
+ main_content = soup.find('main') or soup.find('article') or soup.find('div', class_='content')
1526
+
1527
+ if main_content:
1528
+ content = " ".join(main_content.get_text(separator=" ", strip=True).split()[:250])
1529
+ result["content"] = content
1530
+ except Exception as req_err:
1531
+ logger.warning(f"Fallback extraction failed for {url}: {req_err}")
1532
+ # Keep the original snippet as fallback
1533
+
1534
+ results.append(result)
1535
+ if len(results) >= top_k:
1536
+ break
1537
+
1538
+ except Exception as e:
1539
+ logger.error(f"News search failed: {e}")
1540
+ return format_search_docs([{
1541
+ "source": "Error",
1542
+ "page": "",
1543
+ "content": f"Failed to retrieve news articles for '{query}': {str(e)}"
1544
+ }])
1545
+
1546
+ if not results:
1547
+ # Fallback to regular web search
1548
+ logger.info(f"No news results found, falling back to web_search for {query}")
1549
+ return web_search(query, top_k)
1550
+
1551
+ return format_search_docs(results[:top_k])
1552
+
1553
+ # ───────────────────────────────────────────────────────────── Document Chunking Utilities ──────────────────────────────────────────────────────────
1554
+ def chunk_document(text: str, chunk_size: int = 1000, overlap: int = 100) -> List[str]:
1555
+ """
1556
+ Split a large document into smaller chunks with overlap to maintain context across chunks.
1557
+
1558
+ Args:
1559
+ text: The document text to split into chunks
1560
+ chunk_size: Maximum size of each chunk in characters
1561
+ overlap: Number of characters to overlap between chunks
1562
+
1563
+ Returns:
1564
+ List of text chunks
1565
+ """
1566
+ # If text is smaller than chunk_size, return it as is
1567
+ if len(text) <= chunk_size:
1568
+ return [text]
1569
+
1570
+ chunks = []
1571
+ start = 0
1572
+
1573
+ while start < len(text):
1574
+ # Get chunk with overlap
1575
+ end = min(start + chunk_size, len(text))
1576
+
1577
+ # Try to find sentence boundary for cleaner breaks
1578
+ if end < len(text):
1579
+ # Look for sentence endings: period, question mark, or exclamation followed by space
1580
+ for sentence_end in ['. ', '? ', '! ']:
1581
+ last_period = text[start:end].rfind(sentence_end)
1582
+ if last_period != -1:
1583
+ end = start + last_period + 2 # +2 to include the period and space
1584
+ break
1585
+
1586
+ # Add chunk to list
1587
+ chunks.append(text[start:end])
1588
+
1589
+ # Move start position, accounting for overlap
1590
+ start = end - overlap if end < len(text) else len(text)
1591
+
1592
+ return chunks
1593
+
1594
+ # Document processing utility that uses chunking
1595
+ def process_large_document(text: str, question: str, llm=None) -> str:
1596
+ """
1597
+ Process a large document by chunking it and using retrieval to find relevant parts.
1598
+
1599
+ Args:
1600
+ text: The document text to process
1601
+ question: The question being asked about the document
1602
+ llm: Optional language model to use (defaults to agent's LLM)
1603
+
1604
+ Returns:
1605
+ Summarized answer based on relevant chunks
1606
+ """
1607
+ if not llm:
1608
+ llm = RetryingChatGroq(model="deepseek-r1-distill-llama-70b", streaming=False, temperature=0)
1609
+
1610
+ # Split document into chunks
1611
+ chunks = chunk_document(text)
1612
+
1613
+ # If document is small enough, don't bother with retrieval
1614
+ if len(chunks) <= 1:
1615
+ return text
1616
+
1617
+ # For larger documents, create embeddings to find relevant chunks
1618
+ try:
1619
+ from langchain_community.embeddings import HuggingFaceEmbeddings
1620
+ from langchain.vectorstores import FAISS
1621
+ from langchain.schema import Document
1622
+
1623
+ # Create documents with chunk content
1624
+ documents = [Document(page_content=chunk, metadata={"chunk_id": i}) for i, chunk in enumerate(chunks)]
1625
+
1626
+ # Create embeddings and vector store
1627
+ embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2")
1628
+ vectorstore = FAISS.from_documents(documents, embeddings)
1629
+
1630
+ # Get most relevant chunks
1631
+ relevant_chunks = vectorstore.similarity_search(question, k=2) # Get top 2 most relevant chunks
1632
+
1633
+ # Join the relevant chunks
1634
+ relevant_text = "\n\n".join([doc.page_content for doc in relevant_chunks])
1635
+
1636
+ # Option 1: Return relevant chunks directly
1637
+ return relevant_text
1638
+
1639
+ # Option 2: Summarize with LLM (commented out for now)
1640
+ # prompt = f"Using only the following information, answer the question: '{question}'\n\nInformation:\n{relevant_text}"
1641
+ # response = llm.invoke([HumanMessage(content=prompt)])
1642
+ # return response.content
1643
+
1644
+ except Exception as e:
1645
+ # Fall back to first chunk if retrieval fails
1646
+ logger.warning(f"Retrieval failed: {e}. Falling back to first chunk.")
1647
+ return chunks[0]
utils/agent2.py ADDED
@@ -0,0 +1,259 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+
4
+ from langchain_core.tools import tool
5
+ from langgraph.prebuilt import tools_condition, ToolNode
6
+ from langgraph.graph import START, StateGraph, MessagesState
7
+
8
+ from langchain_community.tools.tavily_search import TavilySearchResults
9
+ from langchain_community.document_loaders import WikipediaLoader, ArxivLoader
10
+ from langchain_core.messages import SystemMessage, HumanMessage
11
+ from langchain_community.vectorstores import FAISS
12
+ from langchain_huggingface import HuggingFaceEmbeddings
13
+ from langchain_groq import ChatGroq
14
+ from langchain.tools.retriever import create_retriever_tool
15
+
16
+ """LangGraph Agent"""
17
+ import os
18
+ from dotenv import load_dotenv
19
+ from langgraph.graph import START, StateGraph, MessagesState
20
+ from langgraph.prebuilt import tools_condition
21
+ from langgraph.prebuilt import ToolNode
22
+ from langchain_google_genai import ChatGoogleGenerativeAI
23
+ from langchain_groq import ChatGroq
24
+ from langchain_huggingface import ChatHuggingFace, HuggingFaceEndpoint, HuggingFaceEmbeddings
25
+ from langchain_community.tools.tavily_search import TavilySearchResults
26
+ from langchain_community.document_loaders import WikipediaLoader
27
+ from langchain_community.document_loaders import ArxivLoader
28
+ from langchain_community.vectorstores import SupabaseVectorStore,FAISS
29
+ from langchain_core.messages import SystemMessage, HumanMessage
30
+ from langchain_core.tools import tool
31
+ from langchain.tools.retriever import create_retriever_tool
32
+ #from supabase.client import Client, create_client
33
+
34
+ # ──────────────────────────────────────────────────────────
35
+ # ENV
36
+ # ──────────────────────────────────────────────────────────
37
+ load_dotenv()
38
+ # API Keys from .env file
39
+ os.environ.setdefault("OPENAI_API_KEY", "<YOUR_OPENAI_KEY>") # Set your own key or env var
40
+ os.environ["GROQ_API_KEY"] = os.getenv("GROQ_API_KEY", "default_key_or_placeholder")
41
+ os.environ["MISTRAL_API_KEY"] = os.getenv("MISTRAL_API_KEY", "default_key_or_placeholder")
42
+
43
+ # Tavily API Key
44
+ TAVILY_API_KEY = os.getenv("TAVILY_API_KEY", "default_key_or_placeholder")
45
+ _forbidden = ["nsfw", "porn", "sex", "explicit"]
46
+ _playwright_available = True # set False to disable Playwright
47
+
48
+ embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2") # dim = 768
49
+
50
+ @tool
51
+ def multiply(a: int, b: int) -> int:
52
+ """Multiply two numbers.
53
+
54
+ Args:
55
+ a: first int
56
+ b: second int
57
+ """
58
+ return a * b
59
+
60
+ @tool
61
+ def add(a: int, b: int) -> int:
62
+ """Add two numbers.
63
+
64
+ Args:
65
+ a: first int
66
+ b: second int
67
+ """
68
+ return a + b
69
+
70
+ @tool
71
+ def subtract(a: int, b: int) -> int:
72
+ """Subtract two numbers.
73
+
74
+ Args:
75
+ a: first int
76
+ b: second int
77
+ """
78
+ return a - b
79
+
80
+ @tool
81
+ def divide(a: int, b: int) -> int:
82
+ """Divide two numbers.
83
+
84
+ Args:
85
+ a: first int
86
+ b: second int
87
+ """
88
+ if b == 0:
89
+ raise ValueError("Cannot divide by zero.")
90
+ return a / b
91
+
92
+ @tool
93
+ def modulus(a: int, b: int) -> int:
94
+ """Get the modulus of two numbers.
95
+
96
+ Args:
97
+ a: first int
98
+ b: second int
99
+ """
100
+ return a % b
101
+
102
+ @tool
103
+ def wiki_search(query: str) -> str:
104
+ """Search Wikipedia for a query and return maximum 2 results.
105
+
106
+ Args:
107
+ query: The search query."""
108
+ search_docs = WikipediaLoader(query=query, load_max_docs=2).load()
109
+ formatted_search_docs = "\n\n---\n\n".join(
110
+ [
111
+ f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
112
+ for doc in search_docs
113
+ ])
114
+ return {"wiki_results": formatted_search_docs}
115
+
116
+ @tool
117
+ def web_search(query: str) -> str:
118
+ """Search Tavily for a query and return maximum 3 results.
119
+
120
+ Args:
121
+ query: The search query."""
122
+ search_docs = TavilySearchResults(max_results=3).invoke(query=query)
123
+ formatted_search_docs = "\n\n---\n\n".join(
124
+ [
125
+ f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content}\n</Document>'
126
+ for doc in search_docs
127
+ ])
128
+ return {"web_results": formatted_search_docs}
129
+
130
+ @tool
131
+ def arvix_search(query: str) -> str:
132
+ """Search Arxiv for a query and return maximum 3 result.
133
+
134
+ Args:
135
+ query: The search query."""
136
+ search_docs = ArxivLoader(query=query, load_max_docs=3).load()
137
+ formatted_search_docs = "\n\n---\n\n".join(
138
+ [
139
+ f'<Document source="{doc.metadata["source"]}" page="{doc.metadata.get("page", "")}"/>\n{doc.page_content[:1000]}\n</Document>'
140
+ for doc in search_docs
141
+ ])
142
+ return {"arvix_results": formatted_search_docs}
143
+
144
+
145
+
146
+ # load the system prompt from the file
147
+ with open("system_prompt.txt", "r", encoding="utf-8") as f:
148
+ system_prompt = f.read()
149
+
150
+ # System message
151
+ sys_msg = SystemMessage(content=system_prompt)
152
+
153
+ # build a retriever
154
+ embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2") # dim=768
155
+
156
+ INDEX_PATH = "faiss_index"
157
+ if os.path.exists(INDEX_PATH):
158
+ vector_store = FAISS.load_local(INDEX_PATH, embeddings, allow_dangerous_deserialization=True)
159
+ else:
160
+ vector_store = FAISS.from_texts(["__init__"], embeddings)
161
+ vector_store.save_local(INDEX_PATH)
162
+
163
+ create_retriever_tool = create_retriever_tool(
164
+ retriever=vector_store.as_retriever(),
165
+ name="Question Search",
166
+ description="A tool to retrieve similar questions from a local FAISS vector store."
167
+ )
168
+
169
+
170
+
171
+ tools = [
172
+ multiply,
173
+ add,
174
+ subtract,
175
+ divide,
176
+ modulus,
177
+ wiki_search,
178
+ web_search,
179
+ arvix_search,
180
+ ]
181
+
182
+ # Build graph function
183
+ def build_graph(provider: str = "groq"):
184
+ """Build the graph"""
185
+ # Load environment variables from .env file
186
+ if provider == "google":
187
+ # Google Gemini
188
+ llm = ChatGoogleGenerativeAI(model="gemini-2.0-flash", temperature=0)
189
+ elif provider == "groq":
190
+ # Groq https://console.groq.com/docs/models
191
+ try:
192
+ llm = ChatGroq(model="deepseek-r1-distill-llama-70b", temperature=0) # optional : qwen-qwq-32b gemma2-9b-it
193
+ except Exception as e:
194
+ print(f"Error initializing Groq: {str(e)}")
195
+ raise
196
+ elif provider == "huggingface":
197
+ # TODO: Add huggingface endpoint
198
+ llm = ChatHuggingFace(
199
+ llm=HuggingFaceEndpoint(
200
+ url="https://api-inference.huggingface.co/models/Meta-DeepLearning/llama-2-7b-chat-hf",
201
+ temperature=0,
202
+ ),
203
+ )
204
+ else:
205
+ raise ValueError("Invalid provider. Choose 'google', 'groq' or 'huggingface'.")
206
+
207
+ # Bind tools to LLM
208
+ llm_with_tools = llm.bind_tools(tools)
209
+
210
+ # Node
211
+ def assistant(state: MessagesState):
212
+ """Assistant node"""
213
+ try:
214
+ if not state["messages"] or not state["messages"][-1].content:
215
+ raise ValueError("Empty message content")
216
+ return {"messages": [llm_with_tools.invoke(state["messages"])]}
217
+ except Exception as e:
218
+ print(f"Error in assistant node: {str(e)}")
219
+ raise
220
+
221
+ def retriever(state: MessagesState):
222
+ """Retriever node"""
223
+ try:
224
+ if not state["messages"] or not state["messages"][0].content:
225
+ raise ValueError("Empty message content")
226
+ similar_question = vector_store.similarity_search(state["messages"][0].content)
227
+ example_msg = HumanMessage(
228
+ content=f"Here I provide a similar question and answer for reference: \n\n{similar_question[0].page_content}",
229
+ )
230
+ return {"messages": [sys_msg] + state["messages"] + [example_msg]}
231
+ except Exception as e:
232
+ print(f"Error in retriever node: {str(e)}")
233
+ raise
234
+
235
+ builder = StateGraph(MessagesState)
236
+ builder.add_node("retriever", retriever)
237
+ builder.add_node("assistant", assistant)
238
+ builder.add_node("tools", ToolNode(tools))
239
+ builder.add_edge(START, "retriever")
240
+ builder.add_edge("retriever", "assistant")
241
+ builder.add_conditional_edges(
242
+ "assistant",
243
+ tools_condition,
244
+ )
245
+ builder.add_edge("tools", "assistant")
246
+
247
+ # Compile graph
248
+ return builder.compile()
249
+
250
+ # test
251
+ if __name__ == "__main__":
252
+ question = "When was a picture of St. Thomas Aquinas first added to the Wikipedia page on the Principle of double effect?"
253
+ # Build the graph
254
+ graph = build_graph(provider="groq")
255
+ # Run the graph
256
+ messages = [HumanMessage(content=question)]
257
+ messages = graph.invoke({"messages": messages})
258
+ for m in messages["messages"]:
259
+ m.pretty_print()
utils/all_analysis.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ {'flow': [{'block_key': 'event_whenflagclicked_1', 'opcode': 'event_whenflagclicked', 'type': 'hat', 'inputs': {}, 'fields': {}}, {'block_key': 'data_setvariableto_1', 'opcode': 'data_setvariableto', 'type': 'stack', 'inputs': {'VALUE': {'kind': 'value', 'value': 0}}, 'fields': {'VARIABLE': ['score', None]}}, {'block_key': 'control_forever_1', 'opcode': 'control_forever', 'type': 'c_block', 'inputs': {'SUBSTACK': [{'block_key': 'control_if_1', 'opcode': 'control_if', 'type': 'c_block', 'inputs': {'CONDITION': [{'block_key': 'sensing_touchingobject_1', 'opcode': 'sensing_touchingobject', 'type': 'boolean', 'inputs': {'TOUCHINGOBJECTMENU': [1, 'sensing_touchingobjectmenu_1']}, 'fields': {}}], 'SUBSTACK': [{'block_key': 'data_changevariableby_1', 'opcode': 'data_changevariableby', 'type': 'stack', 'inputs': {'VALUE': {'kind': 'value', 'value': -1}}, 'fields': {'VARIABLE': ['score', None]}}, {'block_key': 'control_wait_1', 'opcode': 'control_wait', 'type': 'stack', 'inputs': {'DURATION': {'kind': 'value', 'value': 0.1}}, 'fields': {}}]}, 'fields': {}}]}, 'fields': {}}]}
utils/all_analysis_trace.txt ADDED
File without changes
utils/all_generated_blocks.json ADDED
@@ -0,0 +1,189 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "event_whenflagclicked_1": {
3
+ "block_name": "when green flag pressed",
4
+ "block_type": "Events",
5
+ "op_code": "event_whenflagclicked",
6
+ "block_shape": "Hat Block",
7
+ "functionality": "This Hat block initiates the script when the green flag is clicked, serving as the common starting point for most Scratch projects.",
8
+ "inputs": {},
9
+ "fields": {},
10
+ "shadow": false,
11
+ "topLevel": true,
12
+ "id": "event_whenflagclicked_1",
13
+ "next": "data_setvariableto_1",
14
+ "parent": null
15
+ },
16
+ "data_setvariableto_1": {
17
+ "block_name": "set [my variable v] to ()",
18
+ "block_type": "Data",
19
+ "block_shape": "Stack Block",
20
+ "op_code": "data_setvariableto",
21
+ "functionality": "Assigns a specific value (number, string, or boolean) to a variable.",
22
+ "inputs": {
23
+ "VALUE": {
24
+ "kind": "value",
25
+ "value": 0
26
+ }
27
+ },
28
+ "fields": {
29
+ "VARIABLE": [
30
+ "score",
31
+ null
32
+ ]
33
+ },
34
+ "shadow": false,
35
+ "topLevel": false,
36
+ "id": "data_setvariableto_1",
37
+ "next": "control_forever_1",
38
+ "parent": "event_whenflagclicked_1"
39
+ },
40
+ "data_setvariableto_2": {
41
+ "block_name": "set [my variable v] to ()",
42
+ "block_type": "Data",
43
+ "block_shape": "Stack Block",
44
+ "op_code": "data_setvariableto",
45
+ "functionality": "Assigns a specific value (number, string, or boolean) to a variable.",
46
+ "inputs": {
47
+ "VALUE": [
48
+ 1,
49
+ [
50
+ 10,
51
+ "0"
52
+ ]
53
+ ]
54
+ },
55
+ "fields": {
56
+ "VARIABLE": [
57
+ "my variable",
58
+ "`jEk@4|i[#Fk?(8x)AV.-my variable"
59
+ ]
60
+ },
61
+ "shadow": false,
62
+ "topLevel": false,
63
+ "parent": null,
64
+ "next": null
65
+ },
66
+ "control_forever_1": {
67
+ "block_name": "forever",
68
+ "block_type": "Control",
69
+ "block_shape": "C-Block",
70
+ "op_code": "control_forever",
71
+ "functionality": "Continuously runs the blocks inside it.",
72
+ "inputs": {
73
+ "SUBSTACK": [
74
+ 2,
75
+ "control_if_1"
76
+ ]
77
+ },
78
+ "fields": {},
79
+ "shadow": false,
80
+ "topLevel": false,
81
+ "id": "control_forever_1",
82
+ "next": null,
83
+ "parent": "event_whenflagclicked_1"
84
+ },
85
+ "control_if_1": {
86
+ "block_name": "if <> then",
87
+ "block_type": "Control",
88
+ "block_shape": "C-Block",
89
+ "op_code": "control_if",
90
+ "functionality": "Executes the blocks inside it only if the specified boolean condition is true. [NOTE: it takes boolean blocks as input]",
91
+ "inputs": {
92
+ "CONDITION": {
93
+ "kind": "block",
94
+ "block": "sensing_touchingobject_1"
95
+ },
96
+ "SUBSTACK": [
97
+ 2,
98
+ "data_changevariableby_1"
99
+ ]
100
+ },
101
+ "fields": {},
102
+ "shadow": false,
103
+ "topLevel": false,
104
+ "id": "control_if_1",
105
+ "next": null,
106
+ "parent": "control_forever_1"
107
+ },
108
+ "control_wait_1": {
109
+ "block_name": "wait () seconds",
110
+ "block_type": "Control",
111
+ "block_shape": "Stack Block",
112
+ "op_code": "control_wait",
113
+ "functionality": "Pauses the script for a specified duration.",
114
+ "inputs": {
115
+ "DURATION": {
116
+ "kind": "value",
117
+ "value": 0.1
118
+ }
119
+ },
120
+ "fields": {},
121
+ "shadow": false,
122
+ "topLevel": false,
123
+ "id": "control_wait_1",
124
+ "next": null,
125
+ "parent": "control_if_1"
126
+ },
127
+ "sensing_touchingobject_1": {
128
+ "block_name": "<touching [edge v]?>",
129
+ "block_type": "Sensing",
130
+ "op_code": "sensing_touchingobject",
131
+ "block_shape": "Boolean Block",
132
+ "functionality": "Checks if its sprite is touching the mouse-pointer, edge, or another specified sprite.",
133
+ "inputs": {
134
+ "TOUCHINGOBJECTMENU": [
135
+ 1,
136
+ "sensing_touchingobjectmenu_1"
137
+ ]
138
+ },
139
+ "fields": {},
140
+ "shadow": false,
141
+ "topLevel": false,
142
+ "id": "sensing_touchingobject_1",
143
+ "parent": "control_if_1",
144
+ "next": null
145
+ },
146
+ "sensing_touchingobjectmenu_1": {
147
+ "block_name": "touching object menu",
148
+ "block_type": "Sensing",
149
+ "block_shape": "Reporter Block",
150
+ "op_code": "sensing_touchingobjectmenu",
151
+ "functionality": "Menu for touching object block.",
152
+ "inputs": {},
153
+ "fields": {
154
+ "TOUCHINGOBJECTMENU": [
155
+ "other sprite",
156
+ null
157
+ ]
158
+ },
159
+ "shadow": true,
160
+ "topLevel": false,
161
+ "id": "sensing_touchingobjectmenu_1",
162
+ "parent": "sensing_touchingobject_1",
163
+ "next": null
164
+ },
165
+ "data_changevariableby_1": {
166
+ "block_name": "change [my variable v] by ()",
167
+ "block_type": "Data",
168
+ "block_shape": "Stack Block",
169
+ "op_code": "data_changevariableby",
170
+ "functionality": "Increases or decreases a variable's numerical value by a specified amount.",
171
+ "inputs": {
172
+ "VALUE": {
173
+ "kind": "value",
174
+ "value": -1
175
+ }
176
+ },
177
+ "fields": {
178
+ "VARIABLE": [
179
+ "score",
180
+ null
181
+ ]
182
+ },
183
+ "shadow": false,
184
+ "topLevel": false,
185
+ "id": "data_changevariableby_1",
186
+ "next": "control_wait_1",
187
+ "parent": "control_if_1"
188
+ }
189
+ }
utils/block_correcter.py ADDED
@@ -0,0 +1,410 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+
3
+ def update_and_clean_blocks(all_generated_blocks, generated_output_blocks):
4
+ """
5
+ Updates the generated_output_blocks with values from all_generated_blocks
6
+ and removes specified keys from the updated blocks.
7
+
8
+ Args:
9
+ all_generated_blocks (dict): The dictionary containing all generated block data.
10
+ generated_output_blocks (dict): The dictionary to be updated and cleaned.
11
+
12
+ Returns:
13
+ dict: The updated and cleaned dictionary.
14
+ """
15
+ keys_to_remove = ["functionality", "block_shape", "id", "block_name", "block_type"]
16
+
17
+ updated_blocks = {}
18
+ for block_id, generated_block_data in generated_output_blocks.items():
19
+ if block_id in all_generated_blocks:
20
+ all_block_data = all_generated_blocks[block_id]
21
+
22
+ # Create a new dictionary for the updated block, including only desired fields
23
+ current_updated_block = {}
24
+ for key, value in generated_block_data.items():
25
+ if key not in keys_to_remove:
26
+ current_updated_block[key] = value
27
+
28
+ # Update specific values from all_generated_blocks if they exist
29
+ # and are not among the keys to be removed
30
+ if "next" in all_block_data and "next" not in keys_to_remove:
31
+ current_updated_block["next"] = all_block_data["next"]
32
+ if "parent" in all_block_data and "parent" not in keys_to_remove:
33
+ current_updated_block["parent"] = all_block_data["parent"]
34
+ if "topLevel" in all_block_data and "topLevel" not in keys_to_remove:
35
+ current_updated_block["topLevel"] = all_block_data["topLevel"]
36
+
37
+
38
+ updated_blocks[block_id] = current_updated_block
39
+ else:
40
+ # If a block_id from generated_output_blocks is not in all_generated_blocks,
41
+ # just clean it
42
+ current_updated_block = {}
43
+ for key, value in generated_block_data.items():
44
+ if key not in keys_to_remove:
45
+ current_updated_block[key] = value
46
+ updated_blocks[block_id] = current_updated_block
47
+
48
+ return updated_blocks
49
+
50
+ # Load the provided JSON data
51
+ all_generated_blocks = {
52
+ "event_whenflagclicked_1": {
53
+ "block_name": "when green flag pressed",
54
+ "block_type": "Events",
55
+ "op_code": "event_whenflagclicked",
56
+ "block_shape": "Hat Block",
57
+ "functionality": "This Hat block initiates the script when the green flag is clicked, serving as the common starting point for most Scratch projects.",
58
+ "inputs": {},
59
+ "fields": {},
60
+ "shadow": False,
61
+ "topLevel": True,
62
+ "id": "event_whenflagclicked_1",
63
+ "next": "data_setvariableto_1",
64
+ "parent": None
65
+ },
66
+ "data_setvariableto_1": {
67
+ "block_name": "set [my variable v] to ()",
68
+ "block_type": "Data",
69
+ "block_shape": "Stack Block",
70
+ "op_code": "data_setvariableto",
71
+ "functionality": "Assigns a specific value (number, string, or boolean) to a variable.",
72
+ "inputs": {
73
+ "VALUE": {
74
+ "kind": "value",
75
+ "value": 0
76
+ }
77
+ },
78
+ "fields": {
79
+ "VARIABLE": [
80
+ "score",
81
+ None
82
+ ]
83
+ },
84
+ "shadow": False,
85
+ "topLevel": False,
86
+ "id": "data_setvariableto_1",
87
+ "next": "control_forever_1",
88
+ "parent": "event_whenflagclicked_1"
89
+ },
90
+ "data_setvariableto_2": {
91
+ "block_name": "set [my variable v] to ()",
92
+ "block_type": "Data",
93
+ "block_shape": "Stack Block",
94
+ "op_code": "data_setvariableto",
95
+ "functionality": "Assigns a specific value (number, string, or boolean) to a variable.",
96
+ "inputs": {
97
+ "VALUE": [
98
+ 1,
99
+ [
100
+ 10,
101
+ "0"
102
+ ]
103
+ ]
104
+ },
105
+ "fields": {
106
+ "VARIABLE": [
107
+ "my variable",
108
+ "`jEk@4|i[#Fk?(8x)AV.-my variable"
109
+ ]
110
+ },
111
+ "shadow": False,
112
+ "topLevel": False,
113
+ "parent": None,
114
+ "next": None
115
+ },
116
+ "control_forever_1": {
117
+ "block_name": "forever",
118
+ "block_type": "Control",
119
+ "block_shape": "C-Block",
120
+ "op_code": "control_forever",
121
+ "functionality": "Continuously runs the blocks inside it.",
122
+ "inputs": {
123
+ "SUBSTACK": [
124
+ 2,
125
+ "control_if_1"
126
+ ]
127
+ },
128
+ "fields": {},
129
+ "shadow": False,
130
+ "topLevel": False,
131
+ "id": "control_forever_1",
132
+ "next": None,
133
+ "parent": "event_whenflagclicked_1"
134
+ },
135
+ "control_if_1": {
136
+ "block_name": "if <> then",
137
+ "block_type": "Control",
138
+ "block_shape": "C-Block",
139
+ "op_code": "control_if",
140
+ "functionality": "Executes the blocks inside it only if the specified boolean condition is true. [NOTE: it takes boolean blocks as input]",
141
+ "inputs": {
142
+ "CONDITION": {
143
+ "kind": "block",
144
+ "block": "sensing_touchingobject_1"
145
+ },
146
+ "SUBSTACK": [
147
+ 2,
148
+ "data_changevariableby_1"
149
+ ]
150
+ },
151
+ "fields": {},
152
+ "shadow": False,
153
+ "topLevel": False,
154
+ "id": "control_if_1",
155
+ "next": None,
156
+ "parent": "control_forever_1"
157
+ },
158
+ "control_wait_1": {
159
+ "block_name": "wait () seconds",
160
+ "block_type": "Control",
161
+ "block_shape": "Stack Block",
162
+ "op_code": "control_wait",
163
+ "functionality": "Pauses the script for a specified duration.",
164
+ "inputs": {
165
+ "DURATION": {
166
+ "kind": "value",
167
+ "value": 0.1
168
+ }
169
+ },
170
+ "fields": {},
171
+ "shadow": False,
172
+ "topLevel": False,
173
+ "id": "control_wait_1",
174
+ "next": None,
175
+ "parent": "control_if_1"
176
+ },
177
+ "sensing_touchingobject_1": {
178
+ "block_name": "<touching [edge v]?>",
179
+ "block_type": "Sensing",
180
+ "op_code": "sensing_touchingobject",
181
+ "block_shape": "Boolean Block",
182
+ "functionality": "Checks if its sprite is touching the mouse-pointer, edge, or another specified sprite.",
183
+ "inputs": {
184
+ "TOUCHINGOBJECTMENU": [
185
+ 1,
186
+ "sensing_touchingobjectmenu_1"
187
+ ]
188
+ },
189
+ "fields": {},
190
+ "shadow": False,
191
+ "topLevel": False,
192
+ "id": "sensing_touchingobject_1",
193
+ "parent": "control_if_1",
194
+ "next": None
195
+ },
196
+ "sensing_touchingobjectmenu_1": {
197
+ "block_name": "touching object menu",
198
+ "block_type": "Sensing",
199
+ "block_shape": "Reporter Block",
200
+ "op_code": "sensing_touchingobjectmenu",
201
+ "functionality": "Menu for touching object block.",
202
+ "inputs": {},
203
+ "fields": {
204
+ "TOUCHINGOBJECTMENU": [
205
+ "other sprite",
206
+ None
207
+ ]
208
+ },
209
+ "shadow": True,
210
+ "topLevel": False,
211
+ "id": "sensing_touchingobjectmenu_1",
212
+ "parent": "sensing_touchingobject_1",
213
+ "next": None
214
+ },
215
+ "data_changevariableby_1": {
216
+ "block_name": "change [my variable v] by ()",
217
+ "block_type": "Data",
218
+ "block_shape": "Stack Block",
219
+ "op_code": "data_changevariableby",
220
+ "functionality": "Increases or decreases a variable's numerical value by a specified amount.",
221
+ "inputs": {
222
+ "VALUE": {
223
+ "kind": "value",
224
+ "value": -1
225
+ }
226
+ },
227
+ "fields": {
228
+ "VARIABLE": [
229
+ "score",
230
+ None
231
+ ]
232
+ },
233
+ "shadow": False,
234
+ "topLevel": False,
235
+ "id": "data_changevariableby_1",
236
+ "next": "control_wait_1",
237
+ "parent": "control_if_1"
238
+ }
239
+ }
240
+
241
+ generated_output_blocks = {
242
+ "event_whenflagclicked_1": {
243
+ "block_name": "when green flag pressed",
244
+ "block_type": "Events",
245
+ "op_code": "event_whenflagclicked",
246
+ "block_shape": "Hat Block",
247
+ "functionality": "This Hat block initiates the script when the green flag is clicked, serving as the common starting point for most Scratch projects.",
248
+ "inputs": {},
249
+ "fields": {},
250
+ "shadow": False,
251
+ "topLevel": True,
252
+ "parent": None,
253
+ "next": None
254
+ },
255
+ "data_setvariableto_1": {
256
+ "block_name": "set [my variable v] to ()",
257
+ "block_type": "Data",
258
+ "block_shape": "Stack Block",
259
+ "op_code": "data_setvariableto",
260
+ "functionality": "Assigns a specific value (number, string, or boolean) to a variable.",
261
+ "inputs": {
262
+ "VALUE": [
263
+ 1,
264
+ [
265
+ 10,
266
+ "0"
267
+ ]
268
+ ]
269
+ },
270
+ "fields": {
271
+ "VARIABLE": [
272
+ "my variable",
273
+ "`jEk@4|i[#Fk?(8x)AV.-my variable"
274
+ ]
275
+ },
276
+ "shadow": False,
277
+ "topLevel": True,
278
+ "parent": None,
279
+ "next": None
280
+ },
281
+ "data_setvariableto_2": {
282
+ "block_name": "set [my variable v] to ()",
283
+ "block_type": "Data",
284
+ "block_shape": "Stack Block",
285
+ "op_code": "data_setvariableto",
286
+ "functionality": "Assigns a specific value (number, string, or boolean) to a variable.",
287
+ "inputs": {
288
+ "VALUE": [
289
+ 1,
290
+ [
291
+ 10,
292
+ "0"
293
+ ]
294
+ ]
295
+ },
296
+ "fields": {
297
+ "VARIABLE": [
298
+ "my variable",
299
+ "`jEk@4|i[#Fk?(8x)AV.-my variable"
300
+ ]
301
+ },
302
+ "shadow": False,
303
+ "topLevel": True,
304
+ "parent": None,
305
+ "next": None
306
+ },
307
+ "control_forever_1": {
308
+ "block_name": "forever",
309
+ "block_type": "Control",
310
+ "block_shape": "C-Block",
311
+ "op_code": "control_forever",
312
+ "functionality": "Continuously runs the blocks inside it.",
313
+ "inputs": {
314
+ "SUBSTACK": [
315
+ 2,
316
+ None
317
+ ]
318
+ },
319
+ "fields": {},
320
+ "shadow": False,
321
+ "topLevel": True,
322
+ "parent": None,
323
+ "next": None
324
+ },
325
+ "control_if_1": {
326
+ "block_name": "if <> then",
327
+ "block_type": "Control",
328
+ "block_shape": "C-Block",
329
+ "op_code": "control_if",
330
+ "functionality": "Executes the blocks inside it only if the specified boolean condition is true. [NOTE: it takes boolean blocks as input]",
331
+ "inputs": {
332
+ "CONDITION": [
333
+ 2,
334
+ None
335
+ ],
336
+ "SUBSTACK": [
337
+ 2,
338
+ None
339
+ ]
340
+ },
341
+ "fields": {},
342
+ "shadow": False,
343
+ "topLevel": True,
344
+ "parent": None,
345
+ "next": None
346
+ },
347
+ "control_wait_1": {
348
+ "block_name": "wait () seconds",
349
+ "block_type": "Control",
350
+ "block_shape": "Stack Block",
351
+ "op_code": "control_wait",
352
+ "functionality": "Pauses the script for a specified duration.",
353
+ "inputs": {
354
+ "DURATION": [
355
+ 1,
356
+ [
357
+ 5,
358
+ "1"
359
+ ]
360
+ ]
361
+ },
362
+ "fields": {},
363
+ "shadow": False,
364
+ "topLevel": True,
365
+ "parent": None,
366
+ "next": None
367
+ },
368
+ "sensing_touchingobject_1": {
369
+ "block_name": "<touching [edge v]?>",
370
+ "block_type": "Sensing",
371
+ "op_code": "sensing_touchingobject",
372
+ "block_shape": "Boolean Block",
373
+ "functionality": "Checks if its sprite is touching the mouse-pointer, edge, or another specified sprite.",
374
+ "inputs": {
375
+ "TOUCHINGOBJECTMENU": [
376
+ 1,
377
+ "sensing_touchingobjectmenu_1"
378
+ ]
379
+ },
380
+ "fields": {},
381
+ "shadow": False,
382
+ "topLevel": True,
383
+ "parent": None,
384
+ "next": None
385
+ },
386
+ "sensing_touchingobjectmenu_1": {
387
+ "block_name": "touching object menu",
388
+ "block_type": "Sensing",
389
+ "block_shape": "Reporter Block",
390
+ "op_code": "sensing_touchingobjectmenu",
391
+ "functionality": "Menu for touching object block.",
392
+ "inputs": {},
393
+ "fields": {
394
+ "TOUCHINGOBJECTMENU": [
395
+ "_mouse_",
396
+ None
397
+ ]
398
+ },
399
+ "shadow": True,
400
+ "topLevel": False,
401
+ "next": None,
402
+ "parent": "sensing_touchingobject_1"
403
+ }
404
+ }
405
+
406
+ # Process the blocks
407
+ processed_blocks = update_and_clean_blocks(all_generated_blocks, generated_output_blocks)
408
+
409
+ # Print the processed blocks in a readable JSON format
410
+ print(json.dumps(processed_blocks, indent=2))
utils/block_function.py ADDED
@@ -0,0 +1,1147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import copy
3
+ import re
4
+
5
+ def generate_blocks_from_opcodes(opcode_counts, all_block_definitions):
6
+ """
7
+ Generates a dictionary of Scratch-like blocks based on a list of opcodes and a reference block definition.
8
+ It now correctly links parent and menu blocks using their generated unique keys, and handles various block categories.
9
+ It ensures that menu blocks are only generated as children of their respective parent blocks.
10
+
11
+ Args:
12
+ opcode_counts (list): An array of objects, each with an 'opcode' and 'count' property.
13
+ Example: [{"opcode": "motion_gotoxy", "count": 1}]
14
+ all_block_definitions (dict): A comprehensive dictionary containing definitions for all block types.
15
+
16
+ Returns:
17
+ tuple: A tuple containing:
18
+ - dict: A JSON object where keys are generated block IDs and values are the block definitions.
19
+ - dict: The opcode_occurrences dictionary for consistent unique key generation across functions.
20
+ """
21
+ generated_blocks = {}
22
+ opcode_occurrences = {} # To keep track of how many times each opcode (main or menu) has been used for unique keys
23
+
24
+ # Define explicit parent-menu relationships for linking purposes
25
+ # This maps main_opcode -> list of (input_field_name, menu_opcode)
26
+ explicit_menu_links = {
27
+ "motion_goto": [("TO", "motion_goto_menu")],
28
+ "motion_glideto": [("TO", "motion_glideto_menu")],
29
+ "motion_pointtowards": [("TOWARDS", "motion_pointtowards_menu")],
30
+ "sensing_keypressed": [("KEY_OPTION", "sensing_keyoptions")],
31
+ "sensing_of": [("OBJECT", "sensing_of_object_menu")],
32
+ "sensing_touchingobject": [("TOUCHINGOBJECTMENU", "sensing_touchingobjectmenu")],
33
+ "control_create_clone_of": [("CLONE_OPTION", "control_create_clone_of_menu")],
34
+ "sound_play": [("SOUND_MENU", "sound_sounds_menu")],
35
+ "sound_playuntildone": [("SOUND_MENU", "sound_sounds_menu")],
36
+ "looks_switchcostumeto": [("COSTUME", "looks_costume")],
37
+ "looks_switchbackdropto": [("BACKDROP", "looks_backdrops")],
38
+ }
39
+
40
+ # --- Step 1: Process explicitly requested opcodes and generate their instances and associated menus ---
41
+ for item in opcode_counts:
42
+ opcode = item.get("opcode")
43
+ count = item.get("count", 1)
44
+
45
+ if not opcode:
46
+ print("Warning: Skipping item with missing 'opcode'.")
47
+ continue
48
+
49
+ if opcode not in all_block_definitions:
50
+ print(f"Warning: Opcode '{opcode}' not found in all_block_definitions. Skipping.")
51
+ continue
52
+
53
+ for _ in range(count):
54
+ # Increment occurrence count for the current main opcode
55
+ opcode_occurrences[opcode] = opcode_occurrences.get(opcode, 0) + 1
56
+ main_block_instance_num = opcode_occurrences[opcode]
57
+
58
+ main_block_unique_key = f"{opcode}_{main_block_instance_num}"
59
+
60
+ # Create a deep copy of the main block definition
61
+ main_block_data = copy.deepcopy(all_block_definitions[opcode])
62
+
63
+ # Set properties for a top-level main block
64
+ main_block_data["parent"] = None
65
+ main_block_data["next"] = None
66
+ main_block_data["topLevel"] = True
67
+ main_block_data["shadow"] = False # Main blocks are typically not shadows
68
+
69
+ generated_blocks[main_block_unique_key] = main_block_data
70
+
71
+ # If this main block has associated menus, generate and link them now
72
+ if opcode in explicit_menu_links:
73
+ for input_field_name, menu_opcode_type in explicit_menu_links[opcode]:
74
+ if menu_opcode_type in all_block_definitions:
75
+ # Increment the occurrence for the menu block type
76
+ opcode_occurrences[menu_opcode_type] = opcode_occurrences.get(menu_opcode_type, 0) + 1
77
+ menu_block_instance_num = opcode_occurrences[menu_opcode_type]
78
+
79
+ menu_block_data = copy.deepcopy(all_block_definitions[menu_opcode_type])
80
+
81
+ # Generate a unique key for this specific menu instance
82
+ menu_unique_key = f"{menu_opcode_type}_{menu_block_instance_num}"
83
+
84
+ # Set properties for a shadow menu block
85
+ menu_block_data["shadow"] = True
86
+ menu_block_data["topLevel"] = False
87
+ menu_block_data["next"] = None
88
+ menu_block_data["parent"] = main_block_unique_key # Link menu to its parent instance
89
+
90
+ # Update the main block's input to point to this unique menu instance
91
+ if input_field_name in main_block_data.get("inputs", {}) and \
92
+ isinstance(main_block_data["inputs"][input_field_name], list) and \
93
+ len(main_block_data["inputs"][input_field_name]) > 1 and \
94
+ main_block_data["inputs"][input_field_name][0] == 1:
95
+
96
+ main_block_data["inputs"][input_field_name][1] = menu_unique_key
97
+
98
+ generated_blocks[menu_unique_key] = menu_block_data
99
+
100
+ return generated_blocks, opcode_occurrences
101
+
102
+ def interpret_pseudo_code_and_update_blocks(generated_blocks_json, pseudo_code, all_block_definitions, opcode_occurrences):
103
+ """
104
+ Interprets pseudo-code to update the generated Scratch blocks, replacing static values
105
+ with dynamic values and establishing stacking/nesting logic.
106
+
107
+ Args:
108
+ generated_blocks_json (dict): The JSON object of pre-generated blocks.
109
+ pseudo_code (str): The pseudo-code string to interpret.
110
+ all_block_definitions (dict): A comprehensive dictionary containing definitions for all block types.
111
+ opcode_occurrences (dict): A dictionary to keep track of opcode occurrences for unique key generation.
112
+
113
+ Returns:
114
+ dict: The updated JSON object of Scratch blocks.
115
+ """
116
+ updated_blocks = copy.deepcopy(generated_blocks_json)
117
+
118
+ # Helper to find a block by opcode and optionally by a unique part of its key
119
+ def find_block_by_opcode(opcode_to_find, instance_num=None, parent_key=None):
120
+ for key, block in updated_blocks.items():
121
+ if block["opcode"] == opcode_to_find:
122
+ if instance_num is not None:
123
+ # Check if the key ends with the instance number
124
+ if key.endswith(f"_{instance_num}"):
125
+ return key, block
126
+ elif parent_key is not None:
127
+ # For menu blocks, check if their parent matches
128
+ if block.get("shadow") and block.get("parent") == parent_key:
129
+ return key, block
130
+ else:
131
+ # Return the first one found if no specific instance is needed
132
+ return key, block
133
+ return None, None
134
+
135
+ # Helper to get a unique key for a new block if needed
136
+ def get_unique_key(opcode_prefix):
137
+ count = 1
138
+ while f"{opcode_prefix}_{count}" in updated_blocks:
139
+ count += 1
140
+ return f"{opcode_prefix}_{count}"
141
+
142
+ lines = [line.strip() for line in pseudo_code.strip().split('\n') if line.strip()]
143
+
144
+ # Track the current script and nesting
145
+ current_script_head = None
146
+ current_parent_stack = [] # Stores (parent_block_key, indent_level, last_child_key)
147
+ indent_level = 0
148
+
149
+ # Create a mapping from block name patterns to their opcodes and input details
150
+ # Prioritize more specific patterns first
151
+ pseudo_code_to_opcode_map = {
152
+ re.compile(r"when green flag clicked"): {"opcode": "event_whenflagclicked"},
153
+ re.compile(r"go to x: \((.+?)\) y: \((.+?)\)"): {"opcode": "motion_gotoxy", "input_names": ["X", "Y"]},
154
+ re.compile(r"set \[(.+?) v\] to (.+)"): {"opcode": "data_setvariableto", "field_name": "VARIABLE", "input_name": "VALUE"},
155
+ re.compile(r"change \[(.+?) v\] by \((.+)\)"): {"opcode": "data_changevariableby", "field_name": "VARIABLE", "input_name": "VALUE"},
156
+ re.compile(r"show variable \[(.+?) v\]"): {"opcode": "data_showvariable", "field_name": "VARIABLE"},
157
+ re.compile(r"hide variable \[(.+?) v\]"): {"opcode": "data_hidevariable", "field_name": "VARIABLE"},
158
+ re.compile(r"forever"): {"opcode": "control_forever"},
159
+ re.compile(r"glide \((.+?)\) seconds to x: \((.+?)\) y: \((.+?)\)"): {"opcode": "motion_glidesecstoxy", "input_names": ["SECS", "X", "Y"]},
160
+ re.compile(r"if <\((.+)\) < \((.+)\)> then"): {"opcode": "control_if", "input_names": ["OPERAND1", "OPERAND2"], "condition_opcode": "operator_lt"},
161
+ re.compile(r"if <touching \[(.+?) v\]\?> then"): {"opcode": "control_if", "input_name": "TOUCHINGOBJECTMENU", "condition_opcode": "sensing_touchingobject"},
162
+ re.compile(r"set x to \((.+?)\)"): {"opcode": "motion_setx", "input_name": "X"},
163
+ re.compile(r"broadcast \[(.+?) v\]"): {"opcode": "event_broadcast", "input_name": "BROADCAST_INPUT"},
164
+ re.compile(r"stop \[(.+?) v\]"): {"opcode": "control_stop", "field_name": "STOP_OPTION"},
165
+ re.compile(r"end"): {"opcode": "end_block"}, # Special marker for script end/C-block end
166
+ }
167
+
168
+ # Create a reverse lookup for reporter block opcodes based on their pseudo-code representation
169
+ reporter_opcode_lookup = {}
170
+ for opcode, definition in all_block_definitions.items():
171
+ if definition.get("block_shape") == "Reporter Block":
172
+ block_name = definition.get("block_name")
173
+ if block_name:
174
+ # Remove parentheses for matching
175
+ clean_name = block_name.replace("(", "").replace(")", "").strip()
176
+ reporter_opcode_lookup[clean_name] = opcode
177
+ # Handle cases like "x position" vs "(x position)"
178
+ if clean_name not in reporter_opcode_lookup:
179
+ reporter_opcode_lookup[clean_name] = opcode
180
+
181
+ # Function to create a new block instance
182
+ def create_block_instance(opcode, opcode_occurrences, parent_key=None, is_shadow=False, is_top_level=False):
183
+ # Ensure unique key generation is consistent
184
+ opcode_occurrences[opcode] = opcode_occurrences.get(opcode, 0) + 1
185
+ unique_key = f"{opcode}_{opcode_occurrences[opcode]}"
186
+
187
+ new_block = copy.deepcopy(all_block_definitions.get(opcode, {}))
188
+ if not new_block:
189
+ print(f"Error: Definition for opcode '{opcode}' not found.")
190
+ return None, None
191
+
192
+ new_block["parent"] = parent_key
193
+ new_block["next"] = None # Will be set by stacking logic
194
+ new_block["topLevel"] = is_top_level
195
+ new_block["shadow"] = is_shadow
196
+
197
+ # Clear inputs/fields to be populated by pseudo-code parsing
198
+ if "inputs" in new_block:
199
+ new_block["inputs"] = {k: copy.deepcopy(v) for k, v in new_block["inputs"].items()} # Deep copy inputs
200
+ for input_name in new_block["inputs"]:
201
+ if isinstance(new_block["inputs"][input_name], list) and len(new_block["inputs"][input_name]) > 1:
202
+ # Reset input value, keep type 1 for block reference or type 4/10 for literal
203
+ if new_block["inputs"][input_name][0] == 1:
204
+ new_block["inputs"][input_name][1] = None # Placeholder for linked block ID
205
+ else:
206
+ new_block["inputs"][input_name][1] = ["", ""] # Default empty value
207
+ if "fields" in new_block:
208
+ new_block["fields"] = {k: copy.deepcopy(v) for k, v in new_block["fields"].items()} # Deep copy fields
209
+ for field_name in new_block["fields"]:
210
+ if isinstance(new_block["fields"][field_name], list) and len(new_block["fields"][field_name]) > 0:
211
+ new_block["fields"][field_name][0] = "" # Reset field value
212
+
213
+ updated_blocks[unique_key] = new_block
214
+ return unique_key, new_block
215
+
216
+ # Helper to parse input values
217
+ def parse_input_value(value_str):
218
+ value_str = value_str.strip()
219
+ # Handle numeric values (including those with + or - prefix)
220
+ if re.fullmatch(r"[-+]?\d+(\.\d+)?", value_str):
221
+ return [4, value_str] # Type 4 for number
222
+ # Handle string literals (e.g., "Hello!")
223
+ if value_str.startswith('"') and value_str.endswith('"'):
224
+ return [10, value_str.strip('"')] # Type 10 for string
225
+ # Handle variable/list names (e.g., [score v], [my list v])
226
+ if value_str.startswith('[') and value_str.endswith(']'):
227
+ var_name = value_str[1:-1].replace(' v', '').strip()
228
+ # For inputs that expect a variable, we might need a data_variable block
229
+ # For now, if it's a variable reference in an input, we'll return its name.
230
+ # The calling context (e.g., set variable's field vs. an input) will determine type.
231
+ return [12, var_name] # Custom type 12 for variable name, to be resolved later
232
+
233
+ # Handle nested reporter blocks (e.g., (x position))
234
+ if value_str.startswith('(') and value_str.endswith(')'):
235
+ inner_content = value_str[1:-1].strip()
236
+ # Check if it's a known reporter block
237
+ if inner_content in reporter_opcode_lookup:
238
+ return [3, reporter_opcode_lookup[inner_content]] # Type 3 for reporter block reference
239
+ # If not a known reporter, treat as a number or string
240
+ if re.fullmatch(r"[-+]?\d+(\.\d+)?", inner_content):
241
+ return [4, inner_content]
242
+ return [10, inner_content] # Default to string if not found in reporters
243
+
244
+ # Handle boolean conditions (e.g., <(x position) < (-235)>) - these are usually handled by parent regex
245
+ if value_str.startswith('<') and value_str.endswith('>'):
246
+ inner_condition = value_str[1:-1].strip()
247
+ # This is typically handled by the regex that matched the 'if' block itself.
248
+ # If this is called for a standalone boolean, it would be a reporter.
249
+ for op, def_ in all_block_definitions.items():
250
+ if def_.get("block_shape") == "Boolean Block" and def_.get("block_name") and \
251
+ def_["block_name"].replace("<", "").replace(">", "").strip() == inner_condition:
252
+ return [2, op] # Type 2 for boolean block reference
253
+ return [10, inner_condition] # Default to string if not found
254
+
255
+ return [10, value_str] # Default to string literal
256
+
257
+
258
+ # Main parsing loop
259
+ block_stack = [] # (block_key, indent_level, last_child_key_in_scope) for tracking nesting
260
+
261
+ for line_idx, raw_line in enumerate(lines):
262
+ current_line_indent = len(raw_line) - len(raw_line.lstrip())
263
+ line = raw_line.strip()
264
+
265
+ # Adjust block_stack based on current indent level
266
+ while block_stack and current_line_indent <= block_stack[-1][1]:
267
+ block_stack.pop()
268
+
269
+ matched_block_info = None
270
+ matched_values = None
271
+
272
+ # Try to match the line against known block patterns
273
+ for pattern_regex, info in pseudo_code_to_opcode_map.items():
274
+ match = pattern_regex.match(line)
275
+ if match:
276
+ matched_block_info = info
277
+ matched_values = match.groups()
278
+ break
279
+
280
+ if not matched_block_info:
281
+ print(f"Warning: Could not interpret line: '{line}'")
282
+ continue
283
+
284
+ opcode = matched_block_info["opcode"]
285
+
286
+ # Handle 'end' block separately as it signifies closing a C-block
287
+ if opcode == "end_block":
288
+ if block_stack:
289
+ block_stack.pop() # Pop the C-block parent
290
+ continue
291
+
292
+ parent_key = None
293
+ if block_stack:
294
+ parent_key = block_stack[-1][0] # The last block on the stack is the parent
295
+
296
+ # Create the new block instance
297
+ new_block_key, new_block_data = create_block_instance(
298
+ opcode,
299
+ opcode_occurrences,
300
+ parent_key=parent_key,
301
+ is_top_level=(parent_key is None)
302
+ )
303
+ if not new_block_key:
304
+ continue
305
+
306
+ # Link to previous block in the same script/nesting level
307
+ if block_stack:
308
+ # Update the 'next' of the previous block in the current scope
309
+ last_child_key_in_scope = block_stack[-1][2] if len(block_stack[-1]) > 2 else None
310
+ if last_child_key_in_scope and last_child_key_in_scope in updated_blocks:
311
+ updated_blocks[last_child_key_in_scope]["next"] = new_block_key
312
+
313
+ # Update the last child in the current scope
314
+ block_stack[-1] = (block_stack[-1][0], block_stack[-1][1], new_block_key)
315
+
316
+ # Populate inputs and fields
317
+ if matched_values:
318
+ # Handle specific block types with their inputs/fields
319
+ if opcode == "motion_gotoxy":
320
+ x_val = parse_input_value(matched_values[0])
321
+ y_val = parse_input_value(matched_values[1])
322
+ new_block_data["inputs"]["X"][1] = x_val[1]
323
+ new_block_data["inputs"]["Y"][1] = y_val[1]
324
+ new_block_data["inputs"]["X"][0] = x_val[0]
325
+ new_block_data["inputs"]["Y"][0] = y_val[0]
326
+ elif opcode == "data_setvariableto":
327
+ var_name = matched_values[0].replace(' v', '').strip()
328
+ value_parsed = parse_input_value(matched_values[1])
329
+ new_block_data["fields"]["VARIABLE"][0] = var_name
330
+ # Assuming variable ID is generated elsewhere or can be looked up
331
+ new_block_data["fields"]["VARIABLE"][1] = f"`var_{var_name}" # Placeholder for variable ID
332
+ new_block_data["inputs"]["VALUE"][0] = value_parsed[0]
333
+ new_block_data["inputs"]["VALUE"][1] = value_parsed[1]
334
+ elif opcode == "data_showvariable":
335
+ var_name = matched_values[0].replace(' v', '').strip()
336
+ new_block_data["fields"]["VARIABLE"][0] = var_name
337
+ new_block_data["fields"]["VARIABLE"][1] = f"`var_{var_name}" # Placeholder for variable ID
338
+ elif opcode == "motion_glidesecstoxy":
339
+ secs_val = parse_input_value(matched_values[0])
340
+ x_val = parse_input_value(matched_values[1])
341
+ y_val = parse_input_value(matched_values[2])
342
+ new_block_data["inputs"]["SECS"][1] = secs_val[1]
343
+ new_block_data["inputs"]["X"][1] = x_val[1]
344
+ new_block_data["inputs"]["Y"][1] = y_val[1]
345
+ new_block_data["inputs"]["SECS"][0] = secs_val[0]
346
+ new_block_data["inputs"]["X"][0] = x_val[0]
347
+ new_block_data["inputs"]["Y"][0] = y_val[0]
348
+ elif opcode == "motion_setx":
349
+ x_val = parse_input_value(matched_values[0])
350
+ new_block_data["inputs"]["X"][1] = x_val[1]
351
+ new_block_data["inputs"]["X"][0] = x_val[0]
352
+ elif opcode == "control_if":
353
+ condition_opcode = matched_block_info["condition_opcode"]
354
+
355
+ if condition_opcode == "operator_lt":
356
+ op1_str = matched_values[0].strip()
357
+ op2_str = matched_values[1].strip()
358
+
359
+ op1_parsed = parse_input_value(op1_str)
360
+ op2_parsed = parse_input_value(op2_str)
361
+
362
+ # Create operator_lt block as a shadow input for the IF condition
363
+ lt_block_key, lt_block_data = create_block_instance(
364
+ "operator_lt",
365
+ opcode_occurrences,
366
+ parent_key=new_block_key,
367
+ is_shadow=True,
368
+ is_top_level=False
369
+ )
370
+ if lt_block_key:
371
+ new_block_data["inputs"]["CONDITION"][1] = lt_block_key
372
+ new_block_data["inputs"]["CONDITION"][0] = 2 # Type 2 for boolean block reference
373
+
374
+ # Populate operator_lt inputs
375
+ if op1_parsed[0] == 3: # If it's a reporter block
376
+ op1_reporter_key, op1_reporter_data = create_block_instance(
377
+ op1_parsed[1], # Opcode of the reporter
378
+ opcode_occurrences,
379
+ parent_key=lt_block_key,
380
+ is_shadow=True,
381
+ is_top_level=False
382
+ )
383
+ if op1_reporter_key:
384
+ lt_block_data["inputs"]["OPERAND1"][1] = op1_reporter_key
385
+ lt_block_data["inputs"]["OPERAND1"][0] = 3
386
+ else: # Literal value
387
+ lt_block_data["inputs"]["OPERAND1"][1] = op1_parsed[1]
388
+ lt_block_data["inputs"]["OPERAND1"][0] = op1_parsed[0]
389
+
390
+ lt_block_data["inputs"]["OPERAND2"][1] = op2_parsed[1]
391
+ lt_block_data["inputs"]["OPERAND2"][0] = op2_parsed[0]
392
+
393
+ elif condition_opcode == "sensing_touchingobject":
394
+ sprite_name = matched_values[0].replace(' v', '').strip()
395
+ touching_opcode = "sensing_touchingobject"
396
+
397
+ touching_block_key, touching_block_data = create_block_instance(
398
+ touching_opcode,
399
+ opcode_occurrences,
400
+ parent_key=new_block_key,
401
+ is_shadow=True,
402
+ is_top_level=False
403
+ )
404
+ if touching_block_key:
405
+ new_block_data["inputs"]["CONDITION"][1] = touching_block_key
406
+ new_block_data["inputs"]["CONDITION"][0] = 2 # Type 2 for boolean block reference
407
+
408
+ # Create the menu block for TOUCHINGOBJECTMENU
409
+ menu_opcode = "sensing_touchingobjectmenu"
410
+ menu_key, menu_data = create_block_instance(
411
+ menu_opcode,
412
+ opcode_occurrences,
413
+ parent_key=touching_block_key,
414
+ is_shadow=True,
415
+ is_top_level=False
416
+ )
417
+ if menu_key:
418
+ touching_block_data["inputs"]["TOUCHINGOBJECTMENU"][1] = menu_key
419
+ touching_block_data["inputs"]["TOUCHINGOBJECTMENU"][0] = 1 # Type 1 for block reference
420
+ menu_data["fields"]["TOUCHINGOBJECTMENU"][0] = sprite_name
421
+ else:
422
+ print(f"Warning: Could not create touching object block for condition: '{line}'")
423
+
424
+
425
+ elif opcode == "control_forever":
426
+ # Forever blocks are C-blocks, so push them onto the stack
427
+ block_stack.append((new_block_key, current_line_indent, None)) # (parent_key, indent, last_child_key)
428
+ elif opcode == "control_stop":
429
+ option = matched_values[0].replace(' v', '').strip()
430
+ new_block_data["fields"]["STOP_OPTION"][0] = option
431
+ elif opcode == "event_broadcast":
432
+ message = matched_values[0].replace(' v', '').strip()
433
+ # For broadcast, the input is usually a string literal or a variable
434
+ new_block_data["inputs"]["BROADCAST_INPUT"][0] = 11 # Type 11 for broadcast input (string or variable)
435
+ new_block_data["inputs"]["BROADCAST_INPUT"][1] = [10, message] # Assume string literal for now
436
+ # A more robust solution would create a data_variable block if it's a variable.
437
+
438
+ # For C-blocks, push onto stack to track nesting
439
+ if all_block_definitions[opcode].get("block_shape") == "C-Block" and opcode != "control_if": # if is handled above
440
+ block_stack.append((new_block_key, current_line_indent, None)) # (parent_key, indent, last_child_key)
441
+
442
+
443
+ return updated_blocks
444
+
445
+ # --- Consolidated Block Definitions from all provided JSONs ---
446
+ all_block_definitions = {
447
+ # motion_block.json
448
+ "motion_movesteps": {
449
+ "opcode": "motion_movesteps", "next": None, "parent": None,
450
+ "inputs": {"STEPS": [1, [4, "10"]]}, "fields": {}, "shadow": False, "topLevel": True,
451
+ "x": 464, "y": -416
452
+ },
453
+ "motion_turnright": {
454
+ "opcode": "motion_turnright", "next": None, "parent": None,
455
+ "inputs": {"DEGREES": [1, [4, "15"]]}, "fields": {}, "shadow": False, "topLevel": True,
456
+ "x": 467, "y": -316
457
+ },
458
+ "motion_turnleft": {
459
+ "opcode": "motion_turnleft", "next": None, "parent": None,
460
+ "inputs": {"DEGREES": [1, [4, "15"]]}, "fields": {}, "shadow": False, "topLevel": True,
461
+ "x": 464, "y": -210
462
+ },
463
+ "motion_goto": {
464
+ "opcode": "motion_goto", "next": None, "parent": None,
465
+ "inputs": {"TO": [1, "motion_goto_menu"]}, "fields": {}, "shadow": False, "topLevel": True,
466
+ "x": 465, "y": -95
467
+ },
468
+ "motion_goto_menu": {
469
+ "opcode": "motion_goto_menu", "next": None, "parent": "motion_goto",
470
+ "inputs": {}, "fields": {"TO": ["_random_", None]}, "shadow": True, "topLevel": False
471
+ },
472
+ "motion_gotoxy": {
473
+ "opcode": "motion_gotoxy", "next": None, "parent": None,
474
+ "inputs": {"X": [1, [4, "0"]], "Y": [1, [4, "0"]]}, "fields": {}, "shadow": False, "topLevel": True,
475
+ "x": 468, "y": 12
476
+ },
477
+ "motion_glideto": {
478
+ "opcode": "motion_glideto", "next": None, "parent": None,
479
+ "inputs": {"SECS": [1, [4, "1"]], "TO": [1, "motion_glideto_menu"]}, "fields": {}, "shadow": False, "topLevel": True,
480
+ "x": 470, "y": 129
481
+ },
482
+ "motion_glideto_menu": {
483
+ "opcode": "motion_glideto_menu", "next": None, "parent": "motion_glideto",
484
+ "inputs": {}, "fields": {"TO": ["_random_", None]}, "shadow": True, "topLevel": False
485
+ },
486
+ "motion_glidesecstoxy": {
487
+ "opcode": "motion_glidesecstoxy", "next": None, "parent": None,
488
+ "inputs": {"SECS": [1, [4, "1"]], "X": [1, [4, "0"]], "Y": [1, [4, "0"]]}, "fields": {}, "shadow": False, "topLevel": True,
489
+ "x": 476, "y": 239
490
+ },
491
+ "motion_pointindirection": {
492
+ "opcode": "motion_pointindirection", "next": None, "parent": None,
493
+ "inputs": {"DIRECTION": [1, [8, "90"]]}, "fields": {}, "shadow": False, "topLevel": True,
494
+ "x": 493, "y": 361
495
+ },
496
+ "motion_pointtowards": {
497
+ "opcode": "motion_pointtowards", "next": None, "parent": None,
498
+ "inputs": {"TOWARDS": [1, "motion_pointtowards_menu"]}, "fields": {}, "shadow": False, "topLevel": True,
499
+ "x": 492, "y": 463
500
+ },
501
+ "motion_pointtowards_menu": {
502
+ "opcode": "motion_pointtowards_menu", "next": None, "parent": "motion_pointtowards",
503
+ "inputs": {}, "fields": {"TOWARDS": ["_mouse_", None]}, "shadow": True, "topLevel": False
504
+ },
505
+ "motion_changexby": {
506
+ "opcode": "motion_changexby", "next": None, "parent": None,
507
+ "inputs": {"DX": [1, [4, "10"]]}, "fields": {}, "shadow": False, "topLevel": True,
508
+ "x": 851, "y": -409
509
+ },
510
+ "motion_setx": {
511
+ "opcode": "motion_setx", "next": None, "parent": None,
512
+ "inputs": {"X": [1, [4, "0"]]}, "fields": {}, "shadow": False, "topLevel": True,
513
+ "x": 864, "y": -194
514
+ },
515
+ "motion_changeyby": {
516
+ "opcode": "motion_changeyby", "next": None, "parent": None,
517
+ "inputs": {"DY": [1, [4, "10"]]}, "fields": {}, "shadow": False, "topLevel": True,
518
+ "x": 861, "y": -61
519
+ },
520
+ "motion_sety": {
521
+ "opcode": "motion_sety", "next": None, "parent": None,
522
+ "inputs": {"Y": [1, [4, "0"]]}, "fields": {}, "shadow": False, "topLevel": True,
523
+ "x": 864, "y": 66
524
+ },
525
+ "motion_ifonedgebounce": {
526
+ "opcode": "motion_ifonedgebounce", "next": None, "parent": None,
527
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
528
+ "x": 1131, "y": -397
529
+ },
530
+ "motion_setrotationstyle": {
531
+ "opcode": "motion_setrotationstyle", "next": None, "parent": None,
532
+ "inputs": {}, "fields": {"STYLE": ["left-right", None]}, "shadow": False, "topLevel": True,
533
+ "x": 1128, "y": -287
534
+ },
535
+ "motion_xposition": {
536
+ "opcode": "motion_xposition", "next": None, "parent": None,
537
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
538
+ "x": 1193, "y": -136
539
+ },
540
+ "motion_yposition": {
541
+ "opcode": "motion_yposition", "next": None, "parent": None,
542
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
543
+ "x": 1181, "y": -64
544
+ },
545
+ "motion_direction": {
546
+ "opcode": "motion_direction", "next": None, "parent": None,
547
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
548
+ "x": 1188, "y": 21
549
+ },
550
+
551
+ # control_block.json
552
+ "control_wait": {
553
+ "opcode": "control_wait", "next": None, "parent": None,
554
+ "inputs": {"DURATION": [1, [5, "1"]]}, "fields": {}, "shadow": False, "topLevel": True,
555
+ "x": 337, "y": 129
556
+ },
557
+ "control_repeat": {
558
+ "opcode": "control_repeat", "next": None, "parent": None,
559
+ "inputs": {"TIMES": [1, [6, "10"]]}, "fields": {}, "shadow": False, "topLevel": True,
560
+ "x": 348, "y": 265
561
+ },
562
+ "control_forever": {
563
+ "opcode": "control_forever", "next": None, "parent": None,
564
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
565
+ "x": 334, "y": 439
566
+ },
567
+ "control_if": {
568
+ "opcode": "control_if", "next": None, "parent": None,
569
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
570
+ "x": 331, "y": 597
571
+ },
572
+ "control_if_else": {
573
+ "opcode": "control_if_else", "next": None, "parent": None,
574
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
575
+ "x": 335, "y": 779
576
+ },
577
+ "control_wait_until": {
578
+ "opcode": "control_wait_until", "next": None, "parent": None,
579
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
580
+ "x": 676, "y": 285
581
+ },
582
+ "control_repeat_until": {
583
+ "opcode": "control_repeat_until", "next": None, "parent": None,
584
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
585
+ "x": 692, "y": 381
586
+ },
587
+ "control_stop": {
588
+ "opcode": "control_stop", "next": None, "parent": None,
589
+ "inputs": {}, "fields": {"STOP_OPTION": ["all", None]}, "shadow": False, "topLevel": True,
590
+ "x": 708, "y": 545, "mutation": {"tagName": "mutation", "children": [], "hasnext": "false"}
591
+ },
592
+ "control_start_as_clone": {
593
+ "opcode": "control_start_as_clone", "next": None, "parent": None,
594
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
595
+ "x": 665, "y": 672
596
+ },
597
+ "control_create_clone_of": {
598
+ "opcode": "control_create_clone_of", "next": None, "parent": None,
599
+ "inputs": {"CLONE_OPTION": [1, "control_create_clone_of_menu"]}, "fields": {}, "shadow": False, "topLevel": True,
600
+ "x": 648, "y": 797
601
+ },
602
+ "control_create_clone_of_menu": {
603
+ "opcode": "control_create_clone_of_menu", "next": None, "parent": "control_create_clone_of",
604
+ "inputs": {}, "fields": {"CLONE_OPTION": ["_myself_", None]}, "shadow": True, "topLevel": False
605
+ },
606
+ "control_delete_this_clone": {
607
+ "opcode": "control_delete_this_clone", "next": None, "parent": None,
608
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
609
+ "x": 642, "y": 914
610
+ },
611
+
612
+ # data_block.json
613
+ "data_setvariableto": {
614
+ "opcode": "data_setvariableto", "next": None, "parent": None,
615
+ "inputs": {"VALUE": [1, [10, "0"]]}, "fields": {"VARIABLE": ["my variable", "`jEk@4|i[#Fk?(8x)AV.-my variable"]}, "shadow": False, "topLevel": True,
616
+ "x": 348, "y": 241
617
+ },
618
+ "data_changevariableby": {
619
+ "opcode": "data_changevariableby", "next": None, "parent": None,
620
+ "inputs": {"VALUE": [1, [4, "1"]]}, "fields": {"VARIABLE": ["my variable", "`jEk@4|i[#Fk?(8x)AV.-my variable"]}, "shadow": False, "topLevel": True,
621
+ "x": 313, "y": 363
622
+ },
623
+ "data_showvariable": {
624
+ "opcode": "data_showvariable", "next": None, "parent": None,
625
+ "inputs": {}, "fields": {"VARIABLE": ["my variable", "`jEk@4|i[#Fk?(8x)AV.-my variable"]}, "shadow": False, "topLevel": True,
626
+ "x": 415, "y": 473
627
+ },
628
+ "data_hidevariable": {
629
+ "opcode": "data_hidevariable", "next": None, "parent": None,
630
+ "inputs": {}, "fields": {"VARIABLE": ["my variable", "`jEk@4|i[#Fk?(8x)AV.-my variable"]}, "shadow": False, "topLevel": True,
631
+ "x": 319, "y": 587
632
+ },
633
+ "data_addtolist": {
634
+ "opcode": "data_addtolist", "next": None, "parent": None,
635
+ "inputs": {"ITEM": [1, [10, "thing"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True,
636
+ "x": 385, "y": 109
637
+ },
638
+ "data_deleteoflist": {
639
+ "opcode": "data_deleteoflist", "next": None, "parent": None,
640
+ "inputs": {"INDEX": [1, [7, "1"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True,
641
+ "x": 384, "y": 244
642
+ },
643
+ "data_deletealloflist": {
644
+ "opcode": "data_deletealloflist", "next": None, "parent": None,
645
+ "inputs": {}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True,
646
+ "x": 387, "y": 374
647
+ },
648
+ "data_insertatlist": {
649
+ "opcode": "data_insertatlist", "next": None, "parent": None,
650
+ "inputs": {"ITEM": [1, [10, "thing"]], "INDEX": [1, [7, "1"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True,
651
+ "x": 366, "y": 527
652
+ },
653
+ "data_replaceitemoflist": {
654
+ "opcode": "data_replaceitemoflist", "next": None, "parent": None,
655
+ "inputs": {"INDEX": [1, [7, "1"]], "ITEM": [1, [10, "thing"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True,
656
+ "x": 365, "y": 657
657
+ },
658
+ "data_itemoflist": {
659
+ "opcode": "data_itemoflist", "next": None, "parent": None,
660
+ "inputs": {"INDEX": [1, [7, "1"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True,
661
+ "x": 862, "y": 117
662
+ },
663
+ "data_itemnumoflist": {
664
+ "opcode": "data_itemnumoflist", "next": None, "parent": None,
665
+ "inputs": {"ITEM": [1, [10, "thing"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True,
666
+ "x": 883, "y": 238
667
+ },
668
+ "data_lengthoflist": {
669
+ "opcode": "data_lengthoflist", "next": None, "parent": None,
670
+ "inputs": {}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True,
671
+ "x": 876, "y": 342
672
+ },
673
+ "data_listcontainsitem": {
674
+ "opcode": "data_listcontainsitem", "next": None, "parent": None,
675
+ "inputs": {"ITEM": [1, [10, "thing"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True,
676
+ "x": 871, "y": 463
677
+ },
678
+ "data_showlist": {
679
+ "opcode": "data_showlist", "next": None, "parent": None,
680
+ "inputs": {}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True,
681
+ "x": 931, "y": 563
682
+ },
683
+ "data_hidelist": {
684
+ "opcode": "data_hidelist", "next": None, "parent": None,
685
+ "inputs": {}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True,
686
+ "x": 962, "y": 716
687
+ },
688
+
689
+ # event_block.json
690
+ "event_whenflagclicked": {
691
+ "opcode": "event_whenflagclicked", "next": None, "parent": None,
692
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
693
+ "x": 166, "y": -422
694
+ },
695
+ "event_whenkeypressed": {
696
+ "opcode": "event_whenkeypressed", "next": None, "parent": None,
697
+ "inputs": {}, "fields": {"KEY_OPTION": ["space", None]}, "shadow": False, "topLevel": True,
698
+ "x": 151, "y": -329
699
+ },
700
+ "event_whenthisspriteclicked": {
701
+ "opcode": "event_whenthisspriteclicked", "next": None, "parent": None,
702
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
703
+ "x": 156, "y": -223
704
+ },
705
+ "event_whenbackdropswitchesto": {
706
+ "opcode": "event_whenbackdropswitchesto", "next": None, "parent": None,
707
+ "inputs": {}, "fields": {"BACKDROP": ["backdrop1", None]}, "shadow": False, "topLevel": True,
708
+ "x": 148, "y": -101
709
+ },
710
+ "event_whengreaterthan": {
711
+ "opcode": "event_whengreaterthan", "next": None, "parent": None,
712
+ "inputs": {"VALUE": [1, [4, "10"]]}, "fields": {"WHENGREATERTHANMENU": ["LOUDNESS", None]}, "shadow": False, "topLevel": True,
713
+ "x": 150, "y": 10
714
+ },
715
+ "event_whenbroadcastreceived": {
716
+ "opcode": "event_whenbroadcastreceived", "next": None, "parent": None,
717
+ "inputs": {}, "fields": {"BROADCAST_OPTION": ["message1", "5O!nei;S$!c!=hCT}0:a"]}, "shadow": False, "topLevel": True,
718
+ "x": 141, "y": 118
719
+ },
720
+ "event_broadcast": {
721
+ "opcode": "event_broadcast", "next": None, "parent": None,
722
+ "inputs": {"BROADCAST_INPUT": [1, [11, "message1", "5O!nei;S$!c!=hCT}0:a"]]}, "fields": {}, "shadow": False, "topLevel": True,
723
+ "x": 151, "y": 229
724
+ },
725
+ "event_broadcastandwait": {
726
+ "opcode": "event_broadcastandwait", "next": None, "parent": None,
727
+ "inputs": {"BROADCAST_INPUT": [1, [11, "message1", "5O!nei;S$!c!=hCT}0:a"]]}, "fields": {}, "shadow": False, "topLevel": True,
728
+ "x": 157, "y": 340
729
+ },
730
+
731
+ # look_block.json
732
+ "looks_sayforsecs": {
733
+ "opcode": "looks_sayforsecs", "next": None, "parent": None,
734
+ "inputs": {"MESSAGE": [1, [10, "Hello!"]], "SECS": [1, [4, "2"]]}, "fields": {}, "shadow": False, "topLevel": True,
735
+ "x": 408, "y": 91
736
+ },
737
+ "looks_say": {
738
+ "opcode": "looks_say", "next": None, "parent": None,
739
+ "inputs": {"MESSAGE": [1, [10, "Hello!"]]}, "fields": {}, "shadow": False, "topLevel": True,
740
+ "x": 413, "y": 213
741
+ },
742
+ "looks_thinkforsecs": {
743
+ "opcode": "looks_thinkforsecs", "next": None, "parent": None,
744
+ "inputs": {"MESSAGE": [1, [10, "Hmm..."]], "SECS": [1, [4, "2"]]}, "fields": {}, "shadow": False, "topLevel": True,
745
+ "x": 413, "y": 317
746
+ },
747
+ "looks_think": {
748
+ "opcode": "looks_think", "next": None, "parent": None,
749
+ "inputs": {"MESSAGE": [1, [10, "Hmm..."]]}, "fields": {}, "shadow": False, "topLevel": True,
750
+ "x": 412, "y": 432
751
+ },
752
+ "looks_switchcostumeto": {
753
+ "opcode": "looks_switchcostumeto", "next": None, "parent": None,
754
+ "inputs": {"COSTUME": [1, "8;bti4wv(iH9nkOacCJ|"]}, "fields": {}, "shadow": False, "topLevel": True,
755
+ "x": 411, "y": 555
756
+ },
757
+ "looks_costume": {
758
+ "opcode": "looks_costume", "next": None, "parent": "Q#a,6LPWHqo9-0Nu*[SV",
759
+ "inputs": {}, "fields": {"COSTUME": ["costume2", None]}, "shadow": True, "topLevel": False
760
+ },
761
+ "looks_nextcostume": {
762
+ "opcode": "looks_nextcostume", "next": None, "parent": None,
763
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
764
+ "x": 419, "y": 687
765
+ },
766
+ "looks_switchbackdropto": {
767
+ "opcode": "looks_switchbackdropto", "next": None, "parent": None,
768
+ "inputs": {"BACKDROP": [1, "-?yeX}29V*wd6W:unW0i"]}, "fields": {}, "shadow": False, "topLevel": True,
769
+ "x": 901, "y": 91
770
+ },
771
+ "looks_backdrops": {
772
+ "opcode": "looks_backdrops", "next": None, "parent": "`Wm^p~l[(IWzc1|wNv*.",
773
+ "inputs": {}, "fields": {"BACKDROP": ["backdrop1", None]}, "shadow": True, "topLevel": False
774
+ },
775
+ "looks_changesizeby": {
776
+ "opcode": "looks_changesizeby", "next": None, "parent": None,
777
+ "inputs": {"CHANGE": [1, [4, "10"]]}, "fields": {}, "shadow": False, "topLevel": True,
778
+ "x": 895, "y": 192
779
+ },
780
+ "looks_setsizeto": {
781
+ "opcode": "looks_setsizeto", "next": None, "parent": None,
782
+ "inputs": {"SIZE": [1, [4, "100"]]}, "fields": {}, "shadow": False, "topLevel": True,
783
+ "x": 896, "y": 303
784
+ },
785
+ "looks_changeeffectby": {
786
+ "opcode": "looks_changeeffectby", "next": None, "parent": None,
787
+ "inputs": {"CHANGE": [1, [4, "25"]]}, "fields": {"EFFECT": ["COLOR", None]}, "shadow": False, "topLevel": True,
788
+ "x": 892, "y": 416
789
+ },
790
+ "looks_seteffectto": {
791
+ "opcode": "looks_seteffectto", "next": None, "parent": None,
792
+ "inputs": {"VALUE": [1, [4, "0"]]}, "fields": {"EFFECT": ["COLOR", None]}, "shadow": False, "topLevel": True,
793
+ "x": 902, "y": 527
794
+ },
795
+ "looks_cleargraphiceffects": {
796
+ "opcode": "looks_cleargraphiceffects", "next": None, "parent": None,
797
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
798
+ "x": 902, "y": 638
799
+ },
800
+ "looks_show": {
801
+ "opcode": "looks_show", "next": None, "parent": None,
802
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
803
+ "x": 908, "y": 758
804
+ },
805
+ "looks_hide": {
806
+ "opcode": "looks_hide", "next": None, "parent": None,
807
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
808
+ "x": 455, "y": 861
809
+ },
810
+ "looks_gotofrontback": {
811
+ "opcode": "looks_gotofrontback", "next": None, "parent": None,
812
+ "inputs": {}, "fields": {"FRONT_BACK": ["front", None]}, "shadow": False, "topLevel": True,
813
+ "x": 853, "y": 878
814
+ },
815
+ "looks_goforwardbackwardlayers": {
816
+ "opcode": "looks_goforwardbackwardlayers", "next": None, "parent": None,
817
+ "inputs": {"NUM": [1, [7, "1"]]}, "fields": {"FORWARD_BACKWARD": ["forward", None]}, "shadow": False, "topLevel": True,
818
+ "x": 851, "y": 999
819
+ },
820
+ "looks_costumenumbername": {
821
+ "opcode": "looks_costumenumbername", "next": None, "parent": None,
822
+ "inputs": {}, "fields": {"NUMBER_NAME": ["number", None]}, "shadow": False, "topLevel": True,
823
+ "x": 458, "y": 1007
824
+ },
825
+ "looks_backdropnumbername": {
826
+ "opcode": "looks_backdropnumbername", "next": None, "parent": None,
827
+ "inputs": {}, "fields": {"NUMBER_NAME": ["number", None]}, "shadow": False, "topLevel": True,
828
+ "x": 1242, "y": 753
829
+ },
830
+ "looks_size": {
831
+ "opcode": "looks_size", "next": None, "parent": None,
832
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
833
+ "x": 1249, "y": 876
834
+ },
835
+
836
+ # operator_block.json
837
+ "operator_add": {
838
+ "opcode": "operator_add", "next": None, "parent": None,
839
+ "inputs": {"NUM1": [1, [4, ""]], "NUM2": [1, [4, ""]]}, "fields": {}, "shadow": False, "topLevel": True,
840
+ "x": 128, "y": 153
841
+ },
842
+ "operator_subtract": {
843
+ "opcode": "operator_subtract", "next": None, "parent": None,
844
+ "inputs": {"NUM1": [1, [4, ""]], "NUM2": [1, [4, ""]]}, "fields": {}, "shadow": False, "topLevel": True,
845
+ "x": 134, "y": 214
846
+ },
847
+ "operator_multiply": {
848
+ "opcode": "operator_multiply", "next": None, "parent": None,
849
+ "inputs": {"NUM1": [1, [4, ""]], "NUM2": [1, [4, ""]]}, "fields": {}, "shadow": False, "topLevel": True,
850
+ "x": 134, "y": 278
851
+ },
852
+ "operator_divide": {
853
+ "opcode": "operator_divide", "next": None, "parent": None,
854
+ "inputs": {"NUM1": [1, [4, ""]], "NUM2": [1, [4, ""]]}, "fields": {}, "shadow": False, "topLevel": True,
855
+ "x": 138, "y": 359
856
+ },
857
+ "operator_random": {
858
+ "opcode": "operator_random", "next": None, "parent": None,
859
+ "inputs": {"FROM": [1, [4, "1"]], "TO": [1, [4, "10"]]}, "fields": {}, "shadow": False, "topLevel": True,
860
+ "x": 311, "y": 157
861
+ },
862
+ "operator_gt": {
863
+ "opcode": "operator_gt", "next": None, "parent": None,
864
+ "inputs": {"OPERAND1": [1, [10, ""]], "OPERAND2": [1, [10, "50"]]}, "fields": {}, "shadow": False, "topLevel": True,
865
+ "x": 348, "y": 217
866
+ },
867
+ "operator_lt": {
868
+ "opcode": "operator_lt", "next": None, "parent": None,
869
+ "inputs": {"OPERAND1": [1, [10, ""]], "OPERAND2": [1, [10, "50"]]}, "fields": {}, "shadow": False, "topLevel": True,
870
+ "x": 345, "y": 286
871
+ },
872
+ "operator_equals": {
873
+ "opcode": "operator_equals", "next": None, "parent": None,
874
+ "inputs": {"OPERAND1": [1, [10, ""]], "OPERAND2": [1, [10, "50"]]}, "fields": {}, "shadow": False, "topLevel": True,
875
+ "x": 345, "y": 372
876
+ },
877
+ "operator_and": {
878
+ "opcode": "operator_and", "next": None, "parent": None,
879
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
880
+ "x": 701, "y": 158
881
+ },
882
+ "operator_or": {
883
+ "opcode": "operator_or", "next": None, "parent": None,
884
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
885
+ "x": 705, "y": 222
886
+ },
887
+ "operator_not": {
888
+ "opcode": "operator_not", "next": None, "parent": None,
889
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
890
+ "x": 734, "y": 283
891
+ },
892
+ "operator_join": {
893
+ "opcode": "operator_join", "next": None, "parent": None,
894
+ "inputs": {"STRING1": [1, [10, "apple "]], "STRING2": [1, [10, "banana"]]}, "fields": {}, "shadow": False, "topLevel": True,
895
+ "x": 663, "y": 378
896
+ },
897
+ "operator_letter_of": {
898
+ "opcode": "operator_letter_of", "next": None, "parent": None,
899
+ "inputs": {"LETTER": [1, [6, "1"]], "STRING": [1, [10, "apple"]]}, "fields": {}, "shadow": False, "topLevel": True,
900
+ "x": 664, "y": 445
901
+ },
902
+ "operator_length": {
903
+ "opcode": "operator_length", "next": None, "parent": None,
904
+ "inputs": {"STRING": [1, [10, "apple"]]}, "fields": {}, "shadow": False, "topLevel": True,
905
+ "x": 664, "y": 521
906
+ },
907
+ "operator_contains": {
908
+ "opcode": "operator_contains", "next": None, "parent": None,
909
+ "inputs": {"STRING1": [1, [10, "apple"]], "STRING2": [1, [10, "a"]]}, "fields": {}, "shadow": False, "topLevel": True,
910
+ "x": 634, "y": 599
911
+ },
912
+ "operator_mod": {
913
+ "opcode": "operator_mod", "next": None, "parent": None,
914
+ "inputs": {"NUM1": [1, [4, ""]], "NUM2": [1, [4, ""]]}, "fields": {}, "shadow": False, "topLevel": True,
915
+ "x": 295, "y": 594
916
+ },
917
+ "operator_round": {
918
+ "opcode": "operator_round", "next": None, "parent": None,
919
+ "inputs": {"NUM": [1, [4, ""]]}, "fields": {}, "shadow": False, "topLevel": True,
920
+ "x": 307, "y": 674
921
+ },
922
+ "operator_mathop": {
923
+ "opcode": "operator_mathop", "next": None, "parent": None,
924
+ "inputs": {"NUM": [1, [4, ""]]}, "fields": {"OPERATOR": ["abs", None]}, "shadow": False, "topLevel": True,
925
+ "x": 280, "y": 754
926
+ },
927
+
928
+ # sensing_block.json
929
+ "sensing_touchingobject": {
930
+ "opcode": "sensing_touchingobject", "next": None, "parent": None,
931
+ "inputs": {"TOUCHINGOBJECTMENU": [1, "sensing_touchingobjectmenu"]}, "fields": {}, "shadow": False, "topLevel": True,
932
+ "x": 359, "y": 116
933
+ },
934
+ "sensing_touchingobjectmenu": {
935
+ "opcode": "sensing_touchingobjectmenu", "next": None, "parent": "sensing_touchingobject",
936
+ "inputs": {}, "fields": {"TOUCHINGOBJECTMENU": ["_mouse_", None]}, "shadow": True, "topLevel": False
937
+ },
938
+ "sensing_touchingcolor": {
939
+ "opcode": "sensing_touchingcolor", "next": None, "parent": None,
940
+ "inputs": {"COLOR": [1, [9, "#55b888"]]}, "fields": {}, "shadow": False, "topLevel": True,
941
+ "x": 360, "y": 188
942
+ },
943
+ "sensing_coloristouchingcolor": {
944
+ "opcode": "sensing_coloristouchingcolor", "next": None, "parent": None,
945
+ "inputs": {"COLOR": [1, [9, "#d019f2"]], "COLOR2": [1, [9, "#2b0de3"]]}, "fields": {}, "shadow": False, "topLevel": True,
946
+ "x": 348, "y": 277
947
+ },
948
+ "sensing_askandwait": {
949
+ "opcode": "sensing_askandwait", "next": None, "parent": None,
950
+ "inputs": {"QUESTION": [1, [10, "What's your name?"]]}, "fields": {}, "shadow": False, "topLevel": True,
951
+ "x": 338, "y": 354
952
+ },
953
+ "sensing_answer": {
954
+ "opcode": "sensing_answer", "next": None, "parent": None,
955
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
956
+ "x": 782, "y": 111
957
+ },
958
+ "sensing_keypressed": {
959
+ "opcode": "sensing_keypressed", "next": None, "parent": None,
960
+ "inputs": {"KEY_OPTION": [1, "sensing_keyoptions"]}, "fields": {}, "shadow": False, "topLevel": True,
961
+ "x": 762, "y": 207
962
+ },
963
+ "sensing_keyoptions": {
964
+ "opcode": "sensing_keyoptions", "next": None, "parent": "sensing_keypressed",
965
+ "inputs": {}, "fields": {"KEY_OPTION": ["space", None]}, "shadow": True, "topLevel": False
966
+ },
967
+ "sensing_mousedown": {
968
+ "opcode": "sensing_mousedown", "next": None, "parent": None,
969
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
970
+ "x": 822, "y": 422
971
+ },
972
+ "sensing_mousex": {
973
+ "opcode": "sensing_mousex", "next": None, "parent": None,
974
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
975
+ "x": 302, "y": 528
976
+ },
977
+ "sensing_mousey": {
978
+ "opcode": "sensing_mousey", "next": None, "parent": None,
979
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
980
+ "x": 668, "y": 547
981
+ },
982
+ "sensing_setdragmode": {
983
+ "opcode": "sensing_setdragmode", "next": None, "parent": None,
984
+ "inputs": {}, "fields": {"DRAG_MODE": ["draggable", None]}, "shadow": False, "topLevel": True,
985
+ "x": 950, "y": 574
986
+ },
987
+ "sensing_loudness": {
988
+ "opcode": "sensing_loudness", "next": None, "parent": None,
989
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
990
+ "x": 658, "y": 703
991
+ },
992
+ "sensing_timer": {
993
+ "opcode": "sensing_timer", "next": None, "parent": None,
994
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
995
+ "x": 459, "y": 671
996
+ },
997
+ "sensing_resettimer": {
998
+ "opcode": "sensing_resettimer", "next": None, "parent": None,
999
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
1000
+ "x": 462, "y": 781
1001
+ },
1002
+ "sensing_of": {
1003
+ "opcode": "sensing_of", "next": None, "parent": None,
1004
+ "inputs": {"OBJECT": [1, "sensing_of_object_menu"]}, "fields": {"PROPERTY": ["backdrop #", None]}, "shadow": False, "topLevel": True,
1005
+ "x": 997, "y": 754
1006
+ },
1007
+ "sensing_of_object_menu": {
1008
+ "opcode": "sensing_of_object_menu", "next": None, "parent": "sensing_of",
1009
+ "inputs": {}, "fields": {"OBJECT": ["_stage_", None]}, "shadow": True, "topLevel": False
1010
+ },
1011
+ "sensing_current": {
1012
+ "opcode": "sensing_current", "next": None, "parent": None,
1013
+ "inputs": {}, "fields": {"CURRENTMENU": ["YEAR", None]}, "shadow": False, "topLevel": True,
1014
+ "x": 627, "y": 884
1015
+ },
1016
+ "sensing_dayssince2000": {
1017
+ "opcode": "sensing_dayssince2000", "next": None, "parent": None,
1018
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
1019
+ "x": 959, "y": 903
1020
+ },
1021
+ "sensing_username": {
1022
+ "opcode": "sensing_username", "next": None, "parent": None,
1023
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
1024
+ "x": 833, "y": 757
1025
+ },
1026
+
1027
+ # sound_block.json
1028
+ "sound_playuntildone": {
1029
+ "opcode": "sound_playuntildone", "next": None, "parent": None,
1030
+ "inputs": {"SOUND_MENU": [1, "sound_sounds_menu"]}, "fields": {}, "shadow": False, "topLevel": True,
1031
+ "x": 253, "y": 17
1032
+ },
1033
+ "sound_sounds_menu": {
1034
+ "opcode": "sound_sounds_menu", "next": None, "parent": "sound_playuntildone and sound_play",
1035
+ "inputs": {}, "fields": {"SOUND_MENU": ["Meow", None]}, "shadow": True, "topLevel": False
1036
+ },
1037
+ "sound_play": {
1038
+ "opcode": "sound_play", "next": None, "parent": None,
1039
+ "inputs": {"SOUND_MENU": [1, "sound_sounds_menu"]}, "fields": {}, "shadow": False, "topLevel": True,
1040
+ "x": 245, "y": 122
1041
+ },
1042
+ "sound_stopallsounds": {
1043
+ "opcode": "sound_stopallsounds", "next": None, "parent": None,
1044
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
1045
+ "x": 253, "y": 245
1046
+ },
1047
+ "sound_changeeffectby": {
1048
+ "opcode": "sound_changeeffectby", "next": None, "parent": None,
1049
+ "inputs": {"VALUE": [1, [4, "10"]]}, "fields": {"EFFECT": ["PITCH", None]}, "shadow": False, "topLevel": True,
1050
+ "x": 653, "y": 14
1051
+ },
1052
+ "sound_seteffectto": {
1053
+ "opcode": "sound_seteffectto", "next": None, "parent": None,
1054
+ "inputs": {"VALUE": [1, [4, "100"]]}, "fields": {"EFFECT": ["PITCH", None]}, "shadow": False, "topLevel": True,
1055
+ "x": 653, "y": 139
1056
+ },
1057
+ "sound_cleareffects": {
1058
+ "opcode": "sound_cleareffects", "next": None, "parent": None,
1059
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
1060
+ "x": 651, "y": 242
1061
+ },
1062
+ "sound_changevolumeby": {
1063
+ "opcode": "sound_changevolumeby", "next": None, "parent": None,
1064
+ "inputs": {"VOLUME": [1, [4, "-10"]]}, "fields": {}, "shadow": False, "topLevel": True,
1065
+ "x": 645, "y": 353
1066
+ },
1067
+ "sound_setvolumeto": {
1068
+ "opcode": "sound_setvolumeto", "next": None, "parent": None,
1069
+ "inputs": {"VOLUME": [1, [4, "100"]]}, "fields": {}, "shadow": False, "topLevel": True,
1070
+ "x": 1108, "y": 5
1071
+ },
1072
+ "sound_volume": {
1073
+ "opcode": "sound_volume", "next": None, "parent": None,
1074
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
1075
+ "x": 1136, "y": 123
1076
+ },
1077
+ }
1078
+
1079
+ # #Example input with opcodes from various categories
1080
+ # input_opcodes = [
1081
+ # {"opcode": "sound_play", "count": 2}, # New: Sound block with menu
1082
+ # {"opcode": "sound_playuntildone", "count": 2}, # New: Sound block with menu
1083
+ # ]
1084
+
1085
+ # Example input with opcodes from various categories
1086
+ # input_opcodes = [
1087
+ # {"opcode": "sound_play", "count": 2},
1088
+ # {"opcode": "sound_playuntildone", "count": 2},
1089
+ # {"opcode":"motion_goto","count":2},
1090
+ # {"opcode":"motion_glideto","count":2},
1091
+ # {"opcode":"looks_switchbackdropto","count":2},
1092
+ # {"opcode":"looks_switchcostumeto","count":2},
1093
+ # {"opcode":"control_create_clone_of","count":2},
1094
+ # {"opcode":"sensing_touchingobject","count":2},
1095
+ # {"opcode":"sensing_of","count":2},
1096
+ # {"opcode":"sensing_keypressed","count":2},
1097
+ # {"opcode":"motion_pointtowards","count":2},
1098
+ # ]
1099
+
1100
+ # generated_output = generate_blocks_from_opcodes(input_opcodes, all_block_definitions)
1101
+ # print(json.dumps(generated_output, indent=2))
1102
+
1103
+ initial_opcode_counts = [
1104
+ {"opcode":"event_whenflagclicked","count":1},
1105
+ {"opcode":"motion_gotoxy","count":1},
1106
+ {"opcode":"motion_glidesecstoxy","count":1},
1107
+ {"opcode":"motion_xposition","count":1},
1108
+ {"opcode":"motion_setx","count":1},
1109
+ {"opcode":"control_forever","count":1},
1110
+ {"opcode":"control_if","count":1},
1111
+ {"opcode":"control_stop","count":1},
1112
+ {"opcode":"operator_lt","count":1},
1113
+ {"opcode":"sensing_istouching","count":1},
1114
+ {"opcode":"sensing_touchingobjectmenu","count":1}, # This will now be generated as a child of sensing_touchingobject
1115
+ {"opcode":"event_broadcast","count":1},
1116
+ {"opcode":"data_setvariableto","count":2},
1117
+ {"opcode":"data_showvariable","count":2},
1118
+ ]
1119
+
1120
+ # Generate the initial blocks and get the opcode_occurrences
1121
+ generated_output_json, initial_opcode_occurrences = generate_blocks_from_opcodes(initial_opcode_counts, all_block_definitions)
1122
+
1123
+ # Pseudo-code to interpret
1124
+ pseudo_code_input = """
1125
+ when green flag clicked
1126
+ go to x: (240) y: (-135)
1127
+ set [score v] to +1
1128
+ set [speed v] to +1
1129
+ show variable [score v]
1130
+ show variable [speed v]
1131
+ forever
1132
+ glide (2) seconds to x: (-240) y: (-135)
1133
+ if <((x position)) < (-235)> then
1134
+ set x to (240)
1135
+ end
1136
+ if <touching [Sprite1 v]?> then
1137
+ broadcast [Game Over v]
1138
+ stop [all v]
1139
+ end
1140
+ end
1141
+ end
1142
+ """
1143
+
1144
+ # Interpret the pseudo-code and update the blocks, passing opcode_occurrences
1145
+ final_generated_blocks = interpret_pseudo_code_and_update_blocks(generated_output_json, pseudo_code_input, all_block_definitions, initial_opcode_occurrences)
1146
+
1147
+ print(json.dumps(final_generated_blocks, indent=2))
utils/block_function_v1.py ADDED
@@ -0,0 +1,759 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import copy
3
+
4
+ import json
5
+ import copy
6
+
7
+ def generate_blocks_from_opcodes(opcode_counts, all_block_definitions):
8
+ """
9
+ Generates a dictionary of Scratch-like blocks based on a list of opcodes and a reference block definition.
10
+ It now correctly links parent and menu blocks using their generated unique keys, and handles various block categories.
11
+ It ensures that menu blocks are only generated as children of their respective parent blocks.
12
+
13
+ Args:
14
+ opcode_counts (list): An array of objects, each with an 'opcode' and 'count' property.
15
+ Example: [{"opcode": "motion_gotoxy", "count": 1}]
16
+ all_block_definitions (dict): A comprehensive dictionary containing definitions for all block types.
17
+
18
+ Returns:
19
+ dict: A JSON object where keys are generated block IDs and values are the block definitions.
20
+ """
21
+ generated_blocks = {}
22
+ opcode_occurrences = {} # To keep track of how many times each opcode (main or menu) has been used for unique keys
23
+
24
+ # Define explicit parent-menu relationships for linking purposes
25
+ # This maps main_opcode -> list of (input_field_name, menu_opcode)
26
+ explicit_menu_links = {
27
+ "motion_goto": [("TO", "motion_goto_menu")],
28
+ "motion_glideto": [("TO", "motion_glideto_menu")],
29
+ "motion_pointtowards": [("TOWARDS", "motion_pointtowards_menu")],
30
+ "sensing_keypressed": [("KEY_OPTION", "sensing_keyoptions")],
31
+ "sensing_of": [("OBJECT", "sensing_of_object_menu")],
32
+ "sensing_touchingobject": [("TOUCHINGOBJECTMENU", "sensing_touchingobjectmenu")],
33
+ "control_create_clone_of": [("CLONE_OPTION", "control_create_clone_of_menu")],
34
+ "sound_play": [("SOUND_MENU", "sound_sounds_menu")],
35
+ "sound_playuntildone": [("SOUND_MENU", "sound_sounds_menu")],
36
+ "looks_switchcostumeto": [("COSTUME", "looks_costume")],
37
+ "looks_switchbackdropto": [("BACKDROP", "looks_backdrops")],
38
+ }
39
+
40
+ # --- Step 1: Process explicitly requested opcodes and generate their instances and associated menus ---
41
+ for item in opcode_counts:
42
+ opcode = item.get("opcode")
43
+ count = item.get("count", 1)
44
+
45
+ if not opcode:
46
+ print("Warning: Skipping item with missing 'opcode'.")
47
+ continue
48
+
49
+ if opcode not in all_block_definitions:
50
+ print(f"Warning: Opcode '{opcode}' not found in all_block_definitions. Skipping.")
51
+ continue
52
+
53
+ for _ in range(count):
54
+ # Increment occurrence count for the current main opcode
55
+ opcode_occurrences[opcode] = opcode_occurrences.get(opcode, 0) + 1
56
+ main_block_instance_num = opcode_occurrences[opcode]
57
+
58
+ main_block_unique_key = f"{opcode}_{main_block_instance_num}"
59
+
60
+ # Create a deep copy of the main block definition
61
+ main_block_data = copy.deepcopy(all_block_definitions[opcode])
62
+
63
+ # Set properties for a top-level main block
64
+ main_block_data["parent"] = None
65
+ main_block_data["next"] = None
66
+ main_block_data["topLevel"] = True
67
+ main_block_data["shadow"] = False # Main blocks are typically not shadows
68
+
69
+ generated_blocks[main_block_unique_key] = main_block_data
70
+
71
+ # If this main block has associated menus, generate and link them now
72
+ if opcode in explicit_menu_links:
73
+ for input_field_name, menu_opcode_type in explicit_menu_links[opcode]:
74
+ if menu_opcode_type in all_block_definitions:
75
+ # Increment the occurrence for the menu block type
76
+ opcode_occurrences[menu_opcode_type] = opcode_occurrences.get(menu_opcode_type, 0) + 1
77
+ menu_block_instance_num = opcode_occurrences[menu_opcode_type]
78
+
79
+ menu_block_data = copy.deepcopy(all_block_definitions[menu_opcode_type])
80
+
81
+ # Generate a unique key for this specific menu instance
82
+ menu_unique_key = f"{menu_opcode_type}_{menu_block_instance_num}"
83
+
84
+ # Set properties for a shadow menu block
85
+ menu_block_data["shadow"] = True
86
+ menu_block_data["topLevel"] = False
87
+ menu_block_data["next"] = None
88
+ menu_block_data["parent"] = main_block_unique_key # Link menu to its parent instance
89
+
90
+ # Update the main block's input to point to this unique menu instance
91
+ if input_field_name in main_block_data.get("inputs", {}) and \
92
+ isinstance(main_block_data["inputs"][input_field_name], list) and \
93
+ len(main_block_data["inputs"][input_field_name]) > 1 and \
94
+ main_block_data["inputs"][input_field_name][0] == 1:
95
+
96
+ main_block_data["inputs"][input_field_name][1] = menu_unique_key
97
+
98
+ generated_blocks[menu_unique_key] = menu_block_data
99
+
100
+ return generated_blocks
101
+
102
+
103
+ # --- Consolidated Block Definitions from all provided JSONs ---
104
+ all_block_definitions = {
105
+ # motion_block.json
106
+ "motion_movesteps": {
107
+ "opcode": "motion_movesteps", "next": None, "parent": None,
108
+ "inputs": {"STEPS": [1, [4, "10"]]}, "fields": {}, "shadow": False, "topLevel": True,
109
+ "x": 464, "y": -416
110
+ },
111
+ "motion_turnright": {
112
+ "opcode": "motion_turnright", "next": None, "parent": None,
113
+ "inputs": {"DEGREES": [1, [4, "15"]]}, "fields": {}, "shadow": False, "topLevel": True,
114
+ "x": 467, "y": -316
115
+ },
116
+ "motion_turnleft": {
117
+ "opcode": "motion_turnleft", "next": None, "parent": None,
118
+ "inputs": {"DEGREES": [1, [4, "15"]]}, "fields": {}, "shadow": False, "topLevel": True,
119
+ "x": 464, "y": -210
120
+ },
121
+ "motion_goto": {
122
+ "opcode": "motion_goto", "next": None, "parent": None,
123
+ "inputs": {"TO": [1, "motion_goto_menu"]}, "fields": {}, "shadow": False, "topLevel": True,
124
+ "x": 465, "y": -95
125
+ },
126
+ "motion_goto_menu": {
127
+ "opcode": "motion_goto_menu", "next": None, "parent": "motion_goto",
128
+ "inputs": {}, "fields": {"TO": ["_random_", None]}, "shadow": True, "topLevel": False
129
+ },
130
+ "motion_gotoxy": {
131
+ "opcode": "motion_gotoxy", "next": None, "parent": None,
132
+ "inputs": {"X": [1, [4, "0"]], "Y": [1, [4, "0"]]}, "fields": {}, "shadow": False, "topLevel": True,
133
+ "x": 468, "y": 12
134
+ },
135
+ "motion_glideto": {
136
+ "opcode": "motion_glideto", "next": None, "parent": None,
137
+ "inputs": {"SECS": [1, [4, "1"]], "TO": [1, "motion_glideto_menu"]}, "fields": {}, "shadow": False, "topLevel": True,
138
+ "x": 470, "y": 129
139
+ },
140
+ "motion_glideto_menu": {
141
+ "opcode": "motion_glideto_menu", "next": None, "parent": "motion_glideto",
142
+ "inputs": {}, "fields": {"TO": ["_random_", None]}, "shadow": True, "topLevel": False
143
+ },
144
+ "motion_glidesecstoxy": {
145
+ "opcode": "motion_glidesecstoxy", "next": None, "parent": None,
146
+ "inputs": {"SECS": [1, [4, "1"]], "X": [1, [4, "0"]], "Y": [1, [4, "0"]]}, "fields": {}, "shadow": False, "topLevel": True,
147
+ "x": 476, "y": 239
148
+ },
149
+ "motion_pointindirection": {
150
+ "opcode": "motion_pointindirection", "next": None, "parent": None,
151
+ "inputs": {"DIRECTION": [1, [8, "90"]]}, "fields": {}, "shadow": False, "topLevel": True,
152
+ "x": 493, "y": 361
153
+ },
154
+ "motion_pointtowards": {
155
+ "opcode": "motion_pointtowards", "next": None, "parent": None,
156
+ "inputs": {"TOWARDS": [1, "motion_pointtowards_menu"]}, "fields": {}, "shadow": False, "topLevel": True,
157
+ "x": 492, "y": 463
158
+ },
159
+ "motion_pointtowards_menu": {
160
+ "opcode": "motion_pointtowards_menu", "next": None, "parent": "motion_pointtowards",
161
+ "inputs": {}, "fields": {"TOWARDS": ["_mouse_", None]}, "shadow": True, "topLevel": False
162
+ },
163
+ "motion_changexby": {
164
+ "opcode": "motion_changexby", "next": None, "parent": None,
165
+ "inputs": {"DX": [1, [4, "10"]]}, "fields": {}, "shadow": False, "topLevel": True,
166
+ "x": 851, "y": -409
167
+ },
168
+ "motion_setx": {
169
+ "opcode": "motion_setx", "next": None, "parent": None,
170
+ "inputs": {"X": [1, [4, "0"]]}, "fields": {}, "shadow": False, "topLevel": True,
171
+ "x": 864, "y": -194
172
+ },
173
+ "motion_changeyby": {
174
+ "opcode": "motion_changeyby", "next": None, "parent": None,
175
+ "inputs": {"DY": [1, [4, "10"]]}, "fields": {}, "shadow": False, "topLevel": True,
176
+ "x": 861, "y": -61
177
+ },
178
+ "motion_sety": {
179
+ "opcode": "motion_sety", "next": None, "parent": None,
180
+ "inputs": {"Y": [1, [4, "0"]]}, "fields": {}, "shadow": False, "topLevel": True,
181
+ "x": 864, "y": 66
182
+ },
183
+ "motion_ifonedgebounce": {
184
+ "opcode": "motion_ifonedgebounce", "next": None, "parent": None,
185
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
186
+ "x": 1131, "y": -397
187
+ },
188
+ "motion_setrotationstyle": {
189
+ "opcode": "motion_setrotationstyle", "next": None, "parent": None,
190
+ "inputs": {}, "fields": {"STYLE": ["left-right", None]}, "shadow": False, "topLevel": True,
191
+ "x": 1128, "y": -287
192
+ },
193
+ "motion_xposition": {
194
+ "opcode": "motion_xposition", "next": None, "parent": None,
195
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
196
+ "x": 1193, "y": -136
197
+ },
198
+ "motion_yposition": {
199
+ "opcode": "motion_yposition", "next": None, "parent": None,
200
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
201
+ "x": 1181, "y": -64
202
+ },
203
+ "motion_direction": {
204
+ "opcode": "motion_direction", "next": None, "parent": None,
205
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
206
+ "x": 1188, "y": 21
207
+ },
208
+
209
+ # control_block.json
210
+ "control_wait": {
211
+ "opcode": "control_wait", "next": None, "parent": None,
212
+ "inputs": {"DURATION": [1, [5, "1"]]}, "fields": {}, "shadow": False, "topLevel": True,
213
+ "x": 337, "y": 129
214
+ },
215
+ "control_repeat": {
216
+ "opcode": "control_repeat", "next": None, "parent": None,
217
+ "inputs": {"TIMES": [1, [6, "10"]]}, "fields": {}, "shadow": False, "topLevel": True,
218
+ "x": 348, "y": 265
219
+ },
220
+ "control_forever": {
221
+ "opcode": "control_forever", "next": None, "parent": None,
222
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
223
+ "x": 334, "y": 439
224
+ },
225
+ "control_if": {
226
+ "opcode": "control_if", "next": None, "parent": None,
227
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
228
+ "x": 331, "y": 597
229
+ },
230
+ "control_if_else": {
231
+ "opcode": "control_if_else", "next": None, "parent": None,
232
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
233
+ "x": 335, "y": 779
234
+ },
235
+ "control_wait_until": {
236
+ "opcode": "control_wait_until", "next": None, "parent": None,
237
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
238
+ "x": 676, "y": 285
239
+ },
240
+ "control_repeat_until": {
241
+ "opcode": "control_repeat_until", "next": None, "parent": None,
242
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
243
+ "x": 692, "y": 381
244
+ },
245
+ "control_stop": {
246
+ "opcode": "control_stop", "next": None, "parent": None,
247
+ "inputs": {}, "fields": {"STOP_OPTION": ["all", None]}, "shadow": False, "topLevel": True,
248
+ "x": 708, "y": 545, "mutation": {"tagName": "mutation", "children": [], "hasnext": "false"}
249
+ },
250
+ "control_start_as_clone": {
251
+ "opcode": "control_start_as_clone", "next": None, "parent": None,
252
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
253
+ "x": 665, "y": 672
254
+ },
255
+ "control_create_clone_of": {
256
+ "opcode": "control_create_clone_of", "next": None, "parent": None,
257
+ "inputs": {"CLONE_OPTION": [1, "control_create_clone_of_menu"]}, "fields": {}, "shadow": False, "topLevel": True,
258
+ "x": 648, "y": 797
259
+ },
260
+ "control_create_clone_of_menu": {
261
+ "opcode": "control_create_clone_of_menu", "next": None, "parent": "control_create_clone_of",
262
+ "inputs": {}, "fields": {"CLONE_OPTION": ["_myself_", None]}, "shadow": True, "topLevel": False
263
+ },
264
+ "control_delete_this_clone": {
265
+ "opcode": "control_delete_this_clone", "next": None, "parent": None,
266
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
267
+ "x": 642, "y": 914
268
+ },
269
+
270
+ # data_block.json
271
+ "data_setvariableto": {
272
+ "opcode": "data_setvariableto", "next": None, "parent": None,
273
+ "inputs": {"VALUE": [1, [10, "0"]]}, "fields": {"VARIABLE": ["my variable", "`jEk@4|i[#Fk?(8x)AV.-my variable"]}, "shadow": False, "topLevel": True,
274
+ "x": 348, "y": 241
275
+ },
276
+ "data_changevariableby": {
277
+ "opcode": "data_changevariableby", "next": None, "parent": None,
278
+ "inputs": {"VALUE": [1, [4, "1"]]}, "fields": {"VARIABLE": ["my variable", "`jEk@4|i[#Fk?(8x)AV.-my variable"]}, "shadow": False, "topLevel": True,
279
+ "x": 313, "y": 363
280
+ },
281
+ "data_showvariable": {
282
+ "opcode": "data_showvariable", "next": None, "parent": None,
283
+ "inputs": {}, "fields": {"VARIABLE": ["my variable", "`jEk@4|i[#Fk?(8x)AV.-my variable"]}, "shadow": False, "topLevel": True,
284
+ "x": 415, "y": 473
285
+ },
286
+ "data_hidevariable": {
287
+ "opcode": "data_hidevariable", "next": None, "parent": None,
288
+ "inputs": {}, "fields": {"VARIABLE": ["my variable", "`jEk@4|i[#Fk?(8x)AV.-my variable"]}, "shadow": False, "topLevel": True,
289
+ "x": 319, "y": 587
290
+ },
291
+ "data_addtolist": {
292
+ "opcode": "data_addtolist", "next": None, "parent": None,
293
+ "inputs": {"ITEM": [1, [10, "thing"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True,
294
+ "x": 385, "y": 109
295
+ },
296
+ "data_deleteoflist": {
297
+ "opcode": "data_deleteoflist", "next": None, "parent": None,
298
+ "inputs": {"INDEX": [1, [7, "1"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True,
299
+ "x": 384, "y": 244
300
+ },
301
+ "data_deletealloflist": {
302
+ "opcode": "data_deletealloflist", "next": None, "parent": None,
303
+ "inputs": {}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True,
304
+ "x": 387, "y": 374
305
+ },
306
+ "data_insertatlist": {
307
+ "opcode": "data_insertatlist", "next": None, "parent": None,
308
+ "inputs": {"ITEM": [1, [10, "thing"]], "INDEX": [1, [7, "1"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True,
309
+ "x": 366, "y": 527
310
+ },
311
+ "data_replaceitemoflist": {
312
+ "opcode": "data_replaceitemoflist", "next": None, "parent": None,
313
+ "inputs": {"INDEX": [1, [7, "1"]], "ITEM": [1, [10, "thing"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True,
314
+ "x": 365, "y": 657
315
+ },
316
+ "data_itemoflist": {
317
+ "opcode": "data_itemoflist", "next": None, "parent": None,
318
+ "inputs": {"INDEX": [1, [7, "1"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True,
319
+ "x": 862, "y": 117
320
+ },
321
+ "data_itemnumoflist": {
322
+ "opcode": "data_itemnumoflist", "next": None, "parent": None,
323
+ "inputs": {"ITEM": [1, [10, "thing"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True,
324
+ "x": 883, "y": 238
325
+ },
326
+ "data_lengthoflist": {
327
+ "opcode": "data_lengthoflist", "next": None, "parent": None,
328
+ "inputs": {}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True,
329
+ "x": 876, "y": 342
330
+ },
331
+ "data_listcontainsitem": {
332
+ "opcode": "data_listcontainsitem", "next": None, "parent": None,
333
+ "inputs": {"ITEM": [1, [10, "thing"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True,
334
+ "x": 871, "y": 463
335
+ },
336
+ "data_showlist": {
337
+ "opcode": "data_showlist", "next": None, "parent": None,
338
+ "inputs": {}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True,
339
+ "x": 931, "y": 563
340
+ },
341
+ "data_hidelist": {
342
+ "opcode": "data_hidelist", "next": None, "parent": None,
343
+ "inputs": {}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True,
344
+ "x": 962, "y": 716
345
+ },
346
+
347
+ # event_block.json
348
+ "event_whenflagclicked": {
349
+ "opcode": "event_whenflagclicked", "next": None, "parent": None,
350
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
351
+ "x": 166, "y": -422
352
+ },
353
+ "event_whenkeypressed": {
354
+ "opcode": "event_whenkeypressed", "next": None, "parent": None,
355
+ "inputs": {}, "fields": {"KEY_OPTION": ["space", None]}, "shadow": False, "topLevel": True,
356
+ "x": 151, "y": -329
357
+ },
358
+ "event_whenthisspriteclicked": {
359
+ "opcode": "event_whenthisspriteclicked", "next": None, "parent": None,
360
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
361
+ "x": 156, "y": -223
362
+ },
363
+ "event_whenbackdropswitchesto": {
364
+ "opcode": "event_whenbackdropswitchesto", "next": None, "parent": None,
365
+ "inputs": {}, "fields": {"BACKDROP": ["backdrop1", None]}, "shadow": False, "topLevel": True,
366
+ "x": 148, "y": -101
367
+ },
368
+ "event_whengreaterthan": {
369
+ "opcode": "event_whengreaterthan", "next": None, "parent": None,
370
+ "inputs": {"VALUE": [1, [4, "10"]]}, "fields": {"WHENGREATERTHANMENU": ["LOUDNESS", None]}, "shadow": False, "topLevel": True,
371
+ "x": 150, "y": 10
372
+ },
373
+ "event_whenbroadcastreceived": {
374
+ "opcode": "event_whenbroadcastreceived", "next": None, "parent": None,
375
+ "inputs": {}, "fields": {"BROADCAST_OPTION": ["message1", "5O!nei;S$!c!=hCT}0:a"]}, "shadow": False, "topLevel": True,
376
+ "x": 141, "y": 118
377
+ },
378
+ "event_broadcast": {
379
+ "opcode": "event_broadcast", "next": None, "parent": None,
380
+ "inputs": {"BROADCAST_INPUT": [1, [11, "message1", "5O!nei;S$!c!=hCT}0:a"]]}, "fields": {}, "shadow": False, "topLevel": True,
381
+ "x": 151, "y": 229
382
+ },
383
+ "event_broadcastandwait": {
384
+ "opcode": "event_broadcastandwait", "next": None, "parent": None,
385
+ "inputs": {"BROADCAST_INPUT": [1, [11, "message1", "5O!nei;S$!c!=hCT}0:a"]]}, "fields": {}, "shadow": False, "topLevel": True,
386
+ "x": 157, "y": 340
387
+ },
388
+
389
+ # look_block.json
390
+ "looks_sayforsecs": {
391
+ "opcode": "looks_sayforsecs", "next": None, "parent": None,
392
+ "inputs": {"MESSAGE": [1, [10, "Hello!"]], "SECS": [1, [4, "2"]]}, "fields": {}, "shadow": False, "topLevel": True,
393
+ "x": 408, "y": 91
394
+ },
395
+ "looks_say": {
396
+ "opcode": "looks_say", "next": None, "parent": None,
397
+ "inputs": {"MESSAGE": [1, [10, "Hello!"]]}, "fields": {}, "shadow": False, "topLevel": True,
398
+ "x": 413, "y": 213
399
+ },
400
+ "looks_thinkforsecs": {
401
+ "opcode": "looks_thinkforsecs", "next": None, "parent": None,
402
+ "inputs": {"MESSAGE": [1, [10, "Hmm..."]], "SECS": [1, [4, "2"]]}, "fields": {}, "shadow": False, "topLevel": True,
403
+ "x": 413, "y": 317
404
+ },
405
+ "looks_think": {
406
+ "opcode": "looks_think", "next": None, "parent": None,
407
+ "inputs": {"MESSAGE": [1, [10, "Hmm..."]]}, "fields": {}, "shadow": False, "topLevel": True,
408
+ "x": 412, "y": 432
409
+ },
410
+ "looks_switchcostumeto": {
411
+ "opcode": "looks_switchcostumeto", "next": None, "parent": None,
412
+ "inputs": {"COSTUME": [1, "8;bti4wv(iH9nkOacCJ|"]}, "fields": {}, "shadow": False, "topLevel": True,
413
+ "x": 411, "y": 555
414
+ },
415
+ "looks_costume": {
416
+ "opcode": "looks_costume", "next": None, "parent": "Q#a,6LPWHqo9-0Nu*[SV",
417
+ "inputs": {}, "fields": {"COSTUME": ["costume2", None]}, "shadow": True, "topLevel": False
418
+ },
419
+ "looks_nextcostume": {
420
+ "opcode": "looks_nextcostume", "next": None, "parent": None,
421
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
422
+ "x": 419, "y": 687
423
+ },
424
+ "looks_switchbackdropto": {
425
+ "opcode": "looks_switchbackdropto", "next": None, "parent": None,
426
+ "inputs": {"BACKDROP": [1, "-?yeX}29V*wd6W:unW0i"]}, "fields": {}, "shadow": False, "topLevel": True,
427
+ "x": 901, "y": 91
428
+ },
429
+ "looks_backdrops": {
430
+ "opcode": "looks_backdrops", "next": None, "parent": "`Wm^p~l[(IWzc1|wNv*.",
431
+ "inputs": {}, "fields": {"BACKDROP": ["backdrop1", None]}, "shadow": True, "topLevel": False
432
+ },
433
+ "looks_changesizeby": {
434
+ "opcode": "looks_changesizeby", "next": None, "parent": None,
435
+ "inputs": {"CHANGE": [1, [4, "10"]]}, "fields": {}, "shadow": False, "topLevel": True,
436
+ "x": 895, "y": 192
437
+ },
438
+ "looks_setsizeto": {
439
+ "opcode": "looks_setsizeto", "next": None, "parent": None,
440
+ "inputs": {"SIZE": [1, [4, "100"]]}, "fields": {}, "shadow": False, "topLevel": True,
441
+ "x": 896, "y": 303
442
+ },
443
+ "looks_changeeffectby": {
444
+ "opcode": "looks_changeeffectby", "next": None, "parent": None,
445
+ "inputs": {"CHANGE": [1, [4, "25"]]}, "fields": {"EFFECT": ["COLOR", None]}, "shadow": False, "topLevel": True,
446
+ "x": 892, "y": 416
447
+ },
448
+ "looks_seteffectto": {
449
+ "opcode": "looks_seteffectto", "next": None, "parent": None,
450
+ "inputs": {"VALUE": [1, [4, "0"]]}, "fields": {"EFFECT": ["COLOR", None]}, "shadow": False, "topLevel": True,
451
+ "x": 902, "y": 527
452
+ },
453
+ "looks_cleargraphiceffects": {
454
+ "opcode": "looks_cleargraphiceffects", "next": None, "parent": None,
455
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
456
+ "x": 902, "y": 638
457
+ },
458
+ "looks_show": {
459
+ "opcode": "looks_show", "next": None, "parent": None,
460
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
461
+ "x": 908, "y": 758
462
+ },
463
+ "looks_hide": {
464
+ "opcode": "looks_hide", "next": None, "parent": None,
465
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
466
+ "x": 455, "y": 861
467
+ },
468
+ "looks_gotofrontback": {
469
+ "opcode": "looks_gotofrontback", "next": None, "parent": None,
470
+ "inputs": {}, "fields": {"FRONT_BACK": ["front", None]}, "shadow": False, "topLevel": True,
471
+ "x": 853, "y": 878
472
+ },
473
+ "looks_goforwardbackwardlayers": {
474
+ "opcode": "looks_goforwardbackwardlayers", "next": None, "parent": None,
475
+ "inputs": {"NUM": [1, [7, "1"]]}, "fields": {"FORWARD_BACKWARD": ["forward", None]}, "shadow": False, "topLevel": True,
476
+ "x": 851, "y": 999
477
+ },
478
+ "looks_costumenumbername": {
479
+ "opcode": "looks_costumenumbername", "next": None, "parent": None,
480
+ "inputs": {}, "fields": {"NUMBER_NAME": ["number", None]}, "shadow": False, "topLevel": True,
481
+ "x": 458, "y": 1007
482
+ },
483
+ "looks_backdropnumbername": {
484
+ "opcode": "looks_backdropnumbername", "next": None, "parent": None,
485
+ "inputs": {}, "fields": {"NUMBER_NAME": ["number", None]}, "shadow": False, "topLevel": True,
486
+ "x": 1242, "y": 753
487
+ },
488
+ "looks_size": {
489
+ "opcode": "looks_size", "next": None, "parent": None,
490
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
491
+ "x": 1249, "y": 876
492
+ },
493
+
494
+ # operator_block.json
495
+ "operator_add": {
496
+ "opcode": "operator_add", "next": None, "parent": None,
497
+ "inputs": {"NUM1": [1, [4, ""]], "NUM2": [1, [4, ""]]}, "fields": {}, "shadow": False, "topLevel": True,
498
+ "x": 128, "y": 153
499
+ },
500
+ "operator_subtract": {
501
+ "opcode": "operator_subtract", "next": None, "parent": None,
502
+ "inputs": {"NUM1": [1, [4, ""]], "NUM2": [1, [4, ""]]}, "fields": {}, "shadow": False, "topLevel": True,
503
+ "x": 134, "y": 214
504
+ },
505
+ "operator_multiply": {
506
+ "opcode": "operator_multiply", "next": None, "parent": None,
507
+ "inputs": {"NUM1": [1, [4, ""]], "NUM2": [1, [4, ""]]}, "fields": {}, "shadow": False, "topLevel": True,
508
+ "x": 134, "y": 278
509
+ },
510
+ "operator_divide": {
511
+ "opcode": "operator_divide", "next": None, "parent": None,
512
+ "inputs": {"NUM1": [1, [4, ""]], "NUM2": [1, [4, ""]]}, "fields": {}, "shadow": False, "topLevel": True,
513
+ "x": 138, "y": 359
514
+ },
515
+ "operator_random": {
516
+ "opcode": "operator_random", "next": None, "parent": None,
517
+ "inputs": {"FROM": [1, [4, "1"]], "TO": [1, [4, "10"]]}, "fields": {}, "shadow": False, "topLevel": True,
518
+ "x": 311, "y": 157
519
+ },
520
+ "operator_gt": {
521
+ "opcode": "operator_gt", "next": None, "parent": None,
522
+ "inputs": {"OPERAND1": [1, [10, ""]], "OPERAND2": [1, [10, "50"]]}, "fields": {}, "shadow": False, "topLevel": True,
523
+ "x": 348, "y": 217
524
+ },
525
+ "operator_lt": {
526
+ "opcode": "operator_lt", "next": None, "parent": None,
527
+ "inputs": {"OPERAND1": [1, [10, ""]], "OPERAND2": [1, [10, "50"]]}, "fields": {}, "shadow": False, "topLevel": True,
528
+ "x": 345, "y": 286
529
+ },
530
+ "operator_equals": {
531
+ "opcode": "operator_equals", "next": None, "parent": None,
532
+ "inputs": {"OPERAND1": [1, [10, ""]], "OPERAND2": [1, [10, "50"]]}, "fields": {}, "shadow": False, "topLevel": True,
533
+ "x": 345, "y": 372
534
+ },
535
+ "operator_and": {
536
+ "opcode": "operator_and", "next": None, "parent": None,
537
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
538
+ "x": 701, "y": 158
539
+ },
540
+ "operator_or": {
541
+ "opcode": "operator_or", "next": None, "parent": None,
542
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
543
+ "x": 705, "y": 222
544
+ },
545
+ "operator_not": {
546
+ "opcode": "operator_not", "next": None, "parent": None,
547
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
548
+ "x": 734, "y": 283
549
+ },
550
+ "operator_join": {
551
+ "opcode": "operator_join", "next": None, "parent": None,
552
+ "inputs": {"STRING1": [1, [10, "apple "]], "STRING2": [1, [10, "banana"]]}, "fields": {}, "shadow": False, "topLevel": True,
553
+ "x": 663, "y": 378
554
+ },
555
+ "operator_letter_of": {
556
+ "opcode": "operator_letter_of", "next": None, "parent": None,
557
+ "inputs": {"LETTER": [1, [6, "1"]], "STRING": [1, [10, "apple"]]}, "fields": {}, "shadow": False, "topLevel": True,
558
+ "x": 664, "y": 445
559
+ },
560
+ "operator_length": {
561
+ "opcode": "operator_length", "next": None, "parent": None,
562
+ "inputs": {"STRING": [1, [10, "apple"]]}, "fields": {}, "shadow": False, "topLevel": True,
563
+ "x": 664, "y": 521
564
+ },
565
+ "operator_contains": {
566
+ "opcode": "operator_contains", "next": None, "parent": None,
567
+ "inputs": {"STRING1": [1, [10, "apple"]], "STRING2": [1, [10, "a"]]}, "fields": {}, "shadow": False, "topLevel": True,
568
+ "x": 634, "y": 599
569
+ },
570
+ "operator_mod": {
571
+ "opcode": "operator_mod", "next": None, "parent": None,
572
+ "inputs": {"NUM1": [1, [4, ""]], "NUM2": [1, [4, ""]]}, "fields": {}, "shadow": False, "topLevel": True,
573
+ "x": 295, "y": 594
574
+ },
575
+ "operator_round": {
576
+ "opcode": "operator_round", "next": None, "parent": None,
577
+ "inputs": {"NUM": [1, [4, ""]]}, "fields": {}, "shadow": False, "topLevel": True,
578
+ "x": 307, "y": 674
579
+ },
580
+ "operator_mathop": {
581
+ "opcode": "operator_mathop", "next": None, "parent": None,
582
+ "inputs": {"NUM": [1, [4, ""]]}, "fields": {"OPERATOR": ["abs", None]}, "shadow": False, "topLevel": True,
583
+ "x": 280, "y": 754
584
+ },
585
+
586
+ # sensing_block.json
587
+ "sensing_touchingobject": {
588
+ "opcode": "sensing_touchingobject", "next": None, "parent": None,
589
+ "inputs": {"TOUCHINGOBJECTMENU": [1, "sensing_touchingobjectmenu"]}, "fields": {}, "shadow": False, "topLevel": True,
590
+ "x": 359, "y": 116
591
+ },
592
+ "sensing_touchingobjectmenu": {
593
+ "opcode": "sensing_touchingobjectmenu", "next": None, "parent": "sensing_touchingobject",
594
+ "inputs": {}, "fields": {"TOUCHINGOBJECTMENU": ["_mouse_", None]}, "shadow": True, "topLevel": False
595
+ },
596
+ "sensing_touchingcolor": {
597
+ "opcode": "sensing_touchingcolor", "next": None, "parent": None,
598
+ "inputs": {"COLOR": [1, [9, "#55b888"]]}, "fields": {}, "shadow": False, "topLevel": True,
599
+ "x": 360, "y": 188
600
+ },
601
+ "sensing_coloristouchingcolor": {
602
+ "opcode": "sensing_coloristouchingcolor", "next": None, "parent": None,
603
+ "inputs": {"COLOR": [1, [9, "#d019f2"]], "COLOR2": [1, [9, "#2b0de3"]]}, "fields": {}, "shadow": False, "topLevel": True,
604
+ "x": 348, "y": 277
605
+ },
606
+ "sensing_askandwait": {
607
+ "opcode": "sensing_askandwait", "next": None, "parent": None,
608
+ "inputs": {"QUESTION": [1, [10, "What's your name?"]]}, "fields": {}, "shadow": False, "topLevel": True,
609
+ "x": 338, "y": 354
610
+ },
611
+ "sensing_answer": {
612
+ "opcode": "sensing_answer", "next": None, "parent": None,
613
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
614
+ "x": 782, "y": 111
615
+ },
616
+ "sensing_keypressed": {
617
+ "opcode": "sensing_keypressed", "next": None, "parent": None,
618
+ "inputs": {"KEY_OPTION": [1, "sensing_keyoptions"]}, "fields": {}, "shadow": False, "topLevel": True,
619
+ "x": 762, "y": 207
620
+ },
621
+ "sensing_keyoptions": {
622
+ "opcode": "sensing_keyoptions", "next": None, "parent": "sensing_keypressed",
623
+ "inputs": {}, "fields": {"KEY_OPTION": ["space", None]}, "shadow": True, "topLevel": False
624
+ },
625
+ "sensing_mousedown": {
626
+ "opcode": "sensing_mousedown", "next": None, "parent": None,
627
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
628
+ "x": 822, "y": 422
629
+ },
630
+ "sensing_mousex": {
631
+ "opcode": "sensing_mousex", "next": None, "parent": None,
632
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
633
+ "x": 302, "y": 528
634
+ },
635
+ "sensing_mousey": {
636
+ "opcode": "sensing_mousey", "next": None, "parent": None,
637
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
638
+ "x": 668, "y": 547
639
+ },
640
+ "sensing_setdragmode": {
641
+ "opcode": "sensing_setdragmode", "next": None, "parent": None,
642
+ "inputs": {}, "fields": {"DRAG_MODE": ["draggable", None]}, "shadow": False, "topLevel": True,
643
+ "x": 950, "y": 574
644
+ },
645
+ "sensing_loudness": {
646
+ "opcode": "sensing_loudness", "next": None, "parent": None,
647
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
648
+ "x": 658, "y": 703
649
+ },
650
+ "sensing_timer": {
651
+ "opcode": "sensing_timer", "next": None, "parent": None,
652
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
653
+ "x": 459, "y": 671
654
+ },
655
+ "sensing_resettimer": {
656
+ "opcode": "sensing_resettimer", "next": None, "parent": None,
657
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
658
+ "x": 462, "y": 781
659
+ },
660
+ "sensing_of": {
661
+ "opcode": "sensing_of", "next": None, "parent": None,
662
+ "inputs": {"OBJECT": [1, "sensing_of_object_menu"]}, "fields": {"PROPERTY": ["backdrop #", None]}, "shadow": False, "topLevel": True,
663
+ "x": 997, "y": 754
664
+ },
665
+ "sensing_of_object_menu": {
666
+ "opcode": "sensing_of_object_menu", "next": None, "parent": "sensing_of",
667
+ "inputs": {}, "fields": {"OBJECT": ["_stage_", None]}, "shadow": True, "topLevel": False
668
+ },
669
+ "sensing_current": {
670
+ "opcode": "sensing_current", "next": None, "parent": None,
671
+ "inputs": {}, "fields": {"CURRENTMENU": ["YEAR", None]}, "shadow": False, "topLevel": True,
672
+ "x": 627, "y": 884
673
+ },
674
+ "sensing_dayssince2000": {
675
+ "opcode": "sensing_dayssince2000", "next": None, "parent": None,
676
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
677
+ "x": 959, "y": 903
678
+ },
679
+ "sensing_username": {
680
+ "opcode": "sensing_username", "next": None, "parent": None,
681
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
682
+ "x": 833, "y": 757
683
+ },
684
+
685
+ # sound_block.json
686
+ "sound_playuntildone": {
687
+ "opcode": "sound_playuntildone", "next": None, "parent": None,
688
+ "inputs": {"SOUND_MENU": [1, "sound_sounds_menu"]}, "fields": {}, "shadow": False, "topLevel": True,
689
+ "x": 253, "y": 17
690
+ },
691
+ "sound_sounds_menu": {
692
+ "opcode": "sound_sounds_menu", "next": None, "parent": "sound_playuntildone and sound_play",
693
+ "inputs": {}, "fields": {"SOUND_MENU": ["Meow", None]}, "shadow": True, "topLevel": False
694
+ },
695
+ "sound_play": {
696
+ "opcode": "sound_play", "next": None, "parent": None,
697
+ "inputs": {"SOUND_MENU": [1, "sound_sounds_menu"]}, "fields": {}, "shadow": False, "topLevel": True,
698
+ "x": 245, "y": 122
699
+ },
700
+ "sound_stopallsounds": {
701
+ "opcode": "sound_stopallsounds", "next": None, "parent": None,
702
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
703
+ "x": 253, "y": 245
704
+ },
705
+ "sound_changeeffectby": {
706
+ "opcode": "sound_changeeffectby", "next": None, "parent": None,
707
+ "inputs": {"VALUE": [1, [4, "10"]]}, "fields": {"EFFECT": ["PITCH", None]}, "shadow": False, "topLevel": True,
708
+ "x": 653, "y": 14
709
+ },
710
+ "sound_seteffectto": {
711
+ "opcode": "sound_seteffectto", "next": None, "parent": None,
712
+ "inputs": {"VALUE": [1, [4, "100"]]}, "fields": {"EFFECT": ["PITCH", None]}, "shadow": False, "topLevel": True,
713
+ "x": 653, "y": 139
714
+ },
715
+ "sound_cleareffects": {
716
+ "opcode": "sound_cleareffects", "next": None, "parent": None,
717
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
718
+ "x": 651, "y": 242
719
+ },
720
+ "sound_changevolumeby": {
721
+ "opcode": "sound_changevolumeby", "next": None, "parent": None,
722
+ "inputs": {"VOLUME": [1, [4, "-10"]]}, "fields": {}, "shadow": False, "topLevel": True,
723
+ "x": 645, "y": 353
724
+ },
725
+ "sound_setvolumeto": {
726
+ "opcode": "sound_setvolumeto", "next": None, "parent": None,
727
+ "inputs": {"VOLUME": [1, [4, "100"]]}, "fields": {}, "shadow": False, "topLevel": True,
728
+ "x": 1108, "y": 5
729
+ },
730
+ "sound_volume": {
731
+ "opcode": "sound_volume", "next": None, "parent": None,
732
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True,
733
+ "x": 1136, "y": 123
734
+ },
735
+ }
736
+
737
+ # #Example input with opcodes from various categories
738
+ # input_opcodes = [
739
+ # {"opcode": "sound_play", "count": 2}, # New: Sound block with menu
740
+ # {"opcode": "sound_playuntildone", "count": 2}, # New: Sound block with menu
741
+ # ]
742
+
743
+ # Example input with opcodes from various categories
744
+ input_opcodes = [
745
+ {"opcode": "sound_play", "count": 2},
746
+ {"opcode": "sound_playuntildone", "count": 2},
747
+ {"opcode":"motion_goto","count":2},
748
+ {"opcode":"motion_glideto","count":2},
749
+ {"opcode":"looks_switchbackdropto","count":2},
750
+ {"opcode":"looks_switchcostumeto","count":2},
751
+ {"opcode":"control_create_clone_of","count":2},
752
+ {"opcode":"sensing_touchingobject","count":2},
753
+ {"opcode":"sensing_of","count":2},
754
+ {"opcode":"sensing_keypressed","count":2},
755
+ {"opcode":"motion_pointtowards","count":2},
756
+ ]
757
+
758
+ generated_output = generate_blocks_from_opcodes(input_opcodes, all_block_definitions)
759
+ print(json.dumps(generated_output, indent=2))
utils/generated_output_json.json ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "event_whenflagclicked_1": {
3
+ "block_name": "when green flag pressed",
4
+ "block_type": "Events",
5
+ "op_code": "event_whenflagclicked",
6
+ "block_shape": "Hat Block",
7
+ "functionality": "This Hat block initiates the script when the green flag is clicked, serving as the common starting point for most Scratch projects.",
8
+ "inputs": {},
9
+ "fields": {},
10
+ "shadow": false,
11
+ "topLevel": true,
12
+ "parent": null,
13
+ "next": null
14
+ },
15
+ "data_setvariableto_1": {
16
+ "block_name": "set [my variable v] to ()",
17
+ "block_type": "Data",
18
+ "block_shape": "Stack Block",
19
+ "op_code": "data_setvariableto",
20
+ "functionality": "Assigns a specific value (number, string, or boolean) to a variable.",
21
+ "inputs": {
22
+ "VALUE": [
23
+ 1,
24
+ [
25
+ 10,
26
+ "0"
27
+ ]
28
+ ]
29
+ },
30
+ "fields": {
31
+ "VARIABLE": [
32
+ "my variable",
33
+ "`jEk@4|i[#Fk?(8x)AV.-my variable"
34
+ ]
35
+ },
36
+ "shadow": false,
37
+ "topLevel": true,
38
+ "parent": null,
39
+ "next": null
40
+ },
41
+ "data_setvariableto_2": {
42
+ "block_name": "set [my variable v] to ()",
43
+ "block_type": "Data",
44
+ "block_shape": "Stack Block",
45
+ "op_code": "data_setvariableto",
46
+ "functionality": "Assigns a specific value (number, string, or boolean) to a variable.",
47
+ "inputs": {
48
+ "VALUE": [
49
+ 1,
50
+ [
51
+ 10,
52
+ "0"
53
+ ]
54
+ ]
55
+ },
56
+ "fields": {
57
+ "VARIABLE": [
58
+ "my variable",
59
+ "`jEk@4|i[#Fk?(8x)AV.-my variable"
60
+ ]
61
+ },
62
+ "shadow": false,
63
+ "topLevel": true,
64
+ "parent": null,
65
+ "next": null
66
+ },
67
+ "control_forever_1": {
68
+ "block_name": "forever",
69
+ "block_type": "Control",
70
+ "block_shape": "C-Block",
71
+ "op_code": "control_forever",
72
+ "functionality": "Continuously runs the blocks inside it.",
73
+ "inputs": {
74
+ "SUBSTACK": [
75
+ 2,
76
+ null
77
+ ]
78
+ },
79
+ "fields": {},
80
+ "shadow": false,
81
+ "topLevel": true,
82
+ "parent": null,
83
+ "next": null
84
+ },
85
+ "control_if_1": {
86
+ "block_name": "if <> then",
87
+ "block_type": "Control",
88
+ "block_shape": "C-Block",
89
+ "op_code": "control_if",
90
+ "functionality": "Executes the blocks inside it only if the specified boolean condition is true. [NOTE: it takes boolean blocks as input]",
91
+ "inputs": {
92
+ "CONDITION": [
93
+ 2,
94
+ null
95
+ ],
96
+ "SUBSTACK": [
97
+ 2,
98
+ null
99
+ ]
100
+ },
101
+ "fields": {},
102
+ "shadow": false,
103
+ "topLevel": true,
104
+ "parent": null,
105
+ "next": null
106
+ },
107
+ "control_wait_1": {
108
+ "block_name": "wait () seconds",
109
+ "block_type": "Control",
110
+ "block_shape": "Stack Block",
111
+ "op_code": "control_wait",
112
+ "functionality": "Pauses the script for a specified duration.",
113
+ "inputs": {
114
+ "DURATION": [
115
+ 1,
116
+ [
117
+ 5,
118
+ "1"
119
+ ]
120
+ ]
121
+ },
122
+ "fields": {},
123
+ "shadow": false,
124
+ "topLevel": true,
125
+ "parent": null,
126
+ "next": null
127
+ },
128
+ "sensing_touchingobject_1": {
129
+ "block_name": "<touching [edge v]?>",
130
+ "block_type": "Sensing",
131
+ "op_code": "sensing_touchingobject",
132
+ "block_shape": "Boolean Block",
133
+ "functionality": "Checks if its sprite is touching the mouse-pointer, edge, or another specified sprite.",
134
+ "inputs": {
135
+ "TOUCHINGOBJECTMENU": [
136
+ 1,
137
+ "sensing_touchingobjectmenu_1"
138
+ ]
139
+ },
140
+ "fields": {},
141
+ "shadow": false,
142
+ "topLevel": true,
143
+ "parent": null,
144
+ "next": null
145
+ },
146
+ "sensing_touchingobjectmenu_1": {
147
+ "block_name": "touching object menu",
148
+ "block_type": "Sensing",
149
+ "block_shape": "Reporter Block",
150
+ "op_code": "sensing_touchingobjectmenu",
151
+ "functionality": "Menu for touching object block.",
152
+ "inputs": {},
153
+ "fields": {
154
+ "TOUCHINGOBJECTMENU": [
155
+ "_mouse_",
156
+ null
157
+ ]
158
+ },
159
+ "shadow": true,
160
+ "topLevel": false,
161
+ "next": null,
162
+ "parent": "sensing_touchingobject_1"
163
+ }
164
+ }
utils/half_working_plan.py ADDED
The diff for this file is too large to render. See raw diff
 
utils/helper_function.py ADDED
File without changes
utils/logs.txt ADDED
The diff for this file is too large to render. See raw diff
 
utils/opcode_counter.py ADDED
@@ -0,0 +1,228 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import re
3
+ from typing import Any, Dict
4
+ import logging
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+ # Dummy data for demonstration. You should replace this with your actual opcode data.
9
+ # Each item should have at least an 'opcode' and 'text' field.
10
+ hat_block_data = [
11
+ {"opcode": "event_whenflagclicked", "text": "when green flag clicked"},
12
+ {"opcode": "event_whenkeypressed", "text": "when [key] pressed"},
13
+ {"opcode": "event_whenbroadcastreceived", "text": "when I receive [message]"},
14
+ ]
15
+ boolean_block_data = [
16
+ {"opcode": "operator_gt", "text": "< ( ) > ( ) >"},
17
+ {"opcode": "sensing_touchingobject", "text": "<touching [object]?>"},
18
+ {"opcode": "operator_equals", "text": "< ( ) = ( ) >"},
19
+ ]
20
+ c_block_data = [
21
+ {"opcode": "control_forever", "text": "forever"},
22
+ {"opcode": "control_if", "text": "if < > then"},
23
+ {"opcode": "control_repeat", "text": "repeat ( )"},
24
+ ]
25
+ cap_block_data = [
26
+ {"opcode": "control_stop", "text": "stop [all]"},
27
+ ]
28
+ reporter_block_data = [
29
+ {"opcode": "motion_xposition", "text": "(x position)"},
30
+ {"opcode": "motion_yposition", "text": "(y position)"},
31
+ {"opcode": "data_variable", "text": "(variable)"},
32
+ {"opcode": "sensing_answer", "text": "(answer)"},
33
+ ]
34
+ stack_block_data = [
35
+ {"opcode": "motion_gotoxy", "text": "go to x: ( ) y: ( )"},
36
+ {"opcode": "motion_changeyby", "text": "change y by ( )"},
37
+ {"opcode": "motion_setx", "text": "set x to ( )"},
38
+ {"opcode": "motion_glidesecstoxy", "text": "glide ( ) secs to x: ( ) y: ( )"},
39
+ {"opcode": "data_setvariableto", "text": "set [variable] to ( )"},
40
+ {"opcode": "looks_hide", "text": "hide"},
41
+ {"opcode": "looks_show", "text": "show"},
42
+ {"opcode": "event_broadcast", "text": "broadcast [message]"},
43
+ ]
44
+
45
+ # Combine all block data into a single list for easier lookup
46
+ all_opcodes_list = []
47
+ for category_data in [
48
+ hat_block_data,
49
+ boolean_block_data,
50
+ c_block_data,
51
+ cap_block_data,
52
+ reporter_block_data,
53
+ stack_block_data,
54
+ ]:
55
+ all_opcodes_list.extend(category_data)
56
+
57
+
58
+ def extract_json_from_llm_response(response_text: str) -> Dict[str, Any]:
59
+ """Extracts JSON from an LLM response string."""
60
+ try:
61
+ json_match = re.search(r"```json\n(.*)\n```", response_text, re.DOTALL)
62
+ if json_match:
63
+ return json.loads(json_match.group(1))
64
+ return json.loads(response_text) # Try parsing directly if no code block
65
+ except json.JSONDecodeError as e:
66
+ logger.error(f"Failed to decode JSON: {e} from response: {response_text}")
67
+ raise
68
+
69
+ # Node 9:plan with exact count of the opcode used per logic
70
+ def plan_opcode_counter_node(state: Dict[str, Any]) -> Dict[str, Any]:
71
+ """
72
+ For each plan in state["action_plan"]["action_overall_flow"], calls the LLM agent
73
+ to analyze the `logic` string and return a list of {opcode, count} for each category.
74
+ """
75
+ logger.info("=== Running OPCODE COUTER LOGIC with LLM counts ===")
76
+ game_description = state.get("description", "No game description provided.")
77
+ sprite_name = {target["name"]: target["name"] for target in state["project_json"]["targets"]} # Adjusted for direct use
78
+
79
+ action_flow = state.get("action_plan", {}).get("action_overall_flow", {})
80
+ if not action_flow:
81
+ logger.warning("No action_overall_flow found; skipping.")
82
+ return state
83
+
84
+ # Prepare block reference strings for the prompt
85
+ hat_description = "Blocks that start a script when an event happens."
86
+ hat_opcodes_functionalities = "\n".join([f"- {block['opcode']}: {block['text']}" for block in hat_block_data])
87
+
88
+ boolean_description = "Blocks that report a true or false value and fit into hexagonal inputs."
89
+ boolean_opcodes_functionalities = "\n".join([f"- {block['opcode']}: {block['text']}" for block in boolean_block_data])
90
+
91
+ c_description = "Blocks that run scripts inside them repeatedly or conditionally."
92
+ c_opcodes_functionalities = "\n".join([f"- {block['opcode']}: {block['text']}" for block in c_block_data])
93
+
94
+ cap_description = "Blocks that end a script."
95
+ cap_opcodes_functionalities = "\n".join([f"- {block['opcode']}: {block['text']}" for block in cap_block_data])
96
+
97
+ reporter_description = "Blocks that report a value (number or string) and fit into rounded inputs."
98
+ reporter_opcodes_functionalities = "\n".join([f"- {block['opcode']}: {block['text']}" for block in reporter_block_data])
99
+
100
+ stack_description = "Blocks that perform a main action in a script."
101
+ stack_opcodes_functionalities = "\n".join([f"- {block['opcode']}: {block['text']}" for block in stack_block_data])
102
+
103
+ refined_flow: Dict[str, Any] = {}
104
+ for sprite, sprite_data in action_flow.items(): # Use .items() for direct iteration
105
+ refined_plans = []
106
+ for plan in sprite_data.get("plans", []):
107
+ logic = plan.get("logic", "")
108
+ event = plan.get("event", "")
109
+
110
+ # This is where the core change for counting opcodes will happen.
111
+ # We will use the 'logic' string to determine the actual opcodes and their counts.
112
+ opcode_counts = {
113
+ "motion": [],
114
+ "control": [],
115
+ "operator": [],
116
+ "sensing": [],
117
+ "looks": [],
118
+ "sounds": [],
119
+ "events": [],
120
+ "data": [],
121
+ }
122
+
123
+ # Initialize a dictionary to hold counts for each opcode
124
+ temp_opcode_counts = {}
125
+
126
+ # Add the event block explicitly
127
+ if event:
128
+ event_opcode = event.replace('v', '').strip() # Clean the event string
129
+ temp_opcode_counts[event_opcode] = temp_opcode_counts.get(event_opcode, 0) + 1
130
+
131
+
132
+ # Iterate through all known opcodes and check if their 'text' appears in the logic
133
+ for block_info in all_opcodes_list:
134
+ opcode = block_info["opcode"]
135
+ # Use a more robust regex for matching, accounting for variable names or block inputs
136
+ # We need to be careful with common words that are also part of opcodes, e.g., "if"
137
+ # A more robust solution might involve parsing the Scratch-like logic more deeply.
138
+ # For now, let's try to match the "text" from the block definition.
139
+ # Escape special characters in the block text for regex
140
+ block_text_escaped = re.escape(block_info["text"])
141
+
142
+ # Replace placeholders like [key], [object], ( ) with regex wildcards
143
+ block_text_pattern = block_text_escaped.replace(r"\[key\]", r".*?").replace(r"\[message\]", r".*?").replace(r"\[object\]", r".*?").replace(r"\( \)", r".*?")
144
+ block_text_pattern = block_text_pattern.replace(r"\[variable\]", r".*?")
145
+
146
+ # For blocks that might have variations in text (e.g., if-then, if-then-else)
147
+ if opcode == "control_if":
148
+ if_regex = r"if <.+?> then"
149
+ if_else_regex = r"if <.+?> then\n.*else"
150
+
151
+ if re.search(if_else_regex, logic, re.DOTALL):
152
+ temp_opcode_counts["control_if_else"] = temp_opcode_counts.get("control_if_else", 0) + 1
153
+ elif re.search(if_regex, logic, re.DOTALL):
154
+ temp_opcode_counts["control_if"] = temp_opcode_counts.get("control_if", 0) + 1
155
+ continue # Skip general matching for control_if
156
+
157
+ if opcode == "control_forever" and "forever" in logic:
158
+ temp_opcode_counts[opcode] = temp_opcode_counts.get(opcode, 0) + 1
159
+ continue # Skip general matching
160
+
161
+ # General regex match for other blocks
162
+ # We need to make sure we're not just matching substrings of other blocks
163
+ # A simple word boundary or line-by-line check might be better
164
+ # For now, a simple count of occurrences of the "text" within the logic
165
+ # will be used, but this is a simplification.
166
+ count = len(re.findall(block_text_pattern, logic))
167
+ if count > 0:
168
+ temp_opcode_counts[opcode] = temp_opcode_counts.get(opcode, 0) + count
169
+
170
+ # Fill the opcode_counts for each category based on temp_opcode_counts
171
+ def add_to_category(category_list, opcode_name, count):
172
+ if count > 0:
173
+ category_list.append({"opcode": opcode_name, "count": count})
174
+
175
+ for opcode, count in temp_opcode_counts.items():
176
+ if opcode.startswith("motion_"):
177
+ add_to_category(opcode_counts["motion"], opcode, count)
178
+ elif opcode.startswith("control_"):
179
+ add_to_category(opcode_counts["control"], opcode, count)
180
+ elif opcode.startswith("operator_"):
181
+ add_to_category(opcode_counts["operator"], opcode, count)
182
+ elif opcode.startswith("sensing_"):
183
+ add_to_category(opcode_counts["sensing"], opcode, count)
184
+ elif opcode.startswith("looks_"):
185
+ add_to_category(opcode_counts["looks"], opcode, count)
186
+ elif opcode.startswith("sounds_"):
187
+ add_to_category(opcode_counts["sounds"], opcode, count)
188
+ elif opcode.startswith("event_"):
189
+ add_to_category(opcode_counts["events"], opcode, count)
190
+ elif opcode.startswith("data_"):
191
+ add_to_category(opcode_counts["data"], opcode, count)
192
+
193
+ # Assign the new opcode_counts to the plan
194
+ plan["opcode_counts"] = opcode_counts
195
+
196
+ # The original plan structure also had categories as direct keys.
197
+ # You can choose to keep this or remove it, depending on your downstream needs.
198
+ # If you want to keep it, you'd populate them based on opcode_counts.
199
+ # For simplicity, let's keep the new 'opcode_counts' key as requested.
200
+
201
+ # Clear previous lists if you are relying solely on 'opcode_counts'
202
+ plan["motion"] = []
203
+ plan["control"] = []
204
+ plan["operator"] = []
205
+ plan["sensing"] = []
206
+ plan["looks"] = []
207
+ plan["sounds"] = []
208
+ plan["events"] = []
209
+ plan["data"] = []
210
+
211
+ # Populate the individual lists based on the newly calculated opcode_counts if needed
212
+ for category, opcodes_list in opcode_counts.items():
213
+ for item in opcodes_list:
214
+ # Append just the opcode string to the category list
215
+ plan[category].extend([item['opcode']] * item['count'])
216
+
217
+
218
+ refined_plans.append(plan)
219
+
220
+ refined_flow[sprite] = {
221
+ "description": sprite_data.get("description", ""),
222
+ "plans": refined_plans
223
+ }
224
+
225
+ state["temporary_node"] = refined_flow
226
+ print(f"[OPCODE COUTER LOGIC]: {refined_flow}")
227
+ logger.info("=== OPCODE COUTER LOGIC completed ===")
228
+ return state
utils/opcode_occurrence.py ADDED
@@ -0,0 +1,981 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import copy
3
+ import re
4
+ import re
5
+ from collections import defaultdict
6
+ #from opcode_occurrence import generated_output_json
7
+
8
+
9
+ def generate_blocks_from_opcodes(opcode_counts, all_block_definitions):
10
+ """
11
+ Generates a dictionary of Scratch-like blocks based on a list of opcodes and a reference block definition,
12
+ and groups all generated block keys by their corresponding opcode.
13
+
14
+ Returns:
15
+ tuple: (generated_blocks, opcode_to_keys)
16
+ - generated_blocks: dict of block_key -> block_data
17
+ - opcode_to_keys: dict of opcode -> list of block_keys
18
+ """
19
+ generated_blocks = {}
20
+ opcode_counts_map = {} # For counting unique suffix per opcode
21
+ opcode_to_keys = {} # For grouping block keys by opcode
22
+
23
+ explicit_menu_links = {
24
+ "motion_goto": [("TO", "motion_goto_menu")],
25
+ "motion_glideto": [("TO", "motion_glideto_menu")],
26
+ "motion_pointtowards": [("TOWARDS", "motion_pointtowards_menu")],
27
+ "sensing_keypressed": [("KEY_OPTION", "sensing_keyoptions")],
28
+ "sensing_of": [("OBJECT", "sensing_of_object_menu")],
29
+ "sensing_touchingobject": [("TOUCHINGOBJECTMENU", "sensing_touchingobjectmenu")],
30
+ "control_create_clone_of": [("CLONE_OPTION", "control_create_clone_of_menu")],
31
+ "sound_play": [("SOUND_MENU", "sound_sounds_menu")],
32
+ "sound_playuntildone": [("SOUND_MENU", "sound_sounds_menu")],
33
+ "looks_switchcostumeto": [("COSTUME", "looks_costume")],
34
+ "looks_switchbackdropto": [("BACKDROP", "looks_backdrops")],
35
+ }
36
+
37
+ for item in opcode_counts:
38
+ opcode = item.get("opcode")
39
+ count = item.get("count", 1)
40
+
41
+ if opcode == "sensing_istouching":
42
+ opcode = "sensing_touchingobject"
43
+
44
+ if not opcode or opcode not in all_block_definitions:
45
+ print(f"Warning: Skipping opcode '{opcode}' (missing or not in definitions).")
46
+ continue
47
+
48
+ for _ in range(count):
49
+ # Count occurrences per opcode for unique key generation
50
+ opcode_counts_map[opcode] = opcode_counts_map.get(opcode, 0) + 1
51
+ instance_num = opcode_counts_map[opcode]
52
+ main_key = f"{opcode}_{instance_num}"
53
+
54
+ # Track the generated key
55
+ opcode_to_keys.setdefault(opcode, []).append(main_key)
56
+
57
+ main_block_data = copy.deepcopy(all_block_definitions[opcode])
58
+ main_block_data["parent"] = None
59
+ main_block_data["next"] = None
60
+ main_block_data["topLevel"] = True
61
+ main_block_data["shadow"] = False
62
+
63
+ generated_blocks[main_key] = main_block_data
64
+
65
+ # Handle menus
66
+ if opcode in explicit_menu_links:
67
+ for input_name, menu_opcode in explicit_menu_links[opcode]:
68
+ if menu_opcode not in all_block_definitions:
69
+ continue
70
+
71
+ opcode_counts_map[menu_opcode] = opcode_counts_map.get(menu_opcode, 0) + 1
72
+ menu_instance_num = opcode_counts_map[menu_opcode]
73
+ menu_key = f"{menu_opcode}_{menu_instance_num}"
74
+
75
+ opcode_to_keys.setdefault(menu_opcode, []).append(menu_key)
76
+
77
+ menu_block_data = copy.deepcopy(all_block_definitions[menu_opcode])
78
+ menu_block_data["shadow"] = True
79
+ menu_block_data["topLevel"] = False
80
+ menu_block_data["next"] = None
81
+ menu_block_data["parent"] = main_key
82
+
83
+ if input_name in main_block_data.get("inputs", {}) and \
84
+ isinstance(main_block_data["inputs"][input_name], list) and \
85
+ len(main_block_data["inputs"][input_name]) > 1 and \
86
+ main_block_data["inputs"][input_name][0] == 1:
87
+
88
+ main_block_data["inputs"][input_name][1] = menu_key
89
+
90
+ generated_blocks[menu_key] = menu_block_data
91
+
92
+ return generated_blocks, opcode_to_keys
93
+
94
+ all_block_definitions = {
95
+ # motion_block.json
96
+ "motion_movesteps": {
97
+ "block_name": "move () steps", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_movesteps",
98
+ "functionality": "Moves the sprite forward by the specified number of steps in the direction it is currently facing. A positive value moves it forward, and a negative value moves it backward.",
99
+ "inputs": {"STEPS": [1, [4, "10"]]}, "fields": {}, "shadow": False, "topLevel": True
100
+ },
101
+ "motion_turnright": {
102
+ "block_name": "turn right () degrees", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_turnright",
103
+ "functionality": "Turns the sprite clockwise by the specified number of degrees.",
104
+ "inputs": {"DEGREES": [1, [4, "15"]]}, "fields": {}, "shadow": False, "topLevel": True
105
+ },
106
+ "motion_turnleft": {
107
+ "block_name": "turn left () degrees", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_turnleft",
108
+ "functionality": "Turns the sprite counter-clockwise by the specified number of degrees.",
109
+ "inputs": {"DEGREES": [1, [4, "15"]]}, "fields": {}, "shadow": False, "topLevel": True
110
+ },
111
+ "motion_goto": {
112
+ "block_name": "go to ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_goto",
113
+ "functionality": "Moves the sprite to a specified location, which can be a random position or at the mouse pointer or another to the sprite.",
114
+ "inputs": {"TO": [1, "motion_goto_menu"]}, "fields": {}, "shadow": False, "topLevel": True
115
+ },
116
+ "motion_goto_menu": {
117
+ "block_name": "go to menu", "block_type": "Motion", "block_shape": "Reporter Block", "op_code": "motion_goto_menu",
118
+ "functionality": "Menu for go to block.",
119
+ "inputs": {}, "fields": {"TO": ["_random_", None]}, "shadow": True, "topLevel": False
120
+ },
121
+ "motion_gotoxy": {
122
+ "block_name": "go to x: () y: ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_gotoxy",
123
+ "functionality": "Moves the sprite to the specified X and Y coordinates on the stage.",
124
+ "inputs": {"X": [1, [4, "0"]], "Y": [1, [4, "0"]]}, "fields": {}, "shadow": False, "topLevel": True
125
+ },
126
+ "motion_glideto": {
127
+ "block_name": "glide () secs to ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_glideto",
128
+ "functionality": "Glides the sprite smoothly to a specified location (random position, mouse pointer, or another sprite) over a given number of seconds.",
129
+ "inputs": {"SECS": [1, [4, "1"]], "TO": [1, "motion_glideto_menu"]}, "fields": {}, "shadow": False, "topLevel": True
130
+ },
131
+ "motion_glideto_menu": {
132
+ "block_name": "glide to menu", "block_type": "Motion", "block_shape": "Reporter Block", "op_code": "motion_glideto_menu",
133
+ "functionality": "Menu for glide to block.",
134
+ "inputs": {}, "fields": {"TO": ["_random_", None]}, "shadow": True, "topLevel": False
135
+ },
136
+ "motion_glidesecstoxy": {
137
+ "block_name": "glide () secs to x: () y: ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_glidesecstoxy",
138
+ "functionality": "Glides the sprite smoothly to the specified X and Y coordinates over a given number of seconds.",
139
+ "inputs": {"SECS": [1, [4, "1"]], "X": [1, [4, "0"]], "Y": [1, [4, "0"]]}, "fields": {}, "shadow": False, "topLevel": True
140
+ },
141
+ "motion_pointindirection": {
142
+ "block_name": "point in direction ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_pointindirection",
143
+ "functionality": "Sets the sprite's direction to a specified angle in degrees (0 = up, 90 = right, 180 = down, -90 = left).",
144
+ "inputs": {"DIRECTION": [1, [8, "90"]]}, "fields": {}, "shadow": False, "topLevel": True
145
+ },
146
+ "motion_pointtowards": {
147
+ "block_name": "point towards ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_pointtowards",
148
+ "functionality": "Points the sprite towards the mouse pointer or another specified sprite.",
149
+ "inputs": {"TOWARDS": [1, "motion_pointtowards_menu"]}, "fields": {}, "shadow": False, "topLevel": True
150
+ },
151
+ "motion_pointtowards_menu": {
152
+ "block_name": "point towards menu", "block_type": "Motion", "block_shape": "Reporter Block", "op_code": "motion_pointtowards_menu",
153
+ "functionality": "Menu for point towards block.",
154
+ "inputs": {}, "fields": {"TOWARDS": ["_mouse_", None]}, "shadow": True, "topLevel": False
155
+ },
156
+ "motion_changexby": {
157
+ "block_name": "change x by ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_changexby",
158
+ "functionality": "Changes the sprite's X-coordinate by the specified amount, moving it horizontally.",
159
+ "inputs": {"DX": [1, [4, "10"]]}, "fields": {}, "shadow": False, "topLevel": True
160
+ },
161
+ "motion_setx": {
162
+ "block_name": "set x to ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_setx",
163
+ "functionality": "Sets the sprite's X-coordinate to a specific value, placing it at a precise horizontal position.",
164
+ "inputs": {"X": [1, [4, "0"]]}, "fields": {}, "shadow": False, "topLevel": True
165
+ },
166
+ "motion_changeyby": {
167
+ "block_name": "change y by ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_changeyby",
168
+ "functionality": "Changes the sprite's Y-coordinate by the specified amount, moving it vertically.",
169
+ "inputs": {"DY": [1, [4, "10"]]}, "fields": {}, "shadow": False, "topLevel": True
170
+ },
171
+ "motion_sety": {
172
+ "block_name": "set y to ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_sety",
173
+ "functionality": "Sets the sprite's Y-coordinate to a specific value, placing it at a precise vertical position.",
174
+ "inputs": {"Y": [1, [4, "0"]]}, "fields": {}, "shadow": False, "topLevel": True
175
+ },
176
+ "motion_ifonedgebounce": {
177
+ "block_name": "if on edge, bounce", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_ifonedgebounce",
178
+ "functionality": "Reverses the sprite's direction if it touches the edge of the stage.",
179
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
180
+ },
181
+ "motion_setrotationstyle": {
182
+ "block_name": "set rotation style ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_setrotationstyle",
183
+ "functionality": "Determines how the sprite rotates: 'left-right' (flips horizontally), 'don't rotate' (stays facing one direction), or 'all around' (rotates freely).",
184
+ "inputs": {}, "fields": {"STYLE": ["left-right", None]}, "shadow": False, "topLevel": True
185
+ },
186
+ "motion_xposition": {
187
+ "block_name": "(x position)", "block_type": "Motion", "block_shape": "Reporter Block", "op_code": "motion_xposition",
188
+ "functionality": "Reports the current X-coordinate of the sprite.[NOTE: not used in stage/backdrops]",
189
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
190
+ },
191
+ "motion_yposition": {
192
+ "block_name": "(y position)", "block_type": "Motion", "block_shape": "Reporter Block", "op_code": "motion_yposition",
193
+ "functionality": "Reports the current Y coordinate of the sprite on the stage.[NOTE: not used in stage/backdrops]",
194
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
195
+ },
196
+ "motion_direction": {
197
+ "block_name": "(direction)", "block_type": "Motion", "block_shape": "Reporter Block", "op_code": "motion_direction",
198
+ "functionality": "Reports the current direction of the sprite in degrees (0 = up, 90 = right, 180 = down, -90 = left).[NOTE: not used in stage/backdrops]",
199
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
200
+ },
201
+
202
+ # control_block.json
203
+ "control_wait": {
204
+ "block_name": "wait () seconds", "block_type": "Control", "block_shape": "Stack Block", "op_code": "control_wait",
205
+ "functionality": "Pauses the script for a specified duration.",
206
+ "inputs": {"DURATION": [1, [5, "1"]]}, "fields": {}, "shadow": False, "topLevel": True
207
+ },
208
+ "control_repeat": {
209
+ "block_name": "repeat ()", "block_type": "Control", "block_shape": "C-Block", "op_code": "control_repeat",
210
+ "functionality": "Repeats the blocks inside it a specified number of times.",
211
+ "inputs": {"TIMES": [1, [6, "10"]], "SUBSTACK": [2, None]}, "fields": {}, "shadow": False, "topLevel": True
212
+ },
213
+ "control_forever": {
214
+ "block_name": "forever", "block_type": "Control", "block_shape": "C-Block", "op_code": "control_forever",
215
+ "functionality": "Continuously runs the blocks inside it.",
216
+ "inputs": {"SUBSTACK": [2, None]}, "fields": {}, "shadow": False, "topLevel": True
217
+ },
218
+ "control_if": {
219
+ "block_name": "if <> then", "block_type": "Control", "block_shape": "C-Block", "op_code": "control_if",
220
+ "functionality": "Executes the blocks inside it only if the specified boolean condition is true. [NOTE: it takes boolean blocks as input]",
221
+ "inputs": {"CONDITION": [2, None], "SUBSTACK": [2, None]}, "fields": {}, "shadow": False, "topLevel": True
222
+ },
223
+ "control_if_else": {
224
+ "block_name": "if <> then else", "block_type": "Control", "block_shape": "C-Block", "op_code": "control_if_else",
225
+ "functionality": "Executes one set of blocks if the specified boolean condition is true, and a different set of blocks if the condition is false. [NOTE: it takes boolean blocks as input]",
226
+ "inputs": {"CONDITION": [2, None], "SUBSTACK": [2, None], "SUBSTACK2": [2, None]}, "fields": {}, "shadow": False, "topLevel": True
227
+ },
228
+ "control_wait_until": {
229
+ "block_name": "wait until <>", "block_type": "Control", "block_shape": "Stack Block", "op_code": "control_wait_until",
230
+ "functionality": "Pauses the script until the specified boolean condition becomes true. [NOTE: it takes boolean blocks as input]",
231
+ "inputs": {"CONDITION": [2, None]}, "fields": {}, "shadow": False, "topLevel": True
232
+ },
233
+ "control_repeat_until": {
234
+ "block_name": "repeat until <>", "block_type": "Control", "block_shape": "C-Block", "op_code": "control_repeat_until",
235
+ "functionality": "Repeats the blocks inside it until the specified boolean condition becomes true. [NOTE: it takes boolean blocks as input]",
236
+ "inputs": {"CONDITION": [2, None], "SUBSTACK": [2, None]}, "fields": {}, "shadow": False, "topLevel": True
237
+ },
238
+ "control_stop": {
239
+ "block_name": "stop [v]", "block_type": "Control", "block_shape": "Cap Block", "op_code": "control_stop",
240
+ "functionality": "Halts all scripts, only the current script, or other scripts within the same sprite. Its shape can dynamically change based on the selected option.",
241
+ "inputs": {}, "fields": {"STOP_OPTION": ["all", None]}, "shadow": False, "topLevel": True, "mutation": {"tagName": "mutation", "children": [], "hasnext": "false"}
242
+ },
243
+ "control_start_as_clone": {
244
+ "block_name": "When I Start as a Clone", "block_type": "Control", "block_shape": "Hat Block", "op_code": "control_start_as_clone",
245
+ "functionality": "This Hat block initiates the script when a clone of the sprite is created. It defines the behavior of individual clones.",
246
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
247
+ },
248
+ "control_create_clone_of": {
249
+ "block_name": "create clone of ()", "block_type": "Control", "block_shape": "Stack Block", "op_code": "control_create_clone_of",
250
+ "functionality": "Generates a copy, or clone, of a specified sprite (or 'myself' for the current sprite).",
251
+ "inputs": {"CLONE_OPTION": [1, "control_create_clone_of_menu"]}, "fields": {}, "shadow": False, "topLevel": True
252
+ },
253
+ "control_create_clone_of_menu": {
254
+ "block_name": "create clone of menu", "block_type": "Control", "block_shape": "Reporter Block", "op_code": "control_create_clone_of_menu",
255
+ "functionality": "Menu for create clone of block.",
256
+ "inputs": {}, "fields": {"CLONE_OPTION": ["_myself_", None]}, "shadow": True, "topLevel": False
257
+ },
258
+ "control_delete_this_clone": {
259
+ "block_name": "delete this clone", "block_type": "Control", "block_shape": "Cap Block", "op_code": "control_delete_this_clone",
260
+ "functionality": "Removes the clone that is executing it from the stage.",
261
+ "inputs":None, "fields": {}, "shadow": False, "topLevel": True
262
+ },
263
+
264
+ # data_block.json
265
+ "data_setvariableto": {
266
+ "block_name": "set [my variable v] to ()", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_setvariableto",
267
+ "functionality": "Assigns a specific value (number, string, or boolean) to a variable.",
268
+ "inputs": {"VALUE": [1, [10, "0"]]}, "fields": {"VARIABLE": ["my variable", "`jEk@4|i[#Fk?(8x)AV.-my variable"]}, "shadow": False, "topLevel": True
269
+ },
270
+ "data_changevariableby": {
271
+ "block_name": "change [my variable v] by ()", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_changevariableby",
272
+ "functionality": "Increases or decreases a variable's numerical value by a specified amount.",
273
+ "inputs": {"VALUE": [1, [4, "1"]]}, "fields": {"VARIABLE": ["my variable", "`jEk@4|i[#Fk?(8x)AV.-my variable"]}, "shadow": False, "topLevel": True
274
+ },
275
+ "data_showvariable": {
276
+ "block_name": "show variable [my variable v]", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_showvariable",
277
+ "functionality": "Makes a variable's monitor visible on the stage.",
278
+ "inputs": {}, "fields": {"VARIABLE": ["my variable", "`jEk@4|i[#Fk?(8x)AV.-my variable"]}, "shadow": False, "topLevel": True
279
+ },
280
+ "data_hidevariable": {
281
+ "block_name": "hide variable [my variable v]", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_hidevariable",
282
+ "functionality": "Hides a variable's monitor from the stage.",
283
+ "inputs": {}, "fields": {"VARIABLE": ["my variable", "`jEk@4|i[#Fk?(8x)AV.-my variable"]}, "shadow": False, "topLevel": True
284
+ },
285
+ "data_addtolist": {
286
+ "block_name": "add () to [my list v]", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_addtolist",
287
+ "functionality": "Appends an item to the end of a list.",
288
+ "inputs": {"ITEM": [1, [10, "thing"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True
289
+ },
290
+ "data_deleteoflist": {
291
+ "block_name": "delete () of [my list v]", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_deleteoflist",
292
+ "functionality": "Removes an item from a list by its index or by selecting 'all' items.",
293
+ "inputs": {"INDEX": [1, [7, "1"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True
294
+ },
295
+ "data_deletealloflist": {
296
+ "block_name": "delete all of [my list v]", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_deletealloflist",
297
+ "functionality": "Removes all items from a list.",
298
+ "inputs": {}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True
299
+ },
300
+ "data_insertatlist": {
301
+ "block_name": "insert () at () of [my list v]", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_insertatlist",
302
+ "functionality": "Inserts an item at a specific position within a list.",
303
+ "inputs": {"ITEM": [1, [10, "thing"]], "INDEX": [1, [7, "1"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True
304
+ },
305
+ "data_replaceitemoflist": {
306
+ "block_name": "replace item () of [my list v] with ()", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_replaceitemoflist",
307
+ "functionality": "Replaces an item at a specific position in a list with a new value.",
308
+ "inputs": {"INDEX": [1, [7, "1"]], "ITEM": [1, [10, "thing"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True
309
+ },
310
+ "data_itemoflist": {
311
+ "block_name": "(item (2) of [myList v])", "block_type": "Data", "block_shape": "Reporter Block", "op_code": "data_itemoflist",
312
+ "functionality": "Reports the item located at a specific position in a list.",
313
+ "inputs": {"INDEX": [1, [7, "1"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True
314
+ },
315
+ "data_itemnumoflist": {
316
+ "block_name": "(item # of [Dog] in [myList v])", "block_type": "Data", "block_shape": "Reporter Block", "op_code": "data_itemnumoflist",
317
+ "functionality": "Reports the index number of the first occurrence of a specified item in a list. If the item is not found, it reports 0.",
318
+ "inputs": {"ITEM": [1, [10, "thing"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True
319
+ },
320
+ "data_lengthoflist": {
321
+ "block_name": "(length of [myList v])", "block_type": "Data", "block_shape": "Reporter Block", "op_code": "data_lengthoflist",
322
+ "functionality": "Provides the total number of items contained in a list.",
323
+ "inputs": {}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True
324
+ },
325
+ "data_listcontainsitem": {
326
+ "block_name": "<[my list v] contains ()?>", "block_type": "Data", "block_shape": "Boolean Block", "op_code": "data_listcontainsitem",
327
+ "functionality": "Checks if a list includes a specific item.",
328
+ "inputs": {"ITEM": [1, [10, "thing"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True
329
+ },
330
+ "data_showlist": {
331
+ "block_name": "show list [my list v]", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_showlist",
332
+ "functionality": "Makes a list's monitor visible on the stage.",
333
+ "inputs": {}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True
334
+ },
335
+ "data_hidelist": {
336
+ "block_name": "hide list [my list v]", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_hidelist",
337
+ "functionality": "Hides a list's monitor from the stage.",
338
+ "inputs": {}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True
339
+ },
340
+ "data_variable": { # This is a reporter block for a variable's value
341
+ "block_name": "[variable v]", "block_type": "Data", "block_shape": "Reporter Block", "op_code": "data_variable",
342
+ "functionality": "Provides the current value stored in a variable.",
343
+ "inputs": {}, "fields": {"VARIABLE": ["my variable", None]}, "shadow": True, "topLevel": False
344
+ },
345
+
346
+ # event_block.json
347
+ "event_whenflagclicked": {
348
+ "block_name": "when green flag pressed", "block_type": "Events", "op_code": "event_whenflagclicked", "block_shape": "Hat Block",
349
+ "functionality": "This Hat block initiates the script when the green flag is clicked, serving as the common starting point for most Scratch projects.",
350
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
351
+ },
352
+ "event_whenkeypressed": {
353
+ "block_name": "when () key pressed", "block_type": "Events", "op_code": "event_whenkeypressed", "block_shape": "Hat Block",
354
+ "functionality": "This Hat block initiates the script when a specified keyboard key is pressed.",
355
+ "inputs": {}, "fields": {"KEY_OPTION": ["space", None]}, "shadow": False, "topLevel": True
356
+ },
357
+ "event_whenthisspriteclicked": {
358
+ "block_name": "when this sprite clicked", "block_type": "Events", "op_code": "event_whenthisspriteclicked", "block_shape": "Hat Block",
359
+ "functionality": "This Hat block starts the script when the sprite itself is clicked.",
360
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
361
+ },
362
+ "event_whenbackdropswitchesto": {
363
+ "block_name": "when backdrop switches to ()", "block_type": "Events", "op_code": "event_whenbackdropswitchesto", "block_shape": "Hat Block",
364
+ "functionality": "This Hat block triggers the script when the stage backdrop changes to a specified backdrop.",
365
+ "inputs": {}, "fields": {"BACKDROP": ["backdrop1", None]}, "shadow": False, "topLevel": True
366
+ },
367
+ "event_whengreaterthan": {
368
+ "block_name": "when () > ()", "block_type": "Events", "op_code": "event_whengreaterthan", "block_shape": "Hat Block",
369
+ "functionality": "This Hat block starts the script when a certain value (e.g., loudness from a microphone, or the timer) exceeds a defined threshold.",
370
+ "inputs": {"VALUE": [1, [4, "10"]]}, "fields": {"WHENGREATERTHANMENU": ["LOUDNESS", None]}, "shadow": False, "topLevel": True
371
+ },
372
+ "event_whenbroadcastreceived": {
373
+ "block_name": "when I receive ()", "block_type": "Events", "op_code": "event_whenbroadcastreceived", "block_shape": "Hat Block",
374
+ "functionality": "This Hat block initiates the script upon the reception of a specific broadcast message. This mechanism facilitates indirect communication between sprites or the stage.",
375
+ "inputs": {}, "fields": {"BROADCAST_OPTION": ["message1", "5O!nei;S$!c!=hCT}0:a"]}, "shadow": False, "topLevel": True
376
+ },
377
+ "event_broadcast": {
378
+ "block_name": "broadcast ()", "block_type": "Events", "block_shape": "Stack Block", "op_code": "event_broadcast",
379
+ "functionality": "Sends a broadcast message throughout the Scratch program, activating any 'when I receive ()' blocks that are set to listen for that message, enabling indirect communication.",
380
+ "inputs": {"BROADCAST_INPUT": [1, [11, "message1", "5O!nei;S$!c!=hCT}0:a"]]}, "fields": {}, "shadow": False, "topLevel": True
381
+ },
382
+ "event_broadcastandwait": {
383
+ "block_name": "broadcast () and wait", "block_type": "Events", "block_shape": "Stack Block", "op_code": "event_broadcastandwait",
384
+ "functionality": "Sends a broadcast message and pauses the current script until all other scripts activated by that broadcast have completed their execution, ensuring sequential coordination.",
385
+ "inputs": {"BROADCAST_INPUT": [1, [11, "message1", "5O!nei;S$!c!=hCT}0:a"]]}, "fields": {}, "shadow": False, "topLevel": True
386
+ },
387
+
388
+ # looks_block.json
389
+ "looks_sayforsecs": {
390
+ "block_name": "say () for () seconds", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_sayforsecs",
391
+ "functionality": "Displays a speech bubble containing specified text for a set duration.",
392
+ "inputs": {"MESSAGE": [1, [10, "Hello!"]], "SECS": [1, [4, "2"]]}, "fields": {}, "shadow": False, "topLevel": True
393
+ },
394
+ "looks_say": {
395
+ "block_name": "say ()", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_say",
396
+ "functionality": "Displays a speech bubble with the specified text indefinitely until another 'say' or 'think' block is activated.",
397
+ "inputs": {"MESSAGE": [1, [10, "Hello!"]]}, "fields": {}, "shadow": False, "topLevel": True
398
+ },
399
+ "looks_thinkforsecs": {
400
+ "block_name": "think () for () seconds", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_thinkforsecs",
401
+ "functionality": "Displays a thought bubble containing specified text for a set duration.",
402
+ "inputs": {"MESSAGE": [1, [10, "Hmm..."]], "SECS": [1, [4, "2"]]}, "fields": {}, "shadow": False, "topLevel": True
403
+ },
404
+ "looks_think": {
405
+ "block_name": "think ()", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_think",
406
+ "functionality": "Displays a thought bubble with the specified text indefinitely until another 'say' or 'think' block is activated.",
407
+ "inputs": {"MESSAGE": [1, [10, "Hmm..."]]}, "fields": {}, "shadow": False, "topLevel": True
408
+ },
409
+ "looks_switchcostumeto": {
410
+ "block_name": "switch costume to ()", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_switchcostumeto",
411
+ "functionality": "Alters the sprite's appearance to a designated costume.",
412
+ "inputs": {"COSTUME": [1, "looks_costume"]}, "fields": {}, "shadow": False, "topLevel": True
413
+ },
414
+ "looks_costume": {
415
+ "block_name": "costume menu", "block_type": "Looks", "block_shape": "Reporter Block", "op_code": "looks_costume",
416
+ "functionality": "Menu for switch costume to block.",
417
+ "inputs": {}, "fields": {"COSTUME": ["costume1", None]}, "shadow": True, "topLevel": False
418
+ },
419
+ "looks_nextcostume": {
420
+ "block_name": "next costume", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_nextcostume",
421
+ "functionality": "Switches the sprite's costume to the next one in its costume list. If it's the last costume, it cycles back to the first.",
422
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
423
+ },
424
+ "looks_switchbackdropto": {
425
+ "block_name": "switch backdrop to ()", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_switchbackdropto",
426
+ "functionality": "Changes the stage's backdrop to a specified backdrop.",
427
+ "inputs": {"BACKDROP": [1, "looks_backdrops"]}, "fields": {}, "shadow": False, "topLevel": True
428
+ },
429
+ "looks_backdrops": {
430
+ "block_name": "backdrop menu", "block_type": "Looks", "block_shape": "Reporter Block", "op_code": "looks_backdrops",
431
+ "functionality": "Menu for switch backdrop to block.",
432
+ "inputs": {}, "fields": {"BACKDROP": ["backdrop1", None]}, "shadow": True, "topLevel": False
433
+ },
434
+ "looks_switchbackdroptowait": {
435
+ "block_name": "switch backdrop to () and wait", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_switchbackdroptowait",
436
+ "functionality": "Changes the stage's backdrop to a specified backdrop and pauses the script until any 'When backdrop switches to' scripts for that backdrop have finished.",
437
+ "inputs": {"BACKDROP": [1, "looks_backdrops"]}, "fields": {}, "shadow": False, "topLevel": True
438
+ },
439
+ "looks_nextbackdrop": {
440
+ "block_name": "next backdrop", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_nextbackdrop",
441
+ "functionality": "Switches the stage's backdrop to the next one in its backdrop list. If it's the last backdrop, it cycles back to the first.",
442
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
443
+ },
444
+ "looks_changesizeby": {
445
+ "block_name": "change size by ()", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_changesizeby",
446
+ "functionality": "Changes the sprite's size by a specified percentage. Positive values make it larger, negative values make it smaller.",
447
+ "inputs": {"CHANGE": [1, [4, "10"]]}, "fields": {}, "shadow": False, "topLevel": True
448
+ },
449
+ "looks_setsizeto": {
450
+ "block_name": "set size to () %", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_setsizeto",
451
+ "functionality": "Sets the sprite's size to a specific percentage of its original size.",
452
+ "inputs": {"SIZE": [1, [4, "100"]]}, "fields": {}, "shadow": False, "topLevel": True
453
+ },
454
+ "looks_changeeffectby": {
455
+ "block_name": "change () effect by ()", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_changeeffectby",
456
+ "functionality": "Changes a visual effect on the sprite by a specified amount (e.g., color, fisheye, whirl, pixelate, mosaic, brightness, ghost).",
457
+ "inputs": {"CHANGE": [1, [4, "25"]]}, "fields": {"EFFECT": ["COLOR", None]}, "shadow": False, "topLevel": True
458
+ },
459
+ "looks_seteffectto": {
460
+ "block_name": "set () effect to ()", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_seteffectto",
461
+ "functionality": "Sets a visual effect on the sprite to a specific value.",
462
+ "inputs": {"VALUE": [1, [4, "0"]]}, "fields": {"EFFECT": ["COLOR", None]}, "shadow": False, "topLevel": True
463
+ },
464
+ "looks_cleargraphiceffects": {
465
+ "block_name": "clear graphic effects", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_cleargraphiceffects",
466
+ "functionality": "Removes all visual effects applied to the sprite.",
467
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
468
+ },
469
+ "looks_show": {
470
+ "block_name": "show", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_show",
471
+ "functionality": "Makes the sprite visible on the stage.",
472
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
473
+ },
474
+ "looks_hide": {
475
+ "block_name": "hide", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_hide",
476
+ "functionality": "Makes the sprite invisible on the stage.",
477
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
478
+ },
479
+ "looks_gotofrontback": {
480
+ "block_name": "go to () layer", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_gotofrontback",
481
+ "functionality": "Moves the sprite to the front-most or back-most layer of other sprites on the stage.",
482
+ "inputs": {}, "fields": {"FRONT_BACK": ["front", None]}, "shadow": False, "topLevel": True
483
+ },
484
+ "looks_goforwardbackwardlayers": {
485
+ "block_name": "go () layers", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_goforwardbackwardlayers",
486
+ "functionality": "Moves the sprite forward or backward a specified number of layers in relation to other sprites.",
487
+ "inputs": {"NUM": [1, [7, "1"]]}, "fields": {"FORWARD_BACKWARD": ["forward", None]}, "shadow": False, "topLevel": True
488
+ },
489
+ "looks_costumenumbername": {
490
+ "block_name": "(costume ())", "block_type": "Looks", "block_shape": "Reporter Block", "op_code": "looks_costumenumbername",
491
+ "functionality": "Reports the current costume's number or name.",
492
+ "inputs": {}, "fields": {"NUMBER_NAME": ["number", None]}, "shadow": False, "topLevel": True
493
+ },
494
+ "looks_backdropnumbername": {
495
+ "block_name": "(backdrop ())", "block_type": "Looks", "block_shape": "Reporter Block", "op_code": "looks_backdropnumbername",
496
+ "functionality": "Reports the current backdrop's number or name.",
497
+ "inputs": {}, "fields": {"NUMBER_NAME": ["number", None]}, "shadow": False, "topLevel": True
498
+ },
499
+ "looks_size": {
500
+ "block_name": "(size)", "block_type": "Looks", "block_shape": "Reporter Block", "op_code": "looks_size",
501
+ "functionality": "Reports the current size of the sprite as a percentage.",
502
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
503
+ },
504
+
505
+ # operator_block.json
506
+ "operator_add": {
507
+ "block_name": "(() + ())", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_add",
508
+ "functionality": "Adds two numerical values.",
509
+ "inputs": {"NUM1": [1, [4, ""]], "NUM2": [1, [4, ""]]}, "fields": {}, "shadow": False, "topLevel": True
510
+ },
511
+ "operator_subtract": {
512
+ "block_name": "(() - ())", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_subtract",
513
+ "functionality": "Subtracts the second numerical value from the first.",
514
+ "inputs": {"NUM1": [1, [4, ""]], "NUM2": [1, [4, ""]]}, "fields": {}, "shadow": False, "topLevel": True
515
+ },
516
+ "operator_multiply": {
517
+ "block_name": "(() * ())", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_multiply",
518
+ "functionality": "Multiplies two numerical values.",
519
+ "inputs": {"NUM1": [1, [4, ""]], "NUM2": [1, [4, ""]]}, "fields": {}, "shadow": False, "topLevel": True
520
+ },
521
+ "operator_divide": {
522
+ "block_name": "(() / ())", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_divide",
523
+ "functionality": "Divides the first numerical value by the second.",
524
+ "inputs": {"NUM1": [1, [4, ""]], "NUM2": [1, [4, ""]]}, "fields": {}, "shadow": False, "topLevel": True
525
+ },
526
+ "operator_random": {
527
+ "block_name": "(pick random () to ())", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_random",
528
+ "functionality": "Generates a random integer within a specified inclusive range.",
529
+ "inputs": {"FROM": [1, [4, "1"]], "TO": [1, [4, "10"]]}, "fields": {}, "shadow": False, "topLevel": True
530
+ },
531
+ "operator_gt": {
532
+ "block_name": "<() > ()>", "block_type": "operator", "block_shape": "Boolean Block", "op_code": "operator_gt",
533
+ "functionality": "Checks if the first value is greater than the second.",
534
+ "inputs": {"OPERAND1": [1, [10, ""]], "OPERAND2": [1, [10, "50"]]}, "fields": {}, "shadow": False, "topLevel": True
535
+ },
536
+ "operator_lt": {
537
+ "block_name": "<() < ()>", "block_type": "operator", "block_shape": "Boolean Block", "op_code": "operator_lt",
538
+ "functionality": "Checks if the first value is less than the second.",
539
+ "inputs": {"OPERAND1": [1, [10, ""]], "OPERAND2": [1, [10, "50"]]}, "fields": {}, "shadow": False, "topLevel": True
540
+ },
541
+ "operator_equals": {
542
+ "block_name": "<() = ()>", "block_type": "operator", "block_shape": "Boolean Block", "op_code": "operator_equals",
543
+ "functionality": "Checks if two values are equal.",
544
+ "inputs": {"OPERAND1": [1, [10, ""]], "OPERAND2": [1, [10, "50"]]}, "fields": {}, "shadow": False, "topLevel": True
545
+ },
546
+ "operator_and": {
547
+ "block_name": "<<> and <>>", "block_type": "operator", "block_shape": "Boolean Block", "op_code": "operator_and",
548
+ "functionality": "Returns 'true' if both provided Boolean conditions are 'true'.",
549
+ "inputs": {"OPERAND1": [2, None], "OPERAND2": [2, None]}, "fields": {}, "shadow": False, "topLevel": True
550
+ },
551
+ "operator_or": {
552
+ "block_name": "<<> or <>>", "block_type": "operator", "block_shape": "Boolean Block", "op_code": "operator_or",
553
+ "functionality": "Returns 'true' if at least one of the provided Boolean conditions is 'true'.",
554
+ "inputs": {"OPERAND1": [2, None], "OPERAND2": [2, None]}, "fields": {}, "shadow": False, "topLevel": True
555
+ },
556
+ "operator_not": {
557
+ "block_name": "<not <>>", "block_type": "operator", "block_shape": "Boolean Block", "op_code": "operator_not",
558
+ "functionality": "Returns 'true' if the provided Boolean condition is 'false', and 'false' if it is 'true'.",
559
+ "inputs": {"OPERAND": [2, None]}, "fields": {}, "shadow": False, "topLevel": True
560
+ },
561
+ "operator_join": {
562
+ "block_name": "(join ()())", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_join",
563
+ "functionality": "Concatenates two strings or values into a single string.",
564
+ "inputs": {"STRING1": [1, [10, "apple "]], "STRING2": [1, [10, "banana"]]}, "fields": {}, "shadow": False, "topLevel": True
565
+ },
566
+ "operator_letterof": {
567
+ "block_name": "letter () of ()", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_letterof",
568
+ "functionality": "Reports the character at a specific numerical position within a string.",
569
+ "inputs": {"LETTER": [1, [6, "1"]], "STRING": [1, [10, "apple"]]}, "fields": {}, "shadow": False, "topLevel": True
570
+ },
571
+ "operator_length": {
572
+ "block_name": "(length of ())", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_length",
573
+ "functionality": "Reports the total number of characters in a given string.",
574
+ "inputs": {"STRING": [1, [10, "apple"]]}, "fields": {}, "shadow": False, "topLevel": True
575
+ },
576
+ "operator_contains": {
577
+ "block_name": "<() contains ()?>", "block_type": "operator", "block_shape": "Boolean Block", "op_code": "operator_contains",
578
+ "functionality": "Checks if one string contains another string.",
579
+ "inputs": {"STRING1": [1, [10, "apple"]], "STRING2": [1, [10, "a"]]}, "fields": {}, "shadow": False, "topLevel": True
580
+ },
581
+ "operator_mod": {
582
+ "block_name": "(() mod ())", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_mod",
583
+ "functionality": "Reports the remainder when the first number is divided by the second.",
584
+ "inputs": {"NUM1": [1, [4, ""]], "NUM2": [1, [4, ""]]}, "fields": {}, "shadow": False, "topLevel": True
585
+ },
586
+ "operator_round": {
587
+ "block_name": "(round ())", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_round",
588
+ "functionality": "Rounds a numerical value to the nearest integer.",
589
+ "inputs": {"NUM": [1, [4, ""]]}, "fields": {}, "shadow": False, "topLevel": True
590
+ },
591
+ "operator_mathop": {
592
+ "block_name": "(() of ())", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_mathop",
593
+ "functionality": "Performs various mathematical functions (e.g., absolute value, square root, trigonometric functions).",
594
+ "inputs": {"NUM": [1, [4, ""]]}, "fields": {"OPERATOR": ["abs", None]}, "shadow": False, "topLevel": True
595
+ },
596
+
597
+ # sensing_block.json
598
+ "sensing_touchingobject": {
599
+ "block_name": "<touching [edge v]?>", "block_type": "Sensing", "op_code": "sensing_touchingobject", "block_shape": "Boolean Block",
600
+ "functionality": "Checks if its sprite is touching the mouse-pointer, edge, or another specified sprite.",
601
+ "inputs": {"TOUCHINGOBJECTMENU": [1, "sensing_touchingobjectmenu"]}, "fields": {}, "shadow": False, "topLevel": True
602
+ },
603
+ "sensing_touchingobjectmenu": {
604
+ "block_name": "touching object menu", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_touchingobjectmenu",
605
+ "functionality": "Menu for touching object block.",
606
+ "inputs": {}, "fields": {"TOUCHINGOBJECTMENU": ["_mouse_", None]}, "shadow": True, "topLevel": False
607
+ },
608
+ "sensing_touchingcolor": {
609
+ "block_name": "<touching color ()?>", "block_type": "Sensing", "op_code": "sensing_touchingcolor", "block_shape": "Boolean Block",
610
+ "functionality": "Checks whether its sprite is touching a specified color.",
611
+ "inputs": {"COLOR": [1, [9, "#55b888"]]}, "fields": {}, "shadow": False, "topLevel": True
612
+ },
613
+ "sensing_coloristouchingcolor": {
614
+ "block_name": "<color () is touching ()?>", "block_type": "Sensing", "op_code": "sensing_coloristouchingcolor", "block_shape": "Boolean Block",
615
+ "functionality": "Checks whether a specific color on its sprite is touching another specified color on the stage or another sprite.",
616
+ "inputs": {"COLOR1": [1, [9, "#d019f2"]], "COLOR2": [1, [9, "#2b0de3"]]}, "fields": {}, "shadow": False, "topLevel": True
617
+ },
618
+ "sensing_askandwait": {
619
+ "block_name": "Ask () and Wait", "block_type": "Sensing", "block_shape": "Stack Block", "op_code": "sensing_askandwait",
620
+ "functionality": "Displays an input box with specified text at the bottom of the screen, allowing users to input text, which is stored in the 'Answer' block.",
621
+ "inputs": {"QUESTION": [1, [10, "What's your name?"]]}, "fields": {}, "shadow": False, "topLevel": True
622
+ },
623
+ "sensing_answer": {
624
+ "block_name": "(answer)", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_answer",
625
+ "functionality": "Holds the most recent text inputted using the 'Ask () and Wait' block.",
626
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
627
+ },
628
+ "sensing_keypressed": {
629
+ "block_name": "<key () pressed?>", "block_type": "Sensing", "op_code": "sensing_keypressed", "block_shape": "Boolean Block",
630
+ "functionality": "Checks if a specified keyboard key is currently being pressed.",
631
+ "inputs": {"KEY_OPTION": [1, "sensing_keyoptions"]}, "fields": {}, "shadow": False, "topLevel": True
632
+ },
633
+ "sensing_keyoptions": {
634
+ "block_name": "key options menu", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_keyoptions",
635
+ "functionality": "Menu for key pressed block.",
636
+ "inputs": {}, "fields": {"KEY_OPTION": ["space", None]}, "shadow": True, "topLevel": False
637
+ },
638
+ "sensing_mousedown": {
639
+ "block_name": "<mouse down?>", "block_type": "Sensing", "op_code": "sensing_mousedown", "block_shape": "Boolean Block",
640
+ "functionality": "Checks if the computer mouse's primary button is being clicked while the cursor is over the stage.",
641
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
642
+ },
643
+ "sensing_mousex": {
644
+ "block_name": "(mouse x)", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_mousex",
645
+ "functionality": "Reports the mouse-pointer’s current X position on the stage.",
646
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
647
+ },
648
+ "sensing_mousey": {
649
+ "block_name": "(mouse y)", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_mousey",
650
+ "functionality": "Reports the mouse-pointer’s current Y position on the stage.",
651
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
652
+ },
653
+ "sensing_setdragmode": {
654
+ "block_name": "set drag mode [draggable v]", "block_type": "Sensing", "block_shape": "Stack Block", "op_code": "sensing_setdragmode",
655
+ "functionality": "Sets whether the sprite can be dragged by the mouse on the stage.",
656
+ "inputs": {}, "fields": {"DRAG_MODE": ["draggable", None]}, "shadow": False, "topLevel": True
657
+ },
658
+ "sensing_loudness": {
659
+ "block_name": "(loudness)", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_loudness",
660
+ "functionality": "Reports the loudness of noise received by a microphone on a scale of 0 to 100.",
661
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
662
+ },
663
+ "sensing_timer": {
664
+ "block_name": "(timer)", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_timer",
665
+ "functionality": "Reports the elapsed time since Scratch was launched or the timer was reset, increasing by 1 every second.",
666
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
667
+ },
668
+ "sensing_resettimer": {
669
+ "block_name": "Reset Timer", "block_type": "Sensing", "block_shape": "Stack Block", "op_code": "sensing_resettimer",
670
+ "functionality": "Sets the timer’s value back to 0.0.",
671
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
672
+ },
673
+ "sensing_of": {
674
+ "block_name": "(() of ())", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_of",
675
+ "functionality": "Reports a specified value (e.g., x position, direction, costume number) of a specified sprite or the Stage to be accessed in current sprite or stage.",
676
+ "inputs": {"OBJECT": [1, "sensing_of_object_menu"]}, "fields": {"PROPERTY": ["backdrop #", None]}, "shadow": False, "topLevel": True
677
+ },
678
+ "sensing_of_object_menu": {
679
+ "block_name": "of object menu", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_of_object_menu",
680
+ "functionality": "Menu for of block.",
681
+ "inputs": {}, "fields": {"OBJECT": ["_stage_", None]}, "shadow": True, "topLevel": False
682
+ },
683
+ "sensing_current": {
684
+ "block_name": "(current ())", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_current",
685
+ "functionality": "Reports the current local year, month, date, day of the week, hour, minutes, or seconds.",
686
+ "inputs": {}, "fields": {"CURRENTMENU": ["YEAR", None]}, "shadow": False, "topLevel": True
687
+ },
688
+ "sensing_dayssince2000": {
689
+ "block_name": "(days since 2000)", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_dayssince2000",
690
+ "functionality": "Reports the number of days (and fractions of a day) since 00:00:00 UTC on January 1, 2000.",
691
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
692
+ },
693
+ "sensing_username": {
694
+ "block_name": "(username)", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_username",
695
+ "functionality": "Reports the username of the user currently logged into Scratch. If no user is logged in, it reports nothing.",
696
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
697
+ },
698
+
699
+ # sound_block.json
700
+ "sound_playuntildone": {
701
+ "block_name": "play sound () until done", "block_type": "Sound", "block_shape": "Stack Block", "op_code": "sound_playuntildone",
702
+ "functionality": "Plays a specified sound and pauses the script's execution until the sound has completed.",
703
+ "inputs": {"SOUND_MENU": [1, "sound_sounds_menu"]}, "fields": {}, "shadow": False, "topLevel": True
704
+ },
705
+ "sound_sounds_menu": {
706
+ "block_name": "sound menu", "block_type": "Sound", "block_shape": "Reporter Block", "op_code": "sound_sounds_menu",
707
+ "functionality": "Menu for sound blocks.",
708
+ "inputs": {}, "fields": {"SOUND_MENU": ["Meow", None]}, "shadow": True, "topLevel": False
709
+ },
710
+ "sound_play": {
711
+ "block_name": "start sound ()", "block_type": "Sound", "block_shape": "Stack Block", "op_code": "sound_play",
712
+ "functionality": "Initiates playback of a specified sound without pausing the script, allowing other actions to proceed concurrently.",
713
+ "inputs": {"SOUND_MENU": [1, "sound_sounds_menu"]}, "fields": {}, "shadow": False, "topLevel": True
714
+ },
715
+ "sound_stopallsounds": {
716
+ "block_name": "stop all sounds", "block_type": "Sound", "block_shape": "Stack Block", "op_code": "sound_stopallsounds",
717
+ "functionality": "Stops all currently playing sounds.",
718
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
719
+ },
720
+ "sound_changeeffectby": {
721
+ "block_name": "change () effect by ()", "block_type": "Sound", "block_shape": "Stack Block", "op_code": "sound_changeeffectby",
722
+ "functionality": "Changes the project's sound effect by a specified amount.",
723
+ "inputs": {"VALUE": [1, [4, "10"]]}, "fields": {"EFFECT": ["PITCH", None]}, "shadow": False, "topLevel": True
724
+ },
725
+ "sound_seteffectto": {
726
+ "block_name": "set () effect to ()", "block_type": "Sound", "block_shape": "Stack Block", "op_code": "sound_seteffectto",
727
+ "functionality": "Sets the sound effect to a specific value.",
728
+ "inputs": {"VALUE": [1, [4, "100"]]}, "fields": {"EFFECT": ["PITCH", None]}, "shadow": False, "topLevel": True
729
+ },
730
+ "sound_cleareffects": {
731
+ "block_name": "clear sound effects", "block_type": "Sound", "block_shape": "Stack Block", "op_code": "sound_cleareffects",
732
+ "functionality": "Removes all sound effects applied to the sprite.",
733
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
734
+ },
735
+ "sound_changevolumeby": {
736
+ "block_name": "change volume by ()", "block_type": "Sound", "block_shape": "Stack Block", "op_code": "sound_changevolumeby",
737
+ "functionality": "Changes the project's sound volume by a specified amount.",
738
+ "inputs": {"VOLUME": [1, [4, "-10"]]}, "fields": {}, "shadow": False, "topLevel": True
739
+ },
740
+ "sound_setvolumeto": {
741
+ "block_name": "set volume to () %", "block_type": "Sound", "block_shape": "Stack Block", "op_code": "sound_setvolumeto",
742
+ "functionality": "Sets the sound volume to a specific percentage (0-100).",
743
+ "inputs": {"VOLUME": [1, [4, "100"]]}, "fields": {}, "shadow": False, "topLevel": True
744
+ },
745
+ "sound_volume": {
746
+ "block_name": "(volume)", "block_type": "Sound", "block_shape": "Reporter Block", "op_code": "sound_volume",
747
+ "functionality": "Reports the current volume level of the sprite.",
748
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
749
+ },
750
+ }
751
+
752
+ # # Example input with opcodes for the initial generation
753
+ initial_opcode_counts = [
754
+ {"opcode":"event_whenflagclicked","count":1},
755
+ {"opcode":"motion_gotoxy","count":1},
756
+ {"opcode":"motion_glidesecstoxy","count":1},
757
+ {"opcode":"motion_xposition","count":1},
758
+ {"opcode":"motion_setx","count":1},
759
+ {"opcode":"control_forever","count":1},
760
+ {"opcode":"control_if","count":1},
761
+ {"opcode":"control_stop","count":1},
762
+ {"opcode":"operator_lt","count":1},
763
+ {"opcode":"sensing_touchingobject","count":1}, # Changed from sensing_istouching
764
+ {"opcode":"sensing_touchingobjectmenu","count":1},
765
+ {"opcode":"event_broadcast","count":1},
766
+ {"opcode":"data_setvariableto","count":2},
767
+ {"opcode":"data_showvariable","count":2},
768
+ ]
769
+
770
+ # initial_opcode_counts = [
771
+ # {"opcode": "sound_play", "count": 2},
772
+ # {"opcode": "sound_playuntildone", "count": 2},
773
+ # {"opcode":"motion_goto","count":2},
774
+ # {"opcode":"motion_glideto","count":2},
775
+ # {"opcode":"looks_switchbackdropto","count":2},
776
+ # {"opcode":"looks_switchcostumeto","count":2},
777
+ # {"opcode":"control_create_clone_of","count":2},
778
+ # {"opcode":"sensing_touchingobject","count":2},
779
+ # {"opcode":"sensing_of","count":2},
780
+ # {"opcode":"sensing_keypressed","count":2},
781
+ # {"opcode":"motion_pointtowards","count":2},
782
+ # ]
783
+
784
+ # Generate the initial blocks and get the opcode_occurrences
785
+ generated_output_json, initial_opcode_occurrences = generate_blocks_from_opcodes(initial_opcode_counts, all_block_definitions)
786
+ #print(generated_output_json)
787
+ #print(initial_opcode_occurrences)
788
+
789
+
790
+ def build_block_patterns(def_list):
791
+ """
792
+ Given a list of block definitions (from one JSON file),
793
+ return a dict opcode -> { regex:Compiled, inputs:[names], shape, block_name }.
794
+ """
795
+ out = {}
796
+ for d in def_list:
797
+ # 1) escape the literal text
798
+ pattern = re.escape(d['block_name'])
799
+ # 2) replace the escaped "()" placeholders with a capture for anything inside
800
+ pattern = pattern.replace(r"\(\)", r"\((.+?)\)")
801
+ # 3) replace the escaped "<>" placeholders likewise
802
+ pattern = pattern.replace(r"\<\>", r"<(.+?)>")
803
+ # 4) allow any whitespace where spaces occur
804
+ pattern = pattern.replace(r"\ ", r"\s+")
805
+ # anchor from start to end, ignore case
806
+ regex = re.compile(r"^\s*" + pattern + r"\s*$", re.IGNORECASE)
807
+
808
+ inputs = [inp['name'] for inp in (d.get('inputs') or [])]
809
+ out[d['op_code']] = {
810
+ 'regex': regex,
811
+ 'inputs': inputs,
812
+ 'shape': d['block_shape'],
813
+ 'block_name':d['block_name'],
814
+ 'definition':d
815
+ }
816
+ return out
817
+
818
+ def load_all_definitions():
819
+ # Load your JSON files here; adjust paths as needed
820
+ hats = json.load(open(r'blocks\hat_blocks.json'))['blocks']
821
+ c_blocks = json.load(open(r'blocks\c_blocks.json'))['blocks']
822
+ reporters = json.load(open(r'blocks\reporter_blocks.json'))['blocks']
823
+ booleans = json.load(open(r'blocks\boolean_blocks.json'))['blocks']
824
+ # you can also load stack_blocks.json, cap_blocks.json, etc. if you have them
825
+ merged = hats + c_blocks + reporters + booleans
826
+ return build_block_patterns(merged)
827
+
828
+ def generate_plan(generated_input, opcode_keys, pseudo_code):
829
+ """
830
+ A truly generic plan generator.
831
+ Inputs:
832
+ - generated_input: dict of block_key -> block_data
833
+ - opcode_keys: dict of opcode -> [block_key,...]
834
+ - pseudo_code: multiline string
835
+ Returns: { flow: [...] }
836
+ """
837
+ all_defs = load_all_definitions()
838
+ # pointer into each opcode_keys list
839
+ ptrs = defaultdict(int)
840
+ def pick_key(opcode):
841
+ lst = opcode_keys.get(opcode, [])
842
+ if ptrs[opcode] >= len(lst):
843
+ raise KeyError(f"No more generated keys for opcode {opcode!r}")
844
+ key = lst[ptrs[opcode]]
845
+ ptrs[opcode] += 1
846
+ return key
847
+
848
+ # Recursively parse an expression fragment like "(x position)" or "<touching [A]?>"
849
+ def parse_expression(expr_text):
850
+ expr_text = expr_text.strip()
851
+ # Try to match every reporter/boolean pattern
852
+ for op, info in all_defs.items():
853
+ m = info['regex'].match(expr_text)
854
+ if not m: continue
855
+ # Got a match
856
+ node = {'op_code': op, 'inputs': {}}
857
+ groups = m.groups()
858
+ for name, val in zip(info['inputs'], groups):
859
+ # decide kind by input type in definition
860
+ inp_def = next(
861
+ (i for i in info['definition'].get('inputs', []) if i['name']==name),
862
+ {}
863
+ )
864
+ t = inp_def.get('type','any')
865
+ if t in ('number','any'):
866
+ try:
867
+ node['inputs'][name] = {'kind':'value', 'value': float(val) if '.' in val else int(val)}
868
+ except:
869
+ node['inputs'][name] = {'kind':'variable', 'name': val.strip()}
870
+ elif t in ('dropdown','string','string/number'):
871
+ node['inputs'][name] = {'kind':'menu', 'option': val.strip()}
872
+ elif t=='boolean':
873
+ # nested boolean: recurse
874
+ node['inputs'][name] = {'kind':'nested', 'expr': parse_expression(val)}
875
+ else:
876
+ node['inputs'][name] = {'kind':'nested', 'expr': parse_expression(val)}
877
+ return node
878
+ # fallback: literal?
879
+ lit = re.match(r"^\(?\s*(-?\d+(\.\d+)?)\s*\)?$", expr_text)
880
+ if lit:
881
+ num = lit.group(1)
882
+ return {'literal': True, 'kind':'value', 'value': float(num) if '.' in num else int(num)}
883
+ # else treat as raw string
884
+ return {'literal':True, 'kind':'variable', 'name':expr_text}
885
+
886
+ flow = []
887
+ # stack of (indent, container)
888
+ stack = [(-1, flow)]
889
+
890
+ for raw in pseudo_code.splitlines():
891
+ if not raw.strip(): continue
892
+ indent = (len(raw) - len(raw.lstrip())) // 2
893
+ line = raw.strip()
894
+ # drop trailing 'then' or 'end'
895
+ line_clean = re.sub(r'\s*(then|end)\s*$', '', line, flags=re.IGNORECASE)
896
+
897
+ # find a matching block definition
898
+ for opcode, info in all_defs.items():
899
+ if not info['regex'].match(line_clean):
900
+ continue
901
+ # pop up to correct level
902
+ while stack and stack[-1][0] >= indent:
903
+ stack.pop()
904
+ container = stack[-1][1]
905
+
906
+ key = pick_key(opcode)
907
+ node = {'block_key': key}
908
+ shape = info['shape']
909
+ # determine node type
910
+ if 'Hat Block' in shape:
911
+ node['type'] = 'hat'
912
+ node['description'] = info['block_name']
913
+ node['next'] = []
914
+ target_list = node['next']
915
+ elif 'C-Block' in shape:
916
+ node['type'] = 'c_block'
917
+ node['description'] = info['block_name']
918
+ node['condition'] = None
919
+ node['body'] = []
920
+ target_list = node['body']
921
+ # the first input is usually the boolean condition
922
+ if info['inputs']:
923
+ cond_name = info['inputs'][0]
924
+ # extract the <...> portion
925
+ cond_match = re.search(r'<(.+)>', line)
926
+ if cond_match:
927
+ node['condition'] = parse_expression(cond_match.group(1))
928
+ elif 'Cap Block' in shape:
929
+ node['type'] = 'cap'
930
+ # e.g. stop [all v]
931
+ if 'inputs' in info['definition'] and info['definition']['inputs']:
932
+ fld = info['definition']['inputs'][0]['name']
933
+ val = re.search(r'\[\s*([^\]]+)\s*\]', line)
934
+ if val:
935
+ node['option'] = val.group(1)
936
+ target_list = None
937
+ else:
938
+ node['type'] = 'stack'
939
+ # parse any inputs in parentheses or brackets
940
+ node['inputs'] = {}
941
+ # for each input name in the definition, find its occurrence
942
+ for inp_name in info['inputs']:
943
+ # look for "(...)" after the input name or anywhere
944
+ # brute-force: find all "(...)" then map in order
945
+ pass
946
+ target_list = None
947
+
948
+ container.append(node)
949
+ if target_list is not None:
950
+ stack.append((indent, target_list))
951
+ break
952
+ else:
953
+ # no matching opcode β€” you can log or raise here
954
+ raise ValueError(f"Could not classify line: {line!r}")
955
+
956
+ return {'flow': flow}
957
+
958
+ pseudo_code_input = """
959
+ when green flag clicked
960
+ go to x: (240) y: (-135)
961
+ set [score v] to (1)
962
+ set [speed v] to (1)
963
+ show variable [score v]
964
+ show variable [speed v]
965
+ forever
966
+ glide (2) seconds to x: (-240) y: (-135)
967
+ if <((x position)) < (-235)> then
968
+ set x to (240)
969
+ end
970
+ if <touching [Sprite1 v]?> then
971
+ broadcast [Game Over v]
972
+ stop [all v]
973
+ end
974
+ end
975
+ end
976
+ """
977
+
978
+ # ── USAGE ──
979
+ plan = generate_plan(generated_output_json, initial_opcode_occurrences, pseudo_code_input)
980
+ import json
981
+ print(json.dumps(plan, indent=2))
utils/plan_generator.py ADDED
The diff for this file is too large to render. See raw diff
 
utils/plan_generator_2.py ADDED
The diff for this file is too large to render. See raw diff
 
utils/plan_generator_3.py ADDED
The diff for this file is too large to render. See raw diff
 
utils/plan_generator_4.py ADDED
The diff for this file is too large to render. See raw diff
 
utils/plan_generator_5.py ADDED
The diff for this file is too large to render. See raw diff
 
utils/plan_generator_6.py ADDED
The diff for this file is too large to render. See raw diff
 
utils/pseudo_exampls.txt ADDED
@@ -0,0 +1,618 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ pseudo_code_examples = [
2
+ # From motion_block.json
3
+ """
4
+ when green flag clicked
5
+ go to x: (0) y: (0)
6
+ point in direction (90)
7
+ move (50) steps
8
+ end
9
+ """,
10
+ """
11
+ when [right arrow v] key pressed
12
+ turn right (15) degrees
13
+ end
14
+ """,
15
+ """
16
+ when this sprite clicked
17
+ go to [mouse-pointer v]
18
+ """,
19
+ """
20
+ when green flag clicked
21
+ glide (2) secs to x: (150) y: (-100)
22
+ glide (2) secs to x: (-150) y: (100)
23
+ end
24
+ """,
25
+ """
26
+ when green flag clicked
27
+ forever
28
+ point towards [mouse-pointer v]
29
+ move (5) steps
30
+ end
31
+ end
32
+ """,
33
+ """
34
+ when [right arrow v] key pressed
35
+ change x by (10)
36
+ end
37
+ """,
38
+ """
39
+ when green flag clicked
40
+ set x to (0)
41
+ set y to (0)
42
+ end
43
+ """,
44
+ """
45
+ when [up arrow v] key pressed
46
+ change y by (10)
47
+ end
48
+ """,
49
+ """
50
+ when green flag clicked
51
+ forever
52
+ move (10) steps
53
+ if on edge, bounce
54
+ end
55
+ end
56
+ """,
57
+ """
58
+ when green flag clicked
59
+ set rotation style [left-right v]
60
+ forever
61
+ move (10) steps
62
+ if on edge, bounce
63
+ end
64
+ end
65
+ """,
66
+ # From looks_block.json
67
+ """
68
+ when green flag clicked
69
+ say [Grr] for (3) seconds
70
+ say [Have you seen my honey? v] for (3) seconds
71
+ end
72
+ """,
73
+ """
74
+ when green flag clicked
75
+ say [Welcome to my game! v]
76
+ wait (2) seconds
77
+ say []
78
+ end
79
+ """,
80
+ """
81
+ when this sprite clicked
82
+ think [What should I do? v] for (2) seconds
83
+ end
84
+ """,
85
+ """
86
+ when I receive [correct answer v]
87
+ think [That's right! v]
88
+ wait (1) seconds
89
+ think [good v]
90
+ end
91
+ """,
92
+ """
93
+ when I receive [explosion v]
94
+ repeat (5)
95
+ next costume
96
+ end
97
+ hide
98
+ end
99
+ """,
100
+ """
101
+ when green flag clicked
102
+ forever
103
+ next costume
104
+ wait (0.2) seconds
105
+ end
106
+ end
107
+ """,
108
+ """
109
+ when green flag clicked
110
+ switch backdrop to [start screen v]
111
+ end
112
+ """,
113
+ """
114
+ broadcast [game over v]
115
+ switch backdrop to [game over v] and wait
116
+ stop [all v]
117
+ end
118
+ """,
119
+ """
120
+ when [space v] key pressed
121
+ next backdrop
122
+ end
123
+ """,
124
+ """
125
+ when green flag clicked
126
+ repeat (10)
127
+ change size by (5)
128
+ wait (0.1) seconds
129
+ end
130
+ end
131
+ """,
132
+ """
133
+ when green flag clicked
134
+ set size to (50) %
135
+ wait (1) seconds
136
+ set size to (100) %
137
+ end
138
+ """,
139
+ """
140
+ when green flag clicked
141
+ forever
142
+ change [color v] effect by (5)
143
+ wait (0.1) seconds
144
+ end
145
+ end
146
+ """,
147
+ """
148
+ when green flag clicked
149
+ set [ghost v] effect to (75)
150
+ end
151
+ """,
152
+ """
153
+ when green flag clicked
154
+ change [color v] effect by (50)
155
+ wait (2) seconds
156
+ clear graphic effects
157
+ end
158
+ """,
159
+ """
160
+ when green flag clicked
161
+ hide
162
+ when I receive [start game v]
163
+ show
164
+ end
165
+ """,
166
+ """
167
+ when green flag clicked
168
+ hide
169
+ end
170
+ """,
171
+ """
172
+ when green flag clicked
173
+ go to [front v] layer
174
+ end
175
+ """,
176
+ """
177
+ when this sprite clicked
178
+ go [forward v] (1) layers
179
+ end
180
+ """,
181
+ """
182
+ say join [I am costume ] (costume [name v])
183
+ """,
184
+ """
185
+ say join [Current backdrop: ] (backdrop [name v]) for (2) seconds
186
+ """,
187
+ """
188
+ set size to ( (size) + (10) )
189
+ """,
190
+ # From sound_block.json
191
+ """
192
+ when backdrop switches to [winning screen v]
193
+ play sound [fanfare v] until done
194
+ say [You won!] for (2) seconds
195
+ end
196
+ """,
197
+ """
198
+ forever
199
+ play sound [Music v] until done
200
+ end
201
+ """,
202
+ """
203
+ when this sprite clicked
204
+ start sound [Pop v]
205
+ change [score v] by (1)
206
+ end
207
+ """,
208
+ """
209
+ when I receive [game over v]
210
+ stop all sounds
211
+ end
212
+ """,
213
+ """
214
+ when [down arrow v] key pressed
215
+ change volume by (-5)
216
+ end
217
+ """,
218
+ """
219
+ when green flag clicked
220
+ set volume to (50) %
221
+ end
222
+ """,
223
+ """
224
+ say join [Current volume: ] (volume)
225
+ """,
226
+ # From event_block.json
227
+ """
228
+ when green flag clicked
229
+ go to x: (0) y: (0)
230
+ say [Hello!] for (2) seconds
231
+ end
232
+ """,
233
+ """
234
+ when [space v] key pressed
235
+ repeat (10)
236
+ change y by (10)
237
+ wait (0.1) seconds
238
+ change y by (-10)
239
+ end
240
+ end
241
+ """,
242
+ """
243
+ when [right arrow v] key pressed
244
+ point in direction (90)
245
+ move (10) steps
246
+ end
247
+ """,
248
+ """
249
+ when this sprite clicked
250
+ say [Ouch!] for (1) seconds
251
+ change [score v] by (-1)
252
+ end
253
+ """,
254
+ """
255
+ when backdrop switches to [game over v]
256
+ stop [all v]
257
+ end
258
+ """,
259
+ """
260
+ when [loudness v] > (70)
261
+ start sound [scream v]
262
+ end
263
+ """,
264
+ """
265
+ when I receive [start game v]
266
+ show
267
+ go to x: (0) y: (0)
268
+ end
269
+ """,
270
+ """
271
+ when I receive [game over v]
272
+ set score to 0
273
+ stop [all v]
274
+ end
275
+ """,
276
+ """
277
+ if <key [space v] pressed?> then
278
+ broadcast [jump v]
279
+ end
280
+ """,
281
+ """
282
+ broadcast [initialize sprites v] and wait
283
+ say [Game Started!] for (2) seconds
284
+ """,
285
+ # From control_block.json
286
+ """
287
+ say [Hello!] for (1) seconds
288
+ wait (0.5) seconds
289
+ say [Goodbye!] for (1) seconds
290
+ """,
291
+ """
292
+ when green flag clicked
293
+ repeat (10)
294
+ move (10) steps
295
+ wait (0.1) seconds
296
+ end
297
+ end
298
+ """,
299
+ """
300
+ when green flag clicked
301
+ forever
302
+ move (5) steps
303
+ if on edge, bounce
304
+ end
305
+ end
306
+ """,
307
+ """
308
+ forever
309
+ if <touching [color (red) v]?> then
310
+ stop [this script v]
311
+ end
312
+ end
313
+ """,
314
+ """
315
+ if <(score) > (10)> then
316
+ say [You win!] for (2) seconds
317
+ else
318
+ say [Keep trying!] for (2) seconds
319
+ end
320
+ """,
321
+ """
322
+ repeat until <touching [edge v]?>
323
+ move (5) steps
324
+ end
325
+ """,
326
+ """
327
+ if <(health) = (0)> then
328
+ stop [all v]
329
+ end
330
+ """,
331
+ """
332
+ when I start as a clone
333
+ wait until <touching [edge v]?>
334
+ delete this clone
335
+ end
336
+ """,
337
+ """
338
+ when I start as a clone
339
+ go to x: (pick random -240 to 240) y: (pick random -180 to 180)
340
+ show
341
+ forever
342
+ move (10) steps
343
+ if on edge, bounce
344
+ end
345
+ end
346
+ """,
347
+ """
348
+ when I start as a clone
349
+ wait (5) seconds
350
+ delete this clone
351
+ end
352
+ """,
353
+ """
354
+ when green flag clicked
355
+ hide
356
+ forever
357
+ create clone of [myself v]
358
+ wait (1) seconds
359
+ end
360
+ """,
361
+ # From data_block.json
362
+ """
363
+ when green flag clicked
364
+ set [score v] to (0)
365
+ set [player name v] to [Guest]
366
+ end
367
+ """,
368
+ """
369
+ when this sprite clicked
370
+ change [score v] by (1)
371
+ end
372
+ """,
373
+ """
374
+ when green flag clicked
375
+ add [apple] to [shopping list v]
376
+ add [banana] to [shopping list v]
377
+ end
378
+ """,
379
+ """
380
+ when green flag clicked
381
+ delete (all) of [my list v]
382
+ end
383
+ """,
384
+ """
385
+ insert [orange] at (2) of [fruits v]
386
+ """,
387
+ """
388
+ replace item (1) of [colors v] with [blue]
389
+ """,
390
+ """
391
+ when green flag clicked
392
+ show variable [score v]
393
+ end
394
+ """,
395
+ """
396
+ when I receive [game over v]
397
+ hide variable [score v]
398
+ end
399
+ """,
400
+ """
401
+ when green flag clicked
402
+ show list [shopping list v]
403
+ end
404
+ """,
405
+ """
406
+ when I receive [game over v]
407
+ hide list [shopping list v]
408
+ end
409
+ """,
410
+ """
411
+ say ([score v]) for (2) seconds
412
+ """,
413
+ """
414
+ say ([my list v])
415
+ """,
416
+ """
417
+ say (item (2) of [myList v]) for 2 seconds
418
+ """,
419
+ """
420
+ say join (length of [shopping list v]) [ items in the list.]
421
+ """,
422
+ """
423
+ if <(item # of [Dog] in [myList v])> (0)> then
424
+ say join [Dog found at position ] (item # of [Dog] in [my list v])
425
+ end
426
+ """,
427
+ # From reporter_blocks.json (some already covered by other categories)
428
+ """
429
+ when green flag clicked
430
+ say (x position) for (2) seconds
431
+ end
432
+ """,
433
+ """
434
+ set [worms v] to (y position)
435
+ """,
436
+ """
437
+ when green flag clicked
438
+ say (direction) for (2) seconds
439
+ end
440
+ """,
441
+ """
442
+ say join [I am costume ] (costume [name v])
443
+ """,
444
+ """
445
+ set size to ( (size) + (10) )
446
+ """,
447
+ """
448
+ say join [Current backdrop: ] (backdrop [name v]) for (2) seconds
449
+ """,
450
+ """
451
+ say join [Current volume: ] (volume)
452
+ """,
453
+ """
454
+ if <(distance to [Sprite2 v]) < (50)> then
455
+ say [Too close!]
456
+ end
457
+ """,
458
+ """
459
+ ask [What is your name?] and wait
460
+ say join [Hello ] (answer)
461
+ """,
462
+ """
463
+ go to x: (mouse x) y: (mouse y)
464
+ """,
465
+ """
466
+ if <(mouse y) < (0)> then
467
+ say [Below center]
468
+ end
469
+ """,
470
+ """
471
+ when green flag clicked
472
+ forever
473
+ if <(loudness) > (30)> then
474
+ start sound [pop v]
475
+ end
476
+ """,
477
+ """
478
+ when green flag clicked
479
+ reset timer
480
+ wait (5) seconds
481
+ say join [Time elapsed: ] (timer)
482
+ end
483
+ """,
484
+ """
485
+ set [other sprite X v] to ( (x position) of [Sprite2 v] )
486
+ """,
487
+ """
488
+ say join [The current hour is ] (current [hour v])
489
+ """,
490
+ """
491
+ say join [Days passed: ] (days since 2000)
492
+ """,
493
+ """
494
+ say join [Hello, ] (username)
495
+ """,
496
+ """
497
+ set [total v] to ( (number 1) + (number 2) )
498
+ """,
499
+ """
500
+ set [difference v] to ( (number 1) - (number 2) )
501
+ """,
502
+ """
503
+ set [area v] to ( (length) * (width) )
504
+ """,
505
+ """
506
+ set [average v] to ( (total score) / (number of students) )
507
+ """,
508
+ """
509
+ go to x: (pick random -240 to 240) y: (pick random -180 to 180)
510
+ """,
511
+ """
512
+ say (join [Hello ][World!])
513
+ """,
514
+ """
515
+ say (letter (1) of [apple])
516
+ """,
517
+ """
518
+ say (length of [banana])
519
+ """,
520
+ """
521
+ if <([number v] mod (2) = (0))> then
522
+ say [Even number]
523
+ end
524
+ """,
525
+ """
526
+ set [rounded score v] to (round (score))
527
+ """,
528
+ """
529
+ set [distance v] to ([sqrt v] of ( ( (x position) * (x position) ) + ( (y position) * (y position) ) ))
530
+ """,
531
+ # From boolean_blocks.json (conditions already covered by parse_condition)
532
+ """
533
+ if <(score) < (10)> then
534
+ say [Keep trying!]
535
+ end
536
+ """,
537
+ """
538
+ if <(answer) = (5)> then
539
+ say [Correct!]
540
+ end
541
+ """,
542
+ """
543
+ if <([health v]) > (0)> then
544
+ move (10) steps
545
+ else
546
+ stop [all v]
547
+ end
548
+ """,
549
+ """
550
+ if <<mouse down?> and <touching [mouse-pointer]?> > then
551
+ say [You're clicking me!]
552
+ end
553
+ """,
554
+ """
555
+ if <<key [left arrow v] pressed?> or <key [a v] pressed?>> then
556
+ change x by (-10)
557
+ end
558
+ """,
559
+ """
560
+ if <not <touching [Sprite2 v]?>> then
561
+ say [I'm safe!]
562
+ end
563
+ """,
564
+ """
565
+ if <[answer] contains [yes]?> then
566
+ say [Great!]
567
+ end
568
+ """,
569
+ """
570
+ if <touching [Sprite v]?> then
571
+ broadcast [Game Over v]
572
+ end
573
+ """,
574
+ """
575
+ if <touching [edge v]?> then
576
+ bounce off edge
577
+ end
578
+ """,
579
+ """
580
+ if <touching color [#FF0000]?> then
581
+ change [health v] by (-1)
582
+ end
583
+ """,
584
+ """
585
+ if <color [#00FF00] is touching [#FF0000]?> then
586
+ say [Collision!]
587
+ end
588
+ """,
589
+ """
590
+ forever
591
+ if <key [space v] pressed?> then
592
+ broadcast [shoot v]
593
+ end
594
+ end
595
+ """,
596
+ """
597
+ if <mouse down?> then
598
+ go to mouse-pointer
599
+ end
600
+ """,
601
+ """
602
+ if <[inventory v] contains [key]?> then
603
+ say [You have the key!]
604
+ end
605
+ """,
606
+ # Custom block example
607
+ """
608
+ define jump (height)
609
+ change y by (height)
610
+ wait (0.5) seconds
611
+ change y by (0 - (height))
612
+ end
613
+
614
+ when green flag clicked
615
+ jump (50)
616
+ end
617
+ """
618
+ ]
utils/script_plan.py ADDED
@@ -0,0 +1,1449 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import copy
3
+ import re
4
+
5
+ def generate_blocks_from_opcodes(opcode_counts, all_block_definitions):
6
+ """
7
+ Generates a dictionary of Scratch-like blocks based on a list of opcodes and a reference block definition.
8
+ It now correctly links parent and menu blocks using their generated unique keys, and handles various block categories.
9
+ It ensures that menu blocks are only generated as children of their respective parent blocks.
10
+
11
+ Args:
12
+ opcode_counts (list): An array of objects, each with an 'opcode' and 'count' property.
13
+ Example: [{"opcode": "motion_gotoxy", "count": 1}]
14
+ all_block_definitions (dict): A comprehensive dictionary containing definitions for all block types.
15
+
16
+ Returns:
17
+ tuple: A tuple containing:
18
+ - dict: A JSON object where keys are generated block IDs and values are the block definitions.
19
+ - dict: The opcode_occurrences dictionary for consistent unique key generation across functions.
20
+ """
21
+ generated_blocks = {}
22
+ opcode_occurrences = {} # To keep track of how many times each opcode (main or menu) has been used for unique keys
23
+
24
+ # Define explicit parent-menu relationships for linking purposes
25
+ # This maps main_opcode -> list of (input_field_name, menu_opcode)
26
+ explicit_menu_links = {
27
+ "motion_goto": [("TO", "motion_goto_menu")],
28
+ "motion_glideto": [("TO", "motion_glideto_menu")],
29
+ "motion_pointtowards": [("TOWARDS", "motion_pointtowards_menu")],
30
+ "sensing_keypressed": [("KEY_OPTION", "sensing_keyoptions")],
31
+ "sensing_of": [("OBJECT", "sensing_of_object_menu")],
32
+ "sensing_touchingobject": [("TOUCHINGOBJECTMENU", "sensing_touchingobjectmenu")],
33
+ "control_create_clone_of": [("CLONE_OPTION", "control_create_clone_of_menu")],
34
+ "sound_play": [("SOUND_MENU", "sound_sounds_menu")],
35
+ "sound_playuntildone": [("SOUND_MENU", "sound_sounds_menu")],
36
+ "looks_switchcostumeto": [("COSTUME", "looks_costume")],
37
+ "looks_switchbackdropto": [("BACKDROP", "looks_backdrops")],
38
+ }
39
+
40
+ # --- Step 1: Process explicitly requested opcodes and generate their instances and associated menus ---
41
+ for item in opcode_counts:
42
+ opcode = item.get("opcode")
43
+ count = item.get("count", 1)
44
+
45
+ # Special handling for 'sensing_istouching' which maps to 'sensing_touchingobject'
46
+ if opcode == "sensing_istouching":
47
+ opcode = "sensing_touchingobject"
48
+
49
+ if not opcode:
50
+ print("Warning: Skipping item with missing 'opcode'.")
51
+ continue
52
+
53
+ if opcode not in all_block_definitions:
54
+ print(f"Warning: Opcode '{opcode}' not found in all_block_definitions. Skipping.")
55
+ continue
56
+
57
+ for _ in range(count):
58
+ # Increment occurrence count for the current main opcode
59
+ opcode_occurrences[opcode] = opcode_occurrences.get(opcode, 0) + 1
60
+ main_block_instance_num = opcode_occurrences[opcode]
61
+
62
+ main_block_unique_key = f"{opcode}_{main_block_instance_num}"
63
+
64
+ # Create a deep copy of the main block definition
65
+ main_block_data = copy.deepcopy(all_block_definitions[opcode])
66
+
67
+ # Set properties for a top-level main block
68
+ main_block_data["parent"] = None
69
+ main_block_data["next"] = None
70
+ main_block_data["topLevel"] = True
71
+ main_block_data["shadow"] = False # Main blocks are typically not shadows
72
+
73
+ generated_blocks[main_block_unique_key] = main_block_data
74
+
75
+ # If this main block has associated menus, generate and link them now
76
+ if opcode in explicit_menu_links:
77
+ for input_field_name, menu_opcode_type in explicit_menu_links[opcode]:
78
+ if menu_opcode_type in all_block_definitions:
79
+ # Increment the occurrence for the menu block type
80
+ opcode_occurrences[menu_opcode_type] = opcode_occurrences.get(menu_opcode_type, 0) + 1
81
+ menu_block_instance_num = opcode_occurrences[menu_opcode_type]
82
+
83
+ menu_block_data = copy.deepcopy(all_block_definitions[menu_opcode_type])
84
+
85
+ # Generate a unique key for this specific menu instance
86
+ menu_unique_key = f"{menu_opcode_type}_{menu_block_instance_num}"
87
+
88
+ # Set properties for a shadow menu block
89
+ menu_block_data["shadow"] = True
90
+ menu_block_data["topLevel"] = False
91
+ menu_block_data["next"] = None
92
+ menu_block_data["parent"] = main_block_unique_key # Link menu to its parent instance
93
+
94
+ # Update the main block's input to point to this unique menu instance
95
+ if input_field_name in main_block_data.get("inputs", {}) and \
96
+ isinstance(main_block_data["inputs"][input_field_name], list) and \
97
+ len(main_block_data["inputs"][input_field_name]) > 1 and \
98
+ main_block_data["inputs"][input_field_name][0] == 1:
99
+
100
+ main_block_data["inputs"][input_field_name][1] = menu_unique_key
101
+
102
+ generated_blocks[menu_unique_key] = menu_block_data
103
+
104
+ return generated_blocks, opcode_occurrences
105
+
106
+ def interpret_pseudo_code_and_update_blocks(generated_blocks_json, pseudo_code, all_block_definitions, opcode_occurrences):
107
+ """
108
+ Interprets pseudo-code to update the generated Scratch blocks, replacing static values
109
+ with dynamic values and establishing stacking/nesting logic.
110
+
111
+ Args:
112
+ generated_blocks_json (dict): The JSON object of pre-generated blocks.
113
+ pseudo_code (str): The pseudo-code string to interpret.
114
+ all_block_definitions (dict): A comprehensive dictionary containing definitions for all block types.
115
+ opcode_occurrences (dict): A dictionary to keep track of opcode occurrences for unique key generation.
116
+
117
+ Returns:
118
+ dict: The updated JSON object of Scratch blocks.
119
+ """
120
+ updated_blocks = copy.deepcopy(generated_blocks_json)
121
+
122
+ # Helper to create a new block instance (used for shadows/nested blocks)
123
+ def create_block_instance_for_parsing(opcode, parent_key=None, is_shadow=False, is_top_level=False):
124
+ opcode_occurrences[opcode] = opcode_occurrences.get(opcode, 0) + 1
125
+ unique_key = f"{opcode}_{opcode_occurrences[opcode]}"
126
+
127
+ new_block = copy.deepcopy(all_block_definitions.get(opcode, {}))
128
+ if not new_block:
129
+ print(f"Error: Definition for opcode '{opcode}' not found when creating instance for parsing.")
130
+ return None, None
131
+
132
+ new_block["parent"] = parent_key
133
+ new_block["next"] = None
134
+ new_block["topLevel"] = is_top_level
135
+ new_block["shadow"] = is_shadow
136
+
137
+ # Initialize inputs/fields to default empty values, but preserve structure
138
+ if "inputs" in new_block:
139
+ for input_name, input_data in new_block["inputs"].items():
140
+ if isinstance(input_data, list) and len(input_data) > 1:
141
+ if input_data[0] == 1: # Block reference
142
+ new_block["inputs"][input_name][1] = None # Placeholder for linked block ID
143
+ elif input_data[0] in [4, 5, 6, 7, 8, 9, 10]: # Literal types
144
+ new_block["inputs"][input_name][1] = "" # Default empty literal
145
+ if "fields" in new_block:
146
+ for field_name, field_data in new_block["fields"].items():
147
+ if isinstance(field_data, list) and len(field_data) > 0:
148
+ new_block["fields"][field_name][0] = "" # Default empty field value
149
+
150
+ updated_blocks[unique_key] = new_block
151
+ return unique_key, new_block
152
+
153
+ # Helper to parse input values from pseudo-code and create nested blocks if necessary
154
+ def parse_and_link_input(parent_block_key, input_type, pseudo_value, input_name_in_parent=None, field_name_in_parent=None):
155
+ pseudo_value = pseudo_value.strip()
156
+
157
+ # 1. Handle literal numbers (e.g., "2", "+1", "-135")
158
+ if re.fullmatch(r"[-+]?\d+(\.\d+)?", pseudo_value):
159
+ return [4, pseudo_value] # Type 4 for number literal
160
+
161
+ # 2. Handle literal strings (e.g., "Game Over") - often in quotes or simply text
162
+ # If it's a broadcast message, it's typically a string literal.
163
+ # If it's a 'say' message, it's a string literal.
164
+ # If it's a variable name, it's handled by specific patterns later.
165
+ if pseudo_value.startswith('"') and pseudo_value.endswith('"'):
166
+ return [10, pseudo_value.strip('"')] # Type 10 for string literal
167
+
168
+ # 3. Handle variable names (e.g., "[score v]", "[Sprite1 v]", "[Game Over v]")
169
+ # These can be fields or inputs that expect a variable reporter block.
170
+ var_match = re.match(r"\[(.+?) v\]", pseudo_value)
171
+ if var_match:
172
+ var_name = var_match.group(1).strip()
173
+ # If it's a field (like for set variable, show variable)
174
+ if field_name_in_parent:
175
+ return var_name # Return name, parent block will set its field
176
+ # If it's an input that expects a variable reporter (e.g., 'say (score)')
177
+ # Create a data_variable shadow block
178
+ var_reporter_key, var_reporter_data = create_block_instance_for_parsing(
179
+ "data_variable", parent_key=parent_block_key, is_shadow=True
180
+ )
181
+ if var_reporter_key:
182
+ var_reporter_data["fields"]["VARIABLE"] = [var_name, f"`var_{var_name}"] # Placeholder ID
183
+ return [1, var_reporter_key] # Type 1 for block reference
184
+ else:
185
+ print(f"Warning: Could not create data_variable block for '{var_name}'")
186
+ return [10, var_name] # Fallback to string literal
187
+
188
+ # 4. Handle nested reporter blocks (e.g., "(x position)")
189
+ reporter_match = re.match(r"\((.+?)\)", pseudo_value)
190
+ if reporter_match:
191
+ inner_content = reporter_match.group(1).strip()
192
+ # Check if it's a known reporter block (like "x position")
193
+ if inner_content in reporter_opcode_lookup:
194
+ reporter_opcode = reporter_opcode_lookup[inner_content]
195
+ reporter_block_key, reporter_block_data = create_block_instance_for_parsing(
196
+ reporter_opcode, parent_key=parent_block_key, is_shadow=True
197
+ )
198
+ if reporter_block_key:
199
+ return [1, reporter_block_key] # Type 1 for block reference
200
+ else:
201
+ print(f"Warning: Could not create reporter block for '{inner_content}'")
202
+ return [10, inner_content] # Fallback to string literal
203
+ else: # It's a literal number or string inside parentheses
204
+ return parse_and_link_input(parent_block_key, input_type, inner_content) # Recurse for inner content
205
+
206
+ # 5. Handle nested boolean blocks (e.g., "<(...) < (...)>")
207
+ boolean_match = re.match(r"<(.+?)>", pseudo_value)
208
+ if boolean_match:
209
+ inner_condition_str = boolean_match.group(1).strip()
210
+ # This is typically handled by the parent block's parsing (e.g., control_if)
211
+ # For now, if called directly, it implies a boolean reporter.
212
+ # We'll need specific logic for operator_lt, operator_and, etc.
213
+ # This part is complex and often handled by the parent block's specific regex.
214
+ # For this problem, the 'if' block's logic will create the boolean shadow.
215
+ print(f"Warning: Direct parsing of standalone boolean '{pseudo_value}' not fully supported here.")
216
+ return [10, pseudo_value] # Fallback to string literal
217
+
218
+ # Default to string literal if no other pattern matches
219
+ return [10, pseudo_value]
220
+
221
+ lines = [line.strip() for line in pseudo_code.strip().split('\n') if line.strip()]
222
+
223
+ # Track the current script and nesting
224
+ current_script_head = None
225
+ block_stack = [] # Stores (parent_block_key, indent_level, last_child_key_in_scope)
226
+
227
+ # Create a mapping from block name patterns to their opcodes and input/field details
228
+ # The 'input_map' keys are the internal Scratch input names, values are regex group indices.
229
+ # The 'field_map' keys are the internal Scratch field names, values are regex group indices.
230
+ # 'condition_opcode' is for 'if' blocks that take a specific boolean reporter.
231
+ pseudo_code_to_opcode_map = {
232
+ re.compile(r"when green flag clicked"): {"opcode": "event_whenflagclicked"},
233
+ re.compile(r"go to x: \((.+?)\) y: \((.+?)\)"): {"opcode": "motion_gotoxy", "input_map": {"X": 0, "Y": 1}},
234
+ re.compile(r"set \[(.+?) v\] to (.+)"): {"opcode": "data_setvariableto", "field_map": {"VARIABLE": 0}, "input_map": {"VALUE": 1}},
235
+ re.compile(r"show variable \[(.+?) v\]"): {"opcode": "data_showvariable", "field_map": {"VARIABLE": 0}},
236
+ re.compile(r"forever"): {"opcode": "control_forever"},
237
+ re.compile(r"glide \((.+?)\) seconds to x: \((.+?)\) y: \((.+?)\)"): {"opcode": "motion_glidesecstoxy", "input_map": {"SECS": 0, "X": 1, "Y": 2}},
238
+ re.compile(r"if <\((.+)\) < \((.+)\)> then"): {"opcode": "control_if", "condition_type": "operator_lt", "condition_input_map": {"OPERAND1": 0, "OPERAND2": 1}},
239
+ re.compile(r"set x to \((.+?)\)"): {"opcode": "motion_setx", "input_map": {"X": 0}},
240
+ re.compile(r"if <touching \[(.+?) v\]\?> then"): {"opcode": "control_if", "condition_type": "sensing_touchingobject", "condition_field_map": {"TOUCHINGOBJECTMENU": 0}},
241
+ re.compile(r"broadcast \[(.+?) v\]"): {"opcode": "event_broadcast", "input_map": {"BROADCAST_INPUT": 0}},
242
+ re.compile(r"stop \[(.+?) v\]"): {"opcode": "control_stop", "field_map": {"STOP_OPTION": 0}},
243
+ re.compile(r"end"): {"opcode": "end_block"}, # Special marker for script end/C-block end
244
+ }
245
+
246
+ # Create a reverse lookup for reporter block opcodes based on their pseudo-code representation
247
+ reporter_opcode_lookup = {}
248
+ for opcode, definition in all_block_definitions.items():
249
+ if definition.get("block_shape") == "Reporter Block":
250
+ block_name = definition.get("block_name")
251
+ if block_name:
252
+ # Clean up block name for matching: remove parentheses, 'v' for variable, etc.
253
+ clean_name = block_name.replace("(", "").replace(")", "").replace("[", "").replace("]", "").replace(" v", "").strip()
254
+ reporter_opcode_lookup[clean_name] = opcode
255
+ # Add specific entries for common reporters if their pseudo-code differs from clean_name
256
+ if opcode == "motion_xposition":
257
+ reporter_opcode_lookup["x position"] = opcode
258
+ elif opcode == "motion_yposition":
259
+ reporter_opcode_lookup["y position"] = opcode
260
+ # Add more as needed based on pseudo-code patterns
261
+
262
+ for line_idx, raw_line in enumerate(lines):
263
+ current_line_indent = len(raw_line) - len(raw_line.lstrip())
264
+ line = raw_line.strip()
265
+
266
+ # Adjust block_stack based on current indent level
267
+ while block_stack and current_line_indent <= block_stack[-1][1]:
268
+ block_stack.pop()
269
+
270
+ matched_block_info = None
271
+ matched_values = None
272
+
273
+ # Try to match the line against known block patterns
274
+ for pattern_regex, info in pseudo_code_to_opcode_map.items():
275
+ match = pattern_regex.match(line)
276
+ if match:
277
+ matched_block_info = info
278
+ matched_values = match.groups()
279
+ break
280
+
281
+ if not matched_block_info:
282
+ print(f"Warning: Could not interpret line: '{line}' at line {line_idx + 1}")
283
+ continue
284
+
285
+ opcode = matched_block_info["opcode"]
286
+
287
+ # Handle 'end' block separately as it signifies closing a C-block
288
+ if opcode == "end_block":
289
+ # This 'end' matches the most recent C-block on the stack.
290
+ # The while loop at the beginning of the iteration already handles popping.
291
+ continue
292
+
293
+ parent_key = None
294
+ if block_stack:
295
+ parent_key = block_stack[-1][0] # The last block on the stack is the parent
296
+
297
+ # Create the new block instance
298
+ new_block_key, new_block_data = create_block_instance_for_parsing(
299
+ opcode,
300
+ parent_key=parent_key,
301
+ is_top_level=(parent_key is None)
302
+ )
303
+ if not new_block_key:
304
+ continue
305
+
306
+ # Link to previous block in the same script/nesting level
307
+ if block_stack:
308
+ # Update the 'next' of the previous block in the current scope
309
+ last_child_key_in_scope = block_stack[-1][2] if len(block_stack[-1]) > 2 else None
310
+ if last_child_key_in_scope and last_child_key_in_scope in updated_blocks:
311
+ updated_blocks[last_child_key_in_scope]["next"] = new_block_key
312
+
313
+ # Update the last child in the current scope
314
+ block_stack[-1] = (block_stack[-1][0], block_stack[-1][1], new_block_key)
315
+
316
+ # Populate inputs and fields based on matched_block_info and matched_values
317
+ if matched_values:
318
+ # Handle fields
319
+ if "field_map" in matched_block_info:
320
+ for field_name, group_idx in matched_block_info["field_map"].items():
321
+ pseudo_field_value = matched_values[group_idx].replace(' v', '').strip()
322
+ if field_name == "VARIABLE":
323
+ # For variable fields, the actual variable ID is often derived or generated.
324
+ # For now, we use a placeholder and the name.
325
+ new_block_data["fields"][field_name] = [pseudo_field_value, f"`var_{pseudo_field_value}"]
326
+ elif field_name == "STOP_OPTION":
327
+ new_block_data["fields"][field_name] = [pseudo_field_value, None] # No ID needed for dropdown option
328
+ else:
329
+ new_block_data["fields"][field_name][0] = pseudo_field_value
330
+
331
+ # Handle inputs
332
+ if "input_map" in matched_block_info:
333
+ for input_name, group_idx in matched_block_info["input_map"].items():
334
+ pseudo_input_value = matched_values[group_idx].strip()
335
+
336
+ parsed_input_info = parse_and_link_input(new_block_key, new_block_data["inputs"][input_name][0], pseudo_input_value, input_name_in_parent=input_name)
337
+
338
+ if parsed_input_info:
339
+ if parsed_input_info[0] == 1: # It's a linked block (shadow reporter/boolean/menu)
340
+ new_block_data["inputs"][input_name][1] = parsed_input_info[1] # Link block ID
341
+ new_block_data["inputs"][input_name][0] = parsed_input_info[0] # Set type to 1 (block)
342
+ else: # It's a literal value
343
+ new_block_data["inputs"][input_name][1] = parsed_input_info[1]
344
+ new_block_data["inputs"][input_name][0] = parsed_input_info[0] # Set appropriate type (4 for number, 10 for string)
345
+
346
+ # Special handling for 'if' block conditions
347
+ if opcode == "control_if":
348
+ condition_type = matched_block_info.get("condition_type")
349
+ if condition_type:
350
+ condition_block_key, condition_block_data = create_block_instance_for_parsing(
351
+ condition_type, parent_key=new_block_key, is_shadow=True
352
+ )
353
+ if condition_block_key:
354
+ new_block_data["inputs"]["CONDITION"] = [2, condition_block_key] # Type 2 for boolean block reference
355
+
356
+ # Populate inputs for the condition block (e.g., operator_lt)
357
+ if "condition_input_map" in matched_block_info:
358
+ for cond_input_name, cond_group_idx in matched_block_info["condition_input_map"].items():
359
+ pseudo_cond_value = matched_values[cond_group_idx].strip()
360
+ parsed_cond_input_info = parse_and_link_input(condition_block_key, condition_block_data["inputs"][cond_input_name][0], pseudo_cond_value)
361
+ if parsed_cond_input_info:
362
+ if parsed_cond_input_info[0] == 1:
363
+ condition_block_data["inputs"][cond_input_name][1] = parsed_cond_input_info[1]
364
+ condition_block_data["inputs"][cond_input_name][0] = parsed_cond_input_info[0]
365
+ else:
366
+ condition_block_data["inputs"][cond_input_name][1] = parsed_cond_input_info[1]
367
+ condition_block_data["inputs"][cond_input_name][0] = parsed_cond_input_info[0]
368
+
369
+ # Populate fields for the condition block (e.g., sensing_touchingobject's menu)
370
+ if "condition_field_map" in matched_block_info:
371
+ for cond_field_name, cond_group_idx in matched_block_info["condition_field_map"].items():
372
+ pseudo_cond_field_value = matched_values[cond_group_idx].replace(' v', '').strip()
373
+ if cond_field_name == "TOUCHINGOBJECTMENU":
374
+ # Create the menu block for TOUCHINGOBJECTMENU
375
+ menu_opcode = "sensing_touchingobjectmenu"
376
+ menu_key, menu_data = create_block_instance_for_parsing(
377
+ menu_opcode,
378
+ parent_key=condition_block_key,
379
+ is_shadow=True
380
+ )
381
+ if menu_key:
382
+ condition_block_data["inputs"]["TOUCHINGOBJECTMENU"] = [1, menu_key] # Link to menu block
383
+ menu_data["fields"]["TOUCHINGOBJECTMENU"] = [pseudo_cond_field_value, None] # Set menu value
384
+ else:
385
+ print(f"Warning: Could not create menu block for touching object: '{pseudo_cond_field_value}'")
386
+ else:
387
+ condition_block_data["fields"][cond_field_name][0] = pseudo_cond_field_value
388
+ else:
389
+ print(f"Warning: Could not create condition block '{condition_type}' for 'if' statement.")
390
+
391
+
392
+ # For C-blocks, push onto stack to track nesting
393
+ # 'control_if' is a C-block, but its 'next' is inside its substack.
394
+ # 'control_forever' is also a C-block.
395
+ if all_block_definitions[opcode].get("block_shape") == "C-Block" and opcode != "control_if":
396
+ # For C-blocks, the 'next' of the parent is usually null, and children start in 'SUBSTACK'
397
+ # We add the block to the stack, and its "next" will be its first child.
398
+ # The 'next' of the *last child* in its substack will point to the block after the C-block.
399
+ block_stack.append((new_block_key, current_line_indent, None)) # (parent_key, indent, last_child_key_in_substack)
400
+
401
+ # For C-blocks, the first child is linked via the 'SUBSTACK' input
402
+ new_block_data["inputs"]["SUBSTACK"] = [2, None] # Placeholder for the first child block ID
403
+
404
+ # For 'if' blocks, the 'next' of the parent is usually null, and children start in 'SUBSTACK'
405
+ if opcode == "control_if":
406
+ block_stack.append((new_block_key, current_line_indent, None))
407
+ new_block_data["inputs"]["SUBSTACK"] = [2, None] # Placeholder for the first child block ID
408
+
409
+
410
+ # Final pass to ensure topLevel is correctly set for the very first block of a script
411
+ for key, block in updated_blocks.items():
412
+ if block.get("parent") is None and block.get("next") is not None:
413
+ block["topLevel"] = True
414
+ elif block.get("parent") is None and block.get("next") is None and block.get("opcode") == "event_whenflagclicked":
415
+ block["topLevel"] = True # Ensure hat blocks are always topLevel
416
+
417
+ return updated_blocks
418
+
419
+ # --- Consolidated Block Definitions from all provided JSONs ---
420
+ # This dictionary should contain ALL block definitions from your JSON files.
421
+ # I'm using the provided definitions from the previous turn.
422
+ all_block_definitions = {
423
+ # motion_block.json
424
+ "motion_movesteps": {
425
+ "block_name": "move () steps", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_movesteps",
426
+ "functionality": "Moves the sprite forward by the specified number of steps in the direction it is currently facing. A positive value moves it forward, and a negative value moves it backward.",
427
+ "inputs": {"STEPS": [1, [4, "10"]]}, "fields": {}, "shadow": False, "topLevel": True
428
+ },
429
+ "motion_turnright": {
430
+ "block_name": "turn right () degrees", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_turnright",
431
+ "functionality": "Turns the sprite clockwise by the specified number of degrees.",
432
+ "inputs": {"DEGREES": [1, [4, "15"]]}, "fields": {}, "shadow": False, "topLevel": True
433
+ },
434
+ "motion_turnleft": {
435
+ "block_name": "turn left () degrees", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_turnleft",
436
+ "functionality": "Turns the sprite counter-clockwise by the specified number of degrees.",
437
+ "inputs": {"DEGREES": [1, [4, "15"]]}, "fields": {}, "shadow": False, "topLevel": True
438
+ },
439
+ "motion_goto": {
440
+ "block_name": "go to ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_goto",
441
+ "functionality": "Moves the sprite to a specified location, which can be a random position or at the mouse pointer or another to the sprite.",
442
+ "inputs": {"TO": [1, "motion_goto_menu"]}, "fields": {}, "shadow": False, "topLevel": True
443
+ },
444
+ "motion_goto_menu": {
445
+ "block_name": "go to menu", "block_type": "Motion", "block_shape": "Reporter Block", "op_code": "motion_goto_menu",
446
+ "functionality": "Menu for go to block.",
447
+ "inputs": {}, "fields": {"TO": ["_random_", None]}, "shadow": True, "topLevel": False
448
+ },
449
+ "motion_gotoxy": {
450
+ "block_name": "go to x: () y: ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_gotoxy",
451
+ "functionality": "Moves the sprite to the specified X and Y coordinates on the stage.",
452
+ "inputs": {"X": [1, [4, "0"]], "Y": [1, [4, "0"]]}, "fields": {}, "shadow": False, "topLevel": True
453
+ },
454
+ "motion_glideto": {
455
+ "block_name": "glide () secs to ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_glideto",
456
+ "functionality": "Glides the sprite smoothly to a specified location (random position, mouse pointer, or another sprite) over a given number of seconds.",
457
+ "inputs": {"SECS": [1, [4, "1"]], "TO": [1, "motion_glideto_menu"]}, "fields": {}, "shadow": False, "topLevel": True
458
+ },
459
+ "motion_glideto_menu": {
460
+ "block_name": "glide to menu", "block_type": "Motion", "block_shape": "Reporter Block", "op_code": "motion_glideto_menu",
461
+ "functionality": "Menu for glide to block.",
462
+ "inputs": {}, "fields": {"TO": ["_random_", None]}, "shadow": True, "topLevel": False
463
+ },
464
+ "motion_glidesecstoxy": {
465
+ "block_name": "glide () secs to x: () y: ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_glidesecstoxy",
466
+ "functionality": "Glides the sprite smoothly to the specified X and Y coordinates over a given number of seconds.",
467
+ "inputs": {"SECS": [1, [4, "1"]], "X": [1, [4, "0"]], "Y": [1, [4, "0"]]}, "fields": {}, "shadow": False, "topLevel": True
468
+ },
469
+ "motion_pointindirection": {
470
+ "block_name": "point in direction ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_pointindirection",
471
+ "functionality": "Sets the sprite's direction to a specified angle in degrees (0 = up, 90 = right, 180 = down, -90 = left).",
472
+ "inputs": {"DIRECTION": [1, [8, "90"]]}, "fields": {}, "shadow": False, "topLevel": True
473
+ },
474
+ "motion_pointtowards": {
475
+ "block_name": "point towards ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_pointtowards",
476
+ "functionality": "Points the sprite towards the mouse pointer or another specified sprite.",
477
+ "inputs": {"TOWARDS": [1, "motion_pointtowards_menu"]}, "fields": {}, "shadow": False, "topLevel": True
478
+ },
479
+ "motion_pointtowards_menu": {
480
+ "block_name": "point towards menu", "block_type": "Motion", "block_shape": "Reporter Block", "op_code": "motion_pointtowards_menu",
481
+ "functionality": "Menu for point towards block.",
482
+ "inputs": {}, "fields": {"TOWARDS": ["_mouse_", None]}, "shadow": True, "topLevel": False
483
+ },
484
+ "motion_changexby": {
485
+ "block_name": "change x by ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_changexby",
486
+ "functionality": "Changes the sprite's X-coordinate by the specified amount, moving it horizontally.",
487
+ "inputs": {"DX": [1, [4, "10"]]}, "fields": {}, "shadow": False, "topLevel": True
488
+ },
489
+ "motion_setx": {
490
+ "block_name": "set x to ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_setx",
491
+ "functionality": "Sets the sprite's X-coordinate to a specific value, placing it at a precise horizontal position.",
492
+ "inputs": {"X": [1, [4, "0"]]}, "fields": {}, "shadow": False, "topLevel": True
493
+ },
494
+ "motion_changeyby": {
495
+ "block_name": "change y by ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_changeyby",
496
+ "functionality": "Changes the sprite's Y-coordinate by the specified amount, moving it vertically.",
497
+ "inputs": {"DY": [1, [4, "10"]]}, "fields": {}, "shadow": False, "topLevel": True
498
+ },
499
+ "motion_sety": {
500
+ "block_name": "set y to ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_sety",
501
+ "functionality": "Sets the sprite's Y-coordinate to a specific value, placing it at a precise vertical position.",
502
+ "inputs": {"Y": [1, [4, "0"]]}, "fields": {}, "shadow": False, "topLevel": True
503
+ },
504
+ "motion_ifonedgebounce": {
505
+ "block_name": "if on edge, bounce", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_ifonedgebounce",
506
+ "functionality": "Reverses the sprite's direction if it touches the edge of the stage.",
507
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
508
+ },
509
+ "motion_setrotationstyle": {
510
+ "block_name": "set rotation style ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_setrotationstyle",
511
+ "functionality": "Determines how the sprite rotates: 'left-right' (flips horizontally), 'don't rotate' (stays facing one direction), or 'all around' (rotates freely).",
512
+ "inputs": {}, "fields": {"STYLE": ["left-right", None]}, "shadow": False, "topLevel": True
513
+ },
514
+ "motion_xposition": {
515
+ "block_name": "(x position)", "block_type": "Motion", "block_shape": "Reporter Block", "op_code": "motion_xposition",
516
+ "functionality": "Reports the current X-coordinate of the sprite.[NOTE: not used in stage/backdrops]",
517
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
518
+ },
519
+ "motion_yposition": {
520
+ "block_name": "(y position)", "block_type": "Motion", "block_shape": "Reporter Block", "op_code": "motion_yposition",
521
+ "functionality": "Reports the current Y coordinate of the sprite on the stage.[NOTE: not used in stage/backdrops]",
522
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
523
+ },
524
+ "motion_direction": {
525
+ "block_name": "(direction)", "block_type": "Motion", "block_shape": "Reporter Block", "op_code": "motion_direction",
526
+ "functionality": "Reports the current direction of the sprite in degrees (0 = up, 90 = right, 180 = down, -90 = left).[NOTE: not used in stage/backdrops]",
527
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
528
+ },
529
+
530
+ # control_block.json
531
+ "control_wait": {
532
+ "block_name": "wait () seconds", "block_type": "Control", "block_shape": "Stack Block", "op_code": "control_wait",
533
+ "functionality": "Pauses the script for a specified duration.",
534
+ "inputs": {"DURATION": [1, [5, "1"]]}, "fields": {}, "shadow": False, "topLevel": True
535
+ },
536
+ "control_repeat": {
537
+ "block_name": "repeat ()", "block_type": "Control", "block_shape": "C-Block", "op_code": "control_repeat",
538
+ "functionality": "Repeats the blocks inside it a specified number of times.",
539
+ "inputs": {"TIMES": [1, [6, "10"]], "SUBSTACK": [2, None]}, "fields": {}, "shadow": False, "topLevel": True
540
+ },
541
+ "control_forever": {
542
+ "block_name": "forever", "block_type": "Control", "block_shape": "C-Block", "op_code": "control_forever",
543
+ "functionality": "Continuously runs the blocks inside it.",
544
+ "inputs": {"SUBSTACK": [2, None]}, "fields": {}, "shadow": False, "topLevel": True
545
+ },
546
+ "control_if": {
547
+ "block_name": "if <> then", "block_type": "Control", "block_shape": "C-Block", "op_code": "control_if",
548
+ "functionality": "Executes the blocks inside it only if the specified boolean condition is true. [NOTE: it takes boolean blocks as input]",
549
+ "inputs": {"CONDITION": [2, None], "SUBSTACK": [2, None]}, "fields": {}, "shadow": False, "topLevel": True
550
+ },
551
+ "control_if_else": {
552
+ "block_name": "if <> then else", "block_type": "Control", "block_shape": "C-Block", "op_code": "control_if_else",
553
+ "functionality": "Executes one set of blocks if the specified boolean condition is true, and a different set of blocks if the condition is false. [NOTE: it takes boolean blocks as input]",
554
+ "inputs": {"CONDITION": [2, None], "SUBSTACK": [2, None], "SUBSTACK2": [2, None]}, "fields": {}, "shadow": False, "topLevel": True
555
+ },
556
+ "control_wait_until": {
557
+ "block_name": "wait until <>", "block_type": "Control", "block_shape": "Stack Block", "op_code": "control_wait_until",
558
+ "functionality": "Pauses the script until the specified boolean condition becomes true. [NOTE: it takes boolean blocks as input]",
559
+ "inputs": {"CONDITION": [2, None]}, "fields": {}, "shadow": False, "topLevel": True
560
+ },
561
+ "control_repeat_until": {
562
+ "block_name": "repeat until <>", "block_type": "Control", "block_shape": "C-Block", "op_code": "control_repeat_until",
563
+ "functionality": "Repeats the blocks inside it until the specified boolean condition becomes true. [NOTE: it takes boolean blocks as input]",
564
+ "inputs": {"CONDITION": [2, None], "SUBSTACK": [2, None]}, "fields": {}, "shadow": False, "topLevel": True
565
+ },
566
+ "control_stop": {
567
+ "block_name": "stop [v]", "block_type": "Control", "block_shape": "Cap Block", "op_code": "control_stop",
568
+ "functionality": "Halts all scripts, only the current script, or other scripts within the same sprite. Its shape can dynamically change based on the selected option.",
569
+ "inputs": {}, "fields": {"STOP_OPTION": ["all", None]}, "shadow": False, "topLevel": True, "mutation": {"tagName": "mutation", "children": [], "hasnext": "false"}
570
+ },
571
+ "control_start_as_clone": {
572
+ "block_name": "When I Start as a Clone", "block_type": "Control", "block_shape": "Hat Block", "op_code": "control_start_as_clone",
573
+ "functionality": "This Hat block initiates the script when a clone of the sprite is created. It defines the behavior of individual clones.",
574
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
575
+ },
576
+ "control_create_clone_of": {
577
+ "block_name": "create clone of ()", "block_type": "Control", "block_shape": "Stack Block", "op_code": "control_create_clone_of",
578
+ "functionality": "Generates a copy, or clone, of a specified sprite (or 'myself' for the current sprite).",
579
+ "inputs": {"CLONE_OPTION": [1, "control_create_clone_of_menu"]}, "fields": {}, "shadow": False, "topLevel": True
580
+ },
581
+ "control_create_clone_of_menu": {
582
+ "block_name": "create clone of menu", "block_type": "Control", "block_shape": "Reporter Block", "op_code": "control_create_clone_of_menu",
583
+ "functionality": "Menu for create clone of block.",
584
+ "inputs": {}, "fields": {"CLONE_OPTION": ["_myself_", None]}, "shadow": True, "topLevel": False
585
+ },
586
+ "control_delete_this_clone": {
587
+ "block_name": "delete this clone", "block_type": "Control", "block_shape": "Cap Block", "op_code": "control_delete_this_clone",
588
+ "functionality": "Removes the clone that is executing it from the stage.",
589
+ "inputs":None, "fields": {}, "shadow": False, "topLevel": True
590
+ },
591
+
592
+ # data_block.json
593
+ "data_setvariableto": {
594
+ "block_name": "set [my variable v] to ()", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_setvariableto",
595
+ "functionality": "Assigns a specific value (number, string, or boolean) to a variable.",
596
+ "inputs": {"VALUE": [1, [10, "0"]]}, "fields": {"VARIABLE": ["my variable", "`jEk@4|i[#Fk?(8x)AV.-my variable"]}, "shadow": False, "topLevel": True
597
+ },
598
+ "data_changevariableby": {
599
+ "block_name": "change [my variable v] by ()", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_changevariableby",
600
+ "functionality": "Increases or decreases a variable's numerical value by a specified amount.",
601
+ "inputs": {"VALUE": [1, [4, "1"]]}, "fields": {"VARIABLE": ["my variable", "`jEk@4|i[#Fk?(8x)AV.-my variable"]}, "shadow": False, "topLevel": True
602
+ },
603
+ "data_showvariable": {
604
+ "block_name": "show variable [my variable v]", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_showvariable",
605
+ "functionality": "Makes a variable's monitor visible on the stage.",
606
+ "inputs": {}, "fields": {"VARIABLE": ["my variable", "`jEk@4|i[#Fk?(8x)AV.-my variable"]}, "shadow": False, "topLevel": True
607
+ },
608
+ "data_hidevariable": {
609
+ "block_name": "hide variable [my variable v]", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_hidevariable",
610
+ "functionality": "Hides a variable's monitor from the stage.",
611
+ "inputs": {}, "fields": {"VARIABLE": ["my variable", "`jEk@4|i[#Fk?(8x)AV.-my variable"]}, "shadow": False, "topLevel": True
612
+ },
613
+ "data_addtolist": {
614
+ "block_name": "add () to [my list v]", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_addtolist",
615
+ "functionality": "Appends an item to the end of a list.",
616
+ "inputs": {"ITEM": [1, [10, "thing"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True
617
+ },
618
+ "data_deleteoflist": {
619
+ "block_name": "delete () of [my list v]", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_deleteoflist",
620
+ "functionality": "Removes an item from a list by its index or by selecting 'all' items.",
621
+ "inputs": {"INDEX": [1, [7, "1"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True
622
+ },
623
+ "data_deletealloflist": {
624
+ "block_name": "delete all of [my list v]", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_deletealloflist",
625
+ "functionality": "Removes all items from a list.",
626
+ "inputs": {}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True
627
+ },
628
+ "data_insertatlist": {
629
+ "block_name": "insert () at () of [my list v]", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_insertatlist",
630
+ "functionality": "Inserts an item at a specific position within a list.",
631
+ "inputs": {"ITEM": [1, [10, "thing"]], "INDEX": [1, [7, "1"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True
632
+ },
633
+ "data_replaceitemoflist": {
634
+ "block_name": "replace item () of [my list v] with ()", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_replaceitemoflist",
635
+ "functionality": "Replaces an item at a specific position in a list with a new value.",
636
+ "inputs": {"INDEX": [1, [7, "1"]], "ITEM": [1, [10, "thing"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True
637
+ },
638
+ "data_itemoflist": {
639
+ "block_name": "(item (2) of [myList v])", "block_type": "Data", "block_shape": "Reporter Block", "op_code": "data_itemoflist",
640
+ "functionality": "Reports the item located at a specific position in a list.",
641
+ "inputs": {"INDEX": [1, [7, "1"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True
642
+ },
643
+ "data_itemnumoflist": {
644
+ "block_name": "(item # of [Dog] in [myList v])", "block_type": "Data", "block_shape": "Reporter Block", "op_code": "data_itemnumoflist",
645
+ "functionality": "Reports the index number of the first occurrence of a specified item in a list. If the item is not found, it reports 0.",
646
+ "inputs": {"ITEM": [1, [10, "thing"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True
647
+ },
648
+ "data_lengthoflist": {
649
+ "block_name": "(length of [myList v])", "block_type": "Data", "block_shape": "Reporter Block", "op_code": "data_lengthoflist",
650
+ "functionality": "Provides the total number of items contained in a list.",
651
+ "inputs": {}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True
652
+ },
653
+ "data_listcontainsitem": {
654
+ "block_name": "<[my list v] contains ()?>", "block_type": "Data", "block_shape": "Boolean Block", "op_code": "data_listcontainsitem",
655
+ "functionality": "Checks if a list includes a specific item.",
656
+ "inputs": {"ITEM": [1, [10, "thing"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True
657
+ },
658
+ "data_showlist": {
659
+ "block_name": "show list [my list v]", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_showlist",
660
+ "functionality": "Makes a list's monitor visible on the stage.",
661
+ "inputs": {}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True
662
+ },
663
+ "data_hidelist": {
664
+ "block_name": "hide list [my list v]", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_hidelist",
665
+ "functionality": "Hides a list's monitor from the stage.",
666
+ "inputs": {}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True
667
+ },
668
+ "data_variable": { # This is a reporter block for a variable's value
669
+ "block_name": "[variable v]", "block_type": "Data", "block_shape": "Reporter Block", "op_code": "data_variable",
670
+ "functionality": "Provides the current value stored in a variable.",
671
+ "inputs": {}, "fields": {"VARIABLE": ["my variable", None]}, "shadow": True, "topLevel": False
672
+ },
673
+
674
+ # event_block.json
675
+ "event_whenflagclicked": {
676
+ "block_name": "when green flag pressed", "block_type": "Events", "op_code": "event_whenflagclicked", "block_shape": "Hat Block",
677
+ "functionality": "This Hat block initiates the script when the green flag is clicked, serving as the common starting point for most Scratch projects.",
678
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
679
+ },
680
+ "event_whenkeypressed": {
681
+ "block_name": "when () key pressed", "block_type": "Events", "op_code": "event_whenkeypressed", "block_shape": "Hat Block",
682
+ "functionality": "This Hat block initiates the script when a specified keyboard key is pressed.",
683
+ "inputs": {}, "fields": {"KEY_OPTION": ["space", None]}, "shadow": False, "topLevel": True
684
+ },
685
+ "event_whenthisspriteclicked": {
686
+ "block_name": "when this sprite clicked", "block_type": "Events", "op_code": "event_whenthisspriteclicked", "block_shape": "Hat Block",
687
+ "functionality": "This Hat block starts the script when the sprite itself is clicked.",
688
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
689
+ },
690
+ "event_whenbackdropswitchesto": {
691
+ "block_name": "when backdrop switches to ()", "block_type": "Events", "op_code": "event_whenbackdropswitchesto", "block_shape": "Hat Block",
692
+ "functionality": "This Hat block triggers the script when the stage backdrop changes to a specified backdrop.",
693
+ "inputs": {}, "fields": {"BACKDROP": ["backdrop1", None]}, "shadow": False, "topLevel": True
694
+ },
695
+ "event_whengreaterthan": {
696
+ "block_name": "when () > ()", "block_type": "Events", "op_code": "event_whengreaterthan", "block_shape": "Hat Block",
697
+ "functionality": "This Hat block starts the script when a certain value (e.g., loudness from a microphone, or the timer) exceeds a defined threshold.",
698
+ "inputs": {"VALUE": [1, [4, "10"]]}, "fields": {"WHENGREATERTHANMENU": ["LOUDNESS", None]}, "shadow": False, "topLevel": True
699
+ },
700
+ "event_whenbroadcastreceived": {
701
+ "block_name": "when I receive ()", "block_type": "Events", "op_code": "event_whenbroadcastreceived", "block_shape": "Hat Block",
702
+ "functionality": "This Hat block initiates the script upon the reception of a specific broadcast message. This mechanism facilitates indirect communication between sprites or the stage.",
703
+ "inputs": {}, "fields": {"BROADCAST_OPTION": ["message1", "5O!nei;S$!c!=hCT}0:a"]}, "shadow": False, "topLevel": True
704
+ },
705
+ "event_broadcast": {
706
+ "block_name": "broadcast ()", "block_type": "Events", "block_shape": "Stack Block", "op_code": "event_broadcast",
707
+ "functionality": "Sends a broadcast message throughout the Scratch program, activating any 'when I receive ()' blocks that are set to listen for that message, enabling indirect communication.",
708
+ "inputs": {"BROADCAST_INPUT": [1, [11, "message1", "5O!nei;S$!c!=hCT}0:a"]]}, "fields": {}, "shadow": False, "topLevel": True
709
+ },
710
+ "event_broadcastandwait": {
711
+ "block_name": "broadcast () and wait", "block_type": "Events", "block_shape": "Stack Block", "op_code": "event_broadcastandwait",
712
+ "functionality": "Sends a broadcast message and pauses the current script until all other scripts activated by that broadcast have completed their execution, ensuring sequential coordination.",
713
+ "inputs": {"BROADCAST_INPUT": [1, [11, "message1", "5O!nei;S$!c!=hCT}0:a"]]}, "fields": {}, "shadow": False, "topLevel": True
714
+ },
715
+
716
+ # looks_block.json
717
+ "looks_sayforsecs": {
718
+ "block_name": "say () for () seconds", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_sayforsecs",
719
+ "functionality": "Displays a speech bubble containing specified text for a set duration.",
720
+ "inputs": {"MESSAGE": [1, [10, "Hello!"]], "SECS": [1, [4, "2"]]}, "fields": {}, "shadow": False, "topLevel": True
721
+ },
722
+ "looks_say": {
723
+ "block_name": "say ()", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_say",
724
+ "functionality": "Displays a speech bubble with the specified text indefinitely until another 'say' or 'think' block is activated.",
725
+ "inputs": {"MESSAGE": [1, [10, "Hello!"]]}, "fields": {}, "shadow": False, "topLevel": True
726
+ },
727
+ "looks_thinkforsecs": {
728
+ "block_name": "think () for () seconds", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_thinkforsecs",
729
+ "functionality": "Displays a thought bubble containing specified text for a set duration.",
730
+ "inputs": {"MESSAGE": [1, [10, "Hmm..."]], "SECS": [1, [4, "2"]]}, "fields": {}, "shadow": False, "topLevel": True
731
+ },
732
+ "looks_think": {
733
+ "block_name": "think ()", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_think",
734
+ "functionality": "Displays a thought bubble with the specified text indefinitely until another 'say' or 'think' block is activated.",
735
+ "inputs": {"MESSAGE": [1, [10, "Hmm..."]]}, "fields": {}, "shadow": False, "topLevel": True
736
+ },
737
+ "looks_switchcostumeto": {
738
+ "block_name": "switch costume to ()", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_switchcostumeto",
739
+ "functionality": "Alters the sprite's appearance to a designated costume.",
740
+ "inputs": {"COSTUME": [1, "looks_costume"]}, "fields": {}, "shadow": False, "topLevel": True
741
+ },
742
+ "looks_costume": {
743
+ "block_name": "costume menu", "block_type": "Looks", "block_shape": "Reporter Block", "op_code": "looks_costume",
744
+ "functionality": "Menu for switch costume to block.",
745
+ "inputs": {}, "fields": {"COSTUME": ["costume1", None]}, "shadow": True, "topLevel": False
746
+ },
747
+ "looks_nextcostume": {
748
+ "block_name": "next costume", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_nextcostume",
749
+ "functionality": "Switches the sprite's costume to the next one in its costume list. If it's the last costume, it cycles back to the first.",
750
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
751
+ },
752
+ "looks_switchbackdropto": {
753
+ "block_name": "switch backdrop to ()", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_switchbackdropto",
754
+ "functionality": "Changes the stage's backdrop to a specified backdrop.",
755
+ "inputs": {"BACKDROP": [1, "looks_backdrops"]}, "fields": {}, "shadow": False, "topLevel": True
756
+ },
757
+ "looks_backdrops": {
758
+ "block_name": "backdrop menu", "block_type": "Looks", "block_shape": "Reporter Block", "op_code": "looks_backdrops",
759
+ "functionality": "Menu for switch backdrop to block.",
760
+ "inputs": {}, "fields": {"BACKDROP": ["backdrop1", None]}, "shadow": True, "topLevel": False
761
+ },
762
+ "looks_switchbackdroptowait": {
763
+ "block_name": "switch backdrop to () and wait", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_switchbackdroptowait",
764
+ "functionality": "Changes the stage's backdrop to a specified backdrop and pauses the script until any 'When backdrop switches to' scripts for that backdrop have finished.",
765
+ "inputs": {"BACKDROP": [1, "looks_backdrops"]}, "fields": {}, "shadow": False, "topLevel": True
766
+ },
767
+ "looks_nextbackdrop": {
768
+ "block_name": "next backdrop", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_nextbackdrop",
769
+ "functionality": "Switches the stage's backdrop to the next one in its backdrop list. If it's the last backdrop, it cycles back to the first.",
770
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
771
+ },
772
+ "looks_changesizeby": {
773
+ "block_name": "change size by ()", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_changesizeby",
774
+ "functionality": "Changes the sprite's size by a specified percentage. Positive values make it larger, negative values make it smaller.",
775
+ "inputs": {"CHANGE": [1, [4, "10"]]}, "fields": {}, "shadow": False, "topLevel": True
776
+ },
777
+ "looks_setsizeto": {
778
+ "block_name": "set size to () %", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_setsizeto",
779
+ "functionality": "Sets the sprite's size to a specific percentage of its original size.",
780
+ "inputs": {"SIZE": [1, [4, "100"]]}, "fields": {}, "shadow": False, "topLevel": True
781
+ },
782
+ "looks_changeeffectby": {
783
+ "block_name": "change () effect by ()", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_changeeffectby",
784
+ "functionality": "Changes a visual effect on the sprite by a specified amount (e.g., color, fisheye, whirl, pixelate, mosaic, brightness, ghost).",
785
+ "inputs": {"CHANGE": [1, [4, "25"]]}, "fields": {"EFFECT": ["COLOR", None]}, "shadow": False, "topLevel": True
786
+ },
787
+ "looks_seteffectto": {
788
+ "block_name": "set () effect to ()", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_seteffectto",
789
+ "functionality": "Sets a visual effect on the sprite to a specific value.",
790
+ "inputs": {"VALUE": [1, [4, "0"]]}, "fields": {"EFFECT": ["COLOR", None]}, "shadow": False, "topLevel": True
791
+ },
792
+ "looks_cleargraphiceffects": {
793
+ "block_name": "clear graphic effects", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_cleargraphiceffects",
794
+ "functionality": "Removes all visual effects applied to the sprite.",
795
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
796
+ },
797
+ "looks_show": {
798
+ "block_name": "show", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_show",
799
+ "functionality": "Makes the sprite visible on the stage.",
800
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
801
+ },
802
+ "looks_hide": {
803
+ "block_name": "hide", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_hide",
804
+ "functionality": "Makes the sprite invisible on the stage.",
805
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
806
+ },
807
+ "looks_gotofrontback": {
808
+ "block_name": "go to () layer", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_gotofrontback",
809
+ "functionality": "Moves the sprite to the front-most or back-most layer of other sprites on the stage.",
810
+ "inputs": {}, "fields": {"FRONT_BACK": ["front", None]}, "shadow": False, "topLevel": True
811
+ },
812
+ "looks_goforwardbackwardlayers": {
813
+ "block_name": "go () layers", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_goforwardbackwardlayers",
814
+ "functionality": "Moves the sprite forward or backward a specified number of layers in relation to other sprites.",
815
+ "inputs": {"NUM": [1, [7, "1"]]}, "fields": {"FORWARD_BACKWARD": ["forward", None]}, "shadow": False, "topLevel": True
816
+ },
817
+ "looks_costumenumbername": {
818
+ "block_name": "(costume ())", "block_type": "Looks", "block_shape": "Reporter Block", "op_code": "looks_costumenumbername",
819
+ "functionality": "Reports the current costume's number or name.",
820
+ "inputs": {}, "fields": {"NUMBER_NAME": ["number", None]}, "shadow": False, "topLevel": True
821
+ },
822
+ "looks_backdropnumbername": {
823
+ "block_name": "(backdrop ())", "block_type": "Looks", "block_shape": "Reporter Block", "op_code": "looks_backdropnumbername",
824
+ "functionality": "Reports the current backdrop's number or name.",
825
+ "inputs": {}, "fields": {"NUMBER_NAME": ["number", None]}, "shadow": False, "topLevel": True
826
+ },
827
+ "looks_size": {
828
+ "block_name": "(size)", "block_type": "Looks", "block_shape": "Reporter Block", "op_code": "looks_size",
829
+ "functionality": "Reports the current size of the sprite as a percentage.",
830
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
831
+ },
832
+
833
+ # operator_block.json
834
+ "operator_add": {
835
+ "block_name": "(() + ())", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_add",
836
+ "functionality": "Adds two numerical values.",
837
+ "inputs": {"NUM1": [1, [4, ""]], "NUM2": [1, [4, ""]]}, "fields": {}, "shadow": False, "topLevel": True
838
+ },
839
+ "operator_subtract": {
840
+ "block_name": "(() - ())", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_subtract",
841
+ "functionality": "Subtracts the second numerical value from the first.",
842
+ "inputs": {"NUM1": [1, [4, ""]], "NUM2": [1, [4, ""]]}, "fields": {}, "shadow": False, "topLevel": True
843
+ },
844
+ "operator_multiply": {
845
+ "block_name": "(() * ())", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_multiply",
846
+ "functionality": "Multiplies two numerical values.",
847
+ "inputs": {"NUM1": [1, [4, ""]], "NUM2": [1, [4, ""]]}, "fields": {}, "shadow": False, "topLevel": True
848
+ },
849
+ "operator_divide": {
850
+ "block_name": "(() / ())", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_divide",
851
+ "functionality": "Divides the first numerical value by the second.",
852
+ "inputs": {"NUM1": [1, [4, ""]], "NUM2": [1, [4, ""]]}, "fields": {}, "shadow": False, "topLevel": True
853
+ },
854
+ "operator_random": {
855
+ "block_name": "(pick random () to ())", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_random",
856
+ "functionality": "Generates a random integer within a specified inclusive range.",
857
+ "inputs": {"FROM": [1, [4, "1"]], "TO": [1, [4, "10"]]}, "fields": {}, "shadow": False, "topLevel": True
858
+ },
859
+ "operator_gt": {
860
+ "block_name": "<() > ()>", "block_type": "operator", "block_shape": "Boolean Block", "op_code": "operator_gt",
861
+ "functionality": "Checks if the first value is greater than the second.",
862
+ "inputs": {"OPERAND1": [1, [10, ""]], "OPERAND2": [1, [10, "50"]]}, "fields": {}, "shadow": False, "topLevel": True
863
+ },
864
+ "operator_lt": {
865
+ "block_name": "<() < ()>", "block_type": "operator", "block_shape": "Boolean Block", "op_code": "operator_lt",
866
+ "functionality": "Checks if the first value is less than the second.",
867
+ "inputs": {"OPERAND1": [1, [10, ""]], "OPERAND2": [1, [10, "50"]]}, "fields": {}, "shadow": False, "topLevel": True
868
+ },
869
+ "operator_equals": {
870
+ "block_name": "<() = ()>", "block_type": "operator", "block_shape": "Boolean Block", "op_code": "operator_equals",
871
+ "functionality": "Checks if two values are equal.",
872
+ "inputs": {"OPERAND1": [1, [10, ""]], "OPERAND2": [1, [10, "50"]]}, "fields": {}, "shadow": False, "topLevel": True
873
+ },
874
+ "operator_and": {
875
+ "block_name": "<<> and <>>", "block_type": "operator", "block_shape": "Boolean Block", "op_code": "operator_and",
876
+ "functionality": "Returns 'true' if both provided Boolean conditions are 'true'.",
877
+ "inputs": {"OPERAND1": [2, None], "OPERAND2": [2, None]}, "fields": {}, "shadow": False, "topLevel": True
878
+ },
879
+ "operator_or": {
880
+ "block_name": "<<> or <>>", "block_type": "operator", "block_shape": "Boolean Block", "op_code": "operator_or",
881
+ "functionality": "Returns 'true' if at least one of the provided Boolean conditions is 'true'.",
882
+ "inputs": {"OPERAND1": [2, None], "OPERAND2": [2, None]}, "fields": {}, "shadow": False, "topLevel": True
883
+ },
884
+ "operator_not": {
885
+ "block_name": "<not <>>", "block_type": "operator", "block_shape": "Boolean Block", "op_code": "operator_not",
886
+ "functionality": "Returns 'true' if the provided Boolean condition is 'false', and 'false' if it is 'true'.",
887
+ "inputs": {"OPERAND": [2, None]}, "fields": {}, "shadow": False, "topLevel": True
888
+ },
889
+ "operator_join": {
890
+ "block_name": "(join ()())", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_join",
891
+ "functionality": "Concatenates two strings or values into a single string.",
892
+ "inputs": {"STRING1": [1, [10, "apple "]], "STRING2": [1, [10, "banana"]]}, "fields": {}, "shadow": False, "topLevel": True
893
+ },
894
+ "operator_letterof": {
895
+ "block_name": "letter () of ()", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_letterof",
896
+ "functionality": "Reports the character at a specific numerical position within a string.",
897
+ "inputs": {"LETTER": [1, [6, "1"]], "STRING": [1, [10, "apple"]]}, "fields": {}, "shadow": False, "topLevel": True
898
+ },
899
+ "operator_length": {
900
+ "block_name": "(length of ())", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_length",
901
+ "functionality": "Reports the total number of characters in a given string.",
902
+ "inputs": {"STRING": [1, [10, "apple"]]}, "fields": {}, "shadow": False, "topLevel": True
903
+ },
904
+ "operator_contains": {
905
+ "block_name": "<() contains ()?>", "block_type": "operator", "block_shape": "Boolean Block", "op_code": "operator_contains",
906
+ "functionality": "Checks if one string contains another string.",
907
+ "inputs": {"STRING1": [1, [10, "apple"]], "STRING2": [1, [10, "a"]]}, "fields": {}, "shadow": False, "topLevel": True
908
+ },
909
+ "operator_mod": {
910
+ "block_name": "(() mod ())", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_mod",
911
+ "functionality": "Reports the remainder when the first number is divided by the second.",
912
+ "inputs": {"NUM1": [1, [4, ""]], "NUM2": [1, [4, ""]]}, "fields": {}, "shadow": False, "topLevel": True
913
+ },
914
+ "operator_round": {
915
+ "block_name": "(round ())", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_round",
916
+ "functionality": "Rounds a numerical value to the nearest integer.",
917
+ "inputs": {"NUM": [1, [4, ""]]}, "fields": {}, "shadow": False, "topLevel": True
918
+ },
919
+ "operator_mathop": {
920
+ "block_name": "(() of ())", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_mathop",
921
+ "functionality": "Performs various mathematical functions (e.g., absolute value, square root, trigonometric functions).",
922
+ "inputs": {"NUM": [1, [4, ""]]}, "fields": {"OPERATOR": ["abs", None]}, "shadow": False, "topLevel": True
923
+ },
924
+
925
+ # sensing_block.json
926
+ "sensing_touchingobject": {
927
+ "block_name": "<touching [edge v]?>", "block_type": "Sensing", "op_code": "sensing_touchingobject", "block_shape": "Boolean Block",
928
+ "functionality": "Checks if its sprite is touching the mouse-pointer, edge, or another specified sprite.",
929
+ "inputs": {"TOUCHINGOBJECTMENU": [1, "sensing_touchingobjectmenu"]}, "fields": {}, "shadow": False, "topLevel": True
930
+ },
931
+ "sensing_touchingobjectmenu": {
932
+ "block_name": "touching object menu", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_touchingobjectmenu",
933
+ "functionality": "Menu for touching object block.",
934
+ "inputs": {}, "fields": {"TOUCHINGOBJECTMENU": ["_mouse_", None]}, "shadow": True, "topLevel": False
935
+ },
936
+ "sensing_touchingcolor": {
937
+ "block_name": "<touching color ()?>", "block_type": "Sensing", "op_code": "sensing_touchingcolor", "block_shape": "Boolean Block",
938
+ "functionality": "Checks whether its sprite is touching a specified color.",
939
+ "inputs": {"COLOR": [1, [9, "#55b888"]]}, "fields": {}, "shadow": False, "topLevel": True
940
+ },
941
+ "sensing_coloristouchingcolor": {
942
+ "block_name": "<color () is touching ()?>", "block_type": "Sensing", "op_code": "sensing_coloristouchingcolor", "block_shape": "Boolean Block",
943
+ "functionality": "Checks whether a specific color on its sprite is touching another specified color on the stage or another sprite.",
944
+ "inputs": {"COLOR1": [1, [9, "#d019f2"]], "COLOR2": [1, [9, "#2b0de3"]]}, "fields": {}, "shadow": False, "topLevel": True
945
+ },
946
+ "sensing_askandwait": {
947
+ "block_name": "Ask () and Wait", "block_type": "Sensing", "block_shape": "Stack Block", "op_code": "sensing_askandwait",
948
+ "functionality": "Displays an input box with specified text at the bottom of the screen, allowing users to input text, which is stored in the 'Answer' block.",
949
+ "inputs": {"QUESTION": [1, [10, "What's your name?"]]}, "fields": {}, "shadow": False, "topLevel": True
950
+ },
951
+ "sensing_answer": {
952
+ "block_name": "(answer)", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_answer",
953
+ "functionality": "Holds the most recent text inputted using the 'Ask () and Wait' block.",
954
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
955
+ },
956
+ "sensing_keypressed": {
957
+ "block_name": "<key () pressed?>", "block_type": "Sensing", "op_code": "sensing_keypressed", "block_shape": "Boolean Block",
958
+ "functionality": "Checks if a specified keyboard key is currently being pressed.",
959
+ "inputs": {"KEY_OPTION": [1, "sensing_keyoptions"]}, "fields": {}, "shadow": False, "topLevel": True
960
+ },
961
+ "sensing_keyoptions": {
962
+ "block_name": "key options menu", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_keyoptions",
963
+ "functionality": "Menu for key pressed block.",
964
+ "inputs": {}, "fields": {"KEY_OPTION": ["space", None]}, "shadow": True, "topLevel": False
965
+ },
966
+ "sensing_mousedown": {
967
+ "block_name": "<mouse down?>", "block_type": "Sensing", "op_code": "sensing_mousedown", "block_shape": "Boolean Block",
968
+ "functionality": "Checks if the computer mouse's primary button is being clicked while the cursor is over the stage.",
969
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
970
+ },
971
+ "sensing_mousex": {
972
+ "block_name": "(mouse x)", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_mousex",
973
+ "functionality": "Reports the mouse-pointer’s current X position on the stage.",
974
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
975
+ },
976
+ "sensing_mousey": {
977
+ "block_name": "(mouse y)", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_mousey",
978
+ "functionality": "Reports the mouse-pointer’s current Y position on the stage.",
979
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
980
+ },
981
+ "sensing_setdragmode": {
982
+ "block_name": "set drag mode [draggable v]", "block_type": "Sensing", "block_shape": "Stack Block", "op_code": "sensing_setdragmode",
983
+ "functionality": "Sets whether the sprite can be dragged by the mouse on the stage.",
984
+ "inputs": {}, "fields": {"DRAG_MODE": ["draggable", None]}, "shadow": False, "topLevel": True
985
+ },
986
+ "sensing_loudness": {
987
+ "block_name": "(loudness)", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_loudness",
988
+ "functionality": "Reports the loudness of noise received by a microphone on a scale of 0 to 100.",
989
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
990
+ },
991
+ "sensing_timer": {
992
+ "block_name": "(timer)", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_timer",
993
+ "functionality": "Reports the elapsed time since Scratch was launched or the timer was reset, increasing by 1 every second.",
994
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
995
+ },
996
+ "sensing_resettimer": {
997
+ "block_name": "Reset Timer", "block_type": "Sensing", "block_shape": "Stack Block", "op_code": "sensing_resettimer",
998
+ "functionality": "Sets the timer’s value back to 0.0.",
999
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
1000
+ },
1001
+ "sensing_of": {
1002
+ "block_name": "(() of ())", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_of",
1003
+ "functionality": "Reports a specified value (e.g., x position, direction, costume number) of a specified sprite or the Stage to be accessed in current sprite or stage.",
1004
+ "inputs": {"OBJECT": [1, "sensing_of_object_menu"]}, "fields": {"PROPERTY": ["backdrop #", None]}, "shadow": False, "topLevel": True
1005
+ },
1006
+ "sensing_of_object_menu": {
1007
+ "block_name": "of object menu", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_of_object_menu",
1008
+ "functionality": "Menu for of block.",
1009
+ "inputs": {}, "fields": {"OBJECT": ["_stage_", None]}, "shadow": True, "topLevel": False
1010
+ },
1011
+ "sensing_current": {
1012
+ "block_name": "(current ())", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_current",
1013
+ "functionality": "Reports the current local year, month, date, day of the week, hour, minutes, or seconds.",
1014
+ "inputs": {}, "fields": {"CURRENTMENU": ["YEAR", None]}, "shadow": False, "topLevel": True
1015
+ },
1016
+ "sensing_dayssince2000": {
1017
+ "block_name": "(days since 2000)", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_dayssince2000",
1018
+ "functionality": "Reports the number of days (and fractions of a day) since 00:00:00 UTC on January 1, 2000.",
1019
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
1020
+ },
1021
+ "sensing_username": {
1022
+ "block_name": "(username)", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_username",
1023
+ "functionality": "Reports the username of the user currently logged into Scratch. If no user is logged in, it reports nothing.",
1024
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
1025
+ },
1026
+
1027
+ # sound_block.json
1028
+ "sound_playuntildone": {
1029
+ "block_name": "play sound () until done", "block_type": "Sound", "block_shape": "Stack Block", "op_code": "sound_playuntildone",
1030
+ "functionality": "Plays a specified sound and pauses the script's execution until the sound has completed.",
1031
+ "inputs": {"SOUND_MENU": [1, "sound_sounds_menu"]}, "fields": {}, "shadow": False, "topLevel": True
1032
+ },
1033
+ "sound_sounds_menu": {
1034
+ "block_name": "sound menu", "block_type": "Sound", "block_shape": "Reporter Block", "op_code": "sound_sounds_menu",
1035
+ "functionality": "Menu for sound blocks.",
1036
+ "inputs": {}, "fields": {"SOUND_MENU": ["Meow", None]}, "shadow": True, "topLevel": False
1037
+ },
1038
+ "sound_play": {
1039
+ "block_name": "start sound ()", "block_type": "Sound", "block_shape": "Stack Block", "op_code": "sound_play",
1040
+ "functionality": "Initiates playback of a specified sound without pausing the script, allowing other actions to proceed concurrently.",
1041
+ "inputs": {"SOUND_MENU": [1, "sound_sounds_menu"]}, "fields": {}, "shadow": False, "topLevel": True
1042
+ },
1043
+ "sound_stopallsounds": {
1044
+ "block_name": "stop all sounds", "block_type": "Sound", "block_shape": "Stack Block", "op_code": "sound_stopallsounds",
1045
+ "functionality": "Stops all currently playing sounds.",
1046
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
1047
+ },
1048
+ "sound_changeeffectby": {
1049
+ "block_name": "change () effect by ()", "block_type": "Sound", "block_shape": "Stack Block", "op_code": "sound_changeeffectby",
1050
+ "functionality": "Changes the project's sound effect by a specified amount.",
1051
+ "inputs": {"VALUE": [1, [4, "10"]]}, "fields": {"EFFECT": ["PITCH", None]}, "shadow": False, "topLevel": True
1052
+ },
1053
+ "sound_seteffectto": {
1054
+ "block_name": "set () effect to ()", "block_type": "Sound", "block_shape": "Stack Block", "op_code": "sound_seteffectto",
1055
+ "functionality": "Sets the sound effect to a specific value.",
1056
+ "inputs": {"VALUE": [1, [4, "100"]]}, "fields": {"EFFECT": ["PITCH", None]}, "shadow": False, "topLevel": True
1057
+ },
1058
+ "sound_cleareffects": {
1059
+ "block_name": "clear sound effects", "block_type": "Sound", "block_shape": "Stack Block", "op_code": "sound_cleareffects",
1060
+ "functionality": "Removes all sound effects applied to the sprite.",
1061
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
1062
+ },
1063
+ "sound_changevolumeby": {
1064
+ "block_name": "change volume by ()", "block_type": "Sound", "block_shape": "Stack Block", "op_code": "sound_changevolumeby",
1065
+ "functionality": "Changes the project's sound volume by a specified amount.",
1066
+ "inputs": {"VOLUME": [1, [4, "-10"]]}, "fields": {}, "shadow": False, "topLevel": True
1067
+ },
1068
+ "sound_setvolumeto": {
1069
+ "block_name": "set volume to () %", "block_type": "Sound", "block_shape": "Stack Block", "op_code": "sound_setvolumeto",
1070
+ "functionality": "Sets the sound volume to a specific percentage (0-100).",
1071
+ "inputs": {"VOLUME": [1, [4, "100"]]}, "fields": {}, "shadow": False, "topLevel": True
1072
+ },
1073
+ "sound_volume": {
1074
+ "block_name": "(volume)", "block_type": "Sound", "block_shape": "Reporter Block", "op_code": "sound_volume",
1075
+ "functionality": "Reports the current volume level of the sprite.",
1076
+ "inputs": {}, "fields": {}, "shadow": False, "topLevel": True
1077
+ },
1078
+ }
1079
+
1080
+ # Example input with opcodes for the initial generation
1081
+ initial_opcode_counts = [
1082
+ {"opcode":"event_whenflagclicked","count":1},
1083
+ {"opcode":"motion_gotoxy","count":1},
1084
+ {"opcode":"motion_glidesecstoxy","count":1},
1085
+ {"opcode":"motion_xposition","count":1},
1086
+ {"opcode":"motion_setx","count":1},
1087
+ {"opcode":"control_forever","count":1},
1088
+ {"opcode":"control_if","count":1},
1089
+ {"opcode":"control_stop","count":1},
1090
+ {"opcode":"operator_lt","count":1},
1091
+ {"opcode":"sensing_touchingobject","count":1}, # Changed from sensing_istouching
1092
+ {"opcode":"sensing_touchingobjectmenu","count":1},
1093
+ {"opcode":"event_broadcast","count":1},
1094
+ {"opcode":"data_setvariableto","count":2},
1095
+ {"opcode":"data_showvariable","count":2},
1096
+ ]
1097
+
1098
+ # Generate the initial blocks and get the opcode_occurrences
1099
+ generated_output_json, initial_opcode_occurrences = generate_blocks_from_opcodes(initial_opcode_counts, all_block_definitions)
1100
+
1101
+ # Pseudo-code to interpret
1102
+ pseudo_code_input = """
1103
+ when green flag clicked
1104
+ go to x: (240) y: (-135)
1105
+ set [score v] to (1)
1106
+ set [speed v] to (1)
1107
+ show variable [score v]
1108
+ show variable [speed v]
1109
+ forever
1110
+ glide (2) seconds to x: (-240) y: (-135)
1111
+ if <((x position)) < (-235)> then
1112
+ set x to (240)
1113
+ end
1114
+ if <touching [Sprite1 v]?> then
1115
+ broadcast [Game Over v]
1116
+ stop [all v]
1117
+ end
1118
+ end
1119
+ end
1120
+ """
1121
+
1122
+ # Interpret the pseudo-code and update the blocks, passing opcode_occurrences
1123
+ final_generated_blocks = interpret_pseudo_code_and_update_blocks(generated_output_json, pseudo_code_input, all_block_definitions, initial_opcode_occurrences)
1124
+
1125
+ print(initial_opcode_occurrences)
1126
+
1127
+ #print(json.dumps(final_generated_blocks, indent=2))
1128
+ # ```json
1129
+ # {
1130
+ # "event_whenflagclicked_1": {
1131
+ # "block_name": "when green flag pressed",
1132
+ # "block_type": "Events",
1133
+ # "op_code": "event_whenflagclicked",
1134
+ # "block_shape": "Hat Block",
1135
+ # "functionality": "This Hat block initiates the script when the green flag is clicked, serving as the common starting point for most Scratch projects.",
1136
+ # "inputs": {},
1137
+ # "fields": {},
1138
+ # "shadow": false,
1139
+ # "topLevel": true,
1140
+ # "parent": null,
1141
+ # "next": "motion_gotoxy_1"
1142
+ # },
1143
+ # "motion_gotoxy_1": {
1144
+ # "block_name": "go to x: () y: ()",
1145
+ # "block_type": "Motion",
1146
+ # "op_code": "motion_gotoxy",
1147
+ # "functionality": "Moves the sprite to the specified X and Y coordinates on the stage.",
1148
+ # "inputs": {
1149
+ # "X": [
1150
+ # 4,
1151
+ # "240"
1152
+ # ],
1153
+ # "Y": [
1154
+ # 4,
1155
+ # "-135"
1156
+ # ]
1157
+ # },
1158
+ # "fields": {},
1159
+ # "shadow": false,
1160
+ # "topLevel": false,
1161
+ # "parent": null,
1162
+ # "next": "data_setvariableto_1"
1163
+ # },
1164
+ # "motion_glidesecstoxy_1": {
1165
+ # "block_name": "glide () secs to x: () y: ()",
1166
+ # "block_type": "Motion",
1167
+ # "op_code": "motion_glidesecstoxy",
1168
+ # "functionality": "Glides the sprite smoothly to the specified X and Y coordinates over a given number of seconds.",
1169
+ # "inputs": {
1170
+ # "SECS": [
1171
+ # 4,
1172
+ # "2"
1173
+ # ],
1174
+ # "X": [
1175
+ # 4,
1176
+ # "-240"
1177
+ # ],
1178
+ # "Y": [
1179
+ # 4,
1180
+ # "-135"
1181
+ # ]
1182
+ # },
1183
+ # "fields": {},
1184
+ # "shadow": false,
1185
+ # "topLevel": false,
1186
+ # "parent": "control_forever_1",
1187
+ # "next": "control_if_1"
1188
+ # },
1189
+ # "motion_xposition_1": {
1190
+ # "block_name": "(x position)",
1191
+ # "block_type": "Motion",
1192
+ # "op_code": "motion_xposition",
1193
+ # "functionality": "Reports the current X-coordinate of the sprite.[NOTE: not used in stage/backdrops]",
1194
+ # "inputs": {},
1195
+ # "fields": {},
1196
+ # "shadow": true,
1197
+ # "topLevel": false,
1198
+ # "parent": "operator_lt_1",
1199
+ # "next": null
1200
+ # },
1201
+ # "motion_setx_1": {
1202
+ # "block_name": "set x to ()",
1203
+ # "block_type": "Motion",
1204
+ # "op_code": "motion_setx",
1205
+ # "functionality": "Sets the sprite's X-coordinate to a specific value, placing it at a precise horizontal position.",
1206
+ # "inputs": {
1207
+ # "X": [
1208
+ # 4,
1209
+ # "240"
1210
+ # ]
1211
+ # },
1212
+ # "fields": {},
1213
+ # "shadow": false,
1214
+ # "topLevel": false,
1215
+ # "parent": "control_if_1",
1216
+ # "next": null
1217
+ # },
1218
+ # "control_forever_1": {
1219
+ # "block_name": "forever",
1220
+ # "block_type": "Control",
1221
+ # "op_code": "control_forever",
1222
+ # "functionality": "Continuously runs the blocks inside it.",
1223
+ # "inputs": {
1224
+ # "SUBSTACK": [
1225
+ # 2,
1226
+ # "motion_glidesecstoxy_1"
1227
+ # ]
1228
+ # },
1229
+ # "fields": {},
1230
+ # "shadow": false,
1231
+ # "topLevel": false,
1232
+ # "parent": null,
1233
+ # "next": null
1234
+ # },
1235
+ # "control_if_1": {
1236
+ # "block_name": "if <> then",
1237
+ # "block_type": "Control",
1238
+ # "op_code": "control_if",
1239
+ # "functionality": "Executes the blocks inside it only if the specified boolean condition is true. [NOTE: it takes boolean blocks as input]",
1240
+ # "inputs": {
1241
+ # "CONDITION": [
1242
+ # 2,
1243
+ # "operator_lt_1"
1244
+ # ],
1245
+ # "SUBSTACK": [
1246
+ # 2,
1247
+ # "motion_setx_1"
1248
+ # ]
1249
+ # },
1250
+ # "fields": {},
1251
+ # "shadow": false,
1252
+ # "topLevel": false,
1253
+ # "parent": "control_forever_1",
1254
+ # "next": "control_if_2"
1255
+ # },
1256
+ # "control_stop_1": {
1257
+ # "block_name": "stop [v]",
1258
+ # "block_type": "Control",
1259
+ # "op_code": "control_stop",
1260
+ # "functionality": "Halts all scripts, only the current script, or other scripts within the same sprite. Its shape can dynamically change based on the selected option.",
1261
+ # "inputs": {},
1262
+ # "fields": {
1263
+ # "STOP_OPTION": [
1264
+ # "all",
1265
+ # null
1266
+ # ]
1267
+ # },
1268
+ # "shadow": false,
1269
+ # "topLevel": false,
1270
+ # "parent": "control_if_2",
1271
+ # "mutation": {
1272
+ # "tagName": "mutation",
1273
+ # "children": [],
1274
+ # "hasnext": "false"
1275
+ # },
1276
+ # "next": null
1277
+ # },
1278
+ # "operator_lt_1": {
1279
+ # "block_name": "<() < ()>",
1280
+ # "block_type": "operator",
1281
+ # "op_code": "operator_lt",
1282
+ # "functionality": "Checks if the first value is less than the second.",
1283
+ # "inputs": {
1284
+ # "OPERAND1": [
1285
+ # 1,
1286
+ # "motion_xposition_1"
1287
+ # ],
1288
+ # "OPERAND2": [
1289
+ # 4,
1290
+ # "-235"
1291
+ # ]
1292
+ # },
1293
+ # "fields": {},
1294
+ # "shadow": true,
1295
+ # "topLevel": false,
1296
+ # "parent": "control_if_1",
1297
+ # "next": null
1298
+ # },
1299
+ # "sensing_touchingobject_1": {
1300
+ # "block_name": "<touching [edge v]?>",
1301
+ # "block_type": "Sensing",
1302
+ # "op_code": "sensing_touchingobject",
1303
+ # "functionality": "Checks if its sprite is touching the mouse-pointer, edge, or another specified sprite.",
1304
+ # "inputs": {
1305
+ # "TOUCHINGOBJECTMENU": [
1306
+ # 1,
1307
+ # "sensing_touchingobjectmenu_1"
1308
+ # ]
1309
+ # },
1310
+ # "fields": {},
1311
+ # "shadow": true,
1312
+ # "topLevel": false,
1313
+ # "parent": "control_if_2",
1314
+ # "next": null
1315
+ # },
1316
+ # "sensing_touchingobjectmenu_1": {
1317
+ # "block_name": "touching object menu",
1318
+ # "block_type": "Sensing",
1319
+ # "op_code": "sensing_touchingobjectmenu",
1320
+ # "functionality": "Menu for touching object block.",
1321
+ # "inputs": {},
1322
+ # "fields": {
1323
+ # "TOUCHINGOBJECTMENU": [
1324
+ # "Sprite1",
1325
+ # null
1326
+ # ]
1327
+ # },
1328
+ # "shadow": true,
1329
+ # "topLevel": false,
1330
+ # "parent": "sensing_touchingobject_1",
1331
+ # "next": null
1332
+ # },
1333
+ # "event_broadcast_1": {
1334
+ # "block_name": "broadcast ()",
1335
+ # "block_type": "Events",
1336
+ # "op_code": "event_broadcast",
1337
+ # "functionality": "Sends a broadcast message throughout the Scratch program, activating any 'when I receive ()' blocks that are set to listen for that message, enabling indirect communication.",
1338
+ # "inputs": {
1339
+ # "BROADCAST_INPUT": [
1340
+ # 10,
1341
+ # "Game Over"
1342
+ # ]
1343
+ # },
1344
+ # "fields": {},
1345
+ # "shadow": false,
1346
+ # "topLevel": false,
1347
+ # "parent": "control_if_2",
1348
+ # "next": "control_stop_1"
1349
+ # },
1350
+ # "data_setvariableto_1": {
1351
+ # "block_name": "set [my variable v] to ()",
1352
+ # "block_type": "Data",
1353
+ # "op_code": "data_setvariableto",
1354
+ # "functionality": "Assigns a specific value (number, string, or boolean) to a variable.",
1355
+ # "inputs": {
1356
+ # "VALUE": [
1357
+ # 4,
1358
+ # "1"
1359
+ # ]
1360
+ # },
1361
+ # "fields": {
1362
+ # "VARIABLE": [
1363
+ # "score",
1364
+ # "`var_score"
1365
+ # ]
1366
+ # },
1367
+ # "shadow": false,
1368
+ # "topLevel": false,
1369
+ # "parent": null,
1370
+ # "next": "data_setvariableto_2"
1371
+ # },
1372
+ # "data_setvariableto_2": {
1373
+ # "block_name": "set [my variable v] to ()",
1374
+ # "block_type": "Data",
1375
+ # "op_code": "data_setvariableto",
1376
+ # "functionality": "Assigns a specific value (number, string, or boolean) to a variable.",
1377
+ # "inputs": {
1378
+ # "VALUE": [
1379
+ # 4,
1380
+ # "1"
1381
+ # ]
1382
+ # },
1383
+ # "fields": {
1384
+ # "VARIABLE": [
1385
+ # "speed",
1386
+ # "`var_speed"
1387
+ # ]
1388
+ # },
1389
+ # "shadow": false,
1390
+ # "topLevel": false,
1391
+ # "parent": null,
1392
+ # "next": "data_showvariable_1"
1393
+ # },
1394
+ # "data_showvariable_1": {
1395
+ # "block_name": "show variable [my variable v]",
1396
+ # "block_type": "Data",
1397
+ # "op_code": "data_showvariable",
1398
+ # "functionality": "Makes a variable's monitor visible on the stage.",
1399
+ # "inputs": {},
1400
+ # "fields": {
1401
+ # "VARIABLE": [
1402
+ # "score",
1403
+ # "`var_score"
1404
+ # ]
1405
+ # },
1406
+ # "shadow": false,
1407
+ # "topLevel": false,
1408
+ # "parent": null,
1409
+ # "next": "data_showvariable_2"
1410
+ # },
1411
+ # "data_showvariable_2": {
1412
+ # "block_name": "show variable [my variable v]",
1413
+ # "block_type": "Data",
1414
+ # "op_code": "data_showvariable",
1415
+ # "functionality": "Makes a variable's monitor visible on the stage.",
1416
+ # "inputs": {},
1417
+ # "fields": {
1418
+ # "VARIABLE": [
1419
+ # "speed",
1420
+ # "`var_speed"
1421
+ # ]
1422
+ # },
1423
+ # "shadow": false,
1424
+ # "topLevel": false,
1425
+ # "parent": null,
1426
+ # "next": "control_forever_1"
1427
+ # },
1428
+ # "control_if_2": {
1429
+ # "block_name": "if <> then",
1430
+ # "block_type": "Control",
1431
+ # "op_code": "control_if",
1432
+ # "functionality": "Executes the blocks inside it only if the specified boolean condition is true. [NOTE: it takes boolean blocks as input]",
1433
+ # "inputs": {
1434
+ # "CONDITION": [
1435
+ # 2,
1436
+ # "sensing_touchingobject_1"
1437
+ # ],
1438
+ # "SUBSTACK": [
1439
+ # 2,
1440
+ # "event_broadcast_1"
1441
+ # ]
1442
+ # },
1443
+ # "fields": {},
1444
+ # "shadow": false,
1445
+ # "topLevel": false,
1446
+ # "parent": "control_forever_1",
1447
+ # "next": null
1448
+ # }
1449
+ # }
utils/testing.ipynb ADDED
@@ -0,0 +1,419 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 3,
6
+ "id": "9adfcb20",
7
+ "metadata": {},
8
+ "outputs": [],
9
+ "source": [
10
+ "import re"
11
+ ]
12
+ },
13
+ {
14
+ "cell_type": "code",
15
+ "execution_count": 4,
16
+ "id": "de910107",
17
+ "metadata": {},
18
+ "outputs": [
19
+ {
20
+ "data": {
21
+ "text/plain": [
22
+ "'if <not <touching [sprite2 v]?>> then'"
23
+ ]
24
+ },
25
+ "execution_count": 4,
26
+ "metadata": {},
27
+ "output_type": "execute_result"
28
+ }
29
+ ],
30
+ "source": [
31
+ "stmt=\"if <not <touching [Sprite2 v]?>> then\"\n",
32
+ "s = stmt.lower().strip()\n",
33
+ "# 1) strip ALL balanced <…>\n",
34
+ "while s.startswith(\"<\") and s.endswith(\">\"):\n",
35
+ " s = s[1:-1].strip()\n",
36
+ "# 2) strip stray unmatched < or >\n",
37
+ "while s.startswith(\"<\") and not s.endswith(\">\"):\n",
38
+ " s = s[1:].strip()\n",
39
+ "while s.endswith(\">\") and not s.startswith(\"<\"):\n",
40
+ " s = s[:-1].strip()\n",
41
+ " \n",
42
+ "s"
43
+ ]
44
+ },
45
+ {
46
+ "cell_type": "code",
47
+ "execution_count": 20,
48
+ "id": "af367513",
49
+ "metadata": {},
50
+ "outputs": [
51
+ {
52
+ "name": "stdout",
53
+ "output_type": "stream",
54
+ "text": [
55
+ "None\n",
56
+ "the stmt was this if <<mouse down?> and <touching [mouse-pointer v]?>> then and parsed was this if <<mouse down?> and <touching [mouse-pointer v]?>> then\n"
57
+ ]
58
+ },
59
+ {
60
+ "ename": "SyntaxError",
61
+ "evalue": "'return' outside function (234319892.py, line 51)",
62
+ "output_type": "error",
63
+ "traceback": [
64
+ " \u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[20]\u001b[39m\u001b[32m, line 51\u001b[39m\n\u001b[31m \u001b[39m\u001b[31mreturn {\"kind\": \"block\", \"block\": block_id}\u001b[39m\n ^\n\u001b[31mSyntaxError\u001b[39m\u001b[31m:\u001b[39m 'return' outside function\n"
65
+ ]
66
+ }
67
+ ],
68
+ "source": [
69
+ "import re\n",
70
+ "\n",
71
+ "s = \"if <not <touching [sprite2 v]?>> then\"\n",
72
+ "\n",
73
+ "m_touch = re.fullmatch(r\"touching\\s*\\[\\s*([^\\]]+)\\s*v\\]\\??\", s, re.IGNORECASE)\n",
74
+ "print(m_touch)\n",
75
+ "if m_touch:\n",
76
+ " sprite = m_touch.group(1).strip()\n",
77
+ " val = {'mouse-pointer':'_mouse_', 'edge':'_edge_'}.get(sprite, sprite)\n",
78
+ " mid = _register_block(\n",
79
+ " \"sensing_touchingobjectmenu\", parent_key, True, pick_key_func, all_generated_blocks,\n",
80
+ " fields={\"TOUCHINGOBJECTMENU\":[val, None]}\n",
81
+ " )\n",
82
+ " bid = _register_block(\n",
83
+ " \"sensing_touchingobject\", parent_key, False, pick_key_func, all_generated_blocks,\n",
84
+ " inputs={\"TOUCHINGOBJECTMENU\":[1, mid]}\n",
85
+ " )\n",
86
+ " #all_generated_blocks[mid][\"parent\"] = bid \n",
87
+ " print(bid)\n",
88
+ " #return {\"kind\":\"block\",\"block\":bid}\n",
89
+ "\n",
90
+ "s = stmt.lower().strip()\n",
91
+ "#s = strip_outer_angle_brackets(s)\n",
92
+ "# 1) strip ALL balanced <…>\n",
93
+ "while s.startswith(\"<\") and s.endswith(\">\"):\n",
94
+ " s = s[1:-1].strip()\n",
95
+ "# 2) strip stray unmatched < or >\n",
96
+ "while s.startswith(\"<\") and not s.endswith(\">\"):\n",
97
+ " s = s[1:].strip()\n",
98
+ "while s.endswith(\">\") and not s.startswith(\"<\"):\n",
99
+ " s = s[:-1].strip()\n",
100
+ " \n",
101
+ "print(f\"the stmt was this {stmt} and parsed was this {s}\")\n",
102
+ "# 1a) Comparisons with explicit angle wrappers: < (...) op (...) >\n",
103
+ "m = re.fullmatch(\n",
104
+ " r\"\\s*<\\s*(.+?)\\s*(?P<op><|=|>)\\s*(.+?)\\s*>\\s*\",\n",
105
+ " s,\n",
106
+ " re.VERBOSE\n",
107
+ ")\n",
108
+ "if m:\n",
109
+ " left_txt, right_txt = m.group(1), m.group(3)\n",
110
+ " operand1_obj = parse_reporter_or_value(unparen(left_txt), None, pick_key_func, all_generated_blocks)\n",
111
+ " operand2_obj = parse_reporter_or_value(unparen(right_txt), None, pick_key_func, all_generated_blocks)\n",
112
+ " op_map = {'<': 'operator_lt', '=': 'operator_equals', '>': 'operator_gt'}\n",
113
+ " \n",
114
+ " inputs = {\"OPERAND1\": operand1_obj, \"OPERAND2\": operand2_obj}\n",
115
+ " block_id = _register_block(op_map[m.group('op')], parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs)\n",
116
+ " # Set parents for nested inputs\n",
117
+ " if operand1_obj.get(\"kind\") == \"block\": all_generated_blocks[operand1_obj[\"block\"]][\"parent\"] = block_id\n",
118
+ " if operand2_obj.get(\"kind\") == \"block\": all_generated_blocks[operand2_obj[\"block\"]][\"parent\"] = block_id\n",
119
+ " return {\"kind\": \"block\", \"block\": block_id}\n",
120
+ "# 1b) Simple comparisons without angle wrappers: A op B\n",
121
+ "m_simple = re.fullmatch(r\"\\s*(.+?)\\s*(?P<op><|=|>)\\s*(.+?)\\s*\", s)\n",
122
+ "if m_simple:\n",
123
+ " left_txt, right_txt = m_simple.group(1), m_simple.group(3)\n",
124
+ " operand1_obj = parse_reporter_or_value(unparen(left_txt), None, pick_key_func, all_generated_blocks)\n",
125
+ " operand2_obj = parse_reporter_or_value(unparen(right_txt), None, pick_key_func, all_generated_blocks)\n",
126
+ " op_map = {'<': 'operator_lt', '=': 'operator_equals', '>': 'operator_gt'}\n",
127
+ " \n",
128
+ " inputs = {\"OPERAND1\": operand1_obj, \"OPERAND2\": operand2_obj}\n",
129
+ " block_id = _register_block(op_map[m_simple.group('op')], parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs)\n",
130
+ " if operand1_obj.get(\"kind\") == \"block\": all_generated_blocks[operand1_obj[\"block\"]][\"parent\"] = block_id\n",
131
+ " if operand2_obj.get(\"kind\") == \"block\": all_generated_blocks[operand2_obj[\"block\"]][\"parent\"] = block_id\n",
132
+ " return {\"kind\": \"block\", \"block\": block_id}"
133
+ ]
134
+ },
135
+ {
136
+ "cell_type": "code",
137
+ "execution_count": 23,
138
+ "id": "3a315968",
139
+ "metadata": {},
140
+ "outputs": [
141
+ {
142
+ "name": "stdout",
143
+ "output_type": "stream",
144
+ "text": [
145
+ "[TEST] if <mouse down?> then\n",
146
+ "β†’ Extracted: 'mouse down?'\n",
147
+ " Expected: 'mouse down?'\n",
148
+ " βœ… PASS\n",
149
+ "\n",
150
+ "[TEST] if <not <mouse down?>> then\n",
151
+ "β†’ Extracted: 'not <mouse down?>'\n",
152
+ " Expected: 'not <mouse down?>'\n",
153
+ " βœ… PASS\n",
154
+ "\n",
155
+ "[TEST] if <<mouse down?> and <touching [edge v]?>> then\n",
156
+ "β†’ Extracted: 'mouse down?> and <touching [edge v]?'\n",
157
+ " Expected: 'mouse down? and touching [edge v]?'\n",
158
+ " ❌ FAIL\n",
159
+ "\n",
160
+ "[TEST] if <<<mouse down?>> or <not <key pressed?>> >> then\n",
161
+ "β†’ Extracted: 'mouse down?>> or <not <key pressed?'\n",
162
+ " Expected: 'mouse down? or not <key pressed?>'\n",
163
+ " ❌ FAIL\n",
164
+ "\n",
165
+ "[TEST] if <(x position) < (100)> then\n",
166
+ "β†’ Extracted: '<(x position) < (100)> then'\n",
167
+ " Expected: '(x position) < (100)'\n",
168
+ " ❌ FAIL\n",
169
+ "\n"
170
+ ]
171
+ }
172
+ ],
173
+ "source": [
174
+ "def extract_condition_balanced(stmt):\n",
175
+ " stmt = stmt.strip()\n",
176
+ " if \"if \" in stmt.lower():\n",
177
+ " stmt = stmt.lower().split(\"if\", 1)[1].strip()\n",
178
+ "\n",
179
+ " # Handle bracket balancing\n",
180
+ " if stmt.startswith(\"<\"):\n",
181
+ " count = 0\n",
182
+ " condition = \"\"\n",
183
+ " for i, ch in enumerate(stmt):\n",
184
+ " if ch == \"<\":\n",
185
+ " count += 1\n",
186
+ " elif ch == \">\":\n",
187
+ " count -= 1\n",
188
+ " condition += ch\n",
189
+ " if count == 0:\n",
190
+ " break\n",
191
+ "\n",
192
+ " # Strip outer brackets recursively\n",
193
+ " inner = condition\n",
194
+ " while inner.startswith(\"<\") and inner.endswith(\">\"):\n",
195
+ " inner = inner[1:-1].strip()\n",
196
+ "\n",
197
+ " return inner\n",
198
+ " return stmt.strip()\n",
199
+ "\n",
200
+ "test_cases = [\n",
201
+ " (\"if <mouse down?> then\", \"mouse down?\"),\n",
202
+ " (\"if <not <mouse down?>> then\", \"not <mouse down?>\"),\n",
203
+ " (\"if <<mouse down?> and <touching [edge v]?>> then\", \"mouse down? and touching [edge v]?\"),\n",
204
+ " (\"if <<<mouse down?>> or <not <key pressed?>> >> then\", \"mouse down? or not <key pressed?>\"),\n",
205
+ " (\"if <(x position) < (100)> then\", \"(x position) < (100)\"),\n",
206
+ "]\n",
207
+ "\n",
208
+ "for stmt, expected in test_cases:\n",
209
+ " result = extract_condition_balanced(stmt)\n",
210
+ " print(f\"[TEST] {stmt}\\nβ†’ Extracted: '{result}'\\n Expected: '{expected}'\\n {'βœ… PASS' if result == expected else '❌ FAIL'}\\n\")\n"
211
+ ]
212
+ },
213
+ {
214
+ "cell_type": "code",
215
+ "execution_count": 38,
216
+ "id": "34ddb52b",
217
+ "metadata": {},
218
+ "outputs": [
219
+ {
220
+ "name": "stdout",
221
+ "output_type": "stream",
222
+ "text": [
223
+ "--- Testing improved function ---\n",
224
+ "[TEST] if <mouse down?> then\n",
225
+ "β†’ Extracted: 'mouse down?'\n",
226
+ " Expected: 'mouse down?'\n",
227
+ " βœ… PASS\n",
228
+ "\n",
229
+ "[TEST] repeat until <touching [edge v]?>\n",
230
+ "β†’ Extracted: 'touching [edge v]?'\n",
231
+ " Expected: 'touching [edge v]?'\n",
232
+ " βœ… PASS\n",
233
+ "\n",
234
+ "[TEST] if <key [left arrow v] pressed?> then\n",
235
+ "β†’ Extracted: 'key [left arrow v] pressed?'\n",
236
+ " Expected: 'key [left arrow v] pressed?'\n",
237
+ " βœ… PASS\n",
238
+ "\n",
239
+ "[TEST] if <touching [edge v]?> then\n",
240
+ "β†’ Extracted: 'touching [edge v]?'\n",
241
+ " Expected: 'touching [edge v]?'\n",
242
+ " βœ… PASS\n",
243
+ "\n",
244
+ "[TEST] if <<mouse down?> and <touching [edge v]?>> then\n",
245
+ "β†’ Extracted: '<mouse down?> and <touching [edge v]?>'\n",
246
+ " Expected: 'mouse down? and touching [edge v]?'\n",
247
+ " ❌ FAIL\n",
248
+ "\n",
249
+ "[TEST] if <(x position) < (100)> then\n",
250
+ "β†’ Extracted: '(x position) < (100)'\n",
251
+ " Expected: '(x position) < (100)'\n",
252
+ " βœ… PASS\n",
253
+ "\n",
254
+ "[TEST] if <<mouse down?>> then\n",
255
+ "β†’ Extracted: '<mouse down?>'\n",
256
+ " Expected: 'mouse down?'\n",
257
+ " ❌ FAIL\n",
258
+ "\n",
259
+ "[TEST] if <not<>> then\n",
260
+ "β†’ Extracted: 'not<>'\n",
261
+ " Expected: 'not<>'\n",
262
+ " βœ… PASS\n",
263
+ "\n",
264
+ "[TEST] if <<> or <>> then\n",
265
+ "β†’ Extracted: '<> or <>'\n",
266
+ " Expected: '<> or <>'\n",
267
+ " βœ… PASS\n",
268
+ "\n",
269
+ "[TEST] if <> then\n",
270
+ "β†’ Extracted: ''\n",
271
+ " Expected: ''\n",
272
+ " βœ… PASS\n",
273
+ "\n",
274
+ "[TEST] if mouse down? then\n",
275
+ "β†’ Extracted: 'mouse down?'\n",
276
+ " Expected: 'mouse down?'\n",
277
+ " βœ… PASS\n",
278
+ "\n",
279
+ "[TEST] <mouse down?>\n",
280
+ "β†’ Extracted: 'mouse down?'\n",
281
+ " Expected: 'mouse down?'\n",
282
+ " βœ… PASS\n",
283
+ "\n"
284
+ ]
285
+ }
286
+ ],
287
+ "source": [
288
+ "import re\n",
289
+ "\n",
290
+ "def extract_condition_balanced(stmt):\n",
291
+ " # 1. Remove \"if\" and \"then\"\n",
292
+ " stmt = stmt.strip()\n",
293
+ " if stmt.lower().startswith(\"if \"):\n",
294
+ " stmt = stmt[3:].strip()\n",
295
+ " if stmt.lower().startswith(\"repeat until\"):\n",
296
+ " stmt = stmt[12:].strip()\n",
297
+ " if stmt.lower().endswith(\" then\"):\n",
298
+ " stmt = stmt[:-5].strip()\n",
299
+ "\n",
300
+ " # Helper to detect and strip single outer balanced angle brackets\n",
301
+ " def unwrap_balanced(s):\n",
302
+ " if s.startswith(\"<\") and s.endswith(\">\"):\n",
303
+ " depth = 0\n",
304
+ " for i in range(len(s)):\n",
305
+ " if s[i] == \"<\":\n",
306
+ " depth += 1\n",
307
+ " elif s[i] == \">\":\n",
308
+ " depth -= 1\n",
309
+ " if depth == 0 and i < len(s) - 1:\n",
310
+ " return s # Early balance β†’ not a single outer wrapper\n",
311
+ " if depth == 0:\n",
312
+ " return s[1:-1].strip()\n",
313
+ " return s\n",
314
+ "\n",
315
+ " # Recursively simplify things like <not <x>> to not <x>\n",
316
+ " def simplify(s):\n",
317
+ " s = unwrap_balanced(s)\n",
318
+ " s = s.strip()\n",
319
+ "\n",
320
+ " # Match <not <...>> pattern\n",
321
+ " m = re.fullmatch(r\"not\\s*<(.+)>\", s, re.IGNORECASE)\n",
322
+ " if m:\n",
323
+ " inner = m.group(1).strip()\n",
324
+ " inner = simplify(inner)\n",
325
+ " return f\"not <{inner}>\"\n",
326
+ "\n",
327
+ " # Match comparison operators like <(x position) < (100)>\n",
328
+ " m_comp = re.fullmatch(r\"<\\s*\\(([^<>]+?)\\)\\s*([<>=])\\s*\\(([^<>]+?)\\)\\s*>\", stmt)\n",
329
+ " if m_comp:\n",
330
+ " return f\"({m_comp.group(1).strip()}) {m_comp.group(2)} ({m_comp.group(3).strip()})\"\n",
331
+ "\n",
332
+ " return s\n",
333
+ "\n",
334
+ " return simplify(stmt)\n",
335
+ "\n",
336
+ "# Test cases\n",
337
+ "# test_cases = [\n",
338
+ "# (\"if <mouse down?> then\", \"mouse down?\"),\n",
339
+ "# (\"if <not <mouse down?>> then\", \"not <mouse down?>\"),\n",
340
+ "# (\"if <<mouse down?> and <touching [edge v]?>> then\", \"<mouse down?> and <touching [edge v]?>\"),\n",
341
+ "# (\"if <<mouse down?> or <not <key pressed?>>> then\", \"<mouse down?> or <not <key pressed?>>\"),\n",
342
+ "# (\"if <(x position) < (100)> then\", \"(x position) < (100)\"),\n",
343
+ "# ]\n",
344
+ "test_cases = [\n",
345
+ " (\"if <mouse down?> then\", \"mouse down?\"),\n",
346
+ " (\"repeat until <touching [edge v]?>\", \"touching [edge v]?\"),\n",
347
+ " (\"if <key [left arrow v] pressed?> then\", \"key [left arrow v] pressed?\"),\n",
348
+ " (\"if <touching [edge v]?> then\", \"touching [edge v]?\"),\n",
349
+ " (\"if <<mouse down?> and <touching [edge v]?>> then\", \"mouse down? and touching [edge v]?\"),\n",
350
+ " (\"if <(x position) < (100)> then\", \"(x position) < (100)\"),\n",
351
+ " (\"if <<mouse down?>> then\", \"mouse down?\"),\n",
352
+ " (\"if <not<>> then\", \"not<>\"),\n",
353
+ " (\"if <<> or <>> then\", \"<> or <>\"),\n",
354
+ " (\"if <> then\", \"\"),\n",
355
+ " (\"if mouse down? then\", \"mouse down?\"),\n",
356
+ " (\"<mouse down?>\", \"mouse down?\"),\n",
357
+ "]\n",
358
+ "\n",
359
+ "print(\"--- Testing improved function ---\")\n",
360
+ "for stmt, expected in test_cases:\n",
361
+ " result = extract_condition_balanced(stmt)\n",
362
+ " print(f\"[TEST] {stmt}\\nβ†’ Extracted: '{result}'\\n Expected: '{expected}'\\n {'βœ… PASS' if result == expected else '❌ FAIL'}\\n\")\n"
363
+ ]
364
+ },
365
+ {
366
+ "cell_type": "code",
367
+ "execution_count": 40,
368
+ "id": "1ee313ee",
369
+ "metadata": {},
370
+ "outputs": [
371
+ {
372
+ "name": "stdout",
373
+ "output_type": "stream",
374
+ "text": [
375
+ "when I receive [Game Over v]\n",
376
+ " if <(score) > (High Score)> then\n",
377
+ " set [High Score v] to (score)\n",
378
+ " end\n",
379
+ " switch backdrop to [HighScore v]\n",
380
+ "end\n"
381
+ ]
382
+ }
383
+ ],
384
+ "source": [
385
+ "txt = \"when I receive [Game Over v]\\n if <(score) > (High Score)> then\\n set [High Score v] to (score)\\n end\\n switch backdrop to [HighScore v]\\nend\"\n",
386
+ "print(str(txt)) "
387
+ ]
388
+ },
389
+ {
390
+ "cell_type": "code",
391
+ "execution_count": null,
392
+ "id": "4700799d",
393
+ "metadata": {},
394
+ "outputs": [],
395
+ "source": []
396
+ }
397
+ ],
398
+ "metadata": {
399
+ "kernelspec": {
400
+ "display_name": "scratch_env",
401
+ "language": "python",
402
+ "name": "python3"
403
+ },
404
+ "language_info": {
405
+ "codemirror_mode": {
406
+ "name": "ipython",
407
+ "version": 3
408
+ },
409
+ "file_extension": ".py",
410
+ "mimetype": "text/x-python",
411
+ "name": "python",
412
+ "nbconvert_exporter": "python",
413
+ "pygments_lexer": "ipython3",
414
+ "version": "3.12.11"
415
+ }
416
+ },
417
+ "nbformat": 4,
418
+ "nbformat_minor": 5
419
+ }