UNPKG

@robotical/scratch-to-python-transpiler

Version:
1,055 lines (1,054 loc) 109 kB
"use strict"; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __spreadArrays = (this && this.__spreadArrays) || function () { for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; for (var r = Array(s), k = 0, i = 0; i < il; i++) for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) r[k] = a[j]; return r; }; Object.defineProperty(exports, "__esModule", { value: true }); var OpCode_1 = require("../../OpCode"); var NOT_IMPLEMENTED_YET = "# This block is not implemented yet."; var INDENTATION_RESET_CODE = "%^&*()"; /** * Words which are invalid for any Python identifier to be, when it isn't * on a namespace (like `this` or `my_marty.vars`). */ var PYTHON_RESERVED_WORDS = [ "False", "None", "True", "and", "as", "assert", "async", "await", "break", "class", "continue", "def", "del", "elif", "else", "except", "finally", "for", "from", "global", "if", "import", "in", "is", "lambda", "nonlocal", "not", "or", "pass", "raise", "return", "try", "while", "with", "yield" ]; /** * Input shapes are the basic attribute controlling which of a set of syntaxes * is returned for any given block (or primitive value). Provide an input shape * to inputToPython to specify what kind of value should be provided as the value * in that input. If the content of input does not match the desired shape, for * example because it is a block which returns a different type than desired, * it will be automatically cast to the correct type for use in the block. */ var InputShape; (function (InputShape) { /** * Generic shape indicating that any kind of input is acceptable. The input * will never be cast, and may be null, undefined, or any Python value. */ InputShape["Any"] = "any"; /** * Number input shape. If the input block isn't guaranteed to be a number, * it is automatically wrapped with this.toNumber(), which has particular * behavior to match Scratch. */ InputShape["Number"] = "number"; /** * String input shape. If the input block isn't guaranteed to be a string, * it is automatically wrapped with this.toString(), which is just a wrapper * around the built-in String() op but is written so for consistency. * * The string input shape also guarantees that primitive values which could * be statically converted to a number, e.g. the string "1.234", will NOT be * converted. */ InputShape["String"] = "string"; /** * Boolean input shape. If the input block isn't guaranteed to be a boolean, * it is automatically wrapped with this.toBoolean(), which has particular * behavior to match Scratch. Note that Scratch doesn't have a concept of * boolean primitives (no "true" or "false" blocks, nor a "switch" type * control for directly inputting true/false as in Snap!). */ InputShape["Boolean"] = "boolean"; /** * Special "index" shape, representing an arbitrary number which has been * decremented (decreased by 1). Scratch lists are 1-based while Python * arrays and strings are indexed starting from 0, so all indexes converted * from Scratch must be decreased to match. The "index" shape allows number * primitives to be statically decremented, and blocks which include a plus * or minus operator to automtaically "absorb" the following decrement. */ InputShape["Index"] = "index"; /** * "Stack" block, referring to blocks which can be put one after another and * together represent a sequence of steps. Stack inputs may be empty and * otherwise are one or more blocks. In Python, there's no fundamental * difference between a "function" for reporting values and a "command" for * applying effects, so no additional syntax is required to cast any given * input value to a stack. */ InputShape["Stack"] = "stack"; })(InputShape || (InputShape = {})); function uniqueNameGenerator(reservedNames) { if (reservedNames === void 0) { reservedNames = []; } var usedNames = new Set(reservedNames); return uniqueName; function uniqueName(name) { if (!usedNames.has(name)) { usedNames.add(name); return name; } var numResult = /\d+$/.exec(name); if (numResult === null) { return uniqueName(name + "2"); } return uniqueName(name.slice(0, numResult.index) + (parseInt(numResult[0], 10) + 1)); } } function camelCase(name, upper) { if (upper === void 0) { upper = false; } var validChars = /[^a-zA-Z0-9]/; var ignoredChars = /[']/g; var parts = name.replace(ignoredChars, "").split(validChars); parts = parts.map(function (part) { return part.trim(); }); parts = parts.map(function (part) { return part.slice(0, 1).toUpperCase() + part.slice(1).toLowerCase(); }); if (!upper) { parts[0] = parts[0].toLowerCase(); } var result = parts.join(""); // A blank string is no good if (result.length === 0) { result = "_"; } // Variable names cannot start with a number if (!isNaN(parseInt(result[0], 10))) { result = "_" + result; } return result; } function snake_case(name, upper) { if (upper === void 0) { upper = false; } var valid_chars = /[^a-zA-Z0-9]/; var ignored_chars = /[']/g; var parts = name.replace(ignored_chars, "").split(valid_chars); parts = parts.map(function (part) { return part.trim(); }); parts = parts.map(function (part) { return part.slice(0, 1).toUpperCase() + part.slice(1).toLowerCase(); }); if (!upper) { parts[0] = parts[0].toLowerCase(); } var result = parts.join(""); // A blank string is no good if (result.length === 0) { result = "_"; } // Variable names cannot start with a number if (!isNaN(parseInt(result[0], 10))) { result = "_" + result; } return result; } function toPython(options // prettierConfig: prettier.Options = {} ) { if (options === void 0) { options = {}; } var project = this; var defaultOptions = { leopardJSURL: "https://unpkg.com/leopard@^1/dist/index.esm.js", leopardCSSURL: "https://unpkg.com/leopard@^1/dist/index.min.css", getTargetURL: function (_a) { var name = _a.name, from = _a.from; switch (from) { case "index": return "./" + name + "/" + name + ".py"; case "target": return "../" + name + "/" + name + ".py"; } }, getAssetURL: function (_a) { var type = _a.type, target = _a.target, name = _a.name, ext = _a.ext; switch (type) { case "costume": return "./" + target + "/costumes/" + name + "." + ext; case "sound": return "./" + target + "/sounds/" + name + "." + ext; } }, indexURL: "./index.py", autoplay: true }; options = __assign(__assign({}, defaultOptions), options); // Sprite identifier must not conflict with module-level/global identifiers, // imports and any others that are referenced in generated code. // // Only classes and similar capitalized namespaces need to be listed here: // generated sprite names will never conflict with identifiers whose first // letter is lowercase. (This is also why Python reserved words aren't // listed here - they're all lowercase, so sprite names won't conflict.) var uniqueSpriteName = uniqueNameGenerator(["Color", "Costume", "Sound", "Sprite", "Trigger", "Watcher"]); var targetNameMap = {}; var customBlockArgNameMap = new Map(); var variableNameMap = {}; // ID to unique (Leopard) name for (var _i = 0, _a = __spreadArrays([project.stage], project.sprites); _i < _a.length; _i++) { var target = _a[_i]; var newTargetName = uniqueSpriteName(snake_case(target.name, true)); targetNameMap[target.name] = newTargetName; target.setName(newTargetName); // Variables are uniquely named per-target. These are on an empty namespace // so don't have any conflicts. // // Note: since variables are serialized as properties on an object (this.vars), // these never conflict with reserved Python words like "class" or "new". var uniqueVariableName = uniqueNameGenerator(); for (var _b = 0, _c = __spreadArrays(target.lists, target.variables); _b < _c.length; _b++) { var _d = _c[_b], id = _d.id, name_1 = _d.name; var newName = uniqueVariableName(snake_case(name_1)); variableNameMap[id] = newName; } // Scripts are uniquely named per-target. These are on the sprite's main // namespace, so must not conflict with properties and methods defined on // all sprites/targets by Leopard. // // The list of reserved names is technically different between BaseSprite, // Sprite, and Stage, but all three are considered together here, whatever // kind of target will actually be getting script names here. // // Note: since scripts are serialized as class methods, these never conflict // with reserved Python words like "class" or "new" (they're accessed // with the same typeof syntax, e.g. this.whenGreenFlagClicked). var uniqueScriptName = uniqueNameGenerator([ // Essential data "costumes", "effectChain", "effects", "height", "name", "sounds", "triggers", "vars", "watchers", "width", // Other objects "andClones", "clones", "stage", "sprites", "parent", // Motion "direction", "glide", "goto", "move", "rotationStyle", "x", "y", // Looks "costumeNumber", "costume", "moveAhead", "moveBehind", "say", "sayAndWait", "size", "think", "thinkAndWait", "visible", // Sounds "audioEffects", "getSound", "getSoundsPlayedByMe", "playSoundUntilDone", "startSound", "stapAllOfMySounds", "stopAllSounds", // Control & events "broadcast", "broadcastAndWait", "createClone", "deleteThisClone", "fireBackdropChanged", "wait", "warp", // Opeartors - casting "toNumber", "toBoolean", "toString", "compare", // Operators - strings "stringIncludes", "letterOf", // Operators - numbers "degToRad", "degToScratch", "radToDeg", "radToScratch", "random", "scratchToDeg", "scratchToRad", "normalizeDeg", // Sensing "answer", "askAndWait", "colorTouching", "keyPressed", "loudness", "mouse", "restartTimer", "timer", "touching", // Lists (arrays) "arrayIncludes", "indexInArray", "itemOf", // Pen "clearPen", "penColor", "penDown", "penSize", "stamp" ]); for (var _e = 0, _f = target.scripts; _e < _f.length; _e++) { var script = _f[_e]; script.setName(uniqueScriptName(snake_case(script.name))); var argNameMap = {}; customBlockArgNameMap.set(script, argNameMap); // Parameter names aren't defined on a namespace at all, so must not conflict // with Python reserved words. var uniqueParamName = uniqueNameGenerator(PYTHON_RESERVED_WORDS); for (var _g = 0, _h = script.blocks; _g < _h.length; _g++) { var block = _h[_g]; if (block.opcode === OpCode_1.OpCode.procedures_definition) { for (var _j = 0, _k = block.inputs.ARGUMENTS.value; _j < _k.length; _j++) { var argument = _k[_j]; if (argument.type !== "label") { var newName = uniqueParamName(snake_case(argument.name)); argNameMap[argument.name] = newName; argument.name = newName; } } } } } } // Cache a set of variables which are for the stage since whether or not a variable // is local has to be known every time any variable block is converted. We check the // stage because all non-stage variables are "for this sprite only" and because it's // marginally quicker to iterate over a shorter set than a longer one [an assumption // made about projects with primarily "for this sprite only" variables]. var stageVariables = new Set(); for (var _l = 0, _m = project.stage.variables; _l < _m.length; _l++) { var variable = _m[_l]; stageVariables.add(variable.id); } for (var _o = 0, _p = project.stage.lists; _o < _p.length; _o++) { var list = _p[_o]; stageVariables.add(list.id); } function staticBlockInputToLiteral(value, desiredInputShape) { // Short-circuit for string inputs. These must never return number syntax. if (desiredInputShape === "string") { return JSON.stringify(value); } // Other input shapes which static inputs may fulfill: number, index, any. // These are all OK to return Python number literals for. var asNum = Number(value); if (!isNaN(asNum) && value !== "") { if (desiredInputShape === "index") { return JSON.stringify(asNum - 1); } else { return JSON.stringify(asNum); } } return JSON.stringify(value); } function triggerInitCode(script, target) { var hat = script.hat; if (hat === null) { return null; } var triggerInitStr = function (name, options) { var optionsStr = ""; if (options) { var optionValues = []; for (var _i = 0, _a = Object.entries(options); _i < _a.length; _i++) { var _b = _a[_i], optionName = _b[0], optionValue = _b[1]; optionValues.push(optionName + ": " + optionValue); } optionsStr = ", {" + optionValues.join(", ") + "}"; } return "new Trigger(Trigger." + name + optionsStr + ", this." + script.name + ")"; }; switch (hat.opcode) { case OpCode_1.OpCode.event_whenflagclicked: return triggerInitStr("GREEN_FLAG"); case OpCode_1.OpCode.event_whenkeypressed: return triggerInitStr("KEY_PRESSED", { key: JSON.stringify(hat.inputs.KEY_OPTION.value) }); case OpCode_1.OpCode.event_whenthisspriteclicked: case OpCode_1.OpCode.event_whenstageclicked: return triggerInitStr("CLICKED"); case OpCode_1.OpCode.event_whenbroadcastreceived: return triggerInitStr("BROADCAST", { name: JSON.stringify(hat.inputs.BROADCAST_OPTION.value) }); case OpCode_1.OpCode.event_whengreaterthan: { var valueInput = hat.inputs.VALUE; // If the "greater than" value is a literal, we can include it directly. // Otherwise, it's a block that may depend on sprite state and needs to // be a function. var value = valueInput.type === "block" ? "() => " + blockToPythonWithContext(valueInput.value, target) : staticBlockInputToLiteral(valueInput.value, InputShape.Number); return triggerInitStr(hat.inputs.WHENGREATERTHANMENU.value + "_GREATER_THAN", { VALUE: value }); } case OpCode_1.OpCode.control_start_as_clone: return triggerInitStr("CLONE_START"); default: return null; } } function scriptToPython(script, target) { var body = script.body.map(function (block) { return blockToPythonWithContext(block, target, script); }).join("\n"); if (script.hat && script.hat.opcode === OpCode_1.OpCode.procedures_definition) { return "\n * " + script.name + "(" + script.hat.inputs.ARGUMENTS.value .filter(function (arg) { return arg.type !== "label"; }) .map(function (arg) { return arg.name; }) .join(", ") + ") {\n " + body + "\n }\n "; } return "\n " + body + "\n "; } function blockToPythonWithContext(block, target, script) { return blockToPython(block); function increase(leftSide, input, allowIncrementDecrement) { var n = parseNumber(input); if (n === null) { return leftSide + " += (" + inputToPython(input, InputShape.Number) + ")"; } if (allowIncrementDecrement && n === 1) { return leftSide + " += 1"; } else if (allowIncrementDecrement && n === -1) { return leftSide + " -= 1"; } else if (n >= 0) { return leftSide + " += " + JSON.stringify(n); } else if (n < 0) { return leftSide + " -= " + JSON.stringify(-n); } } function decrease(leftSide, input, allowIncrementDecrement) { var n = parseNumber(input); if (n === null) { return leftSide + " -= (" + inputToPython(input, InputShape.Number) + ")"; } if (allowIncrementDecrement && n === 1) { return leftSide + "--"; } else if (allowIncrementDecrement && n === -1) { return leftSide + "++"; } else if (n > 0) { return leftSide + " -= " + JSON.stringify(n); } else if (n <= 0) { return leftSide + " += " + JSON.stringify(-n); } } function parseNumber(input) { // Returns a number if the input was a primitive (static) value and was // able to be parsed as a number; otherwise, returns null. if (input.type === "block") { return null; } var n = Number(input.value); if (isNaN(n)) { return null; } return n; } function inputToPython(input, desiredInputShape) { // TODO: Right now, inputs can be completely undefined if imported from // the .sb3 format (because sb3 is weird). This little check will replace // undefined inputs with the value `null`. In theory, this should // eventually be removed when the sb3 import script is improved. if (input === undefined) { return "NoneType"; } switch (input.type) { case "block": return blockToPython(input.value, desiredInputShape); case "blocks": return input.value.map(function (block) { return blockToPython(block); }).join("\n"); default: { return staticBlockInputToLiteral(input.value, desiredInputShape); } } } function blockToPython(block, desiredInputShape) { var warp = script && script.hat && script.hat.opcode === OpCode_1.OpCode.procedures_definition && script.hat.inputs.WARP.value; // If the block contains a variable or list dropdown, // get the code to grab that variable now for convenience var selectedVarSource = null; var selectedWatcherSource = null; var varInputId = null; if ("VARIABLE" in block.inputs) { varInputId = block.inputs.VARIABLE.value.id; } else if ("LIST" in block.inputs) { varInputId = block.inputs.LIST.value.id; } if (varInputId) { var newName = variableNameMap[varInputId]; if (target === project.stage || !stageVariables.has(varInputId)) { selectedVarSource = "" + newName; selectedWatcherSource = "" + newName; } else { selectedVarSource = "" + newName; selectedWatcherSource = "" + newName; } } var getCorrectedBoardWhoAmI = function (board) { try { var parsedBoard = JSON.parse(board); try { parsedBoard = JSON.parse(parsedBoard); return "\"" + parsedBoard.whoAmI + "\""; } catch (_a) { return board; } } catch (_b) { return board; } }; var getCorrectedBoard = function (board) { try { var parsedBoard = JSON.parse(board); try { parsedBoard = JSON.parse(parsedBoard); return "\"" + parsedBoard.name + "\""; } catch (_a) { return board; } } catch (_b) { return board; } }; var stage = "" + (target.isStage ? "" : ""); var satisfiesInputShape = null; var blockSource = null; switch (block.opcode) { case OpCode_1.OpCode.motion_movesteps: blockSource = NOT_IMPLEMENTED_YET; break; case OpCode_1.OpCode.motion_turnright: blockSource = NOT_IMPLEMENTED_YET; break; case OpCode_1.OpCode.motion_turnleft: blockSource = NOT_IMPLEMENTED_YET; break; case OpCode_1.OpCode.motion_goto: blockSource = NOT_IMPLEMENTED_YET; break; case OpCode_1.OpCode.motion_gotoxy: blockSource = NOT_IMPLEMENTED_YET; break; case OpCode_1.OpCode.motion_glideto: { blockSource = NOT_IMPLEMENTED_YET; break; } case OpCode_1.OpCode.motion_glidesecstoxy: blockSource = NOT_IMPLEMENTED_YET; break; case OpCode_1.OpCode.motion_pointindirection: blockSource = NOT_IMPLEMENTED_YET; break; case OpCode_1.OpCode.motion_pointtowards: { blockSource = NOT_IMPLEMENTED_YET; break; } case OpCode_1.OpCode.motion_changexby: blockSource = NOT_IMPLEMENTED_YET; break; case OpCode_1.OpCode.motion_setx: blockSource = NOT_IMPLEMENTED_YET; break; case OpCode_1.OpCode.motion_changeyby: blockSource = NOT_IMPLEMENTED_YET; break; case OpCode_1.OpCode.motion_sety: blockSource = NOT_IMPLEMENTED_YET; break; case OpCode_1.OpCode.motion_setrotationstyle: blockSource = NOT_IMPLEMENTED_YET; break; case OpCode_1.OpCode.motion_xposition: blockSource = NOT_IMPLEMENTED_YET; break; case OpCode_1.OpCode.motion_yposition: blockSource = NOT_IMPLEMENTED_YET; break; case OpCode_1.OpCode.motion_direction: blockSource = NOT_IMPLEMENTED_YET; break; // Obsolete no-op blocks: case OpCode_1.OpCode.motion_scroll_right: case OpCode_1.OpCode.motion_scroll_up: case OpCode_1.OpCode.motion_align_scene: blockSource = NOT_IMPLEMENTED_YET; break; case OpCode_1.OpCode.motion_xscroll: case OpCode_1.OpCode.motion_yscroll: blockSource = NOT_IMPLEMENTED_YET; break; case OpCode_1.OpCode.looks_sayforsecs: satisfiesInputShape = InputShape.Stack; blockSource = "my_marty.sayAndWait((" + inputToPython(block.inputs.MESSAGE, InputShape.Any) + "), (" + inputToPython(block.inputs.SECS, InputShape.Number) + "))"; break; case OpCode_1.OpCode.looks_say: satisfiesInputShape = InputShape.Stack; blockSource = "my_marty.say(" + inputToPython(block.inputs.MESSAGE, InputShape.Any) + ")"; break; case OpCode_1.OpCode.looks_thinkforsecs: satisfiesInputShape = InputShape.Stack; blockSource = "my_marty.thinkAndWait((" + inputToPython(block.inputs.MESSAGE, InputShape.Any) + "), (" + inputToPython(block.inputs.SECS, InputShape.Number) + "))"; break; case OpCode_1.OpCode.looks_think: satisfiesInputShape = InputShape.Stack; blockSource = "my_marty.think(" + inputToPython(block.inputs.MESSAGE, InputShape.Any) + ")"; break; case OpCode_1.OpCode.looks_switchcostumeto: satisfiesInputShape = InputShape.Stack; blockSource = "my_marty.costume = (" + inputToPython(block.inputs.COSTUME, InputShape.Any) + ")"; break; case OpCode_1.OpCode.looks_nextcostume: satisfiesInputShape = InputShape.Stack; blockSource = "my_marty.costumeNumber++"; break; case OpCode_1.OpCode.looks_switchbackdropto: satisfiesInputShape = InputShape.Stack; blockSource = stage + ".costume = (" + inputToPython(block.inputs.BACKDROP, InputShape.Any) + ")"; break; case OpCode_1.OpCode.looks_nextbackdrop: satisfiesInputShape = InputShape.Stack; blockSource = stage + ".costumeNumber++"; break; case OpCode_1.OpCode.looks_changesizeby: satisfiesInputShape = InputShape.Stack; blockSource = increase("my_marty.size", block.inputs.CHANGE, false); break; case OpCode_1.OpCode.looks_setsizeto: satisfiesInputShape = InputShape.Stack; blockSource = "my_marty.size = (" + inputToPython(block.inputs.SIZE, InputShape.Number) + ")"; break; case OpCode_1.OpCode.looks_changeeffectby: { var effectName = block.inputs.EFFECT.value.toLowerCase(); satisfiesInputShape = InputShape.Stack; blockSource = increase("my_marty.effects." + effectName, block.inputs.CHANGE, false); break; } case OpCode_1.OpCode.looks_seteffectto: { var effectName = block.inputs.EFFECT.value.toLowerCase(); satisfiesInputShape = InputShape.Stack; blockSource = "my_marty.effects." + effectName + " = " + inputToPython(block.inputs.VALUE, InputShape.Number); break; } case OpCode_1.OpCode.looks_cleargraphiceffects: satisfiesInputShape = InputShape.Stack; blockSource = "my_marty.effects.clear()"; break; case OpCode_1.OpCode.looks_show: satisfiesInputShape = InputShape.Stack; blockSource = "my_marty.visible = true"; break; case OpCode_1.OpCode.looks_hide: satisfiesInputShape = InputShape.Stack; blockSource = "my_marty.visible = false"; break; case OpCode_1.OpCode.looks_gotofrontback: satisfiesInputShape = InputShape.Stack; if (block.inputs.FRONT_BACK.value === "front") { blockSource = "my_marty.moveAhead()"; } else { blockSource = "my_marty.moveBehind()"; } break; case OpCode_1.OpCode.looks_goforwardbackwardlayers: satisfiesInputShape = InputShape.Stack; if (block.inputs.FORWARD_BACKWARD.value === "forward") { blockSource = "my_marty.moveAhead(" + inputToPython(block.inputs.NUM, InputShape.Number) + ")"; } else { blockSource = "my_marty.moveBehind(" + inputToPython(block.inputs.NUM, InputShape.Number) + ")"; } break; // Obsolete no-op blocks: case OpCode_1.OpCode.looks_hideallsprites: case OpCode_1.OpCode.looks_changestretchby: case OpCode_1.OpCode.looks_setstretchto: satisfiesInputShape = InputShape.Stack; blockSource = ""; break; case OpCode_1.OpCode.looks_costumenumbername: switch (block.inputs.NUMBER_NAME.value) { case "name": satisfiesInputShape = InputShape.String; blockSource = "my_marty.costume.name"; break; case "number": default: satisfiesInputShape = InputShape.Number; blockSource = "my_marty.costumeNumber"; break; } break; case OpCode_1.OpCode.looks_backdropnumbername: switch (block.inputs.NUMBER_NAME.value) { case "name": satisfiesInputShape = InputShape.String; blockSource = stage + ".costume.name"; break; case "number": default: satisfiesInputShape = InputShape.Number; blockSource = stage + ".costumeNumber"; break; } break; case OpCode_1.OpCode.looks_size: satisfiesInputShape = InputShape.Number; blockSource = "my_marty.size"; break; case OpCode_1.OpCode.sound_playuntildone: satisfiesInputShape = InputShape.Stack; blockSource = "my_marty.playSoundUntilDone(" + inputToPython(block.inputs.SOUND_MENU, InputShape.Any) + ")"; break; case OpCode_1.OpCode.sound_play: satisfiesInputShape = InputShape.Stack; blockSource = "my_marty.startSound(" + inputToPython(block.inputs.SOUND_MENU, InputShape.Any) + ")"; break; case OpCode_1.OpCode.sound_setvolumeto: satisfiesInputShape = InputShape.Stack; blockSource = "my_marty.audioEffects.volume = " + inputToPython(block.inputs.VOLUME, InputShape.Number); break; case OpCode_1.OpCode.sound_changevolumeby: satisfiesInputShape = InputShape.Stack; blockSource = increase("my_marty.audioEffects.volume", block.inputs.VOLUME, false); break; case OpCode_1.OpCode.sound_volume: satisfiesInputShape = InputShape.Number; blockSource = "my_marty.audioEffects.volume"; break; case OpCode_1.OpCode.sound_seteffectto: { satisfiesInputShape = InputShape.Stack; var value = inputToPython(block.inputs.VALUE, InputShape.Number); if (block.inputs.EFFECT.type === "soundEffect") { blockSource = "my_marty.audioEffects." + block.inputs.EFFECT.value.toLowerCase() + " = " + value; } else { blockSource = "my_marty.audioEffects[" + inputToPython(block.inputs.EFFECT, InputShape.Any) + "] = " + value; } break; } case OpCode_1.OpCode.sound_changeeffectby: { satisfiesInputShape = InputShape.Stack; var value = block.inputs.VALUE; if (block.inputs.EFFECT.type === "soundEffect") { blockSource = increase("my_marty.audioEffects." + block.inputs.EFFECT.value.toLowerCase(), value, false); } else { blockSource = increase("my_marty.audioEffects[" + inputToPython(block.inputs.EFFECT, InputShape.Any) + "]", value, false); } break; } case OpCode_1.OpCode.sound_cleareffects: satisfiesInputShape = InputShape.Stack; blockSource = "my_marty.audioEffects.clear()"; break; case OpCode_1.OpCode.sound_stopallsounds: satisfiesInputShape = InputShape.Stack; blockSource = "my_marty.stopAllSounds()"; break; case OpCode_1.OpCode.event_broadcast: satisfiesInputShape = InputShape.Stack; blockSource = "my_marty.broadcast(" + inputToPython(block.inputs.BROADCAST_INPUT, InputShape.String) + ")"; break; case OpCode_1.OpCode.event_broadcastandwait: satisfiesInputShape = InputShape.Stack; blockSource = "my_marty.broadcastAndWait(" + inputToPython(block.inputs.BROADCAST_INPUT, InputShape.String) + ")"; break; case OpCode_1.OpCode.control_wait: satisfiesInputShape = InputShape.Stack; blockSource = "time.sleep(" + inputToPython(block.inputs.DURATION, InputShape.Number) + ")"; break; case OpCode_1.OpCode.control_repeat: satisfiesInputShape = InputShape.Stack; blockSource = "for i in range(" + inputToPython(block.inputs.TIMES, InputShape.Number) + "):\n " + inputToPython(block.inputs.SUBSTACK, InputShape.Stack) + "\n " + (warp ? "" : INDENTATION_RESET_CODE); break; case OpCode_1.OpCode.control_forever: satisfiesInputShape = InputShape.Stack; blockSource = "while True:\n " + inputToPython(block.inputs.SUBSTACK, InputShape.Stack) + "\n " + (warp ? "" : INDENTATION_RESET_CODE) + " \n "; break; case OpCode_1.OpCode.control_if: satisfiesInputShape = InputShape.Stack; blockSource = "if " + inputToPython(block.inputs.CONDITION, InputShape.Boolean) + ":\n " + inputToPython(block.inputs.SUBSTACK, InputShape.Stack); break; case OpCode_1.OpCode.control_if_else: satisfiesInputShape = InputShape.Stack; blockSource = "if " + inputToPython(block.inputs.CONDITION, InputShape.Boolean) + ":\n " + inputToPython(block.inputs.SUBSTACK, InputShape.Stack) + "\n else:\n " + inputToPython(block.inputs.SUBSTACK2, InputShape.Stack); break; case OpCode_1.OpCode.control_wait_until: satisfiesInputShape = InputShape.Stack; blockSource = "while not " + inputToPython(block.inputs.CONDITION, InputShape.Boolean) + ": \n "; break; case OpCode_1.OpCode.control_repeat_until: satisfiesInputShape = InputShape.Stack; blockSource = "while not " + inputToPython(block.inputs.CONDITION, InputShape.Boolean) + ":\n " + inputToPython(block.inputs.SUBSTACK, InputShape.Stack) + "\n " + (warp ? "" : INDENTATION_RESET_CODE); break; case OpCode_1.OpCode.control_while: satisfiesInputShape = InputShape.Stack; blockSource = "while " + inputToPython(block.inputs.CONDITION, InputShape.Boolean) + ":\n " + inputToPython(block.inputs.SUBSTACK, InputShape.Stack) + "\n " + (warp ? "" : INDENTATION_RESET_CODE) + "\n "; break; case OpCode_1.OpCode.control_for_each: satisfiesInputShape = InputShape.Stack; blockSource = "for (" + selectedVarSource + " = 1; " + selectedVarSource + " <= (" + inputToPython(block.inputs.VALUE, InputShape.Number) + ") " + selectedVarSource + "++) {\n " + inputToPython(block.inputs.SUBSTACK, InputShape.Stack) + "\n " + (warp ? "" : INDENTATION_RESET_CODE) + "\n }"; break; case OpCode_1.OpCode.control_all_at_once: satisfiesInputShape = InputShape.Stack; blockSource = inputToPython(block.inputs.SUBSTACK, InputShape.Stack); break; case OpCode_1.OpCode.control_stop: satisfiesInputShape = InputShape.Stack; switch (block.inputs.STOP_OPTION.value) { case "this script": blockSource = "return"; break; default: blockSource = "# TODO: Implement stop " + block.inputs.STOP_OPTION.value; break; } break; case OpCode_1.OpCode.control_create_clone_of: blockSource = NOT_IMPLEMENTED_YET; break; case OpCode_1.OpCode.control_delete_this_clone: blockSource = NOT_IMPLEMENTED_YET; break; case OpCode_1.OpCode.control_get_counter: satisfiesInputShape = InputShape.Stack; blockSource = stage + ".__counter"; break; case OpCode_1.OpCode.control_incr_counter: satisfiesInputShape = InputShape.Stack; blockSource = stage + ".__counter++"; break; case OpCode_1.OpCode.control_clear_counter: satisfiesInputShape = InputShape.Stack; blockSource = stage + ".__counter = 0"; break; case OpCode_1.OpCode.sensing_touchingobject: satisfiesInputShape = InputShape.Boolean; switch (block.inputs.TOUCHINGOBJECTMENU.value) { case "_mouse_": blockSource = "my_marty.touching(\"mouse\")"; break; case "_edge_": blockSource = "my_marty.touching(\"edge\")"; break; default: blockSource = "my_marty.touching(this.sprites[" + JSON.stringify(targetNameMap[block.inputs.TOUCHINGOBJECTMENU.value]) + "].andClones())"; break; } break; case OpCode_1.OpCode.sensing_touchingcolor: satisfiesInputShape = InputShape.Boolean; if (block.inputs.COLOR.type === "color") { var _a = block.inputs.COLOR.value, r = _a.r, g = _a.g, b = _a.b; blockSource = "my_marty.touching(Color.rgb(" + r + ", " + g + ", " + b + "))"; } else { blockSource = "my_marty.touching(Color.num(" + inputToPython(block.inputs.COLOR, InputShape.Number) + "))"; } break; case OpCode_1.OpCode.sensing_coloristouchingcolor: { var color1 = void 0; var color2 = void 0; if (block.inputs.COLOR.type === "color") { var _b = block.inputs.COLOR.value, r = _b.r, g = _b.g, b = _b.b; color1 = "Color.rgb(" + r + ", " + g + ", " + b + ")"; } else { color1 = "Color.num(" + inputToPython(block.inputs.COLOR, InputShape.Number) + ")"; } if (block.inputs.COLOR2.type === "color") { var _c = block.inputs.COLOR2.value, r = _c.r, g = _c.g, b = _c.b; color2 = "Color.rgb(" + r + ", " + g + ", " + b + ")"; } else { color2 = "Color.num(" + inputToPython(block.inputs.COLOR2, InputShape.Number) + ")"; } satisfiesInputShape = InputShape.Boolean; blockSource = "my_marty.colorTouching((" + color1 + "), (" + color2 + "))"; break; } case OpCode_1.OpCode.sensing_distanceto: { var coords = void 0; switch (block.inputs.DISTANCETOMENU.value) { case "_mouse_": coords = "my_marty.mouse"; break; default: coords = "my_marty.sprites[" + JSON.stringify(targetNameMap[block.inputs.DISTANCETOMENU.value]) + "]"; break; } satisfiesInputShape = InputShape.Number; blockSource = "(Math.hypot(" + coords + ".x - this.x, " + coords + ".y - this.y))"; break; } case OpCode_1.OpCode.sensing_askandwait: satisfiesInputShape = InputShape.Stack; blockSource = "my_marty.askAndWait(" + inputToPython(block.inputs.QUESTION, InputShape.Any) + ")"; break; case OpCode_1.OpCode.sensing_answer: satisfiesInputShape = InputShape.String; blockSource = "my_marty.answer"; break; case OpCode_1.OpCode.sensing_keypressed: satisfiesInputShape = InputShape.Boolean; blockSource = "my_marty.keyPressed(" + inputToPython(block.inputs.KEY_OPTION, InputShape.String) + ")"; break; case OpCode_1.OpCode.sensing_mousedown: satisfiesInputShape = InputShape.Boolean; blockSource = "my_marty.mouse.down"; break; case OpCode_1.OpCode.sensing_mousex: satisfiesInputShape = InputShape.Number; blockSource = "my_marty.mouse.x"; break; case OpCode_1.OpCode.sensing_mousey: satisfiesInputShape = InputShape.Number; blockSource = "my_marty.mouse.y"; break; case OpCode_1.OpCode.sensing_loudness: satisfiesInputShape = InputShape.Number; blockSource = "my_marty.loudness"; break; case OpCode_1.OpCode.sensing_timer: satisfiesInputShape = InputShape.Number; blockSource = "my_marty.timer"; break; case OpCode_1.OpCode.sensing_resettimer: satisfiesInputShape = InputShape.Stack; blockSource = "my_marty.restartTimer()"; break; case OpCode_1.OpCode.sensing_of: { var propName = void 0; switch (block.inputs.PROPERTY.value) { case "x position": propName = "x"; satisfiesInputShape = InputShape.Number; break; case "y position": propName = "y"; satisfiesInputShape = InputShape.Number; break; case "direction": propName = "direction"; satisfiesInputShape = InputShape.Number; break; case "costume #": case "backdrop #": propName = "costumeNumber"; satisfiesInputShape = InputShape.Number; break; case "costume name": case "backdrop name": propName = "costume.name"; satisfiesInputShape = InputShape.String; break; case "size": propName = "size"; satisfiesInputShape = InputShape.Number; break; case "volume": propName = null; break; default: { var varOwner = project.stage; if (block.inputs.OBJECT.value !== "_stage_") { varOwner = project.sprites.find(function (sprite) { return sprite.name === targetNameMap[block.inputs.OBJECT.value]; }); } // "of" block gets variables by name, not ID, using lookupVariableByNameAndType in scratch-vm. var variable = varOwner.variables.find(function (variable) { return variable.name === block.inputs.PROPERTY.value; }); var newName = variableNameMap[variable.id]; propName = "vars." + newName; satisfiesInputShape = InputShape.Any; break; } } if (propName === null) { blockSource = "# Cannot access property " + block.inputs.PROPERTY.value + " of target"; break; } var targetObj = void 0; if (block.inputs.OBJECT.value === "_stage_") { targetObj = "my_marty.stage"; } else { targetObj = "my_marty.sprites[" + JSON.stringify(targetNameMap[block.inputs.OBJECT.value]) + "]"; } blockSource = targetObj + "." + propName; break; } case OpCode_1.OpCode.sensing_current: satisfiesInputShape = InputShape.Number; switch (block.inputs.CURRENTMENU.value) { case "YEAR": blockSource = "(new Date().getFullYear())"; break; case "MONTH": blockSource = "(new Date().getMonth() + 1)"; break; case "DATE": blockSource = "(new Date().getDate())"; break; case "DAYOFWEEK": blockSource = "(new Date().getDay() + 1)"; break; case "HOUR":