prthm11 commited on
Commit
34df3ae
·
verified ·
1 Parent(s): 8a9cf86

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +971 -25
app.py CHANGED
@@ -22,6 +22,7 @@ from langchain_experimental.open_clip.open_clip import OpenCLIPEmbeddings
22
  from io import BytesIO
23
  from pathlib import Path
24
  import os
 
25
 
26
  # ============================== #
27
  # INITIALIZE CLIP EMBEDDER #
@@ -120,8 +121,8 @@ class GameState(TypedDict):
120
  description: str
121
  project_id: str
122
  project_image: str
 
123
  action_plan: Optional[Dict]
124
- pseudo_node: Optional[Dict]
125
  temporary_node: Optional[Dict]
126
 
127
 
@@ -263,6 +264,31 @@ def _load_block_catalog(block_type: str) -> Dict:
263
  except Exception as e:
264
  logger.error(f"Unexpected error loading {catalog_path}: {e}")
265
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
  # --- Global variable for the block catalog ---
267
  # --- Global variable for the block catalog ---
268
  ALL_SCRATCH_BLOCKS_CATALOG = {}
@@ -413,10 +439,9 @@ def extract_json_from_llm_response(raw_response: str) -> dict:
413
  # Node 1: Logic updating if any issue here
414
  def pseudo_generator_node(state: GameState):
415
  logger.info("--- Running plan_logic_aligner_node ---")
416
- image = state.get("image", "")
417
-
418
  project_json = state["project_json"]
419
-
420
  # MODIFICATION 1: Include 'Stage' in the list of names to plan for.
421
  # It's crucial to ensure 'Stage' is always present for its global role.
422
  target_names = [t["name"] for t in project_json["targets"]]
@@ -560,21 +585,16 @@ If you find any "Code-Blocks" then,
560
  end
561
  ```
562
  6. **Donot** add any explaination of logic or comments to justify or explain just put the logic content in the json.
563
- 7. **Output**:
564
- Return **only** a JSON object, using double quotes everywhere, where the key for the pseudo-code will be the target name closest to the "Script for:" value. If no code-blocks are found, return `{"refined_logic": "No Code-blocks"}`.
565
-
566
- ```json
567
- {{
568
- "refined_logic":{{
569
- "[Target name similar to script for]": {{
570
- "plan":[
571
- {{"pseudocode":"…your fully‑formatted pseudo‑code here…"}},
572
- {{"pseudocode":"…your fully‑formatted pseudo‑code here…"}}
573
- ]
574
- }}
575
  }}
576
- }}
577
- ```
578
  """
579
  image_input = {
580
  "type": "image_url",
@@ -592,6 +612,7 @@ Return **only** a JSON object, using double quotes everywhere, where the key for
592
  # Invoke the main agent for logic refinement and relationship identification
593
  response = agent.invoke({"messages": [{"role": "user", "content": content}]})
594
  llm_output_raw = response["messages"][-1].content.strip()
 
595
  parsed_llm_output = extract_json_from_llm_response(llm_output_raw)
596
  result = parsed_llm_output
597
  print(f"result:\n\n {result}")
@@ -614,17 +635,923 @@ Return **only** a JSON object, using double quotes everywhere, where the key for
614
  correction_response = agent_json_resolver.invoke({"messages": [{"role": "user", "content": correction_prompt}]})
615
  corrected_output = extract_json_from_llm_response(correction_response["messages"][-1].content)
616
  #block_relationships = corrected_output.get("block_relationships", [])
617
- result = corrected_output
 
618
  except Exception as e_corr:
619
  logger.error(f"Failed to correct JSON output for even after retry: {e_corr}")
620
 
621
 
622
  # Update the original action_plan in the state with the refined version
623
  state["pseudo_code"] = result
 
 
 
624
  print(f"[OVREALL REFINED PSEUDO CODE LOGIC]: {result}")
625
  logger.info("Plan refinement and block relation analysis completed for all plans.")
626
  return state
627
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
628
  scratch_keywords = [
629
  "move", "turn", "wait", "repeat", "if", "else", "broadcast",
630
  "glide", "change", "forever", "when", "switch",
@@ -990,7 +1917,7 @@ def similarity_matching(input_json_path: str, project_folder:str) -> str:
990
  "meta": {
991
  "semver": "3.0.0",
992
  "vm": "11.3.0",
993
- "agent": "OpenAI ScratchVision Agent"
994
  }
995
  }
996
 
@@ -1031,7 +1958,7 @@ def similarity_matching(input_json_path: str, project_folder:str) -> str:
1031
 
1032
  def delay_for_tpm_node(state: GameState):
1033
  logger.info("--- Running DelayForTPMNode ---")
1034
- time.sleep(10) # Adjust the delay as needed
1035
  logger.info("Delay completed.")
1036
  return state
1037
 
@@ -1040,10 +1967,29 @@ workflow = StateGraph(GameState)
1040
 
1041
  # Add all nodes to the workflow
1042
  workflow.add_node("time_delay_1", delay_for_tpm_node)
1043
- workflow.add_node("opcode_counter", pseudo_generator_node)
1044
- workflow.set_entry_point("time_delay_1")
1045
- workflow.add_edge("time_delay_1","opcode_counter")
1046
- workflow.add_edge("opcode_counter", END)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1047
  app_graph = workflow.compile()
1048
 
1049
  # ============== Helper function to Upscale an Image ============== #
 
22
  from io import BytesIO
23
  from pathlib import Path
24
  import os
25
+ from utils.block_relation_builder import block_builder, variable_adder_main
26
 
27
  # ============================== #
28
  # INITIALIZE CLIP EMBEDDER #
 
121
  description: str
122
  project_id: str
123
  project_image: str
124
+ pseudo_code: dict
125
  action_plan: Optional[Dict]
 
126
  temporary_node: Optional[Dict]
127
 
128
 
 
264
  except Exception as e:
265
  logger.error(f"Unexpected error loading {catalog_path}: {e}")
266
 
267
+ def get_block_by_opcode(catalog_data: dict, opcode: str) -> dict | None:
268
+ """
269
+ Search a single catalog (with keys "description" and "blocks": List[dict])
270
+ for a block whose 'op_code' matches the given opcode.
271
+ Returns the block dict or None if not found.
272
+ """
273
+ for block in catalog_data["blocks"]:
274
+ if block.get("op_code") == opcode:
275
+ return block
276
+ return None
277
+
278
+ # Helper function to find a block in all catalogs by opcode
279
+ def find_block_in_all(opcode: str, all_catalogs: list[dict]) -> dict | None:
280
+ """
281
+ Search across multiple catalogs for a given opcode.
282
+ Returns the first matching block dict or None.
283
+ """
284
+ for catalog in all_catalogs:
285
+ blk = get_block_by_opcode(catalog, opcode)
286
+ if blk is not None:
287
+ return blk
288
+ return None
289
+
290
+
291
+
292
  # --- Global variable for the block catalog ---
293
  # --- Global variable for the block catalog ---
294
  ALL_SCRATCH_BLOCKS_CATALOG = {}
 
439
  # Node 1: Logic updating if any issue here
440
  def pseudo_generator_node(state: GameState):
441
  logger.info("--- Running plan_logic_aligner_node ---")
442
+ image = state.get("project_image", "")
 
443
  project_json = state["project_json"]
444
+
445
  # MODIFICATION 1: Include 'Stage' in the list of names to plan for.
446
  # It's crucial to ensure 'Stage' is always present for its global role.
447
  target_names = [t["name"] for t in project_json["targets"]]
 
585
  end
586
  ```
