Spaces:
Runtime error
Runtime error
| /** | |
| * @fileoverview | |
| * The specMap below handles a few pieces of "translation" work between | |
| * the SB2 JSON format and the data we need to run a project | |
| * in the Scratch 3.0 VM. | |
| * Notably: | |
| * - Map 2.0 and 1.4 opcodes (forward:) into 3.0-format (motion_movesteps). | |
| * - Map ordered, unnamed args to unordered, named inputs and fields. | |
| * Keep this up-to-date as 3.0 blocks are renamed, changed, etc. | |
| * Originally this was generated largely by a hand-guided scripting process. | |
| * The relevant data lives here: | |
| * https://github.com/LLK/scratch-flash/blob/master/src/Specs.as | |
| * (for the old opcode and argument order). | |
| * and here: | |
| * https://github.com/LLK/scratch-blocks/tree/develop/blocks_vertical | |
| * (for the new opcodes and argument names). | |
| * and here: | |
| * https://github.com/LLK/scratch-blocks/blob/develop/tests/ | |
| * (for the shadow blocks created for each block). | |
| * I started with the `commands` array in Specs.as, and discarded irrelevant | |
| * properties. By hand, I matched the opcode name to the 3.0 opcode. | |
| * Finally, I filled in the expected arguments as below. | |
| */ | |
| const Variable = require('../engine/variable'); | |
| /** | |
| * @typedef {object} SB2SpecMap_blockInfo | |
| * @property {string} opcode - the Scratch 3.0 block opcode. Use 'extensionID.opcode' for extension opcodes. | |
| * @property {Array.<SB2SpecMap_argInfo>} argMap - metadata for this block's arguments. | |
| */ | |
| /** | |
| * @typedef {object} SB2SpecMap_argInfo | |
| * @property {string} type - the type of this arg (such as 'input' or 'field') | |
| * @property {string} inputOp - the scratch-blocks shadow type for this arg | |
| * @property {string} inputName - the name this argument will take when provided to the block implementation | |
| */ | |
| /** | |
| * Mapping of Scratch 2.0 opcode to Scratch 3.0 block metadata. | |
| * @type {object.<SB2SpecMap_blockInfo>} | |
| */ | |
| const specMap = { | |
| 'forward:': { | |
| opcode: 'motion_movesteps', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'STEPS' | |
| } | |
| ] | |
| }, | |
| 'turnRight:': { | |
| opcode: 'motion_turnright', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'DEGREES' | |
| } | |
| ] | |
| }, | |
| 'turnLeft:': { | |
| opcode: 'motion_turnleft', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'DEGREES' | |
| } | |
| ] | |
| }, | |
| 'heading:': { | |
| opcode: 'motion_pointindirection', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_angle', | |
| inputName: 'DIRECTION' | |
| } | |
| ] | |
| }, | |
| 'pointTowards:': { | |
| opcode: 'motion_pointtowards', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'motion_pointtowards_menu', | |
| inputName: 'TOWARDS' | |
| } | |
| ] | |
| }, | |
| 'gotoX:y:': { | |
| opcode: 'motion_gotoxy', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'X' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'Y' | |
| } | |
| ] | |
| }, | |
| 'gotoSpriteOrMouse:': { | |
| opcode: 'motion_goto', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'motion_goto_menu', | |
| inputName: 'TO' | |
| } | |
| ] | |
| }, | |
| 'glideSecs:toX:y:elapsed:from:': { | |
| opcode: 'motion_glidesecstoxy', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'SECS' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'X' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'Y' | |
| } | |
| ] | |
| }, | |
| 'changeXposBy:': { | |
| opcode: 'motion_changexby', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'DX' | |
| } | |
| ] | |
| }, | |
| 'xpos:': { | |
| opcode: 'motion_setx', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'X' | |
| } | |
| ] | |
| }, | |
| 'changeYposBy:': { | |
| opcode: 'motion_changeyby', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'DY' | |
| } | |
| ] | |
| }, | |
| 'ypos:': { | |
| opcode: 'motion_sety', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'Y' | |
| } | |
| ] | |
| }, | |
| 'bounceOffEdge': { | |
| opcode: 'motion_ifonedgebounce', | |
| argMap: [ | |
| ] | |
| }, | |
| 'setRotationStyle': { | |
| opcode: 'motion_setrotationstyle', | |
| argMap: [ | |
| { | |
| type: 'field', | |
| fieldName: 'STYLE' | |
| } | |
| ] | |
| }, | |
| 'xpos': { | |
| opcode: 'motion_xposition', | |
| argMap: [ | |
| ] | |
| }, | |
| 'ypos': { | |
| opcode: 'motion_yposition', | |
| argMap: [ | |
| ] | |
| }, | |
| 'heading': { | |
| opcode: 'motion_direction', | |
| argMap: [ | |
| ] | |
| }, | |
| 'scrollRight': { | |
| opcode: 'motion_scroll_right', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'DISTANCE' | |
| } | |
| ] | |
| }, | |
| 'scrollUp': { | |
| opcode: 'motion_scroll_up', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'DISTANCE' | |
| } | |
| ] | |
| }, | |
| 'scrollAlign': { | |
| opcode: 'motion_align_scene', | |
| argMap: [ | |
| { | |
| type: 'field', | |
| fieldName: 'ALIGNMENT' | |
| } | |
| ] | |
| }, | |
| 'xScroll': { | |
| opcode: 'motion_xscroll', | |
| argMap: [ | |
| ] | |
| }, | |
| 'yScroll': { | |
| opcode: 'motion_yscroll', | |
| argMap: [ | |
| ] | |
| }, | |
| 'say:duration:elapsed:from:': { | |
| opcode: 'looks_sayforsecs', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'text', | |
| inputName: 'MESSAGE' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'SECS' | |
| } | |
| ] | |
| }, | |
| 'say:': { | |
| opcode: 'looks_say', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'text', | |
| inputName: 'MESSAGE' | |
| } | |
| ] | |
| }, | |
| 'think:duration:elapsed:from:': { | |
| opcode: 'looks_thinkforsecs', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'text', | |
| inputName: 'MESSAGE' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'SECS' | |
| } | |
| ] | |
| }, | |
| 'think:': { | |
| opcode: 'looks_think', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'text', | |
| inputName: 'MESSAGE' | |
| } | |
| ] | |
| }, | |
| 'show': { | |
| opcode: 'looks_show', | |
| argMap: [ | |
| ] | |
| }, | |
| 'hide': { | |
| opcode: 'looks_hide', | |
| argMap: [ | |
| ] | |
| }, | |
| 'hideAll': { | |
| opcode: 'looks_hideallsprites', | |
| argMap: [ | |
| ] | |
| }, | |
| 'lookLike:': { | |
| opcode: 'looks_switchcostumeto', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'looks_costume', | |
| inputName: 'COSTUME' | |
| } | |
| ] | |
| }, | |
| 'nextCostume': { | |
| opcode: 'looks_nextcostume', | |
| argMap: [ | |
| ] | |
| }, | |
| 'startScene': { | |
| opcode: 'looks_switchbackdropto', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'looks_backdrops', | |
| inputName: 'BACKDROP' | |
| } | |
| ] | |
| }, | |
| 'changeGraphicEffect:by:': { | |
| opcode: 'looks_changeeffectby', | |
| argMap: [ | |
| { | |
| type: 'field', | |
| fieldName: 'EFFECT' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'CHANGE' | |
| } | |
| ] | |
| }, | |
| 'setGraphicEffect:to:': { | |
| opcode: 'looks_seteffectto', | |
| argMap: [ | |
| { | |
| type: 'field', | |
| fieldName: 'EFFECT' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'VALUE' | |
| } | |
| ] | |
| }, | |
| 'filterReset': { | |
| opcode: 'looks_cleargraphiceffects', | |
| argMap: [ | |
| ] | |
| }, | |
| 'changeSizeBy:': { | |
| opcode: 'looks_changesizeby', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'CHANGE' | |
| } | |
| ] | |
| }, | |
| 'setSizeTo:': { | |
| opcode: 'looks_setsizeto', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'SIZE' | |
| } | |
| ] | |
| }, | |
| 'changeStretchBy:': { | |
| opcode: 'looks_changestretchby', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'CHANGE' | |
| } | |
| ] | |
| }, | |
| 'setStretchTo:': { | |
| opcode: 'looks_setstretchto', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'STRETCH' | |
| } | |
| ] | |
| }, | |
| 'comeToFront': { | |
| opcode: 'looks_gotofrontback', | |
| argMap: [ | |
| ] | |
| }, | |
| 'goBackByLayers:': { | |
| opcode: 'looks_goforwardbackwardlayers', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_integer', | |
| inputName: 'NUM' | |
| } | |
| ] | |
| }, | |
| 'costumeIndex': { | |
| opcode: 'looks_costumenumbername', | |
| argMap: [ | |
| ] | |
| }, | |
| 'costumeName': { | |
| opcode: 'looks_costumenumbername', | |
| argMap: [ | |
| ] | |
| }, | |
| 'sceneName': { | |
| opcode: 'looks_backdropnumbername', | |
| argMap: [ | |
| ] | |
| }, | |
| 'scale': { | |
| opcode: 'looks_size', | |
| argMap: [ | |
| ] | |
| }, | |
| 'startSceneAndWait': { | |
| opcode: 'looks_switchbackdroptoandwait', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'looks_backdrops', | |
| inputName: 'BACKDROP' | |
| } | |
| ] | |
| }, | |
| 'nextScene': { | |
| opcode: 'looks_nextbackdrop', | |
| argMap: [ | |
| ] | |
| }, | |
| 'backgroundIndex': { | |
| opcode: 'looks_backdropnumbername', | |
| argMap: [ | |
| ] | |
| }, | |
| 'playSound:': { | |
| opcode: 'sound_play', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'sound_sounds_menu', | |
| inputName: 'SOUND_MENU' | |
| } | |
| ] | |
| }, | |
| 'doPlaySoundAndWait': { | |
| opcode: 'sound_playuntildone', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'sound_sounds_menu', | |
| inputName: 'SOUND_MENU' | |
| } | |
| ] | |
| }, | |
| 'stopAllSounds': { | |
| opcode: 'sound_stopallsounds', | |
| argMap: [ | |
| ] | |
| }, | |
| 'playDrum': { | |
| opcode: 'music_playDrumForBeats', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'music_menu_DRUM', | |
| inputName: 'DRUM' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'BEATS' | |
| } | |
| ] | |
| }, | |
| 'drum:duration:elapsed:from:': { | |
| opcode: 'music_midiPlayDrumForBeats', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'DRUM' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'BEATS' | |
| } | |
| ] | |
| }, | |
| 'rest:elapsed:from:': { | |
| opcode: 'music_restForBeats', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'BEATS' | |
| } | |
| ] | |
| }, | |
| 'noteOn:duration:elapsed:from:': { | |
| opcode: 'music_playNoteForBeats', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'note', | |
| inputName: 'NOTE' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'BEATS' | |
| } | |
| ] | |
| }, | |
| 'instrument:': { | |
| opcode: 'music_setInstrument', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'music_menu_INSTRUMENT', | |
| inputName: 'INSTRUMENT' | |
| } | |
| ] | |
| }, | |
| 'midiInstrument:': { | |
| opcode: 'music_midiSetInstrument', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'INSTRUMENT' | |
| } | |
| ] | |
| }, | |
| 'changeVolumeBy:': { | |
| opcode: 'sound_changevolumeby', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'VOLUME' | |
| } | |
| ] | |
| }, | |
| 'setVolumeTo:': { | |
| opcode: 'sound_setvolumeto', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'VOLUME' | |
| } | |
| ] | |
| }, | |
| 'volume': { | |
| opcode: 'sound_volume', | |
| argMap: [ | |
| ] | |
| }, | |
| 'changeTempoBy:': { | |
| opcode: 'music_changeTempo', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'TEMPO' | |
| } | |
| ] | |
| }, | |
| 'setTempoTo:': { | |
| opcode: 'music_setTempo', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'TEMPO' | |
| } | |
| ] | |
| }, | |
| 'tempo': { | |
| opcode: 'music_getTempo', | |
| argMap: [ | |
| ] | |
| }, | |
| 'clearPenTrails': { | |
| opcode: 'pen_clear', | |
| argMap: [ | |
| ] | |
| }, | |
| 'stampCostume': { | |
| opcode: 'pen_stamp', | |
| argMap: [ | |
| ] | |
| }, | |
| 'putPenDown': { | |
| opcode: 'pen_penDown', | |
| argMap: [ | |
| ] | |
| }, | |
| 'putPenUp': { | |
| opcode: 'pen_penUp', | |
| argMap: [ | |
| ] | |
| }, | |
| 'penColor:': { | |
| opcode: 'pen_setPenColorToColor', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'colour_picker', | |
| inputName: 'COLOR' | |
| } | |
| ] | |
| }, | |
| 'changePenHueBy:': { | |
| opcode: 'pen_changePenHueBy', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'HUE' | |
| } | |
| ] | |
| }, | |
| 'setPenHueTo:': { | |
| opcode: 'pen_setPenHueToNumber', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'HUE' | |
| } | |
| ] | |
| }, | |
| 'changePenShadeBy:': { | |
| opcode: 'pen_changePenShadeBy', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'SHADE' | |
| } | |
| ] | |
| }, | |
| 'setPenShadeTo:': { | |
| opcode: 'pen_setPenShadeToNumber', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'SHADE' | |
| } | |
| ] | |
| }, | |
| 'changePenSizeBy:': { | |
| opcode: 'pen_changePenSizeBy', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'SIZE' | |
| } | |
| ] | |
| }, | |
| 'penSize:': { | |
| opcode: 'pen_setPenSizeTo', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'SIZE' | |
| } | |
| ] | |
| }, | |
| 'senseVideoMotion': { | |
| opcode: 'videoSensing_videoOn', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'videoSensing_menu_ATTRIBUTE', | |
| inputName: 'ATTRIBUTE' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'videoSensing_menu_SUBJECT', | |
| inputName: 'SUBJECT' | |
| } | |
| ] | |
| }, | |
| 'whenGreenFlag': { | |
| opcode: 'event_whenflagclicked', | |
| argMap: [ | |
| ] | |
| }, | |
| 'whenKeyPressed': { | |
| opcode: 'event_whenkeypressed', | |
| argMap: [ | |
| { | |
| type: 'field', | |
| fieldName: 'KEY_OPTION' | |
| } | |
| ] | |
| }, | |
| 'whenClicked': { | |
| opcode: 'event_whenthisspriteclicked', | |
| argMap: [ | |
| ] | |
| }, | |
| 'whenSceneStarts': { | |
| opcode: 'event_whenbackdropswitchesto', | |
| argMap: [ | |
| { | |
| type: 'field', | |
| fieldName: 'BACKDROP' | |
| } | |
| ] | |
| }, | |
| 'whenSensorGreaterThan': ([, sensor]) => { | |
| if (sensor === 'video motion') { | |
| return { | |
| opcode: 'videoSensing_whenMotionGreaterThan', | |
| argMap: [ | |
| // skip the first arg, since we converted to a video specific sensing block | |
| {}, | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'REFERENCE' | |
| } | |
| ] | |
| }; | |
| } | |
| return { | |
| opcode: 'event_whengreaterthan', | |
| argMap: [ | |
| { | |
| type: 'field', | |
| fieldName: 'WHENGREATERTHANMENU' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'VALUE' | |
| } | |
| ] | |
| }; | |
| }, | |
| 'whenIReceive': { | |
| opcode: 'event_whenbroadcastreceived', | |
| argMap: [ | |
| { | |
| type: 'field', | |
| fieldName: 'BROADCAST_OPTION', | |
| variableType: Variable.BROADCAST_MESSAGE_TYPE | |
| } | |
| ] | |
| }, | |
| 'broadcast:': { | |
| opcode: 'event_broadcast', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'event_broadcast_menu', | |
| inputName: 'BROADCAST_INPUT', | |
| variableType: Variable.BROADCAST_MESSAGE_TYPE | |
| } | |
| ] | |
| }, | |
| 'doBroadcastAndWait': { | |
| opcode: 'event_broadcastandwait', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'event_broadcast_menu', | |
| inputName: 'BROADCAST_INPUT', | |
| variableType: Variable.BROADCAST_MESSAGE_TYPE | |
| } | |
| ] | |
| }, | |
| 'wait:elapsed:from:': { | |
| opcode: 'control_wait', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_positive_number', | |
| inputName: 'DURATION' | |
| } | |
| ] | |
| }, | |
| 'doRepeat': { | |
| opcode: 'control_repeat', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_whole_number', | |
| inputName: 'TIMES' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'substack', | |
| inputName: 'SUBSTACK' | |
| } | |
| ] | |
| }, | |
| 'doForever': { | |
| opcode: 'control_forever', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'substack', | |
| inputName: 'SUBSTACK' | |
| } | |
| ] | |
| }, | |
| 'doIf': { | |
| opcode: 'control_if', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'boolean', | |
| inputName: 'CONDITION' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'substack', | |
| inputName: 'SUBSTACK' | |
| } | |
| ] | |
| }, | |
| 'doIfElse': { | |
| opcode: 'control_if_else', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'boolean', | |
| inputName: 'CONDITION' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'substack', | |
| inputName: 'SUBSTACK' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'substack', | |
| inputName: 'SUBSTACK2' | |
| } | |
| ] | |
| }, | |
| 'doWaitUntil': { | |
| opcode: 'control_wait_until', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'boolean', | |
| inputName: 'CONDITION' | |
| } | |
| ] | |
| }, | |
| 'doUntil': { | |
| opcode: 'control_repeat_until', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'boolean', | |
| inputName: 'CONDITION' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'substack', | |
| inputName: 'SUBSTACK' | |
| } | |
| ] | |
| }, | |
| 'doWhile': { | |
| opcode: 'control_while', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'boolean', | |
| inputName: 'CONDITION' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'substack', | |
| inputName: 'SUBSTACK' | |
| } | |
| ] | |
| }, | |
| 'doForLoop': { | |
| opcode: 'control_for_each', | |
| argMap: [ | |
| { | |
| type: 'field', | |
| fieldName: 'VARIABLE' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'text', | |
| inputName: 'VALUE' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'substack', | |
| inputName: 'SUBSTACK' | |
| } | |
| ] | |
| }, | |
| 'stopScripts': { | |
| opcode: 'control_stop', | |
| argMap: [ | |
| { | |
| type: 'field', | |
| fieldName: 'STOP_OPTION' | |
| } | |
| ] | |
| }, | |
| 'whenCloned': { | |
| opcode: 'control_start_as_clone', | |
| argMap: [ | |
| ] | |
| }, | |
| 'createCloneOf': { | |
| opcode: 'control_create_clone_of', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'control_create_clone_of_menu', | |
| inputName: 'CLONE_OPTION' | |
| } | |
| ] | |
| }, | |
| 'deleteClone': { | |
| opcode: 'control_delete_this_clone', | |
| argMap: [ | |
| ] | |
| }, | |
| 'COUNT': { | |
| opcode: 'control_get_counter', | |
| argMap: [ | |
| ] | |
| }, | |
| 'INCR_COUNT': { | |
| opcode: 'control_incr_counter', | |
| argMap: [ | |
| ] | |
| }, | |
| 'CLR_COUNT': { | |
| opcode: 'control_clear_counter', | |
| argMap: [ | |
| ] | |
| }, | |
| 'warpSpeed': { | |
| opcode: 'control_all_at_once', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'substack', | |
| inputName: 'SUBSTACK' | |
| } | |
| ] | |
| }, | |
| 'touching:': { | |
| opcode: 'sensing_touchingobject', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'sensing_touchingobjectmenu', | |
| inputName: 'TOUCHINGOBJECTMENU' | |
| } | |
| ] | |
| }, | |
| 'touchingColor:': { | |
| opcode: 'sensing_touchingcolor', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'colour_picker', | |
| inputName: 'COLOR' | |
| } | |
| ] | |
| }, | |
| 'color:sees:': { | |
| opcode: 'sensing_coloristouchingcolor', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'colour_picker', | |
| inputName: 'COLOR' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'colour_picker', | |
| inputName: 'COLOR2' | |
| } | |
| ] | |
| }, | |
| 'distanceTo:': { | |
| opcode: 'sensing_distanceto', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'sensing_distancetomenu', | |
| inputName: 'DISTANCETOMENU' | |
| } | |
| ] | |
| }, | |
| 'doAsk': { | |
| opcode: 'sensing_askandwait', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'text', | |
| inputName: 'QUESTION' | |
| } | |
| ] | |
| }, | |
| 'answer': { | |
| opcode: 'sensing_answer', | |
| argMap: [ | |
| ] | |
| }, | |
| 'keyPressed:': { | |
| opcode: 'sensing_keypressed', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'sensing_keyoptions', | |
| inputName: 'KEY_OPTION' | |
| } | |
| ] | |
| }, | |
| 'mousePressed': { | |
| opcode: 'sensing_mousedown', | |
| argMap: [ | |
| ] | |
| }, | |
| 'mouseX': { | |
| opcode: 'sensing_mousex', | |
| argMap: [ | |
| ] | |
| }, | |
| 'mouseY': { | |
| opcode: 'sensing_mousey', | |
| argMap: [ | |
| ] | |
| }, | |
| 'soundLevel': { | |
| opcode: 'sensing_loudness', | |
| argMap: [ | |
| ] | |
| }, | |
| 'isLoud': { | |
| opcode: 'sensing_loud', | |
| argMap: [ | |
| ] | |
| }, | |
| // 'senseVideoMotion': { | |
| // opcode: 'sensing_videoon', | |
| // argMap: [ | |
| // { | |
| // type: 'input', | |
| // inputOp: 'sensing_videoonmenuone', | |
| // inputName: 'VIDEOONMENU1' | |
| // }, | |
| // { | |
| // type: 'input', | |
| // inputOp: 'sensing_videoonmenutwo', | |
| // inputName: 'VIDEOONMENU2' | |
| // } | |
| // ] | |
| // }, | |
| 'setVideoState': { | |
| opcode: 'videoSensing_videoToggle', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'videoSensing_menu_VIDEO_STATE', | |
| inputName: 'VIDEO_STATE' | |
| } | |
| ] | |
| }, | |
| 'setVideoTransparency': { | |
| opcode: 'videoSensing_setVideoTransparency', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'TRANSPARENCY' | |
| } | |
| ] | |
| }, | |
| 'timer': { | |
| opcode: 'sensing_timer', | |
| argMap: [ | |
| ] | |
| }, | |
| 'timerReset': { | |
| opcode: 'sensing_resettimer', | |
| argMap: [ | |
| ] | |
| }, | |
| 'getAttribute:of:': { | |
| opcode: 'sensing_of', | |
| argMap: [ | |
| { | |
| type: 'field', | |
| fieldName: 'PROPERTY' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'sensing_of_object_menu', | |
| inputName: 'OBJECT' | |
| } | |
| ] | |
| }, | |
| 'timeAndDate': { | |
| opcode: 'sensing_current', | |
| argMap: [ | |
| { | |
| type: 'field', | |
| fieldName: 'CURRENTMENU' | |
| } | |
| ] | |
| }, | |
| 'timestamp': { | |
| opcode: 'sensing_dayssince2000', | |
| argMap: [ | |
| ] | |
| }, | |
| 'getUserName': { | |
| opcode: 'sensing_username', | |
| argMap: [ | |
| ] | |
| }, | |
| 'getUserId': { | |
| opcode: 'sensing_userid', | |
| argMap: [ | |
| ] | |
| }, | |
| '+': { | |
| opcode: 'operator_add', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'NUM1' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'NUM2' | |
| } | |
| ] | |
| }, | |
| '-': { | |
| opcode: 'operator_subtract', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'NUM1' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'NUM2' | |
| } | |
| ] | |
| }, | |
| '*': { | |
| opcode: 'operator_multiply', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'NUM1' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'NUM2' | |
| } | |
| ] | |
| }, | |
| '/': { | |
| opcode: 'operator_divide', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'NUM1' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'NUM2' | |
| } | |
| ] | |
| }, | |
| 'randomFrom:to:': { | |
| opcode: 'operator_random', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'FROM' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'TO' | |
| } | |
| ] | |
| }, | |
| '<': { | |
| opcode: 'operator_lt', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'text', | |
| inputName: 'OPERAND1' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'text', | |
| inputName: 'OPERAND2' | |
| } | |
| ] | |
| }, | |
| '=': { | |
| opcode: 'operator_equals', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'text', | |
| inputName: 'OPERAND1' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'text', | |
| inputName: 'OPERAND2' | |
| } | |
| ] | |
| }, | |
| '>': { | |
| opcode: 'operator_gt', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'text', | |
| inputName: 'OPERAND1' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'text', | |
| inputName: 'OPERAND2' | |
| } | |
| ] | |
| }, | |
| '&': { | |
| opcode: 'operator_and', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'boolean', | |
| inputName: 'OPERAND1' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'boolean', | |
| inputName: 'OPERAND2' | |
| } | |
| ] | |
| }, | |
| '|': { | |
| opcode: 'operator_or', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'boolean', | |
| inputName: 'OPERAND1' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'boolean', | |
| inputName: 'OPERAND2' | |
| } | |
| ] | |
| }, | |
| 'not': { | |
| opcode: 'operator_not', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'boolean', | |
| inputName: 'OPERAND' | |
| } | |
| ] | |
| }, | |
| 'concatenate:with:': { | |
| opcode: 'operator_join', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'text', | |
| inputName: 'STRING1' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'text', | |
| inputName: 'STRING2' | |
| } | |
| ] | |
| }, | |
| 'letter:of:': { | |
| opcode: 'operator_letter_of', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_whole_number', | |
| inputName: 'LETTER' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'text', | |
| inputName: 'STRING' | |
| } | |
| ] | |
| }, | |
| 'stringLength:': { | |
| opcode: 'operator_length', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'text', | |
| inputName: 'STRING' | |
| } | |
| ] | |
| }, | |
| '%': { | |
| opcode: 'operator_mod', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'NUM1' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'NUM2' | |
| } | |
| ] | |
| }, | |
| 'rounded': { | |
| opcode: 'operator_round', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'NUM' | |
| } | |
| ] | |
| }, | |
| 'computeFunction:of:': { | |
| opcode: 'operator_mathop', | |
| argMap: [ | |
| { | |
| type: 'field', | |
| fieldName: 'OPERATOR' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'NUM' | |
| } | |
| ] | |
| }, | |
| 'readVariable': { | |
| opcode: 'data_variable', | |
| argMap: [ | |
| { | |
| type: 'field', | |
| fieldName: 'VARIABLE', | |
| variableType: Variable.SCALAR_TYPE | |
| } | |
| ] | |
| }, | |
| // Scratch 2 uses this alternative variable getter opcode only in monitors, | |
| // blocks use the `readVariable` opcode above. | |
| 'getVar:': { | |
| opcode: 'data_variable', | |
| argMap: [ | |
| { | |
| type: 'field', | |
| fieldName: 'VARIABLE', | |
| variableType: Variable.SCALAR_TYPE | |
| } | |
| ] | |
| }, | |
| 'setVar:to:': { | |
| opcode: 'data_setvariableto', | |
| argMap: [ | |
| { | |
| type: 'field', | |
| fieldName: 'VARIABLE', | |
| variableType: Variable.SCALAR_TYPE | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'text', | |
| inputName: 'VALUE' | |
| } | |
| ] | |
| }, | |
| 'changeVar:by:': { | |
| opcode: 'data_changevariableby', | |
| argMap: [ | |
| { | |
| type: 'field', | |
| fieldName: 'VARIABLE', | |
| variableType: Variable.SCALAR_TYPE | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'VALUE' | |
| } | |
| ] | |
| }, | |
| 'showVariable:': { | |
| opcode: 'data_showvariable', | |
| argMap: [ | |
| { | |
| type: 'field', | |
| fieldName: 'VARIABLE', | |
| variableType: Variable.SCALAR_TYPE | |
| } | |
| ] | |
| }, | |
| 'hideVariable:': { | |
| opcode: 'data_hidevariable', | |
| argMap: [ | |
| { | |
| type: 'field', | |
| fieldName: 'VARIABLE', | |
| variableType: Variable.SCALAR_TYPE | |
| } | |
| ] | |
| }, | |
| 'contentsOfList:': { | |
| opcode: 'data_listcontents', | |
| argMap: [ | |
| { | |
| type: 'field', | |
| fieldName: 'LIST', | |
| variableType: Variable.LIST_TYPE | |
| } | |
| ] | |
| }, | |
| 'append:toList:': { | |
| opcode: 'data_addtolist', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'text', | |
| inputName: 'ITEM' | |
| }, | |
| { | |
| type: 'field', | |
| fieldName: 'LIST', | |
| variableType: Variable.LIST_TYPE | |
| } | |
| ] | |
| }, | |
| 'deleteLine:ofList:': { | |
| opcode: 'data_deleteoflist', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_integer', | |
| inputName: 'INDEX' | |
| }, | |
| { | |
| type: 'field', | |
| fieldName: 'LIST', | |
| variableType: Variable.LIST_TYPE | |
| } | |
| ] | |
| }, | |
| 'insert:at:ofList:': { | |
| opcode: 'data_insertatlist', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'text', | |
| inputName: 'ITEM' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'math_integer', | |
| inputName: 'INDEX' | |
| }, | |
| { | |
| type: 'field', | |
| fieldName: 'LIST', | |
| variableType: Variable.LIST_TYPE | |
| } | |
| ] | |
| }, | |
| 'setLine:ofList:to:': { | |
| opcode: 'data_replaceitemoflist', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_integer', | |
| inputName: 'INDEX' | |
| }, | |
| { | |
| type: 'field', | |
| fieldName: 'LIST', | |
| variableType: Variable.LIST_TYPE | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'text', | |
| inputName: 'ITEM' | |
| } | |
| ] | |
| }, | |
| 'getLine:ofList:': { | |
| opcode: 'data_itemoflist', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_integer', | |
| inputName: 'INDEX' | |
| }, | |
| { | |
| type: 'field', | |
| fieldName: 'LIST', | |
| variableType: Variable.LIST_TYPE | |
| } | |
| ] | |
| }, | |
| 'lineCountOfList:': { | |
| opcode: 'data_lengthoflist', | |
| argMap: [ | |
| { | |
| type: 'field', | |
| fieldName: 'LIST', | |
| variableType: Variable.LIST_TYPE | |
| } | |
| ] | |
| }, | |
| 'list:contains:': { | |
| opcode: 'data_listcontainsitem', | |
| argMap: [ | |
| { | |
| type: 'field', | |
| fieldName: 'LIST', | |
| variableType: Variable.LIST_TYPE | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'text', | |
| inputName: 'ITEM' | |
| } | |
| ] | |
| }, | |
| 'showList:': { | |
| opcode: 'data_showlist', | |
| argMap: [ | |
| { | |
| type: 'field', | |
| fieldName: 'LIST', | |
| variableType: Variable.LIST_TYPE | |
| } | |
| ] | |
| }, | |
| 'hideList:': { | |
| opcode: 'data_hidelist', | |
| argMap: [ | |
| { | |
| type: 'field', | |
| fieldName: 'LIST', | |
| variableType: Variable.LIST_TYPE | |
| } | |
| ] | |
| }, | |
| 'procDef': { | |
| opcode: 'procedures_definition', | |
| argMap: [] | |
| }, | |
| 'getParam': { | |
| // Doesn't map to single opcode. Import step assigns final correct opcode. | |
| opcode: 'argument_reporter_string_number', | |
| argMap: [ | |
| { | |
| type: 'field', | |
| fieldName: 'VALUE' | |
| } | |
| ] | |
| }, | |
| 'call': { | |
| opcode: 'procedures_call', | |
| argMap: [] | |
| } | |
| }; | |
| /** | |
| * Add to the specMap entries for an opcode from a Scratch 2.0 extension. Two entries will be made with the same | |
| * metadata; this is done to support projects saved by both older and newer versions of the Scratch 2.0 editor. | |
| * @param {string} sb2Extension - the Scratch 2.0 name of the extension | |
| * @param {string} sb2Opcode - the Scratch 2.0 opcode | |
| * @param {SB2SpecMap_blockInfo} blockInfo - the Scratch 3.0 block info | |
| */ | |
| const addExtensionOp = function (sb2Extension, sb2Opcode, blockInfo) { | |
| /** | |
| * This string separates the name of an extension and the name of an opcode in more recent Scratch 2.0 projects. | |
| * Earlier projects used '.' as a separator, up until we added the 'LEGO WeDo 2.0' extension... | |
| * @type {string} | |
| */ | |
| const sep = '\u001F'; // Unicode Unit Separator | |
| // make one entry for projects saved by recent versions of the Scratch 2.0 editor | |
| specMap[`${sb2Extension}${sep}${sb2Opcode}`] = blockInfo; | |
| // make a second for projects saved by older versions of the Scratch 2.0 editor | |
| specMap[`${sb2Extension}.${sb2Opcode}`] = blockInfo; | |
| }; | |
| const weDo2 = 'LEGO WeDo 2.0'; | |
| addExtensionOp(weDo2, 'motorOnFor', { | |
| opcode: 'wedo2_motorOnFor', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'wedo2_menu_MOTOR_ID', | |
| inputName: 'MOTOR_ID' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'DURATION' | |
| } | |
| ] | |
| }); | |
| addExtensionOp(weDo2, 'motorOn', { | |
| opcode: 'wedo2_motorOn', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'wedo2_menu_MOTOR_ID', | |
| inputName: 'MOTOR_ID' | |
| } | |
| ] | |
| }); | |
| addExtensionOp(weDo2, 'motorOff', { | |
| opcode: 'wedo2_motorOff', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'wedo2_menu_MOTOR_ID', | |
| inputName: 'MOTOR_ID' | |
| } | |
| ] | |
| }); | |
| addExtensionOp(weDo2, 'startMotorPower', { | |
| opcode: 'wedo2_startMotorPower', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'wedo2_menu_MOTOR_ID', | |
| inputName: 'MOTOR_ID' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'POWER' | |
| } | |
| ] | |
| }); | |
| addExtensionOp(weDo2, 'setMotorDirection', { | |
| opcode: 'wedo2_setMotorDirection', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'wedo2_menu_MOTOR_ID', | |
| inputName: 'MOTOR_ID' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'wedo2_menu_MOTOR_DIRECTION', | |
| inputName: 'MOTOR_DIRECTION' | |
| } | |
| ] | |
| }); | |
| addExtensionOp(weDo2, 'setLED', { | |
| opcode: 'wedo2_setLightHue', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'HUE' | |
| } | |
| ] | |
| }); | |
| addExtensionOp(weDo2, 'playNote', { | |
| opcode: 'wedo2_playNoteFor', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'NOTE' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'DURATION' | |
| } | |
| ] | |
| }); | |
| addExtensionOp(weDo2, 'whenDistance', { | |
| opcode: 'wedo2_whenDistance', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'wedo2_menu_OP', | |
| inputName: 'OP' | |
| }, | |
| { | |
| type: 'input', | |
| inputOp: 'math_number', | |
| inputName: 'REFERENCE' | |
| } | |
| ] | |
| }); | |
| addExtensionOp(weDo2, 'whenTilted', { | |
| opcode: 'wedo2_whenTilted', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'wedo2_menu_TILT_DIRECTION_ANY', | |
| inputName: 'TILT_DIRECTION_ANY' | |
| } | |
| ] | |
| }); | |
| addExtensionOp(weDo2, 'getDistance', { | |
| opcode: 'wedo2_getDistance', | |
| argMap: [] | |
| }); | |
| addExtensionOp(weDo2, 'isTilted', { | |
| opcode: 'wedo2_isTilted', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'wedo2_menu_TILT_DIRECTION_ANY', | |
| inputName: 'TILT_DIRECTION_ANY' | |
| } | |
| ] | |
| }); | |
| addExtensionOp(weDo2, 'getTilt', { | |
| opcode: 'wedo2_getTiltAngle', | |
| argMap: [ | |
| { | |
| type: 'input', | |
| inputOp: 'wedo2_menu_TILT_DIRECTION', | |
| inputName: 'TILT_DIRECTION' | |
| } | |
| ] | |
| }); | |
| module.exports = specMap; | |