scratch-vm/src/blocks/scratch3_control.js

199 lines
5.7 KiB
JavaScript
Raw Normal View History

2017-04-17 15:10:04 -04:00
const Cast = require('../util/cast');
2016-06-30 17:06:50 -04:00
2017-04-17 19:42:48 -04:00
class Scratch3ControlBlocks {
constructor (runtime) {
/**
* The runtime instantiating this block package.
* @type {Runtime}
*/
this.runtime = runtime;
2018-04-07 15:02:49 -04:00
/**
* The "counter" block value. For compatibility with 2.0.
* @type {number}
*/
this._counter = 0;
2017-04-17 19:42:48 -04:00
}
/**
2017-04-17 19:42:48 -04:00
* Retrieve the block primitives implemented by this package.
* @return {object.<string, Function>} Mapping of opcode to Function.
*/
2017-04-17 19:42:48 -04:00
getPrimitives () {
return {
control_repeat: this.repeat,
control_repeat_until: this.repeatUntil,
2018-04-07 12:56:28 -04:00
control_while: this.repeatWhile,
2018-03-05 14:36:26 -05:00
control_for_each: this.forEach,
2017-04-17 19:42:48 -04:00
control_forever: this.forever,
control_wait: this.wait,
control_wait_until: this.waitUntil,
control_if: this.if,
control_if_else: this.ifElse,
control_stop: this.stop,
control_create_clone_of: this.createClone,
2018-04-07 15:02:49 -04:00
control_delete_this_clone: this.deleteClone,
control_get_counter: this.getCounter,
control_incr_counter: this.incrCounter,
2018-04-30 11:18:38 -04:00
control_clear_counter: this.clearCounter,
control_all_at_once: this.allAtOnce
2017-04-17 19:42:48 -04:00
};
}
2017-04-17 19:42:48 -04:00
getHats () {
return {
control_start_as_clone: {
restartExistingThreads: false
}
};
}
2017-04-17 19:42:48 -04:00
repeat (args, util) {
const times = Math.floor(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);
}
}
2017-04-17 19:42:48 -04:00
repeatUntil (args, util) {
const condition = Cast.toBoolean(args.CONDITION);
2018-04-07 12:56:28 -04:00
// If the condition is false (repeat UNTIL), start the branch.
2017-04-17 19:42:48 -04:00
if (!condition) {
util.startBranch(1, true);
}
2016-07-01 11:25:26 -04:00
}
2018-04-07 12:56:28 -04:00
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);
}
}
2018-03-05 14:36:26 -05:00
forEach (args, util) {
const variable = util.target.lookupOrCreateVariable(
args.VARIABLE.id, args.VARIABLE.name);
2018-03-22 17:55:36 -04:00
if (typeof util.stackFrame.index === 'undefined') {
util.stackFrame.index = 0;
2018-03-05 14:36:26 -05:00
}
2018-03-22 17:55:36 -04:00
if (util.stackFrame.index < Number(args.VALUE)) {
util.stackFrame.index++;
variable.value = util.stackFrame.index;
util.startBranch(1, true);
2018-03-05 14:36:26 -05:00
}
}
2017-04-17 19:42:48 -04:00
waitUntil (args, util) {
const condition = Cast.toBoolean(args.CONDITION);
if (!condition) {
util.yield();
}
2016-09-22 17:00:38 -04:00
}
2017-04-17 19:42:48 -04:00
forever (args, util) {
util.startBranch(1, true);
}
wait (args) {
const duration = Math.max(0, 1000 * Cast.toNumber(args.DURATION));
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, duration);
});
}
2016-06-10 10:36:05 -04:00
2017-04-17 19:42:48 -04:00
if (args, util) {
const condition = Cast.toBoolean(args.CONDITION);
if (condition) {
util.startBranch(1, false);
}
2016-06-10 10:36:05 -04:00
}
2017-04-17 19:42:48 -04:00
ifElse (args, util) {
const condition = Cast.toBoolean(args.CONDITION);
if (condition) {
util.startBranch(1, false);
} else {
util.startBranch(2, false);
}
2016-06-10 10:40:15 -04:00
}
2017-04-17 19:42:48 -04:00
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();
}
}
2017-04-17 19:42:48 -04:00
createClone (args, util) {
// Cast argument to string
args.CLONE_OPTION = Cast.toString(args.CLONE_OPTION);
// Set clone target
2017-04-17 19:42:48 -04:00
let cloneTarget;
if (args.CLONE_OPTION === '_myself_') {
cloneTarget = util.target;
} else {
cloneTarget = this.runtime.getSpriteTargetByName(args.CLONE_OPTION);
}
// If clone target is not found, return
if (!cloneTarget) return;
// Create clone
2017-04-17 19:42:48 -04:00
const newClone = cloneTarget.makeClone();
if (newClone) {
this.runtime.targets.push(newClone);
}
}
2017-04-17 19:42:48 -04:00
deleteClone (args, util) {
if (util.target.isOriginal) return;
this.runtime.disposeTarget(util.target);
this.runtime.stopForTarget(util.target);
}
2018-04-07 15:02:49 -04:00
getCounter () {
return this._counter;
}
clearCounter () {
this._counter = 0;
}
incrCounter () {
this._counter++;
}
2018-04-30 11:18:38 -04:00
allAtOnce (args, util) {
2018-05-01 12:43:32 -04:00
// Since the "all at once" block is implemented for compatiblity with
// Scratch 2.0 projects, it behaves the same way it did in 2.0, which
// is to simply run the contained script (like "if 1 = 1").
// (In early versions of Scratch 2.0, it would work the same way as
// "run without screen refresh" custom blocks do now, but this was
// removed before the release of 2.0.)
2018-04-30 11:18:38 -04:00
util.startBranch(1, false);
}
2017-04-17 19:42:48 -04:00
}
module.exports = Scratch3ControlBlocks;