mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2025-01-10 15:02:06 -05:00
Merge branch 'develop' of https://github.com/LLK/scratch-vm into develop
This commit is contained in:
commit
d8e1d42bf2
33 changed files with 1689 additions and 92 deletions
8
.gitignore
vendored
8
.gitignore
vendored
|
@ -14,10 +14,4 @@ npm-*
|
||||||
|
|
||||||
# Build
|
# Build
|
||||||
/dist
|
/dist
|
||||||
/playground/assets
|
/playground
|
||||||
/playground/media
|
|
||||||
/playground/scratch-vm.js
|
|
||||||
/playground/scratch-vm.js.map
|
|
||||||
/playground/vendor.js
|
|
||||||
/playground/vendor.js.map
|
|
||||||
/playground/zenburn.css
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
## Playground
|
## Playground
|
||||||
To run the Playground, make sure the dev server's running and go to [http://localhost:8073/](http://localhost:8073/) - you will be directed to the playground, which demonstrates various tools and internal state.
|
To run the Playground, make sure the dev server's running and go to [http://localhost:8073/playground/](http://localhost:8073/playground/) - you will be directed to the playground, which demonstrates various tools and internal state.
|
||||||
|
|
||||||
![VM Playground Screenshot](https://i.imgur.com/nOCNqEc.gif)
|
![VM Playground Screenshot](https://i.imgur.com/nOCNqEc.gif)
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
## How to include in a Node.js App
|
## How to include in a Node.js App
|
||||||
For an extended setup example, check out the /playground directory, which includes a fully running VM instance.
|
For an extended setup example, check out the /src/playground directory, which includes a fully running VM instance.
|
||||||
```js
|
```js
|
||||||
var VirtualMachine = require('scratch-vm');
|
var VirtualMachine = require('scratch-vm');
|
||||||
var vm = new VirtualMachine();
|
var vm = new VirtualMachine();
|
||||||
|
|
21
package.json
21
package.json
|
@ -25,15 +25,16 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"adm-zip": "0.4.7",
|
"adm-zip": "0.4.7",
|
||||||
"babel-eslint": "7.1.1",
|
"babel-eslint": "^7.1.1",
|
||||||
"copy-webpack-plugin": "4.0.1",
|
"copy-webpack-plugin": "4.0.1",
|
||||||
"eslint": "3.15.0",
|
"eslint": "^3.16.0",
|
||||||
"eslint-config-scratch": "^3.1.0",
|
"eslint-config-scratch": "^3.1.0",
|
||||||
"expose-loader": "0.7.1",
|
"expose-loader": "0.7.3",
|
||||||
"gh-pages": "0.12.0",
|
"gh-pages": "^0.12.0",
|
||||||
"highlightjs": "9.8.0",
|
"highlightjs": "^9.8.0",
|
||||||
"htmlparser2": "3.9.2",
|
"htmlparser2": "3.9.2",
|
||||||
"json": "9.0.4",
|
"jsdom": "^9.11.0",
|
||||||
|
"json": "^9.0.4",
|
||||||
"lodash.defaultsdeep": "4.6.0",
|
"lodash.defaultsdeep": "4.6.0",
|
||||||
"minilog": "3.1.0",
|
"minilog": "3.1.0",
|
||||||
"promise": "7.1.1",
|
"promise": "7.1.1",
|
||||||
|
@ -41,10 +42,10 @@
|
||||||
"scratch-blocks": "latest",
|
"scratch-blocks": "latest",
|
||||||
"scratch-render": "latest",
|
"scratch-render": "latest",
|
||||||
"script-loader": "0.7.0",
|
"script-loader": "0.7.0",
|
||||||
"stats.js": "0.17.0",
|
"stats.js": "^0.17.0",
|
||||||
"tap": "10.0.2",
|
"tap": "^10.2.0",
|
||||||
"travis-after-all": "1.4.4",
|
"travis-after-all": "^1.4.4",
|
||||||
"webpack": "2.2.1",
|
"webpack": "2.2.1",
|
||||||
"webpack-dev-server": "1.16.3"
|
"webpack-dev-server": "^2.4.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,7 +110,7 @@ Scratch3ControlBlocks.prototype.stop = function (args, util) {
|
||||||
option === 'other scripts in stage') {
|
option === 'other scripts in stage') {
|
||||||
util.stopOtherTargetThreads();
|
util.stopOtherTargetThreads();
|
||||||
} else if (option === 'this script') {
|
} else if (option === 'this script') {
|
||||||
util.stopThread();
|
util.stopThisScript();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -147,6 +147,7 @@ Scratch3PenBlocks.prototype._updatePenColor = function (penState) {
|
||||||
penState.penAttributes.color4f[0] = rgb.r / 255.0;
|
penState.penAttributes.color4f[0] = rgb.r / 255.0;
|
||||||
penState.penAttributes.color4f[1] = rgb.g / 255.0;
|
penState.penAttributes.color4f[1] = rgb.g / 255.0;
|
||||||
penState.penAttributes.color4f[2] = rgb.b / 255.0;
|
penState.penAttributes.color4f[2] = rgb.b / 255.0;
|
||||||
|
penState.penAttributes.color4f[3] = 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -260,6 +261,8 @@ Scratch3PenBlocks.prototype.setPenColorToColor = function (args, util) {
|
||||||
penState.penAttributes.color4f[2] = rgb.b / 255.0;
|
penState.penAttributes.color4f[2] = rgb.b / 255.0;
|
||||||
if (rgb.hasOwnProperty('a')) { // Will there always be an 'a'?
|
if (rgb.hasOwnProperty('a')) { // Will there always be an 'a'?
|
||||||
penState.penAttributes.color4f[3] = rgb.a / 255.0;
|
penState.penAttributes.color4f[3] = rgb.a / 255.0;
|
||||||
|
} else {
|
||||||
|
penState.penAttributes.color4f[3] = 1;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -135,8 +135,9 @@ Scratch3SoundBlocks.prototype.playNoteForBeats = function (args, util) {
|
||||||
var beats = Cast.toNumber(args.BEATS);
|
var beats = Cast.toNumber(args.BEATS);
|
||||||
var soundState = this._getSoundState(util.target);
|
var soundState = this._getSoundState(util.target);
|
||||||
var inst = soundState.currentInstrument;
|
var inst = soundState.currentInstrument;
|
||||||
|
var vol = soundState.volume;
|
||||||
if (typeof this.runtime.audioEngine === 'undefined') return;
|
if (typeof this.runtime.audioEngine === 'undefined') return;
|
||||||
return this.runtime.audioEngine.playNoteForBeatsWithInst(note, beats, inst);
|
return this.runtime.audioEngine.playNoteForBeatsWithInstAndVol(note, beats, inst, vol);
|
||||||
};
|
};
|
||||||
|
|
||||||
Scratch3SoundBlocks.prototype.playDrumForBeats = function (args, util) {
|
Scratch3SoundBlocks.prototype.playDrumForBeats = function (args, util) {
|
||||||
|
@ -192,6 +193,7 @@ Scratch3SoundBlocks.prototype.changeEffect = function (args, util) {
|
||||||
Scratch3SoundBlocks.prototype.clearEffects = function (args, util) {
|
Scratch3SoundBlocks.prototype.clearEffects = function (args, util) {
|
||||||
var soundState = this._getSoundState(util.target);
|
var soundState = this._getSoundState(util.target);
|
||||||
for (var effect in soundState.effects) {
|
for (var effect in soundState.effects) {
|
||||||
|
if (!soundState.effects.hasOwnProperty(effect)) continue;
|
||||||
soundState.effects[effect] = 0;
|
soundState.effects[effect] = 0;
|
||||||
}
|
}
|
||||||
if (util.target.audioPlayer === null) return;
|
if (util.target.audioPlayer === null) return;
|
||||||
|
|
|
@ -24,6 +24,7 @@ var domToBlocks = function (blocksDOM) {
|
||||||
// Flatten blocks object into a list.
|
// Flatten blocks object into a list.
|
||||||
var blocksList = [];
|
var blocksList = [];
|
||||||
for (var b in blocks) {
|
for (var b in blocks) {
|
||||||
|
if (!blocks.hasOwnProperty(b)) continue;
|
||||||
blocksList.push(blocks[b]);
|
blocksList.push(blocks[b]);
|
||||||
}
|
}
|
||||||
return blocksList;
|
return blocksList;
|
||||||
|
|
|
@ -149,6 +149,7 @@ Blocks.prototype.getTopLevelScript = function (id) {
|
||||||
*/
|
*/
|
||||||
Blocks.prototype.getProcedureDefinition = function (name) {
|
Blocks.prototype.getProcedureDefinition = function (name) {
|
||||||
for (var id in this._blocks) {
|
for (var id in this._blocks) {
|
||||||
|
if (!this._blocks.hasOwnProperty(id)) continue;
|
||||||
var block = this._blocks[id];
|
var block = this._blocks[id];
|
||||||
if ((block.opcode === 'procedures_defnoreturn' ||
|
if ((block.opcode === 'procedures_defnoreturn' ||
|
||||||
block.opcode === 'procedures_defreturn') &&
|
block.opcode === 'procedures_defreturn') &&
|
||||||
|
@ -166,6 +167,7 @@ Blocks.prototype.getProcedureDefinition = function (name) {
|
||||||
*/
|
*/
|
||||||
Blocks.prototype.getProcedureParamNames = function (name) {
|
Blocks.prototype.getProcedureParamNames = function (name) {
|
||||||
for (var id in this._blocks) {
|
for (var id in this._blocks) {
|
||||||
|
if (!this._blocks.hasOwnProperty(id)) continue;
|
||||||
var block = this._blocks[id];
|
var block = this._blocks[id];
|
||||||
if ((block.opcode === 'procedures_defnoreturn' ||
|
if ((block.opcode === 'procedures_defnoreturn' ||
|
||||||
block.opcode === 'procedures_defreturn') &&
|
block.opcode === 'procedures_defreturn') &&
|
||||||
|
@ -415,6 +417,7 @@ Blocks.prototype.blockToXML = function (blockId) {
|
||||||
}
|
}
|
||||||
// Add any inputs on this block.
|
// Add any inputs on this block.
|
||||||
for (var input in block.inputs) {
|
for (var input in block.inputs) {
|
||||||
|
if (!block.inputs.hasOwnProperty(input)) continue;
|
||||||
var blockInput = block.inputs[input];
|
var blockInput = block.inputs[input];
|
||||||
// Only encode a value tag if the value input is occupied.
|
// Only encode a value tag if the value input is occupied.
|
||||||
if (blockInput.block || blockInput.shadow) {
|
if (blockInput.block || blockInput.shadow) {
|
||||||
|
@ -431,6 +434,7 @@ Blocks.prototype.blockToXML = function (blockId) {
|
||||||
}
|
}
|
||||||
// Add any fields on this block.
|
// Add any fields on this block.
|
||||||
for (var field in block.fields) {
|
for (var field in block.fields) {
|
||||||
|
if (!block.fields.hasOwnProperty(field)) continue;
|
||||||
var blockField = block.fields[field];
|
var blockField = block.fields[field];
|
||||||
var value = blockField.value;
|
var value = blockField.value;
|
||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
|
|
|
@ -110,6 +110,7 @@ var execute = function (sequencer, thread) {
|
||||||
Object.keys(inputs).length === 0) {
|
Object.keys(inputs).length === 0) {
|
||||||
// One field and no inputs - treat as arg.
|
// One field and no inputs - treat as arg.
|
||||||
for (var fieldKey in fields) { // One iteration.
|
for (var fieldKey in fields) { // One iteration.
|
||||||
|
if (!fields.hasOwnProperty(fieldKey)) continue;
|
||||||
handleReport(fields[fieldKey].value);
|
handleReport(fields[fieldKey].value);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -126,11 +127,13 @@ var execute = function (sequencer, thread) {
|
||||||
|
|
||||||
// Add all fields on this block to the argValues.
|
// Add all fields on this block to the argValues.
|
||||||
for (var fieldName in fields) {
|
for (var fieldName in fields) {
|
||||||
|
if (!fields.hasOwnProperty(fieldName)) continue;
|
||||||
argValues[fieldName] = fields[fieldName].value;
|
argValues[fieldName] = fields[fieldName].value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recursively evaluate input blocks.
|
// Recursively evaluate input blocks.
|
||||||
for (var inputName in inputs) {
|
for (var inputName in inputs) {
|
||||||
|
if (!inputs.hasOwnProperty(inputName)) continue;
|
||||||
var input = inputs[inputName];
|
var input = inputs[inputName];
|
||||||
var inputBlockId = input.block;
|
var inputBlockId = input.block;
|
||||||
// Is there no value for this input waiting in the stack frame?
|
// Is there no value for this input waiting in the stack frame?
|
||||||
|
@ -183,8 +186,8 @@ var execute = function (sequencer, thread) {
|
||||||
stopOtherTargetThreads: function () {
|
stopOtherTargetThreads: function () {
|
||||||
runtime.stopForTarget(target, thread);
|
runtime.stopForTarget(target, thread);
|
||||||
},
|
},
|
||||||
stopThread: function () {
|
stopThisScript: function () {
|
||||||
sequencer.retireThread(thread);
|
thread.stopThisScript();
|
||||||
},
|
},
|
||||||
startProcedure: function (procedureCode) {
|
startProcedure: function (procedureCode) {
|
||||||
sequencer.stepToProcedure(thread, procedureCode);
|
sequencer.stepToProcedure(thread, procedureCode);
|
||||||
|
@ -233,8 +236,23 @@ var execute = function (sequencer, thread) {
|
||||||
primitiveReportedValue.then(function (resolvedValue) {
|
primitiveReportedValue.then(function (resolvedValue) {
|
||||||
handleReport(resolvedValue);
|
handleReport(resolvedValue);
|
||||||
if (typeof resolvedValue === 'undefined') {
|
if (typeof resolvedValue === 'undefined') {
|
||||||
var popped = thread.popStack();
|
do {
|
||||||
var nextBlockId = thread.target.blocks.getNextBlock(popped);
|
// In the case that the promise is the last block in the current thread stack
|
||||||
|
// We need to pop out repeatedly until we find the next block.
|
||||||
|
var popped = thread.popStack();
|
||||||
|
if (popped === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var nextBlockId = thread.target.blocks.getNextBlock(popped);
|
||||||
|
if (nextBlockId !== null) {
|
||||||
|
// A next block exists so break out this loop
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Investigate the next block and if not in a loop,
|
||||||
|
// then repeat and pop the next item off the stack frame
|
||||||
|
var stackFrame = thread.peekStackFrame();
|
||||||
|
} while (stackFrame !== null && !stackFrame.isLoop);
|
||||||
|
|
||||||
thread.pushStack(nextBlockId);
|
thread.pushStack(nextBlockId);
|
||||||
} else {
|
} else {
|
||||||
thread.popStack();
|
thread.popStack();
|
||||||
|
|
|
@ -414,7 +414,7 @@ Runtime.prototype.allScriptsDo = function (f, optTarget) {
|
||||||
if (optTarget) {
|
if (optTarget) {
|
||||||
targets = [optTarget];
|
targets = [optTarget];
|
||||||
}
|
}
|
||||||
for (var t = 0; t < targets.length; t++) {
|
for (var t = targets.length - 1; t >= 0; t--) {
|
||||||
var target = targets[t];
|
var target = targets[t];
|
||||||
var scripts = target.blocks.getScripts();
|
var scripts = target.blocks.getScripts();
|
||||||
for (var j = 0; j < scripts.length; j++) {
|
for (var j = 0; j < scripts.length; j++) {
|
||||||
|
@ -439,6 +439,12 @@ Runtime.prototype.startHats = function (requestedHatOpcode,
|
||||||
}
|
}
|
||||||
var instance = this;
|
var instance = this;
|
||||||
var newThreads = [];
|
var newThreads = [];
|
||||||
|
|
||||||
|
for (var opts in optMatchFields) {
|
||||||
|
if (!optMatchFields.hasOwnProperty(opts)) continue;
|
||||||
|
optMatchFields[opts] = optMatchFields[opts].toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
// Consider all scripts, looking for hats with opcode `requestedHatOpcode`.
|
// Consider all scripts, looking for hats with opcode `requestedHatOpcode`.
|
||||||
this.allScriptsDo(function (topBlockId, target) {
|
this.allScriptsDo(function (topBlockId, target) {
|
||||||
var potentialHatOpcode = target.blocks.getBlock(topBlockId).opcode;
|
var potentialHatOpcode = target.blocks.getBlock(topBlockId).opcode;
|
||||||
|
@ -458,6 +464,7 @@ Runtime.prototype.startHats = function (requestedHatOpcode,
|
||||||
if (Object.keys(hatFields).length === 0) {
|
if (Object.keys(hatFields).length === 0) {
|
||||||
var hatInputs = target.blocks.getInputs(topBlockId);
|
var hatInputs = target.blocks.getInputs(topBlockId);
|
||||||
for (var input in hatInputs) {
|
for (var input in hatInputs) {
|
||||||
|
if (!hatInputs.hasOwnProperty(input)) continue;
|
||||||
var id = hatInputs[input].block;
|
var id = hatInputs[input].block;
|
||||||
var fields = target.blocks.getFields(id);
|
var fields = target.blocks.getFields(id);
|
||||||
hatFields = Object.assign(fields, hatFields);
|
hatFields = Object.assign(fields, hatFields);
|
||||||
|
@ -466,7 +473,7 @@ Runtime.prototype.startHats = function (requestedHatOpcode,
|
||||||
|
|
||||||
if (optMatchFields) {
|
if (optMatchFields) {
|
||||||
for (var matchField in optMatchFields) {
|
for (var matchField in optMatchFields) {
|
||||||
if (hatFields[matchField].value !==
|
if (hatFields[matchField].value.toUpperCase() !==
|
||||||
optMatchFields[matchField]) {
|
optMatchFields[matchField]) {
|
||||||
// Field mismatch.
|
// Field mismatch.
|
||||||
return;
|
return;
|
||||||
|
@ -587,6 +594,7 @@ Runtime.prototype.stopAll = function () {
|
||||||
Runtime.prototype._step = function () {
|
Runtime.prototype._step = function () {
|
||||||
// Find all edge-activated hats, and add them to threads to be evaluated.
|
// Find all edge-activated hats, and add them to threads to be evaluated.
|
||||||
for (var hatType in this._hats) {
|
for (var hatType in this._hats) {
|
||||||
|
if (!this._hats.hasOwnProperty(hatType)) continue;
|
||||||
var hat = this._hats[hatType];
|
var hat = this._hats[hatType];
|
||||||
if (hat.edgeActivated) {
|
if (hat.edgeActivated) {
|
||||||
this.startHats(hatType);
|
this.startHats(hatType);
|
||||||
|
@ -785,6 +793,18 @@ Runtime.prototype.getSpriteTargetByName = function (spriteName) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a target by its drawable id.
|
||||||
|
* @param {number} drawableID drawable id of target to find
|
||||||
|
* @return {?Target} The target, if found
|
||||||
|
*/
|
||||||
|
Runtime.prototype.getTargetByDrawableId = function (drawableID) {
|
||||||
|
for (var i = 0; i < this.targets.length; i++) {
|
||||||
|
var target = this.targets[i];
|
||||||
|
if (target.drawableID === drawableID) return target;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the clone counter to track how many clones are created.
|
* Update the clone counter to track how many clones are created.
|
||||||
* @param {number} changeAmount How many clones have been created/destroyed.
|
* @param {number} changeAmount How many clones have been created/destroyed.
|
||||||
|
|
|
@ -140,6 +140,27 @@ Thread.prototype.popStack = function () {
|
||||||
return this.stack.pop();
|
return this.stack.pop();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pop back down the stack frame until we hit a procedure call or the stack frame is emptied
|
||||||
|
*/
|
||||||
|
Thread.prototype.stopThisScript = function () {
|
||||||
|
var blockID = this.peekStack();
|
||||||
|
while (blockID !== null) {
|
||||||
|
var block = this.target.blocks.getBlock(blockID);
|
||||||
|
if (typeof block !== 'undefined' && block.opcode === 'procedures_callnoreturn') {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this.popStack();
|
||||||
|
blockID = this.peekStack();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.stack.length === 0) {
|
||||||
|
// Clean up!
|
||||||
|
this.requestScriptGlowInFrame = false;
|
||||||
|
this.status = Thread.STATUS_DONE;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get top stack item.
|
* Get top stack item.
|
||||||
* @return {?string} Block ID on top of stack.
|
* @return {?string} Block ID on top of stack.
|
||||||
|
|
|
@ -56,7 +56,7 @@ Keyboard.prototype._keyCodeToScratchKey = function (keyCode) {
|
||||||
case 39: return 'right arrow';
|
case 39: return 'right arrow';
|
||||||
case 40: return 'down arrow';
|
case 40: return 'down arrow';
|
||||||
}
|
}
|
||||||
return null;
|
return '';
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -46,7 +46,7 @@ Mouse.prototype.postData = function (data) {
|
||||||
}
|
}
|
||||||
if (typeof data.isDown !== 'undefined') {
|
if (typeof data.isDown !== 'undefined') {
|
||||||
this._isDown = data.isDown;
|
this._isDown = data.isDown;
|
||||||
if (this._isDown) {
|
if (!this._isDown) {
|
||||||
this._activateClickHats(data.x, data.y);
|
this._activateClickHats(data.x, data.y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
var loadProject = function () {
|
var loadProject = function () {
|
||||||
var id = location.hash.substring(1);
|
var id = location.hash.substring(1);
|
||||||
if (id.length < 1 || !isFinite(id)) {
|
if (id.length < 1 || !isFinite(id)) {
|
||||||
|
@ -7,7 +6,7 @@ var loadProject = function () {
|
||||||
var url = 'https://projects.scratch.mit.edu/internalapi/project/' +
|
var url = 'https://projects.scratch.mit.edu/internalapi/project/' +
|
||||||
id + '/get/';
|
id + '/get/';
|
||||||
var r = new XMLHttpRequest();
|
var r = new XMLHttpRequest();
|
||||||
r.onreadystatechange = function() {
|
r.onreadystatechange = function () {
|
||||||
if (this.readyState === 4) {
|
if (this.readyState === 4) {
|
||||||
if (r.status === 200) {
|
if (r.status === 200) {
|
||||||
window.vm.loadProject(this.responseText);
|
window.vm.loadProject(this.responseText);
|
||||||
|
@ -18,7 +17,7 @@ var loadProject = function () {
|
||||||
r.send();
|
r.send();
|
||||||
};
|
};
|
||||||
|
|
||||||
window.onload = function() {
|
window.onload = function () {
|
||||||
// Lots of global variables to make debugging easier
|
// Lots of global variables to make debugging easier
|
||||||
// Instantiate the VM.
|
// Instantiate the VM.
|
||||||
var vm = new window.VirtualMachine();
|
var vm = new window.VirtualMachine();
|
||||||
|
@ -60,6 +59,11 @@ window.onload = function() {
|
||||||
});
|
});
|
||||||
window.workspace = workspace;
|
window.workspace = workspace;
|
||||||
|
|
||||||
|
// Filter available blocks
|
||||||
|
var toolbox = vm.filterToolbox(workspace.options.languageTree);
|
||||||
|
// var toolbox = workspace.options.languageTree;
|
||||||
|
workspace.updateToolbox(toolbox);
|
||||||
|
|
||||||
// Attach scratch-blocks events to VM.
|
// Attach scratch-blocks events to VM.
|
||||||
workspace.addChangeListener(vm.blockListener);
|
workspace.addChangeListener(vm.blockListener);
|
||||||
var flyoutWorkspace = workspace.getFlyout().getWorkspace();
|
var flyoutWorkspace = workspace.getFlyout().getWorkspace();
|
||||||
|
@ -74,7 +78,7 @@ window.onload = function() {
|
||||||
// Playground data tabs.
|
// Playground data tabs.
|
||||||
// Block representation tab.
|
// Block representation tab.
|
||||||
var blockexplorer = document.getElementById('blockexplorer');
|
var blockexplorer = document.getElementById('blockexplorer');
|
||||||
var updateBlockExplorer = function(blocks) {
|
var updateBlockExplorer = function (blocks) {
|
||||||
blockexplorer.innerHTML = JSON.stringify(blocks, null, 2);
|
blockexplorer.innerHTML = JSON.stringify(blocks, null, 2);
|
||||||
window.hljs.highlightBlock(blockexplorer);
|
window.hljs.highlightBlock(blockexplorer);
|
||||||
};
|
};
|
||||||
|
@ -83,7 +87,7 @@ window.onload = function() {
|
||||||
var threadexplorer = document.getElementById('threadexplorer');
|
var threadexplorer = document.getElementById('threadexplorer');
|
||||||
var cachedThreadJSON = '';
|
var cachedThreadJSON = '';
|
||||||
var updateThreadExplorer = function (newJSON) {
|
var updateThreadExplorer = function (newJSON) {
|
||||||
if (newJSON != cachedThreadJSON) {
|
if (newJSON !== cachedThreadJSON) {
|
||||||
cachedThreadJSON = newJSON;
|
cachedThreadJSON = newJSON;
|
||||||
threadexplorer.innerHTML = cachedThreadJSON;
|
threadexplorer.innerHTML = cachedThreadJSON;
|
||||||
window.hljs.highlightBlock(threadexplorer);
|
window.hljs.highlightBlock(threadexplorer);
|
||||||
|
@ -101,7 +105,7 @@ window.onload = function() {
|
||||||
|
|
||||||
// VM handlers.
|
// VM handlers.
|
||||||
// Receipt of new playground data (thread, block representations).
|
// Receipt of new playground data (thread, block representations).
|
||||||
vm.on('playgroundData', function(data) {
|
vm.on('playgroundData', function (data) {
|
||||||
updateThreadExplorer(data.threads);
|
updateThreadExplorer(data.threads);
|
||||||
updateBlockExplorer(data.blocks);
|
updateBlockExplorer(data.blocks);
|
||||||
});
|
});
|
||||||
|
@ -125,7 +129,7 @@ window.onload = function() {
|
||||||
var targetOption = document.createElement('option');
|
var targetOption = document.createElement('option');
|
||||||
targetOption.setAttribute('value', data.targetList[i].id);
|
targetOption.setAttribute('value', data.targetList[i].id);
|
||||||
// If target id matches editingTarget id, select it.
|
// If target id matches editingTarget id, select it.
|
||||||
if (data.targetList[i].id == data.editingTarget) {
|
if (data.targetList[i].id === data.editingTarget) {
|
||||||
targetOption.setAttribute('selected', 'selected');
|
targetOption.setAttribute('selected', 'selected');
|
||||||
}
|
}
|
||||||
targetOption.appendChild(
|
targetOption.appendChild(
|
||||||
|
@ -139,23 +143,23 @@ window.onload = function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Feedback for stacks and blocks running.
|
// Feedback for stacks and blocks running.
|
||||||
vm.on('SCRIPT_GLOW_ON', function(data) {
|
vm.on('SCRIPT_GLOW_ON', function (data) {
|
||||||
workspace.glowStack(data.id, true);
|
workspace.glowStack(data.id, true);
|
||||||
});
|
});
|
||||||
vm.on('SCRIPT_GLOW_OFF', function(data) {
|
vm.on('SCRIPT_GLOW_OFF', function (data) {
|
||||||
workspace.glowStack(data.id, false);
|
workspace.glowStack(data.id, false);
|
||||||
});
|
});
|
||||||
vm.on('BLOCK_GLOW_ON', function(data) {
|
vm.on('BLOCK_GLOW_ON', function (data) {
|
||||||
workspace.glowBlock(data.id, true);
|
workspace.glowBlock(data.id, true);
|
||||||
});
|
});
|
||||||
vm.on('BLOCK_GLOW_OFF', function(data) {
|
vm.on('BLOCK_GLOW_OFF', function (data) {
|
||||||
workspace.glowBlock(data.id, false);
|
workspace.glowBlock(data.id, false);
|
||||||
});
|
});
|
||||||
vm.on('VISUAL_REPORT', function(data) {
|
vm.on('VISUAL_REPORT', function (data) {
|
||||||
workspace.reportValue(data.id, data.value);
|
workspace.reportValue(data.id, data.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
vm.on('SPRITE_INFO_REPORT', function(data) {
|
vm.on('SPRITE_INFO_REPORT', function (data) {
|
||||||
if (data.id !== selectedTarget.value) return; // Not the editingTarget
|
if (data.id !== selectedTarget.value) return; // Not the editingTarget
|
||||||
document.getElementById('sinfo-x').value = data.x;
|
document.getElementById('sinfo-x').value = data.x;
|
||||||
document.getElementById('sinfo-y').value = data.y;
|
document.getElementById('sinfo-y').value = data.y;
|
||||||
|
@ -213,7 +217,7 @@ window.onload = function() {
|
||||||
// Feed keyboard events as VM I/O events.
|
// Feed keyboard events as VM I/O events.
|
||||||
document.addEventListener('keydown', function (e) {
|
document.addEventListener('keydown', function (e) {
|
||||||
// Don't capture keys intended for Blockly inputs.
|
// Don't capture keys intended for Blockly inputs.
|
||||||
if (e.target != document && e.target != document.body) {
|
if (e.target !== document && e.target !== document.body) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
window.vm.postIOData('keyboard', {
|
window.vm.postIOData('keyboard', {
|
||||||
|
@ -222,7 +226,7 @@ window.onload = function() {
|
||||||
});
|
});
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
});
|
});
|
||||||
document.addEventListener('keyup', function(e) {
|
document.addEventListener('keyup', function (e) {
|
||||||
// Always capture up events,
|
// Always capture up events,
|
||||||
// even those that have switched to other targets.
|
// even those that have switched to other targets.
|
||||||
window.vm.postIOData('keyboard', {
|
window.vm.postIOData('keyboard', {
|
||||||
|
@ -230,7 +234,7 @@ window.onload = function() {
|
||||||
isDown: false
|
isDown: false
|
||||||
});
|
});
|
||||||
// E.g., prevent scroll.
|
// E.g., prevent scroll.
|
||||||
if (e.target != document && e.target != document.body) {
|
if (e.target !== document && e.target !== document.body) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -239,25 +243,25 @@ window.onload = function() {
|
||||||
vm.start();
|
vm.start();
|
||||||
|
|
||||||
// Inform VM of animation frames.
|
// Inform VM of animation frames.
|
||||||
var animate = function() {
|
var animate = function () {
|
||||||
stats.update();
|
stats.update();
|
||||||
requestAnimationFrame(animate);
|
requestAnimationFrame(animate);
|
||||||
};
|
};
|
||||||
requestAnimationFrame(animate);
|
requestAnimationFrame(animate);
|
||||||
|
|
||||||
// Handlers for green flag and stop all.
|
// Handlers for green flag and stop all.
|
||||||
document.getElementById('greenflag').addEventListener('click', function() {
|
document.getElementById('greenflag').addEventListener('click', function () {
|
||||||
vm.greenFlag();
|
vm.greenFlag();
|
||||||
});
|
});
|
||||||
document.getElementById('stopall').addEventListener('click', function() {
|
document.getElementById('stopall').addEventListener('click', function () {
|
||||||
vm.stopAll();
|
vm.stopAll();
|
||||||
});
|
});
|
||||||
document.getElementById('turbomode').addEventListener('change', function() {
|
document.getElementById('turbomode').addEventListener('change', function () {
|
||||||
var turboOn = document.getElementById('turbomode').checked;
|
var turboOn = document.getElementById('turbomode').checked;
|
||||||
vm.setTurboMode(turboOn);
|
vm.setTurboMode(turboOn);
|
||||||
});
|
});
|
||||||
document.getElementById('compatmode').addEventListener('change',
|
document.getElementById('compatmode').addEventListener('change',
|
||||||
function() {
|
function () {
|
||||||
var compatibilityMode = document.getElementById('compatmode').checked;
|
var compatibilityMode = document.getElementById('compatmode').checked;
|
||||||
vm.setCompatibilityMode(compatibilityMode);
|
vm.setCompatibilityMode(compatibilityMode);
|
||||||
});
|
});
|
|
@ -104,6 +104,9 @@ var parseScratchObject = function (object, runtime, topLevel) {
|
||||||
if (object.hasOwnProperty('direction')) {
|
if (object.hasOwnProperty('direction')) {
|
||||||
target.direction = object.direction;
|
target.direction = object.direction;
|
||||||
}
|
}
|
||||||
|
if (object.hasOwnProperty('isDraggable')) {
|
||||||
|
target.draggable = object.isDraggable;
|
||||||
|
}
|
||||||
if (object.hasOwnProperty('scale')) {
|
if (object.hasOwnProperty('scale')) {
|
||||||
// SB2 stores as 1.0 = 100%; we use % in the VM.
|
// SB2 stores as 1.0 = 100%; we use % in the VM.
|
||||||
target.size = object.scale * 100;
|
target.size = object.scale * 100;
|
||||||
|
|
|
@ -33,6 +33,13 @@ var RenderedTarget = function (sprite, runtime) {
|
||||||
*/
|
*/
|
||||||
this.drawableID = null;
|
this.drawableID = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drag state of this rendered target. If true, x/y position can't be
|
||||||
|
* changed by blocks.
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
this.dragging = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map of current graphic effect values.
|
* Map of current graphic effect values.
|
||||||
* @type {!Object.<string, number>}
|
* @type {!Object.<string, number>}
|
||||||
|
@ -106,6 +113,12 @@ RenderedTarget.prototype.y = 0;
|
||||||
*/
|
*/
|
||||||
RenderedTarget.prototype.direction = 90;
|
RenderedTarget.prototype.direction = 90;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the rendered target is draggable on the stage
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
RenderedTarget.prototype.draggable = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the rendered target is currently visible.
|
* Whether the rendered target is currently visible.
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
|
@ -160,22 +173,27 @@ RenderedTarget.prototype.rotationStyle = (
|
||||||
* Set the X and Y coordinates.
|
* Set the X and Y coordinates.
|
||||||
* @param {!number} x New X coordinate, in Scratch coordinates.
|
* @param {!number} x New X coordinate, in Scratch coordinates.
|
||||||
* @param {!number} y New Y coordinate, in Scratch coordinates.
|
* @param {!number} y New Y coordinate, in Scratch coordinates.
|
||||||
|
* @param {?boolean} force Force setting X/Y, in case of dragging
|
||||||
*/
|
*/
|
||||||
RenderedTarget.prototype.setXY = function (x, y) {
|
RenderedTarget.prototype.setXY = function (x, y, force) {
|
||||||
if (this.isStage) {
|
if (this.isStage) return;
|
||||||
return;
|
if (this.dragging && !force) return;
|
||||||
}
|
|
||||||
var oldX = this.x;
|
var oldX = this.x;
|
||||||
var oldY = this.y;
|
var oldY = this.y;
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
if (this.renderer) {
|
if (this.renderer) {
|
||||||
|
var position = this.renderer.getFencedPositionOfDrawable(this.drawableID, [x, y]);
|
||||||
|
this.x = position[0];
|
||||||
|
this.y = position[1];
|
||||||
|
|
||||||
this.renderer.updateDrawableProperties(this.drawableID, {
|
this.renderer.updateDrawableProperties(this.drawableID, {
|
||||||
position: [this.x, this.y]
|
position: position
|
||||||
});
|
});
|
||||||
if (this.visible) {
|
if (this.visible) {
|
||||||
this.runtime.requestRedraw();
|
this.runtime.requestRedraw();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
}
|
}
|
||||||
this.emit(RenderedTarget.EVENT_TARGET_MOVED, this, oldX, oldY);
|
this.emit(RenderedTarget.EVENT_TARGET_MOVED, this, oldX, oldY);
|
||||||
this.runtime.spriteInfoReport(this);
|
this.runtime.spriteInfoReport(this);
|
||||||
|
@ -224,6 +242,16 @@ RenderedTarget.prototype.setDirection = function (direction) {
|
||||||
this.runtime.spriteInfoReport(this);
|
this.runtime.spriteInfoReport(this);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set draggability; i.e., whether it's able to be dragged in the player
|
||||||
|
* @param {!boolean} draggable True if should be draggable.
|
||||||
|
*/
|
||||||
|
RenderedTarget.prototype.setDraggable = function (draggable) {
|
||||||
|
if (this.isStage) return;
|
||||||
|
this.draggable = !!draggable;
|
||||||
|
this.runtime.spriteInfoReport(this);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a say bubble.
|
* Set a say bubble.
|
||||||
* @param {?string} type Type of say bubble: "say", "think", or null.
|
* @param {?string} type Type of say bubble: "say", "think", or null.
|
||||||
|
@ -315,6 +343,7 @@ RenderedTarget.prototype.setEffect = function (effectName, value) {
|
||||||
*/
|
*/
|
||||||
RenderedTarget.prototype.clearEffects = function () {
|
RenderedTarget.prototype.clearEffects = function () {
|
||||||
for (var effectName in this.effects) {
|
for (var effectName in this.effects) {
|
||||||
|
if (!this.effects.hasOwnProperty(effectName)) continue;
|
||||||
this.effects[effectName] = 0;
|
this.effects[effectName] = 0;
|
||||||
}
|
}
|
||||||
if (this.renderer) {
|
if (this.renderer) {
|
||||||
|
@ -422,20 +451,23 @@ RenderedTarget.prototype.updateAllDrawableProperties = function () {
|
||||||
if (this.renderer) {
|
if (this.renderer) {
|
||||||
var renderedDirectionScale = this._getRenderedDirectionAndScale();
|
var renderedDirectionScale = this._getRenderedDirectionAndScale();
|
||||||
var costume = this.sprite.costumes[this.currentCostume];
|
var costume = this.sprite.costumes[this.currentCostume];
|
||||||
|
var bitmapResolution = costume.bitmapResolution || 1;
|
||||||
var props = {
|
var props = {
|
||||||
position: [this.x, this.y],
|
position: [this.x, this.y],
|
||||||
direction: renderedDirectionScale.direction,
|
direction: renderedDirectionScale.direction,
|
||||||
|
draggable: this.draggable,
|
||||||
scale: renderedDirectionScale.scale,
|
scale: renderedDirectionScale.scale,
|
||||||
visible: this.visible,
|
visible: this.visible,
|
||||||
skin: costume.skin,
|
skin: costume.skin,
|
||||||
costumeResolution: costume.bitmapResolution,
|
costumeResolution: bitmapResolution,
|
||||||
rotationCenter: [
|
rotationCenter: [
|
||||||
costume.rotationCenterX / costume.bitmapResolution,
|
costume.rotationCenterX / bitmapResolution,
|
||||||
costume.rotationCenterY / costume.bitmapResolution
|
costume.rotationCenterY / bitmapResolution
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
for (var effectID in this.effects) {
|
for (var effectName in this.effects) {
|
||||||
props[effectID] = this.effects[effectID];
|
if (!this.effects.hasOwnProperty(effectName)) continue;
|
||||||
|
props[effectName] = this.effects[effectName];
|
||||||
}
|
}
|
||||||
this.renderer.updateDrawableProperties(this.drawableID, props);
|
this.renderer.updateDrawableProperties(this.drawableID, props);
|
||||||
if (this.visible) {
|
if (this.visible) {
|
||||||
|
@ -582,7 +614,7 @@ RenderedTarget.prototype.goBackLayers = function (nLayers) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move behind some other rendered target.
|
* Move behind some other rendered target.
|
||||||
* @param {!Clone} other Other rendered target to move behind.
|
* @param {!RenderedTarget} other Other rendered target to move behind.
|
||||||
*/
|
*/
|
||||||
RenderedTarget.prototype.goBehindOther = function (other) {
|
RenderedTarget.prototype.goBehindOther = function (other) {
|
||||||
if (this.renderer) {
|
if (this.renderer) {
|
||||||
|
@ -637,11 +669,11 @@ RenderedTarget.prototype.keepInFence = function (newX, newY, optFence) {
|
||||||
/**
|
/**
|
||||||
* Make a clone, copying any run-time properties.
|
* Make a clone, copying any run-time properties.
|
||||||
* If we've hit the global clone limit, returns null.
|
* If we've hit the global clone limit, returns null.
|
||||||
* @return {!RenderedTarget} New clone.
|
* @return {RenderedTarget} New clone.
|
||||||
*/
|
*/
|
||||||
RenderedTarget.prototype.makeClone = function () {
|
RenderedTarget.prototype.makeClone = function () {
|
||||||
if (!this.runtime.clonesAvailable() || this.isStage) {
|
if (!this.runtime.clonesAvailable() || this.isStage) {
|
||||||
return; // Hit max clone limit, or this is the stage.
|
return null; // Hit max clone limit, or this is the stage.
|
||||||
}
|
}
|
||||||
this.runtime.changeCloneCounter(1);
|
this.runtime.changeCloneCounter(1);
|
||||||
var newClone = this.sprite.createClone();
|
var newClone = this.sprite.createClone();
|
||||||
|
@ -649,6 +681,7 @@ RenderedTarget.prototype.makeClone = function () {
|
||||||
newClone.x = this.x;
|
newClone.x = this.x;
|
||||||
newClone.y = this.y;
|
newClone.y = this.y;
|
||||||
newClone.direction = this.direction;
|
newClone.direction = this.direction;
|
||||||
|
newClone.draggable = this.draggable;
|
||||||
newClone.visible = this.visible;
|
newClone.visible = this.visible;
|
||||||
newClone.size = this.size;
|
newClone.size = this.size;
|
||||||
newClone.currentCostume = this.currentCostume;
|
newClone.currentCostume = this.currentCostume;
|
||||||
|
@ -687,15 +720,19 @@ RenderedTarget.prototype.onStopAll = function () {
|
||||||
* @param {object} data An object with sprite info data to set.
|
* @param {object} data An object with sprite info data to set.
|
||||||
*/
|
*/
|
||||||
RenderedTarget.prototype.postSpriteInfo = function (data) {
|
RenderedTarget.prototype.postSpriteInfo = function (data) {
|
||||||
|
var force = data.hasOwnProperty('force') ? data.force : null;
|
||||||
if (data.hasOwnProperty('x')) {
|
if (data.hasOwnProperty('x')) {
|
||||||
this.setXY(data.x, this.y);
|
this.setXY(data.x, this.y, force);
|
||||||
}
|
}
|
||||||
if (data.hasOwnProperty('y')) {
|
if (data.hasOwnProperty('y')) {
|
||||||
this.setXY(this.x, data.y);
|
this.setXY(this.x, data.y, force);
|
||||||
}
|
}
|
||||||
if (data.hasOwnProperty('direction')) {
|
if (data.hasOwnProperty('direction')) {
|
||||||
this.setDirection(data.direction);
|
this.setDirection(data.direction);
|
||||||
}
|
}
|
||||||
|
if (data.hasOwnProperty('draggable')) {
|
||||||
|
this.setDraggable(data.draggable);
|
||||||
|
}
|
||||||
if (data.hasOwnProperty('rotationStyle')) {
|
if (data.hasOwnProperty('rotationStyle')) {
|
||||||
this.setRotationStyle(data.rotationStyle);
|
this.setRotationStyle(data.rotationStyle);
|
||||||
}
|
}
|
||||||
|
@ -704,6 +741,20 @@ RenderedTarget.prototype.postSpriteInfo = function (data) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Put the sprite into the drag state. While in effect, setXY must be forced
|
||||||
|
*/
|
||||||
|
RenderedTarget.prototype.startDrag = function () {
|
||||||
|
this.dragging = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the sprite from the drag state.
|
||||||
|
*/
|
||||||
|
RenderedTarget.prototype.stopDrag = function () {
|
||||||
|
this.dragging = false;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serialize sprite info, used when emitting events about the sprite
|
* Serialize sprite info, used when emitting events about the sprite
|
||||||
* @returns {object} Sprite data as a simple object
|
* @returns {object} Sprite data as a simple object
|
||||||
|
@ -717,6 +768,7 @@ RenderedTarget.prototype.toJSON = function () {
|
||||||
y: this.y,
|
y: this.y,
|
||||||
size: this.size,
|
size: this.size,
|
||||||
direction: this.direction,
|
direction: this.direction,
|
||||||
|
draggable: this.draggable,
|
||||||
costume: this.getCurrentCostume(),
|
costume: this.getCurrentCostume(),
|
||||||
costumeCount: this.getCostumes().length,
|
costumeCount: this.getCostumes().length,
|
||||||
currentCostume: this.currentCostume,
|
currentCostume: this.currentCostume,
|
||||||
|
|
|
@ -46,7 +46,7 @@ var Sprite = function (blocks, runtime) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a clone of this sprite.
|
* Create a clone of this sprite.
|
||||||
* @returns {!Clone} Newly created clone.
|
* @returns {!RenderedTarget} Newly created clone.
|
||||||
*/
|
*/
|
||||||
Sprite.prototype.createClone = function () {
|
Sprite.prototype.createClone = function () {
|
||||||
var newClone = new RenderedTarget(this, this.runtime);
|
var newClone = new RenderedTarget(this, this.runtime);
|
||||||
|
|
|
@ -90,6 +90,15 @@ Cast.toRgbColorObject = function (value) {
|
||||||
return color;
|
return color;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if a Scratch argument is a white space string (or null / empty).
|
||||||
|
* @param {*} val value to check.
|
||||||
|
* @return {boolean} True if the argument is all white spaces or null / empty.
|
||||||
|
*/
|
||||||
|
Cast.isWhiteSpace = function (val) {
|
||||||
|
return val === null || typeof val === 'string' && val.trim().length === 0;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compare two values, using Scratch cast, case-insensitive string compare, etc.
|
* Compare two values, using Scratch cast, case-insensitive string compare, etc.
|
||||||
* In Scratch 2.0, this is captured by `interp.compare.`
|
* In Scratch 2.0, this is captured by `interp.compare.`
|
||||||
|
@ -100,6 +109,11 @@ Cast.toRgbColorObject = function (value) {
|
||||||
Cast.compare = function (v1, v2) {
|
Cast.compare = function (v1, v2) {
|
||||||
var n1 = Number(v1);
|
var n1 = Number(v1);
|
||||||
var n2 = Number(v2);
|
var n2 = Number(v2);
|
||||||
|
if (n1 === 0 && Cast.isWhiteSpace(v1)) {
|
||||||
|
n1 = NaN;
|
||||||
|
} else if (n2 === 0 && Cast.isWhiteSpace(v2)) {
|
||||||
|
n2 = NaN;
|
||||||
|
}
|
||||||
if (isNaN(n1) || isNaN(n2)) {
|
if (isNaN(n1) || isNaN(n2)) {
|
||||||
// At least one argument can't be converted to a number.
|
// At least one argument can't be converted to a number.
|
||||||
// Scratch compares strings as case insensitive.
|
// Scratch compares strings as case insensitive.
|
||||||
|
|
53
src/util/filter-toolbox.js
Normal file
53
src/util/filter-toolbox.js
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
/**
|
||||||
|
* Filter Blockly toolbox XML node containing blocks to only those with
|
||||||
|
* valid opcodes. Return a copy of the node with valid blocks.
|
||||||
|
* @param {HTMLElement} node Blockly toolbox XML node
|
||||||
|
* @param {Array.<string>} opcodes Valid opcodes. Blocks producing other opcodes
|
||||||
|
* will be filtered.
|
||||||
|
* @returns {HTMLElement} filtered toolbox XML node
|
||||||
|
*/
|
||||||
|
var filterToolboxNode = function (node, opcodes) {
|
||||||
|
var filteredCategory = node.cloneNode();
|
||||||
|
for (var block = node.firstElementChild; block; block = block.nextElementSibling) {
|
||||||
|
if (block.nodeName.toLowerCase() !== 'block') continue;
|
||||||
|
var opcode = block.getAttribute('type').toLowerCase();
|
||||||
|
if (opcodes.indexOf(opcode) !== -1) {
|
||||||
|
filteredCategory.appendChild(block.cloneNode(true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filteredCategory;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter Blockly toolbox XML and return a copy which only contains blocks with
|
||||||
|
* existent opcodes. Categories with no valid children will be removed.
|
||||||
|
* @param {HTMLElement} toolbox Blockly toolbox XML node
|
||||||
|
* @param {Array.<string>} opcodes Valid opcodes. Blocks producing other opcodes
|
||||||
|
* will be filtered.
|
||||||
|
* @returns {HTMLElement} filtered toolbox XML node
|
||||||
|
*/
|
||||||
|
var filterToolbox = function (toolbox, opcodes) {
|
||||||
|
if (!toolbox.hasChildNodes()) return toolbox;
|
||||||
|
var filteredToolbox;
|
||||||
|
if (toolbox.firstElementChild.nodeName.toLowerCase() === 'category') {
|
||||||
|
filteredToolbox = toolbox.cloneNode();
|
||||||
|
for (
|
||||||
|
var category = toolbox.firstElementChild;
|
||||||
|
category;
|
||||||
|
category = category.nextElementSibling
|
||||||
|
) {
|
||||||
|
if (category.nodeName.toLowerCase() !== 'category') continue;
|
||||||
|
var filteredCategory = filterToolboxNode(category, opcodes);
|
||||||
|
if (filteredCategory.hasChildNodes() ||
|
||||||
|
filteredCategory.hasAttribute('custom')
|
||||||
|
) {
|
||||||
|
filteredToolbox.appendChild(filteredCategory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
filteredToolbox = filterToolboxNode(toolbox, opcodes);
|
||||||
|
}
|
||||||
|
return filteredToolbox;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = filterToolbox;
|
|
@ -23,16 +23,35 @@ var Timer = function () {};
|
||||||
*/
|
*/
|
||||||
Timer.prototype.startTime = 0;
|
Timer.prototype.startTime = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable use of self.performance for now as it results in lower performance
|
||||||
|
* However, instancing it like below (caching the self.performance to a local variable) negates most of the issues.
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
var USE_PERFORMANCE = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Legacy object to allow for us to call now to get the old style date time (for backwards compatibility)
|
||||||
|
* @deprecated This is only called via the nowObj.now() if no other means is possible...
|
||||||
|
*/
|
||||||
|
var legacyDateCode = {
|
||||||
|
now: function () {
|
||||||
|
return new Date().getTime();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this object to route all time functions through single access points.
|
||||||
|
*/
|
||||||
|
var nowObj = (USE_PERFORMANCE && typeof self !== 'undefined' && self.performance && 'now' in self.performance) ?
|
||||||
|
self.performance : Date.now ? Date : legacyDateCode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the currently known absolute time, in ms precision.
|
* Return the currently known absolute time, in ms precision.
|
||||||
* @returns {number} ms elapsed since 1 January 1970 00:00:00 UTC.
|
* @returns {number} ms elapsed since 1 January 1970 00:00:00 UTC.
|
||||||
*/
|
*/
|
||||||
Timer.prototype.time = function () {
|
Timer.prototype.time = function () {
|
||||||
if (Date.now) {
|
return nowObj.now();
|
||||||
return Date.now();
|
|
||||||
} else {
|
|
||||||
return new Date().getTime();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -43,12 +62,7 @@ Timer.prototype.time = function () {
|
||||||
* @returns {number} ms-scale accurate time relative to other relative times.
|
* @returns {number} ms-scale accurate time relative to other relative times.
|
||||||
*/
|
*/
|
||||||
Timer.prototype.relativeTime = function () {
|
Timer.prototype.relativeTime = function () {
|
||||||
if (typeof self !== 'undefined' &&
|
return nowObj.now();
|
||||||
self.performance && 'now' in self.performance) {
|
|
||||||
return self.performance.now();
|
|
||||||
} else {
|
|
||||||
return this.time();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -56,15 +70,11 @@ Timer.prototype.relativeTime = function () {
|
||||||
* at the most accurate precision possible.
|
* at the most accurate precision possible.
|
||||||
*/
|
*/
|
||||||
Timer.prototype.start = function () {
|
Timer.prototype.start = function () {
|
||||||
this.startTime = this.relativeTime();
|
this.startTime = nowObj.now();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Check time elapsed since `timer.start` was called.
|
|
||||||
* @returns {number} Time elapsed, in ms (possibly sub-ms precision).
|
|
||||||
*/
|
|
||||||
Timer.prototype.timeElapsed = function () {
|
Timer.prototype.timeElapsed = function () {
|
||||||
return this.relativeTime() - this.startTime;
|
return nowObj.now() - this.startTime;
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = Timer;
|
module.exports = Timer;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
var EventEmitter = require('events');
|
var EventEmitter = require('events');
|
||||||
var util = require('util');
|
var util = require('util');
|
||||||
|
|
||||||
|
var filterToolbox = require('./util/filter-toolbox');
|
||||||
var Runtime = require('./engine/runtime');
|
var Runtime = require('./engine/runtime');
|
||||||
|
|
||||||
var sb2 = require('./serialization/sb2');
|
var sb2 = require('./serialization/sb2');
|
||||||
|
@ -379,6 +380,41 @@ VirtualMachine.prototype.emitWorkspaceUpdate = function () {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a target id for a drawable id. Useful for interacting with the renderer
|
||||||
|
* @param {int} drawableId The drawable id to request the target id for
|
||||||
|
* @returns {?string} The target id, if found. Will also be null if the target found is the stage.
|
||||||
|
*/
|
||||||
|
VirtualMachine.prototype.getTargetIdForDrawableId = function (drawableId) {
|
||||||
|
var target = this.runtime.getTargetByDrawableId(drawableId);
|
||||||
|
if (target.hasOwnProperty('id') && target.hasOwnProperty('isStage') && !target.isStage) {
|
||||||
|
return target.id;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Put a target into a "drag" state, during which its X/Y positions will be unaffected
|
||||||
|
* by blocks.
|
||||||
|
* @param {string} targetId The id for the target to put into a drag state
|
||||||
|
*/
|
||||||
|
VirtualMachine.prototype.startDrag = function (targetId) {
|
||||||
|
var target = this.runtime.getTargetById(targetId);
|
||||||
|
if (target) {
|
||||||
|
target.startDrag();
|
||||||
|
this.setEditingTarget(target.id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a target from a drag state, so blocks may begin affecting X/Y position again
|
||||||
|
* @param {string} targetId The id for the target to remove from the drag state
|
||||||
|
*/
|
||||||
|
VirtualMachine.prototype.stopDrag = function (targetId) {
|
||||||
|
var target = this.runtime.getTargetById(targetId);
|
||||||
|
if (target) target.stopDrag();
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Post/edit sprite info for the current editing target.
|
* Post/edit sprite info for the current editing target.
|
||||||
* @param {object} data An object with sprite info data to set.
|
* @param {object} data An object with sprite info data to set.
|
||||||
|
@ -387,4 +423,17 @@ VirtualMachine.prototype.postSpriteInfo = function (data) {
|
||||||
this.editingTarget.postSpriteInfo(data);
|
this.editingTarget.postSpriteInfo(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter Blockly toolbox XML and return a copy which only contains blocks with
|
||||||
|
* existent opcodes. Categories with no valid children will be removed.
|
||||||
|
* @param {HTMLElement} toolbox Blockly toolbox XML node
|
||||||
|
* @returns {HTMLElement} filtered toolbox XML node
|
||||||
|
*/
|
||||||
|
VirtualMachine.prototype.filterToolbox = function (toolbox) {
|
||||||
|
var opcodes = Object.keys(this.runtime._primitives)
|
||||||
|
.concat(Object.keys(this.runtime._hats));
|
||||||
|
return filterToolbox(toolbox, opcodes);
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = VirtualMachine;
|
module.exports = VirtualMachine;
|
||||||
|
|
5
test/fixtures/.eslintrc.js
vendored
Normal file
5
test/fixtures/.eslintrc.js
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
module.exports = {
|
||||||
|
rules: {
|
||||||
|
'max-len': [0]
|
||||||
|
}
|
||||||
|
};
|
BIN
test/fixtures/hat-execution-order.sb2
vendored
Normal file
BIN
test/fixtures/hat-execution-order.sb2
vendored
Normal file
Binary file not shown.
840
test/fixtures/toolboxes.js
vendored
Normal file
840
test/fixtures/toolboxes.js
vendored
Normal file
|
@ -0,0 +1,840 @@
|
||||||
|
var jsdom = require('jsdom').jsdom;
|
||||||
|
var categories = '<xml id="toolbox-categories" style="display: none">' +
|
||||||
|
'<category name="Motion" colour="#4C97FF" secondaryColour="#3373CC">' +
|
||||||
|
'<block type="motion_movesteps">' +
|
||||||
|
'<value name="STEPS">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">10</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="motion_turnright">' +
|
||||||
|
'<value name="DEGREES">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">15</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="motion_turnleft">' +
|
||||||
|
'<value name="DEGREES">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">15</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="motion_pointindirection">' +
|
||||||
|
'<value name="DIRECTION">' +
|
||||||
|
'<shadow type="math_angle">' +
|
||||||
|
'<field name="NUM">90</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="motion_pointtowards">' +
|
||||||
|
'<value name="TOWARDS">' +
|
||||||
|
'<shadow type="motion_pointtowards_menu">' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="motion_gotoxy">' +
|
||||||
|
'<value name="X">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">0</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'<value name="Y">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">0</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="motion_goto">' +
|
||||||
|
'<value name="TO">' +
|
||||||
|
'<shadow type="motion_goto_menu">' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="motion_glidesecstoxy">' +
|
||||||
|
'<value name="SECS">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">1</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'<value name="X">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">0</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'<value name="Y">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">0</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="motion_changexby">' +
|
||||||
|
'<value name="DX">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">10</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="motion_setx">' +
|
||||||
|
'<value name="X">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">0</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="motion_changeyby">' +
|
||||||
|
'<value name="DY">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">10</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="motion_sety">' +
|
||||||
|
'<value name="Y">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">0</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="motion_ifonedgebounce"></block>' +
|
||||||
|
'<block type="motion_setrotationstyle">' +
|
||||||
|
'<value name="STYLE">' +
|
||||||
|
'<shadow type="motion_setrotationstyle_menu"></shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="motion_xposition"></block>' +
|
||||||
|
'<block type="motion_yposition"></block>' +
|
||||||
|
'<block type="motion_direction"></block>' +
|
||||||
|
'</category>' +
|
||||||
|
'<category name="Looks" colour="#9966FF" secondaryColour="#774DCB">' +
|
||||||
|
'<block type="looks_sayforsecs">' +
|
||||||
|
'<value name="MESSAGE">' +
|
||||||
|
'<shadow type="text">' +
|
||||||
|
'<field name="TEXT">Hello!</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'<value name="SECS">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">2</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="looks_say">' +
|
||||||
|
'<value name="MESSAGE">' +
|
||||||
|
'<shadow type="text">' +
|
||||||
|
'<field name="TEXT">Hello!</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="looks_thinkforsecs">' +
|
||||||
|
'<value name="MESSAGE">' +
|
||||||
|
'<shadow type="text">' +
|
||||||
|
'<field name="TEXT">Hmm...</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'<value name="SECS">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">2</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="looks_think">' +
|
||||||
|
'<value name="MESSAGE">' +
|
||||||
|
'<shadow type="text">' +
|
||||||
|
'<field name="TEXT">Hmm...</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="looks_show"></block>' +
|
||||||
|
'<block type="looks_hide"></block>' +
|
||||||
|
'<block type="looks_switchcostumeto">' +
|
||||||
|
'<value name="COSTUME">' +
|
||||||
|
'<shadow type="looks_costume"></shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="looks_nextcostume"></block>' +
|
||||||
|
'<block type="looks_nextbackdrop"></block>' +
|
||||||
|
'<block type="looks_switchbackdropto">' +
|
||||||
|
'<value name="BACKDROP">' +
|
||||||
|
'<shadow type="looks_backdrops"></shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="looks_switchbackdroptoandwait">' +
|
||||||
|
'<value name="BACKDROP">' +
|
||||||
|
'<shadow type="looks_backdrops"></shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="looks_changeeffectby">' +
|
||||||
|
'<value name="EFFECT">' +
|
||||||
|
'<shadow type="looks_effectmenu"></shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'<value name="CHANGE">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">10</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="looks_seteffectto">' +
|
||||||
|
'<value name="EFFECT">' +
|
||||||
|
'<shadow type="looks_effectmenu"></shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'<value name="VALUE">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">10</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="looks_cleargraphiceffects"></block>' +
|
||||||
|
'<block type="looks_changesizeby">' +
|
||||||
|
'<value name="CHANGE">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">10</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="looks_setsizeto">' +
|
||||||
|
'<value name="SIZE">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">100</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="looks_gotofront"></block>' +
|
||||||
|
'<block type="looks_gobacklayers">' +
|
||||||
|
'<value name="NUM">' +
|
||||||
|
'<shadow type="math_integer">' +
|
||||||
|
'<field name="NUM">1</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="looks_costumeorder"></block>' +
|
||||||
|
'<block type="looks_backdroporder"></block>' +
|
||||||
|
'<block type="looks_backdropname"></block>' +
|
||||||
|
'<block type="looks_size"></block>' +
|
||||||
|
'</category>' +
|
||||||
|
'<category name="Sound" colour="#D65CD6" secondaryColour="#BD42BD">' +
|
||||||
|
'<block type="sound_play">' +
|
||||||
|
'<value name="SOUND_MENU">' +
|
||||||
|
'<shadow type="sound_sounds_menu"></shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="sound_playuntildone">' +
|
||||||
|
'<value name="SOUND_MENU">' +
|
||||||
|
'<shadow type="sound_sounds_menu"></shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="sound_stopallsounds"></block>' +
|
||||||
|
'<block type="sound_playdrumforbeats">' +
|
||||||
|
'<value name="DRUM">' +
|
||||||
|
'<shadow type="sound_drums_menu"></shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'<value name="BEATS">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">0.25</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="sound_restforbeats">' +
|
||||||
|
'<value name="BEATS">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">0.25</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="sound_playnoteforbeats">' +
|
||||||
|
'<value name="NOTE">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">60</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'<value name="BEATS">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">0.5</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="sound_setinstrumentto">' +
|
||||||
|
'<value name="INSTRUMENT">' +
|
||||||
|
'<shadow type="sound_instruments_menu"></shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="sound_seteffectto">' +
|
||||||
|
'<value name="EFFECT">' +
|
||||||
|
'<shadow type="sound_effects_menu"></shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'<value name="VALUE">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">100</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="sound_changeeffectby">' +
|
||||||
|
'<value name="EFFECT">' +
|
||||||
|
'<shadow type="sound_effects_menu"></shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'<value name="VALUE">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">10</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="sound_cleareffects"></block>' +
|
||||||
|
'<block type="sound_changevolumeby">' +
|
||||||
|
'<value name="VOLUME">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">-10</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="sound_setvolumeto">' +
|
||||||
|
'<value name="VOLUME">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">100</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="sound_volume"></block>' +
|
||||||
|
'<block type="sound_changetempoby">' +
|
||||||
|
'<value name="TEMPO">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">20</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="sound_settempotobpm">' +
|
||||||
|
'<value name="TEMPO">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">60</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="sound_tempo"></block>' +
|
||||||
|
'</category>' +
|
||||||
|
'<category name="Pen" colour="#00B295" secondaryColour="#0B8E69">' +
|
||||||
|
'<block type="pen_clear"></block>' +
|
||||||
|
'<block type="pen_stamp"></block>' +
|
||||||
|
'<block type="pen_pendown"></block>' +
|
||||||
|
'<block type="pen_penup"></block>' +
|
||||||
|
'<block type="pen_setpencolortocolor">' +
|
||||||
|
'<value name="COLOR">' +
|
||||||
|
'<shadow type="colour_picker">' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="pen_changepencolorby">' +
|
||||||
|
'<value name="COLOR">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">10</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="pen_setpencolortonum">' +
|
||||||
|
'<value name="COLOR">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">0</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="pen_changepenshadeby">' +
|
||||||
|
'<value name="SHADE">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">10</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="pen_setpenshadeto">' +
|
||||||
|
'<value name="SHADE">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">50</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="pen_changepensizeby">' +
|
||||||
|
'<value name="SIZE">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">1</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="pen_setpensizeto">' +
|
||||||
|
'<value name="SIZE">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">1</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'</category>' +
|
||||||
|
'<category name="Data" colour="#FF8C1A" secondaryColour="#DB6E00" custom="VARIABLE">' +
|
||||||
|
'</category>' +
|
||||||
|
'<category name="Lists" colour="#FF8C1A" secondaryColour="#DB6E00">' +
|
||||||
|
'<block type="data_listcontents"></block>' +
|
||||||
|
'<block type="data_addtolist">' +
|
||||||
|
'<value name="ITEM">' +
|
||||||
|
'<shadow type="text">' +
|
||||||
|
'<field name="TEXT">thing</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="data_deleteoflist">' +
|
||||||
|
'<value name="INDEX">' +
|
||||||
|
'<shadow type="data_listindexall">' +
|
||||||
|
'<field name="INDEX">1</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="data_insertatlist">' +
|
||||||
|
'<value name="INDEX">' +
|
||||||
|
'<shadow type="data_listindexrandom">' +
|
||||||
|
'<field name="INDEX">1</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'<value name="ITEM">' +
|
||||||
|
'<shadow type="text">' +
|
||||||
|
'<field name="TEXT">thing</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="data_replaceitemoflist">' +
|
||||||
|
'<value name="INDEX">' +
|
||||||
|
'<shadow type="data_listindexrandom">' +
|
||||||
|
'<field name="INDEX">1</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'<value name="ITEM">' +
|
||||||
|
'<shadow type="text">' +
|
||||||
|
'<field name="TEXT">thing</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="data_itemoflist">' +
|
||||||
|
'<value name="INDEX">' +
|
||||||
|
'<shadow type="data_listindexrandom">' +
|
||||||
|
'<field name="INDEX">1</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="data_lengthoflist"></block>' +
|
||||||
|
'<block type="data_listcontainsitem">' +
|
||||||
|
'<value name="ITEM">' +
|
||||||
|
'<shadow type="text">' +
|
||||||
|
'<field name="TEXT">thing</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="data_showlist"></block>' +
|
||||||
|
'<block type="data_hidelist"></block>' +
|
||||||
|
'</category>' +
|
||||||
|
'<category name="Events" colour="#FFD500" secondaryColour="#CC9900">' +
|
||||||
|
'<block type="event_whenflagclicked"></block>' +
|
||||||
|
'<block type="event_whenkeypressed">' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="event_whenthisspriteclicked"></block>' +
|
||||||
|
'<block type="event_whenbackdropswitchesto">' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="event_whengreaterthan">' +
|
||||||
|
'<value name="VALUE">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">10</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="event_whenbroadcastreceived">' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="event_broadcast">' +
|
||||||
|
'<value name="BROADCAST_OPTION">' +
|
||||||
|
'<shadow type="event_broadcast_menu"></shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="event_broadcastandwait">' +
|
||||||
|
'<value name="BROADCAST_OPTION">' +
|
||||||
|
'<shadow type="event_broadcast_menu"></shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'</category>' +
|
||||||
|
'<category name="Control" colour="#FFAB19" secondaryColour="#CF8B17">' +
|
||||||
|
'<block type="control_wait">' +
|
||||||
|
'<value name="DURATION">' +
|
||||||
|
'<shadow type="math_positive_number">' +
|
||||||
|
'<field name="NUM">1</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="control_repeat">' +
|
||||||
|
'<value name="TIMES">' +
|
||||||
|
'<shadow type="math_whole_number">' +
|
||||||
|
'<field name="NUM">10</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="control_forever"></block>' +
|
||||||
|
'<block type="control_if"></block>' +
|
||||||
|
'<block type="control_if_else"></block>' +
|
||||||
|
'<block type="control_wait_until"></block>' +
|
||||||
|
'<block type="control_repeat_until"></block>' +
|
||||||
|
'<block type="control_stop"></block>' +
|
||||||
|
'<block type="control_start_as_clone"></block>' +
|
||||||
|
'<block type="control_create_clone_of">' +
|
||||||
|
'<value name="CLONE_OPTION">' +
|
||||||
|
'<shadow type="control_create_clone_of_menu"></shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="control_delete_this_clone"></block>' +
|
||||||
|
'</category>' +
|
||||||
|
'<category name="Sensing" colour="#4CBFE6" secondaryColour="#2E8EB8">' +
|
||||||
|
'<block type="sensing_touchingobject">' +
|
||||||
|
'<value name="TOUCHINGOBJECTMENU">' +
|
||||||
|
'<shadow type="sensing_touchingobjectmenu"></shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="sensing_touchingcolor">' +
|
||||||
|
'<value name="COLOR">' +
|
||||||
|
'<shadow type="colour_picker"></shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="sensing_coloristouchingcolor">' +
|
||||||
|
'<value name="COLOR">' +
|
||||||
|
'<shadow type="colour_picker"></shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'<value name="COLOR2">' +
|
||||||
|
'<shadow type="colour_picker"></shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="sensing_distanceto">' +
|
||||||
|
'<value name="DISTANCETOMENU">' +
|
||||||
|
'<shadow type="sensing_distancetomenu"></shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="sensing_askandwait">' +
|
||||||
|
'<value name="QUESTION">' +
|
||||||
|
'<shadow type="text">' +
|
||||||
|
'<field name="TEXT">What\'s your name?</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="sensing_answer"></block>' +
|
||||||
|
'<block type="sensing_keypressed">' +
|
||||||
|
'<value name="KEY_OPTION">' +
|
||||||
|
'<shadow type="sensing_keyoptions"></shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="sensing_mousedown"></block>' +
|
||||||
|
'<block type="sensing_mousex"></block>' +
|
||||||
|
'<block type="sensing_mousey"></block>' +
|
||||||
|
'<block type="sensing_loudness"></block>' +
|
||||||
|
'<block type="sensing_videoon">' +
|
||||||
|
'<value name="VIDEOONMENU1">' +
|
||||||
|
'<shadow type="sensing_videoonmenuone"></shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'<value name="VIDEOONMENU2">' +
|
||||||
|
'<shadow type="sensing_videoonmenutwo"></shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="sensing_videotoggle">' +
|
||||||
|
'<value name="VIDEOTOGGLEMENU">' +
|
||||||
|
'<shadow type="sensing_videotogglemenu"></shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="sensing_setvideotransparency">' +
|
||||||
|
'<value name="TRANSPARENCY">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">50</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="sensing_timer"></block>' +
|
||||||
|
'<block type="sensing_resettimer"></block>' +
|
||||||
|
'<block type="sensing_of">' +
|
||||||
|
'<value name="PROPERTY">' +
|
||||||
|
'<shadow type="sensing_of_property_menu"></shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'<value name="OBJECT">' +
|
||||||
|
'<shadow type="sensing_of_object_menu"></shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="sensing_current">' +
|
||||||
|
'<value name="CURRENTMENU">' +
|
||||||
|
'<shadow type="sensing_currentmenu"></shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="sensing_dayssince2000"></block>' +
|
||||||
|
'<block type="sensing_username"></block>' +
|
||||||
|
'</category>' +
|
||||||
|
'<category name="Operators" colour="#40BF4A" secondaryColour="#389438">' +
|
||||||
|
'<block type="operator_add">' +
|
||||||
|
'<value name="NUM1">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM"></field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'<value name="NUM2">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM"></field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="operator_subtract">' +
|
||||||
|
'<value name="NUM1">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM"></field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'<value name="NUM2">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM"></field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="operator_multiply">' +
|
||||||
|
'<value name="NUM1">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM"></field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'<value name="NUM2">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM"></field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="operator_divide">' +
|
||||||
|
'<value name="NUM1">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM"></field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'<value name="NUM2">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM"></field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="operator_random">' +
|
||||||
|
'<value name="FROM">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">1</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'<value name="TO">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">10</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="operator_lt">' +
|
||||||
|
'<value name="OPERAND1">' +
|
||||||
|
'<shadow type="text">' +
|
||||||
|
'<field name="TEXT"></field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'<value name="OPERAND2">' +
|
||||||
|
'<shadow type="text">' +
|
||||||
|
'<field name="TEXT"></field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="operator_equals">' +
|
||||||
|
'<value name="OPERAND1">' +
|
||||||
|
'<shadow type="text">' +
|
||||||
|
'<field name="TEXT"></field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'<value name="OPERAND2">' +
|
||||||
|
'<shadow type="text">' +
|
||||||
|
'<field name="TEXT"></field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="operator_gt">' +
|
||||||
|
'<value name="OPERAND1">' +
|
||||||
|
'<shadow type="text">' +
|
||||||
|
'<field name="TEXT"></field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'<value name="OPERAND2">' +
|
||||||
|
'<shadow type="text">' +
|
||||||
|
'<field name="TEXT"></field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="operator_and"></block>' +
|
||||||
|
'<block type="operator_or"></block>' +
|
||||||
|
'<block type="operator_not"></block>' +
|
||||||
|
'<block type="operator_join">' +
|
||||||
|
'<value name="STRING1">' +
|
||||||
|
'<shadow type="text">' +
|
||||||
|
'<field name="TEXT">hello</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'<value name="STRING2">' +
|
||||||
|
'<shadow type="text">' +
|
||||||
|
'<field name="TEXT">world</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="operator_letter_of">' +
|
||||||
|
'<value name="LETTER">' +
|
||||||
|
'<shadow type="math_whole_number">' +
|
||||||
|
'<field name="NUM">1</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'<value name="STRING">' +
|
||||||
|
'<shadow type="text">' +
|
||||||
|
'<field name="TEXT">world</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="operator_length">' +
|
||||||
|
'<value name="STRING">' +
|
||||||
|
'<shadow type="text">' +
|
||||||
|
'<field name="TEXT">world</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="operator_mod">' +
|
||||||
|
'<value name="NUM1">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM"></field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'<value name="NUM2">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM"></field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="operator_round">' +
|
||||||
|
'<value name="NUM">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM"></field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="operator_mathop">' +
|
||||||
|
'<value name="OPERATOR">' +
|
||||||
|
'<shadow type="operator_mathop_menu"></shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'<value name="NUM">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM"></field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'</category>' +
|
||||||
|
'<category name="More Blocks" colour="#FF6680" secondaryColour="#FF3355" custom="PROCEDURE"></category>' +
|
||||||
|
'</xml>';
|
||||||
|
var simple = '<xml id="toolbox-simple" style="display: none">' +
|
||||||
|
'<block type="motion_movesteps">' +
|
||||||
|
'<value name="STEPS">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">10</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="motion_turnright">' +
|
||||||
|
'<value name="DEGREES">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">15</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="motion_turnleft">' +
|
||||||
|
'<value name="DEGREES">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">15</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="motion_pointindirection">' +
|
||||||
|
'<value name="DIRECTION">' +
|
||||||
|
'<shadow type="math_angle">' +
|
||||||
|
'<field name="NUM">90</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="motion_pointtowards">' +
|
||||||
|
'<value name="TOWARDS">' +
|
||||||
|
'<shadow type="motion_pointtowards_menu">' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="motion_gotoxy">' +
|
||||||
|
'<value name="X">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">0</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'<value name="Y">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">0</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="motion_goto">' +
|
||||||
|
'<value name="TO">' +
|
||||||
|
'<shadow type="motion_goto_menu">' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="motion_glidesecstoxy">' +
|
||||||
|
'<value name="SECS">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">1</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'<value name="X">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">0</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'<value name="Y">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">0</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="motion_changexby">' +
|
||||||
|
'<value name="DX">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">10</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="motion_setx">' +
|
||||||
|
'<value name="X">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">0</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="motion_changeyby">' +
|
||||||
|
'<value name="DY">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">10</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="motion_sety">' +
|
||||||
|
'<value name="Y">' +
|
||||||
|
'<shadow type="math_number">' +
|
||||||
|
'<field name="NUM">0</field>' +
|
||||||
|
'</shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="motion_ifonedgebounce"></block>' +
|
||||||
|
'<block type="motion_setrotationstyle">' +
|
||||||
|
'<value name="STYLE">' +
|
||||||
|
'<shadow type="motion_setrotationstyle_menu"></shadow>' +
|
||||||
|
'</value>' +
|
||||||
|
'</block>' +
|
||||||
|
'<block type="motion_xposition"></block>' +
|
||||||
|
'<block type="motion_yposition"></block>' +
|
||||||
|
'<block type="motion_direction"></block>' +
|
||||||
|
'</xml>';
|
||||||
|
var empty = '<xml id="toolbox-simple" style="display: none"></xml>';
|
||||||
|
module.exports = {
|
||||||
|
categories: jsdom(categories).body.firstElementChild,
|
||||||
|
simple: jsdom(simple).body.firstElementChild,
|
||||||
|
empty: jsdom(empty).body.firstElementChild
|
||||||
|
};
|
|
@ -34,6 +34,7 @@ test('complex', function (t) {
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 10,
|
y: 10,
|
||||||
direction: 90,
|
direction: 90,
|
||||||
|
draggable: true,
|
||||||
rotationStyle: 'all around',
|
rotationStyle: 'all around',
|
||||||
visible: true
|
visible: true
|
||||||
});
|
});
|
||||||
|
|
39
test/integration/hat-execution-order.js
Normal file
39
test/integration/hat-execution-order.js
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
var path = require('path');
|
||||||
|
var test = require('tap').test;
|
||||||
|
var extract = require('../fixtures/extract');
|
||||||
|
var VirtualMachine = require('../../src/index');
|
||||||
|
|
||||||
|
var projectUri = path.resolve(__dirname, '../fixtures/hat-execution-order.sb2');
|
||||||
|
var project = extract(projectUri);
|
||||||
|
|
||||||
|
test('complex', function (t) {
|
||||||
|
var vm = new VirtualMachine();
|
||||||
|
|
||||||
|
// Evaluate playground data and exit
|
||||||
|
vm.on('playgroundData', function (e) {
|
||||||
|
var threads = JSON.parse(e.threads);
|
||||||
|
t.ok(threads.length === 0);
|
||||||
|
|
||||||
|
var results = vm.runtime.targets[0].lists.results.contents;
|
||||||
|
t.deepEqual(results, ['3', '2', '1', 'stage']);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
process.nextTick(process.exit);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start VM, load project, and run
|
||||||
|
t.doesNotThrow(function () {
|
||||||
|
vm.start();
|
||||||
|
vm.clear();
|
||||||
|
vm.setCompatibilityMode(false);
|
||||||
|
vm.setTurboMode(false);
|
||||||
|
vm.loadProject(project);
|
||||||
|
vm.greenFlag();
|
||||||
|
});
|
||||||
|
|
||||||
|
// After two seconds, get playground data and stop
|
||||||
|
setTimeout(function () {
|
||||||
|
vm.getPlaygroundData();
|
||||||
|
vm.stopAll();
|
||||||
|
}, 2000);
|
||||||
|
});
|
|
@ -104,7 +104,7 @@ test('stop', function (t) {
|
||||||
var state = {
|
var state = {
|
||||||
stopAll: 0,
|
stopAll: 0,
|
||||||
stopOtherTargetThreads: 0,
|
stopOtherTargetThreads: 0,
|
||||||
stopThread: 0
|
stopThisScript: 0
|
||||||
};
|
};
|
||||||
var util = {
|
var util = {
|
||||||
stopAll: function () {
|
stopAll: function () {
|
||||||
|
@ -113,8 +113,8 @@ test('stop', function (t) {
|
||||||
stopOtherTargetThreads: function () {
|
stopOtherTargetThreads: function () {
|
||||||
state.stopOtherTargetThreads++;
|
state.stopOtherTargetThreads++;
|
||||||
},
|
},
|
||||||
stopThread: function () {
|
stopThisScript: function () {
|
||||||
state.stopThread++;
|
state.stopThisScript++;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -125,6 +125,6 @@ test('stop', function (t) {
|
||||||
c.stop({STOP_OPTION: 'this script'}, util);
|
c.stop({STOP_OPTION: 'this script'}, util);
|
||||||
t.strictEqual(state.stopAll, 1);
|
t.strictEqual(state.stopAll, 1);
|
||||||
t.strictEqual(state.stopOtherTargetThreads, 2);
|
t.strictEqual(state.stopOtherTargetThreads, 2);
|
||||||
t.strictEqual(state.stopThread, 1);
|
t.strictEqual(state.stopThisScript, 1);
|
||||||
t.end();
|
t.end();
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,8 +1,178 @@
|
||||||
var test = require('tap').test;
|
var test = require('tap').test;
|
||||||
var Sequencer = require('../../src/engine/sequencer');
|
var Sequencer = require('../../src/engine/sequencer');
|
||||||
|
var Runtime = require('../../src/engine/runtime');
|
||||||
|
var Thread = require('../../src/engine/thread');
|
||||||
|
var RenderedTarget = require('../../src/sprites/rendered-target');
|
||||||
|
var Sprite = require('../../src/sprites/sprite');
|
||||||
|
|
||||||
test('spec', function (t) {
|
test('spec', function (t) {
|
||||||
t.type(Sequencer, 'function');
|
t.type(Sequencer, 'function');
|
||||||
// @todo
|
|
||||||
|
var r = new Runtime();
|
||||||
|
var s = new Sequencer(r);
|
||||||
|
|
||||||
|
t.type(s, 'object');
|
||||||
|
t.ok(s instanceof Sequencer);
|
||||||
|
|
||||||
|
t.type(s.stepThreads, 'function');
|
||||||
|
t.type(s.stepThread, 'function');
|
||||||
|
t.type(s.stepToBranch, 'function');
|
||||||
|
t.type(s.stepToProcedure, 'function');
|
||||||
|
t.type(s.retireThread, 'function');
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
var randomString = function () {
|
||||||
|
var top = Math.random().toString(36);
|
||||||
|
return top.substring(7);
|
||||||
|
};
|
||||||
|
|
||||||
|
var generateBlock = function (id) {
|
||||||
|
var block = {fields: Object,
|
||||||
|
id: id,
|
||||||
|
inputs: {},
|
||||||
|
STEPS: Object,
|
||||||
|
block: 'fakeBlock',
|
||||||
|
name: 'fakeName',
|
||||||
|
next: null,
|
||||||
|
opcode: 'procedures_defnoreturn',
|
||||||
|
mutation: {proccode: 'fakeCode'},
|
||||||
|
parent: null,
|
||||||
|
shadow: false,
|
||||||
|
topLevel: true,
|
||||||
|
x: 0,
|
||||||
|
y: 0
|
||||||
|
};
|
||||||
|
return block;
|
||||||
|
};
|
||||||
|
|
||||||
|
var generateBlockInput = function (id, next, inp) {
|
||||||
|
var block = {fields: Object,
|
||||||
|
id: id,
|
||||||
|
inputs: {SUBSTACK: {block: inp, name: 'SUBSTACK'}},
|
||||||
|
STEPS: Object,
|
||||||
|
block: 'fakeBlock',
|
||||||
|
name: 'fakeName',
|
||||||
|
next: next,
|
||||||
|
opcode: 'procedures_defnoreturn',
|
||||||
|
mutation: {proccode: 'fakeCode'},
|
||||||
|
parent: null,
|
||||||
|
shadow: false,
|
||||||
|
topLevel: true,
|
||||||
|
x: 0,
|
||||||
|
y: 0
|
||||||
|
};
|
||||||
|
return block;
|
||||||
|
};
|
||||||
|
|
||||||
|
var generateThread = function (runtime) {
|
||||||
|
var s = new Sprite();
|
||||||
|
var rt = new RenderedTarget(s, runtime);
|
||||||
|
var th = new Thread(randomString());
|
||||||
|
|
||||||
|
var next = randomString();
|
||||||
|
var inp = randomString();
|
||||||
|
var name = th.topBlock;
|
||||||
|
|
||||||
|
rt.blocks.createBlock(generateBlockInput(name, next, inp));
|
||||||
|
th.pushStack(name);
|
||||||
|
rt.blocks.createBlock(generateBlock(inp));
|
||||||
|
|
||||||
|
for (var i = 0; i < 10; i++) {
|
||||||
|
name = next;
|
||||||
|
next = randomString();
|
||||||
|
inp = randomString();
|
||||||
|
|
||||||
|
rt.blocks.createBlock(generateBlockInput(name, next, inp));
|
||||||
|
th.pushStack(name);
|
||||||
|
rt.blocks.createBlock(generateBlock(inp));
|
||||||
|
}
|
||||||
|
rt.blocks.createBlock(generateBlock(next));
|
||||||
|
th.pushStack(next);
|
||||||
|
th.target = rt;
|
||||||
|
|
||||||
|
runtime.threads.push(th);
|
||||||
|
|
||||||
|
return th;
|
||||||
|
};
|
||||||
|
|
||||||
|
test('stepThread', function (t) {
|
||||||
|
var r = new Runtime();
|
||||||
|
var s = new Sequencer(r);
|
||||||
|
var th = generateThread(r);
|
||||||
|
t.notEquals(th.status, Thread.STATUS_DONE);
|
||||||
|
s.stepThread(th);
|
||||||
|
t.strictEquals(th.status, Thread.STATUS_DONE);
|
||||||
|
th = generateThread(r);
|
||||||
|
th.status = Thread.STATUS_YIELD;
|
||||||
|
s.stepThread(th);
|
||||||
|
t.notEquals(th.status, Thread.STATUS_DONE);
|
||||||
|
th.status = Thread.STATUS_PROMISE_WAIT;
|
||||||
|
s.stepThread(th);
|
||||||
|
t.notEquals(th.status, Thread.STATUS_DONE);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('stepToBranch', function (t) {
|
||||||
|
var r = new Runtime();
|
||||||
|
var s = new Sequencer(r);
|
||||||
|
var th = generateThread(r);
|
||||||
|
s.stepToBranch(th, 2, false);
|
||||||
|
t.strictEquals(th.peekStack(), null);
|
||||||
|
th.popStack();
|
||||||
|
s.stepToBranch(th, 1, false);
|
||||||
|
t.strictEquals(th.peekStack(), null);
|
||||||
|
th.popStack();
|
||||||
|
th.popStack();
|
||||||
|
s.stepToBranch(th, 1, false);
|
||||||
|
t.notEquals(th.peekStack(), null);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('retireThread', function (t) {
|
||||||
|
var r = new Runtime();
|
||||||
|
var s = new Sequencer(r);
|
||||||
|
var th = generateThread(r);
|
||||||
|
t.strictEquals(th.stack.length, 12);
|
||||||
|
s.retireThread(th);
|
||||||
|
t.strictEquals(th.stack.length, 0);
|
||||||
|
t.strictEquals(th.status, Thread.STATUS_DONE);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('stepToProcedure', function (t) {
|
||||||
|
var r = new Runtime();
|
||||||
|
var s = new Sequencer(r);
|
||||||
|
var th = generateThread(r);
|
||||||
|
var expectedBlock = th.peekStack();
|
||||||
|
s.stepToProcedure(th, '');
|
||||||
|
t.strictEquals(th.peekStack(), expectedBlock);
|
||||||
|
s.stepToProcedure(th, 'faceCode');
|
||||||
|
t.strictEquals(th.peekStack(), expectedBlock);
|
||||||
|
s.stepToProcedure(th, 'faceCode');
|
||||||
|
th.target.blocks.getBlock(th.stack[th.stack.length - 4]).mutation.proccode = 'othercode';
|
||||||
|
expectedBlock = th.stack[th.stack.length - 4];
|
||||||
|
s.stepToProcedure(th, 'othercode');
|
||||||
|
t.strictEquals(th.peekStack(), expectedBlock);
|
||||||
|
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('stepThreads', function (t) {
|
||||||
|
var r = new Runtime();
|
||||||
|
r.currentStepTime = Infinity;
|
||||||
|
var s = new Sequencer(r);
|
||||||
|
t.strictEquals(s.stepThreads().length, 0);
|
||||||
|
generateThread(r);
|
||||||
|
t.strictEquals(r.threads.length, 1);
|
||||||
|
t.strictEquals(s.stepThreads().length, 0);
|
||||||
|
r.threads[0].status = Thread.STATUS_RUNNING;
|
||||||
|
t.strictEquals(s.stepThreads().length, 1);
|
||||||
|
|
||||||
t.end();
|
t.end();
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,8 +1,277 @@
|
||||||
var test = require('tap').test;
|
var test = require('tap').test;
|
||||||
var Thread = require('../../src/engine/thread');
|
var Thread = require('../../src/engine/thread');
|
||||||
|
var RenderedTarget = require('../../src/sprites/rendered-target');
|
||||||
|
var Sprite = require('../../src/sprites/sprite');
|
||||||
|
|
||||||
test('spec', function (t) {
|
test('spec', function (t) {
|
||||||
t.type(Thread, 'function');
|
t.type(Thread, 'function');
|
||||||
// @todo
|
|
||||||
|
var th = new Thread('arbitraryString');
|
||||||
|
t.type(th, 'object');
|
||||||
|
t.ok(th instanceof Thread);
|
||||||
|
t.type(th.pushStack, 'function');
|
||||||
|
t.type(th.reuseStackForNextBlock, 'function');
|
||||||
|
t.type(th.popStack, 'function');
|
||||||
|
t.type(th.stopThisScript, 'function');
|
||||||
|
t.type(th.peekStack, 'function');
|
||||||
|
t.type(th.peekStackFrame, 'function');
|
||||||
|
t.type(th.peekParentStackFrame, 'function');
|
||||||
|
t.type(th.pushReportedValue, 'function');
|
||||||
|
t.type(th.pushParam, 'function');
|
||||||
|
t.type(th.peekStack, 'function');
|
||||||
|
t.type(th.getParam, 'function');
|
||||||
|
t.type(th.atStackTop, 'function');
|
||||||
|
t.type(th.goToNextBlock, 'function');
|
||||||
|
t.type(th.isRecursiveCall, 'function');
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('pushStack', function (t) {
|
||||||
|
var th = new Thread('arbitraryString');
|
||||||
|
th.pushStack('arbitraryString');
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('popStack', function (t) {
|
||||||
|
var th = new Thread('arbitraryString');
|
||||||
|
th.pushStack('arbitraryString');
|
||||||
|
t.strictEquals(th.popStack(), 'arbitraryString');
|
||||||
|
t.strictEquals(th.popStack(), undefined);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('atStackTop', function (t) {
|
||||||
|
var th = new Thread('arbitraryString');
|
||||||
|
th.pushStack('arbitraryString');
|
||||||
|
th.pushStack('secondString');
|
||||||
|
t.strictEquals(th.atStackTop(), false);
|
||||||
|
th.popStack();
|
||||||
|
t.strictEquals(th.atStackTop(), true);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('reuseStackForNextBlock', function (t) {
|
||||||
|
var th = new Thread('arbitraryString');
|
||||||
|
th.pushStack('arbitraryString');
|
||||||
|
th.reuseStackForNextBlock('secondString');
|
||||||
|
t.strictEquals(th.popStack(), 'secondString');
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('peekStackFrame', function (t) {
|
||||||
|
var th = new Thread('arbitraryString');
|
||||||
|
th.pushStack('arbitraryString');
|
||||||
|
t.strictEquals(th.peekStackFrame().warpMode, false);
|
||||||
|
th.popStack();
|
||||||
|
t.strictEquals(th.peekStackFrame(), null);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('peekParentStackFrame', function (t) {
|
||||||
|
var th = new Thread('arbitraryString');
|
||||||
|
th.pushStack('arbitraryString');
|
||||||
|
th.peekStackFrame().warpMode = true;
|
||||||
|
t.strictEquals(th.peekParentStackFrame(), null);
|
||||||
|
th.pushStack('secondString');
|
||||||
|
t.strictEquals(th.peekParentStackFrame().warpMode, true);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('pushReportedValue', function (t) {
|
||||||
|
var th = new Thread('arbitraryString');
|
||||||
|
th.pushStack('arbitraryString');
|
||||||
|
th.pushStack('secondString');
|
||||||
|
th.pushReportedValue('value');
|
||||||
|
t.strictEquals(th.peekParentStackFrame().reported.null, 'value');
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('peekStack', function (t) {
|
||||||
|
var th = new Thread('arbitraryString');
|
||||||
|
th.pushStack('arbitraryString');
|
||||||
|
t.strictEquals(th.peekStack(), 'arbitraryString');
|
||||||
|
th.popStack();
|
||||||
|
t.strictEquals(th.peekStack(), null);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('PushGetParam', function (t) {
|
||||||
|
var th = new Thread('arbitraryString');
|
||||||
|
th.pushStack('arbitraryString');
|
||||||
|
th.pushParam('testParam', 'testValue');
|
||||||
|
t.strictEquals(th.peekStackFrame().params.testParam, 'testValue');
|
||||||
|
t.strictEquals(th.getParam('testParam'), 'testValue');
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('goToNextBlock', function (t) {
|
||||||
|
var th = new Thread('arbitraryString');
|
||||||
|
var s = new Sprite();
|
||||||
|
var rt = new RenderedTarget(s, null);
|
||||||
|
var block1 = {fields: Object,
|
||||||
|
id: 'arbitraryString',
|
||||||
|
inputs: Object,
|
||||||
|
STEPS: Object,
|
||||||
|
block: 'fakeBlock',
|
||||||
|
name: 'STEPS',
|
||||||
|
next: 'secondString',
|
||||||
|
opcode: 'motion_movesteps',
|
||||||
|
parent: null,
|
||||||
|
shadow: false,
|
||||||
|
topLevel: true,
|
||||||
|
x: 0,
|
||||||
|
y: 0
|
||||||
|
};
|
||||||
|
var block2 = {fields: Object,
|
||||||
|
id: 'secondString',
|
||||||
|
inputs: Object,
|
||||||
|
STEPS: Object,
|
||||||
|
block: 'fakeBlock',
|
||||||
|
name: 'STEPS',
|
||||||
|
next: null,
|
||||||
|
opcode: 'procedures_callnoreturn',
|
||||||
|
mutation: {proccode: 'fakeCode'},
|
||||||
|
parent: null,
|
||||||
|
shadow: false,
|
||||||
|
topLevel: true,
|
||||||
|
x: 0,
|
||||||
|
y: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
rt.blocks.createBlock(block1);
|
||||||
|
rt.blocks.createBlock(block2);
|
||||||
|
rt.blocks.createBlock(block2);
|
||||||
|
th.target = rt;
|
||||||
|
|
||||||
|
t.strictEquals(th.peekStack(), null);
|
||||||
|
th.pushStack('secondString');
|
||||||
|
t.strictEquals(th.peekStack(), 'secondString');
|
||||||
|
th.goToNextBlock();
|
||||||
|
t.strictEquals(th.peekStack(), null);
|
||||||
|
th.pushStack('secondString');
|
||||||
|
th.pushStack('arbitraryString');
|
||||||
|
t.strictEquals(th.peekStack(), 'arbitraryString');
|
||||||
|
th.goToNextBlock();
|
||||||
|
t.strictEquals(th.peekStack(), 'secondString');
|
||||||
|
th.goToNextBlock();
|
||||||
|
t.strictEquals(th.peekStack(), null);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('stopThisScript', function (t) {
|
||||||
|
var th = new Thread('arbitraryString');
|
||||||
|
var s = new Sprite();
|
||||||
|
var rt = new RenderedTarget(s, null);
|
||||||
|
var block1 = {fields: Object,
|
||||||
|
id: 'arbitraryString',
|
||||||
|
inputs: Object,
|
||||||
|
STEPS: Object,
|
||||||
|
block: 'fakeBlock',
|
||||||
|
name: 'STEPS',
|
||||||
|
next: null,
|
||||||
|
opcode: 'motion_movesteps',
|
||||||
|
parent: null,
|
||||||
|
shadow: false,
|
||||||
|
topLevel: true,
|
||||||
|
x: 0,
|
||||||
|
y: 0
|
||||||
|
};
|
||||||
|
var block2 = {fields: Object,
|
||||||
|
id: 'secondString',
|
||||||
|
inputs: Object,
|
||||||
|
STEPS: Object,
|
||||||
|
block: 'fakeBlock',
|
||||||
|
name: 'STEPS',
|
||||||
|
next: null,
|
||||||
|
opcode: 'procedures_callnoreturn',
|
||||||
|
mutation: {proccode: 'fakeCode'},
|
||||||
|
parent: null,
|
||||||
|
shadow: false,
|
||||||
|
topLevel: true,
|
||||||
|
x: 0,
|
||||||
|
y: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
rt.blocks.createBlock(block1);
|
||||||
|
rt.blocks.createBlock(block2);
|
||||||
|
th.target = rt;
|
||||||
|
|
||||||
|
th.stopThisScript();
|
||||||
|
t.strictEquals(th.peekStack(), null);
|
||||||
|
th.pushStack('arbitraryString');
|
||||||
|
t.strictEquals(th.peekStack(), 'arbitraryString');
|
||||||
|
th.stopThisScript();
|
||||||
|
t.strictEquals(th.peekStack(), null);
|
||||||
|
th.pushStack('arbitraryString');
|
||||||
|
th.pushStack('secondString');
|
||||||
|
th.stopThisScript();
|
||||||
|
t.strictEquals(th.peekStack(), 'secondString');
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('isRecursiveCall', function (t) {
|
||||||
|
var th = new Thread('arbitraryString');
|
||||||
|
var s = new Sprite();
|
||||||
|
var rt = new RenderedTarget(s, null);
|
||||||
|
var block1 = {fields: Object,
|
||||||
|
id: 'arbitraryString',
|
||||||
|
inputs: Object,
|
||||||
|
STEPS: Object,
|
||||||
|
block: 'fakeBlock',
|
||||||
|
name: 'STEPS',
|
||||||
|
next: null,
|
||||||
|
opcode: 'motion_movesteps',
|
||||||
|
parent: null,
|
||||||
|
shadow: false,
|
||||||
|
topLevel: true,
|
||||||
|
x: 0,
|
||||||
|
y: 0
|
||||||
|
};
|
||||||
|
var block2 = {fields: Object,
|
||||||
|
id: 'secondString',
|
||||||
|
inputs: Object,
|
||||||
|
STEPS: Object,
|
||||||
|
block: 'fakeBlock',
|
||||||
|
name: 'STEPS',
|
||||||
|
next: null,
|
||||||
|
opcode: 'procedures_callnoreturn',
|
||||||
|
mutation: {proccode: 'fakeCode'},
|
||||||
|
parent: null,
|
||||||
|
shadow: false,
|
||||||
|
topLevel: true,
|
||||||
|
x: 0,
|
||||||
|
y: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
rt.blocks.createBlock(block1);
|
||||||
|
rt.blocks.createBlock(block2);
|
||||||
|
th.target = rt;
|
||||||
|
|
||||||
|
t.strictEquals(th.isRecursiveCall('fakeCode'), false);
|
||||||
|
th.pushStack('secondString');
|
||||||
|
t.strictEquals(th.isRecursiveCall('fakeCode'), false);
|
||||||
|
th.pushStack('arbitraryString');
|
||||||
|
t.strictEquals(th.isRecursiveCall('fakeCode'), true);
|
||||||
|
th.pushStack('arbitraryString');
|
||||||
|
t.strictEquals(th.isRecursiveCall('fakeCode'), true);
|
||||||
|
th.popStack();
|
||||||
|
t.strictEquals(th.isRecursiveCall('fakeCode'), true);
|
||||||
|
th.popStack();
|
||||||
|
t.strictEquals(th.isRecursiveCall('fakeCode'), false);
|
||||||
|
th.popStack();
|
||||||
|
t.strictEquals(th.isRecursiveCall('fakeCode'), false);
|
||||||
|
|
||||||
t.end();
|
t.end();
|
||||||
});
|
});
|
||||||
|
|
22
test/unit/util_filter-toolbox.js
Normal file
22
test/unit/util_filter-toolbox.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
var toolboxes = require('../fixtures/toolboxes');
|
||||||
|
var test = require('tap').test;
|
||||||
|
var filterToolbox = require('../../src/util/filter-toolbox');
|
||||||
|
|
||||||
|
test('categories', function (t) {
|
||||||
|
var filteredToolbox = filterToolbox(toolboxes.categories, ['motion_movesteps']);
|
||||||
|
t.strictEqual(filteredToolbox.children.length, 3);
|
||||||
|
t.strictEqual(filteredToolbox.firstElementChild.children.length, 1);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('simple', function (t) {
|
||||||
|
var filteredToolbox = filterToolbox(toolboxes.simple, ['motion_movesteps']);
|
||||||
|
t.strictEqual(filteredToolbox.children.length, 1);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('empty', function (t) {
|
||||||
|
var filteredToolbox = filterToolbox(toolboxes.empty, ['motion_movesteps']);
|
||||||
|
t.strictEqual(filteredToolbox.children.length, 0);
|
||||||
|
t.end();
|
||||||
|
});
|
|
@ -5,7 +5,7 @@ var webpack = require('webpack');
|
||||||
|
|
||||||
var base = {
|
var base = {
|
||||||
devServer: {
|
devServer: {
|
||||||
contentBase: path.resolve(__dirname, 'playground'),
|
contentBase: false,
|
||||||
host: '0.0.0.0',
|
host: '0.0.0.0',
|
||||||
port: process.env.PORT || 8073
|
port: process.env.PORT || 8073
|
||||||
},
|
},
|
||||||
|
@ -108,6 +108,8 @@ module.exports = [
|
||||||
to: 'media'
|
to: 'media'
|
||||||
}, {
|
}, {
|
||||||
from: 'node_modules/highlightjs/styles/zenburn.css'
|
from: 'node_modules/highlightjs/styles/zenburn.css'
|
||||||
|
}, {
|
||||||
|
from: 'src/playground'
|
||||||
}])
|
}])
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue