WebashalarForML commited on
Commit
63dd93e
·
verified ·
1 Parent(s): 3d3703f

Upload 39 files

Browse files
.gitattributes CHANGED
@@ -42,3 +42,6 @@ scratch_VLM/game_samples/Pong[[:space:]]Game.sb3 filter=lfs diff=lfs merge=lfs -
42
  scratch_VLM/scratch_agent/uploads/documents/LLM_based_QA_chatbot_builder.pdf filter=lfs diff=lfs merge=lfs -text
43
  v2/scratch_agent/static/assets/backdrops/ef9973bcff6d4cbc558e946028ec7d23.png filter=lfs diff=lfs merge=lfs -text
44
  v2/scratch_agent/uploads/documents/LLM_based_QA_chatbot_builder.pdf filter=lfs diff=lfs merge=lfs -text
 
 
 
 
42
  scratch_VLM/scratch_agent/uploads/documents/LLM_based_QA_chatbot_builder.pdf filter=lfs diff=lfs merge=lfs -text
43
  v2/scratch_agent/static/assets/backdrops/ef9973bcff6d4cbc558e946028ec7d23.png filter=lfs diff=lfs merge=lfs -text
44
  v2/scratch_agent/uploads/documents/LLM_based_QA_chatbot_builder.pdf filter=lfs diff=lfs merge=lfs -text
