Spaces:
Running
Running
/** | |
* @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; | |