import json import copy import re from collections import defaultdict def generate_blocks_from_opcodes(opcode_counts, all_block_definitions): """ Generates a dictionary of Scratch-like blocks based on a list of opcodes and a reference block definition, and groups all generated block keys by their corresponding opcode. Returns: tuple: (generated_blocks, opcode_to_keys) - generated_blocks: dict of block_key -> block_data - opcode_to_keys: dict of opcode -> list of block_keys """ generated_blocks = {} opcode_counts_map = {} # For counting unique suffix per opcode opcode_to_keys = {} # For grouping block keys by opcode explicit_menu_links = { "motion_goto": [("TO", "motion_goto_menu")], "motion_glideto": [("TO", "motion_glideto_menu")], "motion_pointtowards": [("TOWARDS", "motion_pointtowards_menu")], "sensing_keypressed": [("KEY_OPTION", "sensing_keyoptions")], "sensing_of": [("OBJECT", "sensing_of_object_menu")], "sensing_touchingobject": [("TOUCHINGOBJECTMENU", "sensing_touchingobjectmenu")], "control_create_clone_of": [("CLONE_OPTION", "control_create_clone_of_menu")], "sound_play": [("SOUND_MENU", "sound_sounds_menu")], "sound_playuntildone": [("SOUND_MENU", "sound_sounds_menu")], "looks_switchcostumeto": [("COSTUME", "looks_costume")], "looks_switchbackdropto": [("BACKDROP", "looks_backdrops")], } for item in opcode_counts: opcode = item.get("opcode") count = item.get("count", 1) if opcode == "sensing_istouching": # Handle potential old opcode name opcode = "sensing_touchingobject" if not opcode or opcode not in all_block_definitions: print(f"Warning: Skipping opcode '{opcode}' (missing or not in definitions).") continue for _ in range(count): # Count occurrences per opcode for unique key generation opcode_counts_map[opcode] = opcode_counts_map.get(opcode, 0) + 1 instance_num = opcode_counts_map[opcode] main_key = f"{opcode}_{instance_num}" # Track the generated key opcode_to_keys.setdefault(opcode, []).append(main_key) main_block_data = copy.deepcopy(all_block_definitions[opcode]) main_block_data["parent"] = None main_block_data["next"] = None main_block_data["topLevel"] = True main_block_data["shadow"] = False generated_blocks[main_key] = main_block_data # Handle menus if opcode in explicit_menu_links: for input_name, menu_opcode in explicit_menu_links[opcode]: if menu_opcode not in all_block_definitions: continue opcode_counts_map[menu_opcode] = opcode_counts_map.get(menu_opcode, 0) + 1 menu_instance_num = opcode_counts_map[menu_opcode] menu_key = f"{menu_opcode}_{menu_instance_num}" opcode_to_keys.setdefault(menu_opcode, []).append(menu_key) menu_block_data = copy.deepcopy(all_block_definitions[menu_opcode]) menu_block_data["shadow"] = True menu_block_data["topLevel"] = False menu_block_data["next"] = None menu_block_data["parent"] = main_key if input_name in main_block_data.get("inputs", {}) and \ isinstance(main_block_data["inputs"][input_name], list) and \ len(main_block_data["inputs"][input_name]) > 1 and \ main_block_data["inputs"][input_name][0] == 1: main_block_data["inputs"][input_name][1] = menu_key generated_blocks[menu_key] = menu_block_data return generated_blocks, opcode_to_keys # Consolidated block definitions from all JSON files all_block_definitions = { # motion_block.json "motion_movesteps": { "block_name": "move () steps", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_movesteps", "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.", "inputs": {"STEPS": [1, [4, "10"]]}, "fields": {}, "shadow": False, "topLevel": True }, "motion_turnright": { "block_name": "turn right () degrees", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_turnright", "functionality": "Turns the sprite clockwise by the specified number of degrees.", "inputs": {"DEGREES": [1, [4, "15"]]}, "fields": {}, "shadow": False, "topLevel": True }, "motion_turnleft": { "block_name": "turn left () degrees", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_turnleft", "functionality": "Turns the sprite counter-clockwise by the specified number of degrees.", "inputs": {"DEGREES": [1, [4, "15"]]}, "fields": {}, "shadow": False, "topLevel": True }, "motion_goto": { "block_name": "go to ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_goto", "functionality": "Moves the sprite to a specified location, which can be a random position or at the mouse pointer or another to the sprite.", "inputs": {"TO": [1, "motion_goto_menu"]}, "fields": {}, "shadow": False, "topLevel": True }, "motion_goto_menu": { "block_name": "go to menu", "block_type": "Motion", "block_shape": "Reporter Block", "op_code": "motion_goto_menu", "functionality": "Menu for go to block.", "inputs": {}, "fields": {"TO": ["_random_", None]}, "shadow": True, "topLevel": False }, "motion_gotoxy": { "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": [1, [4, "0"]], "Y": [1, [4, "0"]]}, "fields": {}, "shadow": False, "topLevel": True }, "motion_glideto": { "block_name": "glide () secs to ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_glideto", "functionality": "Glides the sprite smoothly to a specified location (random position, mouse pointer, or another sprite) over a given number of seconds.", "inputs": {"SECS": [1, [4, "1"]], "TO": [1, "motion_glideto_menu"]}, "fields": {}, "shadow": False, "topLevel": True }, "motion_glideto_menu": { "block_name": "glide to menu", "block_type": "Motion", "block_shape": "Reporter Block", "op_code": "motion_glideto_menu", "functionality": "Menu for glide to block.", "inputs": {}, "fields": {"TO": ["_random_", None]}, "shadow": True, "topLevel": False }, "motion_glidesecstoxy": { "block_name": "glide () secs to x: () y: ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_glidesecstoxy", "functionality": "Glides the sprite smoothly to the specified X and Y coordinates over a given number of seconds.", "inputs": {"SECS": [1, [4, "1"]], "X": [1, [4, "0"]], "Y": [1, [4, "0"]]}, "fields": {}, "shadow": False, "topLevel": True }, "motion_pointindirection": { "block_name": "point in direction ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_pointindirection", "functionality": "Sets the sprite's direction to a specified angle in degrees (0 = up, 90 = right, 180 = down, -90 = left).", "inputs": {"DIRECTION": [1, [8, "90"]]}, "fields": {}, "shadow": False, "topLevel": True }, "motion_pointtowards": { "block_name": "point towards ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_pointtowards", "functionality": "Points the sprite towards the mouse pointer or another specified sprite.", "inputs": {"TOWARDS": [1, "motion_pointtowards_menu"]}, "fields": {}, "shadow": False, "topLevel": True }, "motion_pointtowards_menu": { "block_name": "point towards menu", "block_type": "Motion", "block_shape": "Reporter Block", "op_code": "motion_pointtowards_menu", "functionality": "Menu for point towards block.", "inputs": {}, "fields": {"TOWARDS": ["_mouse_", None]}, "shadow": True, "topLevel": False }, "motion_changexby": { "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": [1, [4, "10"]]}, "fields": {}, "shadow": False, "topLevel": True }, "motion_setx": { "block_name": "set x to ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_setx", "functionality": "Sets the sprite's X-coordinate to a specific value, placing it at a precise horizontal position.", "inputs": {"X": [1, [4, "0"]]}, "fields": {}, "shadow": False, "topLevel": True }, "motion_changeyby": { "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": [1, [4, "10"]]}, "fields": {}, "shadow": False, "topLevel": True }, "motion_sety": { "block_name": "set y to ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_sety", "functionality": "Sets the sprite's Y-coordinate to a specific value, placing it at a precise vertical position.", "inputs": {"Y": [1, [4, "0"]]}, "fields": {}, "shadow": False, "topLevel": True }, "motion_ifonedgebounce": { "block_name": "if on edge, bounce", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_ifonedgebounce", "functionality": "Reverses the sprite's direction if it touches the edge of the stage.", "inputs": {}, "fields": {}, "shadow": False, "topLevel": True }, "motion_setrotationstyle": { "block_name": "set rotation style ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_setrotationstyle", "functionality": "Determines how the sprite rotates: 'left-right' (flips horizontally), 'don't rotate' (stays facing one direction), or 'all around' (rotates freely).", "inputs": {}, "fields": {"STYLE": ["left-right", None]}, "shadow": False, "topLevel": True }, "motion_xposition": { "block_name": "(x position)", "block_type": "Motion", "block_shape": "Reporter Block", "op_code": "motion_xposition", "functionality": "Reports the current X-coordinate of the sprite.[NOTE: not used in stage/backdrops]", "inputs": {}, "fields": {}, "shadow": False, "topLevel": True }, "motion_yposition": { "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": True }, "motion_direction": { "block_name": "(direction)", "block_type": "Motion", "block_shape": "Reporter Block", "op_code": "motion_direction", "functionality": "Reports the current direction of the sprite in degrees (0 = up, 90 = right, 180 = down, -90 = left).[NOTE: not used in stage/backdrops]", "inputs": {}, "fields": {}, "shadow": False, "topLevel": True }, "sensing_distanceto": { # Added sensing_distanceto "block_name": "(distance to ())", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_distanceto", "functionality": "Reports the distance from the sprite to the mouse-pointer or another specified sprite.", "inputs": {}, "fields": {"TARGET": ["_mouse_", None]}, "shadow": False, "topLevel": True }, # control_block.json "control_wait": { "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": [1, [5, "1"]]}, "fields": {}, "shadow": False, "topLevel": True }, "control_repeat": { "block_name": "repeat ()", "block_type": "Control", "block_shape": "C-Block", "op_code": "control_repeat", "functionality": "Repeats the blocks inside it a specified number of times.", "inputs": {"TIMES": [1, [6, "10"]], "SUBSTACK": [2, None]}, "fields": {}, "shadow": False, "topLevel": True }, "control_forever": { "block_name": "forever", "block_type": "Control", "block_shape": "C-Block", "op_code": "control_forever", "functionality": "Continuously runs the blocks inside it.", "inputs": {"SUBSTACK": [2, None]}, "fields": {}, "shadow": False, "topLevel": True }, "control_if": { "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": [2, None], "SUBSTACK": [2, None]}, "fields": {}, "shadow": False, "topLevel": True }, "control_if_else": { "block_name": "if <> then else", "block_type": "Control", "block_shape": "C-Block", "op_code": "control_if_else", "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]", "inputs": {"CONDITION": [2, None], "SUBSTACK": [2, None], "SUBSTACK2": [2, None]}, "fields": {}, "shadow": False, "topLevel": True }, "control_wait_until": { "block_name": "wait until <>", "block_type": "Control", "block_shape": "Stack Block", "op_code": "control_wait_until", "functionality": "Pauses the script until the specified boolean condition becomes true. [NOTE: it takes boolean blocks as input]", "inputs": {"CONDITION": [2, None]}, "fields": {}, "shadow": False, "topLevel": True }, "control_repeat_until": { "block_name": "repeat until <>", "block_type": "Control", "block_shape": "C-Block", "op_code": "control_repeat_until", "functionality": "Repeats the blocks inside it until the specified boolean condition becomes true. [NOTE: it takes boolean blocks as input]", "inputs": {"CONDITION": [2, None], "SUBSTACK": [2, None]}, "fields": {}, "shadow": False, "topLevel": True }, "control_stop": { "block_name": "stop [v]", "block_type": "Control", "block_shape": "Cap Block", "op_code": "control_stop", "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.", "inputs": {}, "fields": {"STOP_OPTION": ["all", None]}, "shadow": False, "topLevel": True, "mutation": {"tagName": "mutation", "children": [], "hasnext": "false"} }, "control_start_as_clone": { "block_name": "When I Start as a Clone", "block_type": "Control", "block_shape": "Hat Block", "op_code": "control_start_as_clone", "functionality": "This Hat block initiates the script when a clone of the sprite is created. It defines the behavior of individual clones.", "inputs": {}, "fields": {}, "shadow": False, "topLevel": True }, "control_create_clone_of": { "block_name": "create clone of ()", "block_type": "Control", "block_shape": "Stack Block", "op_code": "control_create_clone_of", "functionality": "Generates a copy, or clone, of a specified sprite (or 'myself' for the current sprite).", "inputs": {"CLONE_OPTION": [1, "control_create_clone_of_menu"]}, "fields": {}, "shadow": False, "topLevel": True }, "control_create_clone_of_menu": { "block_name": "create clone of menu", "block_type": "Control", "block_shape": "Reporter Block", "op_code": "control_create_clone_of_menu", "functionality": "Menu for create clone of block.", "inputs": {}, "fields": {"CLONE_OPTION": ["_myself_", None]}, "shadow": True, "topLevel": False }, "control_delete_this_clone": { "block_name": "delete this clone", "block_type": "Control", "block_shape": "Cap Block", "op_code": "control_delete_this_clone", "functionality": "Removes the clone that is executing it from the stage.", "inputs":None, "fields": {}, "shadow": False, "topLevel": True }, # data_block.json "data_setvariableto": { "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": [1, [10, "0"]]}, "fields": {"VARIABLE": ["my variable", "`jEk@4|i[#Fk?(8x)AV.-my variable"]}, "shadow": False, "topLevel": True }, "data_changevariableby": { "block_name": "change [my variable v] by ()", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_changevariableby", "functionality": "Increases or decreases a variable's numerical value by a specified amount.", "inputs": {"VALUE": [1, [4, "1"]]}, "fields": {"VARIABLE": ["my variable", "`jEk@4|i[#Fk?(8x)AV.-my variable"]}, "shadow": False, "topLevel": True }, "data_showvariable": { "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": ["my variable", "`jEk@4|i[#Fk?(8x)AV.-my variable"]}, "shadow": False, "topLevel": True }, "data_hidevariable": { "block_name": "hide variable [my variable v]", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_hidevariable", "functionality": "Hides a variable's monitor from the stage.", "inputs": {}, "fields": {"VARIABLE": ["my variable", "`jEk@4|i[#Fk?(8x)AV.-my variable"]}, "shadow": False, "topLevel": True }, "data_addtolist": { "block_name": "add () to [my list v]", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_addtolist", "functionality": "Appends an item to the end of a list.", "inputs": {"ITEM": [1, [10, "thing"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True }, "data_deleteoflist": { "block_name": "delete () of [my list v]", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_deleteoflist", "functionality": "Removes an item from a list by its index or by selecting 'all' items.", "inputs": {"INDEX": [1, [7, "1"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True }, "data_deletealloflist": { "block_name": "delete all of [my list v]", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_deletealloflist", "functionality": "Removes all items from a list.", "inputs": {}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True }, "data_insertatlist": { "block_name": "insert () at () of [my list v]", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_insertatlist", "functionality": "Inserts an item at a specific position within a list.", "inputs": {"ITEM": [1, [10, "thing"]], "INDEX": [1, [7, "1"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True }, "data_replaceitemoflist": { "block_name": "replace item () of [my list v] with ()", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_replaceitemoflist", "functionality": "Replaces an item at a specific position in a list with a new value.", "inputs": {"INDEX": [1, [7, "1"]], "ITEM": [1, [10, "thing"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True }, "data_itemoflist": { "block_name": "(item (2) of [myList v])", "block_type": "Data", "block_shape": "Reporter Block", "op_code": "data_itemoflist", "functionality": "Reports the item located at a specific position in a list.", "inputs": {"INDEX": [1, [7, "1"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True }, "data_itemnumoflist": { "block_name": "(item # of [Dog] in [myList v])", "block_type": "Data", "block_shape": "Reporter Block", "op_code": "data_itemnumoflist", "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.", "inputs": {"ITEM": [1, [10, "thing"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True }, "data_lengthoflist": { "block_name": "(length of [myList v])", "block_type": "Data", "block_shape": "Reporter Block", "op_code": "data_lengthoflist", "functionality": "Provides the total number of items contained in a list.", "inputs": {}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True }, "data_listcontainsitem": { "block_name": "<[my list v] contains ()?>", "block_type": "Data", "block_shape": "Boolean Block", "op_code": "data_listcontainsitem", "functionality": "Checks if a list includes a specific item.", "inputs": {"ITEM": [1, [10, "thing"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True }, "data_showlist": { "block_name": "show list [my list v]", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_showlist", "functionality": "Makes a list's monitor visible on the stage.", "inputs": {}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True }, "data_hidelist": { "block_name": "hide list [my list v]", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_hidelist", "functionality": "Hides a list's monitor from the stage.", "inputs": {}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True }, "data_variable": { # This is a reporter block for a variable's value "block_name": "[variable v]", "block_type": "Data", "block_shape": "Reporter Block", "op_code": "data_variable", "functionality": "Provides the current value stored in a variable.", "inputs": {}, "fields": {"VARIABLE": ["my variable", None]}, "shadow": True, "topLevel": False }, "data_list": { # Added this block definition "block_name": "[list v]", "block_type": "Data", "block_shape": "Reporter Block", "op_code": "data_list", "functionality": "Reports the entire content of a specified list. When clicked in the editor, it displays the list as a monitor.", "inputs": {}, "fields": {"LIST": ["my list", None]}, "shadow": True, "topLevel": False }, # event_block.json "event_whenflagclicked": { "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 }, "event_whenkeypressed": { "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 }, "event_whenthisspriteclicked": { "block_name": "when this sprite clicked", "block_type": "Events", "op_code": "event_whenthisspriteclicked", "block_shape": "Hat Block", "functionality": "This Hat block starts the script when the sprite itself is clicked.", "inputs": {}, "fields": {}, "shadow": False, "topLevel": True }, "event_whenbackdropswitchesto": { "block_name": "when backdrop switches to ()", "block_type": "Events", "op_code": "event_whenbackdropswitchesto", "block_shape": "Hat Block", "functionality": "This Hat block triggers the script when the stage backdrop changes to a specified backdrop.", "inputs": {}, "fields": {"BACKDROP": ["backdrop1", None]}, "shadow": False, "topLevel": True }, "event_whengreaterthan": { "block_name": "when () > ()", "block_type": "Events", "op_code": "event_whengreaterthan", "block_shape": "Hat Block", "functionality": "This Hat block starts the script when a certain value (e.g., loudness from a microphone, or the timer) exceeds a defined threshold.", "inputs": {"VALUE": [1, [4, "10"]]}, "fields": {"WHENGREATERTHANMENU": ["LOUDNESS", None]}, "shadow": False, "topLevel": True }, "event_whenbroadcastreceived": { "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": ["message1", "5O!nei;S$!c!=hCT}0:a"]}, "shadow": False, "topLevel": True }, "event_broadcast": { "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": [1, [11, "message1", "5O!nei;S$!c!=hCT}0:a"]]}, "fields": {}, "shadow": False, "topLevel": True }, "event_broadcastandwait": { "block_name": "broadcast () and wait", "block_type": "Events", "block_shape": "Stack Block", "op_code": "event_broadcastandwait", "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.", "inputs": {"BROADCAST_INPUT": [1, [11, "message1", "5O!nei;S$!c!=hCT}0:a"]]}, "fields": {}, "shadow": False, "topLevel": True }, # looks_block.json "looks_sayforsecs": { "block_name": "say () for () seconds", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_sayforsecs", "functionality": "Displays a speech bubble containing specified text for a set duration.", "inputs": {"MESSAGE": [1, [10, "Hello!"]], "SECS": [1, [4, "2"]]}, "fields": {}, "shadow": False, "topLevel": True }, "looks_say": { "block_name": "say ()", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_say", "functionality": "Displays a speech bubble with the specified text indefinitely until another 'say' or 'think' block is activated.", "inputs": {"MESSAGE": [1, [10, "Hello!"]]}, "fields": {}, "shadow": False, "topLevel": True }, "looks_thinkforsecs": { "block_name": "think () for () seconds", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_thinkforsecs", "functionality": "Displays a thought bubble containing specified text for a set duration.", "inputs": {"MESSAGE": [1, [10, "Hmm..."]], "SECS": [1, [4, "2"]]}, "fields": {}, "shadow": False, "topLevel": True }, "looks_think": { "block_name": "think ()", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_think", "functionality": "Displays a thought bubble with the specified text indefinitely until another 'say' or 'think' block is activated.", "inputs": {"MESSAGE": [1, [10, "Hmm..."]]}, "fields": {}, "shadow": False, "topLevel": True }, "looks_switchcostumeto": { "block_name": "switch costume to ()", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_switchcostumeto", "functionality": "Alters the sprite's appearance to a designated costume.", "inputs": {"COSTUME": [1, "looks_costume"]}, "fields": {}, "shadow": False, "topLevel": True }, "looks_costume": { "block_name": "costume menu", "block_type": "Looks", "block_shape": "Reporter Block", "op_code": "looks_costume", "functionality": "Menu for switch costume to block.", "inputs": {}, "fields": {"COSTUME": ["costume1", None]}, "shadow": True, "topLevel": False }, "looks_nextcostume": { "block_name": "next costume", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_nextcostume", "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.", "inputs": {}, "fields": {}, "shadow": False, "topLevel": True }, "looks_switchbackdropto": { "block_name": "switch backdrop to ()", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_switchbackdropto", "functionality": "Changes the stage's backdrop to a specified backdrop.", "inputs": {"BACKDROP": [1, "looks_backdrops"]}, "fields": {}, "shadow": False, "topLevel": True }, "looks_backdrops": { "block_name": "backdrop menu", "block_type": "Looks", "block_shape": "Reporter Block", "op_code": "looks_backdrops", "functionality": "Menu for switch backdrop to block.", "inputs": {}, "fields": {"BACKDROP": ["backdrop1", None]}, "shadow": True, "topLevel": False }, "looks_switchbackdroptowait": { "block_name": "switch backdrop to () and wait", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_switchbackdroptowait", "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.", "inputs": {"BACKDROP": [1, "looks_backdrops"]}, "fields": {}, "shadow": False, "topLevel": True }, "looks_nextbackdrop": { "block_name": "next backdrop", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_nextbackdrop", "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.", "inputs": {}, "fields": {}, "shadow": False, "topLevel": True }, "looks_changesizeby": { "block_name": "change size by ()", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_changesizeby", "functionality": "Changes the sprite's size by a specified percentage. Positive values make it larger, negative values make it smaller.", "inputs": {"CHANGE": [1, [4, "10"]]}, "fields": {}, "shadow": False, "topLevel": True }, "looks_setsizeto": { "block_name": "set size to () %", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_setsizeto", "functionality": "Sets the sprite's size to a specific percentage of its original size.", "inputs": {"SIZE": [1, [4, "100"]]}, "fields": {}, "shadow": False, "topLevel": True }, "looks_changeeffectby": { "block_name": "change () effect by ()", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_changeeffectby", "functionality": "Changes a visual effect on the sprite by a specified amount (e.g., color, fisheye, whirl, pixelate, mosaic, brightness, ghost).", "inputs": {"CHANGE": [1, [4, "25"]]}, "fields": {"EFFECT": ["COLOR", None]}, "shadow": False, "topLevel": True }, "looks_seteffectto": { "block_name": "set () effect to ()", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_seteffectto", "functionality": "Sets a visual effect on the sprite to a specific value.", "inputs": {"VALUE": [1, [4, "0"]]}, "fields": {"EFFECT": ["COLOR", None]}, "shadow": False, "topLevel": True }, "looks_cleargraphiceffects": { "block_name": "clear graphic effects", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_cleargraphiceffects", "functionality": "Removes all visual effects applied to the sprite.", "inputs": {}, "fields": {}, "shadow": False, "topLevel": True }, "looks_show": { "block_name": "show", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_show", "functionality": "Makes the sprite visible on the stage.", "inputs": {}, "fields": {}, "shadow": False, "topLevel": True }, "looks_hide": { "block_name": "hide", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_hide", "functionality": "Makes the sprite invisible on the stage.", "inputs": {}, "fields": {}, "shadow": False, "topLevel": True }, "looks_gotofrontback": { "block_name": "go to () layer", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_gotofrontback", "functionality": "Moves the sprite to the front-most or back-most layer of other sprites on the stage.", "inputs": {}, "fields": {"FRONT_BACK": ["front", None]}, "shadow": False, "topLevel": True }, "looks_goforwardbackwardlayers": { "block_name": "go () layers", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_goforwardbackwardlayers", "functionality": "Moves the sprite forward or backward a specified number of layers in relation to other sprites.", "inputs": {"NUM": [1, [7, "1"]]}, "fields": {"FORWARD_BACKWARD": ["forward", None]}, "shadow": False, "topLevel": True }, "looks_costumenumbername": { "block_name": "(costume ())", "block_type": "Looks", "block_shape": "Reporter Block", "op_code": "looks_costumenumbername", "functionality": "Reports the current costume's number or name.", "inputs": {}, "fields": {"NUMBER_NAME": ["number", None]}, "shadow": False, "topLevel": True }, "looks_backdropnumbername": { "block_name": "(backdrop ())", "block_type": "Looks", "block_shape": "Reporter Block", "op_code": "looks_backdropnumbername", "functionality": "Reports the current backdrop's number or name.", "inputs": {}, "fields": {"NUMBER_NAME": ["number", None]}, "shadow": False, "topLevel": True }, "looks_size": { "block_name": "(size)", "block_type": "Looks", "block_shape": "Reporter Block", "op_code": "looks_size", "functionality": "Reports the current size of the sprite as a percentage.", "inputs": {}, "fields": {}, "shadow": False, "topLevel": True }, # operator_block.json "operator_add": { "block_name": "(() + ())", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_add", "functionality": "Adds two numerical values.", "inputs": {"NUM1": [1, [4, ""]], "NUM2": [1, [4, ""]]}, "fields": {}, "shadow": False, "topLevel": True }, "operator_subtract": { "block_name": "(() - ())", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_subtract", "functionality": "Subtracts the second numerical value from the first.", "inputs": {"NUM1": [1, [4, ""]], "NUM2": [1, [4, ""]]}, "fields": {}, "shadow": False, "topLevel": True }, "operator_multiply": { "block_name": "(() * ())", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_multiply", "functionality": "Multiplies two numerical values.", "inputs": {"NUM1": [1, [4, ""]], "NUM2": [1, [4, ""]]}, "fields": {}, "shadow": False, "topLevel": True }, "operator_divide": { "block_name": "(() / ())", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_divide", "functionality": "Divides the first numerical value by the second.", "inputs": {"NUM1": [1, [4, ""]], "NUM2": [1, [4, ""]]}, "fields": {}, "shadow": False, "topLevel": True }, "operator_random": { "block_name": "(pick random () to ())", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_random", "functionality": "Generates a random integer within a specified inclusive range.", "inputs": {"FROM": [1, [4, "1"]], "TO": [1, [4, "10"]]}, "fields": {}, "shadow": False, "topLevel": True }, "operator_gt": { "block_name": "<() > ()>", "block_type": "operator", "block_shape": "Boolean Block", "op_code": "operator_gt", "functionality": "Checks if the first value is greater than the second.", "inputs": {"OPERAND1": [1, [10, ""]], "OPERAND2": [1, [10, "50"]]}, "fields": {}, "shadow": False, "topLevel": True }, "operator_lt": { "block_name": "<() < ()>", "block_type": "operator", "block_shape": "Boolean Block", "op_code": "operator_lt", "functionality": "Checks if the first value is less than the second.", "inputs": {"OPERAND1": [1, [10, ""]], "OPERAND2": [1, [10, "50"]]}, "fields": {}, "shadow": False, "topLevel": True }, "operator_equals": { "block_name": "<() = ()>", "block_type": "operator", "block_shape": "Boolean Block", "op_code": "operator_equals", "functionality": "Checks if two values are equal.", "inputs": {"OPERAND1": [1, [10, ""]], "OPERAND2": [1, [10, "50"]]}, "fields": {}, "shadow": False, "topLevel": True }, "operator_and": { "block_name": "<<> and <>>", "block_type": "operator", "block_shape": "Boolean Block", "op_code": "operator_and", "functionality": "Returns 'true' if both provided Boolean conditions are 'true'.", "inputs": {"OPERAND1": [2, None], "OPERAND2": [2, None]}, "fields": {}, "shadow": False, "topLevel": True }, "operator_or": { "block_name": "<<> or <>>", "block_type": "operator", "block_shape": "Boolean Block", "op_code": "operator_or", "functionality": "Returns 'true' if at least one of the provided Boolean conditions is 'true'.", "inputs": {"OPERAND1": [2, None], "OPERAND2": [2, None]}, "fields": {}, "shadow": False, "topLevel": True }, "operator_not": { "block_name": ">", "block_type": "operator", "block_shape": "Boolean Block", "op_code": "operator_not", "functionality": "Returns 'true' if the provided Boolean condition is 'false', and 'false' if it is 'true'.", "inputs": {"OPERAND": [2, None]}, "fields": {}, "shadow": False, "topLevel": True }, "operator_join": { "block_name": "(join ()())", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_join", "functionality": "Concatenates two strings or values into a single string.", "inputs": {"STRING1": [1, [10, "apple "]], "STRING2": [1, [10, "banana"]]}, "fields": {}, "shadow": False, "topLevel": True }, "operator_letterof": { "block_name": "letter () of ()", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_letterof", "functionality": "Reports the character at a specific numerical position within a string.", "inputs": {"LETTER": [1, [6, "1"]], "STRING": [1, [10, "apple"]]}, "fields": {}, "shadow": False, "topLevel": True }, "operator_length": { "block_name": "(length of ())", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_length", "functionality": "Reports the total number of characters in a given string.", "inputs": {"STRING": [1, [10, "apple"]]}, "fields": {}, "shadow": False, "topLevel": True }, "operator_contains": { "block_name": "<() contains ()?>", "block_type": "operator", "block_shape": "Boolean Block", "op_code": "operator_contains", "functionality": "Checks if one string contains another string.", "inputs": {"STRING1": [1, [10, "apple"]], "STRING2": [1, [10, "a"]]}, "fields": {}, "shadow": False, "topLevel": True }, "operator_mod": { "block_name": "(() mod ())", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_mod", "functionality": "Reports the remainder when the first number is divided by the second.", "inputs": {"NUM1": [1, [4, ""]], "NUM2": [1, [4, ""]]}, "fields": {}, "shadow": False, "topLevel": True }, "operator_round": { "block_name": "(round ())", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_round", "functionality": "Rounds a numerical value to the nearest integer.", "inputs": {"NUM": [1, [4, ""]]}, "fields": {}, "shadow": False, "topLevel": True }, "operator_mathop": { "block_name": "(() of ())", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_mathop", "functionality": "Performs various mathematical functions (e.g., absolute value, square root, trigonometric functions).", "inputs": {"NUM": [1, [4, ""]]}, "fields": {"OPERATOR": ["abs", None]}, "shadow": False, "topLevel": True }, # sensing_block.json "sensing_touchingobject": { "block_name": "", "block_type": "Sensing", "op_code": "sensing_touchingobject", "block_shape": "Boolean Block", "functionality": "Checks if its sprite is touching the mouse-pointer, edge, or another specified sprite.", "inputs": {"TOUCHINGOBJECTMENU": [1, "sensing_touchingobjectmenu"]}, "fields": {}, "shadow": False, "topLevel": True }, "sensing_touchingobjectmenu": { "block_name": "touching object menu", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_touchingobjectmenu", "functionality": "Menu for touching object block.", "inputs": {}, "fields": {"TOUCHINGOBJECTMENU": ["_mouse_", None]}, "shadow": True, "topLevel": False }, "sensing_touchingcolor": { "block_name": "", "block_type": "Sensing", "op_code": "sensing_touchingcolor", "block_shape": "Boolean Block", "functionality": "Checks whether its sprite is touching a specified color.", "inputs": {"COLOR": [1, [9, "#55b888"]]}, "fields": {}, "shadow": False, "topLevel": True }, "sensing_coloristouchingcolor": { "block_name": "", "block_type": "Sensing", "op_code": "sensing_coloristouchingcolor", "block_shape": "Boolean Block", "functionality": "Checks whether a specific color on its sprite is touching another specified color on the stage or another sprite.", "inputs": {"COLOR1": [1, [9, "#d019f2"]], "COLOR2": [1, [9, "#2b0de3"]]}, "fields": {}, "shadow": False, "topLevel": True }, "sensing_askandwait": { "block_name": "Ask () and Wait", "block_type": "Sensing", "block_shape": "Stack Block", "op_code": "sensing_askandwait", "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.", "inputs": {"QUESTION": [1, [10, "What's your name?"]]}, "fields": {}, "shadow": False, "topLevel": True }, "sensing_answer": { "block_name": "(answer)", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_answer", "functionality": "Holds the most recent text inputted using the 'Ask () and Wait' block.", "inputs": {}, "fields": {}, "shadow": False, "topLevel": True }, "sensing_keypressed": { "block_name": "", "block_type": "Sensing", "op_code": "sensing_keypressed", "block_shape": "Boolean Block", "functionality": "Checks if a specified keyboard key is currently being pressed.", "inputs": {"KEY_OPTION": [1, "sensing_keyoptions"]}, "fields": {}, "shadow": False, "topLevel": True }, "sensing_keyoptions": { "block_name": "key options menu", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_keyoptions", "functionality": "Menu for key pressed block.", "inputs": {}, "fields": {"KEY_OPTION": ["space", None]}, "shadow": True, "topLevel": False }, "sensing_mousedown": { "block_name": "", "block_type": "Sensing", "op_code": "sensing_mousedown", "block_shape": "Boolean Block", "functionality": "Checks if the computer mouse's primary button is being clicked while the cursor is over the stage.", "inputs": {}, "fields": {}, "shadow": False, "topLevel": True }, "sensing_mousex": { "block_name": "(mouse x)", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_mousex", "functionality": "Reports the mouse-pointer’s current X position on the stage.", "inputs": {}, "fields": {}, "shadow": False, "topLevel": True }, "sensing_mousey": { "block_name": "(mouse y)", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_mousey", "functionality": "Reports the mouse-pointer’s current Y position on the stage.", "inputs": {}, "fields": {}, "shadow": False, "topLevel": True }, "sensing_setdragmode": { "block_name": "set drag mode [draggable v]", "block_type": "Sensing", "block_shape": "Stack Block", "op_code": "sensing_setdragmode", "functionality": "Sets whether the sprite can be dragged by the mouse on the stage.", "inputs": {}, "fields": {"DRAG_MODE": ["draggable", None]}, "shadow": False, "topLevel": True }, "sensing_loudness": { "block_name": "(loudness)", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_loudness", "functionality": "Reports the loudness of noise received by a microphone on a scale of 0 to 100.", "inputs": {}, "fields": {}, "shadow": False, "topLevel": True }, "sensing_timer": { "block_name": "(timer)", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_timer", "functionality": "Reports the elapsed time since Scratch was launched or the timer was reset, increasing by 1 every second.", "inputs": {}, "fields": {}, "shadow": False, "topLevel": True }, "sensing_resettimer": { "block_name": "Reset Timer", "block_type": "Sensing", "block_shape": "Stack Block", "op_code": "sensing_resettimer", "functionality": "Sets the timer’s value back to 0.0.", "inputs": {}, "fields": {}, "shadow": False, "topLevel": True }, "sensing_of": { "block_name": "(() of ())", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_of", "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.", "inputs": {"OBJECT": [1, "sensing_of_object_menu"]}, "fields": {"PROPERTY": ["backdrop #", None]}, "shadow": False, "topLevel": True }, "sensing_of_object_menu": { "block_name": "of object menu", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_of_object_menu", "functionality": "Menu for of block.", "inputs": {}, "fields": {"OBJECT": ["_stage_", None]}, "shadow": True, "topLevel": False }, "sensing_current": { "block_name": "(current ())", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_current", "functionality": "Reports the current local year, month, date, day of the week, hour, minutes, or seconds.", "inputs": {}, "fields": {"CURRENTMENU": ["YEAR", None]}, "shadow": False, "topLevel": True }, "sensing_dayssince2000": { "block_name": "(days since 2000)", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_dayssince2000", "functionality": "Reports the number of days (and fractions of a day) since 00:00:00 UTC on January 1, 2000.", "inputs": {}, "fields": {}, "shadow": False, "topLevel": True }, "sensing_username": { "block_name": "(username)", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_username", "functionality": "Reports the username of the user currently logged into Scratch. If no user is logged in, it reports nothing.", "inputs": {}, "fields": {}, "shadow": False, "topLevel": True }, # sound_block.json "sound_playuntildone": { "block_name": "play sound () until done", "block_type": "Sound", "block_shape": "Stack Block", "op_code": "sound_playuntildone", "functionality": "Plays a specified sound and pauses the script's execution until the sound has completed.", "inputs": {"SOUND_MENU": [1, "sound_sounds_menu"]}, "fields": {}, "shadow": False, "topLevel": True }, "sound_sounds_menu": { "block_name": "sound menu", "block_type": "Sound", "block_shape": "Reporter Block", "op_code": "sound_sounds_menu", "functionality": "Menu for sound blocks.", "inputs": {}, "fields": {"SOUND_MENU": ["Meow", None]}, "shadow": True, "topLevel": False }, "sound_play": { "block_name": "start sound ()", "block_type": "Sound", "block_shape": "Stack Block", "op_code": "sound_play", "functionality": "Initiates playback of a specified sound without pausing the script, allowing other actions to proceed concurrently.", "inputs": {"SOUND_MENU": [1, "sound_sounds_menu"]}, "fields": {}, "shadow": False, "topLevel": True }, "sound_stopallsounds": { "block_name": "stop all sounds", "block_type": "Sound", "block_shape": "Stack Block", "op_code": "sound_stopallsounds", "functionality": "Stops all currently playing sounds.", "inputs": {}, "fields": {}, "shadow": False, "topLevel": True }, "sound_changeeffectby": { "block_name": "change () effect by ()", "block_type": "Sound", "block_shape": "Stack Block", "op_code": "sound_changeeffectby", "functionality": "Changes the project's sound effect by a specified amount.", "inputs": {"VALUE": [1, [4, "10"]]}, "fields": {"EFFECT": ["PITCH", None]}, "shadow": False, "topLevel": True }, "sound_seteffectto": { "block_name": "set () effect to ()", "block_type": "Sound", "block_shape": "Stack Block", "op_code": "sound_seteffectto", "functionality": "Sets the sound effect to a specific value.", "inputs": {"VALUE": [1, [4, "100"]]}, "fields": {}, "shadow": False, "topLevel": True }, "sound_cleareffects": { "block_name": "clear sound effects", "block_type": "Sound", "block_shape": "Stack Block", "op_code": "sound_cleareffects", "functionality": "Removes all sound effects applied to the sprite.", "inputs": {}, "fields": {}, "shadow": False, "topLevel": True }, "sound_changevolumeby": { "block_name": "change volume by ()", "block_type": "Sound", "block_shape": "Stack Block", "op_code": "sound_changevolumeby", "functionality": "Changes the project's sound volume by a specified amount.", "inputs": {"VOLUME": [1, [4, "-10"]]}, "fields": {}, "shadow": False, "topLevel": True }, "sound_setvolumeto": { "block_name": "set volume to () %", "block_type": "Sound", "block_shape": "Stack Block", "op_code": "sound_setvolumeto", "functionality": "Sets the sound volume to a specific percentage (0-100).", "inputs": {"VOLUME": [1, [4, "100"]]}, "fields": {}, "shadow": False, "topLevel": True }, "sound_volume": { "block_name": "(volume)", "block_type": "Sound", "block_shape": "Reporter Block", "op_code": "sound_volume", "functionality": "Reports the current volume level of the sprite.", "inputs": {}, "fields": {}, "shadow": False, "topLevel": True }, "procedures_definition": { "block_name": "define [my custom block]", "block_type": "My Blocks", "op_code": "procedures_definition", "block_shape": "Hat Block", "functionality": "This Hat block serves as the definition header for a custom block's script.", "inputs": [ { "name": "PROCCONTAINER", "type": "block_prototype" } ], "fields": {}, "shadow": False, "topLevel": True }, "procedures_call": { "block_name": "[my custom block]", "block_type": "My Blocks", "block_shape": "Stack Block", "op_code": "procedures_call", "functionality": "Executes the script defined by a corresponding 'define' Hat block.", "inputs": [], # Inputs are dynamic based on definition "fields": {}, "shadow": False, "topLevel": True } } def unparen(s): s = s.strip() # keep peeling off *all* matching outer parens while True: m = re.fullmatch(r"\((.*)\)", s) if not m: break s = m.group(1).strip() return s def _register_block(opcode, parent_key, is_shadow, pick_key_func, all_generated_blocks, inputs=None, fields=None): """ Helper to create and register a block in all_generated_blocks. Returns the key of the newly created block. """ key = pick_key_func(opcode) # Use the passed pick_key_func block_data = copy.deepcopy(all_block_definitions[opcode]) block_data["id"] = key block_data["parent"] = parent_key block_data["next"] = None # Nested blocks as inputs don't have a 'next' block_data["topLevel"] = False block_data["shadow"] = is_shadow # Initialize inputs and fields if not provided to prevent KeyError later if "inputs" not in block_data: block_data["inputs"] = {} if "fields" not in block_data: block_data["fields"] = {} if inputs: block_data["inputs"].update(inputs) # Use update to merge, not overwrite if fields: block_data["fields"].update(fields) # Use update to merge, not overwrite all_generated_blocks[key] = block_data return key def _auto_balance(text): # if there are more "(" than ")", append the missing ")" diff = text.count("(") - text.count(")") if diff > 0: text = text + ")"*diff # same for square brackets diff = text.count("[") - text.count("]") if diff > 0: text = text + "]"*diff return text # Nested helper for parsing reporters or values def parse_reporter_or_value(text, parent_key, pick_key_func, all_generated_blocks): text = _auto_balance(text.strip()) text = unparen(text.strip()) # Check for numeric literal (including parenthesized numbers like "(0)" or "(10)") m_num = re.fullmatch(r"\(?\s*(-?\d+(\.\d+)?)\s*\)?", text) if m_num: val_str = m_num.group(1) return {"kind": "value", "value": float(val_str) if '.' in val_str else int(val_str)} # # Check for string literal (e.g., "[Hello!]") # if text.startswith('[') and text.endswith(']'): # return {"kind": "value", "value": text[1:-1]} # Variable reporter: [score v], [health v], etc. m_var = re.fullmatch(r"\[([^\]]+)\s*v\]", text) if m_var: var_name = m_var.group(1).strip() block_id = _register_block("data_variable", parent_key, True, pick_key_func, all_generated_blocks, fields={"VARIABLE": [var_name, None]}) return {"kind": "block", "block": block_id} # Now catch other bracketed values as literal strings if text.startswith('[') and text.endswith(']'): return {"kind": "value", "value": text[1:-1]} # --- Reporter Blocks --- # (x position), (y position), (direction), (mouse x), (mouse y), (loudness), (timer), (days since 2000), (username), (answer), (size), (volume) simple_reporters = { "x position": "motion_xposition", "y position": "motion_yposition", "direction": "motion_direction", "mouse x": "sensing_mousex", "mouse y": "sensing_mousey", "loudness": "sensing_loudness", "timer": "sensing_timer", "days since 2000": "sensing_dayssince2000", "username": "sensing_username", "answer": "sensing_answer", "size": "looks_size", "volume": "sound_volume" } # Check for simple reporters, potentially with outer parentheses m_simple_reporter = re.fullmatch(r"\((.+?)\)", text) if m_simple_reporter: inner_text = m_simple_reporter.group(1).strip() if inner_text in simple_reporters: block_id = _register_block(simple_reporters[inner_text], parent_key, False, pick_key_func, all_generated_blocks) return {"kind": "block", "block": block_id} # Also check for simple reporters without parentheses (e.g., if passed directly) if text in simple_reporters: block_id = _register_block(simple_reporters[text], parent_key, False, pick_key_func, all_generated_blocks) return {"kind": "block", "block": block_id} # Variable reporter: [score v] or (score) or just "score" m_var = re.fullmatch(r"\[([^\]]+)\s*v\]", text) if m_var: var_name = m_var.group(1).strip() block_id = _register_block("data_variable", parent_key, True, pick_key_func, all_generated_blocks, fields={"VARIABLE": [var_name, None]}) return {"kind": "block", "block": block_id} m_paren_var = re.fullmatch(r"\(([^)]+)\)", text) if m_paren_var: potential_var_name = m_paren_var.group(1).strip() # Ensure it's not a simple reporter already handled, or a number if potential_var_name not in simple_reporters and not re.fullmatch(r"-?\d+(\.\d+)?", potential_var_name): block_id = _register_block("data_variable", parent_key, True, pick_key_func, all_generated_blocks, fields={"VARIABLE": [potential_var_name, None]}) return {"kind": "block", "block": block_id} # Handle plain variable names like "score", "number 1", "total score" if re.fullmatch(r"[a-zA-Z_][a-zA-Z0-9_ ]*", text): # Allow spaces for "number 1" etc. # Exclude known simple reporters that don't have 'v' or parentheses if text not in simple_reporters: block_id = _register_block("data_variable", parent_key, True, pick_key_func, all_generated_blocks, fields={"VARIABLE": [text, None]}) return {"kind": "block", "block": block_id} # List reporter: [my list v] m_list_reporter = re.fullmatch(r"\[([^\]]+)\s*v\]", text) if m_list_reporter: list_name = m_list_reporter.group(1).strip() block_id = _register_block("data_list", parent_key, True, pick_key_func, all_generated_blocks, fields={"LIST": [list_name, None]}) return {"kind": "block", "block": block_id} # (pick random () to ()) (operator_random) m = re.search(r"pick random \((.+?)\) to \((.+?)\)", text) if m: min_val_obj = parse_reporter_or_value(m.group(1).strip(), None, pick_key_func, all_generated_blocks) # Parent will be set later max_val_obj = parse_reporter_or_value(m.group(2).strip(), None, pick_key_func, all_generated_blocks) # Parent will be set later inputs = {"FROM": min_val_obj, "TO": max_val_obj} block_id = _register_block("operator_random", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs) # Set parents for nested inputs if min_val_obj.get("kind") == "block": all_generated_blocks[min_val_obj["block"]]["parent"] = block_id if max_val_obj.get("kind") == "block": all_generated_blocks[max_val_obj["block"]]["parent"] = block_id return {"kind": "block", "block": block_id} # (join ()()) (operator_join) - handle both [] and () for inputs # m = re.search(r"join \((.+?)\) \((.+?)\)", text) # Try (val) (val) # if not m: # m = re.search(r"join \[(.+?)\] \[(.+?)\]", text) # Try [val] [val] # if m: # str1_obj = parse_reporter_or_value(m.group(1).strip(), None, pick_key_func, all_generated_blocks) # str2_obj = parse_reporter_or_value(m.group(2).strip(), None, pick_key_func, all_generated_blocks) # inputs = {"STRING1": str1_obj, "STRING2": str2_obj} # block_id = _register_block("operator_join", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs) # if str1_obj.get("kind") == "block": all_generated_blocks[str1_obj["block"]]["parent"] = block_id # if str2_obj.get("kind") == "block": all_generated_blocks[str2_obj["block"]]["parent"] = block_id # return {"kind": "block", "block": block_id} #m = re.search(r"join\s+(\[.+?\]|\(.+?\))\s+(\[.+?\]|\(.+?\))", text) # Try (val) (val) m = re.search(r"join\s+(\[.+?\]|\(.+?\))\s*(\[.+?\]|\(.+?\))", text) if m: part1_txt = m.group(1).strip() part2_txt = m.group(2).strip() str1_obj = parse_reporter_or_value(part1_txt, None, pick_key_func, all_generated_blocks) str2_obj = parse_reporter_or_value(part2_txt, None, pick_key_func, all_generated_blocks) inputs = {"STRING1": str1_obj, "STRING2": str2_obj} block_id = _register_block("operator_join", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs) # set parents if nested blocks for obj in (str1_obj, str2_obj): if obj.get("kind") == "block": all_generated_blocks[obj["block"]]["parent"] = block_id return {"kind": "block", "block": block_id} # letter () of () (operator_letterof) - handle both [] and () for inputs m = re.search(r"letter \((.+?)\) of \((.+?)\)", text) if not m: m = re.search(r"letter \((.+?)\) of \[(.+?)\]", text) if m: index_obj = parse_reporter_or_value(m.group(1).strip(), None, pick_key_func, all_generated_blocks) string_val_obj = parse_reporter_or_value(m.group(2).strip(), None, pick_key_func, all_generated_blocks) inputs = {"LETTER": index_obj, "STRING": string_val_obj} block_id = _register_block("operator_letterof", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs) if index_obj.get("kind") == "block": all_generated_blocks[index_obj["block"]]["parent"] = block_id if string_val_obj.get("kind") == "block": all_generated_blocks[string_val_obj["block"]]["parent"] = block_id return {"kind": "block", "block": block_id} # (length of ()) (operator_length) - handle both [] and () for inputs #m = re.search(r"length of \((.+?)\)", text) m = re.search(r"length of\s*(?:\((.+?)\)|\[(.+?)\])", text) if not m: m = re.search(r"length of \[([^\]]+)\s*v\]", text) if m: arg_txt = (m.group(1) or m.group(2)).strip() list_or_string_val_obj = parse_reporter_or_value(arg_txt, None, pick_key_func, all_generated_blocks) #list_or_string_val_obj = parse_reporter_or_value(m.group(1).strip(), None, pick_key_func, all_generated_blocks) inputs = {"STRING": list_or_string_val_obj} block_id = _register_block("operator_length", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs) if list_or_string_val_obj.get("kind") == "block": all_generated_blocks[list_or_string_val_obj["block"]]["parent"] = block_id return {"kind": "block", "block": block_id} # (() mod ()) (operator_mod) # m = re.search(r"\((.+?)\)\s*mod\s*\((.+?)\)", text) m = re.search(r"\[([^\]]+)\s*v\]\s*mod\s*\(?\s*(.+?)\s*\)?", text) if m: num1_obj = parse_reporter_or_value(m.group(1).strip(), None, pick_key_func, all_generated_blocks) num2_obj = parse_reporter_or_value(m.group(2).strip(), None, pick_key_func, all_generated_blocks) inputs = {"NUM1": num1_obj, "NUM2": num2_obj} block_id = _register_block("operator_mod", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs) if num1_obj.get("kind") == "block": all_generated_blocks[num1_obj["block"]]["parent"] = block_id if num2_obj.get("kind") == "block": all_generated_blocks[num2_obj["block"]]["parent"] = block_id return {"kind": "block", "block": block_id} # (round ()) (operator_round) m = re.search(r"round \((.+?)\)", text) if m: num_obj = parse_reporter_or_value(m.group(1).strip(), None, pick_key_func, all_generated_blocks) inputs = {"NUM": num_obj} block_id = _register_block("operator_round", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs) if num_obj.get("kind") == "block": all_generated_blocks[num_obj["block"]]["parent"] = block_id return {"kind": "block", "block": block_id} # (() of ()) (operator_mathop) - handle variable for function type m = re.search(r"\[([^\]]+)\s*v\] of \((.+?)\)", text) # e.g. [sqrt v] of ((x pos) * (x pos)) if m: func_type = m.group(1).strip() value_obj = parse_reporter_or_value(m.group(2).strip(), None, pick_key_func, all_generated_blocks) inputs = {"NUM": value_obj} fields = {"OPERATOR": [func_type.upper(), None]} block_id = _register_block("operator_mathop", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs, fields=fields) if value_obj.get("kind") == "block": all_generated_blocks[value_obj["block"]]["parent"] = block_id return {"kind": "block", "block": block_id} # Also handle direct string for function type (e.g., "abs of (x)") m = re.search(r"([a-zA-Z]+)\s*of\s*\((.+?)\)", text) if m: func_type = m.group(1).strip() value_obj = parse_reporter_or_value(m.group(2).strip(), None, pick_key_func, all_generated_blocks) inputs = {"NUM": value_obj} fields = {"OPERATOR": [func_type.upper(), None]} block_id = _register_block("operator_mathop", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs, fields=fields) if value_obj.get("kind") == "block": all_generated_blocks[value_obj["block"]]["parent"] = block_id return {"kind": "block", "block": block_id} # Arithmetic operations: (() + ()), (() - ()), (() * ()), (() / ()) # This regex is designed to handle nested parentheses correctly. # It looks for an opening parenthesis, then non-parenthesis characters or balanced parentheses, # followed by an operator, and then the second operand. # This is a simplified approach; a full-fledged parser would use a stack. # arithmetic_match = re.search(r"\((.+?)\)\s*([+\-*/])\s*\((.+?)\)", text) arithmetic_match = re.search(r"\(?\s*(.+?)\s*\)?\s*([\+\-\*/])\s*\(?\s*(.+?)\s*\)?", text) if not arithmetic_match: # Try to match without outer parentheses for the operands, but still with an operator arithmetic_match = re.search(r"(.+?)\s*([+\-*/])\s*(.+)", text) if arithmetic_match: op1_str = arithmetic_match.group(1).strip() operator_symbol = arithmetic_match.group(2).strip() op2_str = arithmetic_match.group(3).strip() op1_obj = parse_reporter_or_value(op1_str, None, pick_key_func, all_generated_blocks) op2_obj = parse_reporter_or_value(op2_str, None, pick_key_func, all_generated_blocks) opcode_map = {'+': 'operator_add', '-': 'operator_subtract', '*': 'operator_multiply', '/': 'operator_divide'} inputs = {"NUM1": op1_obj, "NUM2": op2_obj} block_id = _register_block(opcode_map[operator_symbol], parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs) if op1_obj.get("kind") == "block": all_generated_blocks[op1_obj["block"]]["parent"] = block_id if op2_obj.get("kind") == "block": all_generated_blocks[op2_obj["block"]]["parent"] = block_id return {"kind": "block", "block": block_id} # (costume ()) (looks_costumenumbername) - handle with or without 'v' m = re.search(r"costume \((.+?)\)", text) if not m: m = re.search(r"costume \[([^\]]+)\s*v\]", text) if m: option = m.group(1).strip() fields = {"NUMBER_NAME": [option, None]} block_id = _register_block("looks_costumenumbername", parent_key, False, pick_key_func, all_generated_blocks, fields=fields) return {"kind": "block", "block": block_id} # (backdrop ()) (looks_backdropnumbername) - handle with or without 'v' m = re.search(r"backdrop \((.+?)\)", text) if not m: m = re.search(r"backdrop \[([^\]]+)\s*v\]", text) if m: option = m.group(1).strip() fields = {"NUMBER_NAME": [option, None]} block_id = _register_block("looks_backdropnumbername", parent_key, False, pick_key_func, all_generated_blocks, fields=fields) return {"kind": "block", "block": block_id} # (distance to ()) (sensing_distanceto) - handle with or without 'v' m = re.search(r"distance to \((.+?)\)", text) if not m: m = re.search(r"distance to \[([^\]]+)\s*v\]", text) if m: target = m.group(1).strip() if target == "mouse-pointer": target_val = "_mouse_" elif target == "edge": target_val = "_edge_" else: target_val = target inputs = {"TARGET": [target_val, None]} # This is a direct value, not a block block_id = _register_block("sensing_distanceto", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs) return {"kind": "block", "block": block_id} # (current ()) (sensing_current) - handle with or without 'v' m = re.search(r"current \((.+?)\)", text) if not m: m = re.search(r"current \[([^\]]+)\s*v\]", text) if m: unit = m.group(1).strip() fields = {"CURRENTMENU": [unit.upper(), None]} block_id = _register_block("sensing_current", parent_key, False, pick_key_func, all_generated_blocks, fields=fields) return {"kind": "block", "block": block_id} # (() of ()) (sensing_of) - handle both variable and non-variable properties, and objects # Updated regex to correctly capture the property and object, including nested reporters for property m = re.search(r"\((.+?)\)\s*of\s*\((.+?)\)", text) # (prop) of (obj) if not m: m = re.search(r"\((.+?)\)\s*of\s*\[([^\]]+)\s*v\]", text) # (prop) of [obj v] if m: prop_str = m.group(1).strip() obj = m.group(2).strip() # Map common property names to their internal Scratch representation prop_map = { "x position": "x position", "y position": "y position", "direction": "direction", "costume #": "costume number", "costume name": "costume name", "size": "size", "volume": "volume", "backdrop #": "backdrop number", "backdrop name": "backdrop name" } property_value = prop_map.get(prop_str, prop_str) # Use mapped value or original string # The object can be a sprite name or "_stage_" obj_kind = "menu" if obj.lower() == "stage": obj_val = "_stage_" elif obj.lower() == "myself": obj_val = "_myself_" else: obj_val = obj # Assume it's a sprite name # Create the menu block for OBJECT input object_menu_id = _register_block("sensing_of_object_menu", parent_key, True, pick_key_func, all_generated_blocks, fields={"OBJECT": [obj_val, None]}) # inputs = {"OBJECT": [1, object_menu_id]} # Link to the menu block # block_id = _register_block("sensing_of", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs, fields=fields) # # Update parent for the menu block # all_generated_blocks[object_menu_id]["parent"] = block_id # return {"kind": "block", "block": block_id} of_fields = {"OBJECT": [obj_val, None]} inputs = {"OBJECT": [1, object_menu_id]} block_id = _register_block("sensing_of", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs, fields=of_fields) all_generated_blocks[object_menu_id]["parent"] = block_id return {"kind": "block", "block": block_id} # (item (index) of [list v]) (data_itemoflist) - handle with or without 'v' and parentheses for index m = re.search(r"item \((.+?)\) of \((.+?)\)", text) if not m: m = re.search(r"item \((.+?)\) of \[([^\]]+)\s*v\]", text) if m: index_obj = parse_reporter_or_value(m.group(1).strip(), None, pick_key_func, all_generated_blocks) list_name = m.group(2).strip() inputs = {"INDEX": index_obj} fields = {"LIST": [list_name, None]} block_id = _register_block("data_itemoflist", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs, fields=fields) if index_obj.get("kind") == "block": all_generated_blocks[index_obj["block"]]["parent"] = block_id return {"kind": "block", "block": block_id} # (item # of [item] in [list v]) (data_itemnumoflist) - handle with or without 'v' and parentheses for item m = re.search(r"item # of \((.+?)\) in \((.+?)\)", text) if not m: m = re.search(r"item # of \[([^\]]+)\] in \[([^\]]+)\s*v\]", text) if m: item_obj = parse_reporter_or_value(m.group(1).strip(), None, pick_key_func, all_generated_blocks) list_name = m.group(2).strip() inputs = {"ITEM": item_obj} fields = {"LIST": [list_name, None]} block_id = _register_block("data_itemnumoflist", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs, fields=fields) if item_obj.get("kind") == "block": all_generated_blocks[item_obj["block"]]["parent"] = block_id return {"kind": "block", "block": block_id} # inside parse_reporter_or_value m = re.search(r"\(\s*(.+?)\s*\)\s*([\+\-\*/])\s*\(\s*(.+?)\s*\)", text) if m: left = parse_reporter_or_value(f"({m.group(1).strip()})", parent_key, pick_key_func, all_generated_blocks) right = parse_reporter_or_value(f"({m.group(3).strip()})", parent_key, pick_key_func, all_generated_blocks) op_map = {"+": "operator_add", "-": "operator_subtract", "*": "operator_multiply", "/": "operator_divide"} opcode = op_map[m.group(2).strip()] inputs = {"NUM1": left, "NUM2": right} block_id = _register_block(opcode, parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs) for side in (left, right): if side.get("kind") == "block": all_generated_blocks[side["block"]]["parent"] = block_id return {"kind": "block", "block": block_id} raise ValueError(f"Can't parse reporter or value: {text}") def parse_condition(stmt, parent_key, pick_key_func, all_generated_blocks): """ Parse Scratch-style boolean conditions, handling comparisons (<, =, >), boolean operators (and, or, not), and other sensing conditions. """ s = stmt.lower().strip() while s.startswith("<") and s.endswith(">"): s = s[1:-1].strip() s = s.lower() # 1a) Comparisons with explicit angle wrappers: < (...) op (...) > m = re.fullmatch( r"\s*<\s*(.+?)\s*(?P<|=|>)\s*(.+?)\s*>\s*", s, re.VERBOSE ) if m: left_txt, right_txt = m.group(1), m.group(3) operand1_obj = parse_reporter_or_value(unparen(left_txt), None, pick_key_func, all_generated_blocks) operand2_obj = parse_reporter_or_value(unparen(right_txt), None, pick_key_func, all_generated_blocks) op_map = {'<': 'operator_lt', '=': 'operator_equals', '>': 'operator_gt'} inputs = {"OPERAND1": operand1_obj, "OPERAND2": operand2_obj} block_id = _register_block(op_map[m.group('op')], parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs) # Set parents for nested inputs if operand1_obj.get("kind") == "block": all_generated_blocks[operand1_obj["block"]]["parent"] = block_id if operand2_obj.get("kind") == "block": all_generated_blocks[operand2_obj["block"]]["parent"] = block_id return {"kind": "block", "block": block_id} # 1b) Simple comparisons without angle wrappers: A op B m_simple = re.fullmatch(r"\s*(.+?)\s*(?P<|=|>)\s*(.+?)\s*", s) if m_simple: left_txt, right_txt = m_simple.group(1), m_simple.group(3) operand1_obj = parse_reporter_or_value(unparen(left_txt), None, pick_key_func, all_generated_blocks) operand2_obj = parse_reporter_or_value(unparen(right_txt), None, pick_key_func, all_generated_blocks) op_map = {'<': 'operator_lt', '=': 'operator_equals', '>': 'operator_gt'} inputs = {"OPERAND1": operand1_obj, "OPERAND2": operand2_obj} block_id = _register_block(op_map[m_simple.group('op')], parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs) if operand1_obj.get("kind") == "block": all_generated_blocks[operand1_obj["block"]]["parent"] = block_id if operand2_obj.get("kind") == "block": all_generated_blocks[operand2_obj["block"]]["parent"] = block_id return {"kind": "block", "block": block_id} # 2) Boolean AND / OR m = re.fullmatch(r"\s*<\s*(.+?)\s+(and|or)\s+(.+?)\s*>\s*", s) if m: cond1_obj = parse_condition(m.group(1), None, pick_key_func, all_generated_blocks) cond2_obj = parse_condition(m.group(3), None, pick_key_func, all_generated_blocks) op_block = 'operator_and' if m.group(2) == 'and' else 'operator_or' inputs = {"OPERAND1": cond1_obj, "OPERAND2": cond2_obj} block_id = _register_block(op_block, parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs) if cond1_obj.get("kind") == "block": all_generated_blocks[cond1_obj["block"]]["parent"] = block_id if cond2_obj.get("kind") == "block": all_generated_blocks[cond2_obj["block"]]["parent"] = block_id return {"kind": "block", "block": block_id} # 3) Boolean NOT m = re.fullmatch(r"\s*<\s*not\s+(.+?)\s*>\s*", s) if m: inner_obj = parse_condition(m.group(1), None, pick_key_func, all_generated_blocks) inputs = {"OPERAND": inner_obj} block_id = _register_block("operator_not", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs) if inner_obj.get("kind") == "block": all_generated_blocks[inner_obj["block"]]["parent"] = block_id return {"kind": "block", "block": block_id} # 4) Contains: <[list v] contains [item]?> m = re.search(r"\[([^\]]+)\s*v\] contains \[(.+?)\]\?", s) if m: list_name = m.group(1).strip() item_val = {"kind": "value", "value": m.group(2).strip()} # Item can be a value or a block # Create the data_list reporter block list_block_id = _register_block("data_list", parent_key, True, pick_key_func, all_generated_blocks, fields={"LIST": [list_name, None]}) inputs = {"LIST": {"kind": "block", "block": list_block_id}, "ITEM": item_val} block_id = _register_block("data_listcontainsitem", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs) all_generated_blocks[list_block_id]["parent"] = block_id return {"kind": "block", "block": block_id} # 5) Touching object: # m = re.search(r"touching \[([^\]]+)\s*v\]\?", s) # if m: # opt = m.group(1).strip() # opt_val = {'mouse-pointer':'_mouse_','edge':'_edge_'}.get(opt, opt) # # Create the menu block for TOUCHINGOBJECTMENU input # menu_block_id = _register_block("sensing_touchingobjectmenu", parent_key, True, pick_key_func, all_generated_blocks, fields={"TOUCHINGOBJECTMENU": [opt_val, None]}) # inputs = {"TOUCHINGOBJECTMENU": [1, menu_block_id]} # Link to the menu block # block_id = _register_block("sensing_touchingobject", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs) # all_generated_blocks[menu_block_id]["parent"] = block_id # return {"kind": "block", "block": block_id} m = re.search(r"touching\s+\[\s*([^\]]+?)\s*v\s*\]\?", s) if m: opt = m.group(1).strip() opt_val = {'mouse-pointer':'_mouse_', 'edge':'_edge_'}.get(opt, opt) # Create the menu block for TOUCHINGOBJECTMENU input menu_block_id = _register_block("sensing_touchingobjectmenu", parent_key, True, pick_key_func, all_generated_blocks, fields={"TOUCHINGOBJECTMENU": [opt_val, None]}) inputs = {"TOUCHINGOBJECTMENU": [1, menu_block_id]} # Link to the menu block block_id = _register_block("sensing_touchingobject", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs) all_generated_blocks[menu_block_id]["parent"] = block_id return {"kind": "block", "block": block_id} # 6) Touching color: m = re.search(r"touching color \[(#[0-9A-Fa-f]{6})\]\?", s) if m: inputs = {"COLOR": [1, [9, m.group(1)]]} # Color input is special, often a list [type, value] block_id = _register_block("sensing_touchingcolor", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs) return {"kind": "block", "block": block_id} # 7) Color is touching color: m = re.search(r"color \[(#[0-9A-Fa-f]{6})\] is touching \[(#[0-9A-Fa-f]{6})\]\?", s) if m: inputs = {"COLOR1": [1, [9, m.group(1)]], "COLOR2": [1, [9, m.group(2)]]} block_id = _register_block("sensing_coloristouchingcolor", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs) return {"kind": "block", "block": block_id} # 8) Key pressed: m = re.search(r"key \[([^\]]+)\s*v\] pressed\?", s) if m: option = m.group(1).strip() menu_block_id = _register_block("sensing_keyoptions", parent_key, True, pick_key_func, all_generated_blocks, fields={"KEY_OPTION": [option, None]}) inputs = {"KEY_OPTION": [1, menu_block_id]} block_id = _register_block("sensing_keypressed", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs) all_generated_blocks[menu_block_id]["parent"] = block_id return {"kind": "block", "block": block_id} # 9) Mouse down?: mouse down? if s == "mouse down?": block_id = _register_block("sensing_mousedown", parent_key, False, pick_key_func, all_generated_blocks) return {"kind": "block", "block": block_id} val_obj = parse_reporter_or_value(unparen(stmt), parent_key, pick_key_func, all_generated_blocks) if val_obj: return val_obj raise ValueError(f"Can't parse condition: {stmt}") def classify(line): """ Classifies a pseudo-code line into its corresponding Scratch opcode and block type. Order of checks matters: more specific patterns should come before more general ones. """ l = line.lower().strip() # Ignore comments if l.startswith("//"): return None, None # Hat Blocks (most specific first) if re.match(r"when green flag click(ed)?", l): return "event_whenflagclicked", "hat" if re.match(r"when (.+?) key press(ed)?", l): return "event_whenkeypressed", "hat" if re.match(r"when this sprite click(ed)?", l): return "event_whenthisspriteclicked", "hat" if l.startswith("when backdrop switches to"): return "event_whenbackdropswitchesto", "hat" if l.startswith("when ") and " > (" in l: return "event_whengreaterthan", "hat" if l.startswith("when i receive"): return "event_whenbroadcastreceived", "hat" if re.match(r"when i start as a clo(ne)?", l): return "control_start_as_clone", "hat" if l.startswith("define "): return "procedures_definition", "hat" if l.startswith("procedure "): return "procedures_definition", "hat" # For "procedure moveBall" # Motion Blocks if l.startswith("go to x:"): return "motion_gotoxy", "stack" # IMPORTANT: More specific glide block before less specific one if l.startswith("glide ") and " secs to x:" in l: return "motion_glidesecstoxy", "stack" if l.startswith("glide ") and " secs to " in l: return "motion_glideto", "stack" if l.startswith("move "): return "motion_movesteps", "stack" if l.startswith("turn right "): return "motion_turnright", "stack" if l.startswith("turn left "): return "motion_turnleft", "stack" if l.startswith("go to "): return "motion_goto", "stack" if l.startswith("point in direction"): return "motion_pointindirection", "stack" if l.startswith("point towards"): return "motion_pointtowards", "stack" if l.startswith("change x by"): return "motion_changexby", "stack" if re.match(r"set x to\s*\(.+\)", l): return "motion_setx", "stack" # Specific for set x if l.startswith("change y by"): return "motion_changeyby", "stack" if re.match(r"set y to\s*\(.+\)", l): return "motion_sety", "stack" # Specific for set y #if re.match(r"if on edge, bounce( off edge)?", l): return "motion_ifonedgebounce", "stack" if re.match(r"if on edge,\s*bounc(e)?(\s+off\s+edge)?", l.strip(), re.IGNORECASE): return "motion_ifonedgebounce", "stack" if re.match(r"bounce off edg(e)?", l): return "motion_ifonedgebounce", "stack" # Alias if l.startswith("set rotation style"): return "motion_setrotationstyle", "stack" # Looks Blocks if l.startswith("say ") and " for " in l: return "looks_sayforsecs", "stack" if l.startswith("say "): return "looks_say", "stack" if l.startswith("think ") and " for " in l: return "looks_thinkforsecs", "stack" if l.startswith("think "): return "looks_think", "stack" if l.startswith("switch costume to"): return "looks_switchcostumeto", "stack" if re.match(r"next costum(e)?", l): return "looks_nextcostume", "stack" if l.startswith("switch backdrop to ") and " and wait" in l: return "looks_switchbackdroptowait", "stack" if l.startswith("switch backdrop to"): return "looks_switchbackdropto", "stack" if l == "next backdrop": return "looks_nextbackdrop", "stack" if l.startswith("change size by"): return "looks_changesizeby", "stack" if l.startswith("set size to"): return "looks_setsizeto", "stack" # Updated regex for change/set effect by/to if re.match(r"change\s*(\[.+?v\]|\(.+?\))?\s*effect by", l): return "looks_changeeffectby", "stack" if re.match(r"set\s*(\[.+?v\]|\(.+?\))?\s*effect to", l): return "looks_seteffectto", "stack" if l == "clear graphic effects": return "looks_cleargraphiceffects", "stack" if l == "show": return "looks_show", "stack" if l == "hide": return "looks_hide", "stack" if l.startswith("go to ") and " layer" in l: return "looks_gotofrontback", "stack" if l.startswith("go ") and " layers" in l: return "looks_goforwardbackwardlayers", "stack" # Sound Blocks if re.match(r"play sound (.+?) until do(ne)?", l): return "sound_playuntildone", "stack" if l.startswith("start sound "): return "sound_play", "stack" if l == "stop all sounds": return "sound_stopallsounds", "stack" if l.startswith("change volume by"): return "sound_changevolumeby", "stack" if l.startswith("set volume to"): return "sound_setvolumeto", "stack" # Event Blocks (broadcasts) if l.startswith("broadcast ") and " and wait" in l: return "event_broadcastandwait", "stack" if l.startswith("broadcast "): return "event_broadcast", "stack" # Control Blocks if re.match(r"wait (.+?) seconds", l): return "control_wait", "stack" if l.startswith("wait until <"): return "control_wait_until", "stack" if l.startswith("repeat ("): return "control_repeat", "c_block" if l == "forever": return "control_forever", "c_block" if l.startswith("if <") and " then else" in l: return "control_if_else", "c_block" if l.startswith("if <"): return "control_if", "c_block" if l.startswith("repeat until <"): return "control_repeat_until", "c_block" # Updated regex for stop block to handle different options if re.match(r"stop \[(all|this script|other scripts in sprite)\s*v\]", l): return "control_stop", "cap" if l.startswith("create clone of"): return "control_create_clone_of", "stack" if l == "delete this clone": return "control_delete_this_clone", "cap" # Data Blocks if l.startswith("set [") and " to " in l: return "data_setvariableto", "stack" if l.startswith("change [") and " by " in l: return "data_changevariableby", "stack" if l.startswith("show variable"): return "data_showvariable", "stack" if l.startswith("hide variable"): return "data_hidevariable", "stack" if l.startswith("add ") and " to [" in l: return "data_addtolist", "stack" # Updated regex for delete of list if re.match(r"delete \((.+?)\) of \[([^\]]+)\s*v\]", l): return "data_deleteoflist", "stack" if l.startswith("delete all of [" ): return "data_deletealloflist", "stack" if l.startswith("insert ") and " at " in l: return "data_insertatlist", "stack" if l.startswith("replace item ") and " of [" in l: return "data_replaceitemoflist", "stack" if l.startswith("show list"): return "data_showlist", "stack" if l.startswith("hide list"): return "data_hidelist", "stack" # Sensing Blocks if re.match(r"ask (.+?) and wai(t)?", l): return "sensing_askandwait", "stack" if l == "reset timer": return "sensing_resettimer", "stack" if l.startswith("set drag mode"): return "sensing_setdragmode", "stack" # Custom Blocks (procedures_call) - specific rule for "call" if l.startswith("call "): return "procedures_call", "stack" # Custom Blocks (procedures_call) - LAST RESORT (generic match) # This should be the very last check for stack-type blocks to avoid conflicts. # It tries to match anything that looks like a function call with or without arguments. custom_block_match = re.match(r"([a-zA-Z_][a-zA-Z0-9_ ]*)(?:\s*\((.+?)\))*\s*$", l) if custom_block_match: # Before returning, ensure it's not a known simple reporter or variable name # that might have been missed or is being used standalone. # This is a heuristic; a full parser would be more robust. potential_name = custom_block_match.group(1).strip() if potential_name not in ["x position", "y position", "direction", "mouse x", "mouse y", "loudness", "timer", "days since 2000", "username", "answer", "size", "volume"] and \ not re.fullmatch(r"\[[^\]]+\]", potential_name) and \ not re.fullmatch(r"\[[^\]]+\]\s*v", potential_name): return "procedures_call", "stack" raise ValueError(f"Unknown statement: {line!r}") def generate_plan(generated_input, opcode_keys, pseudo_code): """ Build a nested “plan” tree from: • generated_input: dict of block_key -> block_data (pre-generated block definitions) • opcode_keys: dict of opcode -> list of block_keys (in order) • pseudo_code: a multiline string, indented with two‑space levels Returns: { "flow": [ ... list of block dictionaries ... ] } """ # helper: pick next unused block_key for an opcode ptrs = defaultdict(int) def pick_key(opcode): lst = opcode_keys.get(opcode, []) idx = ptrs[opcode] if idx >= len(lst): # Fallback: if no more pre-generated keys, create a new one. ptrs[opcode] += 1 return f"{opcode}_{idx + 1}" ptrs[opcode] += 1 return lst[idx] all_generated_blocks = {} for key, block_data in generated_input.items(): all_generated_blocks[key] = copy.deepcopy(block_data) # Ensure initial blocks have no next/parent/topLevel set from previous runs all_generated_blocks[key]["next"] = None all_generated_blocks[key]["parent"] = None all_generated_blocks[key]["topLevel"] = False # Will be set to True for Hat blocks # Stack stores (indent, parent_block_key, first_block_in_this_chain, last_block_in_this_chain) # The last element of the stack is always the currently active scope. # Sentinel: global scope (indent -1, no parent, no blocks yet in this "chain") stack = [(-1, None, None, None)] top_level_script_keys = [] lines = pseudo_code.splitlines() i = 0 while i < len(lines): raw_line = lines[i] stripped_line = raw_line.strip() # Skip empty lines and comments if not stripped_line or stripped_line.startswith("//"): i += 1 continue current_indent = (len(raw_line) - len(raw_line.lstrip())) // 2 # Handle 'else' and 'end' keywords first, as they control scope if stripped_line.lower() == "else": # Pop the 'then' substack's scope # This pop should always be safe as 'else' implies an 'if' scope exists popped_indent, popped_parent_key, first_then_block, last_then_block = stack.pop() if last_then_block: all_generated_blocks[last_then_block]["next"] = None # Link the 'then' substack to its 'if-else' parent if popped_parent_key and all_generated_blocks[popped_parent_key]["op_code"] == "control_if_else": all_generated_blocks[popped_parent_key]["inputs"]["SUBSTACK"] = [2, first_then_block] if first_then_block else [2, None] # Push a new scope for the 'else' substack, with the same parent stack.append((current_indent, popped_parent_key, None, None)) i += 1 continue if stripped_line.lower() == "end": # Pop the current substack's scope # This pop should always be safe if pseudo-code is balanced popped_indent, popped_parent_key, first_substack_block, last_substack_block = stack.pop() if last_substack_block: all_generated_blocks[last_substack_block]["next"] = None if popped_parent_key: parent_block = all_generated_blocks[popped_parent_key] # Determine which substack input to set (SUBSTACK or SUBSTACK2 for if-else) if parent_block["op_code"] == "control_if_else" and \ "SUBSTACK" in parent_block["inputs"] and \ parent_block["inputs"]["SUBSTACK"][1] is not None and \ "SUBSTACK2" not in parent_block["inputs"]: parent_block["inputs"]["SUBSTACK2"] = [2, first_substack_block] if first_substack_block else [2, None] elif parent_block["block_shape"] == "C-Block" or parent_block["op_code"] == "procedures_definition": parent_block["inputs"]["SUBSTACK"] = [2, first_substack_block] if first_substack_block else [2, None] i += 1 continue # Adjust stack based on indentation # Pop scopes whose indentation is greater than or equal to the current line's indentation # The `len(stack) > 1` prevents popping the sentinel. while len(stack) > 1 and stack[-1][0] >= current_indent: popped_indent, popped_parent_key, first_chain_block, last_chain_block = stack.pop() if last_chain_block: all_generated_blocks[last_chain_block]["next"] = None # If this popped scope was a substack, ensure its parent links to it. # This part is crucial for correctly linking chains after indentation changes. if popped_parent_key: parent_block = all_generated_blocks[popped_parent_key] if parent_block["block_shape"] == "C-Block" or parent_block["op_code"] == "procedures_definition": if parent_block["op_code"] == "control_if_else" and \ "SUBSTACK" in parent_block["inputs"] and \ parent_block["inputs"]["SUBSTACK"][1] is not None and \ "SUBSTACK2" not in parent_block["inputs"]: parent_block["inputs"]["SUBSTACK2"] = [2, first_chain_block] if first_chain_block else [2, None] else: parent_block["inputs"]["SUBSTACK"] = [2, first_chain_block] if first_chain_block else [2, None] # Get the current active scope from the stack (guaranteed not empty due to sentinel) current_scope_indent, current_parent_key, first_active_block_in_chain, last_active_block_in_chain = stack[-1] # Classify the statement stmt_for_parse = stripped_line.rstrip("then").strip() opcode, ntype = classify(stmt_for_parse) if opcode is None: i += 1 continue # Create the new block key = pick_key(opcode) info = copy.deepcopy(all_block_definitions[opcode]) info["id"] = key info["next"] = None # Default if "inputs" not in info: info["inputs"] = {} if "fields" not in info: info["fields"] = {} # Parse inputs and fields # Numeric inputs (e.g., move (10) steps, wait (1) seconds) if opcode == "motion_movesteps": m = re.search(r"move\s*\(\s*(-?\d+(\.\d+)?)\s*\)\s*steps", stmt_for_parse, re.IGNORECASE) if m: info["inputs"]["STEPS"] = {"kind": "value", "value": float(m.group(1)) if '.' in m.group(1) else int(m.group(1))} elif opcode == "motion_turnright" or opcode == "motion_turnleft": m = re.search(r"turn\s*(?:right|left)?\s*\(.*?\)\s*\(\s*(-?\d+(\.\d+)?)\s*\)\s*degrees", stmt_for_parse, re.IGNORECASE) if m: info["inputs"]["DEGREES"] = {"kind": "value", "value": float(m.group(1)) if '.' in m.group(1) else int(m.group(1))} elif opcode == "motion_gotoxy": m_x = re.search(r"x:\s*\(\s*(-?\d+(\.\d+)?)\s*\)", stmt_for_parse, re.IGNORECASE) m_y = re.search(r"y:\s*\(\s*(-?\d+(\.\d+)?)\s*\)", stmt_for_parse, re.IGNORECASE) if m_x: info["inputs"]["X"] = {"kind": "value", "value": float(m_x.group(1)) if '.' in m_x.group(1) else int(m_x.group(1))} if m_y: info["inputs"]["Y"] = {"kind": "value", "value": float(m_y.group(1)) if '.' in m_y.group(1) else int(m_y.group(1))} elif opcode == "motion_glidesecstoxy": m_secs = re.search(r"glide\s*\(\s*(-?\d+(\.\d+)?)\s*\)\s*secs", stmt_for_parse, re.IGNORECASE) m_x = re.search(r"x:\s*\(\s*(-?\d+(\.\d+)?)\s*\)", stmt_for_parse, re.IGNORECASE) m_y = re.search(r"y:\s*\(\s*(-?\d+(\.\d+)?)\s*\)", stmt_for_parse, re.IGNORECASE) if m_secs: info["inputs"]["SECS"] = {"kind": "value", "value": float(m_secs.group(1)) if '.' in m_secs.group(1) else int(m_secs.group(1))} if m_x: info["inputs"]["X"] = {"kind": "value", "value": float(m_x.group(1)) if '.' in m_x.group(1) else int(m_x.group(1))} if m_y: info["inputs"]["Y"] = {"kind": "value", "value": float(m_y.group(1)) if '.' in m_y.group(1) else int(m_y.group(1))} elif opcode == "motion_pointindirection": m = re.search(r"direction\s*\(\s*(-?\d+(\.\d+)?)\s*\)", stmt_for_parse, re.IGNORECASE) if m: info["inputs"]["DIRECTION"] = {"kind": "value", "value": float(m.group(1)) if '.' in m.group(1) else int(m.group(1))} elif opcode in ["motion_changexby", "motion_changeyby"]: m = re.search(r"by\s*\(\s*(-?\d+(\.\d+)?)\s*\)", stmt_for_parse, re.IGNORECASE) if m: info["inputs"]["DX" if opcode == "motion_changexby" else "DY"] = {"kind": "value", "value": float(m.group(1)) if '.' in m.group(1) else int(m.group(1))} elif opcode in ["motion_setx", "motion_sety"]: m = re.search(r"(?:set x to|set y to)\s*\(\s*(-?\d+(\.\d+)?)\s*\)", stmt_for_parse, re.IGNORECASE) if m: info["inputs"]["X" if opcode == "motion_setx" else "Y"] = {"kind": "value", "value": float(m.group(1)) if '.' in m.group(1) else int(m.group(1))} elif opcode == "looks_changesizeby": m = re.search(r"by\s*\(\s*(-?\d+(\.\d+)?)\s*\)", stmt_for_parse, re.IGNORECASE) if m: info["inputs"]["CHANGE"] = {"kind": "value", "value": float(m.group(1)) if '.' in m.group(1) else int(m.group(1))} elif opcode == "looks_setsizeto": m = re.search(r"to\s*\(\s*(-?\d+(\.\d+)?)\s*\)\s*%", stmt_for_parse, re.IGNORECASE) if m: info["inputs"]["SIZE"] = {"kind": "value", "value": float(m.group(1)) if '.' in m.group(1) else int(m.group(1))} elif opcode in ["looks_changeeffectby", "sound_changeeffectby"]: m = re.search(r"by\s*\(\s*(-?\d+(\.\d+)?)\s*\)", stmt_for_parse, re.IGNORECASE) if m: info["inputs"]["CHANGE" if opcode == "looks_changeeffectby" else "VALUE"] = {"kind": "value", "value": float(m.group(1)) if '.' in m.group(1) else int(m.group(1))} elif opcode in ["looks_seteffectto", "sound_setvolumeto"]: m = re.search(r"to\s*\(\s*(-?\d+(\.\d+)?)\s*\)(?:\s*%)?", stmt_for_parse, re.IGNORECASE) if m: info["inputs"]["VALUE" if opcode == "looks_seteffectto" else "VOLUME"] = {"kind": "value", "value": float(m.group(1)) if '.' in m.group(1) else int(m.group(1))} elif opcode == "looks_goforwardbackwardlayers": m = re.search(r"go\s*(?:forward|backward)\s*\(\s*(-?\d+)\s*\)\s*layers", stmt_for_parse, re.IGNORECASE) if m: info["inputs"]["NUM"] = {"kind": "value", "value": int(m.group(1))} elif opcode == "control_wait": m = re.search(r"wait\s*\(\s*(-?\d+(\.\d+)?)\s*\)\s*seconds", stmt_for_parse, re.IGNORECASE) if m: info["inputs"]["DURATION"] = {"kind": "value", "value": float(m.group(1)) if '.' in m.group(1) else int(m.group(1))} elif opcode == "control_repeat": m = re.search(r"repeat\s*\(\s*(-?\d+(\.\d+)?)\s*\)", stmt_for_parse, re.IGNORECASE) if m: info["inputs"]["TIMES"] = {"kind": "value", "value": int(m.group(1))} elif opcode == "data_changevariableby": m = re.search(r"by\s*\(\s*(-?\d+(\.\d+)?)\s*\)", stmt_for_parse, re.IGNORECASE) if m: info["inputs"]["VALUE"] = {"kind": "value", "value": float(m.group(1)) if '.' in m.group(1) else int(m.group(1))} elif opcode == "data_deleteoflist": m = re.search(r"delete\s*\((.+?)\)\s*of", stmt_for_parse, re.IGNORECASE) if m: val_str = m.group(1).strip() if val_str.isdigit(): info["inputs"]["INDEX"] = {"kind": "value", "value": int(val_str)} else: # "all", "last", "random" info["inputs"]["INDEX"] = {"kind": "menu", "option": val_str} elif opcode == "data_insertatlist": m_item = re.search(r"insert\s*\[([^\]]+)\]\s*at", stmt_for_parse, re.IGNORECASE) m_index = re.search(r"at\s*\(\s*(-?\d+)\s*\)\s*of", stmt_for_parse, re.IGNORECASE) if m_item: info["inputs"]["ITEM"] = {"kind": "value", "value": m_item.group(1).strip()} if m_index: info["inputs"]["INDEX"] = {"kind": "value", "value": int(m_index.group(1))} elif opcode == "data_replaceitemoflist": m_index = re.search(r"replace item\s*\(\s*(-?\d+)\s*\)\s*of", stmt_for_parse, re.IGNORECASE) m_item = re.search(r"with\s*\[([^\]]+)\]", stmt_for_parse, re.IGNORECASE) if m_index: info["inputs"]["INDEX"] = {"kind": "value", "value": int(m_index.group(1))} if m_item: info["inputs"]["ITEM"] = {"kind": "value", "value": m_item.group(1).strip()} elif opcode == "event_whengreaterthan": m = re.search(r">\s*\(\s*(-?\d+(\.\d+)?)\s*\)", stmt_for_parse, re.IGNORECASE) if m: info["inputs"]["VALUE"] = {"kind": "value", "value": float(m.group(1)) if '.' in m.group(1) else int(m.group(1))} # String inputs elif opcode == "looks_sayforsecs": m = re.search(r"say\s*\[([^\]]+)\]\s*for\s*\(\s*(-?\d+(\.\d+)?)\s*\)\s*seconds", stmt_for_parse, re.IGNORECASE) if m: info["inputs"]["MESSAGE"] = {"kind": "value", "value": m.group(1).strip()} info["inputs"]["SECS"] = {"kind": "value", "value": float(m.group(2)) if '.' in m.group(2) else int(m.group(2))} elif opcode == "looks_say": m = re.search(r"say\s*(.+)", stmt_for_parse, re.IGNORECASE) if m: info["inputs"]["MESSAGE"] = parse_reporter_or_value(m.group(1).strip(), key, pick_key, all_generated_blocks) elif opcode == "looks_thinkforsecs": m = re.search(r"think\s*\[([^\]]+)\]\s*for\s*\(\s*(-?\d+(\.\d+)?)\s*\)\s*seconds", stmt_for_parse, re.IGNORECASE) if m: info["inputs"]["MESSAGE"] = {"kind": "value", "value": m.group(1).strip()} info["inputs"]["SECS"] = {"kind": "value", "value": float(m.group(2)) if '.' in m.group(2) else int(m.group(2))} elif opcode == "looks_think": m = re.search(r"think\s*\[([^\]]+)\]", stmt_for_parse, re.IGNORECASE) if m: info["inputs"]["MESSAGE"] = {"kind": "value", "value": m.group(1).strip()} elif opcode == "sensing_askandwait": m = re.search(r"ask\s*\[([^\]]+)\]\s*and wait", stmt_for_parse, re.IGNORECASE) if m: info["inputs"]["QUESTION"] = {"kind": "value", "value": m.group(1).strip()} elif opcode == "data_addtolist": m = re.search(r"add\s*\[([^\]]+)\]\s*to", stmt_for_parse, re.IGNORECASE) if m: info["inputs"]["ITEM"] = {"kind": "value", "value": m.group(1).strip()} elif opcode == "data_setvariableto": m_var = re.search(r"set\s*\[([^\]]+)\s*v\]\s*to\s*(.+)", stmt_for_parse, re.IGNORECASE) if m_var: var_name = m_var.group(1).strip() value_str = m_var.group(2).strip() info["fields"]["VARIABLE"] = [var_name, None] info["inputs"]["VALUE"] = parse_reporter_or_value(value_str, key, pick_key, all_generated_blocks) # Dropdown/Menu inputs elif opcode == "motion_goto": m = re.search(r"go to\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE) if m: option = m.group(1).strip() if option == "random position": option = "_random_" elif option == "mouse-pointer": option = "_mouse_" info["inputs"]["TO"] = {"kind": "menu", "option": option} elif opcode == "motion_glideto": m_secs = re.search(r"glide\s*\(\s*(-?\d+(\.\d+)?)\s*\)\s*secs to\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE) if m_secs: info["inputs"]["SECS"] = {"kind": "value", "value": float(m_secs.group(1)) if '.' in m_secs.group(1) else int(m_secs.group(1))} option = m_secs.group(2).strip() if option == "random position": option = "_random_" elif option == "mouse-pointer": option = "_mouse_" info["inputs"]["TO"] = {"kind": "menu", "option": option} elif opcode == "motion_pointtowards": m = re.search(r"point towards\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE) if m: option = m.group(1).strip() if option == "mouse-pointer": option = "_mouse_" info["inputs"]["TOWARDS"] = {"kind": "menu", "option": option} elif opcode == "sensing_keypressed": # For boolean block m = re.search(r"key \[([^\]]+)\s*v\] pressed\?", stmt_for_parse, re.IGNORECASE) if m: info["inputs"]["KEY_OPTION"] = {"kind": "menu", "option": m.group(1).strip()} elif opcode == "sensing_touchingobject": # For boolean block m = re.search(r"touching \[([^\]]+)\s*v\]\?", stmt_for_parse, re.IGNORECASE) if m: option = m.group(1).strip() if option == "mouse-pointer": option = "_mouse_" elif option == "edge": option = "_edge_" info["inputs"]["TOUCHINGOBJECTMENU"] = {"kind": "menu", "option": option} elif opcode == "control_create_clone_of": m = re.search(r"create clone of\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE) if m: option = m.group(1).strip() if option == "myself": option = "_myself_" info["inputs"]["CLONE_OPTION"] = {"kind": "menu", "option": option} elif opcode in ["sound_playuntildone", "sound_play"]: m = re.search(r"(?:play sound|start sound)\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE) if m: info["inputs"]["SOUND_MENU"] = {"kind": "menu", "option": m.group(1).strip()} elif opcode == "looks_switchcostumeto": m = re.search(r"switch costume to\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE) if m: info["inputs"]["COSTUME"] = {"kind": "menu", "option": m.group(1).strip()} elif opcode in ["looks_switchbackdropto", "looks_switchbackdroptowait"]: m = re.search(r"switch backdrop to\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE) if m: info["inputs"]["BACKDROP"] = {"kind": "menu", "option": m.group(1).strip()} elif opcode in ["event_broadcast", "event_broadcastandwait"]: m = re.search(r"broadcast\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE) if m: info["inputs"]["BROADCAST_INPUT"] = {"kind": "menu", "option": m.group(1).strip()} elif opcode == "event_whenbroadcastreceived": m = re.search(r"when i receive\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE) if m: info.setdefault("fields", {})["BROADCAST_OPTION"] = [m.group(1).strip(), None] # Conditional inputs (Boolean blocks) elif opcode in ["control_if", "control_if_else", "control_wait_until", "control_repeat_until"]: cond_match = re.search(r"<(.*?)>", stmt_for_parse) if cond_match: # Pass pick_key to parse_condition info["inputs"]["CONDITION"] = parse_condition(cond_match.group(1).strip(), key, pick_key, all_generated_blocks) elif opcode in ["operator_and", "operator_or", "operator_not", "operator_contains", "sensing_touchingcolor", "sensing_coloristouchingcolor", "sensing_mousedown"]: # These are handled by parse_condition directly, which returns the nested structure # No need to re-parse inputs here, as they are part of the condition structure pass # Inputs are set when parse_condition is called for the parent block # Fields parsing if "VARIABLE" in info["fields"]: m = re.search(r"\[([^\]]+)\s*v\]", stmt_for_parse) if m: var_name = m.group(1).strip() info["fields"]["VARIABLE"] = [var_name, None] if "LIST" in info["fields"]: m = re.search(r"(?:to|of|in)\s*\[([^\]]+)\s*v\]", stmt_for_parse) if m: info["fields"]["LIST"] = [m.group(1), None] if "STOP_OPTION" in info["fields"]: m = re.search(r"stop \[([^\]]+)\s*v\]", stmt_for_parse) if m: info["fields"]["STOP_OPTION"] = [m.group(1), None] if "STYLE" in info["fields"]: m = re.search(r"set rotation style \[([^\]]+)\s*v\]", stmt_for_parse) if m: info["fields"]["STYLE"] = [m.group(1), None] if "DRAG_MODE" in info["fields"]: m = re.search(r"set drag mode \[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE) if m: info["fields"]["DRAG_MODE"] = [m.group(1), None] if "EFFECT" in info["fields"] and opcode in ["looks_changeeffectby", "looks_seteffectto", "sound_changeeffectby", "sound_seteffectto"]: m = re.search(r"(?:change|set)\s*\[([^\]]+)\s*v\] effect", stmt_for_parse, re.IGNORECASE) if m: info["fields"]["EFFECT"] = [m.group(1).upper(), None] if "NUMBER_NAME" in info["fields"] and opcode in ["looks_costumenumbername", "looks_backdropnumbername"]: m = re.search(r"(?:costume|backdrop)\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE) if m: info["fields"]["NUMBER_NAME"] = [m.group(1), None] if "FRONT_BACK" in info["fields"] and opcode == "looks_gotofrontback": m = re.search(r"go to\s*\[([^\]]+)\s*v\] layer", stmt_for_parse, re.IGNORECASE) if m: info["fields"]["FRONT_BACK"] = [m.group(1), None] if "FORWARD_BACKWARD" in info["fields"] and opcode == "looks_goforwardbackwardlayers": m = re.search(r"go\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE) if m: info["fields"]["FORWARD_BACKWARD"] = [m.group(1), None] if "OPERATOR" in info["fields"] and opcode == "operator_mathop": m = re.search(r"\[([^\]]+)\s*v\] of", stmt_for_parse, re.IGNORECASE) if m: info["fields"]["OPERATOR"] = [m.group(1).upper(), None] if "CURRENTMENU" in info["fields"] and opcode == "sensing_current": m = re.search(r"current\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE) if m: info["fields"]["CURRENTMENU"] = [m.group(1).upper(), None] if "PROPERTY" in info["fields"] and opcode == "sensing_of": m = re.search(r"\((.+?)\) of", stmt_for_parse, re.IGNORECASE) if m: prop = m.group(1).strip() prop_map = { "x position": "x position", "y position": "y position", "direction": "direction", "costume #": "costume number", "costume name": "costume name", "size": "size", "volume": "volume", "backdrop #": "backdrop number", "backdrop name": "backdrop name" } info["fields"]["PROPERTY"] = [prop_map.get(prop, prop), None] if "WHENGREATERTHANMENU" in info["fields"] and opcode == "event_whengreaterthan": m = re.search(r"when\s*\[([^\]]+)\s*v\] >", stmt_for_parse, re.IGNORECASE) if m: info["fields"]["WHENGREATERTHANMENU"] = [m.group(1).upper(), None] if "KEY_OPTION" in info["fields"] and opcode == "event_whenkeypressed": # For event_whenkeypressed hat block's field m = re.search(r"when\s*\[([^\]]+)\s*v\] key pressed", stmt_for_parse, re.IGNORECASE) if m: info["fields"]["KEY_OPTION"] = [m.group(1), None] if "BACKDROP" in info["fields"] and opcode == "event_whenbackdropswitchesto": # For event_whenbackdropswitchesto hat block's field m = re.search(r"when backdrop switches to\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE) if m: info["fields"]["BACKDROP"] = [m.group(1), None] if "BROADCAST_OPTION" in info["fields"] and opcode == "event_whenbroadcastreceived": # For event_whenbroadcastreceived hat block's field m = re.search(r"when i receive\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE) if m: info["fields"]["BROADCAST_OPTION"] = [m.group(1), None] # Custom block specific parsing if opcode == "procedures_definition": proc_def_match = re.match(r"(?:define|procedure)\s+([a-zA-Z_][a-zA-Z0-9_ ]*)(?:\s*\((.+?)\))?", stmt_for_parse, re.IGNORECASE) if proc_def_match: proc_name = proc_def_match.group(1).strip() args_str = proc_def_match.group(2) info["procedure_name"] = proc_name info["is_custom_definition"] = True if args_str: args = [arg.strip() for arg in args_str.split(',')] info["inputs"]["PROCCONTAINER"] = { "kind": "block_prototype", "name": proc_name, "arguments": [{"name": arg, "type": "any"} for arg in args] } # For a define block, its children form its body, so push a new scope # This is handled below in the stack update logic. elif opcode == "procedures_call": call_match = re.match(r"(?:call\s+)?([a-zA-Z_][a-zA-Z0-9_ ]*)(?:\s*\((.+?)\))*\s*$", stmt_for_parse, re.IGNORECASE) if call_match: custom_block_name = call_match.group(1).strip() args_str = call_match.group(2) info["custom_block_name"] = custom_block_name if args_str: args = [arg.strip() for arg in args_str.split(',')] for idx, arg_val_str in enumerate(args): info["inputs"][f"argument_name_{idx+1}"] = parse_reporter_or_value(arg_val_str, key, pick_key, all_generated_blocks) # Add the fully constructed block to the global collection all_generated_blocks[key] = info # Update stack based on block type AFTER the block is created and linked if ntype == "hat": info["topLevel"] = True info["parent"] = None # Hat blocks have no parent top_level_script_keys.append(key) # Add to top-level scripts # This hat block *starts* a new chain. # The next block will be its child. # Push a new scope for the children of this hat block. # The `first_active_block_in_chain` for this new scope will be set # when the *first child* is encountered. stack.append((current_indent, key, None, None)) # New scope for children elif ntype == "c_block" or opcode == "procedures_definition": info["topLevel"] = False info["parent"] = current_parent_key # This C-block is the 'next' of the previous block in the current chain if last_active_block_in_chain: all_generated_blocks[last_active_block_in_chain]["next"] = key else: # This C-block is the first block in its chain (e.g., first block after a hat) # If the parent is a hat block, this block is its 'next'. if current_parent_key and all_generated_blocks[current_parent_key]["block_shape"] == "Hat Block": all_generated_blocks[current_parent_key]["next"] = key # Also update the first_active_block_in_chain for the current scope stack[-1] = (current_scope_indent, current_parent_key, key, last_active_block_in_chain) # Update the last_active_block_in_chain of the *current* scope to this C-block. stack[-1] = (current_scope_indent, current_parent_key, first_active_block_in_chain if first_active_block_in_chain else key, key) # Now, push a new scope for the C-block's/define block's substack. stack.append((current_indent, key, None, None)) # New scope for children of this C-block else: # Regular stack block or reporter/boolean block info["topLevel"] = False info["parent"] = current_parent_key # This block is the 'next' of the previous block in the current chain if last_active_block_in_chain: all_generated_blocks[last_active_block_in_chain]["next"] = key else: # This block is the first block in its chain (e.g., first block after a hat) # If the parent is a hat block, this block is its 'next'. if current_parent_key and all_generated_blocks[current_parent_key]["block_shape"] == "Hat Block": all_generated_blocks[current_parent_key]["next"] = key # Also update the first_active_block_in_chain for the current scope stack[-1] = (current_scope_indent, current_parent_key, key, last_active_block_in_chain) # Update the last block in the current chain. stack[-1] = (current_scope_indent, current_parent_key, first_active_block_in_chain if first_active_block_in_chain else key, key) i += 1 # Move to the next line # Final pass to link substacks and ensure last blocks have next: None # This loop now also handles setting the 'next' for hat blocks if they have children. while len(stack) > 1: # Keep the initial sentinel popped_indent, popped_parent_key, first_substack_block, last_substack_block = stack.pop() if last_substack_block: all_generated_blocks[last_substack_block]["next"] = None if popped_parent_key: parent_block = all_generated_blocks[popped_parent_key] # If the parent is a Hat block and this is its first child, set its 'next' if parent_block["block_shape"] == "Hat Block" and parent_block["next"] is None: parent_block["next"] = first_substack_block # Link the substack to its parent if it's a C-block or procedure definition if parent_block["block_shape"] == "C-Block" or parent_block["op_code"] == "procedures_definition": if parent_block["op_code"] == "control_if_else" and \ "SUBSTACK" in parent_block["inputs"] and \ parent_block["inputs"]["SUBSTACK"][1] is not None and \ "SUBSTACK2" not in parent_block["inputs"]: parent_block["inputs"]["SUBSTACK2"] = [2, first_substack_block] if first_substack_block else [2, None] else: parent_block["inputs"]["SUBSTACK"] = [2, first_substack_block] if first_substack_block else [2, None] print(f"[ALL OPCODE BLCOKS KEY 2]: {all_generated_blocks}") # Construct the final flow output based on the collected top-level keys # Recursively build the block structure for each top-level script def build_script_flow(current_block_key, visited=None): if visited is None: visited = set() script_flow = [] current_iter_key = current_block_key while current_iter_key: # Detect cyclic reference if current_iter_key in visited: script_flow.append({ "block_key": current_iter_key, "opcode": "control_stop", "type": "statement", "inputs": {}, "fields": {}, "comment": "Cycle detected, stopping recursion" }) break visited.add(current_iter_key) block = all_generated_blocks.get(current_iter_key) if not block: break # Should not happen if keys are correct output_block = { "block_key": block["id"], "opcode": block["op_code"], "type": block["block_shape"].replace(" Block", "").lower().replace("c-", "c_"), "inputs": {}, "fields": {} } # Handle all input types for inp_name, inp_val in block.get("inputs", {}).items(): if inp_name in ["SUBSTACK", "SUBSTACK2"]: if inp_val and len(inp_val) > 1 and inp_val[1] in all_generated_blocks: output_block["inputs"][inp_name] = build_script_flow(inp_val[1], visited.copy()) else: output_block["inputs"][inp_name] = [] elif inp_name == "PROCCONTAINER" and block.get("is_custom_definition"): output_block["inputs"][inp_name] = inp_val elif isinstance(inp_val, dict) and inp_val.get("kind") == "block": # Recursively build nested reporter/boolean blocks nested_block_key = inp_val["block"] if nested_block_key in all_generated_blocks: output_block["inputs"][inp_name] = build_script_flow(nested_block_key, visited.copy()) else: output_block["inputs"][inp_name] = inp_val # Keep original if not found (shouldn't happen) elif isinstance(inp_val, dict) and inp_val.get("kind") == "nested_reporter": nested_block_key = inp_val["reporter"]["block"] if nested_block_key in all_generated_blocks: output_block["inputs"][inp_name] = build_script_flow(nested_block_key, visited.copy()) else: output_block["inputs"][inp_name] = inp_val else: output_block["inputs"][inp_name] = inp_val for field_name, field_val in block.get("fields", {}).items(): output_block["fields"][field_name] = field_val if block.get("custom_block_name"): output_block["custom_block_name"] = block["custom_block_name"] if block.get("procedure_name"): output_block["procedure_name"] = block["procedure_name"] output_block["is_custom_definition"] = True script_flow.append(output_block) # Proceed to the next block in sequence next_key = block.get("next") if next_key in visited: script_flow.append({ "block_key": next_key, "opcode": "control_stop", "type": "statement", "inputs": {}, "fields": {}, "comment": "Cycle detected in 'next' pointer" }) break current_iter_key = next_key return script_flow final_flow_output = [] for key in top_level_script_keys: final_flow_output.extend(build_script_flow(key)) return {"flow": final_flow_output} # Example input with opcodes for the initial generation # Example input with opcodes for the initial generation initial_opcode_counts = [ {"opcode":"event_whenflagclicked","count":1}, {"opcode":"motion_gotoxy","count":1}, {"opcode":"motion_glidesecstoxy","count":1}, {"opcode":"motion_xposition","count":3}, # Used multiple times in conditions {"opcode":"motion_setx","count":1}, {"opcode":"control_forever","count":1}, {"opcode":"control_if","count":2}, # Two if blocks {"opcode":"control_stop","count":2}, # stop all v, stop this script v {"opcode":"operator_lt","count":1}, # Used in condition {"opcode":"sensing_touchingobject","count":2}, # Used in condition, and for if on edge, bounce {"opcode":"sensing_touchingobjectmenu","count":2}, # Menu for touchingobject {"opcode":"event_broadcast","count":2}, # broadcast [Game Over v], broadcast [jump v] {"opcode":"data_setvariableto","count":3}, # set [score v] to (1), set [speed v] to (1), set [other sprite X v] to ( (x position) of [Sprite2 v] ) {"opcode":"data_showvariable","count":2}, # show variable [score v], show variable [speed v] {"opcode":"operator_add","count":2}, # For set [var] to ((var) + (val)), (number 1) + (number 2) {"opcode":"data_variable","count":5}, # For variable reporters like (score) or [score v] {"opcode":"looks_sayforsecs","count":2}, # For "say [Hello!] for (2) seconds", say [You win!] for (2) seconds {"opcode":"looks_say","count":2}, # For "say [Hello! v]", say [Welcome to my game! v] {"opcode":"motion_movesteps","count":3}, # For "move (10) steps" {"opcode":"control_wait","count":3}, # For "wait (0.1) seconds", wait (0.5) seconds, wait (1) seconds {"opcode":"motion_changeyby","count":2}, # For "change y by (10)" {"opcode":"motion_pointindirection","count":1}, # For "point in direction (90)" {"opcode":"event_whenkeypressed","count":3}, # For "when [space v] key pressed", when [up arrow v] key pressed, when [right arrow v] key pressed, when [left arrow v] key pressed {"opcode":"control_repeat","count":2}, # For "repeat (10)" {"opcode":"event_whenthisspriteclicked","count":2}, # For "when this sprite clicked" {"opcode":"looks_costumenumbername","count":1}, # For "(costume [name v])" {"opcode":"operator_join","count":3}, # For "join [Hello ] (answer)", join (length of [shopping list v]) [ items in the list.], join [Hello, ] (username) {"opcode":"sensing_answer","count":1}, # For "(answer)" {"opcode":"looks_hide","count":2}, # For "hide" {"opcode":"control_create_clone_of","count":2}, # For "create clone of [myself v]" {"opcode":"control_start_as_clone","count":2}, # For "when I start as a clone" {"opcode":"operator_random","count":1}, # For "pick random -240 to 240" {"opcode":"motion_ifonedgebounce","count":2}, # For "if on edge, bounce" {"opcode":"operator_gt","count":2}, # For "if <(score) > (10)> then", if <(item # of [Dog] in [myList v])> (0)> then {"opcode":"control_if_else","count":1}, # For "if <(score) > (10)> then else" {"opcode":"sound_play","count":2}, # Changed from sound_start to sound_play {"opcode":"sensing_loudness","count":2}, # For "(loudness)" {"opcode":"event_whengreaterthan","count":1}, # For "when [loudness v] > (70)" {"opcode":"control_repeat_until","count":1}, # For "repeat until " {"opcode":"looks_cleargraphiceffects","count":1}, # For "clear graphic effects" {"opcode":"looks_changeeffectby","count":2}, # For "change [color v] effect by (50)", change [fisheye v] effect by (5) {"opcode":"looks_seteffectto","count":1}, # For "set [ghost v] effect to (75)" {"opcode":"looks_setsizeto","count":1}, # For "set size to (50) %" {"opcode":"looks_changesizeby","count":1}, # For "change size by (5)" {"opcode":"looks_nextcostume","count":2}, # For "next costume" {"opcode":"looks_switchbackdroptowait","count":1}, # For "switch backdrop to [game over v] and wait" {"opcode":"looks_nextbackdrop","count":1}, # For "next backdrop" {"opcode":"sound_playuntildone","count":2}, # For "play sound [fanfare v] until done" {"opcode":"sound_stopallsounds","count":2}, # For "stop all sounds" {"opcode":"sound_changevolumeby","count":1}, # For "change volume by (-5)" {"opcode":"sound_setvolumeto","count":1}, # For "set volume to (50) %" {"opcode":"sensing_resettimer","count":2}, # For "reset timer" {"opcode":"sensing_setdragmode","count":2}, # For "set drag mode [not draggable v]" {"opcode":"data_addtolist","count":1}, # For "add [apple] to [shopping list v]" {"opcode":"data_deleteoflist","count":1}, # For "delete (all) of [my list v]" {"opcode":"data_insertatlist","count":1}, # For "insert [orange] at (2) of [fruits v]" {"opcode":"data_replaceitemoflist","count":1}, # For "replace item (1) of [colors v] with [blue]" {"opcode":"data_listcontainsitem","count":1}, # For "<[inventory v] contains [key]?>" {"opcode":"data_itemoflist","count":1}, # For "(item (2) of [myList v])" {"opcode":"data_lengthoflist","count":2}, # For "(length of [shopping list v])" {"opcode":"data_itemnumoflist","count":1}, # For "(item # of [Dog] in [myList v])" {"opcode":"sensing_touchingcolor","count":1}, # For "" {"opcode":"sensing_coloristouchingcolor","count":1}, # For "" {"opcode":"operator_and","count":1}, # For "< and >" {"opcode":"operator_or","count":1}, # For "< or >" {"opcode":"operator_not","count":1}, # For ">" {"opcode":"operator_contains","count":1}, # For "<[answer] contains [yes]?>" {"opcode":"procedures_call","count":1}, # For "jump (50)" {"opcode":"procedures_definition","count":1}, # For "define jump (height)" {"opcode":"sensing_of","count":1}, # For "(x position) of [Sprite2 v]" {"opcode":"sensing_current","count":1}, # For "(current [hour v])" {"opcode":"sensing_mousex","count":1}, # For "(mouse x)" {"opcode":"sensing_mousey","count":1}, # For "(mouse y)" {"opcode":"operator_subtract","count":1}, # For "((10) - (4))" {"opcode":"operator_multiply","count":1}, # For "(6) * (7)" {"opcode":"operator_divide","count":1}, # For "((20) / (5))" {"opcode":"data_list","count":1}, # For "[my list v]" {"opcode":"looks_gotofrontback","count":1}, # For "go to [front v] layer" {"opcode":"looks_goforwardbackwardlayers","count":1}, # For "go [forward v] (1) layers" {"opcode":"sound_sounds_menu","count":2}, # For sound menus {"opcode":"motion_goto_menu","count":1}, # For motion_goto menu {"opcode":"motion_glideto_menu","count":1}, # For motion_glideto menu {"opcode":"motion_pointtowards_menu","count":1}, # For motion_pointtowards menu {"opcode":"sensing_keyoptions","count":1}, # For sensing_keypressed menu {"opcode":"sensing_of_object_menu","count":1}, # For sensing_of menu {"opcode":"control_create_clone_of_menu","count":1}, # For control_create_clone_of menu {"opcode":"looks_costume","count":1}, # For looks_switchcostumeto menu {"opcode":"looks_backdrops","count":1}, # For looks_switchbackdropto menu ] initial_opcode_counts = [ {"opcode":"event_whenflagclicked","count":1}, {"opcode":"motion_gotoxy","count":1}, {"opcode":"motion_xposition","count":1}, {"opcode":"motion_setx","count":1}, {"opcode":"control_forever","count":1}, {"opcode":"control_if","count":2}, {"opcode":"control_stop","count":1}, {"opcode":"operator_lt","count":1}, {"opcode":"sensing_touchingobject","count":1}, {"opcode":"sensing_touchingobjectmenu","count":1}, {"opcode":"event_broadcast","count":1}, {"opcode":"data_setvariableto","count":2}, {"opcode":"data_showvariable","count":2}, ] # Generate the initial blocks and get the opcode_occurrences generated_output_json, initial_opcode_occurrences = generate_blocks_from_opcodes(initial_opcode_counts, all_block_definitions) # Example pseudo-code inputs from the JSON files pseudo_code_examples = [ # From motion_block.json """ when green flag clicked go to x: (0) y: (0) point in direction (90) move (50) steps end """, """ when [right arrow v] key pressed turn right (15) degrees end """, """ when this sprite clicked go to [mouse-pointer v] """, """ when green flag clicked glide (2) secs to x: (150) y: (-100) glide (2) secs to x: (-150) y: (100) end """, """ when green flag clicked forever point towards [mouse-pointer v] move (5) steps end end """, """ when [right arrow v] key pressed change x by (10) end """, """ when green flag clicked set x to (0) set y to (0) end """, """ when [up arrow v] key pressed change y by (10) end """, """ when green flag clicked forever move (10) steps if on edge, bounce end end """, """ when green flag clicked set rotation style [left-right v] forever move (10) steps if on edge, bounce end end """, # From looks_block.json """ when green flag clicked say [Grr] for (3) seconds say [Have you seen my honey? v] for (3) seconds end """, """ when green flag clicked say [Welcome to my game! v] wait (2) seconds say [] end """, """ when this sprite clicked think [What should I do? v] for (2) seconds end """, """ when I receive [correct answer v] think [That's right! v] wait (1) seconds think [good v] end """, """ when I receive [explosion v] repeat (5) next costume end hide end """, """ when green flag clicked forever next costume wait (0.2) seconds end end """, """ when green flag clicked switch backdrop to [start screen v] end """, """ broadcast [game over v] switch backdrop to [game over v] and wait stop [all v] end """, """ when [space v] key pressed next backdrop end """, """ when green flag clicked repeat (10) change size by (5) wait (0.1) seconds end end """, """ when green flag clicked set size to (50) % wait (1) seconds set size to (100) % end """, """ when green flag clicked forever change [color v] effect by (5) wait (0.1) seconds end end """, """ when green flag clicked set [ghost v] effect to (75) end """, """ when green flag clicked change [color v] effect by (50) wait (2) seconds clear graphic effects end """, """ when green flag clicked hide when I receive [start game v] show end """, """ when green flag clicked hide end """, """ when green flag clicked go to [front v] layer end """, """ when this sprite clicked go [forward v] (1) layers end """, """ say join [I am costume ] (costume [name v]) """, """ say join [Current backdrop: ] (backdrop [name v]) for (2) seconds """, """ set size to ( (size) + (10) ) """, # From sound_block.json """ when backdrop switches to [winning screen v] play sound [fanfare v] until done say [You won!] for (2) seconds end """, """ forever play sound [Music v] until done end """, """ when this sprite clicked start sound [Pop v] change [score v] by (1) end """, """ when I receive [game over v] stop all sounds end """, """ when [down arrow v] key pressed change volume by (-5) end """, """ when green flag clicked set volume to (50) % end """, """ say join [Current volume: ] ([volume v]) """, # From event_block.json """ when green flag clicked go to x: (0) y: (0) say [Hello!] for (2) seconds end """, """ when [space v] key pressed repeat (10) change y by (10) wait (0.1) seconds change y by (-10) end end """, """ when [right arrow v] key pressed point in direction (90) move (10) steps end """, """ when this sprite clicked say [Ouch!] for (1) seconds change [score v] by (-1) end """, """ when backdrop switches to [game over v] stop [all v] end """, """ when [loudness v] > (70) start sound [scream v] end """, """ when I receive [start game v] show go to x: (0) y: (0) end """, """ when I receive [game over v] set score to 0 stop [all v] end """, """ if then broadcast [jump v] end """, """ broadcast [initialize sprites v] and wait say [Game Started!] for (2) seconds """, # From control_block.json """ say [Hello!] for (1) seconds wait (0.5) seconds say [Goodbye!] for (1) seconds """, """ when green flag clicked repeat (10) move (10) steps wait (0.1) seconds end end """, """ when green flag clicked forever move (5) steps if on edge, bounce end end """, """ forever if then stop [this script v] end end """, """ if <([score v]) > (10)> then say [You win!] for (2) seconds else say [Keep trying!] for (2) seconds end """, """ repeat until move (5) steps end """, """ if <(health) = (0)> then stop [all v] end """, """ when I start as a clone wait until delete this clone end """, """ when I start as a clone go to x: (pick random -240 to 240) y: (pick random -180 to 180) show forever move (10) steps if on edge, bounce end end """, """ when I start as a clone wait (5) seconds delete this clone end """, """ when green flag clicked hide forever create clone of [myself v] wait (1) seconds end """, # From data_block.json """ when green flag clicked set [score v] to (0) set [player name v] to [Guest] end """, """ when this sprite clicked change [score v] by (1) end """, """ when green flag clicked add [apple] to [shopping list v] add [banana] to [shopping list v] end """, """ when green flag clicked delete (all) of [my list v] end """, """ insert [orange] at (2) of [fruits v] """, """ replace item (1) of [colors v] with [blue] """, """ when green flag clicked show variable [score v] end """, """ when I receive [game over v] hide variable [score v] end """, """ when green flag clicked show list [shopping list v] end """, """ when I receive [game over v] hide list [shopping list v] end """, """ say ([score v]) for (2) seconds """, """ say ([my list v]) """, """ say (item (2) of [myList v]) for 2 seconds """, """ say join (length of [shopping list v]) [ items in the list.] """, """ if <(item # of [Dog] in [myList v])> (0)> then say join [Dog found at position ] (item # of [Dog] in [my list v]) end """, # From reporter_blocks.json (some already covered by other categories) """ when green flag clicked say (x position) for (2) seconds end """, """ set [worms v] to (y position) """, """ when green flag clicked say (direction) for (2) seconds end """, """ say join [I am costume ] (costume [name v]) """, """ set size to ( (size) + (10) ) """, """ say join [Current backdrop: ] (backdrop [name v]) for (2) seconds """, """ say join [Current volume: ] (volume) """, """ if <(distance to [Sprite2 v]) < (50)> then say [Too close!] end """, """ ask [What is your name?] and wait say join [Hello ] (answer) """, """ go to x: (mouse x) y: (mouse y) """, """ if <(mouse y) < (0)> then say [Below center] end """, """ when green flag clicked forever if <(loudness) > (30)> then start sound [pop v] end """, """ when green flag clicked reset timer wait (5) seconds say join [Time elapsed: ] (timer) end """, """ set [other sprite X v] to ( (x position) of [Sprite2 v] ) """, """ say join [The current hour is ] (current [hour v]) """, """ say join [Days passed: ] (days since 2000) """, """ say join [Hello, ] (username) """, """ set [total v] to ( (number 1) + (number 2) ) """, """ set [difference v] to ( (number 1) - (number 2) ) """, """ set [area v] to ( (length) * (width) ) """, """ set [average v] to ( (total score) / (number of students) ) """, """ go to x: (pick random -240 to 240) y: (pick random -180 to 180) """, """ say (join [Hello ][World!]) """, """ say (letter (1) of [apple]) """, """ say (length of [banana]) """, """ if <([number v] mod (2) = (0))> then say [Even number] end """, """ set [rounded score v] to (round (score)) """, """ set [distance v] to ([sqrt v] of (((x position) * (x position)) + ((y position) * (y position)))) """, # From boolean_blocks.json (conditions already covered by parse_condition) """ if <(score) < (10)> then say [Keep trying!] end """, """ if <(answer) = (5)> then say [Correct!] end """, """ if <([health v]) > (0)> then move (10) steps else stop [all v] end """, """ if < and > then say [You're clicking me!] end """, """ if < or > then change x by (-10) end """, """ if > then say [I'm safe!] end """, """ if <[answer] contains [yes]?> then say [Great!] end """, """ if then broadcast [Game Over v] end """, """ if then bounce off edge end """, """ if then change [health v] by (-1) end """, """ if then say [Collision!] end """, """ forever if then broadcast [shoot v] end end """, """ if then go to mouse-pointer end """, """ if <[inventory v] contains [key]?> then say [You have the key!] end """, # Custom block example """ define jump (height) change y by (height) wait (0.5) seconds change y by (0 - (height)) end when green flag clicked jump (50) end """ ] # pseudo_code_examples = [""" # when green flag clicked # go to x: (240) y: (-135) # set [score v] to (1) # set [speed v] to (1) # show variable [score v] # show variable [speed v] # forever # if <((x position)) < (-235)> then # set x to (240) # end # if then # broadcast [Game Over v] # stop [all v] # end # end # end # """,] txt="" trace="" # Process each example and print the plan for i, pseudo_code_input in enumerate(pseudo_code_examples): print(f"\n--- Processing Example {i+1} ---") print(pseudo_code_input.strip()) try: # Regenerate blocks and opcode_occurrences for each run to ensure fresh keys # This is important because pick_key uses a defaultdict that persists state. generated_output_json, initial_opcode_occurrences = generate_blocks_from_opcodes(initial_opcode_counts, all_block_definitions) plan = generate_plan(generated_output_json, initial_opcode_occurrences, pseudo_code_input) print(json.dumps(plan, indent=2)) txt += str(plan) + " \n" except Exception as e: print(f"Error processing example: {e}") import traceback #traceback.print_exc() trace += str(e) +" "+str(pseudo_code_input)+" \n" with open("all_analysis.txt", "w", encoding="utf-8") as f: f.write(txt) with open("all_analysis_trace.txt", "w", encoding="utf-8") as f: f.write(trace)