45
+ v2/utils/__pycache__/block_relation_builder.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text
46
+ v2/utils/__pycache__/plan_generator_13.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text
47
+ v2/utils/testing.ipynb filter=lfs diff=lfs merge=lfs -text
v2/utils/__pycache__/block_relation_builder.cpython-312.pyc ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9c686438dd391592343c2837c7eb952141313d9a15e99bc26f3783b1385a9f91
3
+ size 111900
v2/utils/__pycache__/plan_generator_13.cpython-312.pyc ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:05eb37cdab6c26c54f57ea851420ddb9b3bdb4656ce47fd43d4942fba6488b76
3
+ size 114434
v2/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}")
v2/utils/action_plan_sample.txt ADDED
@@ -0,0 +1,1577 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ First sample:
2
+
3
+ {
4
+ 'Stage': {
5
+ 'description': 'Background and global game state management, including broadcasts, rewards, and score.',
6
+ 'plans': [
7
+ {
8
+ 'event': 'event_whenflagclicked',
9
+ 'logic': 'when green flag clicked\n switch backdrop to [blue sky v]\n set [score v] to 0\n show variable [score v]\n broadcast [Game Start v]',
10
+ 'motion': [
11
+
12
+ ],
13
+ 'control': [
14
+
15
+ ],
16
+ 'operator': [
17
+
18
+ ],
19
+ 'sensing': [
20
+
21
+ ],
22
+ 'looks': [
23
+ 'looks_switchbackdropto'
24
+ ],
25
+ 'sounds': [
26
+
27
+ ],
28
+ 'events': [
29
+ 'event_broadcast'
30
+ ],
31
+ 'data': [
32
+ 'data_setvariableto',
33
+ 'data_showvariable'
34
+ ],
35
+ 'opcode_counts': [
36
+ {
37
+ 'opcode': 'event_whenflagclicked',
38
+ 'count': 1
39
+ },
40
+ {
41
+ 'opcode': 'looks_switchbackdropto',
42
+ 'count': 1
43
+ },
44
+ {
45
+ 'opcode': 'data_setvariableto',
46
+ 'count': 1
47
+ },
48
+ {
49
+ 'opcode': 'data_showvariable',
50
+ 'count': 1
51
+ },
52
+ {
53
+ 'opcode': 'event_broadcast',
54
+ 'count': 1
55
+ }
56
+ ]
57
+ },
58
+ {
59
+ 'event': 'event_whenbroadcastreceived',
60
+ 'logic': '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 [Game Over v]',
61
+ 'motion': [
62
+
63
+ ],
64
+ 'control': [
65
+ 'control_if'
66
+ ],
67
+ 'operator': [
68
+ 'operator_gt'
69
+ ],
70
+ 'sensing': [
71
+
72
+ ],
73
+ 'looks': [
74
+ 'looks_switchbackdropto'
75
+ ],
76
+ 'sounds': [
77
+
78
+ ],
79
+ 'events': [
80
+
81
+ ],
82
+ 'data': [
83
+ 'data_setvariableto'
84
+ ],
85
+ 'opcode_counts': [
86
+ {
87
+ 'opcode': 'event_whenbroadcastreceived',
88
+ 'count': 1
89
+ },
90
+ {
91
+ 'opcode': 'control_if',
92
+ 'count': 1
93
+ },
94
+ {
95
+ 'opcode': 'operator_gt',
96
+ 'count': 1
97
+ },
98
+ {
99
+ 'opcode': 'data_setvariableto',
100
+ 'count': 1
101
+ },
102
+ {
103
+ 'opcode': 'looks_switchbackdropto',
104
+ 'count': 1
105
+ }
106
+ ]
107
+ },
108
+ {
109
+ 'event': 'event_whenbroadcastreceived',
110
+ 'logic': 'when I receive [Level Up v]\n change [level v] by 1\n set [ballSpeed v] to (ballSpeed * 1.1)',
111
+ 'motion': [
112
+
113
+ ],
114
+ 'control': [
115
+
116
+ ],
117
+ 'operator': [
118
+ 'operator_multiply'
119
+ ],
120
+ 'sensing': [
121
+
122
+ ],
123
+ 'looks': [
124
+
125
+ ],
126
+ 'sounds': [
127
+
128
+ ],
129
+ 'events': [
130
+
131
+ ],
132
+ 'data': [
133
+ 'data_changevariableby',
134
+ 'data_setvariableto'
135
+ ],
136
+ 'opcode_counts': [
137
+ {
138
+ 'opcode': 'event_whenbroadcastreceived',
139
+ 'count': 1
140
+ },
141
+ {
142
+ 'opcode': 'data_changevariableby',
143
+ 'count': 1
144
+ },
145
+ {
146
+ 'opcode': 'data_setvariableto',
147
+ 'count': 1
148
+ },
149
+ {
150
+ 'opcode': 'operator_multiply',
151
+ 'count': 1
152
+ }
153
+ ]
154
+ }
155
+ ]
156
+ },
157
+ 'Cat': {
158
+ 'description': 'Main character (cat) actions',
159
+ 'plans': [
160
+ {
161
+ 'event': 'event_whenflagclicked',
162
+ 'logic': 'when green flag clicked\n go to x: 0 y: -100\n forever\n if <key [space v] pressed?> then\n jump\n end\n move 5 steps\n if <touching [Ball v]?> then\n broadcast [Game Over v]\n end\n end',
163
+ 'motion': [
164
+ 'motion_movesteps',
165
+ 'motion_gotoxy'
166
+ ],
167
+ 'control': [
168
+ 'control_forever',
169
+ 'control_if'
170
+ ],
171
+ 'operator': [
172
+
173
+ ],
174
+ 'sensing': [
175
+ 'sensing_keypressed',
176
+ 'sensing_touchingobject'
177
+ ],
178
+ 'looks': [
179
+
180
+ ],
181
+ 'sounds': [
182
+
183
+ ],
184
+ 'events': [
185
+ 'event_broadcast'
186
+ ],
187
+ 'data': [
188
+
189
+ ],
190
+ 'opcode_counts': [
191
+ {
192
+ 'opcode': 'event_whenflagclicked',
193
+ 'count': 1
194
+ },
195
+ {
196
+ 'opcode': 'motion_gotoxy',
197
+ 'count': 1
198
+ },
199
+ {
200
+ 'opcode': 'control_forever',
201
+ 'count': 1
202
+ },
203
+ {
204
+ 'opcode': 'control_if',
205
+ 'count': 2
206
+ },
207
+ {
208
+ 'opcode': 'sensing_keypressed',
209
+ 'count': 1
210
+ },
211
+ {
212
+ 'opcode': 'sensing_touchingobject',
213
+ 'count': 1
214
+ },
215
+ {
216
+ 'opcode': 'motion_movesteps',
217
+ 'count': 1
218
+ },
219
+ {
220
+ 'opcode': 'event_broadcast',
221
+ 'count': 1
222
+ }
223
+ ]
224
+ },
225
+ {
226
+ 'event': 'event_whenkeypressed',
227
+ 'logic': 'when [space v] key pressed\n change y by 10\n wait 0.2 seconds\n change y by -10',
228
+ 'motion': [
229
+ 'motion_changeyby'
230
+ ],
231
+ 'control': [
232
+ 'control_wait'
233
+ ],
234
+ 'operator': [
235
+
236
+ ],
237
+ 'sensing': [
238
+
239
+ ],
240
+ 'looks': [
241
+
242
+ ],
243
+ 'sounds': [
244
+
245
+ ],
246
+ 'events': [
247
+
248
+ ],
249
+ 'data': [
250
+
251
+ ],
252
+ 'opcode_counts': [
253
+ {
254
+ 'opcode': 'event_whenkeypressed',
255
+ 'count': 1
256
+ },
257
+ {
258
+ 'opcode': 'motion_changeyby',
259
+ 'count': 2
260
+ },
261
+ {
262
+ 'opcode': 'control_wait',
263
+ 'count': 1
264
+ }
265
+ ]
266
+ }
267
+ ]
268
+ },
269
+ 'Ball': {
270
+ 'description': 'Obstacle movement and interaction',
271
+ 'plans': [
272
+ {
273
+ 'event': 'event_whenflagclicked',
274
+ 'logic': 'when green flag clicked\n go to x: 0 y: 100\n forever\n glide 2 seconds to x: pick random (-240, 240) y: 100\n if <touching [Cat v]?> then\n broadcast [Game Over v]\n end\n end',
275
+ 'motion': [
276
+ 'motion_glidesecstoxy',
277
+ 'motion_xposition'
278
+ ],
279
+ 'control': [
280
+ 'control_forever'
281
+ ],
282
+ 'operator': [
283
+ 'operator_random'
284
+ ],
285
+ 'sensing': [
286
+ 'sensing_touchingobject'
287
+ ],
288
+ 'looks': [
289
+
290
+ ],
291
+ 'sounds': [
292
+
293
+ ],
294
+ 'events': [
295
+ 'event_broadcast'
296
+ ],
297
+ 'data': [
298
+
299
+ ],
300
+ 'opcode_counts': [
301
+ {
302
+ 'opcode': 'event_whenflagclicked',
303
+ 'count': 1
304
+ },
305
+ {
306
+ 'opcode': 'motion_gotoxy',
307
+ 'count': 1
308
+ },
309
+ {
310
+ 'opcode': 'motion_glidesecstoxy',
311
+ 'count': 1
312
+ },
313
+ {
314
+ 'opcode': 'operator_random',
315
+ 'count': 1
316
+ },
317
+ {
318
+ 'opcode': 'control_forever',
319
+ 'count': 1
320
+ },
321
+ {
322
+ 'opcode': 'sensing_touchingobject',
323
+ 'count': 1
324
+ },
325
+ {
326
+ 'opcode': 'event_broadcast',
327
+ 'count': 1
328
+ }
329
+ ]
330
+ }
331
+ ]
332
+ }
333
+ }
334
+
335
+ second sample
336
+
337
+ [Refined Action Plan]: {
338
+ "Stage": {
339
+ "description": "Background and global game state management, including broadcasts, rewards, and score.",
340
+ "plans": [
341
+ {
342
+ "event": "event_whenflagclicked",
343
+ "logic": "when green flag clicked\n set [score v] to 0\n set [speed v] to 2\n set [lives v] to 1\n broadcast [Game Start v]",
344
+ "control": [],
345
+ "operator": [],
346
+ "sensing": [],
347
+ "looks": [],
348
+ "sounds": [],
349
+ "events": [
350
+ "event_broadcast"
351
+ ],
352
+ "data": [
353
+ "data_setvariableto"
354
+ ]
355
+ },
356
+ {
357
+ "event": "event_whenbroadcastreceived",
358
+ "logic": "when I receive [Game Over v]\n if <(score) > (High Score)> then\n set [High Score v] to (score)\n end\n broadcast [Reset Game v]",
359
+ "control": [
360
+ "control_if"
361
+ ],
362
+ "operator": [
363
+ "operator_gt"
364
+ ],
365
+ "sensing": [],
366
+ "looks": [],
367
+ "sounds": [],
368
+ "events": [
369
+ "event_broadcast"
370
+ ],
371
+ "data": [
372
+ "data_setvariableto"
373
+ ]
374
+ },
375
+ {
376
+ "event": "event_whenbroadcastreceived",
377
+ "logic": "when I receive [Reset Game v]\n set [score v] to 0\n set [speed v] to 2\n set [lives v] to 1",
378
+ "control": [],
379
+ "operator": [],
380
+ "sensing": [],
381
+ "looks": [],
382
+ "sounds": [],
383
+ "events": [
384
+ "event_broadcast"
385
+ ],
386
+ "data": [
387
+ "data_setvariableto"
388
+ ]
389
+ }
390
+ ]
391
+ },
392
+ "Cat": {
393
+ "description": "Main character (cat) actions",
394
+ "plans": [
395
+ {
396
+ "event": "event_whenflagclicked",
397
+ "logic": "when green flag clicked\n go to x: 0 y: -130\n forever\n if <key [space v] pressed?> then\n jump\n end\n move 5 steps\n end",
398
+ "motion": [
399
+ "motion_movesteps",
400
+ "motion_setx",
401
+ "motion_sety"
402
+ ],
403
+ "control": [
404
+ "control_forever",
405
+ "control_if"
406
+ ],
407
+ "operator": [],
408
+ "sensing": [
409
+ "sensing_keypressed"
410
+ ],
411
+ "looks": [],
412
+ "sounds": [],
413
+ "events": [],
414
+ "data": []
415
+ },
416
+ {
417
+ "event": "event_whenkeypressed",
418
+ "logic": "when [space v] key pressed\n if <(y position) = -130> then\n repeat (10)\n change y by (20)\n wait (0.1) seconds\n change y by (-20)\n end\n end",
419
+ "control": [
420
+ "control_repeat",
421
+ "control_if"
422
+ ],
423
+ "operator": [],
424
+ "sensing": [],
425
+ "looks": [],
426
+ "sounds": [],
427
+ "events": [],
428
+ "data": []
429
+ }
430
+ ]
431
+ },
432
+ "Ball": {
433
+ "description": "Obstacle movement and interaction",
434
+ "plans": [
435
+ {
436
+ "event": "event_whenflagclicked",
437
+ "logic": "when green flag clicked\n go to x: 240 y: 0\n forever\n glide (2) seconds to x: -240 y: 0\n if <(x position) < -235> then\n
438
+ set x to 240\n end\n if <touching [Cat v]?> then\n broadcast [Game Over v]\n end\n end",
439
+ "motion": [
440
+ "motion_gotoxy",
441
+ "motion_glidesecstoxy",
442
+ "motion_xposition",
443
+ "motion_setx"
444
+ ],
445
+ "control": [
446
+ "control_forever",
447
+ "control_if"
448
+ ],
449
+ "operator": [
450
+ "operator_lt"
451
+ ],
452
+ "sensing": [
453
+ "sensing_istouching"
454
+ ],
455
+ "looks": [],
456
+ "sounds": [],
457
+ "events": [
458
+ "event_broadcast"
459
+ ],
460
+ "data": []
461
+ }
462
+ ]
463
+ }
464
+ }
465
+
466
+
467
+ sample 3:
468
+ {
469
+ 'Stage': {
470
+ 'description': 'Background and global game state management, including broadcasts, rewards, and score.',
471
+ 'plans': [
472
+ {
473
+ 'event': 'event_whenflagclicked',
474
+ 'logic': 'when green flag clicked\n set [score v] to 0\n set [health v] to 1\n set [speed v] to 1\n switch backdrop to [blue sky v]\n broadcast [Game Start v]',
475
+ 'motion': [
476
+
477
+ ],
478
+ 'control': [
479
+
480
+ ],
481
+ 'operator': [
482
+
483
+ ],
484
+ 'sensing': [
485
+
486
+ ],
487
+ 'looks': [
488
+ 'looks_switchbackdropto'
489
+ ],
490
+ 'sounds': [
491
+
492
+ ],
493
+ 'events': [
494
+ 'event_broadcast'
495
+ ],
496
+ 'data': [
497
+ 'data_setvariableto',
498
+ 'data_showvariable'
499
+ ],
500
+ 'opcode_counts': [
501
+ {
502
+ 'opcode': 'event_whenflagclicked',
503
+ 'count': 1
504
+ },
505
+ {
506
+ 'opcode': 'data_setvariableto',
507
+ 'count': 3
508
+ },
509
+ {
510
+ 'opcode': 'looks_switchbackdropto',
511
+ 'count': 1
512
+ },
513
+ {
514
+ 'opcode': 'event_broadcast',
515
+ 'count': 1
516
+ }
517
+ ]
518
+ },
519
+ {
520
+ 'event': 'event_whenbroadcastreceived',
521
+ 'logic': '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 [Game Over v]',
522
+ 'motion': [
523
+
524
+ ],
525
+ 'control': [
526
+ 'control_if'
527
+ ],
528
+ 'operator': [
529
+ 'operator_gt'
530
+ ],
531
+ 'sensing': [
532
+
533
+ ],
534
+ 'looks': [
535
+ 'looks_switchbackdropto'
536
+ ],
537
+ 'sounds': [
538
+
539
+ ],
540
+ 'events': [
541
+
542
+ ],
543
+ 'data': [
544
+ 'data_setvariableto'
545
+ ],
546
+ 'opcode_counts': [
547
+ {
548
+ 'opcode': 'event_whenbroadcastreceived',
549
+ 'count': 1
550
+ },
551
+ {
552
+ 'opcode': 'control_if',
553
+ 'count': 1
554
+ },
555
+ {
556
+ 'opcode': 'operator_gt',
557
+ 'count': 1
558
+ },
559
+ {
560
+ 'opcode': 'data_setvariableto',
561
+ 'count': 1
562
+ },
563
+ {
564
+ 'opcode': 'looks_switchbackdropto',
565
+ 'count': 1
566
+ }
567
+ ]
568
+ }
569
+ ]
570
+ },
571
+ 'Cat': {
572
+ 'description': 'Main character (cat) actions',
573
+ 'plans': [
574
+ {
575
+ 'event': 'event_whenflagclicked',
576
+ 'logic': 'when green flag clicked\n go to x: 0 y: -130\n set [shield v] to 0',
577
+ 'motion': [
578
+ 'motion_gotoxy'
579
+ ],
580
+ 'control': [
581
+
582
+ ],
583
+ 'operator': [
584
+
585
+ ],
586
+ 'sensing': [
587
+
588
+ ],
589
+ 'looks': [
590
+
591
+ ],
592
+ 'sounds': [
593
+
594
+ ],
595
+ 'events': [
596
+
597
+ ],
598
+ 'data': [
599
+
600
+ ],
601
+ 'opcode_counts': [
602
+ {
603
+ 'opcode': 'event_whenflagclicked',
604
+ 'count': 1
605
+ },
606
+ {
607
+ 'opcode': 'motion_gotoxy',
608
+ 'count': 1
609
+ },
610
+ {
611
+ 'opcode': 'data_setvariableto',
612
+ 'count': 1
613
+ }
614
+ ]
615
+ },
616
+ {
617
+ 'event': 'event_whenkeypressed',
618
+ 'logic': 'when [space v] key pressed\n if <(y position) = -130> then\n repeat (10)\n change y by 20\n wait (0.1) seconds\n change y by -20\n end\n end',
619
+ 'motion': [
620
+ 'motion_changeyby'
621
+ ],
622
+ 'control': [
623
+ 'control_repeat',
624
+ 'control_wait',
625
+ 'control_if'
626
+ ],
627
+ 'operator': [
628
+
629
+ ],
630
+ 'sensing': [
631
+
632
+ ],
633
+ 'looks': [
634
+
635
+ ],
636
+ 'sounds': [
637
+
638
+ ],
639
+ 'events': [
640
+
641
+ ],
642
+ 'data': [
643
+
644
+ ],
645
+ 'opcode_counts': [
646
+ {
647
+ 'opcode': 'event_whenkeypressed',
648
+ 'count': 1
649
+ },
650
+ {
651
+ 'opcode': 'control_if',
652
+ 'count': 1
653
+ },
654
+ {
655
+ 'opcode': 'control_repeat',
656
+ 'count': 1
657
+ },
658
+ {
659
+ 'opcode': 'control_wait',
660
+ 'count': 1
661
+ },
662
+ {
663
+ 'opcode': 'motion_changeyby',
664
+ 'count': 2
665
+ }
666
+ ]
667
+ },
668
+ {
669
+ 'event': 'event_whenbroadcastreceived',
670
+ 'logic': 'when I receive [Power-Up v]\n if <(Power-Up) = [Shield v]> then\n set [shield v] to 1\n end',
671
+ 'motion': [
672
+
673
+ ],
674
+ 'control': [
675
+
676
+ ],
677
+ 'operator': [
678
+
679
+ ],
680
+ 'sensing': [
681
+
682
+ ],
683
+ 'looks': [
684
+
685
+ ],
686
+ 'sounds': [
687
+
688
+ ],
689
+ 'events': [
690
+
691
+ ],
692
+ 'data': [
693
+
694
+ ],
695
+ 'opcode_counts': [
696
+ {
697
+ 'opcode': 'event_whenbroadcastreceived',
698
+ 'count': 1
699
+ },
700
+ {
701
+ 'opcode': 'control_if',
702
+ 'count': 1
703
+ },
704
+ {
705
+ 'opcode': 'operator_eq',
706
+ 'count': 1
707
+ },
708
+ {
709
+ 'opcode': 'data_setvariableto',
710
+ 'count': 1
711
+ }
712
+ ]
713
+ }
714
+ ]
715
+ },
716
+ 'Ball': {
717
+ 'description': 'Obstacle movement and interaction',
718
+ 'plans': [
719
+ {
720
+ 'event': 'event_whenflagclicked',
721
+ 'logic': 'when green flag clicked\n go to x: 50 y: 100\n forever\n change x by 5\n if <(x position) > 240> then\n set x to -240\n end\n if <(x position) < -240> then\n set x to 240\n end\n if <touching [Cat v]?> then\n broadcast [Game Over v]\n end\n end',
722
+ 'motion': [
723
+ 'motion_changexby',
724
+ 'motion_setx'
725
+ ],
726
+ 'control': [
727
+ 'control_forever',
728
+ 'control_if'
729
+ ],
730
+ 'operator': [
731
+
732
+ ],
733
+ 'sensing': [
734
+ 'sensing_touchingobject'
735
+ ],
736
+ 'looks': [
737
+
738
+ ],
739
+ 'sounds': [
740
+
741
+ ],
742
+ 'events': [
743
+ 'event_broadcast'
744
+ ],
745
+ 'data': [
746
+
747
+ ],
748
+ 'opcode_counts': [
749
+ {
750
+ 'opcode': 'event_whenflagclicked',
751
+ 'count': 1
752
+ },
753
+ {
754
+ 'opcode': 'motion_gotoxy',
755
+ 'count': 1
756
+ },
757
+ {
758
+ 'opcode': 'motion_changexby',
759
+ 'count': 1
760
+ },
761
+ {
762
+ 'opcode': 'motion_setx',
763
+ 'count': 2
764
+ },
765
+ {
766
+ 'opcode': 'control_forever',
767
+ 'count': 1
768
+ },
769
+ {
770
+ 'opcode': 'control_if',
771
+ 'count': 2
772
+ },
773
+ {
774
+ 'opcode': 'sensing_touchingobject',
775
+ 'count': 1
776
+ },
777
+ {
778
+ 'opcode': 'event_broadcast',
779
+ 'count': 1
780
+ }
781
+ ]
782
+ }
783
+ ]
784
+ }
785
+ }
786
+
787
+
788
+ refinement_prompt = f"""
789
+ You are an expert in Scratch 3.0 game development, specializing in understanding block relationships (stacked, nested).
790
+ Review the following plan for '{target_name}' triggered by '{event}'.
791
+
792
+ Game Description:
793
+ {game_description}
794
+
795
+ --- Scratch 3.0 Block Reference ---
796
+ ### Hat Blocks
797
+ Description: {hat_description}
798
+ Blocks:
799
+ {hat_opcodes_functionalities}
800
+
801
+ ### Boolean Blocks
802
+ Description: {boolean_description}
803
+ Blocks:
804
+ {boolean_opcodes_functionalities}
805
+
806
+ ### C Blocks
807
+ Description: {c_description}
808
+ Blocks:
809
+ {c_opcodes_functionalities}
810
+
811
+ ### Cap Blocks
812
+ Description: {cap_description}
813
+ Blocks:
814
+ {cap_opcodes_functionalities}
815
+
816
+ ### Reporter Blocks
817
+ Description: {reporter_description}
818
+ Blocks:
819
+ {reporter_opcodes_functionalities}
820
+
821
+ ### Stack Blocks
822
+ Description: {stack_description}
823
+ Blocks:
824
+ {stack_opcodes_functionalities}
825
+ -----------------------------------
826
+ Current Plan Details:
827
+ - Event (Hat Block Opcode): {event}
828
+ - Overall plans made on {target_name} are in the list: {overall_plans_in_sprite}
829
+ - Associated Opcodes by Category: {json.dumps(opcodes, indent=2)}
830
+ - Reference code, functionality, and logic script to check: {combined_blocks}
831
+ - Current Logic Description to Update if required: "{original_logic}"
832
+
833
+ Your task is to:
834
+ 1. **Refine the 'logic'**: Make it precise, accurate, and fully aligned with the Game Description. Use Scratch‑consistent verbs and phrasing. **Do NOT** use raw double‑quotes inside the logic string.
835
+
836
+ 2. **Structural requirements**:
837
+ - **Numeric values** `(e.g., 0, 5, 0.2, -130)` **must** be in parentheses: `(0)`, `(5)`, `(0.2)`, `(-130)`.
838
+ - **AlphaNumeric values** `(e.g., hello, say 5, 4, hi!)` **must** be in parentheses: `(hello)`, `(say 5)`, `(4)`, `(hi!)`.
839
+ - **Variables** must be in the form `[variable v]` (e.g., `[score v]`), even when used inside expressions two example use `set [score v] to (1)` or `show variable ([speed v])`.
840
+ - **Dropdown options** must be in the form `[option v]` (e.g., `[Game Start v]`, `[blue sky v]`). example use `when [space v] key pressed`.
841
+ - **Reporter blocks** used as inputs must be double‑wrapped: `((x position))`, `((y position))`. example use `if <((y position)) = (-130)> then` or `(((x position)) * (1))`.
842
+ - **Boolean blocks** in conditions must be inside `< >`, including nested ones: `<not <condition>>`, `<<cond1> and <cond2>>`,`<<cond1> or <cond2>>`.
843
+ - **Other Boolean blocks** in conditions must be inside `< >`, including nested ones or values or variables: `<(block/value/variable) * (block/value/variable)>`,`<(block/value/variable) < (block/value/variable)>`, and example of another variable`<[apple v] contains [a v]?>`.
844
+ - **Operator expressions** must use explicit Scratch operator blocks, e.g.:
845
+ ```
846
+ (([ballSpeed v]) * (1.1))
847
+ ```
848
+ - **Every hat block script must end** with a final `end` on its own line.
849
+
850
+ 3. **Pseudo‑code formatting**:
851
+ - Represent each block or nested block on its own line.
852
+ - Indent nested blocks by 4 spaces under their parent (`forever`, `if`, etc.).
853
+ - No comments or explanatory text—just the block sequence.
854
+ - a natural language breakdown of each step taken after the event, formatted as a multi-line string representing pseudo-code. Ensure clarity and granularity—each described action should map closely to a Scratch block or tight sequence.
855
+
856
+ 4. **Logic content**:
857
+ - Build clear flow for mechanics (movement, jumping, flying, scoring, collisions).
858
+ - Match each action closely to a Scratch block or tight sequence.
859
+ - Do **NOT** include any justification or comments—only the raw logic.
860
+
861
+ 5. **Examples for reference**:
862
+ **Correct** pattern for a simple start script:
863
+ ```
864
+ when green flag clicked
865
+ switch backdrop to [blue sky v]
866
+ set [score v] to (0)
867
+ show variable [score v]
868
+ broadcast [Game Start v]
869
+ end
870
+ ```
871
+ **Correct** pattern for updating the high score variable handling:
872
+ ```
873
+ when I receive [Game Over v]
874
+ if <((score)) > (([High Score v]))> then
875
+ set [High Score v] to ([score v])
876
+ end
877
+ switch backdrop to [Game Over v]
878
+ end
879
+ ```
880
+ **Correct** pattern for level up and increase difficulty use:
881
+ ```
882
+ when I receive [Level Up v]
883
+ change [level v] by (1)
884
+ set [ballSpeed v] to ((([ballSpeed v]) * (1.1)))
885
+ end
886
+ ```
887
+ **Correct** pattern for jumping mechanics use:
888
+ ```
889
+ when [space v] key pressed
890
+ if <((y position)) = (-100)> then
891
+ repeat (5)
892
+ change y by (100)
893
+ wait (0.1) seconds
894
+ change y by (-100)
895
+ wait (0.1) seconds
896
+ end
897
+ end
898
+ end
899
+ ```
900
+ **Correct** pattern for continuos moving objects use:
901
+ ```
902
+ when green flag clicked
903
+ go to x: (240) y: (-100)
904
+ set [speed v] to (-5)
905
+ show variable [speed v]
906
+ forever
907
+ change x by ([speed v])
908
+ if <((x position)) < (-240)> then
909
+ go to x: (240) y: (-100)
910
+ end
911
+ end
912
+ end
913
+ ```
914
+ **Correct** pattern for continuos moving objects use:
915
+ ```
916
+ when green flag clicked
917
+ go to x: (240) y: (-100)
918
+ set [speed v] to (-5)
919
+ show variable [speed v]
920
+ forever
921
+ change x by ([speed v])
922
+ if <((x position)) < (-240)> then
923
+ go to x: (240) y: (-100)
924
+ end
925
+ end
926
+ end
927
+ ```
928
+ 6. **Donot** add any explaination of logic or comments to justify or explain just put the logic content in the json.
929
+ 7. **Output**:
930
+ Return **only** a JSON object, using double quotes everywhere:
931
+ ```json
932
+ {{
933
+ "refined_logic": "…your fully‑formatted pseudo‑code here…"
934
+ }}
935
+ ```
936
+ """
937
+
938
+
939
+ refinement_prompt = f"""
940
+ You are an expert in Scratch 3.0 game development, specializing in understanding block relationships (stacked, nested).
941
+ Review the following plan for '{target_name}' triggered by '{event}'.
942
+
943
+ Game Description:
944
+ {game_description}
945
+
946
+ --- Scratch 3.0 Block Reference ---
947
+ ### Hat Blocks
948
+ Description: {hat_description}
949
+ Blocks:
950
+ {hat_opcodes_functionalities}
951
+
952
+ ### Boolean Blocks
953
+ Description: {boolean_description}
954
+ Blocks:
955
+ {boolean_opcodes_functionalities}
956
+
957
+ ### C Blocks
958
+ Description: {c_description}
959
+ Blocks:
960
+ {c_opcodes_functionalities}
961
+
962
+ ### Cap Blocks
963
+ Description: {cap_description}
964
+ Blocks:
965
+ {cap_opcodes_functionalities}
966
+
967
+ ### Reporter Blocks
968
+ Description: {reporter_description}
969
+ Blocks:
970
+ {reporter_opcodes_functionalities}
971
+
972
+ ### Stack Blocks
973
+ Description: {stack_description}
974
+ Blocks:
975
+ {stack_opcodes_functionalities}
976
+ -----------------------------------
977
+ Current Plan Details:
978
+ - Event (Hat Block Opcode): {event}
979
+ - Overall plans made on {target_name} are in the list: {overall_plans_in_sprite}
980
+ - Associated Opcodes by Category: {json.dumps(opcodes, indent=2)}
981
+ - Reference code and functionality and logics script for to check is script correct: {combined_blocks}
982
+ - Current Logic Description to Update if required: "{original_logic}"
983
+
984
+ Your task is to:
985
+ 1. **Refine the 'Logic'**: Make it more precise, accurate, and fully aligned with the game mechanics described in the 'Game Description'. Ensure it uses Scratch-consistent verbs and phrasing. **Do NOT** use double quotes within the 'logic' string itself for values.
986
+ 2. The logic should build flow which can correctly describe motion or flow for e.g jumping, running, flying and other mechanics.
987
+ 3. **'logic'**: a natural language breakdown of each step taken after the event, formatted as a multi-line string representing pseudo-code. Ensure clarity and granularity—each described action should map closely to a Scratch block or tight sequence.
988
+ 4 The understading of the block generation and important instruction:
989
+ some understanding of pseudo-code as given:
990
+ logic:
991
+ "
992
+ when green flag clicked
993
+ go to x: (240) y: (-135)
994
+ set [score v] to (1)
995
+ set [speed v] to (1)
996
+ show variable [score v]
997
+ show variable [speed v]
998
+ forever
999
+ glide (2) seconds to x: (-240) y: (-135)
1000
+ if <((x position)) < (-235)> then
1001
+ set x to (240)
1002
+ end
1003
+ if <touching [Sprite1 v]?> then
1004
+ broadcast [Game Over v]
1005
+ stop [all v]
1006
+ end
1007
+ end
1008
+ end
1009
+ "
1010
+ * **Block Value Types:**
1011
+
1012
+ * `[variable v]` (e.g., `[score v]`, `[speed v]`): Denotes a **variable** within a block, such as in `set [score v] to (1)` or `show variable [speed v]`. It's distinct from selecting an option from a dropdown.
1013
+ * `[option v]` (e.g., `[space v]` in `when [space v] key pressed`): Denotes a **dropdown option** selected within a block.
1014
+ * `(10)`: Denotes a **numeric value** input into a block, like in `move (50)`.
1015
+ * `(hello)`: Denotes an **alphanumeric string value** input into a block, like in `say (hello)`.
1016
+ * `([level v])`: Denotes a **variable used as an input value** for another block, like in `set x to ([level v])`.
1017
+ * `((x position))`: Denotes an **inbuilt reporter block** used as an input value, like in `go to ((x position))`.
1018
+ * `(input)`: Parentheses `()` can enclose a value, a variable, or another block that serves as an input.
1019
+ * `<condition>`: Angle brackets `<>` enclose a **Boolean block**.
1020
+ * Nested Boolean blocks: Some Boolean blocks can contain other Boolean blocks, for example, `<not <condition>>`, `<<condition> and <condition>>`, or `<<condition> or <condition>>`.
1021
+ * **Structuring Pseudo-code:**
1022
+ * **Indentation:** Use consistent indentation to show block nesting (e.g., blocks inside a `forever` loop or an `if` statement).
1023
+ * **Stacking:** Blocks that are stacked sequentially should be on new lines without additional indentation.
1024
+ * **Comments:** Do not include comments or explanations within the pseudo-code itself; the 'logic' should be self-explanatory based on Scratch block structure.
1025
+ [Note: This understanding will let you know about the pseudo-code generation.]
1026
+ 5. Few Example of content of logics inside for a specific plan:
1027
+ - example 1[continuos moving objects]: "
1028
+ when green flag clicked
1029
+ go to x: (240) y: (-100)
1030
+ set [speed v] to (-5)
1031
+ show variable [speed v]
1032
+ forever
1033
+ change x by ([speed v])
1034
+ if <((x position)) < (-240)> then
1035
+ go to x: (240) y: (-100)
1036
+ end
1037
+ end
1038
+ end
1039
+ "
1040
+ - example 2[jumping script of an plan]: "
1041
+ when [space v] key pressed
1042
+ if <((y position)) = (-100)> then
1043
+ repeat (5)
1044
+ change y by (100)
1045
+ wait (0.1) seconds
1046
+ change y by (-100)
1047
+ wait (0.1) seconds
1048
+ end
1049
+ end
1050
+ end
1051
+ "
1052
+ - example 3[score decrement on collision]: "
1053
+ when green flag clicked
1054
+ set [score v] to (0)
1055
+ forever
1056
+ if <touching [other sprite v]?> then
1057
+ change [score v] by (-1)
1058
+ wait (0.1) seconds
1059
+ end
1060
+ end
1061
+ end
1062
+ end
1063
+ "
1064
+ 6. **Donot** add any explaination of logic or comments to justify or explain just put the logic content in the json.
1065
+ 7. Output a JSON object and [NOTE: use double quates always ""]:
1066
+ - `refined_logic`: The refined logic string.
1067
+ Output ONLY the JSON object.
1068
+ """
1069
+
1070
+
1071
+ [Overall Action Plan received at the block generator]: {
1072
+ "Stage": {
1073
+ "description": "Background and global game state management, including broadcasts, rewards, and score.",
1074
+ "plans": [
1075
+ {
1076
+ "event": "event_whenflagclicked",
1077
+ "logic": "when green flag clicked\n set [score v] to (0)\n set [lives v] to (3)\n show variable [score v]\n show variable [lives v]\n broadcast [Game Start v]",
1078
+ "motion": [],
1079
+ "control": [],
1080
+ "operator": [],
1081
+ "sensing": [],
1082
+ "looks": [],
1083
+ "sounds": [],
1084
+ "events": [
1085
+ "event_broadcast"
1086
+ ],
1087
+ "data": [
1088
+ "data_setvariableto",
1089
+ "data_showvariable"
1090
+ ],
1091
+ "opcode_counts": [
1092
+ {
1093
+ "opcode": "event_whenflagclicked",
1094
+ "count": 1
1095
+ },
1096
+ {
1097
+ "opcode": "data_setvariableto",
1098
+ "count": 2
1099
+ },
1100
+ {
1101
+ "opcode": "data_showvariable",
1102
+ "count": 2
1103
+ },
1104
+ {
1105
+ "opcode": "event_broadcast",
1106
+ "count": 1
1107
+ }
1108
+ ]
1109
+ },
1110
+ {
1111
+ "event": "event_whenbroadcastreceived",
1112
+ "logic": "when I receive [Game Over v]\n broadcast [Reset Game v]\n set [score v] to (0)\n set [lives v] to (3)",
1113
+ "motion": [],
1114
+ "control": [],
1115
+ "operator": [],
1116
+ "sensing": [],
1117
+ "looks": [],
1118
+ "sounds": [],
1119
+ "events": [],
1120
+ "data": [
1121
+ "data_setvariableto"
1122
+ ],
1123
+ "opcode_counts": [
1124
+ {
1125
+ "opcode": "event_whenbroadcastreceived",
1126
+ "count": 1
1127
+ },
1128
+ {
1129
+ "opcode": "event_broadcast",
1130
+ "count": 1
1131
+ },
1132
+ {
1133
+ "opcode": "data_setvariableto",
1134
+ "count": 2
1135
+ }
1136
+ ]
1137
+ }
1138
+ ]
1139
+ },
1140
+ "Cat": {
1141
+ "description": "Main character (cat) actions",
1142
+ "plans": [
1143
+ {
1144
+ "event": "event_whenflagclicked",
1145
+ "logic": "when green flag clicked\n go to x: (0) y: (-100)\n set [speed v] to (0)",
1146
+ "motion": [
1147
+ "motion_gotoxy"
1148
+ ],
1149
+ "control": [],
1150
+ "operator": [],
1151
+ "sensing": [],
1152
+ "looks": [],
1153
+ "sounds": [],
1154
+ "events": [],
1155
+ "data": [],
1156
+ "opcode_counts": [
1157
+ {
1158
+ "opcode": "event_whenflagclicked",
1159
+ "count": 1
1160
+ },
1161
+ {
1162
+ "opcode": "motion_gotoxy",
1163
+ "count": 1
1164
+ },
1165
+ {
1166
+ "opcode": "data_setvariableto",
1167
+ "count": 1
1168
+ }
1169
+ ]
1170
+ },
1171
+ {
1172
+ "event": "event_whenkeypressed",
1173
+ "logic": "when [right arrow v] key pressed\n change x by (10)",
1174
+ "motion": [
1175
+ "motion_changexby"
1176
+ ],
1177
+ "control": [],
1178
+ "operator": [],
1179
+ "sensing": [],
1180
+ "looks": [],
1181
+ "sounds": [],
1182
+ "events": [],
1183
+ "data": [],
1184
+ "opcode_counts": [
1185
+ {
1186
+ "opcode": "event_whenkeypressed",
1187
+ "count": 1
1188
+ },
1189
+ {
1190
+ "opcode": "motion_changexby",
1191
+ "count": 1
1192
+ }
1193
+ ]
1194
+ },
1195
+ {
1196
+ "event": "event_whenkeypressed",
1197
+ "logic": "when [left arrow v] key pressed\n change x by (-10)",
1198
+ "motion": [
1199
+ "motion_changexby"
1200
+ ],
1201
+ "control": [],
1202
+ "operator": [],
1203
+ "sensing": [],
1204
+ "looks": [],
1205
+ "sounds": [],
1206
+ "events": [],
1207
+ "data": [],
1208
+ "opcode_counts": [
1209
+ {
1210
+ "opcode": "event_whenkeypressed",
1211
+ "count": 1
1212
+ },
1213
+ {
1214
+ "opcode": "motion_changexby",
1215
+ "count": 1
1216
+ }
1217
+ ]
1218
+ },
1219
+ {
1220
+ "event": "event_whenkeypressed",
1221
+ "logic": "when [space v] key pressed\n if <((y position)) = (-100)> then \n forever\n change y by (10)\n wait (0.1) seconds\n change y by (-10)\n wait (0.1) seconds\n end\n end",
1222
+ "motion": [
1223
+ "motion_changeyby"
1224
+ ],
1225
+ "control": [
1226
+ "control_forever",
1227
+ "control_if"
1228
+ ],
1229
+ "operator": [],
1230
+ "sensing": [],
1231
+ "looks": [],
1232
+ "sounds": [],
1233
+ "events": [],
1234
+ "data": [],
1235
+ "opcode_counts": [
1236
+ {
1237
+ "opcode": "event_whenkeypressed",
1238
+ "count": 1
1239
+ },
1240
+ {
1241
+ "opcode": "control_if",
1242
+ "count": 1
1243
+ },
1244
+ {
1245
+ "opcode": "control_forever",
1246
+ "count": 1
1247
+ },
1248
+ {
1249
+ "opcode": "motion_changeyby",
1250
+ "count": 2
1251
+ }
1252
+ ]
1253
+ }
1254
+ ]
1255
+ },
1256
+ "ball": {
1257
+ "description": "Obstacle movement and interaction",
1258
+ "plans": [
1259
+ {
1260
+ "event": "event_whenflagclicked",
1261
+ "logic": "when green flag clicked\n go to x: (0) y: (100)\n set [ballSpeed v] to (5)\n forever\n glide (2) seconds to x: (-240) y: (100)\n
1262
+ if <(x position) < (-235)> then\n set x to (240)\n end\n if <touching [Cat v]?> then\n broadcast [Game Over v]\n stop [all v]\n end\n end",
1263
+ "motion": [
1264
+ "motion_gotoxy",
1265
+ "motion_glidesecstoxy",
1266
+ "motion_xposition",
1267
+ "motion_setx"
1268
+ ],
1269
+ "control": [
1270
+ "control_forever",
1271
+ "control_if",
1272
+ "control_stop"
1273
+ ],
1274
+ "operator": [
1275
+ "operator_lt"
1276
+ ],
1277
+ "sensing": [
1278
+ "sensing_istouching"
1279
+ ],
1280
+ "looks": [],
1281
+ "sounds": [],
1282
+ "events": [
1283
+ "event_broadcast"
1284
+ ],
1285
+ "data": [],
1286
+ "opcode_counts": [
1287
+ {
1288
+ "opcode": "event_whenflagclicked",
1289
+ "count": 1
1290
+ },
1291
+ {
1292
+ "opcode": "motion_gotoxy",
1293
+ "count": 1
1294
+ },
1295
+ {
1296
+ "opcode": "motion_glidesecstoxy",
1297
+ "count": 1
1298
+ },
1299
+ {
1300
+ "opcode": "motion_xposition",
1301
+ "count": 1
1302
+ },
1303
+ {
1304
+ "opcode": "motion_setx",
1305
+ "count": 1
1306
+ },
1307
+ {
1308
+ "opcode": "control_forever",
1309
+ "count": 1
1310
+ },
1311
+ {
1312
+ "opcode": "control_if",
1313
+ "count": 2
1314
+ },
1315
+ {
1316
+ "opcode": "operator_lt",
1317
+ "count": 1
1318
+ },
1319
+ {
1320
+ "opcode": "sensing_istouching",
1321
+ "count": 1
1322
+ },
1323
+ {
1324
+ "opcode": "event_broadcast",
1325
+ "count": 1
1326
+ },
1327
+ {
1328
+ "opcode": "control_stop",
1329
+ "count": 1
1330
+ },
1331
+ {
1332
+ "opcode": "data_setvariableto",
1333
+ "count": 1
1334
+ }
1335
+ ]
1336
+ }
1337
+ ]
1338
+ }
1339
+ }
1340
+ [ALL OPCODE BLCOKS KEY 2]: {'event_whenflagclicked_1': {'block_name': 'when green flag pressed', 'block_type': 'Events', 'op_code': 'event_whenflagclicked', 'block_shape': 'Hat Block', 'functionality': 'This Hat block initiates the script when the green flag is clicked, serving as the common starting point for most Scratch projects.', 'inputs': {}, 'fields': {}, 'shadow': False, 'topLevel': True, 'id': 'event_whenflagclicked_1', 'parent': None, 'next': 'data_setvariableto_1'}, 'data_setvariableto_1': {'block_name': 'set [my variable v] to ()', 'block_type': 'Data', 'block_shape': 'Stack Block', 'op_code': 'data_setvariableto', 'functionality': 'Assigns a specific value (number, string, or boolean) to a variable.', 'inputs': {'VALUE': {'kind': 'value', 'value': 0}}, 'fields': {'VARIABLE': ['score', None]}, 'shadow': False, 'topLevel': False, 'id': 'data_setvariableto_1', 'parent': 'event_whenflagclicked_1', 'next': 'data_setvariableto_2'}, 'data_setvariableto_2': {'block_name': 'set [my variable v] to ()', 'block_type': 'Data', 'block_shape': 'Stack Block', 'op_code': 'data_setvariableto', 'functionality': 'Assigns a specific value (number, string, or boolean) to a variable.', 'inputs': {'VALUE': {'kind': 'value', 'value': 3}}, 'fields': {'VARIABLE': ['lives', None]}, 'shadow': False, 'topLevel': False, 'id': 'data_setvariableto_2', 'parent': 'data_setvariableto_1', 'next': 'data_showvariable_1'}, 'data_showvariable_1': {'block_name': 'show variable [my variable v]', 'block_type': 'Data', 'block_shape': 'Stack Block', 'op_code': 'data_showvariable', 'functionality': "Makes a variable's monitor visible on the stage.", 'inputs': {}, 'fields': {'VARIABLE': ['score', None]}, 'shadow': False, 'topLevel': False, 'id': 'data_showvariable_1', 'parent': 'data_setvariableto_2', 'next': 'data_showvariable_2'}, 'data_showvariable_2': {'block_name': 'show variable [my variable v]', 'block_type': 'Data', 'block_shape': 'Stack Block', 'op_code': 'data_showvariable', 'functionality': "Makes a variable's monitor visible on the stage.", 'inputs': {}, 'fields': {'VARIABLE': ['lives', None]}, 'shadow': False, 'topLevel': False, 'id': 'data_showvariable_2', 'parent': 'data_showvariable_1', 'next': 'event_broadcast_1'}, 'event_broadcast_1': {'block_name': 'broadcast ()', 'block_type': 'Events', 'block_shape': 'Stack Block', 'op_code': 'event_broadcast', '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.", 'inputs': {'BROADCAST_INPUT': {'kind': 'menu', 'option': 'Game Start'}}, 'fields': {}, 'shadow': False, 'topLevel': False, 'id': 'event_broadcast_1', 'parent': 'data_showvariable_2', 'next': None}}
1341
+ 2025-07-25 14:08:27,550 - __main__ - INFO - Action blocks added for sprite 'Stage' by OverallBlockBuilderNode.
1342
+ [ALL OPCODE BLCOKS KEY 2]: {'event_whenbroadcastreceived_1': {'block_name': 'when I receive ()', 'block_type': 'Events', 'op_code': 'event_whenbroadcastreceived', 'block_shape': 'Hat Block', '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.', 'inputs': {}, 'fields': {'BROADCAST_OPTION': ['Game Over ', None]}, 'shadow': False, 'topLevel': True, 'id': 'event_whenbroadcastreceived_1', 'parent': None, 'next': 'event_broadcast_1'}, 'event_broadcast_1': {'block_name': 'broadcast ()', 'block_type': 'Events', 'block_shape': 'Stack Block', 'op_code': 'event_broadcast', '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.", 'inputs': {'BROADCAST_INPUT': {'kind': 'menu', 'option': 'Reset Game'}}, 'fields': {}, 'shadow': False, 'topLevel': False, 'id': 'event_broadcast_1', 'parent': 'event_whenbroadcastreceived_1', 'next': 'data_setvariableto_1'}, 'data_setvariableto_1': {'block_name': 'set [my variable v] to ()', 'block_type': 'Data', 'block_shape': 'Stack Block', 'op_code': 'data_setvariableto', 'functionality': 'Assigns a specific value (number, string, or boolean) to a variable.', 'inputs': {'VALUE': {'kind': 'value', 'value': 0}}, 'fields': {'VARIABLE': ['score', None]}, 'shadow': False, 'topLevel': False, 'id': 'data_setvariableto_1', 'parent': 'event_broadcast_1', 'next': 'data_setvariableto_2'}, 'data_setvariableto_2': {'block_name': 'set [my variable v] to ()', 'block_type': 'Data', 'block_shape': 'Stack Block', 'op_code': 'data_setvariableto', 'functionality': 'Assigns a specific value (number, string, or boolean) to a variable.', 'inputs': {'VALUE': {'kind': 'value', 'value': 3}}, 'fields': {'VARIABLE': ['lives', None]}, 'shadow': False, 'topLevel': False, 'id': 'data_setvariableto_2', 'parent': 'data_setvariableto_1', 'next': None}}
1343
+ 2025-07-25 14:08:27,551 - __main__ - INFO - Action blocks added for sprite 'Stage' by OverallBlockBuilderNode.
1344
+ [ALL OPCODE BLCOKS KEY 2]: {'event_whenflagclicked_1': {'block_name': 'when green flag pressed', 'block_type': 'Events', 'op_code': 'event_whenflagclicked', 'block_shape': 'Hat Block', 'functionality': 'This Hat block initiates the script when the green flag is clicked, serving as the common starting point for most Scratch projects.', 'inputs': {}, 'fields': {}, 'shadow': False, 'topLevel': True, 'id': 'event_whenflagclicked_1', 'parent': None, 'next': 'motion_gotoxy_1'}, 'motion_gotoxy_1': {'block_name': 'go to x: () y: ()', 'block_type': 'Motion', 'block_shape': 'Stack Block', 'op_code': 'motion_gotoxy', 'functionality': 'Moves the sprite to the specified X and Y coordinates on the stage.', 'inputs': {'X': {'kind': 'value', 'value': 0}, 'Y': {'kind': 'value', 'value': -100}}, 'fields': {}, 'shadow': False, 'topLevel': False, 'id': 'motion_gotoxy_1', 'parent': 'event_whenflagclicked_1', 'next': 'data_setvariableto_1'}, 'data_setvariableto_1': {'block_name': 'set [my variable v] to ()', 'block_type': 'Data', 'block_shape': 'Stack Block', 'op_code': 'data_setvariableto', 'functionality': 'Assigns a specific value (number, string, or boolean) to a variable.', 'inputs': {'VALUE': {'kind': 'value', 'value': 0}}, 'fields': {'VARIABLE': ['speed', None]}, 'shadow': False, 'topLevel': False, 'id': 'data_setvariableto_1', 'parent': 'motion_gotoxy_1', 'next': None}}
1345
+ 2025-07-25 14:08:27,552 - __main__ - INFO - Action blocks added for sprite 'Cat' by OverallBlockBuilderNode.
1346
+ [ALL OPCODE BLCOKS KEY 2]: {'event_whenkeypressed_1': {'block_name': 'when () key pressed', 'block_type': 'Events', 'op_code': 'event_whenkeypressed', 'block_shape': 'Hat Block', 'functionality': 'This Hat block initiates the script when a specified keyboard key is pressed.', 'inputs': {}, 'fields': {'KEY_OPTION': ['right arrow ', None]}, 'shadow': False, 'topLevel': True, 'id': 'event_whenkeypressed_1', 'parent': None, 'next': 'motion_changexby_1'}, 'motion_changexby_1': {'block_name': 'change x by ()', 'block_type': 'Motion', 'block_shape': 'Stack Block', 'op_code': 'motion_changexby', 'functionality': "Changes the sprite's X-coordinate by the specified amount, moving it horizontally.", 'inputs': {'DX': {'kind': 'value', 'value': 10}}, 'fields': {}, 'shadow': False, 'topLevel': False, 'id': 'motion_changexby_1', 'parent': 'event_whenkeypressed_1', 'next': None}}
1347
+ 2025-07-25 14:08:27,553 - __main__ - INFO - Action blocks added for sprite 'Cat' by OverallBlockBuilderNode.
1348
+ [ALL OPCODE BLCOKS KEY 2]: {'event_whenkeypressed_1': {'block_name': 'when () key pressed', 'block_type': 'Events', 'op_code': 'event_whenkeypressed', 'block_shape': 'Hat Block', 'functionality': 'This Hat block initiates the script when a specified keyboard key is pressed.', 'inputs': {}, 'fields': {'KEY_OPTION': ['left arrow ', None]}, 'shadow': False, 'topLevel': True, 'id': 'event_whenkeypressed_1', 'parent': None, 'next': 'motion_changexby_1'}, 'motion_changexby_1': {'block_name': 'change x by ()', 'block_type': 'Motion', 'block_shape': 'Stack Block', 'op_code': 'motion_changexby', 'functionality': "Changes the sprite's X-coordinate by the specified amount, moving it horizontally.", 'inputs': {'DX': {'kind': 'value', 'value': -10}}, 'fields': {}, 'shadow': False, 'topLevel': False, 'id': 'motion_changexby_1', 'parent': 'event_whenkeypressed_1', 'next': None}}
1349
+ 2025-07-25 14:08:27,554 - __main__ - INFO - Action blocks added for sprite 'Cat' by OverallBlockBuilderNode.
1350
+ [THE CONDA MATCH]------------->((y position)) = (-100)
1351
+ the stmt was this ((y position)) = (-100) and parsed was this ((y position)) = (-100)
1352
+ [ALL OPCODE BLCOKS KEY 2]: {'event_whenkeypressed_1': {'block_name': 'when () key pressed', 'block_type': 'Events', 'op_code': 'event_whenkeypressed', 'block_shape': 'Hat Block', 'functionality': 'This Hat block initiates the script when a specified keyboard key is pressed.', 'inputs': {}, 'fields': {'KEY_OPTION': ['space ', None]}, 'shadow': False, 'topLevel': True, 'id': 'event_whenkeypressed_1', 'parent': None, 'next': 'control_if_1'}, 'control_if_1': {'block_name': 'if <> then', 'block_type': 'Control', 'block_shape': 'C-Block', 'op_code': 'control_if', 'functionality': 'Executes the blocks inside it only if the specified boolean condition is true. [NOTE: it takes boolean blocks as input]', 'inputs': {'CONDITION': {'kind': 'block', 'block': 'operator_equals_1'}, 'SUBSTACK': [2, 'control_forever_1']}, 'fields': {}, 'shadow': False, 'topLevel': False, 'id': 'control_if_1', 'parent': 'event_whenkeypressed_1', 'next': None}, 'motion_yposition_1': {'block_name': '(y position)', 'block_type': 'Motion', 'block_shape': 'Reporter Block', 'op_code': 'motion_yposition', 'functionality': 'Reports the current Y coordinate of the sprite on the stage.[NOTE: not used in stage/backdrops]', 'inputs': {}, 'fields': {}, 'shadow': False, 'topLevel': False, 'id': 'motion_yposition_1', 'parent': 'operator_equals_1', 'next': None}, 'operator_equals_1': {'block_name': '<() = ()>', 'block_type': 'operator', 'block_shape': 'Boolean Block', 'op_code': 'operator_equals', 'functionality': 'Checks if two values are equal.', 'inputs': {'OPERAND1': {'kind': 'block', 'block': 'motion_yposition_1'}, 'OPERAND2': {'kind': 'value', 'value': -100}}, 'fields': {}, 'shadow': False, 'topLevel': False, 'id': 'operator_equals_1', 'parent': 'control_if_1', 'next': None}, 'control_forever_1': {'block_name': 'forever', 'block_type': 'Control', 'block_shape': 'C-Block', 'op_code': 'control_forever', 'functionality': 'Continuously runs the blocks inside it.', 'inputs': {'SUBSTACK': [2, 'motion_changeyby_1']}, 'fields': {}, 'shadow': False, 'topLevel': False, 'id': 'control_forever_1', 'parent': 'control_if_1', 'next': None}, 'motion_changeyby_1': {'block_name': 'change y by ()', 'block_type': 'Motion', 'block_shape': 'Stack Block', 'op_code': 'motion_changeyby', 'functionality': "Changes the sprite's Y-coordinate by the specified amount, moving it vertically.", 'inputs': {'DY': {'kind': 'value', 'value': 10}}, 'fields': {}, 'shadow': False, 'topLevel': False, 'id': 'motion_changeyby_1', 'parent': 'control_forever_1', 'next': 'control_wait_1'}, 'control_wait_1': {'block_name': 'wait () seconds', 'block_type': 'Control', 'block_shape': 'Stack Block', 'op_code': 'control_wait', 'functionality': 'Pauses the script for a specified duration.', 'inputs': {'DURATION': {'kind': 'value', 'value': 0.1}}, 'fields': {}, 'shadow': False, 'topLevel': False, 'id': 'control_wait_1', 'parent': 'motion_changeyby_1', 'next': 'motion_changeyby_2'}, 'motion_changeyby_2': {'block_name': 'change y by ()', 'block_type': 'Motion', 'block_shape': 'Stack Block', 'op_code': 'motion_changeyby', 'functionality': "Changes the sprite's Y-coordinate by the specified amount, moving it vertically.", 'inputs': {'DY': {'kind': 'value', 'value': -10}}, 'fields': {}, 'shadow': False, 'topLevel': False, 'id': 'motion_changeyby_2', 'parent': 'control_wait_1', 'next': 'control_wait_2'}, 'control_wait_2': {'block_name': 'wait () seconds', 'block_type': 'Control', 'block_shape': 'Stack Block', 'op_code': 'control_wait', 'functionality': 'Pauses the script for a specified duration.', 'inputs': {'DURATION': {'kind': 'value', 'value': 0.1}}, 'fields': {}, 'shadow': False, 'topLevel': False, 'id': 'control_wait_2', 'parent': 'motion_changeyby_2', 'next': None}}
1353
+ 2025-07-25 14:08:27,557 - __main__ - INFO - Action blocks added for sprite 'Cat' by OverallBlockBuilderNode.
1354
+ Error generating plan from blocks: Can't parse reporter or value: 2) seconds to x: (
1355
+ 2025-07-25 14:08:27,560 - __main__ - INFO - Action blocks added for sprite 'ball' by OverallBlockBuilderNode.
1356
+ 2025-07-25 14:08:27,562 - __main__ - INFO - Final project JSON saved to generated_projects\8f9aca5d-60b6-4ca3-b9c9-d69410033020\project.json
1357
+ 2025-07-25 14:08:27,586 - __main__ - INFO - Project folder zipped to: D:\DEV PATEL\2025\scratch_VLM\scratch_agent\generated_projects\8f9aca5d-60b6-4ca3-b9c9-d69410033020.zip
1358
+ 2025-07-25 14:08:27,586 - __main__ - INFO - Renamed D:\DEV PATEL\2025\scratch_VLM\scratch_agent\generated_projects\8f9aca5d-60b6-4ca3-b9c9-d69410033020.zip to generated_projects\8f9aca5d-60b6-4ca3-b9c9-d69410033020.sb3
1359
+ 2025-07-25 14:08:27,587 - __main__ - INFO - Successfully created SB3 file: generated_projects\8f9aca5d-60b6-4ca3-b9c9-d69410033020.sb3
1360
+ 2025-07-25 14:08:27,587 - werkzeug - INFO - 127.0.0.1 - - [25/Jul/2025 14:08:27] "POST /generate_game HTTP/1.1" 200 -
1361
+
1362
+ Raw response from LLM [OverallPlannerNode 2]: ```json
1363
+ {
1364
+ "action_overall_flow": {
1365
+ "Stage": {
1366
+ "description": "Background and global game state management, including broadcasts, rewards, and score.",
1367
+ "plans": [
1368
+ {
1369
+ "event": "event_whenflagclicked",
1370
+ "logic": "when green flag clicked\nswitch backdrop to [blue sky v]\nset [score v] to (0)\nshow variable [score v]\nbroadcast [Game Start v]",
1371
+ "motion": [],
1372
+ "control": [],
1373
+ "operator": [],
1374
+ "sensing": [],
1375
+ "looks": [
1376
+ "looks_switchbackdropto"
1377
+ ],
1378
+ "sounds": [],
1379
+ "events": [
1380
+ "event_broadcast"
1381
+ ],
1382
+ "data": [
1383
+ "data_setvariableto",
1384
+ "data_showvariable"
1385
+ ]
1386
+ },
1387
+ {
1388
+ "event": "event_whenbroadcastreceived",
1389
+ "logic": "when I receive [Game Over v]\nif <(score) > ([High Score v])> then\nset [High Score v] to (score)\nend\nswitch backdrop to [Game Over v]",
1390
+ "motion": [],
1391
+ "control": [
1392
+ "control_if"
1393
+ ],
1394
+ "operator": [
1395
+ "operator_gt"
1396
+ ],
1397
+ "sensing": [],
1398
+ "looks": [
1399
+ "looks_switchbackdropto"
1400
+ ],
1401
+ "sounds": [],
1402
+ "events": [],
1403
+ "data": [
1404
+ "data_setvariableto"
1405
+ ]
1406
+ }
1407
+ ]
1408
+ },
1409
+ "Cat": {
1410
+ "description": "Main character (cat) actions",
1411
+ "plans": [
1412
+ {
1413
+ "event": "event_whenflagclicked",
1414
+ "logic": "when green flag clicked\ngo to x: (0) y: (-120)\nend",
1415
+ "motion": [
1416
+ "motion_gotoxy"
1417
+ ],
1418
+ "control": [],
1419
+ "operator": [],
1420
+ "sensing": [],
1421
+ "looks": [],
1422
+ "sounds": [],
1423
+ "events": [],
1424
+ "data": []
1425
+ },
1426
+ {
1427
+ "event": "event_whenkeypressed",
1428
+ "logic": "when [space v] key pressed\nif <((y position)) = (-120)> then\nrepeat (10)\nchange y by (10)\nwait (0.1) seconds\nchange y by (-10)\nwait (0.1) seconds\nend\nend",
1429
+ "motion": [
1430
+ "motion_changeyby"
1431
+ ],
1432
+ "control": [
1433
+ "control_repeat",
1434
+ "control_if",
1435
+ "control_wait"
1436
+ ],
1437
+ "operator": [],
1438
+ "sensing": [],
1439
+ "looks": [],
1440
+ "sounds": [],
1441
+ "events": [],
1442
+ "data": []
1443
+ }
1444
+ ]
1445
+ },
1446
+ "ball": {
1447
+ "description": "Obstacle movement and interaction",
1448
+ "plans": [
1449
+ {
1450
+ "event": "event_whenflagclicked",
1451
+ "logic": "when green flag clicked\ngo to x: (130) y: (0)\nforever\nchange x by (-5)\nif <((x position)) < (-130)> then\nset x to (130)\nend\nif <touching [Cat v]?> then\nbroadcast [Game Over v]\nstop [all v]\nend\nend",
1452
+ "motion": [
1453
+ "motion_gotoxy",
1454
+ "motion_changexby",
1455
+ "motion_setx"
1456
+ ],
1457
+ "control": [
1458
+ "control_forever",
1459
+ "control_if",
1460
+ "control_stop"
1461
+ ],
1462
+ "operator": [],
1463
+ "sensing": [
1464
+ "sensing_touchingobject"
1465
+ ],
1466
+ "looks": [],
1467
+ "sounds": [],
1468
+ "events": [
1469
+ "event_broadcast"
1470
+ ],
1471
+ "data": []
1472
+ }
1473
+ ]
1474
+ }
1475
+ }
1476
+ }
1477
+ ```
1478
+
1479
+ [OVREALL REFINED LOGIC]: {'Stage': {'description': 'Background and global game state management, including broadcasts, rewards, and score.', 'plans': [{'event': 'event_whenflagclicked', 'logic': '\nwhen green flag clicked\n switch backdrop to [blue sky v]\n set [score v] to (0)\n show variable [score v]\n broadcast [Game Start v]\nend\n', 'motion': [], 'control': [], 'operator': [], 'sensing': [], 'looks': ['looks_switchbackdropto'], 'sounds': [], 'events': ['event_broadcast'], 'data': ['data_setvariableto', 'data_showvariable']}, {'event': 'event_whenbroadcastreceived', 'logic': '\n when I receive [Game Over v]\n if <((score) > (([High Score v])))> then\n set [High Score v] to ((score) v)\n end\n switch backdrop to [Game Over v]\n end\n', 'motion': [], 'control': ['control_if'], 'operator': ['operator_gt'], 'sensing': [], 'looks': ['looks_switchbackdropto'], 'sounds': [], 'events': [], 'data': ['data_setvariableto']}]}, 'Cat': {'description': 'Main character (cat) actions', 'plans': [{'event': 'event_whenflagclicked', 'logic': '\nwhen green flag clicked\n go to x: (0) y: (-120)\n set [score v] to (0)\n show variable [score v]\n forever\n change x by (5)\n if <((x position)) > (240)> then\n go to x: (-240) y: ((y position))\n end\n if <((y position)) < (-150)> then\n change y by (5)\n end\n if <((y position)) > (150)> then\n change y by (-5)\n end\n end\nend\n', 'motion': ['motion_gotoxy'], 'control': [], 'operator': [], 'sensing': [], 'looks': [], 'sounds': [], 'events': [], 'data': []}, {'event': 'event_whenkeypressed', 'logic': '\nwhen [space v] key pressed\n if <((y position)) = (-120)> then\n set [jump height v] to (10)\n set [gravity v] to (1)\n repeat (10)\n change y by (([jump height v]) * (1))\n set [jump height v] to (([jump height v]) - ([gravity v])))\n wait (0.1) seconds\n change y by (-(([jump height v]) * (1)))\n wait (0.1) seconds\n end\n end\nend\n', 'motion': ['motion_changeyby'], 'control': ['control_repeat', 'control_if', 'control_wait'], 'operator': [], 'sensing': [], 'looks': [], 'sounds': [], 'events': [], 'data': []}]}, 'ball': {'description': 'Obstacle movement and interaction', 'plans': [{'event': 'event_whenflagclicked', 'logic': '\nwhen green flag clicked\ngo to x: (130) y: (0)\nforever\nchange x by (-5)\nif <((x position)) < (-130)> then\nset x to (130)\nend\nif <touching [Cat v]?> then\nbroadcast [Game Over v]\nstop [all v]\nend\nend\n', 'motion': ['motion_gotoxy', 'motion_changexby', 'motion_setx'], 'control': ['control_forever', 'control_if', 'control_stop'], 'operator': [], 'sensing': ['sensing_touchingobject'], 'looks': [], 'sounds': [], 'events': ['event_broadcast'], 'data': []}]}}
1480
+
1481
+
1482
+ [Refined Action Plan]: {
1483
+ "action_overall_flow": {
1484
+ "Stage": {
1485
+ "description": "Background and global game state management, including broadcasts, rewards, and score.",
1486
+ "plans": [
1487
+ {
1488
+ "event": "event_whenflagclicked",
1489
+ "logic": "when green flag clicked\n set [score v] to (0)\n show variable [score v]\n broadcast [Game Start v]",
1490
+ "control": [
1491
+ "control_broadcast"
1492
+ ],
1493
+ "data": [
1494
+ "data_setvariableto",
1495
+ "data_showvariable"
1496
+ ],
1497
+ "events": [
1498
+ "event_broadcast"
1499
+ ]
1500
+ },
1501
+ {
1502
+ "event": "event_whenbroadcastreceived",
1503
+ "logic": "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 [Game Over v]",
1504
+ "control": [
1505
+ "control_if"
1506
+ ],
1507
+ "operator": [
1508
+ "operator_gt"
1509
+ ],
1510
+ "looks": [
1511
+ "looks_switchbackdropto"
1512
+ ],
1513
+ "data": [
1514
+ "data_setvariableto"
1515
+ ]
1516
+ }
1517
+ ]
1518
+ },
1519
+ "Cat": {
1520
+ "description": "Main character (cat) actions",
1521
+ "plans": [
1522
+ {
1523
+ "event": "event_whenflagclicked",
1524
+ "logic": "when green flag clicked\n go to x: (0) y: (-50)\n set [speed v] to (5)\n forever\n change x by ([speed v])\n if <((x position)) > (240)> then\n set x to (-240)\n end\n if <((x position)) < (-240)> then\n set x to (240)\n end\n end",
1525
+ "motion": [
1526
+ "motion_gotoxy",
1527
+ "motion_changexby"
1528
+ ],
1529
+ "control": [
1530
+ "control_forever",
1531
+ "control_if"
1532
+ ],
1533
+ "operator": [
1534
+ "operator_gt",
1535
+ "operator_lt"
1536
+ ]
1537
+ },
1538
+ {
1539
+ "event": "event_whenkeypressed",
1540
+ "logic": "when [space v] key pressed\n change y by (100)\n wait (0.2) seconds\n change y by (-100)",
1541
+ "motion": [
1542
+ "motion_changeyby"
1543
+ ],
1544
+ "control": [
1545
+ "control_wait"
1546
+ ]
1547
+ }
1548
+ ]
1549
+ },
1550
+ "ball": {
1551
+ "description": "Obstacle movement and interaction",
1552
+ "plans": [
1553
+ {
1554
+ "event": "event_whenflagclicked",
1555
+ "logic": "when green flag clicked\n go to x: (100) y: (50)\n forever\n glide (2) seconds to x: (-240) y: (50)\n if <((x position)) < (-235)> then\n set x to (240)\n end\n if <touching [Cat v]?> then\n broadcast [Game Over v]\n stop [all v]\n end\n end",
1556
+ "motion": [
1557
+ "motion_gotoxy",
1558
+ "motion_glidesecstoxy",
1559
+ "motion_xposition",
1560
+ "motion_setx"
1561
+ ],
1562
+ "control": [
1563
+ "control_forever",
1564
+ "control_if",
1565
+ "control_stop"
1566
+ ],
1567
+ "sensing": [
1568
+ "sensing_istouching"
1569
+ ],
1570
+ "events": [
1571
+ "event_broadcast"
1572
+ ]
1573
+ }
1574
+ ]
1575
+ }
1576
+ }
1577
+ }
v2/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]
v2/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()
v2/utils/all_analysis.txt ADDED
File without changes
v2/utils/all_analysis_trace.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ Unknown statement: 'glide 2 seconds to x: (pick random -240 to 240) y: 100'
2
+ when green flag clicked
3
+ go to x: 0 y: 100
4
+ forever
5
+ glide 2 seconds to x: (pick random -240 to 240) y: 100
6
+ if <touching [Cat v]?> then
7
+ broadcast [Game Over v]
8
+ end
9
+ end
10
+
v2/utils/all_generated_blocks.json ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "event_whenkeypressed_1": {
3
+ "block_name": "when () key pressed",
4
+ "block_type": "Events",
5
+ "op_code": "event_whenkeypressed",
6
+ "block_shape": "Hat Block",
7
+ "functionality": "This Hat block initiates the script when a specified keyboard key is pressed.",
8
+ "inputs": {},
9
+ "fields": {
10
+ "KEY_OPTION": [
11
+ "space ",
12
+ null
13
+ ]
14
+ },
15
+ "shadow": false,
16
+ "topLevel": true,
17
+ "id": "event_whenkeypressed_1",
18
+ "parent": null,
19
+ "next": "motion_changeyby_1"
20
+ },
21
+ "motion_changeyby_1": {
22
+ "block_name": "change y by ()",
23
+ "block_type": "Motion",
24
+ "block_shape": "Stack Block",
25
+ "op_code": "motion_changeyby",
26
+ "functionality": "Changes the sprite's Y-coordinate by the specified amount, moving it vertically.",
27
+ "inputs": {
28
+ "DY": {
29
+ "kind": "value",
30
+ "value": 10
31
+ }
32
+ },
33
+ "fields": {},
34
+ "shadow": false,
35
+ "topLevel": false,
36
+ "id": "motion_changeyby_1",
37
+ "parent": "event_whenkeypressed_1",
38
+ "next": "control_wait_1"
39
+ },
40
+ "motion_changeyby_2": {
41
+ "block_name": "change y by ()",
42
+ "block_type": "Motion",
43
+ "block_shape": "Stack Block",
44
+ "op_code": "motion_changeyby",
45
+ "functionality": "Changes the sprite's Y-coordinate by the specified amount, moving it vertically.",
46
+ "inputs": {
47
+ "DY": {
48
+ "kind": "value",
49
+ "value": -10
50
+ }
51
+ },
52
+ "fields": {},
53
+ "shadow": false,
54
+ "topLevel": false,
55
+ "id": "motion_changeyby_2",
56
+ "parent": "control_wait_1",
57
+ "next": null
58
+ },
59
+ "control_wait_1": {
60
+ "block_name": "wait () seconds",
61
+ "block_type": "Control",
62
+ "block_shape": "Stack Block",
63
+ "op_code": "control_wait",
64
+ "functionality": "Pauses the script for a specified duration.",
65
+ "inputs": {
66
+ "DURATION": {
67
+ "kind": "value",
68
+ "value": 0.2
69
+ }
70
+ },
71
+ "fields": {},
72
+ "shadow": false,
73
+ "topLevel": false,
74
+ "id": "control_wait_1",
75
+ "parent": "motion_changeyby_1",
76
+ "next": "motion_changeyby_2"
77
+ }
78
+ }
v2/utils/block_builder_main.py ADDED
@@ -0,0 +1,446 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import copy
3
+ import re
4
+ from collections import defaultdict
5
+ import secrets
6
+ import string
7
+ from typing import Dict, Any, TypedDict
8
+ from plan_generator_10 import generate_plan,generate_blocks_from_opcodes,all_block_definitions
9
+
10
+ #################################################################################################################################################################
11
+ #--------------------------------------------------[Security key id generation for the better understanding of keys]---------------------------------------------
12
+ #################################################################################################################################################################
13
+
14
+ def generate_secure_token(length=20):
15
+ charset = string.ascii_letters + string.digits + "!@#$%^&*()[]{}=+-_~"
16
+ return ''.join(secrets.choice(charset) for _ in range(length))
17
+
18
+ #################################################################################################################################################################
19
+ #--------------------------------------------------[Processed the two Skelton as input and generate refined skelton json]----------------------------------------
20
+ #################################################################################################################################################################
21
+
22
+ def process_scratch_blocks(all_generated_blocks, generated_output_json):
23
+
24
+ processed_blocks = {}
25
+
26
+ # Initialize dictionaries to store and reuse generated unique IDs
27
+ # This prevents creating multiple unique IDs for the same variable/broadcast across different blocks
28
+ variable_id_map = defaultdict(lambda: generate_secure_token(20))
29
+ broadcast_id_map = defaultdict(lambda: generate_secure_token(20))
30
+
31
+ for block_id, gen_block_data in generated_output_json.items():
32
+ processed_block = {}
33
+ all_gen_block_data = all_generated_blocks.get(block_id, {})
34
+
35
+ # Copy and update fields, inputs, next, parent, shadow, topLevel, mutation, and opcode
36
+ processed_block["opcode"] = all_gen_block_data.get("op_code", gen_block_data.get("op_code"))
37
+ processed_block["inputs"] = {}
38
+ processed_block["fields"] = {}
39
+ processed_block["shadow"] = all_gen_block_data.get("shadow", gen_block_data.get("shadow"))
40
+ processed_block["topLevel"] = all_gen_block_data.get("topLevel", gen_block_data.get("topLevel"))
41
+ processed_block["parent"] = all_gen_block_data.get("parent", gen_block_data.get("parent"))
42
+ processed_block["next"] = all_gen_block_data.get("next", gen_block_data.get("next"))
43
+ if "mutation" in all_gen_block_data:
44
+ processed_block["mutation"] = all_gen_block_data["mutation"]
45
+
46
+ # Process inputs
47
+ if "inputs" in all_gen_block_data:
48
+ for input_name, input_data in all_gen_block_data["inputs"].items():
49
+ if input_name in ["SUBSTACK", "CONDITION"]:
50
+ # These should always be type 2
51
+ if isinstance(input_data, list) and len(input_data) == 2:
52
+ processed_block["inputs"][input_name] = [2, input_data[1]]
53
+ elif isinstance(input_data, dict) and input_data.get("kind") == "block":
54
+ processed_block["inputs"][input_name] = [2, input_data.get("block")]
55
+ else: # Fallback for unexpected formats, try to use the original if possible
56
+ processed_block["inputs"][input_name] = gen_block_data["inputs"].get(input_name, [2, None])
57
+
58
+ elif isinstance(input_data, dict):
59
+ if input_data.get("kind") == "value":
60
+ # Case 1: Direct value input
61
+ processed_block["inputs"][input_name] = [
62
+ 1,
63
+ [
64
+ 4,
65
+ str(input_data.get("value", ""))
66
+ ]
67
+ ]
68
+ elif input_data.get("kind") == "block":
69
+ # Case 3: Nested block input
70
+ existing_shadow_value = ""
71
+ if input_name in gen_block_data.get("inputs", {}) and \
72
+ isinstance(gen_block_data["inputs"][input_name], list) and \
73
+ len(gen_block_data["inputs"][input_name]) > 2 and \
74
+ isinstance(gen_block_data["inputs"][input_name][2], list) and \
75
+ len(gen_block_data["inputs"][input_name][2]) > 1:
76
+ existing_shadow_value = gen_block_data["inputs"][input_name][2][1]
77
+
78
+ processed_block["inputs"][input_name] = [
79
+ 3,
80
+ input_data.get("block", ""),
81
+ [
82
+ 10, # Assuming 10 for number/string shadow
83
+ existing_shadow_value
84
+ ]
85
+ ]
86
+ elif input_data.get("kind") == "menu":
87
+ # Handle menu inputs like in event_broadcast
88
+ menu_option = input_data.get("option", "")
89
+
90
+ # Generate or retrieve a unique ID for the broadcast message
91
+ broadcast_id = broadcast_id_map[menu_option] # Use defaultdict for unique IDs
92
+
93
+ processed_block["inputs"][input_name] = [
94
+ 1,
95
+ [
96
+ 11, # This is typically the code for menu dropdowns
97
+ menu_option,
98
+ broadcast_id
99
+ ]
100
+ ]
101
+ elif isinstance(input_data, list):
102
+ # For cases like TOUCHINGOBJECTMENU, where input_data is a list [1, "block_id"]
103
+ processed_block["inputs"][input_name] = input_data
104
+
105
+
106
+ # Process fields
107
+ if "fields" in all_gen_block_data:
108
+ for field_name, field_value in all_gen_block_data["fields"].items():
109
+ if field_name == "VARIABLE" and isinstance(field_value, list) and len(field_value) > 0:
110
+ # Generate or retrieve a unique ID for the variable
111
+ variable_name = field_value[0]
112
+ unique_id = variable_id_map[variable_name] # Use defaultdict for unique IDs
113
+
114
+ processed_block["fields"][field_name] = [
115
+ variable_name,
116
+ unique_id
117
+ ]
118
+ elif field_name == "STOP_OPTION":
119
+ processed_block["fields"][field_name] = [
120
+ field_value[0],
121
+ None
122
+ ]
123
+ elif field_name == "TOUCHINGOBJECTMENU":
124
+ referenced_menu_block_id = all_gen_block_data["inputs"].get("TOUCHINGOBJECTMENU", [None, None])[1]
125
+ if referenced_menu_block_id and referenced_menu_block_id in all_generated_blocks:
126
+ menu_block = all_generated_blocks[referenced_menu_block_id]
127
+ menu_value = menu_block.get("fields", {}).get("TOUCHINGOBJECTMENU", ["", None])[0]
128
+ processed_block["fields"][field_name] = [menu_value, None]
129
+ else:
130
+ processed_block["fields"][field_name] = [field_value[0], None]
131
+ else:
132
+ processed_block["fields"][field_name] = field_value
133
+
134
+ # Remove unwanted keys from the processed block
135
+ keys_to_remove = ["functionality", "block_shape", "id", "block_name", "block_type"]
136
+ for key in keys_to_remove:
137
+ if key in processed_block:
138
+ del processed_block[key]
139
+
140
+ processed_blocks[block_id] = processed_block
141
+ return processed_blocks
142
+ #################################################################################################################################################################
143
+ #--------------------------------------------------[Unique secret key for skelton json to make sure it donot overwrite each other]-------------------------------
144
+ #################################################################################################################################################################
145
+
146
+ def rename_blocks(block_json: dict, opcode_count: dict) -> tuple[dict, dict]:
147
+ """
148
+ Replace each block key in block_json and each identifier in opcode_count
149
+ with a newly generated secure token.
150
+
151
+ Args:
152
+ block_json: Mapping of block_key -> block_data.
153
+ opcode_count: Mapping of opcode -> list of block_keys.
154
+
155
+ Returns:
156
+ A tuple of (new_block_json, new_opcode_count) with updated keys.
157
+ """
158
+ # Step 1: Generate a secure token mapping for every existing block key
159
+ token_map = {}
160
+ for old_key in block_json.keys():
161
+ # Ensure uniqueness in the unlikely event of a collision
162
+ while True:
163
+ new_key = generate_secure_token()
164
+ if new_key not in token_map.values():
165
+ break
166
+ token_map[old_key] = new_key
167
+
168
+ # Step 2: Rebuild block_json with new keys
169
+ new_block_json = {}
170
+ for old_key, block in block_json.items():
171
+ new_key = token_map[old_key]
172
+ new_block_json[new_key] = block.copy()
173
+
174
+ # Update parent and next references
175
+ if 'parent' in block and block['parent'] in token_map:
176
+ new_block_json[new_key]['parent'] = token_map[block['parent']]
177
+ if 'next' in block and block['next'] in token_map:
178
+ new_block_json[new_key]['next'] = token_map[block['next']]
179
+
180
+ # Update inputs if they reference blocks
181
+ for inp_key, inp_val in block.get('inputs', {}).items():
182
+ if isinstance(inp_val, list) and len(inp_val) == 2:
183
+ idx, ref = inp_val
184
+ if idx in (2, 3) and isinstance(ref, str) and ref in token_map:
185
+ new_block_json[new_key]['inputs'][inp_key] = [idx, token_map[ref]]
186
+
187
+ # Step 3: Update opcode count map
188
+ new_opcode_count = {}
189
+ for opcode, key_list in opcode_count.items():
190
+ new_opcode_count[opcode] = [token_map.get(k, k) for k in key_list]
191
+
192
+ return new_block_json, new_opcode_count
193
+
194
+ #################################################################################################################################################################
195
+ #--------------------------------------------------[Helper function to add Variables and Broadcasts [USed in main app file for main projectjson]]----------------
196
+ #################################################################################################################################################################
197
+
198
+ def variable_intialization(project_data):
199
+ """
200
+ Updates variable and broadcast definitions in a Scratch project JSON,
201
+ populating the 'variables' and 'broadcasts' sections of the Stage target
202
+ and extracting initial values for variables.
203
+
204
+ Args:
205
+ project_data (dict): The loaded JSON data of the Scratch project.
206
+
207
+ Returns:
208
+ dict: The updated project JSON data.
209
+ """
210
+
211
+ stage_target = None
212
+ for target in project_data['targets']:
213
+ if target.get('isStage'):
214
+ stage_target = target
215
+ break
216
+
217
+ if stage_target is None:
218
+ print("Error: Stage target not found in the project data.")
219
+ return project_data
220
+
221
+ # Ensure 'variables' and 'broadcasts' exist in the Stage target
222
+ if "variables" not in stage_target:
223
+ stage_target["variables"] = {}
224
+ if "broadcasts" not in stage_target:
225
+ stage_target["broadcasts"] = {}
226
+
227
+ # Helper function to recursively find and update variable/broadcast fields
228
+ def process_dict(obj):
229
+ if isinstance(obj, dict):
230
+ # Check for "data_setvariableto" opcode to extract initial values
231
+ if obj.get("opcode") == "data_setvariableto":
232
+ variable_field = obj.get("fields", {}).get("VARIABLE")
233
+ value_input = obj.get("inputs", {}).get("VALUE")
234
+
235
+ if variable_field and isinstance(variable_field, list) and len(variable_field) == 2:
236
+ var_name = variable_field[0]
237
+ var_id = variable_field[1]
238
+
239
+ initial_value = ""
240
+ if value_input and isinstance(value_input, list) and len(value_input) > 1 and \
241
+ isinstance(value_input[1], list) and len(value_input[1]) > 1:
242
+ # Extract value from various formats, e.g., [1, [10, "0"]] or [3, [12, "score", "id"], [10, "0"]]
243
+ if value_input[1][0] == 10: # Direct value like [10, "0"]
244
+ initial_value = str(value_input[1][1])
245
+ elif value_input[1][0] == 12 and len(value_input) > 2 and isinstance(value_input[2], list) and value_input[2][0] == 10: # Variable reference with initial value block
246
+ initial_value = str(value_input[2][1])
247
+ elif isinstance(value_input[1], (str, int, float)): # For direct number/string inputs
248
+ initial_value = str(value_input[1])
249
+
250
+
251
+ # Add/update the variable in the Stage's 'variables' with its initial value
252
+ stage_target["variables"][var_id] = [var_name, initial_value]
253
+
254
+
255
+ for key, value in obj.items():
256
+ # Process variable definitions in 'fields' (for blocks that define variables like 'show variable')
257
+ if key == "VARIABLE" and isinstance(value, list) and len(value) == 2:
258
+ var_name = value[0]
259
+ var_id = value[1]
260
+ # Only add if not already defined with an initial value from set_variableto
261
+ if var_id not in stage_target["variables"]:
262
+ stage_target["variables"][var_id] = [var_name, ""] # Default to empty string if no initial value found yet
263
+ elif stage_target["variables"][var_id][0] != var_name: # Update name if ID exists but name is different
264
+ stage_target["variables"][var_id][0] = var_name
265
+
266
+
267
+ # Process broadcast definitions in 'inputs' (BROADCAST_INPUT)
268
+ elif key == "BROADCAST_INPUT" and isinstance(value, list) and len(value) == 2 and \
269
+ isinstance(value[1], list) and len(value[1]) == 3 and value[1][0] == 11:
270
+ broadcast_name = value[1][1]
271
+ broadcast_id = value[1][2]
272
+ # Add/update the broadcast in the Stage's 'broadcasts'
273
+ stage_target["broadcasts"][broadcast_id] = broadcast_name
274
+
275
+ # Process broadcast definitions in 'fields' (BROADCAST_OPTION)
276
+ elif key == "BROADCAST_OPTION" and isinstance(value, list) and len(value) == 2:
277
+ broadcast_name = value[0]
278
+ broadcast_id = value[1]
279
+ # Add/update the broadcast in the Stage's 'broadcasts'
280
+ stage_target["broadcasts"][broadcast_id] = broadcast_name
281
+
282
+ # Recursively call for nested dictionaries or lists
283
+ process_dict(value)
284
+ elif isinstance(obj, list):
285
+ for i, item in enumerate(obj):
286
+ # Process variable references in 'inputs' (like [12, "score", "id"])
287
+ if isinstance(item, list) and len(item) == 3 and item[0] == 12:
288
+ var_name = item[1]
289
+ var_id = item[2]
290
+ # Only add if not already defined with an initial value from set_variableto
291
+ if var_id not in stage_target["variables"]:
292
+ stage_target["variables"][var_id] = [var_name, ""] # Default to empty string if no initial value found yet
293
+ elif stage_target["variables"][var_id][0] != var_name: # Update name if ID exists but name is different
294
+ stage_target["variables"][var_id][0] = var_name
295
+
296
+ process_dict(item)
297
+
298
+ # Iterate through all targets to process their blocks
299
+ for target in project_data['targets']:
300
+ if "blocks" in target:
301
+ for block_id, block_data in target["blocks"].items():
302
+ process_dict(block_data)
303
+
304
+ return project_data
305
+
306
+ def deduplicate_variables(project_data):
307
+ """
308
+ Removes duplicate variable entries in the 'variables' dictionary of the Stage target,
309
+ prioritizing entries with non-empty values.
310
+
311
+ Args:
312
+ project_data (dict): The loaded JSON data of the Scratch project.
313
+
314
+ Returns:
315
+ dict: The updated project JSON data with deduplicated variables.
316
+ """
317
+
318
+ stage_target = None
319
+ for target in project_data['targets']:
320
+ if target.get('isStage'):
321
+ stage_target = target
322
+ break
323
+
324
+ if stage_target is None:
325
+ print("Error: Stage target not found in the project data.")
326
+ return project_data
327
+
328
+ if "variables" not in stage_target:
329
+ return project_data # No variables to deduplicate
330
+
331
+ # Use a temporary dictionary to store the preferred variable entry by name
332
+ # Format: {variable_name: [variable_id, variable_name, variable_value]}
333
+ resolved_variables = {}
334
+
335
+ for var_id, var_info in stage_target["variables"].items():
336
+ var_name = var_info[0]
337
+ var_value = var_info[1]
338
+
339
+ if var_name not in resolved_variables:
340
+ # If the variable name is not yet seen, add it
341
+ resolved_variables[var_name] = [var_id, var_name, var_value]
342
+ else:
343
+ # If the variable name is already seen, decide which one to keep
344
+ existing_id, existing_name, existing_value = resolved_variables[var_name]
345
+
346
+ # Prioritize the entry with a non-empty value
347
+ if var_value != "" and existing_value == "":
348
+ resolved_variables[var_name] = [var_id, var_name, var_value]
349
+ # If both have non-empty values, or both are empty, keep the current one (arbitrary choice, but consistent)
350
+ # The current logic will effectively keep the last one encountered that has a value,
351
+ # or the very last one if all are empty.
352
+ elif var_value != "" and existing_value != "":
353
+ # If there are multiple non-empty values for the same variable name
354
+ # this keeps the one from the most recent iteration.
355
+ # For the given example, this will correctly keep "5".
356
+ resolved_variables[var_name] = [var_id, var_name, var_value]
357
+ elif var_value == "" and existing_value == "":
358
+ # If both are empty, just keep the current one (arbitrary)
359
+ resolved_variables[var_name] = [var_id, var_name, var_value]
360
+
361
+
362
+ # Reconstruct the 'variables' dictionary using the resolved entries
363
+ new_variables_dict = {}
364
+ for var_name, var_data in resolved_variables.items():
365
+ var_id_to_keep = var_data[0]
366
+ var_name_to_keep = var_data[1]
367
+ var_value_to_keep = var_data[2]
368
+ new_variables_dict[var_id_to_keep] = [var_name_to_keep, var_value_to_keep]
369
+
370
+ stage_target["variables"] = new_variables_dict
371
+
372
+ return project_data
373
+
374
+ def variable_adder_main(project_data):
375
+ try:
376
+ declare_variable_json= variable_intialization(project_data)
377
+ except Exception as e:
378
+ print(f"Error error in the variable initialization opcodes: {e}")
379
+ try:
380
+ processed_json= deduplicate_variables(declare_variable_json)
381
+ return
382
+ except Exception as e:
383
+ print(f"Error error in the variable initialization opcodes: {e}")
384
+
385
+ #################################################################################################################################################################
386
+ #--------------------------------------------------[Helper main function]----------------------------------------------------------------------------------------
387
+ #################################################################################################################################################################
388
+
389
+ def block_builder(opcode_count,pseudo_code):
390
+ try:
391
+ generated_output_json, initial_opcode_occurrences = generate_blocks_from_opcodes(opcode_count, all_block_definitions)
392
+ except Exception as e:
393
+ print(f"Error generating blocks from opcodes: {e}")
394
+ return {}
395
+ try:
396
+ all_generated_blocks = generate_plan(generated_output_json, initial_opcode_occurrences, pseudo_code)
397
+ except Exception as e:
398
+ print(f"Error generating plan from blocks: {e}")
399
+ return {}
400
+ try:
401
+ processed_blocks= process_scratch_blocks(all_generated_blocks, generated_output_json)
402
+ except Exception as e:
403
+ print(f"Error processing Scratch blocks: {e}")
404
+ return {}
405
+ renamed_blocks, renamed_counts = rename_blocks(processed_blocks, initial_opcode_occurrences)
406
+ return renamed_blocks
407
+
408
+ #################################################################################################################################################################
409
+ #--------------------------------------------------[Example use of the function here]----------------------------------------------------------------------------
410
+ #################################################################################################################################################################
411
+
412
+ initial_opcode_counts = [
413
+ {
414
+ "opcode": "event_whenflagclicked",
415
+ "count": 1
416
+ },
417
+ {
418
+ "opcode": "data_setvariableto",
419
+ "count": 2
420
+ },
421
+ {
422
+ "opcode": "data_showvariable",
423
+ "count": 2
424
+ },
425
+ {
426
+ "opcode": "event_broadcast",
427
+ "count": 1
428
+ }
429
+ ]
430
+ pseudo_code="""
431
+ when green flag clicked
432
+ set [score v] to (0)
433
+ set [lives v] to (3)
434
+ show variable [score v]
435
+ show variable [lives v]
436
+ broadcast [Game Start v]
437
+ """
438
+
439
+ generated_output_json, initial_opcode_occurrences = generate_blocks_from_opcodes(initial_opcode_counts, all_block_definitions)
440
+ all_generated_blocks = generate_plan(generated_output_json, initial_opcode_occurrences, pseudo_code)
441
+ processed_blocks= process_scratch_blocks(all_generated_blocks, generated_output_json)
442
+ print(all_generated_blocks)
443
+ print("--------------\n\n")
444
+ print(processed_blocks)
445
+ print("--------------\n\n")
446
+ print(initial_opcode_occurrences)
v2/utils/block_correcter.py ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import secrets
3
+ import string
4
+ from collections import defaultdict
5
+
6
+ def generate_secure_token(length=20):
7
+ charset = string.ascii_letters + string.digits + "!@#$%^&*()[]{}=+-_~"
8
+ return ''.join(secrets.choice(charset) for _ in range(length))
9
+
10
+ def process_scratch_blocks(all_generated_blocks, generated_output_json):
11
+ processed_blocks = {}
12
+
13
+ # Initialize dictionaries to store and reuse generated unique IDs
14
+ # This prevents creating multiple unique IDs for the same variable/broadcast across different blocks
15
+ variable_id_map = defaultdict(lambda: generate_secure_token(20))
16
+ broadcast_id_map = defaultdict(lambda: generate_secure_token(20))
17
+
18
+ for block_id, gen_block_data in generated_output_json.items():
19
+ processed_block = {}
20
+ all_gen_block_data = all_generated_blocks.get(block_id, {})
21
+
22
+ # Copy and update fields, inputs, next, parent, shadow, topLevel, mutation, and opcode
23
+ processed_block["opcode"] = all_gen_block_data.get("op_code", gen_block_data.get("op_code"))
24
+ processed_block["inputs"] = {}
25
+ processed_block["fields"] = {}
26
+ processed_block["shadow"] = all_gen_block_data.get("shadow", gen_block_data.get("shadow"))
27
+ processed_block["topLevel"] = all_gen_block_data.get("topLevel", gen_block_data.get("topLevel"))
28
+ processed_block["parent"] = all_gen_block_data.get("parent", gen_block_data.get("parent"))
29
+ processed_block["next"] = all_gen_block_data.get("next", gen_block_data.get("next"))
30
+ if "mutation" in all_gen_block_data:
31
+ processed_block["mutation"] = all_gen_block_data["mutation"]
32
+
33
+ # Process inputs
34
+ if "inputs" in all_gen_block_data:
35
+ for input_name, input_data in all_gen_block_data["inputs"].items():
36
+ if input_name in ["SUBSTACK", "CONDITION"]:
37
+ # These should always be type 2
38
+ if isinstance(input_data, list) and len(input_data) == 2:
39
+ processed_block["inputs"][input_name] = [2, input_data[1]]
40
+ elif isinstance(input_data, dict) and input_data.get("kind") == "block":
41
+ processed_block["inputs"][input_name] = [2, input_data.get("block")]
42
+ else: # Fallback for unexpected formats, try to use the original if possible
43
+ processed_block["inputs"][input_name] = gen_block_data["inputs"].get(input_name, [2, None])
44
+
45
+ elif isinstance(input_data, dict):
46
+ if input_data.get("kind") == "value":
47
+ # Case 1: Direct value input
48
+ processed_block["inputs"][input_name] = [
49
+ 1,
50
+ [
51
+ 4,
52
+ str(input_data.get("value", ""))
53
+ ]
54
+ ]
55
+ elif input_data.get("kind") == "block":
56
+ # Case 3: Nested block input
57
+ existing_shadow_value = ""
58
+ if input_name in gen_block_data.get("inputs", {}) and \
59
+ isinstance(gen_block_data["inputs"][input_name], list) and \
60
+ len(gen_block_data["inputs"][input_name]) > 2 and \
61
+ isinstance(gen_block_data["inputs"][input_name][2], list) and \
62
+ len(gen_block_data["inputs"][input_name][2]) > 1:
63
+ existing_shadow_value = gen_block_data["inputs"][input_name][2][1]
64
+
65
+ processed_block["inputs"][input_name] = [
66
+ 3,
67
+ input_data.get("block", ""),
68
+ [
69
+ 10, # Assuming 10 for number/string shadow
70
+ existing_shadow_value
71
+ ]
72
+ ]
73
+ elif input_data.get("kind") == "menu":
74
+ # Handle menu inputs like in event_broadcast
75
+ menu_option = input_data.get("option", "")
76
+
77
+ # Generate or retrieve a unique ID for the broadcast message
78
+ broadcast_id = broadcast_id_map[menu_option] # Use defaultdict for unique IDs
79
+
80
+ processed_block["inputs"][input_name] = [
81
+ 1,
82
+ [
83
+ 11, # This is typically the code for menu dropdowns
84
+ menu_option,
85
+ broadcast_id
86
+ ]
87
+ ]
88
+ elif isinstance(input_data, list):
89
+ # For cases like TOUCHINGOBJECTMENU, where input_data is a list [1, "block_id"]
90
+ processed_block["inputs"][input_name] = input_data
91
+
92
+
93
+ # Process fields
94
+ if "fields" in all_gen_block_data:
95
+ for field_name, field_value in all_gen_block_data["fields"].items():
96
+ if field_name == "VARIABLE" and isinstance(field_value, list) and len(field_value) > 0:
97
+ # Generate or retrieve a unique ID for the variable
98
+ variable_name = field_value[0]
99
+ unique_id = variable_id_map[variable_name] # Use defaultdict for unique IDs
100
+
101
+ processed_block["fields"][field_name] = [
102
+ variable_name,
103
+ unique_id
104
+ ]
105
+ elif field_name == "STOP_OPTION":
106
+ processed_block["fields"][field_name] = [
107
+ field_value[0],
108
+ None
109
+ ]
110
+ elif field_name == "TOUCHINGOBJECTMENU":
111
+ referenced_menu_block_id = all_gen_block_data["inputs"].get("TOUCHINGOBJECTMENU", [None, None])[1]
112
+ if referenced_menu_block_id and referenced_menu_block_id in all_generated_blocks:
113
+ menu_block = all_generated_blocks[referenced_menu_block_id]
114
+ menu_value = menu_block.get("fields", {}).get("TOUCHINGOBJECTMENU", ["", None])[0]
115
+ processed_block["fields"][field_name] = [menu_value, None]
116
+ else:
117
+ processed_block["fields"][field_name] = [field_value[0], None]
118
+ else:
119
+ processed_block["fields"][field_name] = field_value
120
+
121
+ # Remove unwanted keys from the processed block
122
+ keys_to_remove = ["functionality", "block_shape", "id", "block_name", "block_type"]
123
+ for key in keys_to_remove:
124
+ if key in processed_block:
125
+ del processed_block[key]
126
+
127
+ processed_blocks[block_id] = processed_block
128
+ return processed_blocks
129
+
130
+ # Path to your JSON file
131
+ if __name__ == "__main__":
132
+ all_generated_blocks_path = 'all_generated_blocks.json'
133
+ generated_output_json_path = 'generated_output_json.json'
134
+
135
+ # Open and load the JSON files into Python dictionaries
136
+ with open(all_generated_blocks_path, 'r') as f:
137
+ all_generated_blocks_data = json.load(f)
138
+
139
+ with open(generated_output_json_path, 'r') as f:
140
+ generated_output_json_data = json.load(f)
141
+
142
+ processed_blocks = process_scratch_blocks(all_generated_blocks_data, generated_output_json_data)
143
+ print(json.dumps(processed_blocks, indent=4))
v2/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))
v2/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))
v2/utils/block_naming.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import secrets
2
+ import string
3
+ from typing import Dict, Any, TypedDict
4
+
5
+ def generate_secure_token(length=20):
6
+ charset = string.ascii_letters + string.digits + "!@#$%^&*()[]{}=+-_~"
7
+ return ''.join(secrets.choice(charset) for _ in range(length))
8
+
9
+ def rename_blocks(block_json: dict, opcode_count: dict) -> tuple[dict, dict]:
10
+ """
11
+ Replace each block key in block_json and each identifier in opcode_count
12
+ with a newly generated secure token.
13
+
14
+ Args:
15
+ block_json: Mapping of block_key -> block_data.
16
+ opcode_count: Mapping of opcode -> list of block_keys.
17
+
18
+ Returns:
19
+ A tuple of (new_block_json, new_opcode_count) with updated keys.
20
+ """
21
+ # Step 1: Generate a secure token mapping for every existing block key
22
+ token_map = {}
23
+ for old_key in block_json.keys():
24
+ # Ensure uniqueness in the unlikely event of a collision
25
+ while True:
26
+ new_key = generate_secure_token()
27
+ if new_key not in token_map.values():
28
+ break
29
+ token_map[old_key] = new_key
30
+
31
+ # Step 2: Rebuild block_json with new keys
32
+ new_block_json = {}
33
+ for old_key, block in block_json.items():
34
+ new_key = token_map[old_key]
35
+ new_block_json[new_key] = block.copy()
36
+
37
+ # Update parent and next references
38
+ if 'parent' in block and block['parent'] in token_map:
39
+ new_block_json[new_key]['parent'] = token_map[block['parent']]
40
+ if 'next' in block and block['next'] in token_map:
41
+ new_block_json[new_key]['next'] = token_map[block['next']]
42
+
43
+ # Update inputs if they reference blocks
44
+ for inp_key, inp_val in block.get('inputs', {}).items():
45
+ if isinstance(inp_val, list) and len(inp_val) == 2:
46
+ idx, ref = inp_val
47
+ if idx in (2, 3) and isinstance(ref, str) and ref in token_map:
48
+ new_block_json[new_key]['inputs'][inp_key] = [idx, token_map[ref]]
49
+
50
+ # Step 3: Update opcode count map
51
+ new_opcode_count = {}
52
+ for opcode, key_list in opcode_count.items():
53
+ new_opcode_count[opcode] = [token_map.get(k, k) for k in key_list]
54
+
55
+ return new_block_json, new_opcode_count
56
+
57
+ # Example usage:
58
+ if __name__ == "__main__":
59
+ blocks = {'event_whenflagclicked_1': {'opcode': 'event_whenflagclicked', 'inputs': {}, 'fields': {}, 'shadow': False, 'topLevel': True, 'parent': None, 'next': 'motion_gotoxy_1'}, 'motion_gotoxy_1': {'opcode': 'motion_gotoxy', 'inputs': {'X': [1, [4, '240']], 'Y': [1, [4, '-135']]}, 'fields': {}, 'shadow': False, 'topLevel': False, 'parent': 'event_whenflagclicked_1', 'next': 'data_setvariableto_1'}, 'motion_xposition_1': {'opcode': 'motion_xposition', 'inputs': {}, 'fields': {}, 'shadow': False, 'topLevel': False, 'parent': 'operator_lt_1', 'next': None}, 'motion_setx_1': {'opcode': 'motion_setx', 'inputs': {'X': [1, [4, '240']]}, 'fields': {}, 'shadow': False, 'topLevel': False, 'parent': 'control_if_1', 'next': None}, 'control_forever_1': {'opcode': 'control_forever', 'inputs': {'SUBSTACK': [2, 'control_if_1']}, 'fields': {}, 'shadow': False, 'topLevel': False, 'parent': 'event_whenflagclicked_1', 'next': None}, 'control_if_1': {'opcode': 'control_if', 'inputs': {'CONDITION': [2, 'operator_lt_1'], 'SUBSTACK': [2, 'motion_setx_1']}, 'fields': {}, 'shadow': False, 'topLevel': False, 'parent': 'control_forever_1', 'next': 'control_if_2'}, 'control_if_2': {'opcode': 'control_if', 'inputs': {'CONDITION': [2, 'sensing_touchingobject_1'], 'SUBSTACK': [2, 'event_broadcast_1']}, 'fields': {}, 'shadow': False, 'topLevel': False, 'parent': 'control_forever_1', 'next': None}, 'control_stop_1': {'opcode': 'control_stop', 'inputs': {}, 'fields': {'STOP_OPTION': ['all ', None]}, 'shadow': False, 'topLevel': False, 'parent': 'control_if_2', 'next': None, 'mutation': {'tagName': 'mutation', 'children': [], 'hasnext': 'false'}}, 'operator_lt_1': {'opcode': 'operator_lt', 'inputs': {'OPERAND1': [3, 'motion_xposition_1', [10, '']], 'OPERAND2': [1, [4, '-235']]}, 'fields': {}, 'shadow': False, 'topLevel': False, 'parent': 'control_if_1', 'next': None}, 'sensing_touchingobject_1': {'opcode': 'sensing_touchingobject', 'inputs': {'TOUCHINGOBJECTMENU': [1, 'sensing_touchingobjectmenu_1']}, 'fields': {}, 'shadow': False, 'topLevel': False, 'parent': 'control_if_2', 'next': None}, 'sensing_touchingobjectmenu_1': {'opcode': 'sensing_touchingobjectmenu', 'inputs': {}, 'fields': {'TOUCHINGOBJECTMENU': ['sprite1', None]}, 'shadow': True, 'topLevel': False, 'parent': 'sensing_touchingobject_1', 'next': None}, 'sensing_touchingobjectmenu_2': {'opcode': 'sensing_touchingobjectmenu', 'inputs': {}, 'fields': {'TOUCHINGOBJECTMENU': ['_mouse_', None]}, 'shadow': False, 'topLevel': False, 'parent': None, 'next': None}, 'event_broadcast_1': {'opcode': 'event_broadcast', 'inputs': {'BROADCAST_INPUT': [1, [11, 'Game Over', '<Game Over_unique_id>']]}, 'fields': {}, 'shadow': False, 'topLevel': False, 'parent': 'control_if_2', 'next': 'control_stop_1'}, 'data_setvariableto_1': {'opcode': 'data_setvariableto', 'inputs': {'VALUE': [1, [4, '1']]}, 'fields': {'VARIABLE': ['score', 'score']}, 'shadow': False, 'topLevel': False, 'parent': 'event_whenflagclicked_1', 'next': 'data_setvariableto_2'}, 'data_setvariableto_2': {'opcode': 'data_setvariableto', 'inputs': {'VALUE': [1, [4, '1']]}, 'fields': {'VARIABLE': ['speed', 'speed']}, 'shadow': False, 'topLevel': False, 'parent': 'event_whenflagclicked_1', 'next': 'data_showvariable_1'}, 'data_showvariable_1': {'opcode': 'data_showvariable', 'inputs': {}, 'fields': {'VARIABLE': ['score', 'score']}, 'shadow': False, 'topLevel': False, 'parent': 'event_whenflagclicked_1', 'next': 'data_showvariable_2'}, 'data_showvariable_2': {'opcode': 'data_showvariable', 'inputs': {}, 'fields': {'VARIABLE': ['speed', 'speed']}, 'shadow': False, 'topLevel': False, 'parent': 'event_whenflagclicked_1', 'next': 'control_forever_1'}} # your first JSON
60
+ opcode_keys = {'event_whenflagclicked': ['event_whenflagclicked_1'], 'motion_gotoxy': ['motion_gotoxy_1'], 'motion_xposition': ['motion_xposition_1'], 'motion_setx': ['motion_setx_1'], 'control_forever': ['control_forever_1'], 'control_if': ['control_if_1', 'control_if_2'], 'control_stop': ['control_stop_1'], 'operator_lt': ['operator_lt_1'], 'sensing_touchingobject': ['sensing_touchingobject_1'], 'sensing_touchingobjectmenu': ['sensing_touchingobjectmenu_1', 'sensing_touchingobjectmenu_2'], 'event_broadcast': ['event_broadcast_1'], 'data_setvariableto': ['data_setvariableto_1', 'data_setvariableto_2'], 'data_showvariable': ['data_showvariable_1', 'data_showvariable_2']} # your second JSON
61
+ renamed_blocks, renamed_counts = rename_blocks(blocks, opcode_keys)
62
+ print(renamed_blocks)
v2/utils/block_relation_builder.py ADDED
The diff for this file is too large to render. See raw diff
 
