s4s-editor / local-scratch-vm /src /blocks /scratch3_control.js
soiz1's picture
Upload 811 files
30c32c8 verified
raw
history blame
10.5 kB
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;