587
  6. **Donot** add any explaination of logic or comments to justify or explain just put the logic content in the json.
588
+ 7. **Output**:
589
+ Return **only** a JSON object, using double quotes everywhere:
590
+ ```json
591
+ {{
592
+ "refined_logic":{{
593
+ "name_variable": 'Value of "Sript for: "',
594
+ "pseudocode":"…your fully‑formatted pseudo‑code here…",
595
+ }}
 
 
 
 
596
  }}
597
+ ```
 
598
  """
599
  image_input = {
600
  "type": "image_url",
 
612
  # Invoke the main agent for logic refinement and relationship identification
613
  response = agent.invoke({"messages": [{"role": "user", "content": content}]})
614
  llm_output_raw = response["messages"][-1].content.strip()
615
+ print(f"llm_output_raw: {response}")
616
  parsed_llm_output = extract_json_from_llm_response(llm_output_raw)
617
  result = parsed_llm_output
618
  print(f"result:\n\n {result}")
 
635
  correction_response = agent_json_resolver.invoke({"messages": [{"role": "user", "content": correction_prompt}]})
636
  corrected_output = extract_json_from_llm_response(correction_response["messages"][-1].content)
637
  #block_relationships = corrected_output.get("block_relationships", [])
638
+ result = corrected_output
639
+ print(f"result:\n\n {result}")
640
  except Exception as e_corr:
641
  logger.error(f"Failed to correct JSON output for even after retry: {e_corr}")
642
 
643
 
644
  # Update the original action_plan in the state with the refined version
645
  state["pseudo_code"] = result
646
+
647
+ # with open("debug_state.json", "w", encoding="utf-8") as f:
648
+ # json.dump(state, f, indent=2, ensure_ascii=False)
649
  print(f"[OVREALL REFINED PSEUDO CODE LOGIC]: {result}")
650
  logger.info("Plan refinement and block relation analysis completed for all plans.")
651
  return state
652
 
653
+ def overall_planner_node(state: GameState):
654
+ """
655
+ Generates a comprehensive action plan for sprites, including detailed Scratch block information.
656
+ This node acts as an overall planner, leveraging knowledge of all block shapes and categories.
657
+ """
658
+ logger.info("--- Running OverallPlannerNode ---")
659
+
660
+ project_json = state["project_json"]
661
+ raw = state.get("pseudo_code", {})
662
+ refined_logic_data = raw.get("refined_logic", {})
663
+ sprite_name = refined_logic_data.get("name_variable", "<unknown>")
664
+ pseudo = refined_logic_data.get("pseudocode", "")
665
+
666
+ # MODIFICATION 1: Include 'Stage' in the list of names to plan for.
667
+ # It's crucial to ensure 'Stage' is always present for its global role.
668
+ target_names = [t["name"] for t in project_json["targets"]]
669
+
670
+ # MODIFICATION 2: Get sprite positions, providing default for Stage as it doesn't have x,y
671
+ sprite_positions = {}
672
+ for target in project_json["targets"]:
673
+ if not target["isStage"]:
674
+ sprite_positions[target["name"]] = {"x": target.get("x", 0), "y": target.get("y", 0)}
675
+ else:
676
+ sprite_positions[target["name"]] = {"x": "N/A", "y": "N/A"} # Stage doesn't have positional coordinates
677
+
678
+ # declaration_plan = state["declaration_plan"]
679
+
680
+ planning_prompt = f"""
681
+ Generate a detailed action plan for the game's sprites and stage based on the given pseudo-code and sprite details for the given sprite name and .
682
+
683
+ Description:
684
+ **Sprite_name**: {sprite_name}
685
+ **and its corresponding Pseudo_code:**
686
+ '{pseudo}'
687
+
688
+ [Note: Make sure you just refine the pseudo code by correting mistake and adding the missing opcode if any and *Do not* generate any new logic]
689
+ ----
690
+ **Targets in Game (Sprites and Stage) available in project_json:** {', '.join(target_names)}
691
+
692
+ --- Scratch 3.0 Block Reference ---
693
+ This section provides a comprehensive reference of Scratch 3.0 blocks, categorized by shape, including their opcodes and functional descriptions. Use this to accurately identify block types and behavior.
694
+
695
+ ### Hat Blocks
696
+ Description: {hat_description}
697
+ Blocks:
698
+ {hat_opcodes_functionalities}
699
+
700
+ ### Boolean Blocks
701
+ Description: {boolean_description}
702
+ Blocks:
703
+ {boolean_opcodes_functionalities}
704
+
705
+ ### C Blocks
706
+ Description: {c_description}
707
+ Blocks:
708
+ {c_opcodes_functionalities}
709
+
710
+ ### Cap Blocks
711
+ Description: {cap_description}
712
+ Blocks:
713
+ {cap_opcodes_functionalities}
714
+
715
+ ### Reporter Blocks
716
+ Description: {reporter_description}
717
+ Blocks:
718
+ {reporter_opcodes_functionalities}
719
+
720
+ ### Stack Blocks
721
+ Description: {stack_description}
722
+ Blocks:
723
+ {stack_opcodes_functionalities}
724
+
725
+ -----------------------------------
726
+
727
+ Your task is to use the `Sprite_name` given and `Pseudo_code` and add it to the specific target name and define the primary actions and movements.
728
+ The output should be a JSON object with a single key 'action_overall_flow'. Each key inside this object should be a sprite or 'Stage' name (e.g., 'Player', 'Enemy', 'Stage'), and its value must include a 'description' and a list of 'plans'.
729
+ Each plan must include a **single Scratch Hat Block** (e.g., 'event_whenflagclicked') to start scratch project and should contain:
730
+ 1. **'event'**: the exact `opcode` of the hat block that initiates the logic.
731
+ [NOTE: INSTRUCTIONN TO FOLLOW IF PSEUDO_CODE HAVING PROBLEM ]
732
+ 2. **'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.
733
+ - Use 'forever: ...' or 'repeat(10): ...' to prefix repeating logic suitable taking reference from the C blocks.
734
+ - Use Scratch-consistent verbs: 'move', 'change', 'wait', 'hide', 'show', 'say', 'glide', etc.
735
+ - **Numeric values** `(e.g., 0, 5, 0.2, -130)` **must** be in parentheses: `(0)`, `(5)`, `(0.2)`, `(-130)`.
736
+ - **AlphaNumeric values** `(e.g., hello, say 5, 4, hi!)` **must** be in parentheses: `(hello)`, `(say 5)`, `(4)`, `(hi!)`.
737
+ - **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])`.
738
+ - **Dropdown options** must be in the form `[option v]` (e.g., `[Game Start v]`, `[blue sky v]`). example use `when [space v] key pressed`.
739
+ - **Reporter blocks** used as inputs must be double‑wrapped: `((x position))`, `((y position))`. example use `if <((y position)) = (-130)> then` or `(((x position)) * (1))`.
740
+ - **Boolean blocks** in conditions must be inside `< >`, including nested ones: `<not <condition>>`, `<<cond1> and <cond2>>`,`<<cond1> or <cond2>>`.
741
+ - **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]?>`.
742
+ - **Operator expressions** must use explicit Scratch operator blocks, e.g.:
743
+ ```
744
+ (([ballSpeed v]) * (1.1))
745
+ ```
746
+ - **Every hat block script must end** with a final `end` on its own line.
747
+ 3. **Opcode Lists**: include relevant Scratch opcodes grouped under `motion`, `control`, `operator`, `sensing`, `looks`, `sounds`, `events`, and `data`. List only the non-empty categories. Use exact opcodes.
748
+ 4. Few Example of content of logics inside for a specific plan as scratch pseudo-code:
749
+ - example 1[continues moving objects]:
750
+ ```
751
+ when green flag clicked
752
+ go to x: (240) y: (-100)
753
+ set [speed v] to (-5)
754
+ show variable [speed v]
755
+ forever
756
+ change x by ([speed v])
757
+ if <((x position)) < (-240)> then
758
+ go to x: (240) y: (-100)
759
+ end
760
+ end
761
+ end
762
+ ```
763
+ - example 2[jumping script of an plan]:
764
+ ```
765
+ when [space v] key pressed
766
+ if <((y position)) = (-100)> then
767
+ repeat (5)
768
+ change y by (100)
769
+ wait (0.1) seconds
770
+ change y by (-100)
771
+ wait (0.1) seconds
772
+ end
773
+ end
774
+ end
775
+ ```
776
+ - example 3 [pattern for level up and increase difficulty]:
777
+ ```
778
+ when I receive [Level Up v]
779
+ change [level v] by (1)
780
+ set [ballSpeed v] to ((([ballSpeed v]) * (1.1)))
781
+ end
782
+ ```
783
+ 5. Use target names exactly as listed in `Targets in Game`. Do NOT rename or invent new targets.
784
+ 6. Ensure the plan reflects accurate opcode usage derived strictly from the block reference above.
785
+ 7. Few shot Example structure for 'action_overall_flow':
786
+ ```json
787
+ {{
788
+ "action_overall_flow": {{
789
+ "Stage": {{
790
+ "description": "Background and global game state management, including broadcasts, rewards, and score.",
791
+ "plans": [
792
+ {{
793
+ "event": "event_whenflagclicked",
794
+ "logic": "when green flag clicked\n switch backdrop to [backdrop1 v]\n set [score v] to 0\n show variable [score v]\n broadcast [Game Start v]",
795
+ "motion": [],
796
+ "control": [],
797
+ "operator": [],
798
+ "sensing": [],
799
+ "looks": [
800
+ "looks_switchbackdropto"
801
+ ],
802
+ "sounds": [],
803
+ "events": [
804
+ "event_broadcast"
805
+ ],
806
+ "data": [
807
+ "data_setvariableto",
808
+ "data_showvariable"
809
+ ]
810
+ }},
811
+ {{
812
+ "event": "event_whenbroadcastreceived",
813
+ "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 [HighScore v]",
814
+ "motion": [],
815
+ "control": [
816
+ "control_if"
817
+ ],
818
+ "operator": [
819
+ "operator_gt"
820
+ ],
821
+ "sensing": [],
822
+ "looks": [
823
+ "looks_switchbackdropto"
824
+ ],
825
+ "sounds": [],
826
+ "events": [],
827
+ "data": [
828
+ "data_setvariableto"
829
+ ]
830
+ }}
831
+ ]
832
+ }},
833
+ "Sprite1": {{
834
+ "description": "Main character (cat) actions",
835
+ "plans": [
836
+ {{
837
+ "event": "event_whenflagclicked",
838
+ "logic": "when green flag clicked\n go to x: 240 y: -100\n end\n.",
839
+ "motion": [
840
+ "motion_gotoxy"
841
+ ],
842
+ "control": [],
843
+ "operator": [],
844
+ "sensing": [],
845
+ "looks": [],
846
+ "sounds": [],
847
+ "events": [],
848
+ "data": []
849
+ }},
850
+ {{
851
+ "event": "event_whenkeypressed",
852
+ "logic": "when [space v] key pressed\n repeat (10)\n change y by (20)\n wait (0.1) seconds\n change y by (-20)\n end",
853
+ "motion": [
854
+ "motion_changeyby"
855
+ ],
856
+ "control": [
857
+ "control_repeat",
858
+ "control_wait"
859
+ ],
860
+ "operator": [],
861
+ "sensing": [],
862
+ "looks": [],
863
+ "sounds": [],
864
+ "events": [],
865
+ "data": []
866
+ }}
867
+ ]
868
+ }},
869
+ "soccer ball": {{
870
+ "description": "Obstacle movement and interaction",
871
+ "plans": [
872
+ {{
873
+ "event": "event_whenflagclicked",
874
+ "logic": "when green flag clicked\n go to x: 240 y: -135\n forever\n glide 2 seconds to x: -240 y: -135\n if <(x position) < -235> then\n set x to 240\n end\n if <touching [Sprite1 v]?> then\n broadcast [Game Over v]\n stop [all v]\n end\n end",
875
+ "motion": [
876
+ "motion_gotoxy",
877
+ "motion_glidesecstoxy",
878
+ "motion_xposition",
879
+ "motion_setx"
880
+ ],
881
+ "control": [
882
+ "control_forever",
883
+ "control_if",
884
+ "control_stop"
885
+ ],
886
+ "operator": [
887
+ "operator_lt"
888
+ ],
889
+ "sensing": [
890
+ "sensing_istouching",
891
+ "sensing_touchingobjectmenu"
892
+ ],
893
+ "looks": [],
894
+ "sounds": [],
895
+ "events": [
896
+ "event_broadcast"
897
+ ],
898
+ "data": []
899
+ }}
900
+ ]
901
+ }}
902
+
903
+ }}
904
+ }}
905
+ ```
906
+ 8. Based on the provided context, generate the `action_overall_flow`.
907
+ - Maintain the **exact JSON structure** shown above.
908
+ - All `logic` fields must be **clear and granular**.
909
+ - Only include opcode categories that contain relevant opcodes.
910
+ - Ensure that each opcode matches its intended Scratch functionality.
911
+ - If feedback suggests major change, **rethink the entire plan** for the affected sprite(s).
912
+ - If feedback is minor, make precise, minimal improvements only.
913
+ """
914
+
915
+ try:
916
+ response = agent.invoke({"messages": [{"role": "user", "content": planning_prompt}]})
917
+ print("Raw response from LLM [OverallPlannerNode 1]:",response)
918
+ raw_response = response["messages"][-1].content#strip_noise(response["messages"][-1].content)
919
+ print("Raw response from LLM [OverallPlannerNode 2]:", raw_response) # Uncomment for debugging
920
+ # json debugging and solving
921
+ try:
922
+ overall_plan = extract_json_from_llm_response(raw_response)
923
+ except json.JSONDecodeError as error_json:
924
+ logger.error("Failed to extract JSON from LLM response. Attempting to correct the response.")
925
+ # Use the JSON resolver agent to fix the response
926
+ correction_prompt = (
927
+ "Your task is to correct the provided JSON string to ensure it is **syntactically perfect and adheres strictly to JSON rules**.\n"
928
+ "Carefully review the JSON for any errors, especially focusing on the reported error at:\n"
929
+ f"- **Error Details**: {error_json}\n\n"
930
+ "**Strict Instructions for your response:**\n"
931
+ "1. **ONLY** output the corrected JSON. Do not include any other text, comments, or explanations outside the JSON.\n"
932
+ "2. Ensure all property names (keys) are enclosed in **double quotes**.\n"
933
+ "3. Ensure string values are correctly enclosed in **double quotes** and any internal special characters (like newlines `\\n`, tabs `\\t`, backslashes `\\\\`, or double quotes `\\`) are properly **escaped**.\n"
934
+ "4. Verify that there are **no extra commas**, especially between key-value pairs or after the last element in an object or array.\n"
935
+ "5. Ensure proper nesting and matching of curly braces `{}` and square brackets `[]`.\n"
936
+ "6. **Crucially, remove any extraneous characters or duplicate closing braces outside the main JSON object.**\n"
937
+ "7. The corrected JSON must be a **complete and valid** JSON object.\n\n"
938
+ "Here is the problematic JSON string to correct:\n"
939
+ "```json\n"
940
+ f"{raw_response}\n"
941
+ "```\n"
942
+ "Corrected JSON:\n"
943
+ )
944
+ correction_response = agent_json_resolver.invoke({"messages": [{"role": "user", "content": correction_prompt}]})
945
+ print(f"[JSON CORRECTOR RESPONSE AT OVERALLPLANNERNODE ]: {correction_response["messages"][-1].content}")
946
+ overall_plan= extract_json_from_llm_response(correction_response["messages"][-1].content)#strip_noise(correction_response["messages"][-1].content))
947
+
948
+ state["action_plan"] = overall_plan
949
+ logger.info("Overall plan generated by OverallPlannerNode.")
950
+
951
+ # with open("debug_state.json", "w", encoding="utf-8") as f:
952
+ # json.dump(state, f, indent=2, ensure_ascii=False)
953
+
954
+ return state
955
+
956
+ except Exception as e:
957
+ logger.error(f"Error in OverallPlannerNode: {e}")
958
+ raise
959
+
960
+ def refined_planner_node(state: GameState):
961
+ """
962
+ Refines the action plan based on validation feedback and game description.
963
+ """
964
+ logger.info("--- Running RefinedPlannerNode ---")
965
+ raw = state.get("pseudo_code", {})
966
+ refined_logic_data = raw.get("refined_logic", {})
967
+ sprite_name = refined_logic_data.get("name_variable", "<unknown>")
968
+ pseudo = refined_logic_data.get("pseudocode", "")
969
+ #detailed_game_description = state.get("detailed_game_description", state.get("description", "A game."))
970
+ current_action_plan = state.get("action_plan", {})
971
+ print(f"[current_action_plan before refinement] on ({state.get('iteration_count', 0)}): {json.dumps(current_action_plan, indent=2)}")
972
+ plan_validation_feedback = state.get("plan_validation_feedback", "No specific feedback provided. Assume general refinement is needed.")
973
+ project_json = state["project_json"]
974
+ target_names = [t["name"] for t in project_json["targets"]]
975
+
976
+ # MODIFICATION 2: Get sprite positions, providing default for Stage as it doesn't have x,y
977
+ sprite_positions = {}
978
+ for target in project_json["targets"]:
979
+ if not target["isStage"]:
980
+ sprite_positions[target["name"]] = {"x": target.get("x", 0), "y": target.get("y", 0)}
981
+ else:
982
+ sprite_positions[target["name"]] = {"x": "N/A", "y": "N/A"} # Stage doesn't have positional coordinates
983
+
984
+ #declaration_plan = state["declaration_plan"]
985
+
986
+ refinement_prompt = f"""
987
+ Refine and correct the JSON object `action_overall_flow` so that it fully aligns with the detailed game description, sprite positions, variable/broadcast declarations, and Scratch 3.0 block reference—while also validating for common formatting or opcode errors.
988
+
989
+ Here is the overall script available:
990
+ **Sprite name**: {sprite_name}
991
+ **and its corresponding Pseudo_code:**
992
+ '{pseudo}'
993
+
994
+ [Note: Make sure you just refine the pseudo code by correting mistake and adding the missing opcode if any and *Do not* generate any new logic]
995
+ ---
996
+ **Targets in Game (Sprites and Stage):** {', '.join(target_names)}
997
+ **Current action plan:**
998
+ {current_action_plan}
999
+
1000
+ **Validation Feedback:**
1001
+ '{plan_validation_feedback}'
1002
+
1003
+ --- Scratch 3.0 Block Reference ---
1004
+ ### Hat Blocks
1005
+ {hat_opcodes_functionalities}
1006
+
1007
+ ### Boolean Blocks
1008
+ {boolean_opcodes_functionalities}
1009
+
1010
+ ### C Blocks
1011
+ {c_opcodes_functionalities}
1012
+
1013
+ ### Cap Blocks
1014
+ {cap_opcodes_functionalities}
1015
+
1016
+ ### Reporter Blocks
1017
+ {reporter_opcodes_functionalities}
1018
+
1019
+ ### Stack Blocks
1020
+ {stack_opcodes_functionalities}
1021
+
1022
+ -----------------------------------
1023
+
1024
+ * **Your task is to align to description, refine and correct the JSON object 'action_overall_flow'.**
1025
+ Use sprite names exactly as provided in `sprite_names` (e.g., 'Sprite1', 'soccer ball'); and also the stage, do **NOT** rename them.
1026
+ 1. **'event'**: the exact `opcode` of the hat block that initiates the logic.
1027
+ 2. **'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.
1028
+ - Do **NOT** include any justification or comments—only the raw logic.
1029
+ - Use 'forever: ...' or 'repeat(10): ...' to prefix repeating logic suitable taking reference from the C blocks.
1030
+ - Use Scratch-consistent verbs: 'move', 'change', 'wait', 'hide', 'show', 'say', 'glide', etc.
1031
+ - **Numeric values** `(e.g., 0, 5, 0.2, -130)` **must** be in parentheses: `(0)`, `(5)`, `(0.2)`, `(-130)`.
1032
+ - **AlphaNumeric values** `(e.g., hello, say 5, 4, hi!)` **must** be in parentheses: `(hello)`, `(say 5)`, `(4)`, `(hi!)`.
1033
+ - **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])`.
1034
+ - **Dropdown options** must be in the form `[option v]` (e.g., `[Game Start v]`, `[blue sky v]`). example use `when [space v] key pressed`.
1035
+ - **Reporter blocks** used as inputs must be double‑wrapped: `((x position))`, `((y position))`. example use `if <((y position)) = (-130)> then` or `(((x position)) * (1))`.
1036
+ - **Boolean blocks** in conditions must be inside `< >`, including nested ones: `<not <condition>>`, `<<cond1> and <cond2>>`,`<<cond1> or <cond2>>`.
1037
+ - **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]?>`.
1038
+ - **Operator expressions** must use explicit Scratch operator blocks, e.g.:
1039
+ ```
1040
+ (([ballSpeed v]) * (1.1))
1041
+ ```
1042
+ - **Every hat block script must end** with a final `end` on its own line.
1043
+ 3. **Validation & Formatting Checks**
1044
+ - **Opcode Coverage**: Ensure every action in `logic` has a matching opcode in the lists.
1045
+ - **Bracket Nesting**: Confirm every `(` has a matching `)`, e.g., `(pick random (100) to (-100))`.
1046
+ - **Operator Formatting**: Validate that operators use Scratch operator blocks, not inline math.
1047
+ - **Common Errors**:
1048
+ - `pick random (100,-100)` → `(pick random (100) to (-100))`
1049
+ - Missing `end` at script conclusion
1050
+ - Unwrapped reporter inputs or Boolean tests
1051
+ 4. **Opcode Lists**: include relevant Scratch opcodes grouped under `motion`, `control`, `operator`, `sensing`, `looks`, `sounds`, `events`, and `data`. List only the non-empty categories. Use exact opcodes .
1052
+ 5. Few Example of content of logics inside for a specific plan as scratch pseudo-code:
1053
+ - example 1[continues moving objects]:
1054
+ ```
1055
+ when green flag clicked
1056
+ go to x: (240) y: (-100)
1057
+ set [speed v] to (-5)
1058
+ show variable [speed v]
1059
+ forever
1060
+ change x by ([speed v])
1061
+ if <((x position)) < (-240)> then
1062
+ go to x: (240) y: (-100)
1063
+ end
1064
+ end
1065
+ end
1066
+ ```
1067
+ - example 2[jumping script of an plan]:
1068
+ ```
1069
+ when [space v] key pressed
1070
+ if <((y position)) = (-100)> then
1071
+ repeat (5)
1072
+ change y by (100)
1073
+ wait (0.1) seconds
1074
+ change y by (-100)
1075
+ wait (0.1) seconds
1076
+ end
1077
+ end
1078
+ end
1079
+ ```
1080
+ - example 3 [pattern for level up and increase difficulty]:
1081
+ ```
1082
+ when I receive [Level Up v]
1083
+ change [level v] by (1)
1084
+ set [ballSpeed v] to ((([ballSpeed v]) * (1.1)))
1085
+ end
1086
+ ```
1087
+ 6. Use target names exactly as listed in `Targets in Game`. Do NOT rename or invent new targets.
1088
+ 7. Ensure the plan reflects accurate opcode usage derived strictly from the block reference above.
1089
+ 8. Few shot Example structure for 'action_overall_flow':
1090
+ ```json
1091
+ {{
1092
+ "action_overall_flow": {{
1093
+ "Stage": {{
1094
+ "description": "Background and global game state management, including broadcasts, rewards, and score.",
1095
+ "plans": [
1096
+ {{
1097
+ "event": "event_whenflagclicked",
1098
+ "logic": "when green flag clicked\n switch backdrop to [backdrop1 v]\n set [score v] to 0\n show variable [score v]\n broadcast [Game Start v]",
1099
+ "motion": [],
1100
+ "control": [],
1101
+ "operator": [],
1102
+ "sensing": [],
1103
+ "looks": [
1104
+ "looks_switchbackdropto"
1105
+ ],
1106
+ "sounds": [],
1107
+ "events": [
1108
+ "event_broadcast"
1109
+ ],
1110
+ "data": [
1111
+ "data_setvariableto",
1112
+ "data_showvariable"
1113
+ ]
1114
+ }},
1115
+ {{
1116
+ "event": "event_whenbroadcastreceived",
1117
+ "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 [HighScore v]",
1118
+ "motion": [],
1119
+ "control": [
1120
+ "control_if"
1121
+ ],
1122
+ "operator": [
1123
+ "operator_gt"
1124
+ ],
1125
+ "sensing": [],
1126
+ "looks": [
1127
+ "looks_switchbackdropto"
1128
+ ],
1129
+ "sounds": [],
1130
+ "events": [],
1131
+ "data": [
1132
+ "data_setvariableto"
1133
+ ]
1134
+ }}
1135
+ ]
1136
+ }},
1137
+ "Sprite1": {{
1138
+ "description": "Main character (cat) actions",
1139
+ "plans": [
1140
+ {{
1141
+ "event": "event_whenflagclicked",
1142
+ "logic": "when green flag clicked\n go to x: 240 y: -100\n end\n.",
1143
+ "motion": [
1144
+ "motion_gotoxy"
1145
+ ],
1146
+ "control": [],
1147
+ "operator": [],
1148
+ "sensing": [],
1149
+ "looks": [],
1150
+ "sounds": [],
1151
+ "events": [],
1152
+ "data": []
1153
+ }},
1154
+ {{
1155
+ "event": "event_whenkeypressed",
1156
+ "logic": "when [space v] key pressed\n repeat (10)\n change y by (20)\n wait (0.1) seconds\n change y by (-20)\n end",
1157
+ "motion": [
1158
+ "motion_changeyby"
1159
+ ],
1160
+ "control": [
1161
+ "control_repeat",
1162
+ "control_wait"
1163
+ ],
1164
+ "operator": [],
1165
+ "sensing": [],
1166
+ "looks": [],
1167
+ "sounds": [],
1168
+ "events": [],
1169
+ "data": []
1170
+ }}
1171
+ ]
1172
+ }},
1173
+ "soccer ball": {{
1174
+ "description": "Obstacle movement and interaction",
1175
+ "plans": [
1176
+ {{
1177
+ "event": "event_whenflagclicked",
1178
+ "logic": "when green flag clicked\n go to x: 240 y: -135\n forever\n glide 2 seconds to x: -240 y: -135\n if <(x position) < -235> then\n set x to 240\n end\n if <touching [Sprite1 v]?> then\n broadcast [Game Over v]\n stop [all v]\n end\n end",
1179
+ "motion": [
1180
+ "motion_gotoxy",
1181
+ "motion_glidesecstoxy",
1182
+ "motion_xposition",
1183
+ "motion_setx"
1184
+ ],
1185
+ "control": [
1186
+ "control_forever",
1187
+ "control_if",
1188
+ "control_stop"
1189
+ ],
1190
+ "operator": [
1191
+ "operator_lt"
1192
+ ],
1193
+ "sensing": [
1194
+ "sensing_istouching",
1195
+ "sensing_touchingobjectmenu"
1196
+ ],
1197
+ "looks": [],
1198
+ "sounds": [],
1199
+ "events": [
1200
+ "event_broadcast"
1201
+ ],
1202
+ "data": []
1203
+ }}
1204
+ ]
1205
+ }}
1206
+ }}
1207
+ }}
1208
+ ```
1209
+ 9. Use the validation feedback to address errors, fill in missing logic, or enhance clarity.
1210
+ example of few possible improvements: 1.event_whenflagclicked is used to control sprite but its used for actual start scratch project and reset scratch. 2. looping like forever used where we should use iterative. 3. missing of for variable we used in the block
1211
+ - Maintain the **exact JSON structure** shown above.
1212
+ - All `logic` fields must be **clear and granular**.
1213
+ - Only include opcode categories that contain relevant opcodes.
1214
+ - Ensure that each opcode matches its intended Scratch functionality.
1215
+ - If feedback suggests major change, **rethink the entire plan** for the affected sprite(s).
1216
+ - If feedback is minor, make precise, minimal improvements only.
1217
+ """
1218
+ try:
1219
+ response = agent.invoke({"messages": [{"role": "user", "content": refinement_prompt}]})
1220
+ raw_response = response["messages"][-1].content#strip_noise(response["messages"][-1].content)
1221
+ logger.info(f"Raw response from LLM [RefinedPlannerNode]: {raw_response[:500]}...")
1222
+ # json debugging and solving
1223
+ try:
1224
+ refined_plan = extract_json_from_llm_response(raw_response)
1225
+ except json.JSONDecodeError as error_json:
1226
+ logger.error("Failed to extract JSON from LLM response. Attempting to correct the response.")
1227
+ # Use the JSON resolver agent to fix the response
1228
+ correction_prompt = (
1229
+ "Your task is to correct the provided JSON string to ensure it is **syntactically perfect and adheres strictly to JSON rules**.\n"
1230
+ "Carefully review the JSON for any errors, especially focusing on the reported error at:\n"
1231
+ f"- **Error Details**: {error_json}\n\n"
1232
+ "**Strict Instructions for your response:**\n"
1233
+ "1. **ONLY** output the corrected JSON. Do not include any other text, comments, or explanations outside the JSON.\n"
1234
+ "2. Ensure all property names (keys) are enclosed in **double quotes**.\n"
1235
+ "3. Ensure string values are correctly enclosed in **double quotes** and any internal special characters (like newlines `\\n`, tabs `\\t`, backslashes `\\\\`, or double quotes `\\`) are properly **escaped**.\n"
1236
+ "4. IN `logic` field make sure content enclosed in **double quotes** should not have invalid **double quotes**, **eliminate** all quotes inside the content if any. "
1237
+ "4. Verify that there are **no extra commas**, especially between key-value pairs or after the last element in an object or array.\n"
1238
+ "5. Ensure proper nesting and matching of curly braces `{}` and square brackets `[]`.\n"
1239
+ "6. **Crucially, remove any extraneous characters or duplicate closing braces outside the main JSON object.**\n" # Added instruction
1240
+ "7. The corrected JSON must be a **complete and valid** JSON object.\n\n"
1241
+ "Here is the problematic JSON string to correct:\n"
1242
+ "```json\n"
1243
+ f"{raw_response}\n"
1244
+ "```\n"
1245
+ "Corrected JSON:\n"
1246
+ )
1247
+ correction_response = agent_json_resolver.invoke({"messages": [{"role": "user", "content": correction_prompt}]})
1248
+ print(f"[JSON CORRECTOR RESPONSE AT REFINEPLANNER ]: {correction_response["messages"][-1].content}")
1249
+ refined_plan = extract_json_from_llm_response(correction_response["messages"][-1].content)#strip_noise(correction_response["messages"][-1].content))
1250
+ logger.info("Refined plan corrected by JSON resolver agent.")
1251
+
1252
+ if refined_plan:
1253
+ #state["action_plan"] = refined_plan.get("action_overall_flow", {}) # Update to the key 'action_overall_flow' [error]
1254
+ state["action_plan"] = refined_plan #.get("action_overall_flow", {}) # Update the main the prompt includes updated only
1255
+ logger.info("Action plan refined by RefinedPlannerNode.")
1256
+ else:
1257
+ logger.warning("RefinedPlannerNode did not return a valid 'action_overall_flow' structure. Keeping previous plan.")
1258
+ print("[Refined Action Plan]:", json.dumps(state["action_plan"], indent=2))
1259
+ #print("[current state after refinement]:", json.dumps(state, indent=2))
1260
+
1261
+ # with open("debug_state.json", "w", encoding="utf-8") as f:
1262
+ # json.dump(state, f, indent=2, ensure_ascii=False)
1263
+
1264
+ return state
1265
+ except Exception as e:
1266
+ logger.error(f"Error in RefinedPlannerNode: {e}")
1267
+ raise
1268
+
1269
+ def plan_opcode_counter_node(state: Dict[str, Any]) -> Dict[str, Any]:
1270
+ """
1271
+ For each plan in state["action_plan"], calls the LLM agent
1272
+ to analyze the `logic` string and return a list of {opcode, count} for each category.
1273
+ """
1274
+ logger.info("=== Running OPCODE COUTER LOGIC with LLM counts ===")
1275
+ #game_description = state.get("description", "No game description provided.")
1276
+ sprite_name = {}
1277
+ project_json_targets = state.get("project_json", {}).get("targets", [])
1278
+ for target in project_json_targets:
1279
+ sprite_name[target["name"]] = target["name"]
1280
+
1281
+ action_flow = state.get("action_plan", {})
1282
+
1283
+ if action_flow.get("action_overall_flow", {}) == {}:
1284
+ plan_data = action_flow.items()
1285
+ else:
1286
+ plan_data = action_flow.get("action_overall_flow", {}).items()
1287
+
1288
+ refined_flow: Dict[str, Any] = {}
1289
+ for sprite, sprite_data in plan_data:
1290
+ refined_plans = []
1291
+ for plan in sprite_data.get("plans", []):
1292
+ logic = plan.get("logic", "")
1293
+ event = plan.get("event", "")
1294
+
1295
+ # These are for guiding the LLM, not for the final output format directly
1296
+ opcodes_from_plan = {
1297
+ "motion": plan.get("motion", []),
1298
+ "control": plan.get("control", []),
1299
+ "operator": plan.get("operator", []),
1300
+ "sensing": plan.get("sensing", []),
1301
+ "looks": plan.get("looks", []),
1302
+ "sounds": plan.get("sounds", []),
1303
+ "events": plan.get("events", []) + ([event] if isinstance(event, str) else []),
1304
+ "data": plan.get("data", []),
1305
+ }
1306
+
1307
+ refinement_prompt = f"""
1308
+ You are a Scratch 3.0 expert with deep knowledge of block types, nesting and stack relationships.
1309
+ Your job: read the plan logic below and decide exactly which blocks (and how many of each) are required to implement it.
1310
+ Review the following plan for '{sprite}' triggered by '{event}'.
1311
+ --- Scratch 3.0 Block Reference ---
1312
+ ### Hat Blocks
1313
+ Description: {hat_description}
1314
+ Blocks:
1315
+ {hat_opcodes_functionalities}
1316
+
1317
+ ### Boolean Blocks
1318
+ Description: {boolean_description}
1319
+ Blocks:
1320
+ {boolean_opcodes_functionalities}
1321
+
1322
+ ### C Blocks
1323
+ Description: {c_description}
1324
+ Blocks:
1325
+ {c_opcodes_functionalities}
1326
+
1327
+ ### Cap Blocks
1328
+ Description: {cap_description}
1329
+ Blocks:
1330
+ {cap_opcodes_functionalities}
1331
+
1332
+ ### Reporter Blocks
1333
+ Description: {reporter_description}
1334
+ Blocks:
1335
+ {reporter_opcodes_functionalities}
1336
+
1337
+ ### Stack Blocks
1338
+ Description: {stack_description}
1339
+ Blocks:
1340
+ {stack_opcodes_functionalities}
1341
+ -----------------------------------
1342
+ Current Plan Details:
1343
+ - Event (Hat Block Opcode): {event}
1344
+ - Associated Opcodes by Category: {json.dumps(opcodes_from_plan, indent=2)}
1345
+
1346
+ ── Game Context ──
1347
+ Sprite: "{sprite}"
1348
+
1349
+ ── Current Plan ──
1350
+ Event (hat block): {event}
1351
+ Logic (pseudo-Scratch): {logic}
1352
+ Plan : {plan}
1353
+
1354
+ ── Opcode Candidates ──
1355
+ Motion: {opcodes_from_plan["motion"]}
1356
+ Control: {opcodes_from_plan["control"]}
1357
+ Operator: {opcodes_from_plan["operator"]}
1358
+ Sensing: {opcodes_from_plan["sensing"]}
1359
+ Looks: {opcodes_from_plan["looks"]}
1360
+ Sounds: {opcodes_from_plan["sounds"]}
1361
+ Events: {opcodes_from_plan["events"]}
1362
+ Data: {opcodes_from_plan["data"]}
1363
+
1364
+ ── Your Task ──
1365
+ 1. Analyze the “Logic” steps and choose exactly which opcodes are needed.
1366
+ 2. Return a top-level JSON object with a single key: "opcode_counts".
1367
+ 3. The value of "opcode_counts" should be a list of objects, where each object has "opcode": "<opcode_name>" and "count": <integer>.
1368
+ 4. Ensure the list includes the hat block for this plan (e.g., event_whenflagclicked, event_whenkeypressed, event_whenbroadcastreceived) with a count of 1.
1369
+ 5. The order of opcodes within the "opcode_counts" list does not matter.
1370
+ 6. If any plan logic is None do not generate the opcode_counts for it.
1371
+ 7. Use only double quotes and ensure valid JSON.
1372
+
1373
+ Example output:
1374
+ **example 1**
1375
+ ```json
1376
+ {{
1377
+ "opcode_counts":[
1378
+ {{"opcode":"motion_gotoxy","count":1}},
1379
+ {{"opcode":"control_forever","count":1}},
1380
+ {{"opcode":"control_if","count":1}},
1381
+ {{"opcode":"looks_switchbackdropto","count":1}},
1382
+ {{"opcode":"event_whenflagclicked","count":1}},
1383
+ {{"opcode":"event_broadcast","count":1}},
1384
+ {{"opcode":"data_setvariableto","count":2}},
1385
+ {{"opcode":"data_showvariable","count":2}}
1386
+ ]
1387
+ }}
1388
+ ```
1389
+ **example 2**
1390
+ ```json
1391
+ {{
1392
+ "opcode_counts":[
1393
+ {{"opcode":"motion_gotoxy","count":1}},
1394
+ {{"opcode":"motion_glidesecstoxy","count":1}},
1395
+ {{"opcode":"motion_xposition","count":1}},
1396
+ {{"opcode":"motion_setx","count":1}},
1397
+ {{"opcode":"control_forever","count":1}},
1398
+ {{"opcode":"control_if","count":2}},
1399
+ {{"opcode":"operator_lt","count":1}},
1400
+ {{"opcode":"sensing_istouching","count":1}},
1401
+ {{"opcode":"event_whenflagclicked","count":1}},
1402
+ {{"opcode":"event_broadcast","count":1}}
1403
+ ]
1404
+ }}
1405
+ ```
1406
+ """
1407
+ try:
1408
+ response = agent.invoke({"messages": [{"role": "user", "content": refinement_prompt}]})
1409
+ llm_output = response["messages"][-1].content
1410
+ llm_json = extract_json_from_llm_response(llm_output)
1411
+ logger.info(f"Successfully analyze the opcode requirement for {sprite} - {event}.")
1412
+
1413
+ except json.JSONDecodeError as error_json:
1414
+ logger.error(f"JSON Decode Error for {sprite} - {event}: {error_json}. Attempting correction.")
1415
+ correction_prompt = (
1416
+ "Your task is to correct the provided JSON string to ensure it is **syntactically perfect and adheres strictly to JSON rules**.\n"
1417
+ "It must be a JSON object with a single key `opcode_counts` containing a list of objects like {{'opcode': '<opcode_name>', 'count': <integer>}}.\n"
1418
+ f"- **Error Details**: {error_json}\n\n"
1419
+ "**Strict Instructions for your response:**\n"
1420
+ "1. **ONLY** output the corrected JSON. Do not include any other text or explanations.\n"
1421
+ "2. Ensure all keys and string values are enclosed in **double quotes**. Escape internal quotes (`\\`).\n"
1422
+ "3. No trailing commas. Correct nesting.\n\n"
1423
+ "Here is the problematic JSON string to correct:\n"
1424
+ f"```json\n{llm_output}\n```\n"
1425
+ "Corrected JSON:\n"
1426
+ )
1427
+ try:
1428
+ correction_response = agent_json_resolver.invoke({"messages": [{"role": "user", "content": correction_prompt}]})
1429
+ llm_json = extract_json_from_llm_response(correction_response["messages"][-1].content)
1430
+ logger.info(f"Successfully corrected JSON output for {sprite} - {event}.")
1431
+ except Exception as e_corr:
1432
+ logger.error(f"Failed to correct JSON output for {sprite} - {event} even after retry: {e_corr}")
1433
+ continue
1434
+
1435
+ # Directly use the 'opcode_counts' list from the LLM's output
1436
+ plan["opcode_counts"] = llm_json.get("opcode_counts", [])
1437
+
1438
+ # Optionally, you can remove the individual category lists from the plan
1439
+ # if they are no longer needed after the LLM provides the consolidated list.
1440
+ # for key in ["motion", "control", "operator", "sensing", "looks", "sounds", "events", "data"]:
1441
+ # if key in plan:
1442
+ # del plan[key]
1443
+
1444
+ refined_plans.append(plan)
1445
+
1446
+ refined_flow[sprite] = {
1447
+ "description": sprite_data.get("description", ""),
1448
+ "plans": refined_plans
1449
+ }
1450
+
1451
+ if refined_flow:
1452
+ state["action_plan"] = refined_flow
1453
+ logger.info("logic aligned by logic_aligner_Node.")
1454
+
1455
+ state["temporary_node"] = refined_flow
1456
+ #state["temporary_node"] = refined_flow
1457
+ print(f"[OPCODE COUTER LOGIC]: {refined_flow}")
1458
+ logger.info("=== OPCODE COUTER LOGIC completed ===")
1459
+ return state
1460
+
1461
+ # Node 10:Function based block builder node
1462
+ def overall_block_builder_node_2(state: dict):
1463
+ logger.info("--- Running OverallBlockBuilderNode ---")
1464
+
1465
+ project_json = state["project_json"]
1466
+ targets = project_json["targets"]
1467
+ # --- Sprite and Stage Target Mapping ---
1468
+ sprite_map = {target["name"]: target for target in targets if not target["isStage"]}
1469
+ stage_target = next((target for target in targets if target["isStage"]), None)
1470
+ if stage_target:
1471
+ sprite_map[stage_target["name"]] = stage_target
1472
+
1473
+ action_plan = state.get("action_plan", {})
1474
+ print("[Overall Action Plan received at the block generator]:", json.dumps(action_plan, indent=2))
1475
+ if not action_plan:
1476
+ logger.warning("No action plan found in state. Skipping OverallBlockBuilderNode.")
1477
+ return state
1478
+
1479
+ # Initialize offsets for script placement on the Scratch canvas
1480
+ script_y_offset = {}
1481
+ script_x_offset_per_sprite = {name: 0 for name in sprite_map.keys()}
1482
+
1483
+ # This handles potential variations in the action_plan structure.
1484
+ if action_plan.get("action_overall_flow", {}) == {}:
1485
+ plan_data = action_plan.items()
1486
+ else:
1487
+ plan_data = action_plan.get("action_overall_flow", {}).items()
1488
+
1489
+ # --- Extract global project context for LLM ---
1490
+ all_sprite_names = list(sprite_map.keys())
1491
+ all_variable_names = {}
1492
+ all_list_names = {}
1493
+ all_broadcast_messages = {}
1494
+
1495
+ for target in targets:
1496
+ for var_id, var_info in target.get("variables", {}).items():
1497
+ all_variable_names[var_info[0]] = var_id # Store name -> ID mapping (e.g., "myVariable": "myVarId123")
1498
+ for list_id, list_info in target.get("lists", {}).items():
1499
+ all_list_names[list_info[0]] = list_id # Store name -> ID mapping
1500
+ for broadcast_id, broadcast_name in target.get("broadcasts", {}).items():
1501
+ all_broadcast_messages[broadcast_name] = broadcast_id # Store name -> ID mapping
1502
+
1503
+ # --- Process each sprite's action plan ---
1504
+ for sprite_name, sprite_actions_data in plan_data:
1505
+ if sprite_name in sprite_map:
1506
+ current_sprite_target = sprite_map[sprite_name]
1507
+ if "blocks" not in current_sprite_target:
1508
+ current_sprite_target["blocks"] = {}
1509
+
1510
+ if sprite_name not in script_y_offset:
1511
+ script_y_offset[sprite_name] = 0
1512
+
1513
+ for plan_entry in sprite_actions_data.get("plans", []):
1514
+ logic_sequence = str(plan_entry["logic"])
1515
+ opcode_counts = plan_entry.get("opcode_counts", {})
1516
+
1517
+ try:
1518
+ generated_blocks=block_builder(opcode_counts,logic_sequence)
1519
+ if "blocks" in generated_blocks and isinstance(generated_blocks["blocks"], dict):
1520
+ logger.warning(f"LLM returned nested 'blocks' key for {sprite_name}. Unwrapping.")
1521
+ generated_blocks = generated_blocks["blocks"]
1522
+
1523
+ # Update block positions for top-level script
1524
+ for block_id, block_data in generated_blocks.items():
1525
+ if block_data.get("topLevel"):
1526
+ block_data["x"] = script_x_offset_per_sprite.get(sprite_name, 0)
1527
+ block_data["y"] = script_y_offset[sprite_name]
1528
+ script_y_offset[sprite_name] += 150 # Increment for next script
1529
+
1530
+ current_sprite_target["blocks"].update(generated_blocks)
1531
+ print(f"[current_sprite_target block updated]: {current_sprite_target['blocks']}")
1532
+ state["iteration_count"] = 0
1533
+ logger.info(f"Action blocks added for sprite '{sprite_name}' by OverallBlockBuilderNode.")
1534
+ except Exception as e:
1535
+ logger.error(f"Error generating blocks for sprite '{sprite_name}': {e}")
1536
+
1537
+ state["project_json"] = project_json
1538
+ # with open("debug_state.json", "w", encoding="utf-8") as f:
1539
+ # json.dump(state, f, indent=2, ensure_ascii=False)
1540
+
1541
+ return state
1542
+
1543
+ # # Node 11: Variable adder node
1544
+ def variable_adder_node(state: GameState):
1545
+ project_json = state["project_json"]
1546
+ try:
1547
+ updated_project_json = variable_adder_main(project_json)
1548
+ state["project_json"]=updated_project_json
1549
+ return state
1550
+ except Exception as e:
1551
+ logger.error(f"Error in variable adder node while updating project_json': {e}")
1552
+ raise
1553
+
1554
+
1555
  scratch_keywords = [
1556
  "move", "turn", "wait", "repeat", "if", "else", "broadcast",
1557
  "glide", "change", "forever", "when", "switch",
 
1917
  "meta": {
1918
  "semver": "3.0.0",
1919
  "vm": "11.3.0",
1920
+ "agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36"
1921
  }
1922
  }
1923
 
 
1958
 
1959
  def delay_for_tpm_node(state: GameState):
1960
  logger.info("--- Running DelayForTPMNode ---")
1961
+ time.sleep(60) # Adjust the delay as needed
1962
  logger.info("Delay completed.")
1963
  return state
1964
 
 
1967
 
1968
  # Add all nodes to the workflow
1969
  workflow.add_node("time_delay_1", delay_for_tpm_node)
1970
+ workflow.add_node("time_delay_2", delay_for_tpm_node)
1971
+ workflow.add_node("time_delay_3", delay_for_tpm_node)
1972
+ # workflow.add_node("time_delay_4", delay_for_tpm_node)
1973
+ workflow.add_node("pseudo_generator", pseudo_generator_node)
1974
+ workflow.add_node("plan_generator", overall_planner_node)
1975
+ #workflow.add_node("logic_alignment", plan_logic_aligner_node)
1976
+ #workflow.add_node("plan_verifier", plan_verification_node)
1977
+ workflow.add_node("refined_planner", refined_planner_node) # Refines the action plan
1978
+ workflow.add_node("opcode_counter", plan_opcode_counter_node)
1979
+ workflow.add_node("block_builder", overall_block_builder_node_2)
1980
+ # #workflow.add_node("variable_initializer", variable_adder_node)
1981
+
1982
+ workflow.set_entry_point("pseudo_generator")
1983
+ workflow.add_edge("pseudo_generator","time_delay_1")
1984
+ workflow.add_edge("time_delay_1","plan_generator")
1985
+ workflow.add_edge("plan_generator","time_delay_2")
1986
+ workflow.add_edge("time_delay_2",END)
1987
+ workflow.add_edge("time_delay_2","refined_planner")
1988
+ workflow.add_edge("refined_planner","time_delay_3")
1989
+ workflow.add_edge("time_delay_3","opcode_counter")
1990
+ workflow.add_edge("opcode_counter","block_builder")
1991
+ workflow.add_edge("block_builder",END)
1992
+ #workflow.add_edge("variable_initializer", END)
1993
  app_graph = workflow.compile()
1994
 
1995
  # ============== Helper function to Upscale an Image ============== #