v2/utils/block_relation_builder_v2.py ADDED
The diff for this file is too large to render. See raw diff
 
v2/utils/block_var_setter.py ADDED
@@ -0,0 +1,290 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+
3
+ def update_scratch_project_data(project_data):
4
+ """
5
+ Updates variable and broadcast definitions in a Scratch project JSON,
6
+ populating the 'variables' and 'broadcasts' sections of the Stage target
7
+ and extracting initial values for variables.
8
+
9
+ Args:
10
+ project_data (dict): The loaded JSON data of the Scratch project.
11
+
12
+ Returns:
13
+ dict: The updated project JSON data.
14
+ """
15
+
16
+ stage_target = None
17
+ for target in project_data['targets']:
18
+ if target.get('isStage'):
19
+ stage_target = target
20
+ break
21
+
22
+ if stage_target is None:
23
+ print("Error: Stage target not found in the project data.")
24
+ return project_data
25
+
26
+ # Ensure 'variables' and 'broadcasts' exist in the Stage target
27
+ if "variables" not in stage_target:
28
+ stage_target["variables"] = {}
29
+ if "broadcasts" not in stage_target:
30
+ stage_target["broadcasts"] = {}
31
+
32
+ # Helper function to recursively find and update variable/broadcast fields
33
+ def process_dict(obj):
34
+ if isinstance(obj, dict):
35
+ # Check for "data_setvariableto" opcode to extract initial values
36
+ if obj.get("opcode") == "data_setvariableto":
37
+ variable_field = obj.get("fields", {}).get("VARIABLE")
38
+ value_input = obj.get("inputs", {}).get("VALUE")
39
+
40
+ if variable_field and isinstance(variable_field, list) and len(variable_field) == 2:
41
+ var_name = variable_field[0]
42
+ var_id = variable_field[1]
43
+
44
+ initial_value = ""
45
+ if value_input and isinstance(value_input, list) and len(value_input) > 1 and \
46
+ isinstance(value_input[1], list) and len(value_input[1]) > 1:
47
+ # Extract value from various formats, e.g., [1, [10, "0"]] or [3, [12, "score", "id"], [10, "0"]]
48
+ if value_input[1][0] == 10: # Direct value like [10, "0"]
49
+ initial_value = str(value_input[1][1])
50
+ elif value_input[1][0] == 12 and len(value_input) > 2 and isinstance(value_input[2], list) and value_input[2][0] == 10: # Variable reference with initial value block
51
+ initial_value = str(value_input[2][1])
52
+ elif isinstance(value_input[1], (str, int, float)): # For direct number/string inputs
53
+ initial_value = str(value_input[1])
54
+
55
+
56
+ # Add/update the variable in the Stage's 'variables' with its initial value
57
+ stage_target["variables"][var_id] = [var_name, initial_value]
58
+
59
+
60
+ for key, value in obj.items():
61
+ # Process variable definitions in 'fields' (for blocks that define variables like 'show variable')
62
+ if key == "VARIABLE" and isinstance(value, list) and len(value) == 2:
63
+ var_name = value[0]
64
+ var_id = value[1]
65
+ # Only add if not already defined with an initial value from set_variableto
66
+ if var_id not in stage_target["variables"]:
67
+ stage_target["variables"][var_id] = [var_name, ""] # Default to empty string if no initial value found yet
68
+ elif stage_target["variables"][var_id][0] != var_name: # Update name if ID exists but name is different
69
+ stage_target["variables"][var_id][0] = var_name
70
+
71
+
72
+ # Process broadcast definitions in 'inputs' (BROADCAST_INPUT)
73
+ elif key == "BROADCAST_INPUT" and isinstance(value, list) and len(value) == 2 and \
74
+ isinstance(value[1], list) and len(value[1]) == 3 and value[1][0] == 11:
75
+ broadcast_name = value[1][1]
76
+ broadcast_id = value[1][2]
77
+ # Add/update the broadcast in the Stage's 'broadcasts'
78
+ stage_target["broadcasts"][broadcast_id] = broadcast_name
79
+
80
+ # Process broadcast definitions in 'fields' (BROADCAST_OPTION)
81
+ elif key == "BROADCAST_OPTION" and isinstance(value, list) and len(value) == 2:
82
+ broadcast_name = value[0]
83
+ broadcast_id = value[1]
84
+ # Add/update the broadcast in the Stage's 'broadcasts'
85
+ stage_target["broadcasts"][broadcast_id] = broadcast_name
86
+
87
+ # Recursively call for nested dictionaries or lists
88
+ process_dict(value)
89
+ elif isinstance(obj, list):
90
+ for i, item in enumerate(obj):
91
+ # Process variable references in 'inputs' (like [12, "score", "id"])
92
+ if isinstance(item, list) and len(item) == 3 and item[0] == 12:
93
+ var_name = item[1]
94
+ var_id = item[2]
95
+ # Only add if not already defined with an initial value from set_variableto
96
+ if var_id not in stage_target["variables"]:
97
+ stage_target["variables"][var_id] = [var_name, ""] # Default to empty string if no initial value found yet
98
+ elif stage_target["variables"][var_id][0] != var_name: # Update name if ID exists but name is different
99
+ stage_target["variables"][var_id][0] = var_name
100
+
101
+ process_dict(item)
102
+
103
+ # Iterate through all targets to process their blocks
104
+ for target in project_data['targets']:
105
+ if "blocks" in target:
106
+ for block_id, block_data in target["blocks"].items():
107
+ process_dict(block_data)
108
+
109
+ return project_data
110
+
111
+ def deduplicate_variables(project_data):
112
+ """
113
+ Removes duplicate variable entries in the 'variables' dictionary of the Stage target,
114
+ prioritizing entries with non-empty values.
115
+
116
+ Args:
117
+ project_data (dict): The loaded JSON data of the Scratch project.
118
+
119
+ Returns:
120
+ dict: The updated project JSON data with deduplicated variables.
121
+ """
122
+
123
+ stage_target = None
124
+ for target in project_data['targets']:
125
+ if target.get('isStage'):
126
+ stage_target = target
127
+ break
128
+
129
+ if stage_target is None:
130
+ print("Error: Stage target not found in the project data.")
131
+ return project_data
132
+
133
+ if "variables" not in stage_target:
134
+ return project_data # No variables to deduplicate
135
+
136
+ # Use a temporary dictionary to store the preferred variable entry by name
137
+ # Format: {variable_name: [variable_id, variable_name, variable_value]}
138
+ resolved_variables = {}
139
+
140
+ for var_id, var_info in stage_target["variables"].items():
141
+ var_name = var_info[0]
142
+ var_value = var_info[1]
143
+
144
+ if var_name not in resolved_variables:
145
+ # If the variable name is not yet seen, add it
146
+ resolved_variables[var_name] = [var_id, var_name, var_value]
147
+ else:
148
+ # If the variable name is already seen, decide which one to keep
149
+ existing_id, existing_name, existing_value = resolved_variables[var_name]
150
+
151
+ # Prioritize the entry with a non-empty value
152
+ if var_value != "" and existing_value == "":
153
+ resolved_variables[var_name] = [var_id, var_name, var_value]
154
+ # If both have non-empty values, or both are empty, keep the current one (arbitrary choice, but consistent)
155
+ # The current logic will effectively keep the last one encountered that has a value,
156
+ # or the very last one if all are empty.
157
+ elif var_value != "" and existing_value != "":
158
+ # If there are multiple non-empty values for the same variable name
159
+ # this keeps the one from the most recent iteration.
160
+ # For the given example, this will correctly keep "5".
161
+ resolved_variables[var_name] = [var_id, var_name, var_value]
162
+ elif var_value == "" and existing_value == "":
163
+ # If both are empty, just keep the current one (arbitrary)
164
+ resolved_variables[var_name] = [var_id, var_name, var_value]
165
+
166
+
167
+ # Reconstruct the 'variables' dictionary using the resolved entries
168
+ new_variables_dict = {}
169
+ for var_name, var_data in resolved_variables.items():
170
+ var_id_to_keep = var_data[0]
171
+ var_name_to_keep = var_data[1]
172
+ var_value_to_keep = var_data[2]
173
+ new_variables_dict[var_id_to_keep] = [var_name_to_keep, var_value_to_keep]
174
+
175
+ stage_target["variables"] = new_variables_dict
176
+
177
+ return project_data
178
+
179
+ # Example usage with your provided JSON:
180
+ if __name__ == "__main__":
181
+ json_data = {
182
+ "targets": [
183
+ {"isStage": True, "name": "Stage", "variables": {},"lists": {}, "broadcasts": {}, "blocks": {
184
+ "event_whenflagclicked_1": {
185
+ "opcode": "event_whenflagclicked",
186
+ "inputs": {},
187
+ "fields": {},
188
+ "shadow": False,
189
+ "topLevel": True,
190
+ "parent": None,
191
+ "next": "data_setvariableto_1"
192
+ },
193
+ "data_setvariableto_1": {
194
+ "opcode": "data_setvariableto",
195
+ "inputs": {
196
+ "VALUE": [
197
+ 1,
198
+ [
199
+ 4,
200
+ "0"
201
+ ]
202
+ ]
203
+ },
204
+ "fields": {
205
+ "VARIABLE": [
206
+ "score",
207
+ "$ELgBAKpb[&l+DX$EMK4"
208
+ ]
209
+ },
210
+ "shadow": False,
211
+ "topLevel": False,
212
+ "parent": "event_whenflagclicked_1",
213
+ "next": "data_setvariableto_2"
214
+ },
215
+ "data_setvariableto_2": {
216
+ "opcode": "data_setvariableto",
217
+ "inputs": {
218
+ "VALUE": [
219
+ 1,
220
+ [
221
+ 4,
222
+ "3"
223
+ ]
224
+ ]
225
+ },
226
+ "fields": {
227
+ "VARIABLE": [
228
+ "lives",
229
+ "Gb]1jA+H{h_6z1^Fn!-a"
230
+ ]
231
+ },
232
+ "shadow": False,
233
+ "topLevel": False,
234
+ "parent": "data_setvariableto_1",
235
+ "next": "data_showvariable_1"
236
+ },
237
+ "data_showvariable_1": {
238
+ "opcode": "data_showvariable",
239
+ "inputs": {},
240
+ "fields": {
241
+ "VARIABLE": [
242
+ "score",
243
+ "$ELgBAKpb[&l+DX$EMK4"
244
+ ]
245
+ },
246
+ "shadow": False,
247
+ "topLevel": False,
248
+ "parent": "data_setvariableto_2",
249
+ "next": "data_showvariable_2"
250
+ },
251
+ "data_showvariable_2": {
252
+ "opcode": "data_showvariable",
253
+ "inputs": {},
254
+ "fields": {
255
+ "VARIABLE": [
256
+ "lives",
257
+ "Gb]1jA+H{h_6z1^Fn!-a"
258
+ ]
259
+ },
260
+ "shadow": False,
261
+ "topLevel": False,
262
+ "parent": "data_showvariable_1",
263
+ "next": "event_broadcast_1"
264
+ },
265
+ "event_broadcast_1": {
266
+ "opcode": "event_broadcast",
267
+ "inputs": {
268
+ "BROADCAST_INPUT": [
269
+ 1,
270
+ [
271
+ 11,
272
+ "Game Start",
273
+ "hN1d@^S}e)H~8(qp)rGN"
274
+ ]
275
+ ]
276
+ },
277
+ "fields": {},
278
+ "shadow": False,
279
+ "topLevel": False,
280
+ "parent": "data_showvariable_2",
281
+ "next": None
282
+ }
283
+ }, "comments": {}, "currentCostume": 1, "costumes": [{"name": "backdrop1", "dataFormat": "svg", "assetId": "cd21514d0531fdffb22204e0ec5ed84a", "md5ext": "cd21514d0531fdffb22204e0ec5ed84a.svg", "rotationCenterX": 240, "rotationCenterY": 180}, {"name": "Blue Sky", "bitmapResolution": 1, "dataFormat": "svg", "assetId": "e7c147730f19d284bcd7b3f00af19bb6", "rotationCenterX": 240, "rotationCenterY": 180}], "sounds": [{"name": "pop", "assetId": "83a9787d4cb6f3b7632b4ddfebf74367", "dataFormat": "wav", "format": "", "rate": 48000, "sampleCount": 1123, "md5ext": "83a9787d4cb6f3b7632b4ddfebf74367.wav"}], "volume": 100, "layerOrder": 0, "tempo": 60, "videoTransparency": 50, "videoState": "on", "textToSpeechLanguage": None},
284
+ {"isStage": False, "name": "Sprite1", "variables": {}, "lists": {}, "broadcasts": {}, "blocks": {}, "comments": {}, "currentCostume": 0, "costumes": [{"name": "costume1", "bitmapResolution": 1, "dataFormat": "svg", "assetId": "bcf454acf82e4504149f7ffe07081dbc", "md5ext": "bcf454acf82e4504149f7ffe07081dbc.svg", "rotationCenterX": 48, "rotationCenterY": 50}, {"name": "costume2", "bitmapResolution": 1, "dataFormat": "svg", "assetId": "0fb9be3e8397c983338cb71dc84d0b25", "md5ext": "0fb9be3e8397c983338cb71dc84d0b25.svg", "rotationCenterX": 46, "rotationCenterY": 53}], "sounds": [{"name": "Meow", "assetId": "83c36d806dc92327b9e7049a565c6bff", "dataFormat": "wav", "format": "", "rate": 48000, "sampleCount": 40681, "md5ext": "83c36d806dc92327b9e7049a565c6bff.wav"}], "volume": 100, "layerOrder": 1, "visible": True, "x": 0, "y": -120, "size": 100, "direction": 90, "draggable": False, "rotationStyle": "all around"},
285
+ {"isStage": False, "name": "Soccer Ball", "variables": {}, "lists": {}, "broadcasts": {}, "blocks": {}, "comments": {}, "currentCostume": 0, "costumes": [{"name": "soccer ball", "bitmapResolution": 1, "dataFormat": "svg", "assetId": "5d973d7a3a8be3f3bd6e1cd0f73c32b5", "md5ext": "5d973d7a3a8be3f3bd6e1cd0f73c32b5.svg", "rotationCenterX": 23, "rotationCenterY": 22}], "sounds": [{"name": "basketball bounce", "assetId": "1727f65b5f22d151685b8e5917456a60", "dataFormat": "wav", "format": "adpcm", "rate": 22050, "sampleCount": 8129, "md5ext": "1727f65b5f22d151685b8e5917456a60.wav"}], "volume": 100, "layerOrder": 2, "visible": True, "x": -130, "y": -60, "size": 100, "direction": 90, "draggable": False, "rotationStyle": "all around"}], "monitors": [{"id": "76*2udMupx@8!=)9Uqvj", "mode": "default", "opcode": "data_variable", "params": {"VARIABLE": "score"}, "spriteName": None, "value": "0", "width": 0, "height": 0, "x": 5, "y": 5, "visible": True, "sliderMin": 0, "sliderMax": 100, "isDiscrete": True}, {"id": "YiV;2%+lgjnJ$|*Jy.{H", "mode": "default", "opcode": "data_variable", "params": {"VARIABLE": "cloud Var"}, "spriteName": None, "value": 0, "width": 0, "height": 0, "x": 5, "y": 32, "visible": True, "sliderMin": 0, "sliderMax": 100, "isDiscrete": True}, {"id": "@D/}fdt{0XaJ`kRE0t~F", "mode": "default", "opcode": "data_variable", "params": {"VARIABLE": "lives"}, "spriteName": None, "value": "3", "width": 0, "height": 0, "x": 5, "y": 59, "visible": True, "sliderMin": 0, "sliderMax": 100, "isDiscrete": True}, {"id": "^R,uz7yRjtK`=uP~6SN4", "mode": "default", "opcode": "data_variable", "params": {"VARIABLE": "speed"}, "spriteName": None, "value": "5", "width": 0, "height": 0, "x": 5, "y": 86, "visible": True, "sliderMin": 0, "sliderMax": 100, "isDiscrete": True}], "extensions": [], "meta": {"semver": "3.0.0", "vm": "11.3.0", "agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"}}
286
+
287
+
288
+ updated_json_data = update_scratch_project_data(json_data)
289
+ updated_json_data = deduplicate_variables(json_data)
290
+ print(json.dumps(updated_json_data, indent=1))
v2/utils/generated_output_json.json ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "event_whenkeypressed_1": {
3
+ "block_name": "when () key pressed",
4
+ "block_type": "Events",
5
+ "op_code": "event_whenkeypressed",
6
+ "block_shape": "Hat Block",
7
+ "functionality": "This Hat block initiates the script when a specified keyboard key is pressed.",
8
+ "inputs": {},
9
+ "fields": {
10
+ "KEY_OPTION": [
11
+ "space",
12
+ null
13
+ ]
14
+ },
15
+ "shadow": false,
16
+ "topLevel": false,
17
+ "id": "event_whenkeypressed_1",
18
+ "parent": null,
19
+ "next": null
20
+ },
21
+ "motion_changeyby_1": {
22
+ "block_name": "change y by ()",
23
+ "block_type": "Motion",
24
+ "block_shape": "Stack Block",
25
+ "op_code": "motion_changeyby",
26
+ "functionality": "Changes the sprite's Y-coordinate by the specified amount, moving it vertically.",
27
+ "inputs": {
28
+ "DY": [
29
+ 1,
30
+ [
31
+ 4,
32
+ "10"
33
+ ]
34
+ ]
35
+ },
36
+ "fields": {},
37
+ "shadow": false,
38
+ "topLevel": false,
39
+ "id": "motion_changeyby_1",
40
+ "parent": null,
41
+ "next": null
42
+ },
43
+ "motion_changeyby_2": {
44
+ "block_name": "change y by ()",
45
+ "block_type": "Motion",
46
+ "block_shape": "Stack Block",
47
+ "op_code": "motion_changeyby",
48
+ "functionality": "Changes the sprite's Y-coordinate by the specified amount, moving it vertically.",
49
+ "inputs": {
50
+ "DY": [
51
+ 1,
52
+ [
53
+ 4,
54
+ "10"
55
+ ]
56
+ ]
57
+ },
58
+ "fields": {},
59
+ "shadow": false,
60
+ "topLevel": false,
61
+ "id": "motion_changeyby_2",
62
+ "parent": null,
63
+ "next": null
64
+ },
65
+ "control_wait_1": {
66
+ "block_name": "wait () seconds",
67
+ "block_type": "Control",
68
+ "block_shape": "Stack Block",
69
+ "op_code": "control_wait",
70
+ "functionality": "Pauses the script for a specified duration.",
71
+ "inputs": {
72
+ "DURATION": [
73
+ 1,
74
+ [
75
+ 5,
76
+ "1"
77
+ ]
78
+ ]
79
+ },
80
+ "fields": {},
81
+ "shadow": false,
82
+ "topLevel": false,
83
+ "id": "control_wait_1",
84
+ "parent": null,
85
+ "next": null
86
+ }
87
+ }
v2/utils/half_working_plan.py ADDED
The diff for this file is too large to render. See raw diff
 
