From d31c035a1d96e3917f1690a84e68e23d3717a70d Mon Sep 17 00:00:00 2001 From: Benjamin Montgomery Date: Fri, 12 Jul 2024 13:32:06 -0400 Subject: [PATCH 1/4] feat: allow for parsing data blocks --- src/conversion/scratch-conversion-data.mjs | 147 +++++++++++++++++++++ src/conversion/scratch-conversion.mjs | 7 + 2 files changed, 154 insertions(+) create mode 100644 src/conversion/scratch-conversion-data.mjs diff --git a/src/conversion/scratch-conversion-data.mjs b/src/conversion/scratch-conversion-data.mjs new file mode 100644 index 00000000000..f2e67b42f8f --- /dev/null +++ b/src/conversion/scratch-conversion-data.mjs @@ -0,0 +1,147 @@ +import PatchTargetThread from "./patch-target-thread.mjs"; +import ScratchBlock from "./scratch-block.mjs"; + +import { indentLines, processInputs } from "./scratch-conversion-helper.mjs"; + +export default class ScratchConversionData { + /** + * + * @param {Object.} blocks + * @param {string} blockId + * @param {Object. Date: Fri, 12 Jul 2024 13:40:14 -0400 Subject: [PATCH 2/4] feat: parse global and local variables when converting --- src/conversion/scratch-conversion.mjs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/conversion/scratch-conversion.mjs b/src/conversion/scratch-conversion.mjs index 812fe45cc3e..bef03e91377 100644 --- a/src/conversion/scratch-conversion.mjs +++ b/src/conversion/scratch-conversion.mjs @@ -83,6 +83,8 @@ export default class ScratchConverter { const jsonDataString = await zip.files["project.json"].async("text").then((text) => text); const vmState = JSON.parse(jsonDataString); + const globalVariables = []; + // This function will convert each target's blocks and local variables into Patch code. // Then, it will remove the blocks from the JSON (not strictly necessary) and handle backgrounds and other // things that Patch and Scratch store differently. Also, everything will be moved to being a child of a json @@ -99,6 +101,14 @@ export default class ScratchConverter { // remover however. for (let i = 0; i < vmState.targets.length; i++) { vmState.targets[i].blocks = {}; + vmState.targets[i].variables.forEach(variable => { + if (vmState.targets[i].isStage) { + // In Scratch, global variables are actually stored as sprite variables on the stage. + globalVariables.push({name: variable[0], value: variable[1]}); + } else { + globalVariables.push({name: `${vmState.targets[i].name}_${variable[0]}`, value: variable[1]}); + } + }); vmState.targets[i].variables = {}; vmState.targets[i].lists = {}; vmState.targets[i].broadcasts = {}; From ef8f057587f8a2b6096179ffb024e4e9d62bf870 Mon Sep 17 00:00:00 2001 From: Benjamin Montgomery Date: Fri, 12 Jul 2024 16:25:20 -0400 Subject: [PATCH 3/4] fix: significant refactor of scratch-to-patch conversion code --- src/conversion/conversion-layer.mjs | 818 +++++++++--------- src/conversion/scratch-conversion-control.mjs | 39 +- src/conversion/scratch-conversion-data.mjs | 52 +- src/conversion/scratch-conversion-helper.mjs | 143 ++- .../scratch-conversion-operator.mjs | 52 +- src/conversion/scratch-conversion.mjs | 181 +--- 6 files changed, 614 insertions(+), 671 deletions(-) diff --git a/src/conversion/conversion-layer.mjs b/src/conversion/conversion-layer.mjs index 6f3a88ffe19..1ffaf556479 100644 --- a/src/conversion/conversion-layer.mjs +++ b/src/conversion/conversion-layer.mjs @@ -1,417 +1,415 @@ -export default class ConversionLayer { - static patchApi = { - // Motion blocks: - move: { - opcode: "motion_movesteps", - parameters: ["STEPS"], - }, - goToXY: { - opcode: "motion_gotoxy", - parameters: ["X", "Y"], - }, - goTo: { - opcode: "motion_goto", - parameters: ["TO"], - }, - turnRight: { - opcode: "motion_turnright", - parameters: ["DEGREES"], - }, - turnLeft: { - opcode: "motion_turnleft", - parameters: ["DEGREES"], - }, - pointInDirection: { - opcode: "motion_pointindirection", - parameters: ["DIRECTION"], - }, - pointTowards: { - opcode: "motion_pointtowards", - parameters: ["TOWARDS"], - }, - glide: { - opcode: "motion_glidesecstoxy", - parameters: ["SECS", "X", "Y"], - }, - glideTo: { - opcode: "motion_glideto", - parameters: ["SECS", "TO"], - }, - ifOnEdgeBounce: { - opcode: "motion_ifonedgebounce", - parameters: [], - }, - setRotationStyle: { - opcode: "motion_setrotationstyle", - parameters: ["STYLE"], - }, - changeX: { - opcode: "motion_changexby", - parameters: ["DX"], - }, - setX: { - opcode: "motion_setx", - parameters: ["X"], - }, - changeY: { - opcode: "motion_changeyby", - parameters: ["DY"], - }, - setY: { - opcode: "motion_sety", - parameters: ["Y"], - }, - getX: { - opcode: "motion_xposition", - parameters: [], - }, - getY: { - opcode: "motion_yposition", - parameters: [], - }, - getDirection: { - opcode: "motion_direction", - parameters: [], - }, - goToMenu: { - opcode: "motion_goto_menu", - parameters: ["TO"], - returnParametersInstead: ["TO"], - }, - glideToMenu: { - opcode: "motion_glideto_menu", - parameters: ["TO"], - returnParametersInstead: ["TO"], - }, - pointTowardsMenu: { - opcode: "motion_pointtowards_menu", - parameters: ["TOWARDS"], - returnParametersInstead: ["TOWARDS"], - }, +export default { + // Motion blocks: + move: { + opcode: "motion_movesteps", + parameters: ["STEPS"], + }, + goToXY: { + opcode: "motion_gotoxy", + parameters: ["X", "Y"], + }, + goTo: { + opcode: "motion_goto", + parameters: ["TO"], + }, + turnRight: { + opcode: "motion_turnright", + parameters: ["DEGREES"], + }, + turnLeft: { + opcode: "motion_turnleft", + parameters: ["DEGREES"], + }, + pointInDirection: { + opcode: "motion_pointindirection", + parameters: ["DIRECTION"], + }, + pointTowards: { + opcode: "motion_pointtowards", + parameters: ["TOWARDS"], + }, + glide: { + opcode: "motion_glidesecstoxy", + parameters: ["SECS", "X", "Y"], + }, + glideTo: { + opcode: "motion_glideto", + parameters: ["SECS", "TO"], + }, + ifOnEdgeBounce: { + opcode: "motion_ifonedgebounce", + parameters: [], + }, + setRotationStyle: { + opcode: "motion_setrotationstyle", + parameters: ["STYLE"], + }, + changeX: { + opcode: "motion_changexby", + parameters: ["DX"], + }, + setX: { + opcode: "motion_setx", + parameters: ["X"], + }, + changeY: { + opcode: "motion_changeyby", + parameters: ["DY"], + }, + setY: { + opcode: "motion_sety", + parameters: ["Y"], + }, + getX: { + opcode: "motion_xposition", + parameters: [], + }, + getY: { + opcode: "motion_yposition", + parameters: [], + }, + getDirection: { + opcode: "motion_direction", + parameters: [], + }, + goToMenu: { + opcode: "motion_goto_menu", + parameters: ["TO"], + returnParametersInstead: ["TO"], + }, + glideToMenu: { + opcode: "motion_glideto_menu", + parameters: ["TO"], + returnParametersInstead: ["TO"], + }, + pointTowardsMenu: { + opcode: "motion_pointtowards_menu", + parameters: ["TOWARDS"], + returnParametersInstead: ["TOWARDS"], + }, - // Looks blocks: - say: { - opcode: "looks_say", - parameters: ["MESSAGE"], - }, - sayFor: { - opcode: "looks_sayforsecs", - parameters: ["MESSAGE", "SECS"], - }, - think: { - opcode: "looks_think", - parameters: ["MESSAGE"], - }, - thinkFor: { - opcode: "looks_thinkforsecs", - parameters: ["MESSAGE", "SECS"], - }, - show: { - opcode: "looks_show", - parameters: [], - }, - hide: { - opcode: "looks_hide", - parameters: [], - }, - setCostumeTo: { - opcode: "looks_switchcostumeto", - parameters: ["COSTUME"], - }, - setBackdropTo: { - opcode: "looks_switchbackdropto", - parameters: ["BACKDROP"], - }, - setBackdropToAndWait: { - opcode: "looks_switchbackdroptoandwait", - parameters: ["BACKDROP"], - }, - nextCostume: { - opcode: "looks_nextcostume", - parameters: [], - }, - nextBackdrop: { - opcode: "looks_nextbackdrop", - parameters: [], - }, - changeGraphicEffectBy: { - opcode: "looks_changeeffectby", - parameters: ["EFFECT", "CHANGE"], - }, - setGraphicEffectTo: { - opcode: "looks_seteffectto", - parameters: ["EFFECT", "VALUE"], - }, - clearGraphicEffects: { - opcode: "looks_cleargraphiceffects", - parameters: [], - }, - changeSizeBy: { - opcode: "looks_changesizeby", - parameters: ["CHANGE"], - }, - setSizeTo: { - opcode: "looks_setsizeto", - parameters: ["SIZE"], - }, - setLayerTo: { - opcode: "looks_gotofrontback", - parameters: ["FRONT_BACK"], - }, - changeLayerBy: { - opcode: "looks_goforwardbackwardlayers", - parameters: ["FORWARD_BACKWARD", "NUM"], - }, - getSize: { - opcode: "looks_size", - parameters: [], - }, - getCostume: { - opcode: "looks_costumenumbername", - parameters: [], - }, - getBackdrop: { - opcode: "looks_backdropnumbername", - parameters: [], - }, - costume: { - opcode: "looks_costume", - parameters: ["COSTUME"], - returnParametersInstead: ["COSTUME"], - }, - backdrops: { - opcode: "looks_backdrops", - parameters: ["BACKDROP"], - returnParametersInstead: ["BACKDROP"], - }, + // Looks blocks: + say: { + opcode: "looks_say", + parameters: ["MESSAGE"], + }, + sayFor: { + opcode: "looks_sayforsecs", + parameters: ["MESSAGE", "SECS"], + }, + think: { + opcode: "looks_think", + parameters: ["MESSAGE"], + }, + thinkFor: { + opcode: "looks_thinkforsecs", + parameters: ["MESSAGE", "SECS"], + }, + show: { + opcode: "looks_show", + parameters: [], + }, + hide: { + opcode: "looks_hide", + parameters: [], + }, + setCostumeTo: { + opcode: "looks_switchcostumeto", + parameters: ["COSTUME"], + }, + setBackdropTo: { + opcode: "looks_switchbackdropto", + parameters: ["BACKDROP"], + }, + setBackdropToAndWait: { + opcode: "looks_switchbackdroptoandwait", + parameters: ["BACKDROP"], + }, + nextCostume: { + opcode: "looks_nextcostume", + parameters: [], + }, + nextBackdrop: { + opcode: "looks_nextbackdrop", + parameters: [], + }, + changeGraphicEffectBy: { + opcode: "looks_changeeffectby", + parameters: ["EFFECT", "CHANGE"], + }, + setGraphicEffectTo: { + opcode: "looks_seteffectto", + parameters: ["EFFECT", "VALUE"], + }, + clearGraphicEffects: { + opcode: "looks_cleargraphiceffects", + parameters: [], + }, + changeSizeBy: { + opcode: "looks_changesizeby", + parameters: ["CHANGE"], + }, + setSizeTo: { + opcode: "looks_setsizeto", + parameters: ["SIZE"], + }, + setLayerTo: { + opcode: "looks_gotofrontback", + parameters: ["FRONT_BACK"], + }, + changeLayerBy: { + opcode: "looks_goforwardbackwardlayers", + parameters: ["FORWARD_BACKWARD", "NUM"], + }, + getSize: { + opcode: "looks_size", + parameters: [], + }, + getCostume: { + opcode: "looks_costumenumbername", + parameters: [], + }, + getBackdrop: { + opcode: "looks_backdropnumbername", + parameters: [], + }, + costume: { + opcode: "looks_costume", + parameters: ["COSTUME"], + returnParametersInstead: ["COSTUME"], + }, + backdrops: { + opcode: "looks_backdrops", + parameters: ["BACKDROP"], + returnParametersInstead: ["BACKDROP"], + }, - // Sound blocks: - playSound: { - opcode: "sound_play", - parameters: ["SOUND_MENU"], - }, - playSoundUntilDone: { - opcode: "sound_playuntildone", - parameters: ["SOUND_MENU"], - }, - stopAllSounds: { - opcode: "sound_stopallsounds", - parameters: [], - }, - setSoundEffectTo: { - opcode: "sound_seteffectto", - parameters: ["EFFECT", "VALUE"], - }, - changeSoundEffectBy: { - opcode: "sound_changeeffectby", - parameters: ["EFFECT", "VALUE"], - }, - clearSoundEffects: { - opcode: "sound_cleareffects", - parameters: [], - }, - setVolumeTo: { - opcode: "sound_setvolumeto", - parameters: ["VOLUME"], - }, - changeVolumeBy: { - opcode: "sound_changevolumeby", - parameters: ["VOLUME"], - }, - getVolume: { - opcode: "sound_volume", - parameters: [], - }, - soundsMenu: { - opcode: "sound_sounds_menu", - parameters: ["SOUND_MENU"], - returnParametersInstead: ["SOUND_MENU"], - }, + // Sound blocks: + playSound: { + opcode: "sound_play", + parameters: ["SOUND_MENU"], + }, + playSoundUntilDone: { + opcode: "sound_playuntildone", + parameters: ["SOUND_MENU"], + }, + stopAllSounds: { + opcode: "sound_stopallsounds", + parameters: [], + }, + setSoundEffectTo: { + opcode: "sound_seteffectto", + parameters: ["EFFECT", "VALUE"], + }, + changeSoundEffectBy: { + opcode: "sound_changeeffectby", + parameters: ["EFFECT", "VALUE"], + }, + clearSoundEffects: { + opcode: "sound_cleareffects", + parameters: [], + }, + setVolumeTo: { + opcode: "sound_setvolumeto", + parameters: ["VOLUME"], + }, + changeVolumeBy: { + opcode: "sound_changevolumeby", + parameters: ["VOLUME"], + }, + getVolume: { + opcode: "sound_volume", + parameters: [], + }, + soundsMenu: { + opcode: "sound_sounds_menu", + parameters: ["SOUND_MENU"], + returnParametersInstead: ["SOUND_MENU"], + }, - // Broadcast blocks: - // The way these work in Scratch is that you have to create each broadcast, then it becomes an option in the dropdown - // on the blocks. However, in Patch, it will just accept any string on both the send and recieve. For this reason, - // broadcasts are removed from each Scratch target in the conversion from Scratch to Patch. - broadcast: { - opcode: "event_broadcast", - parameters: ["BROADCAST_INPUT"], - }, - broadcastAndWait: { - opcode: "event_broadcastandwait", - parameters: ["BROADCAST_INPUT"], - }, + // Broadcast blocks: + // The way these work in Scratch is that you have to create each broadcast, then it becomes an option in the dropdown + // on the blocks. However, in Patch, it will just accept any string on both the send and recieve. For this reason, + // broadcasts are removed from each Scratch target in the conversion from Scratch to Patch. + broadcast: { + opcode: "event_broadcast", + parameters: ["BROADCAST_INPUT"], + }, + broadcastAndWait: { + opcode: "event_broadcastandwait", + parameters: ["BROADCAST_INPUT"], + }, - // Sensing blocks: - isTouching: { - opcode: "sensing_touchingobject", - parameters: ["TOUCHINGOBJECTMENU"], - }, - isTouchingColor: { - opcode: "sensing_touchingcolor", - parameters: ["COLOR"], - }, - isColorTouchingColor: { - opcode: "sensing_coloristouchingcolor", - parameters: ["COLOR", "COLOR2"], - }, - distanceTo: { - opcode: "sensing_distanceto", - parameters: ["DISTANCETOMENU"], - }, - getTimer: { - opcode: "sensing_timer", - parameters: [], - }, - resetTimer: { - opcode: "sensing_resettimer", - parameters: [], - }, - getAttributeOf: { - opcode: "sensing_of", - parameters: ["OBJECT", "PROPERTY"], - }, - getMouseX: { - opcode: "sensing_mousex", - parameters: [], - }, - getMouseY: { - opcode: "sensing_mousey", - parameters: [], - }, - isMouseDown: { - opcode: "sensing_mousedown", - parameters: [], - }, - // setDragMode: { - // opcode: "sensing_setdragmode", - // parameters: ["degrees"], - // }, - isKeyPressed: { - opcode: "sensing_keypressed", - parameters: ["KEY_OPTION"], - }, - current: { - opcode: "sensing_current", - parameters: ["CURRENTMENU"], - }, - daysSince2000: { - opcode: "sensing_dayssince2000", - parameters: [], - }, - getLoudness: { - opcode: "sensing_loudness", - parameters: [], - }, - getUsername: { - opcode: "sensing_username", - parameters: [], - }, - ask: { - opcode: "sensing_askandwait", - parameters: ["QUESTION"], - }, - // getAnswer: { - // opcode: "sensing_answer" - // }, - getAnswer: { - opcode: "sensing_answer", - parameters: [], - returnInstead: ["_patchAnswer"], - }, - touchingObjectMenu: { - opcode: "sensing_touchingobjectmenu", - parameters: ["TOUCHINGOBJECTMENU"], - returnParametersInstead: ["TOUCHINGOBJECTMENU"] - }, - distanceToMenu: { - opcode: "sensing_distancetomenu", - parameters: ["DISTANCETOMENU"], - returnParametersInstead: ["DISTANCETOMENU"] - }, - keyOptions: { - opcode: "sensing_keyoptions", - parameters: ["KEY_OPTION"], - returnParametersInstead: ["KEY_OPTION"] - }, - getAttributeOfObjectMenu: { - opcode: "sensing_of_object_menu", - parameters: ["OBJECT"], - returnParametersInstead: ["OBJECT"] - }, + // Sensing blocks: + isTouching: { + opcode: "sensing_touchingobject", + parameters: ["TOUCHINGOBJECTMENU"], + }, + isTouchingColor: { + opcode: "sensing_touchingcolor", + parameters: ["COLOR"], + }, + isColorTouchingColor: { + opcode: "sensing_coloristouchingcolor", + parameters: ["COLOR", "COLOR2"], + }, + distanceTo: { + opcode: "sensing_distanceto", + parameters: ["DISTANCETOMENU"], + }, + getTimer: { + opcode: "sensing_timer", + parameters: [], + }, + resetTimer: { + opcode: "sensing_resettimer", + parameters: [], + }, + getAttributeOf: { + opcode: "sensing_of", + parameters: ["OBJECT", "PROPERTY"], + }, + getMouseX: { + opcode: "sensing_mousex", + parameters: [], + }, + getMouseY: { + opcode: "sensing_mousey", + parameters: [], + }, + isMouseDown: { + opcode: "sensing_mousedown", + parameters: [], + }, + // setDragMode: { + // opcode: "sensing_setdragmode", + // parameters: ["degrees"], + // }, + isKeyPressed: { + opcode: "sensing_keypressed", + parameters: ["KEY_OPTION"], + }, + current: { + opcode: "sensing_current", + parameters: ["CURRENTMENU"], + }, + daysSince2000: { + opcode: "sensing_dayssince2000", + parameters: [], + }, + getLoudness: { + opcode: "sensing_loudness", + parameters: [], + }, + getUsername: { + opcode: "sensing_username", + parameters: [], + }, + ask: { + opcode: "sensing_askandwait", + parameters: ["QUESTION"], + }, + // getAnswer: { + // opcode: "sensing_answer" + // }, + getAnswer: { + opcode: "sensing_answer", + parameters: [], + returnInstead: ["_patchAnswer"], + }, + touchingObjectMenu: { + opcode: "sensing_touchingobjectmenu", + parameters: ["TOUCHINGOBJECTMENU"], + returnParametersInstead: ["TOUCHINGOBJECTMENU"] + }, + distanceToMenu: { + opcode: "sensing_distancetomenu", + parameters: ["DISTANCETOMENU"], + returnParametersInstead: ["DISTANCETOMENU"] + }, + keyOptions: { + opcode: "sensing_keyoptions", + parameters: ["KEY_OPTION"], + returnParametersInstead: ["KEY_OPTION"] + }, + getAttributeOfObjectMenu: { + opcode: "sensing_of_object_menu", + parameters: ["OBJECT"], + returnParametersInstead: ["OBJECT"] + }, - wait: { - opcode: "control_wait", - parameters: ["DURATION"], - }, - // waitUntil: { - // opcode: "control_wait_until", - // parameters: ["condition"], - // }, - stop: { - opcode: "control_stop", - parameters: ["STOP_OPTION"], - }, - createClone: { - opcode: "control_create_clone_of", - parameters: ["CLONE_OPTION"], - }, - deleteClone: { - opcode: "control_delete_this_clone", - parameters: [], - }, - createCloneMenu: { - opcode: "control_create_clone_of_menu", - parameters: ["CLONE_OPTION"], - returnParametersInstead: ["CLONE_OPTION"], - }, + wait: { + opcode: "control_wait", + parameters: ["DURATION"], + }, + // waitUntil: { + // opcode: "control_wait_until", + // parameters: ["condition"], + // }, + stop: { + opcode: "control_stop", + parameters: ["STOP_OPTION"], + }, + createClone: { + opcode: "control_create_clone_of", + parameters: ["CLONE_OPTION"], + }, + deleteClone: { + opcode: "control_delete_this_clone", + parameters: [], + }, + createCloneMenu: { + opcode: "control_create_clone_of_menu", + parameters: ["CLONE_OPTION"], + returnParametersInstead: ["CLONE_OPTION"], + }, - erasePen: { - opcode: "pen_clear", - parameters: [], - }, - stampPen: { - opcode: "pen_stamp", - parameters: [], - }, - penDown: { - opcode: "pen_penDown", - parameters: [], - }, - penUp: { - opcode: "pen_penUp", - parameters: [], - }, - setPenColor: { - opcode: "pen_setPenColorToColor", - parameters: ["COLOR"], - }, - changePenEffect: { - opcode: "pen_changePenColorParamBy", - parameters: ["COLOR_PARAM", "VALUE"], - }, - setPenEffect: { - opcode: "pen_setPenColorParamTo", - parameters: ["COLOR_PARAM", "VALUE"], - }, - changePenSize: { - opcode: "pen_changePenSizeBy", - parameters: ["SIZE"], - }, - setPenSize: { - opcode: "pen_setPenSizeTo", - parameters: ["SIZE"], - }, - penEffectMenu: { - opcode: "pen_menu_colorParam", - // The opcode should be camelcase; this isn't a mistake (unless it isn't camelcase, in which case it shouls - // be made camelcase). - parameters: ["colorParam"], - returnParametersInstead: ["colorParam"], - }, + erasePen: { + opcode: "pen_clear", + parameters: [], + }, + stampPen: { + opcode: "pen_stamp", + parameters: [], + }, + penDown: { + opcode: "pen_penDown", + parameters: [], + }, + penUp: { + opcode: "pen_penUp", + parameters: [], + }, + setPenColor: { + opcode: "pen_setPenColorToColor", + parameters: ["COLOR"], + }, + changePenEffect: { + opcode: "pen_changePenColorParamBy", + parameters: ["COLOR_PARAM", "VALUE"], + }, + setPenEffect: { + opcode: "pen_setPenColorParamTo", + parameters: ["COLOR_PARAM", "VALUE"], + }, + changePenSize: { + opcode: "pen_changePenSizeBy", + parameters: ["SIZE"], + }, + setPenSize: { + opcode: "pen_setPenSizeTo", + parameters: ["SIZE"], + }, + penEffectMenu: { + opcode: "pen_menu_colorParam", + // The opcode should be camelcase; this isn't a mistake (unless it isn't camelcase, in which case it shouls + // be made camelcase). + parameters: ["colorParam"], + returnParametersInstead: ["colorParam"], + }, - endThread: { - opcode: "core_endthread", - parameters: [], - }, - }; -} \ No newline at end of file + endThread: { + opcode: "core_endthread", + parameters: [], + }, +}; diff --git a/src/conversion/scratch-conversion-control.mjs b/src/conversion/scratch-conversion-control.mjs index d76691b19af..549f5985b5c 100644 --- a/src/conversion/scratch-conversion-control.mjs +++ b/src/conversion/scratch-conversion-control.mjs @@ -1,23 +1,17 @@ import PatchTargetThread from "./patch-target-thread.mjs"; import ScratchBlock from "./scratch-block.mjs"; -import { indentLines, processInputs } from "./scratch-conversion-helper.mjs"; +import { indentLines, processInputs, convertBlocksPart } from "./scratch-conversion-helper.mjs"; export default class ScratchConversionControl { /** * - * @param {Object.} blocks + * @param {*} target * @param {string} blockId - * @param {Object.} blocks + * @param {*} target * @param {string} blockId - * @param {Object. 0) { + if (blocks[hatId].opcode === "event_whenkeypressed") { + // eslint-disable-next-line prefer-destructuring + thread.triggerEventOption = blocks[hatId].fields[hatFieldsKeys[0]][0].toUpperCase(); + } else { + // eslint-disable-next-line prefer-destructuring + thread.triggerEventOption = blocks[hatId].fields[hatFieldsKeys[0]][0]; + } + } + + // Convert code + let currentBlockId = nextId; + while (currentBlockId) { + const currentBlock = blocks[currentBlockId]; + // Store a copy of the opcode so we don't have to keep doing currentBlock.opcode + const { opcode } = currentBlock; + + // TODO: figure out nested blocks + + const patchApiKeys = Object.keys(patchApi); + + // Convert the block + // Duplicates shouldn't exist in the translation API, but if they do the first entry will be used + let patchKey = null; + for (let i = 0; i < patchApiKeys.length; i++) { + const key = patchApiKeys[i]; + + if (patchApi[key].opcode === opcode) { + patchKey = key; + break; + } + } + + if (!patchKey) { + if (opcode.substring(0, 8) === "control_") { + const conversionResult = this.scratchControlConverter.convertControlBlock(target, currentBlockId); + thread.script += `${conversionResult}\n`; + } else if (opcode.substring(0, 9) === "operator_") { + const conversionResult = this.scratchOperatorConverter.convertOperatorBlock(target, currentBlockId); + thread.script += `${conversionResult}\n`; + } else if (opcode.substring(0, 5) === "data_") { + const conversionResult = this.scratchDataConverter.convertDataBlock(target, currentBlockId); + thread.script += `${conversionResult}\n`; + } else { + // Couldn't find the opcode in the map. + console.error("Error translating from scratch to patch. Unable to find the key for the opcode %s.", opcode); + } + } else { + // const inputsKeys = Object.keys(currentBlock.inputs); + const detectedArgs = processInputs(target, currentBlockId, true, false); + + let patchCode = ""; + + const conversionLayerResult = patchApi[patchKey]; + if (conversionLayerResult.hasOwnProperty("returnInstead")) { + let patchArgs = ""; + for (let i = 0; i < conversionLayerResult.returnInstead.length; i++) { + const val = conversionLayerResult.returnInstead[i]; + + // Add options to change this based on language later. + if (patchArgs !== "") { + patchArgs += ", "; + } + + patchArgs += val; + } + + patchCode = `${patchArgs}\n`; + } else if (conversionLayerResult.hasOwnProperty("returnParametersInstead")) { + let patchArgs = ""; + for (let i = 0; i < conversionLayerResult.returnParametersInstead.length; i++) { + const parameter = conversionLayerResult.returnParametersInstead[i];// .toUpperCase(); + + // Add options to change this based on language later. + if (patchArgs !== "") { + patchArgs += ", "; + } + + if (detectedArgs[parameter]) { + patchArgs += detectedArgs[parameter]; + } else { + console.warn("Couldn't find parameter with opcode %s.", parameter); + patchArgs += "\"# Error: couldn't find the parameter to go here.\""; + } + } + + patchCode = `${patchArgs}\n`; + } else { + let patchArgs = ""; + for (let i = 0; i < conversionLayerResult.parameters.length; i++) { + const parameter = conversionLayerResult.parameters[i];// .toUpperCase(); + + // Add options to change this based on language later. + if (patchArgs !== "") { + patchArgs += ", "; + } + + if (detectedArgs[parameter]) { + patchArgs += detectedArgs[parameter]; + } else { + console.warn("Couldn't find parameter with opcode %s.", parameter); + patchArgs += "\"# Error: couldn't find the parameter to go here.\""; + } + } + + // Handle a special case: Patch implements the Ask block differently + // TODO: should this be a global variable? + if (currentBlock.opcode === "sensing_askandwait") { + patchKey = `_patchAnswer = ${patchKey}`; + } + + // Join all the bits and pieces together. Add options to change this based on language later. + patchCode = `${patchKey}(${patchArgs})\n` + } + + thread.script += patchCode; + } + + // Next block + currentBlockId = currentBlock.next; + } + + return thread; +} \ No newline at end of file diff --git a/src/conversion/scratch-conversion-operator.mjs b/src/conversion/scratch-conversion-operator.mjs index 64df8af9295..e4b911b4725 100644 --- a/src/conversion/scratch-conversion-operator.mjs +++ b/src/conversion/scratch-conversion-operator.mjs @@ -4,130 +4,124 @@ import ScratchBlock from "./scratch-block.mjs"; export default class ScratchConversionOperator { /** * - * @param {Object.} blocks + * @param {*} target * @param {string} blockId - * @param {Object. ${OPERAND2}`; break; } case "operator_and": { - const { OPERAND1, OPERAND2 } = processInputs(blocks, currentBlockId, currentBlock, patchApi, patchApiKeys, convertBlocksPart, true, true); + const { OPERAND1, OPERAND2 } = processInputs(target, currentBlockId, true, true); script += `${OPERAND1} and ${OPERAND2}`; break; } case "operator_or": { - const { OPERAND1, OPERAND2 } = processInputs(blocks, currentBlockId, currentBlock, patchApi, patchApiKeys, convertBlocksPart, true, true); + const { OPERAND1, OPERAND2 } = processInputs(target, currentBlockId, true, true); script += `${OPERAND1} or ${OPERAND2}`; break; } case "operator_not": { - const { OPERAND } = processInputs(blocks, currentBlockId, currentBlock, patchApi, patchApiKeys, convertBlocksPart, true, true); + const { OPERAND } = processInputs(target, currentBlockId, true, true); script += `not ${OPERAND}`; break; } case "operator_random": { - const { FROM, TO } = processInputs(blocks, currentBlockId, currentBlock, patchApi, patchApiKeys, convertBlocksPart, true, true); + const { FROM, TO } = processInputs(target, currentBlockId, true, true); script += `patch_random(${FROM}, ${TO})`; break; } case "operator_join": { - const { STRING1, STRING2 } = processInputs(blocks, currentBlockId, currentBlock, patchApi, patchApiKeys, convertBlocksPart, true, false); + const { STRING1, STRING2 } = processInputs(target, currentBlockId, true, false); // TODO: is there a more pythonic way to implement this? script += `${STRING1} + ${STRING2}`; break; } case "operator_letter_of": { - const { STRING } = processInputs(blocks, currentBlockId, currentBlock, patchApi, patchApiKeys, convertBlocksPart, true, false); - const { LETTER } = processInputs(blocks, currentBlockId, currentBlock, patchApi, patchApiKeys, convertBlocksPart, true, true); + const { STRING } = processInputs(target, currentBlockId, true, false); + const { LETTER } = processInputs(target, currentBlockId, true, true); script += `${STRING}[${LETTER - 1}]`; break; } case "operator_length": { - const { STRING } = processInputs(blocks, currentBlockId, currentBlock, patchApi, patchApiKeys, convertBlocksPart, true, false); + const { STRING } = processInputs(target, currentBlockId, true, false); script += `len(${STRING})`; break; } case "operator_contains": { - const { STRING1, STRING2 } = processInputs(blocks, currentBlockId, currentBlock, patchApi, patchApiKeys, convertBlocksPart, true, false); + const { STRING1, STRING2 } = processInputs(target, currentBlockId, true, false); script += `${STRING2} in ${STRING1}`; break; } case "operator_mod": { - const { NUM1, NUM2 } = processInputs(blocks, currentBlockId, currentBlock, patchApi, patchApiKeys, convertBlocksPart, true); + const { NUM1, NUM2 } = processInputs(target, currentBlockId, true); script += `${NUM1} % ${NUM2}`; break; } case "operator_round": { - const { NUM } = processInputs(blocks, currentBlockId, currentBlock, patchApi, patchApiKeys, convertBlocksPart, true); + const { NUM } = processInputs(target, currentBlockId, true); script += `round(${NUM})`; break; } case "operator_mathop": { - const { OPERATOR } = processInputs(blocks, currentBlockId, currentBlock, patchApi, patchApiKeys, convertBlocksPart, true); - const { NUM } = processInputs(blocks, currentBlockId, currentBlock, patchApi, patchApiKeys, convertBlocksPart, true, true); + const { OPERATOR } = processInputs(target, currentBlockId, true); + const { NUM } = processInputs(target, currentBlockId, true, true); // Remove the quotation marks that processInputs adds const formattedOperator = OPERATOR.substring(1, OPERATOR.length - 1); diff --git a/src/conversion/scratch-conversion.mjs b/src/conversion/scratch-conversion.mjs index bef03e91377..35a8df2c66e 100644 --- a/src/conversion/scratch-conversion.mjs +++ b/src/conversion/scratch-conversion.mjs @@ -1,17 +1,15 @@ import JSZip from "jszip"; -import ConversionLayer from "./conversion-layer.mjs"; import Scratch3EventBlocks from "../blocks/scratch3_event.mjs"; -import PatchTargetThread from "./patch-target-thread.mjs"; - import ScratchConversionControl from "./scratch-conversion-control.mjs"; import ScratchConversionOperator from "./scratch-conversion-operator.mjs"; import ScratchConversionData from "./scratch-conversion-data.mjs"; -import { processInputs } from "./scratch-conversion-helper.mjs"; import Scratch3ControlBlocks from "../blocks/scratch3_control.mjs"; +import patchApi from "./conversion-layer.mjs"; + export default class ScratchConverter { data = null; @@ -93,7 +91,7 @@ export default class ScratchConverter { // Step 1: blocks + variables to code; then add code for (let i = 0; i < vmState.targets.length; i++) { - vmState.targets[i].threads = this.convertTargetBlocks(vmState.targets[i].blocks, vmState.targets[i].variables); + vmState.targets[i].threads = this.convertTargetBlocks(vmState.targets[i].blocks, vmState.targets[i].variables, vmState.targets[i].name); } // Step 2: remove blocks (this isn't strictly necessary) and variables + broadcasts (this is necessary) @@ -129,174 +127,6 @@ export default class ScratchConverter { return newJsonBlob; } - convertBlocksPart(blocks, hatId, nextId, patchApi, patchApiKeys) { - const thread = new PatchTargetThread(); - - thread.triggerEventId = blocks[hatId].opcode; - console.log("blocks[hatId].opcode", blocks[hatId].opcode); - // TODO: triggerEventOption - const hatFieldsKeys = Object.keys(blocks[hatId].fields); - if (hatFieldsKeys && hatFieldsKeys.length > 0) { - if (blocks[hatId].opcode === "event_whenkeypressed") { - // eslint-disable-next-line prefer-destructuring - thread.triggerEventOption = blocks[hatId].fields[hatFieldsKeys[0]][0].toUpperCase(); - } else { - // eslint-disable-next-line prefer-destructuring - thread.triggerEventOption = blocks[hatId].fields[hatFieldsKeys[0]][0]; - } - } - - // Convert code - let currentBlockId = nextId; - while (currentBlockId) { - const currentBlock = blocks[currentBlockId]; - // Store a copy of the opcode so we don't have to keep doing currentBlock.opcode - const { opcode } = currentBlock; - - // TODO: figure out nested blocks - - // Convert the block - // Duplicates shouldn't exist in the translation API, but if they do the first entry will be used - let patchKey = null; - for (let i = 0; i < patchApiKeys.length; i++) { - const key = patchApiKeys[i]; - - if (patchApi[key].opcode === opcode) { - patchKey = key; - break; - } - } - - if (!patchKey) { - if (opcode.substring(0, 8) === "control_") { - const conversionResult = this.scratchControlConverter.convertControlBlock(blocks, currentBlockId, patchApi, patchApiKeys, this.convertBlocksPart, this); - thread.script += `${conversionResult}\n`; - } else if (opcode.substring(0, 9) === "operator_") { - const conversionResult = this.scratchOperatorConverter.convertOperatorBlock(blocks, currentBlockId, patchApi, patchApiKeys, this.convertBlocksPart, this); - thread.script += `${conversionResult}\n`; - } else if (opcode.substring(0, 5) === "data_") { - const conversionResult = this.scratchDataConverter.convertDataBlock(blocks, currentBlockId, patchApi, patchApiKeys, this.convertBlocksPart, this); - thread.script += `${conversionResult}\n`; - } else { - // Couldn't find the opcode in the map. - console.error("Error translating from scratch to patch. Unable to find the key for the opcode %s.", opcode); - } - } else { - // const inputsKeys = Object.keys(currentBlock.inputs); - const detectedArgs = processInputs(blocks, currentBlockId, currentBlock, patchApi, patchApiKeys, this.convertBlocksPart.bind(this), true, false); - - /* for (let i = 0; i < inputsKeys.length; i++) { - const inputsKey = inputsKeys[i]; - - // Add options to change this based on language later. - if (patchArgs !== "") { - patchArgs += ", "; - } - - // TODO: validate this more - let newArg = ""; - - const argType = getArgType(currentBlock.inputs[inputsKey]) - - switch (argType) { - case 0: { - newArg = `${currentBlock.inputs[inputsKey][1][1]}`; - break; - } - case 1: { - newArg = `"${currentBlock.inputs[inputsKey][1][1]}"`; - break; - } - case 2: { - // Nested block - const subThread = this.convertBlocksPart(blocks, currentBlockId, currentBlock.inputs[inputsKey][1], patchApi, patchApiKeys); - // remove the newline - newArg = subThread.script.substring(0, subThread.script.length - 1); - break; - } - default: { - console.error("Unknown argType."); - break; - } - } - - patchArgs += newArg; - } */ - - let patchCode = ""; - - const conversionLayerResult = patchApi[patchKey]; - if (conversionLayerResult.hasOwnProperty("returnInstead")) { - let patchArgs = ""; - for (let i = 0; i < conversionLayerResult.returnInstead.length; i++) { - const val = conversionLayerResult.returnInstead[i]; - - // Add options to change this based on language later. - if (patchArgs !== "") { - patchArgs += ", "; - } - - patchArgs += val; - } - - patchCode = `${patchArgs}\n`; - } else if (conversionLayerResult.hasOwnProperty("returnParametersInstead")) { - let patchArgs = ""; - for (let i = 0; i < conversionLayerResult.returnParametersInstead.length; i++) { - const parameter = conversionLayerResult.returnParametersInstead[i];// .toUpperCase(); - - // Add options to change this based on language later. - if (patchArgs !== "") { - patchArgs += ", "; - } - - if (detectedArgs[parameter]) { - patchArgs += detectedArgs[parameter]; - } else { - console.warn("Couldn't find parameter with opcode %s.", parameter); - patchArgs += "\"# Error: couldn't find the parameter to go here.\""; - } - } - - patchCode = `${patchArgs}\n`; - } else { - let patchArgs = ""; - for (let i = 0; i < conversionLayerResult.parameters.length; i++) { - const parameter = conversionLayerResult.parameters[i];// .toUpperCase(); - - // Add options to change this based on language later. - if (patchArgs !== "") { - patchArgs += ", "; - } - - if (detectedArgs[parameter]) { - patchArgs += detectedArgs[parameter]; - } else { - console.warn("Couldn't find parameter with opcode %s.", parameter); - patchArgs += "\"# Error: couldn't find the parameter to go here.\""; - } - } - - // Handle a special case: Patch implements the Ask block differently - // TODO: should this be a global variable? - if (currentBlock.opcode === "sensing_askandwait") { - patchKey = `_patchAnswer = ${patchKey}`; - } - - // Join all the bits and pieces together. Add options to change this based on language later. - patchCode = `${patchKey}(${patchArgs})\n` - } - - thread.script += patchCode; - } - - // Next block - currentBlockId = currentBlock.next; - } - - return thread; - } - /** * Converts an object representation of a Scratch target's blocks into an object * representation of the corresponding Patch threads and thread code. @@ -305,7 +135,7 @@ export default class ScratchConverter { * @param {Object.} variables * @returns {PatchTargetThread[]} An array of object representations of the patch threads */ - convertTargetBlocks(blocks, variables) { + convertTargetBlocks(blocks, variables, spriteName) { // TODO: convert variables // https://en.scratch-wiki.info/wiki/Scratch_File_Format#Blocks @@ -327,11 +157,10 @@ export default class ScratchConverter { } }); - const { patchApi } = ConversionLayer; const patchApiKeys = Object.keys(patchApi); hatLocations.forEach(hatId => { - const returnValPart = this.convertBlocksPart(blocks, hatId, blocks[hatId].next, patchApi, patchApiKeys); + const returnValPart = this.convertBlocksPart(blocks, hatId, blocks[hatId].next, patchApi, patchApiKeys, spriteName); if (returnValPart.script.includes("math.")) { returnValPart.script = `import math\n\n${ returnValPart.script }`; From ac429ab37e3474d78b81059000d7f69ba4d6c931 Mon Sep 17 00:00:00 2001 From: Benjamin Montgomery Date: Fri, 12 Jul 2024 16:58:52 -0400 Subject: [PATCH 4/4] fix: finish up importing variables from scratch to patch conversion --- src/conversion/scratch-conversion-control.mjs | 157 +++++---- src/conversion/scratch-conversion-data.mjs | 299 +++++++++-------- src/conversion/scratch-conversion-helper.mjs | 10 +- .../scratch-conversion-operator.mjs | 302 +++++++++--------- src/conversion/scratch-conversion.mjs | 30 +- 5 files changed, 408 insertions(+), 390 deletions(-) diff --git a/src/conversion/scratch-conversion-control.mjs b/src/conversion/scratch-conversion-control.mjs index 549f5985b5c..2bea5a70880 100644 --- a/src/conversion/scratch-conversion-control.mjs +++ b/src/conversion/scratch-conversion-control.mjs @@ -1,91 +1,84 @@ -import PatchTargetThread from "./patch-target-thread.mjs"; -import ScratchBlock from "./scratch-block.mjs"; - -import { indentLines, processInputs, convertBlocksPart } from "./scratch-conversion-helper.mjs"; - -export default class ScratchConversionControl { - /** +/** * * @param {*} target * @param {string} blockId * @returns {string} */ - convertControlBlock(target, currentBlockId) { - const currentBlock = target.blocks[currentBlockId]; - const { opcode } = currentBlock; +export default function convertControlBlock(target, currentBlockId, processInputs, indentLines, convertBlocksPart) { + const currentBlock = target.blocks[currentBlockId]; + const { opcode } = currentBlock; - let script = ""; + let script = ""; - switch (opcode) { - case "control_forever": { - // Forever loop - let SUBSTACK = convertBlocksPart(target, currentBlockId, currentBlock.inputs.SUBSTACK[1]).script; - SUBSTACK = SUBSTACK.substring(0, SUBSTACK.length - 1); - // Add options to change this based on language later. - // TODO: change this to game loop if/when game loop is added - script += "\n"; - script += "while True:" - script += indentLines(SUBSTACK); - break; - } - case "control_if": { - // If (but no else) statement - // 2 args: "CONDITION" and "SUBSTACK" - let CONDITION = convertBlocksPart(target, currentBlockId, currentBlock.inputs.CONDITION[1]).script; - CONDITION = CONDITION.substring(0, CONDITION.length - 1); - let SUBSTACK = convertBlocksPart(target, currentBlockId, currentBlock.inputs.SUBSTACK[1]).script; - SUBSTACK = SUBSTACK.substring(0, SUBSTACK.length - 1); - script += `if ${CONDITION}:`; - script += indentLines(SUBSTACK); - script += "\n"; - break; - } - case "control_if_else": { - // If + else statement - // 3 args: "CONDITION", "SUBSTACK", and "SUBSTACK2" - let CONDITION = convertBlocksPart(target, currentBlockId, currentBlock.inputs.CONDITION[1]).script; - CONDITION = CONDITION.substring(0, CONDITION.length - 1); - let SUBSTACK = convertBlocksPart(target, currentBlockId, currentBlock.inputs.SUBSTACK[1]).script; - SUBSTACK = SUBSTACK.substring(0, SUBSTACK.length - 1); - let SUBSTACK2 = convertBlocksPart(target, currentBlockId, currentBlock.inputs.SUBSTACK2[1]).script; - SUBSTACK2 = SUBSTACK2.substring(0, SUBSTACK2.length - 1); - script += `if ${CONDITION}:`; - script += indentLines(SUBSTACK); - script += "\nelse:"; - script += indentLines(SUBSTACK2); - script += "\n"; - break; - } - case "control_repeat": { - const { SUBSTACK } = processInputs(target, currentBlockId, false); - const { TIMES } = processInputs(target, currentBlockId, true, true); - script += `for _ in range(${TIMES}):`; - script += indentLines(SUBSTACK); - script += "\n"; - break; - } - case "control_wait_until": { - const { CONDITION } = processInputs(target, currentBlockId, false); - console.warn("WARN: the Wait Until block isn't supported in Patch, so a basic substitute will be used."); - script += `while True:`; - script += indentLines(`if ${CONDITION}:${indentLines(`break`)}`); - script += "\n"; - break; - } - case "control_repeat_until": { - const { SUBSTACK, CONDITION } = processInputs(target, currentBlockId, false); - console.warn("WARN: the Repeat Until block isn't supported in Patch, so a basic substitute will be used."); - script += `while True:`; - script += indentLines(`if ${CONDITION}:${indentLines(`break`)}\nelse:${indentLines(SUBSTACK)}`); - script += "\n"; - break; - } - default: { - console.warn("The control block conversion couldn't figure out how to handle opcode %s.", currentBlock.opcode); - break; - } + switch (opcode) { + case "control_forever": { + // Forever loop + let SUBSTACK = convertBlocksPart(target, currentBlockId, currentBlock.inputs.SUBSTACK[1]).script; + SUBSTACK = SUBSTACK.substring(0, SUBSTACK.length - 1); + // Add options to change this based on language later. + // TODO: change this to game loop if/when game loop is added + script += "\n"; + script += "while True:" + script += indentLines(SUBSTACK); + break; + } + case "control_if": { + // If (but no else) statement + // 2 args: "CONDITION" and "SUBSTACK" + let CONDITION = convertBlocksPart(target, currentBlockId, currentBlock.inputs.CONDITION[1]).script; + CONDITION = CONDITION.substring(0, CONDITION.length - 1); + let SUBSTACK = convertBlocksPart(target, currentBlockId, currentBlock.inputs.SUBSTACK[1]).script; + SUBSTACK = SUBSTACK.substring(0, SUBSTACK.length - 1); + script += `if ${CONDITION}:`; + script += indentLines(SUBSTACK); + script += "\n"; + break; + } + case "control_if_else": { + // If + else statement + // 3 args: "CONDITION", "SUBSTACK", and "SUBSTACK2" + let CONDITION = convertBlocksPart(target, currentBlockId, currentBlock.inputs.CONDITION[1]).script; + CONDITION = CONDITION.substring(0, CONDITION.length - 1); + let SUBSTACK = convertBlocksPart(target, currentBlockId, currentBlock.inputs.SUBSTACK[1]).script; + SUBSTACK = SUBSTACK.substring(0, SUBSTACK.length - 1); + let SUBSTACK2 = convertBlocksPart(target, currentBlockId, currentBlock.inputs.SUBSTACK2[1]).script; + SUBSTACK2 = SUBSTACK2.substring(0, SUBSTACK2.length - 1); + script += `if ${CONDITION}:`; + script += indentLines(SUBSTACK); + script += "\nelse:"; + script += indentLines(SUBSTACK2); + script += "\n"; + break; + } + case "control_repeat": { + const { SUBSTACK } = processInputs(target, currentBlockId, false); + const { TIMES } = processInputs(target, currentBlockId, true, true); + script += `for _ in range(${TIMES}):`; + script += indentLines(SUBSTACK); + script += "\n"; + break; + } + case "control_wait_until": { + const { CONDITION } = processInputs(target, currentBlockId, false); + console.warn("WARN: the Wait Until block isn't supported in Patch, so a basic substitute will be used."); + script += `while True:`; + script += indentLines(`if ${CONDITION}:${indentLines(`break`)}`); + script += "\n"; + break; + } + case "control_repeat_until": { + const { SUBSTACK, CONDITION } = processInputs(target, currentBlockId, false); + console.warn("WARN: the Repeat Until block isn't supported in Patch, so a basic substitute will be used."); + script += `while True:`; + script += indentLines(`if ${CONDITION}:${indentLines(`break`)}\nelse:${indentLines(SUBSTACK)}`); + script += "\n"; + break; + } + default: { + console.warn("The control block conversion couldn't figure out how to handle opcode %s.", currentBlock.opcode); + break; } - - return script; } -} \ No newline at end of file + + return script; +} diff --git a/src/conversion/scratch-conversion-data.mjs b/src/conversion/scratch-conversion-data.mjs index 37b60377e4c..3b4dd7919c0 100644 --- a/src/conversion/scratch-conversion-data.mjs +++ b/src/conversion/scratch-conversion-data.mjs @@ -1,141 +1,172 @@ -import PatchTargetThread from "./patch-target-thread.mjs"; -import ScratchBlock from "./scratch-block.mjs"; +function checkVariableName(target, variable) { + let trimmed = variable.substring(1, variable.length - 1); -import { indentLines, processInputs } from "./scratch-conversion-helper.mjs"; + const variableKeys = Object.keys(target.variables); -export default class ScratchConversionData { - /** - * - * @param {*} target - * @param {string} blockId - * @returns {string} - */ - convertDataBlock(target, currentBlockId) { - const currentBlock = target.blocks[currentBlockId]; - const { opcode } = currentBlock; + for (let i = 0; i < variableKeys.length; i++) { + if (target.variables[variableKeys[i]][0] === trimmed) { + trimmed = `${target.name}_${trimmed}`; - let script = ""; + return trimmed; + } + } + + const listKeys = Object.keys(target.lists); + + for (let i = 0; i < listKeys.length; i++) { + if (target.lists[listKeys[i]][0] === trimmed) { + trimmed = `${target.name}_${trimmed}`; - switch (opcode) { - case "data_setvariableto": { - // Set variable - const { VARIABLE } = processInputs(target, currentBlockId, false); - const { VALUE } = processInputs(target, currentBlockId, false, true); - // Add options to change this based on language later. - script += `${VARIABLE.substring(1, VARIABLE.length - 1)} = ${VALUE}`; - break; - } - case "data_changevariableby": { - // Change variable by - const { VARIABLE } = processInputs(target, currentBlockId, false); - const { VALUE } = processInputs(target, currentBlockId, false, true); - // Add options to change this based on language later. - script += `${VARIABLE.substring(1, VARIABLE.length - 1)} += ${VALUE}`; - break; - } - case "data_showvariable": { - // Display variable on screen - console.warn("WARN: the Show Variable block isn't supported in Patch."); - break; - } - case "data_hidevariable": { - // Stop displaying variable on screen - console.warn("WARN: the Hide Variable block isn't supported in Patch."); - break; - } - case "data_addtolist": { - // Append to list - const { LIST } = processInputs(target, currentBlockId, false); - const { ITEM } = processInputs(target, currentBlockId, false, true); - // Add options to change this based on language later. - console.warn("WARN: using lists as variables isn't currently supported in Patch. Code will be generated but it may or may not function."); - script += `${LIST.substring(1, LIST.length - 1)}.append(${ITEM})`; - break; - } - case "data_deleteoflist": { - // Delete item at index from list - const { LIST } = processInputs(target, currentBlockId, false); - const { INDEX } = processInputs(target, currentBlockId, false, true); - // Add options to change this based on language later. - console.warn("WARN: using lists as variables isn't currently supported in Patch. Code will be generated but it may or may not function."); - script += `${LIST.substring(1, LIST.length - 1)}.pop(${INDEX})`; - break; - } - case "data_deletealloflist": { - // Clear a list - const { LIST } = processInputs(target, currentBlockId, false); - // Add options to change this based on language later. - console.warn("WARN: using lists as variables isn't currently supported in Patch. Code will be generated but it may or may not function."); - script += `${LIST.substring(1, LIST.length - 1)}.clear()`; - break; - } - case "data_insertatlist": { - // Insert an item into the list - const { LIST } = processInputs(target, currentBlockId, false); - const { ITEM, INDEX } = processInputs(target, currentBlockId, false, true); - // Add options to change this based on language later. - console.warn("WARN: using lists as variables isn't currently supported in Patch. Code will be generated but it may or may not function."); - script += `${LIST.substring(1, LIST.length - 1)}.insert(${INDEX}, ${ITEM})`; - break; - } - case "data_replaceitemoflist": { - // Replace a list item - const { LIST } = processInputs(target, currentBlockId, false); - const { ITEM, INDEX } = processInputs(target, currentBlockId, false, true); - // Add options to change this based on language later. - console.warn("WARN: using lists as variables isn't currently supported in Patch. Code will be generated but it may or may not function."); - script += `${LIST.substring(1, LIST.length - 1)}[${INDEX}] = ${ITEM}`; - break; - } - case "data_itemoflist": { - // Get a list item - const { LIST } = processInputs(target, currentBlockId, false); - const { INDEX } = processInputs(target, currentBlockId, false, true); - // Add options to change this based on language later. - console.warn("WARN: using lists as variables isn't currently supported in Patch. Code will be generated but it may or may not function."); - script += `${LIST.substring(1, LIST.length - 1)}[${INDEX}]`; - break; - } - case "data_itemnumoflist": { - // Get the index of an item in the list - const { LIST } = processInputs(target, currentBlockId, false); - const { ITEM } = processInputs(target, currentBlockId, false, true); - // Add options to change this based on language later. - console.warn("WARN: using lists as variables isn't currently supported in Patch. Code will be generated but it may or may not function."); - script += `${LIST.substring(1, LIST.length - 1)}.index(${ITEM})`; - break; - } - case "data_lengthoflist": { - // Get the length of the list - const { LIST } = processInputs(target, currentBlockId, false); - // Add options to change this based on language later. - console.warn("WARN: using lists as variables isn't currently supported in Patch. Code will be generated but it may or may not function."); - script += `len(${LIST.substring(1, LIST.length - 1)})`; - break; - } - case "data_listcontainsitem": { - // Check if the list contains a certain item - const { LIST } = processInputs(target, currentBlockId, false); - const { ITEM } = processInputs(target, currentBlockId, false, true); - // Add options to change this based on language later. - console.warn("WARN: using lists as variables isn't currently supported in Patch. Code will be generated but it may or may not function."); - script += `${ITEM} in ${LIST.substring(1, LIST.length - 1)}`; - break; - } - case "data_showlist": { - console.warn("WARN: the Show List block isn't supported in Patch."); - break; - } - case "data_hidelist": { - console.warn("WARN: the Hide List block isn't supported in Patch."); - break; - } - default: { - console.warn("The data block conversion couldn't figure out how to handle opcode %s.", currentBlock.opcode); - break; - } + return trimmed; } + } + + return trimmed; +} + +/** + * + * @param {*} target + * @param {string} blockId + * @param {*} processInputs + * @returns {string} + */ +export default function convertDataBlock(target, currentBlockId, processInputs) { + const currentBlock = target.blocks[currentBlockId]; + const { opcode } = currentBlock; - return script; + let script = ""; + + switch (opcode) { + case "data_setvariableto": { + // Set variable + const { VARIABLE } = processInputs(target, currentBlockId, false); + const { VALUE } = processInputs(target, currentBlockId, false, true); + const VARIABLE_TRIMMED = checkVariableName(target, VARIABLE); + // Add options to change this based on language later. + script += `${VARIABLE_TRIMMED} = ${VALUE}`; + break; + } + case "data_changevariableby": { + // Change variable by + const { VARIABLE } = processInputs(target, currentBlockId, false); + const { VALUE } = processInputs(target, currentBlockId, false, true); + const VARIABLE_TRIMMED = checkVariableName(target, VARIABLE); + // Add options to change this based on language later. + script += `${VARIABLE_TRIMMED} += ${VALUE}`; + break; + } + case "data_showvariable": { + // Display variable on screen + console.warn("WARN: the Show Variable block isn't supported in Patch."); + break; + } + case "data_hidevariable": { + // Stop displaying variable on screen + console.warn("WARN: the Hide Variable block isn't supported in Patch."); + break; + } + case "data_addtolist": { + // Append to list + const { LIST } = processInputs(target, currentBlockId, false); + const { ITEM } = processInputs(target, currentBlockId, false, true); + const LIST_TRIMMED = checkVariableName(target, LIST); + // Add options to change this based on language later. + console.warn("WARN: using lists as variables isn't currently supported in Patch. Code will be generated but it may or may not function."); + script += `${LIST_TRIMMED}.append(${ITEM})`; + break; + } + case "data_deleteoflist": { + // Delete item at index from list + const { LIST } = processInputs(target, currentBlockId, false); + const { INDEX } = processInputs(target, currentBlockId, false, true); + const LIST_TRIMMED = checkVariableName(target, LIST); + // Add options to change this based on language later. + console.warn("WARN: using lists as variables isn't currently supported in Patch. Code will be generated but it may or may not function."); + script += `${LIST_TRIMMED}.pop(${INDEX})`; + break; + } + case "data_deletealloflist": { + // Clear a list + const { LIST } = processInputs(target, currentBlockId, false); + const LIST_TRIMMED = checkVariableName(target, LIST); + // Add options to change this based on language later. + console.warn("WARN: using lists as variables isn't currently supported in Patch. Code will be generated but it may or may not function."); + script += `${LIST_TRIMMED}.clear()`; + break; + } + case "data_insertatlist": { + // Insert an item into the list + const { LIST } = processInputs(target, currentBlockId, false); + const { ITEM, INDEX } = processInputs(target, currentBlockId, false, true); + const LIST_TRIMMED = checkVariableName(target, LIST); + // Add options to change this based on language later. + console.warn("WARN: using lists as variables isn't currently supported in Patch. Code will be generated but it may or may not function."); + script += `${LIST_TRIMMED}.insert(${INDEX}, ${ITEM})`; + break; + } + case "data_replaceitemoflist": { + // Replace a list item + const { LIST } = processInputs(target, currentBlockId, false); + const { ITEM, INDEX } = processInputs(target, currentBlockId, false, true); + const LIST_TRIMMED = checkVariableName(target, LIST); + // Add options to change this based on language later. + console.warn("WARN: using lists as variables isn't currently supported in Patch. Code will be generated but it may or may not function."); + script += `${LIST_TRIMMED}[${INDEX}] = ${ITEM}`; + break; + } + case "data_itemoflist": { + // Get a list item + const { LIST } = processInputs(target, currentBlockId, false); + const { INDEX } = processInputs(target, currentBlockId, false, true); + const LIST_TRIMMED = checkVariableName(target, LIST); + // Add options to change this based on language later. + console.warn("WARN: using lists as variables isn't currently supported in Patch. Code will be generated but it may or may not function."); + script += `${LIST_TRIMMED}[${INDEX}]`; + break; + } + case "data_itemnumoflist": { + // Get the index of an item in the list + const { LIST } = processInputs(target, currentBlockId, false); + const { ITEM } = processInputs(target, currentBlockId, false, true); + const LIST_TRIMMED = checkVariableName(target, LIST); + // Add options to change this based on language later. + console.warn("WARN: using lists as variables isn't currently supported in Patch. Code will be generated but it may or may not function."); + script += `${LIST_TRIMMED}.index(${ITEM})`; + break; + } + case "data_lengthoflist": { + // Get the length of the list + const { LIST } = processInputs(target, currentBlockId, false); + const LIST_TRIMMED = checkVariableName(target, LIST); + // Add options to change this based on language later. + console.warn("WARN: using lists as variables isn't currently supported in Patch. Code will be generated but it may or may not function."); + script += `len(${LIST_TRIMMED})`; + break; + } + case "data_listcontainsitem": { + // Check if the list contains a certain item + const { LIST } = processInputs(target, currentBlockId, false); + const { ITEM } = processInputs(target, currentBlockId, false, true); + const LIST_TRIMMED = checkVariableName(target, LIST); + // Add options to change this based on language later. + console.warn("WARN: using lists as variables isn't currently supported in Patch. Code will be generated but it may or may not function."); + script += `${ITEM} in ${LIST_TRIMMED}`; + break; + } + case "data_showlist": { + console.warn("WARN: the Show List block isn't supported in Patch."); + break; + } + case "data_hidelist": { + console.warn("WARN: the Hide List block isn't supported in Patch."); + break; + } + default: { + console.warn("The data block conversion couldn't figure out how to handle opcode %s.", currentBlock.opcode); + break; + } } -} \ No newline at end of file + + return script; +} diff --git a/src/conversion/scratch-conversion-helper.mjs b/src/conversion/scratch-conversion-helper.mjs index a83a0117e5d..673d4d39f56 100644 --- a/src/conversion/scratch-conversion-helper.mjs +++ b/src/conversion/scratch-conversion-helper.mjs @@ -2,6 +2,10 @@ import patchApi from "./conversion-layer.mjs"; import PatchTargetThread from "./patch-target-thread.mjs"; +import convertControlBlock from "./scratch-conversion-control.mjs"; +import convertDataBlock from "./scratch-conversion-data.mjs"; +import convertOperatorBlock from "./scratch-conversion-operator.mjs"; + // 0: number, 1: string, 2: nested, -1: error export function getArgType(inputJson) { const argType = inputJson[1][0]; @@ -168,13 +172,13 @@ export function convertBlocksPart(target, hatId, nextId) { if (!patchKey) { if (opcode.substring(0, 8) === "control_") { - const conversionResult = this.scratchControlConverter.convertControlBlock(target, currentBlockId); + const conversionResult = convertControlBlock(target, currentBlockId, processInputs, indentLines, convertBlocksPart); thread.script += `${conversionResult}\n`; } else if (opcode.substring(0, 9) === "operator_") { - const conversionResult = this.scratchOperatorConverter.convertOperatorBlock(target, currentBlockId); + const conversionResult = convertOperatorBlock(target, currentBlockId, processInputs); thread.script += `${conversionResult}\n`; } else if (opcode.substring(0, 5) === "data_") { - const conversionResult = this.scratchDataConverter.convertDataBlock(target, currentBlockId); + const conversionResult = convertDataBlock(target, currentBlockId, processInputs); thread.script += `${conversionResult}\n`; } else { // Couldn't find the opcode in the map. diff --git a/src/conversion/scratch-conversion-operator.mjs b/src/conversion/scratch-conversion-operator.mjs index e4b911b4725..02ee2d7c1e0 100644 --- a/src/conversion/scratch-conversion-operator.mjs +++ b/src/conversion/scratch-conversion-operator.mjs @@ -1,159 +1,155 @@ -import { processInputs } from "./scratch-conversion-helper.mjs" -import ScratchBlock from "./scratch-block.mjs"; - -export default class ScratchConversionOperator { - /** +/** * * @param {*} target * @param {string} blockId + * @param {*} processInputs * @returns {string} */ - convertOperatorBlock(target, currentBlockId) { - const currentBlock = target.blocks[currentBlockId]; - const { opcode } = currentBlock; - - let script = ""; - - switch (opcode) { - case "operator_add": { - const { NUM1, NUM2 } = processInputs(target, currentBlockId, true); - - script += `${NUM1} + ${NUM2}`; - break; - } - case "operator_subtract": { - const { NUM1, NUM2 } = processInputs(target, currentBlockId, true); - - script += `${NUM1} - ${NUM2}`; - break; - } - case "operator_multiply": { - const { NUM1, NUM2 } = processInputs(target, currentBlockId, true); - - script += `${NUM1} * ${NUM2}`; - break; - } - case "operator_divide": { - const { NUM1, NUM2 } = processInputs(target, currentBlockId, true); - - script += `${NUM1} / ${NUM2}`; - break; - } - case "operator_lt": { - const { OPERAND1, OPERAND2 } = processInputs(target, currentBlockId, true, true); - - script += `${OPERAND1} < ${OPERAND2}`; - break; - } - case "operator_equals": { - const { OPERAND1, OPERAND2 } = processInputs(target, currentBlockId, true, true); - - script += `${OPERAND1} == ${OPERAND2}`; - break; - } - case "operator_gt": { - const { OPERAND1, OPERAND2 } = processInputs(target, currentBlockId, true, true); - - script += `${OPERAND1} > ${OPERAND2}`; - break; - } - case "operator_and": { - const { OPERAND1, OPERAND2 } = processInputs(target, currentBlockId, true, true); - - script += `${OPERAND1} and ${OPERAND2}`; - break; - } - case "operator_or": { - const { OPERAND1, OPERAND2 } = processInputs(target, currentBlockId, true, true); - - script += `${OPERAND1} or ${OPERAND2}`; - break; - } - case "operator_not": { - const { OPERAND } = processInputs(target, currentBlockId, true, true); - - script += `not ${OPERAND}`; - break; - } - case "operator_random": { - const { FROM, TO } = processInputs(target, currentBlockId, true, true); - - script += `patch_random(${FROM}, ${TO})`; - break; - } - case "operator_join": { - const { STRING1, STRING2 } = processInputs(target, currentBlockId, true, false); - - // TODO: is there a more pythonic way to implement this? - script += `${STRING1} + ${STRING2}`; - break; - } - case "operator_letter_of": { - const { STRING } = processInputs(target, currentBlockId, true, false); - const { LETTER } = processInputs(target, currentBlockId, true, true); - - script += `${STRING}[${LETTER - 1}]`; - break; - } - case "operator_length": { - const { STRING } = processInputs(target, currentBlockId, true, false); - - script += `len(${STRING})`; - break; - } - case "operator_contains": { - const { STRING1, STRING2 } = processInputs(target, currentBlockId, true, false); - - script += `${STRING2} in ${STRING1}`; - break; - } - case "operator_mod": { - const { NUM1, NUM2 } = processInputs(target, currentBlockId, true); - - script += `${NUM1} % ${NUM2}`; - break; - } - case "operator_round": { - const { NUM } = processInputs(target, currentBlockId, true); - - script += `round(${NUM})`; - break; - } - case "operator_mathop": { - const { OPERATOR } = processInputs(target, currentBlockId, true); - const { NUM } = processInputs(target, currentBlockId, true, true); - - // Remove the quotation marks that processInputs adds - const formattedOperator = OPERATOR.substring(1, OPERATOR.length - 1); - - const mathOpsDict = { - "abs": `abs(${ NUM })`, - "ceiling": `math.ceil(${ NUM })`, - "sqrt": `math.sqrt(${ NUM })`, - "floor": `math.floor(${ NUM })`, - /* Trig in scratch uses degrees. To keep this consistent, we must convert the inputs of - trig (but not inverse trig) */ - "sin": `math.sin(math.radians(${ NUM }))`, - "cos": `math.cos(math.radians(${ NUM }))`, - "tan": `math.tan(math.radians(${ NUM }))`, - "asin": `math.degrees(math.asin(${ NUM }))`, - "acos": `math.degrees(math.acos(${ NUM }))`, - "atan": `math.degrees(math.atan(${ NUM }))`, - /* in Python, math.log defaults to base e, not base 10 */ - "ln": `math.log(${ NUM })`, - "log": `math.log(${ NUM }, 10)`, - "e ^": `pow(math.e, ${ NUM })`, /* `math.exp(${ NUM })`, */ - "10 ^": `pow(10, ${ NUM })` - }; - - script += mathOpsDict[formattedOperator]; - break; - } - default: { - break; - } - } - - return script; +export default function convertOperatorBlock(target, currentBlockId, processInputs) { + const currentBlock = target.blocks[currentBlockId]; + const { opcode } = currentBlock; + + let script = ""; + + switch (opcode) { + case "operator_add": { + const { NUM1, NUM2 } = processInputs(target, currentBlockId, true); + + script += `${NUM1} + ${NUM2}`; + break; + } + case "operator_subtract": { + const { NUM1, NUM2 } = processInputs(target, currentBlockId, true); + + script += `${NUM1} - ${NUM2}`; + break; + } + case "operator_multiply": { + const { NUM1, NUM2 } = processInputs(target, currentBlockId, true); + + script += `${NUM1} * ${NUM2}`; + break; + } + case "operator_divide": { + const { NUM1, NUM2 } = processInputs(target, currentBlockId, true); + + script += `${NUM1} / ${NUM2}`; + break; + } + case "operator_lt": { + const { OPERAND1, OPERAND2 } = processInputs(target, currentBlockId, true, true); + + script += `${OPERAND1} < ${OPERAND2}`; + break; + } + case "operator_equals": { + const { OPERAND1, OPERAND2 } = processInputs(target, currentBlockId, true, true); + + script += `${OPERAND1} == ${OPERAND2}`; + break; + } + case "operator_gt": { + const { OPERAND1, OPERAND2 } = processInputs(target, currentBlockId, true, true); + + script += `${OPERAND1} > ${OPERAND2}`; + break; + } + case "operator_and": { + const { OPERAND1, OPERAND2 } = processInputs(target, currentBlockId, true, true); + + script += `${OPERAND1} and ${OPERAND2}`; + break; + } + case "operator_or": { + const { OPERAND1, OPERAND2 } = processInputs(target, currentBlockId, true, true); + + script += `${OPERAND1} or ${OPERAND2}`; + break; + } + case "operator_not": { + const { OPERAND } = processInputs(target, currentBlockId, true, true); + + script += `not ${OPERAND}`; + break; + } + case "operator_random": { + const { FROM, TO } = processInputs(target, currentBlockId, true, true); + + script += `patch_random(${FROM}, ${TO})`; + break; + } + case "operator_join": { + const { STRING1, STRING2 } = processInputs(target, currentBlockId, true, false); + + // TODO: is there a more pythonic way to implement this? + script += `${STRING1} + ${STRING2}`; + break; + } + case "operator_letter_of": { + const { STRING } = processInputs(target, currentBlockId, true, false); + const { LETTER } = processInputs(target, currentBlockId, true, true); + + script += `${STRING}[${LETTER - 1}]`; + break; + } + case "operator_length": { + const { STRING } = processInputs(target, currentBlockId, true, false); + + script += `len(${STRING})`; + break; + } + case "operator_contains": { + const { STRING1, STRING2 } = processInputs(target, currentBlockId, true, false); + + script += `${STRING2} in ${STRING1}`; + break; + } + case "operator_mod": { + const { NUM1, NUM2 } = processInputs(target, currentBlockId, true); + + script += `${NUM1} % ${NUM2}`; + break; + } + case "operator_round": { + const { NUM } = processInputs(target, currentBlockId, true); + + script += `round(${NUM})`; + break; + } + case "operator_mathop": { + const { OPERATOR } = processInputs(target, currentBlockId, true); + const { NUM } = processInputs(target, currentBlockId, true, true); + + // Remove the quotation marks that processInputs adds + const formattedOperator = OPERATOR.substring(1, OPERATOR.length - 1); + + const mathOpsDict = { + "abs": `abs(${NUM})`, + "ceiling": `math.ceil(${NUM})`, + "sqrt": `math.sqrt(${NUM})`, + "floor": `math.floor(${NUM})`, + /* Trig in scratch uses degrees. To keep this consistent, we must convert the inputs of + trig (but not inverse trig) */ + "sin": `math.sin(math.radians(${NUM}))`, + "cos": `math.cos(math.radians(${NUM}))`, + "tan": `math.tan(math.radians(${NUM}))`, + "asin": `math.degrees(math.asin(${NUM}))`, + "acos": `math.degrees(math.acos(${NUM}))`, + "atan": `math.degrees(math.atan(${NUM}))`, + /* in Python, math.log defaults to base e, not base 10 */ + "ln": `math.log(${NUM})`, + "log": `math.log(${NUM}, 10)`, + "e ^": `pow(math.e, ${NUM})`, /* `math.exp(${ NUM })`, */ + "10 ^": `pow(10, ${NUM})` + }; + + script += mathOpsDict[formattedOperator]; + break; + } + default: { + break; + } } -} \ No newline at end of file + + return script; +} diff --git a/src/conversion/scratch-conversion.mjs b/src/conversion/scratch-conversion.mjs index 35a8df2c66e..3c9f82e0cf8 100644 --- a/src/conversion/scratch-conversion.mjs +++ b/src/conversion/scratch-conversion.mjs @@ -2,25 +2,15 @@ import JSZip from "jszip"; import Scratch3EventBlocks from "../blocks/scratch3_event.mjs"; -import ScratchConversionControl from "./scratch-conversion-control.mjs"; -import ScratchConversionOperator from "./scratch-conversion-operator.mjs"; -import ScratchConversionData from "./scratch-conversion-data.mjs"; - import Scratch3ControlBlocks from "../blocks/scratch3_control.mjs"; -import patchApi from "./conversion-layer.mjs"; +import { convertBlocksPart } from "./scratch-conversion-helper.mjs"; export default class ScratchConverter { data = null; scratchJson = null; - scratchControlConverter = new ScratchConversionControl(); - - scratchOperatorConverter = new ScratchConversionOperator(); - - scratchDataConverter = new ScratchConversionData(); - /** * * @param {ArrayBuffer} scratchData An ArrayBuffer representation of the .sb3 file to convert @@ -91,7 +81,7 @@ export default class ScratchConverter { // Step 1: blocks + variables to code; then add code for (let i = 0; i < vmState.targets.length; i++) { - vmState.targets[i].threads = this.convertTargetBlocks(vmState.targets[i].blocks, vmState.targets[i].variables, vmState.targets[i].name); + vmState.targets[i].threads = this.convertTargetBlocks(vmState.targets[i]); } // Step 2: remove blocks (this isn't strictly necessary) and variables + broadcasts (this is necessary) @@ -99,7 +89,10 @@ export default class ScratchConverter { // remover however. for (let i = 0; i < vmState.targets.length; i++) { vmState.targets[i].blocks = {}; - vmState.targets[i].variables.forEach(variable => { + const variableKeys = Object.keys(vmState.targets[i].variables); + variableKeys.forEach(key => { + const variable = vmState.targets[i].variables[key]; + console.log(variable); if (vmState.targets[i].isStage) { // In Scratch, global variables are actually stored as sprite variables on the stage. globalVariables.push({name: variable[0], value: variable[1]}); @@ -120,7 +113,8 @@ export default class ScratchConverter { // Step 4: make everything a child of "vmstate" and add global variables // TODO: global variables - const baseJson = { vmstate: vmState, globalVariables: [] }; + console.log(globalVariables); + const baseJson = { vmstate: vmState, globalVariables: globalVariables }; // Step 4: convert this back to a blob, make everything a child of "vmstate", and return it. const newJsonBlob = new Blob([JSON.stringify(baseJson)], { type: "application/json" }); @@ -135,10 +129,12 @@ export default class ScratchConverter { * @param {Object.} variables * @returns {PatchTargetThread[]} An array of object representations of the patch threads */ - convertTargetBlocks(blocks, variables, spriteName) { + convertTargetBlocks(target) { // TODO: convert variables // https://en.scratch-wiki.info/wiki/Scratch_File_Format#Blocks + const {blocks} = target; + const blocksKeys = Object.keys(blocks); const returnVal = []; @@ -157,10 +153,8 @@ export default class ScratchConverter { } }); - const patchApiKeys = Object.keys(patchApi); - hatLocations.forEach(hatId => { - const returnValPart = this.convertBlocksPart(blocks, hatId, blocks[hatId].next, patchApi, patchApiKeys, spriteName); + const returnValPart = convertBlocksPart(target, hatId, blocks[hatId].next); if (returnValPart.script.includes("math.")) { returnValPart.script = `import math\n\n${ returnValPart.script }`;