Spaces:
Build error
Build error
const Cast = require('../util/cast'); | |
const SandboxRunner = require('../util/sandboxed-javascript-runner.js'); | |
class Scratch3ControlBlocks { | |
constructor (runtime) { | |
/** | |
* The runtime instantiating this block package. | |
* @type {Runtime} | |
*/ | |
this.runtime = runtime; | |
/** | |
* The "counter" block value. For compatibility with 2.0. | |
* @type {number} | |
*/ | |
this._counter = 0; // used by compiler | |
/** | |
* The "error" block value. | |
* @type {string} | |
*/ | |
this._error = ''; // used by compiler | |
this.runtime.on('RUNTIME_DISPOSED', this.clearCounter.bind(this)); | |
} | |
/** | |
* Retrieve the block primitives implemented by this package. | |
* @return {object.<string, Function>} Mapping of opcode to Function. | |
*/ | |
getPrimitives () { | |
return { | |
control_repeat: this.repeat, | |
control_repeat_until: this.repeatUntil, | |
control_while: this.repeatWhile, | |
control_for_each: this.forEach, | |
control_forever: this.forever, | |
control_wait: this.wait, | |
control_repeatForSeconds: this.repeatForSeconds, | |
control_waittick: this.waitTick, | |
control_waitsecondsoruntil: this.waitOrUntil, | |
control_wait_until: this.waitUntil, | |
control_if: this.if, | |
control_if_else: this.ifElse, | |
control_stop: this.stop, | |
control_stop_sprite: this.stopSprite, | |
control_create_clone_of: this.createClone, | |
control_delete_this_clone: this.deleteClone, | |
control_delete_clones_of: this.deleteClonesOf, | |
control_get_counter: this.getCounter, | |
control_incr_counter: this.incrCounter, | |
control_decr_counter: this.decrCounter, | |
control_set_counter: this.setCounter, | |
control_clear_counter: this.clearCounter, | |
control_all_at_once: this.allAtOnce, | |
control_backToGreenFlag: this.backToGreenFlag, | |
control_if_return_else_return: this.if_return_else_return, | |
control_javascript_command: this.runJavascript | |
}; | |
} | |
getMonitored () { | |
return { | |
control_get_counter: { | |
getId: () => 'get_counter' | |
} | |
}; | |
} | |
backToGreenFlag(_, util) { | |
const thisThread = util.thread.topBlock; | |
this.runtime.emit("PROJECT_START_BEFORE_RESET"); | |
this.runtime.threads | |
.filter(thread => thread.topBlock !== thisThread) | |
.forEach(thread => thread.stopThisScript()); | |
// green flag behaviour | |
this.runtime.emit("PROJECT_START"); | |
this.runtime.updateCurrentMSecs(); | |
this.runtime.ioDevices.clock.resetProjectTimer(); | |
this.runtime.targets.forEach(target => target.clearEdgeActivatedValues()); | |
for (let i = this.runtime.targets.length - 1; i >= 0; i--) { | |
const thisTarget = this.runtime.targets[i]; | |
thisTarget.onGreenFlag(); | |
if (!thisTarget.isOriginal) { | |
this.runtime.disposeTarget(thisTarget); | |
this.runtime.stopForTarget(thisTarget); | |
} | |
} | |
this.runtime.startHats("event_whenflagclicked"); | |
} | |
if_return_else_return (args) { | |
return Cast.toBoolean(args.boolean) ? args.TEXT1 : args.TEXT2; | |
} | |
getHats () { | |
return { | |
control_start_as_clone: { | |
restartExistingThreads: false | |
} | |
}; | |
} | |
runJavascript(args) { | |
return new Promise((resolve) => { | |
const js = Cast.toString(args.JS); | |
SandboxRunner.execute(js).then(result => { | |
resolve(result.value); | |
}); | |
}); | |
} | |
repeat (args, util) { | |
const times = Math.round(Cast.toNumber(args.TIMES)); | |
// Initialize loop | |
if (typeof util.stackFrame.loopCounter === 'undefined') { | |
util.stackFrame.loopCounter = times; | |
} | |
// Only execute once per frame. | |
// When the branch finishes, `repeat` will be executed again and | |
// the second branch will be taken, yielding for the rest of the frame. | |
// Decrease counter | |
util.stackFrame.loopCounter--; | |
// If we still have some left, start the branch. | |
if (util.stackFrame.loopCounter >= 0) { | |
util.startBranch(1, true); | |
} | |
} | |
repeatUntil (args, util) { | |
const condition = Cast.toBoolean(args.CONDITION); | |
// If the condition is false (repeat UNTIL), start the branch. | |
if (!condition) { | |
util.startBranch(1, true); | |
} | |
} | |
repeatWhile (args, util) { | |
const condition = Cast.toBoolean(args.CONDITION); | |
// If the condition is true (repeat WHILE), start the branch. | |
if (condition) { | |
util.startBranch(1, true); | |
} | |
} | |
forEach (args, util) { | |
const variable = util.target.lookupOrCreateVariable( | |
args.VARIABLE.id, args.VARIABLE.name); | |
if (typeof util.stackFrame.index === 'undefined') { | |
util.stackFrame.index = 0; | |
} | |
if (util.stackFrame.index < Number(args.VALUE)) { | |
util.stackFrame.index++; | |
variable.value = util.stackFrame.index; | |
util.startBranch(1, true); | |
} | |
} | |
waitUntil (args, util) { | |
const condition = Cast.toBoolean(args.CONDITION); | |
if (!condition) { | |
util.yield(); | |
} | |
} | |
forever (args, util) { | |
util.startBranch(1, true); | |
} | |
wait (args, util) { | |
if (util.stackTimerNeedsInit()) { | |
const duration = Math.max(0, 1000 * Cast.toNumber(args.DURATION)); | |
util.startStackTimer(duration); | |
this.runtime.requestRedraw(); | |
util.yield(); | |
} else if (!util.stackTimerFinished()) { | |
util.yield(); | |
} | |
} | |
repeatForSeconds (args, util) { | |
if (util.stackTimerNeedsInit()) { | |
const duration = Math.max(0, 1000 * Cast.toNumber(args.TIMES)); | |
util.startStackTimer(duration); | |
this.runtime.requestRedraw(); | |
util.startBranch(1, true); | |
util.yield(); | |
} else if (!util.stackTimerFinished()) { | |
util.startBranch(1, true); | |
util.yield(); | |
} | |
} | |
waitTick (_, util) { | |
util.yieldTick(); | |
} | |
waitOrUntil (args, util) { | |
const condition = Cast.toBoolean(args.CONDITION); | |
if (!condition) { | |
if (util.stackTimerNeedsInit()) { | |
const duration = Math.max(0, 1000 * Cast.toNumber(args.DURATION)); | |
util.startStackTimer(duration); | |
this.runtime.requestRedraw(); | |
util.yield(); | |
return; | |
} | |
if (!util.stackTimerFinished()) { | |
util.yield(); | |
} | |
} | |
} | |
if (args, util) { | |
const condition = Cast.toBoolean(args.CONDITION); | |
if (condition) { | |
util.startBranch(1, false); | |
} | |
} | |
ifElse (args, util) { | |
const condition = Cast.toBoolean(args.CONDITION); | |
if (condition) { | |
util.startBranch(1, false); | |
} else { | |
util.startBranch(2, false); | |
} | |
} | |
stop (args, util) { | |
const option = args.STOP_OPTION; | |
if (option === 'all') { | |
util.stopAll(); | |
} else if (option === 'other scripts in sprite' || | |
option === 'other scripts in stage') { | |
util.stopOtherTargetThreads(); | |
} else if (option === 'this script') { | |
util.stopThisScript(); | |
} | |
} | |
stopSprite (args, util) { | |
const option = args.STOP_OPTION; | |
// Set target | |
let target; | |
if (option === '_myself_') { | |
target = util.target; | |
} else if (option === '_stage_') { | |
target = this.runtime.getTargetForStage(); | |
} else { | |
target = this.runtime.getSpriteTargetByName(option); | |
} | |
if (!target) return; | |
this.runtime.stopForTarget(target); | |
} | |
createClone (args, util) { | |
this._createClone(Cast.toString(args.CLONE_OPTION), util.target); | |
} | |
_createClone (cloneOption, target) { // used by compiler | |
// Set clone target | |
let cloneTarget; | |
if (cloneOption === '_myself_') { | |
cloneTarget = target; | |
} else { | |
cloneTarget = this.runtime.getSpriteTargetByName(cloneOption); | |
} | |
// If clone target is not found, return | |
if (!cloneTarget) return; | |
// Create clone | |
const newClone = cloneTarget.makeClone(); | |
if (newClone) { | |
this.runtime.addTarget(newClone); | |
// Place behind the original target. | |
newClone.goBehindOther(cloneTarget); | |
} | |
} | |
deleteClone (args, util) { | |
if (util.target.isOriginal) return; | |
this.runtime.disposeTarget(util.target); | |
this.runtime.stopForTarget(util.target); | |
} | |
deleteClonesOf (args, util) { | |
const cloneOption = Cast.toString(args.CLONE_OPTION); | |
// Set clone target | |
let cloneTarget; | |
if (cloneOption === '_myself_') { | |
cloneTarget = util.target; | |
} else { | |
cloneTarget = this.runtime.getSpriteTargetByName(cloneOption); | |
} | |
// If clone target is not found, return | |
if (!cloneTarget) return; | |
const sprite = cloneTarget.sprite; | |
if (!sprite) return; | |
if (!sprite.clones) return; | |
const cloneList = [].concat(sprite.clones); | |
cloneList.forEach(clone => { | |
if (clone.isOriginal) return; | |
if (clone.isStage) return; | |
this.runtime.disposeTarget(clone); | |
this.runtime.stopForTarget(clone); | |
}) | |
} | |
getCounter () { | |
return this._counter; | |
} | |
setCounter (args) { | |
const num = Cast.toNumber(args.VALUE); | |
this._counter = num; | |
} | |
clearCounter () { | |
this._counter = 0; | |
} | |
incrCounter () { | |
this._counter++; | |
} | |
decrCounter () { | |
this._counter--; | |
} | |
allAtOnce (util) { | |
util.thread.peekStackFrame().warpMode = false; | |
util.startBranch(1, false); | |
util.thread.peekStackFrame().warpMode = true; | |
} | |
} | |
module.exports = Scratch3ControlBlocks; | |