v2/utils/helper_function.py ADDED
File without changes
v2/utils/logs.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ the plan_generator_6 is working well but sometime losses the boolean parent and reportor booleans
2
+ the plan genratr_7 works well then the 6 version.
3
+ the plan generator_8 works well for parent and child logics.
4
+ the plan generator_9 has better but lacks menu features.
5
+ the plan generator_10 worke better for parent and as well as menu features.
6
+ the plan 11 is for generel case but lackk substack logics.
v2/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
v2/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))
v2/utils/plan_generator.py ADDED
The diff for this file is too large to render. See raw diff
 
v2/utils/plan_generator_10.py ADDED
The diff for this file is too large to render. See raw diff
 
v2/utils/plan_generator_11.py ADDED
The diff for this file is too large to render. See raw diff
 
v2/utils/plan_generator_12.py ADDED
The diff for this file is too large to render. See raw diff
 
v2/utils/plan_generator_13.py ADDED
The diff for this file is too large to render. See raw diff
 
v2/utils/plan_generator_2.py ADDED
The diff for this file is too large to render. See raw diff
 
v2/utils/plan_generator_3.py ADDED
The diff for this file is too large to render. See raw diff
 
v2/utils/plan_generator_4.py ADDED
The diff for this file is too large to render. See raw diff
 
v2/utils/plan_generator_5.py ADDED
The diff for this file is too large to render. See raw diff
 
v2/utils/plan_generator_6.py ADDED
The diff for this file is too large to render. See raw diff
 
v2/utils/plan_generator_7.py ADDED
The diff for this file is too large to render. See raw diff
 
v2/utils/plan_generator_8.py ADDED
The diff for this file is too large to render. See raw diff
 
v2/utils/plan_generator_9.py ADDED
The diff for this file is too large to render. See raw diff
 
v2/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
+ ]
v2/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
+ # }
v2/utils/testing.ipynb ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:723bba2d91688a68ec24c08e909950748e0798ca288b015ab8ad32c19d852311
3
+ size 13761971