@scratch/scratch-vm 11.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.jsdoc.json +20 -0
- package/.nvmrc +1 -0
- package/CHANGELOG.md +7004 -0
- package/LICENSE +12 -0
- package/README.md +120 -0
- package/TRADEMARK +1 -0
- package/commitlint.config.js +4 -0
- package/docs/extensions.md +527 -0
- package/package.json +103 -0
- package/release.config.js +13 -0
- package/src/blocks/scratch3_control.js +206 -0
- package/src/blocks/scratch3_core_example.js +69 -0
- package/src/blocks/scratch3_data.js +257 -0
- package/src/blocks/scratch3_event.js +137 -0
- package/src/blocks/scratch3_looks.js +612 -0
- package/src/blocks/scratch3_motion.js +288 -0
- package/src/blocks/scratch3_operators.js +154 -0
- package/src/blocks/scratch3_procedures.js +79 -0
- package/src/blocks/scratch3_sensing.js +336 -0
- package/src/blocks/scratch3_sound.js +350 -0
- package/src/dispatch/central-dispatch.js +143 -0
- package/src/dispatch/shared-dispatch.js +235 -0
- package/src/dispatch/worker-dispatch.js +110 -0
- package/src/engine/adapter.js +176 -0
- package/src/engine/block-utility.js +242 -0
- package/src/engine/blocks-execute-cache.js +19 -0
- package/src/engine/blocks-runtime-cache.js +78 -0
- package/src/engine/blocks.js +1299 -0
- package/src/engine/comment.js +56 -0
- package/src/engine/execute.js +580 -0
- package/src/engine/monitor-record.js +23 -0
- package/src/engine/mutation-adapter.js +48 -0
- package/src/engine/profiler.js +390 -0
- package/src/engine/runtime.js +2686 -0
- package/src/engine/scratch-blocks-constants.js +27 -0
- package/src/engine/sequencer.js +361 -0
- package/src/engine/stage-layering.js +29 -0
- package/src/engine/target.js +799 -0
- package/src/engine/thread.js +404 -0
- package/src/engine/variable.js +70 -0
- package/src/extension-support/argument-type.js +47 -0
- package/src/extension-support/block-type.js +50 -0
- package/src/extension-support/define-messages.js +18 -0
- package/src/extension-support/extension-manager.js +440 -0
- package/src/extension-support/extension-metadata.js +64 -0
- package/src/extension-support/extension-worker.js +59 -0
- package/src/extension-support/reporter-scope.js +18 -0
- package/src/extension-support/target-type.js +17 -0
- package/src/extensions/scratch3_boost/index.js +2113 -0
- package/src/extensions/scratch3_ev3/index.js +1355 -0
- package/src/extensions/scratch3_gdx_for/index.js +981 -0
- package/src/extensions/scratch3_gdx_for/scratch-link-device-adapter.js +44 -0
- package/src/extensions/scratch3_makeymakey/index.js +396 -0
- package/src/extensions/scratch3_microbit/index.js +984 -0
- package/src/extensions/scratch3_music/assets/drums/1-snare.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/10-wood-block.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/11-cowbell.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/12-triangle.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/13-bongo.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/14-conga.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/15-cabasa.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/16-guiro.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/17-vibraslap.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/18-cuica.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/2-bass-drum.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/3-side-stick.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/4-crash-cymbal.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/5-open-hi-hat.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/6-closed-hi-hat.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/7-tambourine.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/8-hand-clap.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/9-claves.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/108.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/24.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/72.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/84.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/96.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/10-clarinet/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/10-clarinet/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/11-saxophone/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/11-saxophone/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/11-saxophone/84.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/12-flute/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/12-flute/72.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/13-wooden-flute/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/13-wooden-flute/72.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/14-bassoon/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/14-bassoon/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/14-bassoon/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/15-choir/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/15-choir/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/15-choir/72.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/16-vibraphone/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/16-vibraphone/72.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/17-music-box/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/18-steel-drum/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/19-marimba/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/2-electric-piano/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/20-synth-lead/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/21-synth-pad/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/3-organ/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/4-guitar/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/5-electric-guitar/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/6-bass/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/6-bass/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/7-pizzicato/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/8-cello/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/8-cello/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/8-cello/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/9-trombone/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/9-trombone/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/9-trombone/60.mp3 +0 -0
- package/src/extensions/scratch3_music/index.js +1333 -0
- package/src/extensions/scratch3_music/manifest.js +63 -0
- package/src/extensions/scratch3_pen/index.js +770 -0
- package/src/extensions/scratch3_speech2text/index.js +700 -0
- package/src/extensions/scratch3_text2speech/index.js +766 -0
- package/src/extensions/scratch3_translate/index.js +286 -0
- package/src/extensions/scratch3_video_sensing/debug.js +13 -0
- package/src/extensions/scratch3_video_sensing/index.js +594 -0
- package/src/extensions/scratch3_video_sensing/library.js +384 -0
- package/src/extensions/scratch3_video_sensing/math.js +76 -0
- package/src/extensions/scratch3_video_sensing/view.js +509 -0
- package/src/extensions/scratch3_wedo2/index.js +1616 -0
- package/src/import/load-costume.js +416 -0
- package/src/import/load-sound.js +116 -0
- package/src/index.js +10 -0
- package/src/io/ble.js +256 -0
- package/src/io/bt.js +202 -0
- package/src/io/clock.js +39 -0
- package/src/io/cloud.js +169 -0
- package/src/io/keyboard.js +146 -0
- package/src/io/mouse.js +145 -0
- package/src/io/mouseWheel.js +28 -0
- package/src/io/userData.js +24 -0
- package/src/io/video.js +210 -0
- package/src/playground/benchmark.css +83 -0
- package/src/playground/benchmark.js +753 -0
- package/src/playground/index.html +111 -0
- package/src/playground/suite.css +78 -0
- package/src/playground/suite.html +25 -0
- package/src/playground/suite.js +544 -0
- package/src/playground/video-sensing.html +18 -0
- package/src/playground/video-sensing.js +133 -0
- package/src/serialization/deserialize-assets.js +178 -0
- package/src/serialization/sb2.js +1295 -0
- package/src/serialization/sb2_specmap.js +1818 -0
- package/src/serialization/sb3.js +1320 -0
- package/src/serialization/serialize-assets.js +60 -0
- package/src/sprites/rendered-target.js +1115 -0
- package/src/sprites/sprite.js +177 -0
- package/src/util/base64-util.js +48 -0
- package/src/util/cast.js +218 -0
- package/src/util/clone.js +17 -0
- package/src/util/color.js +204 -0
- package/src/util/fetch-with-timeout.js +53 -0
- package/src/util/get-monitor-id.js +33 -0
- package/src/util/jsonrpc.js +114 -0
- package/src/util/log.js +4 -0
- package/src/util/math-util.js +122 -0
- package/src/util/maybe-format-message.js +18 -0
- package/src/util/new-block-ids.js +33 -0
- package/src/util/rateLimiter.js +73 -0
- package/src/util/scratch-link-websocket.js +133 -0
- package/src/util/string-util.js +94 -0
- package/src/util/task-queue.js +203 -0
- package/src/util/timer.js +103 -0
- package/src/util/uid.js +29 -0
- package/src/util/variable-util.js +48 -0
- package/src/util/xml-escape.js +33 -0
- package/src/virtual-machine.js +1586 -0
- package/webpack.config.js +134 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
const RenderedTarget = require('./rendered-target');
|
|
2
|
+
const Blocks = require('../engine/blocks');
|
|
3
|
+
const {loadSoundFromAsset} = require('../import/load-sound');
|
|
4
|
+
const {loadCostumeFromAsset} = require('../import/load-costume');
|
|
5
|
+
const newBlockIds = require('../util/new-block-ids');
|
|
6
|
+
const StringUtil = require('../util/string-util');
|
|
7
|
+
const StageLayering = require('../engine/stage-layering');
|
|
8
|
+
|
|
9
|
+
class Sprite {
|
|
10
|
+
/**
|
|
11
|
+
* Sprite to be used on the Scratch stage.
|
|
12
|
+
* All clones of a sprite have shared blocks, shared costumes, shared variables,
|
|
13
|
+
* shared sounds, etc.
|
|
14
|
+
* @param {?Blocks} blocks Shared blocks object for all clones of sprite.
|
|
15
|
+
* @param {Runtime} runtime Reference to the runtime.
|
|
16
|
+
* @constructor
|
|
17
|
+
*/
|
|
18
|
+
constructor (blocks, runtime) {
|
|
19
|
+
this.runtime = runtime;
|
|
20
|
+
if (!blocks) {
|
|
21
|
+
// Shared set of blocks for all clones.
|
|
22
|
+
blocks = new Blocks(runtime);
|
|
23
|
+
}
|
|
24
|
+
this.blocks = blocks;
|
|
25
|
+
/**
|
|
26
|
+
* Human-readable name for this sprite (and all clones).
|
|
27
|
+
* @type {string}
|
|
28
|
+
*/
|
|
29
|
+
this.name = '';
|
|
30
|
+
/**
|
|
31
|
+
* List of costumes for this sprite.
|
|
32
|
+
* Each entry is an object, e.g.,
|
|
33
|
+
* {
|
|
34
|
+
* skinId: 1,
|
|
35
|
+
* name: "Costume Name",
|
|
36
|
+
* bitmapResolution: 2,
|
|
37
|
+
* rotationCenterX: 0,
|
|
38
|
+
* rotationCenterY: 0
|
|
39
|
+
* }
|
|
40
|
+
* @type {Array.<!Object>}
|
|
41
|
+
*/
|
|
42
|
+
this.costumes_ = [];
|
|
43
|
+
/**
|
|
44
|
+
* List of sounds for this sprite.
|
|
45
|
+
*/
|
|
46
|
+
this.sounds = [];
|
|
47
|
+
/**
|
|
48
|
+
* List of clones for this sprite, including the original.
|
|
49
|
+
* @type {Array.<!RenderedTarget>}
|
|
50
|
+
*/
|
|
51
|
+
this.clones = [];
|
|
52
|
+
|
|
53
|
+
this.soundBank = null;
|
|
54
|
+
if (this.runtime && this.runtime.audioEngine) {
|
|
55
|
+
this.soundBank = this.runtime.audioEngine.createBank();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Add an array of costumes, taking care to avoid duplicate names.
|
|
61
|
+
* @param {!Array<object>} costumes Array of objects representing costumes.
|
|
62
|
+
*/
|
|
63
|
+
set costumes (costumes) {
|
|
64
|
+
this.costumes_ = [];
|
|
65
|
+
for (const costume of costumes) {
|
|
66
|
+
this.addCostumeAt(costume, this.costumes_.length);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get full costume list
|
|
72
|
+
* @return {object[]} list of costumes. Note that mutating the returned list will not
|
|
73
|
+
* mutate the list on the sprite. The sprite list should be mutated by calling
|
|
74
|
+
* addCostumeAt, deleteCostumeAt, or setting costumes.
|
|
75
|
+
*/
|
|
76
|
+
get costumes () {
|
|
77
|
+
return this.costumes_;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Add a costume at the given index, taking care to avoid duplicate names.
|
|
82
|
+
* @param {!object} costumeObject Object representing the costume.
|
|
83
|
+
* @param {!int} index Index at which to add costume
|
|
84
|
+
*/
|
|
85
|
+
addCostumeAt (costumeObject, index) {
|
|
86
|
+
if (!costumeObject.name) {
|
|
87
|
+
costumeObject.name = '';
|
|
88
|
+
}
|
|
89
|
+
const usedNames = this.costumes_.map(costume => costume.name);
|
|
90
|
+
costumeObject.name = StringUtil.unusedName(costumeObject.name, usedNames);
|
|
91
|
+
this.costumes_.splice(index, 0, costumeObject);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Delete a costume by index.
|
|
96
|
+
* @param {number} index Costume index to be deleted
|
|
97
|
+
* @return {?object} The deleted costume
|
|
98
|
+
*/
|
|
99
|
+
deleteCostumeAt (index) {
|
|
100
|
+
return this.costumes.splice(index, 1)[0];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Create a clone of this sprite.
|
|
105
|
+
* @param {string=} optLayerGroup Optional layer group the clone's drawable should be added to
|
|
106
|
+
* Defaults to the sprite layer group
|
|
107
|
+
* @returns {!RenderedTarget} Newly created clone.
|
|
108
|
+
*/
|
|
109
|
+
createClone (optLayerGroup) {
|
|
110
|
+
const newClone = new RenderedTarget(this, this.runtime);
|
|
111
|
+
newClone.isOriginal = this.clones.length === 0;
|
|
112
|
+
this.clones.push(newClone);
|
|
113
|
+
newClone.initAudio();
|
|
114
|
+
if (newClone.isOriginal) {
|
|
115
|
+
// Default to the sprite layer group if optLayerGroup is not provided
|
|
116
|
+
const layerGroup = typeof optLayerGroup === 'string' ? optLayerGroup : StageLayering.SPRITE_LAYER;
|
|
117
|
+
newClone.initDrawable(layerGroup);
|
|
118
|
+
this.runtime.fireTargetWasCreated(newClone);
|
|
119
|
+
} else {
|
|
120
|
+
this.runtime.fireTargetWasCreated(newClone, this.clones[0]);
|
|
121
|
+
}
|
|
122
|
+
return newClone;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Disconnect a clone from this sprite. The clone is unmodified.
|
|
127
|
+
* In particular, the clone's dispose() method is not called.
|
|
128
|
+
* @param {!RenderedTarget} clone - the clone to be removed.
|
|
129
|
+
*/
|
|
130
|
+
removeClone (clone) {
|
|
131
|
+
this.runtime.fireTargetWasRemoved(clone);
|
|
132
|
+
const cloneIndex = this.clones.indexOf(clone);
|
|
133
|
+
if (cloneIndex >= 0) {
|
|
134
|
+
this.clones.splice(cloneIndex, 1);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
duplicate () {
|
|
139
|
+
const newSprite = new Sprite(null, this.runtime);
|
|
140
|
+
const blocksContainer = this.blocks._blocks;
|
|
141
|
+
const originalBlocks = Object.keys(blocksContainer).map(key => blocksContainer[key]);
|
|
142
|
+
const copiedBlocks = JSON.parse(JSON.stringify(originalBlocks));
|
|
143
|
+
newBlockIds(copiedBlocks);
|
|
144
|
+
copiedBlocks.forEach(block => {
|
|
145
|
+
newSprite.blocks.createBlock(block);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
const allNames = this.runtime.targets.map(t => t.sprite.name);
|
|
150
|
+
newSprite.name = StringUtil.unusedName(this.name, allNames);
|
|
151
|
+
|
|
152
|
+
const assetPromises = [];
|
|
153
|
+
|
|
154
|
+
newSprite.costumes = this.costumes_.map(costume => {
|
|
155
|
+
const newCostume = Object.assign({}, costume);
|
|
156
|
+
assetPromises.push(loadCostumeFromAsset(newCostume, this.runtime));
|
|
157
|
+
return newCostume;
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
newSprite.sounds = this.sounds.map(sound => {
|
|
161
|
+
const newSound = Object.assign({}, sound);
|
|
162
|
+
const soundAsset = sound.asset;
|
|
163
|
+
assetPromises.push(loadSoundFromAsset(newSound, soundAsset, this.runtime, newSprite.soundBank));
|
|
164
|
+
return newSound;
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
return Promise.all(assetPromises).then(() => newSprite);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
dispose () {
|
|
171
|
+
if (this.soundBank) {
|
|
172
|
+
this.soundBank.dispose();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
module.exports = Sprite;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const atob = require('atob');
|
|
2
|
+
const btoa = require('btoa');
|
|
3
|
+
|
|
4
|
+
class Base64Util {
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Convert a base64 encoded string to a Uint8Array.
|
|
8
|
+
* @param {string} base64 - a base64 encoded string.
|
|
9
|
+
* @return {Uint8Array} - a decoded Uint8Array.
|
|
10
|
+
*/
|
|
11
|
+
static base64ToUint8Array (base64) {
|
|
12
|
+
const binaryString = atob(base64);
|
|
13
|
+
const len = binaryString.length;
|
|
14
|
+
const array = new Uint8Array(len);
|
|
15
|
+
for (let i = 0; i < len; i++) {
|
|
16
|
+
array[i] = binaryString.charCodeAt(i);
|
|
17
|
+
}
|
|
18
|
+
return array;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Convert a Uint8Array to a base64 encoded string.
|
|
23
|
+
* @param {Uint8Array} array - the array to convert.
|
|
24
|
+
* @return {string} - the base64 encoded string.
|
|
25
|
+
*/
|
|
26
|
+
static uint8ArrayToBase64 (array) {
|
|
27
|
+
const base64 = btoa(String.fromCharCode.apply(null, array));
|
|
28
|
+
return base64;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Convert an array buffer to a base64 encoded string.
|
|
33
|
+
* @param {array} buffer - an array buffer to convert.
|
|
34
|
+
* @return {string} - the base64 encoded string.
|
|
35
|
+
*/
|
|
36
|
+
static arrayBufferToBase64 (buffer) {
|
|
37
|
+
let binary = '';
|
|
38
|
+
const bytes = new Uint8Array(buffer);
|
|
39
|
+
const len = bytes.byteLength;
|
|
40
|
+
for (let i = 0; i < len; i++) {
|
|
41
|
+
binary += String.fromCharCode(bytes[ i ]);
|
|
42
|
+
}
|
|
43
|
+
return btoa(binary);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = Base64Util;
|
package/src/util/cast.js
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
const Color = require('../util/color');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview
|
|
5
|
+
* Utilities for casting and comparing Scratch data-types.
|
|
6
|
+
* Scratch behaves slightly differently from JavaScript in many respects,
|
|
7
|
+
* and these differences should be encapsulated below.
|
|
8
|
+
* For example, in Scratch, add(1, join("hello", world")) -> 1.
|
|
9
|
+
* This is because "hello world" is cast to 0.
|
|
10
|
+
* In JavaScript, 1 + Number("hello" + "world") would give you NaN.
|
|
11
|
+
* Use when coercing a value before computation.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
class Cast {
|
|
15
|
+
/**
|
|
16
|
+
* Scratch cast to number.
|
|
17
|
+
* Treats NaN as 0.
|
|
18
|
+
* In Scratch 2.0, this is captured by `interp.numArg.`
|
|
19
|
+
* @param {*} value Value to cast to number.
|
|
20
|
+
* @return {number} The Scratch-casted number value.
|
|
21
|
+
*/
|
|
22
|
+
static toNumber (value) {
|
|
23
|
+
// If value is already a number we don't need to coerce it with
|
|
24
|
+
// Number().
|
|
25
|
+
if (typeof value === 'number') {
|
|
26
|
+
// Scratch treats NaN as 0, when needed as a number.
|
|
27
|
+
// E.g., 0 + NaN -> 0.
|
|
28
|
+
if (Number.isNaN(value)) {
|
|
29
|
+
return 0;
|
|
30
|
+
}
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
const n = Number(value);
|
|
34
|
+
if (Number.isNaN(n)) {
|
|
35
|
+
// Scratch treats NaN as 0, when needed as a number.
|
|
36
|
+
// E.g., 0 + NaN -> 0.
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
|
39
|
+
return n;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Scratch cast to boolean.
|
|
44
|
+
* In Scratch 2.0, this is captured by `interp.boolArg.`
|
|
45
|
+
* Treats some string values differently from JavaScript.
|
|
46
|
+
* @param {*} value Value to cast to boolean.
|
|
47
|
+
* @return {boolean} The Scratch-casted boolean value.
|
|
48
|
+
*/
|
|
49
|
+
static toBoolean (value) {
|
|
50
|
+
// Already a boolean?
|
|
51
|
+
if (typeof value === 'boolean') {
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
if (typeof value === 'string') {
|
|
55
|
+
// These specific strings are treated as false in Scratch.
|
|
56
|
+
if ((value === '') ||
|
|
57
|
+
(value === '0') ||
|
|
58
|
+
(value.toLowerCase() === 'false')) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
// All other strings treated as true.
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
// Coerce other values and numbers.
|
|
65
|
+
return Boolean(value);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Scratch cast to string.
|
|
70
|
+
* @param {*} value Value to cast to string.
|
|
71
|
+
* @return {string} The Scratch-casted string value.
|
|
72
|
+
*/
|
|
73
|
+
static toString (value) {
|
|
74
|
+
return String(value);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Cast any Scratch argument to an RGB color array to be used for the renderer.
|
|
79
|
+
* @param {*} value Value to convert to RGB color array.
|
|
80
|
+
* @return {Array.<number>} [r,g,b], values between 0-255.
|
|
81
|
+
*/
|
|
82
|
+
static toRgbColorList (value) {
|
|
83
|
+
const color = Cast.toRgbColorObject(value);
|
|
84
|
+
return [color.r, color.g, color.b];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Cast any Scratch argument to an RGB color object to be used for the renderer.
|
|
89
|
+
* @param {*} value Value to convert to RGB color object.
|
|
90
|
+
* @return {RGBOject} [r,g,b], values between 0-255.
|
|
91
|
+
*/
|
|
92
|
+
static toRgbColorObject (value) {
|
|
93
|
+
let color;
|
|
94
|
+
if (typeof value === 'string' && value.substring(0, 1) === '#') {
|
|
95
|
+
color = Color.hexToRgb(value);
|
|
96
|
+
|
|
97
|
+
// If the color wasn't *actually* a hex color, cast to black
|
|
98
|
+
if (!color) color = {r: 0, g: 0, b: 0, a: 255};
|
|
99
|
+
} else {
|
|
100
|
+
color = Color.decimalToRgb(Cast.toNumber(value));
|
|
101
|
+
}
|
|
102
|
+
return color;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Determine if a Scratch argument is a white space string (or null / empty).
|
|
107
|
+
* @param {*} val value to check.
|
|
108
|
+
* @return {boolean} True if the argument is all white spaces or null / empty.
|
|
109
|
+
*/
|
|
110
|
+
static isWhiteSpace (val) {
|
|
111
|
+
return val === null || (typeof val === 'string' && val.trim().length === 0);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Compare two values, using Scratch cast, case-insensitive string compare, etc.
|
|
116
|
+
* In Scratch 2.0, this is captured by `interp.compare.`
|
|
117
|
+
* @param {*} v1 First value to compare.
|
|
118
|
+
* @param {*} v2 Second value to compare.
|
|
119
|
+
* @returns {number} Negative number if v1 < v2; 0 if equal; positive otherwise.
|
|
120
|
+
*/
|
|
121
|
+
static compare (v1, v2) {
|
|
122
|
+
let n1 = Number(v1);
|
|
123
|
+
let n2 = Number(v2);
|
|
124
|
+
if (n1 === 0 && Cast.isWhiteSpace(v1)) {
|
|
125
|
+
n1 = NaN;
|
|
126
|
+
} else if (n2 === 0 && Cast.isWhiteSpace(v2)) {
|
|
127
|
+
n2 = NaN;
|
|
128
|
+
}
|
|
129
|
+
if (isNaN(n1) || isNaN(n2)) {
|
|
130
|
+
// At least one argument can't be converted to a number.
|
|
131
|
+
// Scratch compares strings as case insensitive.
|
|
132
|
+
const s1 = String(v1).toLowerCase();
|
|
133
|
+
const s2 = String(v2).toLowerCase();
|
|
134
|
+
if (s1 < s2) {
|
|
135
|
+
return -1;
|
|
136
|
+
} else if (s1 > s2) {
|
|
137
|
+
return 1;
|
|
138
|
+
}
|
|
139
|
+
return 0;
|
|
140
|
+
}
|
|
141
|
+
// Handle the special case of Infinity
|
|
142
|
+
if (
|
|
143
|
+
(n1 === Infinity && n2 === Infinity) ||
|
|
144
|
+
(n1 === -Infinity && n2 === -Infinity)
|
|
145
|
+
) {
|
|
146
|
+
return 0;
|
|
147
|
+
}
|
|
148
|
+
// Compare as numbers.
|
|
149
|
+
return n1 - n2;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Determine if a Scratch argument number represents a round integer.
|
|
154
|
+
* @param {*} val Value to check.
|
|
155
|
+
* @return {boolean} True if number looks like an integer.
|
|
156
|
+
*/
|
|
157
|
+
static isInt (val) {
|
|
158
|
+
// Values that are already numbers.
|
|
159
|
+
if (typeof val === 'number') {
|
|
160
|
+
if (isNaN(val)) { // NaN is considered an integer.
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
// True if it's "round" (e.g., 2.0 and 2).
|
|
164
|
+
return val === parseInt(val, 10);
|
|
165
|
+
} else if (typeof val === 'boolean') {
|
|
166
|
+
// `True` and `false` always represent integer after Scratch cast.
|
|
167
|
+
return true;
|
|
168
|
+
} else if (typeof val === 'string') {
|
|
169
|
+
// If it contains a decimal point, don't consider it an int.
|
|
170
|
+
return val.indexOf('.') < 0;
|
|
171
|
+
}
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
static get LIST_INVALID () {
|
|
176
|
+
return 'INVALID';
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
static get LIST_ALL () {
|
|
180
|
+
return 'ALL';
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Compute a 1-based index into a list, based on a Scratch argument.
|
|
185
|
+
* Two special cases may be returned:
|
|
186
|
+
* LIST_ALL: if the block is referring to all of the items in the list.
|
|
187
|
+
* LIST_INVALID: if the index was invalid in any way.
|
|
188
|
+
* @param {*} index Scratch arg, including 1-based numbers or special cases.
|
|
189
|
+
* @param {number} length Length of the list.
|
|
190
|
+
* @param {boolean} acceptAll Whether it should accept "all" or not.
|
|
191
|
+
* @return {(number|string)} 1-based index for list, LIST_ALL, or LIST_INVALID.
|
|
192
|
+
*/
|
|
193
|
+
static toListIndex (index, length, acceptAll) {
|
|
194
|
+
if (typeof index !== 'number') {
|
|
195
|
+
if (index === 'all') {
|
|
196
|
+
return acceptAll ? Cast.LIST_ALL : Cast.LIST_INVALID;
|
|
197
|
+
}
|
|
198
|
+
if (index === 'last') {
|
|
199
|
+
if (length > 0) {
|
|
200
|
+
return length;
|
|
201
|
+
}
|
|
202
|
+
return Cast.LIST_INVALID;
|
|
203
|
+
} else if (index === 'random' || index === 'any') {
|
|
204
|
+
if (length > 0) {
|
|
205
|
+
return 1 + Math.floor(Math.random() * length);
|
|
206
|
+
}
|
|
207
|
+
return Cast.LIST_INVALID;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
index = Math.floor(Cast.toNumber(index));
|
|
211
|
+
if (index < 1 || index > length) {
|
|
212
|
+
return Cast.LIST_INVALID;
|
|
213
|
+
}
|
|
214
|
+
return index;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
module.exports = Cast;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Methods for cloning JavaScript objects.
|
|
3
|
+
* @type {object}
|
|
4
|
+
*/
|
|
5
|
+
class Clone {
|
|
6
|
+
/**
|
|
7
|
+
* Deep-clone a "simple" object: one which can be fully expressed with JSON.
|
|
8
|
+
* Non-JSON values, such as functions, will be stripped from the clone.
|
|
9
|
+
* @param {object} original - the object to be cloned.
|
|
10
|
+
* @returns {object} a deep clone of the original object.
|
|
11
|
+
*/
|
|
12
|
+
static simple (original) {
|
|
13
|
+
return JSON.parse(JSON.stringify(original));
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = Clone;
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
class Color {
|
|
2
|
+
/**
|
|
3
|
+
* @typedef {object} RGBObject - An object representing a color in RGB format.
|
|
4
|
+
* @property {number} r - the red component, in the range [0, 255].
|
|
5
|
+
* @property {number} g - the green component, in the range [0, 255].
|
|
6
|
+
* @property {number} b - the blue component, in the range [0, 255].
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {object} HSVObject - An object representing a color in HSV format.
|
|
11
|
+
* @property {number} h - hue, in the range [0-359).
|
|
12
|
+
* @property {number} s - saturation, in the range [0,1].
|
|
13
|
+
* @property {number} v - value, in the range [0,1].
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** @type {RGBObject} */
|
|
17
|
+
static get RGB_BLACK () {
|
|
18
|
+
return {r: 0, g: 0, b: 0};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** @type {RGBObject} */
|
|
22
|
+
static get RGB_WHITE () {
|
|
23
|
+
return {r: 255, g: 255, b: 255};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Convert a Scratch decimal color to a hex string, #RRGGBB.
|
|
28
|
+
* @param {number} decimal RGB color as a decimal.
|
|
29
|
+
* @return {string} RGB color as #RRGGBB hex string.
|
|
30
|
+
*/
|
|
31
|
+
static decimalToHex (decimal) {
|
|
32
|
+
if (decimal < 0) {
|
|
33
|
+
decimal += 0xFFFFFF + 1;
|
|
34
|
+
}
|
|
35
|
+
let hex = Number(decimal).toString(16);
|
|
36
|
+
hex = `#${'000000'.substr(0, 6 - hex.length)}${hex}`;
|
|
37
|
+
return hex;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Convert a Scratch decimal color to an RGB color object.
|
|
42
|
+
* @param {number} decimal RGB color as decimal.
|
|
43
|
+
* @return {RGBObject} rgb - {r: red [0,255], g: green [0,255], b: blue [0,255]}.
|
|
44
|
+
*/
|
|
45
|
+
static decimalToRgb (decimal) {
|
|
46
|
+
const a = (decimal >> 24) & 0xFF;
|
|
47
|
+
const r = (decimal >> 16) & 0xFF;
|
|
48
|
+
const g = (decimal >> 8) & 0xFF;
|
|
49
|
+
const b = decimal & 0xFF;
|
|
50
|
+
return {r: r, g: g, b: b, a: a > 0 ? a : 255};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Convert a hex color (e.g., F00, #03F, #0033FF) to an RGB color object.
|
|
55
|
+
* CC-BY-SA Tim Down:
|
|
56
|
+
* https://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
|
|
57
|
+
* @param {!string} hex Hex representation of the color.
|
|
58
|
+
* @return {RGBObject} null on failure, or rgb: {r: red [0,255], g: green [0,255], b: blue [0,255]}.
|
|
59
|
+
*/
|
|
60
|
+
static hexToRgb (hex) {
|
|
61
|
+
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
|
|
62
|
+
hex = hex.replace(shorthandRegex, (m, r, g, b) => r + r + g + g + b + b);
|
|
63
|
+
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
|
64
|
+
return result ? {
|
|
65
|
+
r: parseInt(result[1], 16),
|
|
66
|
+
g: parseInt(result[2], 16),
|
|
67
|
+
b: parseInt(result[3], 16)
|
|
68
|
+
} : null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Convert an RGB color object to a hex color.
|
|
73
|
+
* @param {RGBObject} rgb - {r: red [0,255], g: green [0,255], b: blue [0,255]}.
|
|
74
|
+
* @return {!string} Hex representation of the color.
|
|
75
|
+
*/
|
|
76
|
+
static rgbToHex (rgb) {
|
|
77
|
+
return Color.decimalToHex(Color.rgbToDecimal(rgb));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Convert an RGB color object to a Scratch decimal color.
|
|
82
|
+
* @param {RGBObject} rgb - {r: red [0,255], g: green [0,255], b: blue [0,255]}.
|
|
83
|
+
* @return {!number} Number representing the color.
|
|
84
|
+
*/
|
|
85
|
+
static rgbToDecimal (rgb) {
|
|
86
|
+
return (rgb.r << 16) + (rgb.g << 8) + rgb.b;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Convert a hex color (e.g., F00, #03F, #0033FF) to a decimal color number.
|
|
91
|
+
* @param {!string} hex Hex representation of the color.
|
|
92
|
+
* @return {!number} Number representing the color.
|
|
93
|
+
*/
|
|
94
|
+
static hexToDecimal (hex) {
|
|
95
|
+
return Color.rgbToDecimal(Color.hexToRgb(hex));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Convert an HSV color to RGB format.
|
|
100
|
+
* @param {HSVObject} hsv - {h: hue [0,360), s: saturation [0,1], v: value [0,1]}
|
|
101
|
+
* @return {RGBObject} rgb - {r: red [0,255], g: green [0,255], b: blue [0,255]}.
|
|
102
|
+
*/
|
|
103
|
+
static hsvToRgb (hsv) {
|
|
104
|
+
let h = hsv.h % 360;
|
|
105
|
+
if (h < 0) h += 360;
|
|
106
|
+
const s = Math.max(0, Math.min(hsv.s, 1));
|
|
107
|
+
const v = Math.max(0, Math.min(hsv.v, 1));
|
|
108
|
+
|
|
109
|
+
const i = Math.floor(h / 60);
|
|
110
|
+
const f = (h / 60) - i;
|
|
111
|
+
const p = v * (1 - s);
|
|
112
|
+
const q = v * (1 - (s * f));
|
|
113
|
+
const t = v * (1 - (s * (1 - f)));
|
|
114
|
+
|
|
115
|
+
let r;
|
|
116
|
+
let g;
|
|
117
|
+
let b;
|
|
118
|
+
|
|
119
|
+
switch (i) {
|
|
120
|
+
default:
|
|
121
|
+
case 0:
|
|
122
|
+
r = v;
|
|
123
|
+
g = t;
|
|
124
|
+
b = p;
|
|
125
|
+
break;
|
|
126
|
+
case 1:
|
|
127
|
+
r = q;
|
|
128
|
+
g = v;
|
|
129
|
+
b = p;
|
|
130
|
+
break;
|
|
131
|
+
case 2:
|
|
132
|
+
r = p;
|
|
133
|
+
g = v;
|
|
134
|
+
b = t;
|
|
135
|
+
break;
|
|
136
|
+
case 3:
|
|
137
|
+
r = p;
|
|
138
|
+
g = q;
|
|
139
|
+
b = v;
|
|
140
|
+
break;
|
|
141
|
+
case 4:
|
|
142
|
+
r = t;
|
|
143
|
+
g = p;
|
|
144
|
+
b = v;
|
|
145
|
+
break;
|
|
146
|
+
case 5:
|
|
147
|
+
r = v;
|
|
148
|
+
g = p;
|
|
149
|
+
b = q;
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
r: Math.floor(r * 255),
|
|
155
|
+
g: Math.floor(g * 255),
|
|
156
|
+
b: Math.floor(b * 255)
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Convert an RGB color to HSV format.
|
|
162
|
+
* @param {RGBObject} rgb - {r: red [0,255], g: green [0,255], b: blue [0,255]}.
|
|
163
|
+
* @return {HSVObject} hsv - {h: hue [0,360), s: saturation [0,1], v: value [0,1]}
|
|
164
|
+
*/
|
|
165
|
+
static rgbToHsv (rgb) {
|
|
166
|
+
const r = rgb.r / 255;
|
|
167
|
+
const g = rgb.g / 255;
|
|
168
|
+
const b = rgb.b / 255;
|
|
169
|
+
const x = Math.min(Math.min(r, g), b);
|
|
170
|
+
const v = Math.max(Math.max(r, g), b);
|
|
171
|
+
|
|
172
|
+
// For grays, hue will be arbitrarily reported as zero. Otherwise, calculate
|
|
173
|
+
let h = 0;
|
|
174
|
+
let s = 0;
|
|
175
|
+
if (x !== v) {
|
|
176
|
+
const f = (r === x) ? g - b : ((g === x) ? b - r : r - g);
|
|
177
|
+
const i = (r === x) ? 3 : ((g === x) ? 5 : 1);
|
|
178
|
+
h = ((i - (f / (v - x))) * 60) % 360;
|
|
179
|
+
s = (v - x) / v;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return {h: h, s: s, v: v};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Linear interpolation between rgb0 and rgb1.
|
|
187
|
+
* @param {RGBObject} rgb0 - the color corresponding to fraction1 <= 0.
|
|
188
|
+
* @param {RGBObject} rgb1 - the color corresponding to fraction1 >= 1.
|
|
189
|
+
* @param {number} fraction1 - the interpolation parameter. If this is 0.5, for example, mix the two colors equally.
|
|
190
|
+
* @return {RGBObject} the interpolated color.
|
|
191
|
+
*/
|
|
192
|
+
static mixRgb (rgb0, rgb1, fraction1) {
|
|
193
|
+
if (fraction1 <= 0) return rgb0;
|
|
194
|
+
if (fraction1 >= 1) return rgb1;
|
|
195
|
+
const fraction0 = 1 - fraction1;
|
|
196
|
+
return {
|
|
197
|
+
r: (fraction0 * rgb0.r) + (fraction1 * rgb1.r),
|
|
198
|
+
g: (fraction0 * rgb0.g) + (fraction1 * rgb1.g),
|
|
199
|
+
b: (fraction0 * rgb0.b) + (fraction1 * rgb1.b)
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
module.exports = Color;
|