@robotical/scratch-to-python-transpiler
Version:
Transpile Scratch project files to python code
1,055 lines (1,054 loc) • 109 kB
JavaScript
"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":