diff --git a/README.md b/README.md index cd14f003f..2888fc6c0 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,7 @@ [](https://travis-ci.org/LLK/scratch-vm) [](https://coveralls.io/github/LLK/scratch-vm?branch=develop) -[](https://david-dm.org/LLK/scratch-vm) -[](https://david-dm.org/LLK/scratch-vm#info=devDependencies) +[](https://greenkeeper.io/) ## Installation This requires you to have Git and Node.js installed. diff --git a/package.json b/package.json index 663e19e31..6ab89c9f3 100644 --- a/package.json +++ b/package.json @@ -25,27 +25,26 @@ }, "devDependencies": { "adm-zip": "0.4.7", - "babel-eslint": "7.0.0", - "copy-webpack-plugin": "3.0.1", - "eslint": "3.8.1", - "eslint-config-scratch": "^2.0.0", + "babel-eslint": "7.1.1", + "copy-webpack-plugin": "4.0.1", + "eslint": "3.15.0", + "eslint-config-scratch": "^3.1.0", "expose-loader": "0.7.1", - "gh-pages": "0.11.0", - "highlightjs": "8.7.0", - "htmlparser2": "3.9.0", + "gh-pages": "0.12.0", + "highlightjs": "9.8.0", + "htmlparser2": "3.9.2", "json": "9.0.4", - "json-loader": "0.5.4", "lodash.defaultsdeep": "4.6.0", - "minilog": "3.0.1", + "minilog": "3.1.0", "promise": "7.1.1", "scratch-audio": "latest", "scratch-blocks": "latest", "scratch-render": "latest", "script-loader": "0.7.0", - "stats.js": "0.16.0", - "tap": "5.7.1", + "stats.js": "0.17.0", + "tap": "10.0.2", "travis-after-all": "1.4.4", - "webpack": "1.13.0", - "webpack-dev-server": "1.14.1" + "webpack": "2.2.1", + "webpack-dev-server": "1.16.3" } } diff --git a/src/blocks/scratch3_control.js b/src/blocks/scratch3_control.js index cbfe61a24..28737b361 100644 --- a/src/blocks/scratch3_control.js +++ b/src/blocks/scratch3_control.js @@ -11,7 +11,7 @@ var Scratch3ControlBlocks = function (runtime) { /** * Retrieve the block primitives implemented by this package. - * @return {Object.<string, Function>} Mapping of opcode to Function. + * @return {object.<string, Function>} Mapping of opcode to Function. */ Scratch3ControlBlocks.prototype.getPrimitives = function () { return { diff --git a/src/blocks/scratch3_data.js b/src/blocks/scratch3_data.js index 55a6b8bb7..b17835638 100644 --- a/src/blocks/scratch3_data.js +++ b/src/blocks/scratch3_data.js @@ -10,7 +10,7 @@ var Scratch3DataBlocks = function (runtime) { /** * Retrieve the block primitives implemented by this package. - * @return {Object.<string, Function>} Mapping of opcode to Function. + * @return {object.<string, Function>} Mapping of opcode to Function. */ Scratch3DataBlocks.prototype.getPrimitives = function () { return { diff --git a/src/blocks/scratch3_event.js b/src/blocks/scratch3_event.js index 8bfef42c2..b4c40116e 100644 --- a/src/blocks/scratch3_event.js +++ b/src/blocks/scratch3_event.js @@ -10,7 +10,7 @@ var Scratch3EventBlocks = function (runtime) { /** * Retrieve the block primitives implemented by this package. - * @return {Object.<string, Function>} Mapping of opcode to Function. + * @return {object.<string, Function>} Mapping of opcode to Function. */ Scratch3EventBlocks.prototype.getPrimitives = function () { return { diff --git a/src/blocks/scratch3_looks.js b/src/blocks/scratch3_looks.js index 60d95fd50..c7a9fcadf 100644 --- a/src/blocks/scratch3_looks.js +++ b/src/blocks/scratch3_looks.js @@ -10,7 +10,7 @@ var Scratch3LooksBlocks = function (runtime) { /** * Retrieve the block primitives implemented by this package. - * @return {Object.<string, Function>} Mapping of opcode to Function. + * @return {object.<string, Function>} Mapping of opcode to Function. */ Scratch3LooksBlocks.prototype.getPrimitives = function () { return { diff --git a/src/blocks/scratch3_motion.js b/src/blocks/scratch3_motion.js index be96e9cd8..857644dd5 100644 --- a/src/blocks/scratch3_motion.js +++ b/src/blocks/scratch3_motion.js @@ -12,7 +12,7 @@ var Scratch3MotionBlocks = function (runtime) { /** * Retrieve the block primitives implemented by this package. - * @return {Object.<string, Function>} Mapping of opcode to Function. + * @return {object.<string, Function>} Mapping of opcode to Function. */ Scratch3MotionBlocks.prototype.getPrimitives = function () { return { diff --git a/src/blocks/scratch3_operators.js b/src/blocks/scratch3_operators.js index eb2a98135..fce7fb3a7 100644 --- a/src/blocks/scratch3_operators.js +++ b/src/blocks/scratch3_operators.js @@ -1,4 +1,5 @@ var Cast = require('../util/cast.js'); +var MathUtil = require('../util/math-util.js'); var Scratch3OperatorsBlocks = function (runtime) { /** @@ -10,7 +11,7 @@ var Scratch3OperatorsBlocks = function (runtime) { /** * Retrieve the block primitives implemented by this package. - * @return {Object.<string, Function>} Mapping of opcode to Function. + * @return {object.<string, Function>} Mapping of opcode to Function. */ Scratch3OperatorsBlocks.prototype.getPrimitives = function () { return { @@ -126,9 +127,9 @@ Scratch3OperatorsBlocks.prototype.mathop = function (args) { case 'floor': return Math.floor(n); case 'ceiling': return Math.ceil(n); case 'sqrt': return Math.sqrt(n); - case 'sin': return Math.sin((Math.PI * n) / 180); - case 'cos': return Math.cos((Math.PI * n) / 180); - case 'tan': return Math.tan((Math.PI * n) / 180); + case 'sin': return parseFloat(Math.sin((Math.PI * n) / 180).toFixed(10)); + case 'cos': return parseFloat(Math.cos((Math.PI * n) / 180).toFixed(10)); + case 'tan': return MathUtil.tan(n); case 'asin': return (Math.asin(n) * 180) / Math.PI; case 'acos': return (Math.acos(n) * 180) / Math.PI; case 'atan': return (Math.atan(n) * 180) / Math.PI; diff --git a/src/blocks/scratch3_pen.js b/src/blocks/scratch3_pen.js index 5d652531b..c8f98341e 100644 --- a/src/blocks/scratch3_pen.js +++ b/src/blocks/scratch3_pen.js @@ -163,7 +163,7 @@ Scratch3PenBlocks.prototype._wrapHueOrShade = function (value) { /** * Retrieve the block primitives implemented by this package. - * @return {Object.<string, Function>} Mapping of opcode to Function. + * @return {object.<string, Function>} Mapping of opcode to Function. */ Scratch3PenBlocks.prototype.getPrimitives = function () { return { @@ -258,6 +258,9 @@ Scratch3PenBlocks.prototype.setPenColorToColor = function (args, util) { penState.penAttributes.color4f[0] = rgb.r / 255.0; penState.penAttributes.color4f[1] = rgb.g / 255.0; penState.penAttributes.color4f[2] = rgb.b / 255.0; + if (rgb.hasOwnProperty('a')) { // Will there always be an 'a'? + penState.penAttributes.color4f[3] = rgb.a / 255.0; + } }; /** diff --git a/src/blocks/scratch3_procedures.js b/src/blocks/scratch3_procedures.js index 62c7b8dbe..471205f48 100644 --- a/src/blocks/scratch3_procedures.js +++ b/src/blocks/scratch3_procedures.js @@ -8,7 +8,7 @@ var Scratch3ProcedureBlocks = function (runtime) { /** * Retrieve the block primitives implemented by this package. - * @return {Object.<string, Function>} Mapping of opcode to Function. + * @return {object.<string, Function>} Mapping of opcode to Function. */ Scratch3ProcedureBlocks.prototype.getPrimitives = function () { return { diff --git a/src/blocks/scratch3_sensing.js b/src/blocks/scratch3_sensing.js index c940c5068..1601fc650 100644 --- a/src/blocks/scratch3_sensing.js +++ b/src/blocks/scratch3_sensing.js @@ -10,7 +10,7 @@ var Scratch3SensingBlocks = function (runtime) { /** * Retrieve the block primitives implemented by this package. - * @return {Object.<string, Function>} Mapping of opcode to Function. + * @return {object.<string, Function>} Mapping of opcode to Function. */ Scratch3SensingBlocks.prototype.getPrimitives = function () { return { diff --git a/src/blocks/scratch3_sound.js b/src/blocks/scratch3_sound.js index f1a16f179..20d137580 100644 --- a/src/blocks/scratch3_sound.js +++ b/src/blocks/scratch3_sound.js @@ -1,5 +1,6 @@ var MathUtil = require('../util/math-util'); var Cast = require('../util/cast'); +var Clone = require('../util/clone'); var Scratch3SoundBlocks = function (runtime) { /** @@ -9,9 +10,46 @@ var Scratch3SoundBlocks = function (runtime) { this.runtime = runtime; }; +/** + * The key to load & store a target's sound-related state. + * @type {string} + */ +Scratch3SoundBlocks.STATE_KEY = 'Scratch.sound'; + +/** + * The default sound-related state, to be used when a target has no existing sound state. + * @type {SoundState} + */ +Scratch3SoundBlocks.DEFAULT_SOUND_STATE = { + volume: 100, + currentInstrument: 0, + effects: { + pitch: 0, + pan: 0, + echo: 0, + reverb: 0, + fuzz: 0, + robot: 0 + } +}; + +/** + * @param {Target} target - collect sound state for this target. + * @returns {SoundState} the mutable sound state associated with that target. This will be created if necessary. + * @private + */ +Scratch3SoundBlocks.prototype._getSoundState = function (target) { + var soundState = target.getCustomState(Scratch3SoundBlocks.STATE_KEY); + if (!soundState) { + soundState = Clone.simple(Scratch3SoundBlocks.DEFAULT_SOUND_STATE); + target.setCustomState(Scratch3SoundBlocks.STATE_KEY, soundState); + } + return soundState; +}; + /** * Retrieve the block primitives implemented by this package. - * @return {Object.<string, Function>} Mapping of opcode to Function. + * @return {object.<string, Function>} Mapping of opcode to Function. */ Scratch3SoundBlocks.prototype.getPrimitives = function () { return { @@ -30,6 +68,7 @@ Scratch3SoundBlocks.prototype.getPrimitives = function () { sound_effects_menu: this.effectsMenu, sound_setvolumeto: this.setVolume, sound_changevolumeby: this.changeVolume, + sound_volume: this.getVolume, sound_settempotobpm: this.setTempo, sound_changetempoby: this.changeTempo, sound_tempo: this.getTempo @@ -38,94 +77,166 @@ Scratch3SoundBlocks.prototype.getPrimitives = function () { Scratch3SoundBlocks.prototype.playSound = function (args, util) { var index = this._getSoundIndex(args.SOUND_MENU, util); - util.target.audioPlayer.playSound(index); + if (index >= 0) { + var md5 = util.target.sprite.sounds[index].md5; + if (util.target.audioPlayer === null) return; + util.target.audioPlayer.playSound(md5); + } }; Scratch3SoundBlocks.prototype.playSoundAndWait = function (args, util) { var index = this._getSoundIndex(args.SOUND_MENU, util); - return util.target.audioPlayer.playSound(index); + if (index >= 0) { + var md5 = util.target.sprite.sounds[index].md5; + if (util.target.audioPlayer === null) return; + return util.target.audioPlayer.playSound(md5); + } }; Scratch3SoundBlocks.prototype._getSoundIndex = function (soundName, util) { - if (util.target.sprite.sounds.length === 0) { - return 0; + // if the sprite has no sounds, return -1 + var len = util.target.sprite.sounds.length; + if (len === 0) { + return -1; } + var index; - if (Number(soundName)) { - soundName = Number(soundName); - var len = util.target.sprite.sounds.length; - index = MathUtil.wrapClamp(soundName, 1, len) - 1; - } else { - index = util.target.getSoundIndexByName(soundName); - if (index === -1) { - index = 0; - } + // try to convert to a number and use that as an index + var num = parseInt(soundName, 10); + if (!isNaN(num)) { + index = MathUtil.wrapClamp(num, 0, len - 1); + return index; } + + // return the index for the sound of that name + index = this.getSoundIndexByName(soundName, util); return index; }; +Scratch3SoundBlocks.prototype.getSoundIndexByName = function (soundName, util) { + var sounds = util.target.sprite.sounds; + for (var i = 0; i < sounds.length; i++) { + if (sounds[i].name === soundName) { + return i; + } + } + // if there is no sound by that name, return -1 + return -1; +}; + Scratch3SoundBlocks.prototype.stopAllSounds = function (args, util) { + if (util.target.audioPlayer === null) return; util.target.audioPlayer.stopAllSounds(); }; Scratch3SoundBlocks.prototype.playNoteForBeats = function (args, util) { - return util.target.audioPlayer.playNoteForBeats(args.NOTE, args.BEATS); + var note = Cast.toNumber(args.NOTE); + var beats = Cast.toNumber(args.BEATS); + var soundState = this._getSoundState(util.target); + var inst = soundState.currentInstrument; + if (typeof this.runtime.audioEngine === 'undefined') return; + return this.runtime.audioEngine.playNoteForBeatsWithInst(note, beats, inst); }; Scratch3SoundBlocks.prototype.playDrumForBeats = function (args, util) { - return util.target.audioPlayer.playDrumForBeats(args.DRUM, args.BEATS); + var drum = Cast.toNumber(args.DRUM); + drum -= 1; // drums are one-indexed + if (typeof this.runtime.audioEngine === 'undefined') return; + drum = MathUtil.wrapClamp(drum, 0, this.runtime.audioEngine.numDrums); + var beats = Cast.toNumber(args.BEATS); + if (util.target.audioPlayer === null) return; + return util.target.audioPlayer.playDrumForBeats(drum, beats); }; -Scratch3SoundBlocks.prototype.restForBeats = function (args, util) { - return util.target.audioPlayer.waitForBeats(args.BEATS); +Scratch3SoundBlocks.prototype.restForBeats = function (args) { + var beats = Cast.toNumber(args.BEATS); + if (typeof this.runtime.audioEngine === 'undefined') return; + return this.runtime.audioEngine.waitForBeats(beats); }; Scratch3SoundBlocks.prototype.setInstrument = function (args, util) { + var soundState = this._getSoundState(util.target); var instNum = Cast.toNumber(args.INSTRUMENT); - return util.target.audioPlayer.setInstrument(instNum); + instNum -= 1; // instruments are one-indexed + if (typeof this.runtime.audioEngine === 'undefined') return; + instNum = MathUtil.wrapClamp(instNum, 0, this.runtime.audioEngine.numInstruments); + soundState.currentInstrument = instNum; + return this.runtime.audioEngine.instrumentPlayer.loadInstrument(soundState.currentInstrument); }; Scratch3SoundBlocks.prototype.setEffect = function (args, util) { + var effect = Cast.toString(args.EFFECT).toLowerCase(); var value = Cast.toNumber(args.VALUE); - util.target.audioPlayer.setEffect(args.EFFECT, value); + + var soundState = this._getSoundState(util.target); + if (!soundState.effects.hasOwnProperty(effect)) return; + + soundState.effects[effect] = value; + if (util.target.audioPlayer === null) return; + util.target.audioPlayer.setEffect(effect, soundState.effects[effect]); }; Scratch3SoundBlocks.prototype.changeEffect = function (args, util) { + var effect = Cast.toString(args.EFFECT).toLowerCase(); var value = Cast.toNumber(args.VALUE); - util.target.audioPlayer.changeEffect(args.EFFECT, value); + + var soundState = this._getSoundState(util.target); + if (!soundState.effects.hasOwnProperty(effect)) return; + + soundState.effects[effect] += value; + if (util.target.audioPlayer === null) return; + util.target.audioPlayer.setEffect(effect, soundState.effects[effect]); }; Scratch3SoundBlocks.prototype.clearEffects = function (args, util) { + var soundState = this._getSoundState(util.target); + for (var effect in soundState.effects) { + soundState.effects[effect] = 0; + } + if (util.target.audioPlayer === null) return; util.target.audioPlayer.clearEffects(); }; Scratch3SoundBlocks.prototype.setVolume = function (args, util) { - var value = Cast.toNumber(args.VOLUME); - util.target.audioPlayer.setVolume(value); + var volume = Cast.toNumber(args.VOLUME); + this._updateVolume(volume, util); }; Scratch3SoundBlocks.prototype.changeVolume = function (args, util) { - var value = Cast.toNumber(args.VOLUME); - util.target.audioPlayer.changeVolume(value); + var soundState = this._getSoundState(util.target); + var volume = Cast.toNumber(args.VOLUME) + soundState.volume; + this._updateVolume(volume, util); +}; + +Scratch3SoundBlocks.prototype._updateVolume = function (volume, util) { + var soundState = this._getSoundState(util.target); + volume = MathUtil.clamp(volume, 0, 100); + soundState.volume = volume; + if (util.target.audioPlayer === null) return; + util.target.audioPlayer.setVolume(soundState.volume); }; Scratch3SoundBlocks.prototype.getVolume = function (args, util) { - return util.target.audioPlayer.currentVolume; + var soundState = this._getSoundState(util.target); + return soundState.volume; }; -Scratch3SoundBlocks.prototype.setTempo = function (args, util) { +Scratch3SoundBlocks.prototype.setTempo = function (args) { var value = Cast.toNumber(args.TEMPO); - util.target.audioPlayer.setTempo(value); + if (typeof this.runtime.audioEngine === 'undefined') return; + this.runtime.audioEngine.setTempo(value); }; -Scratch3SoundBlocks.prototype.changeTempo = function (args, util) { +Scratch3SoundBlocks.prototype.changeTempo = function (args) { var value = Cast.toNumber(args.TEMPO); - util.target.audioPlayer.changeTempo(value); + if (typeof this.runtime.audioEngine === 'undefined') return; + this.runtime.audioEngine.changeTempo(value); }; -Scratch3SoundBlocks.prototype.getTempo = function (args, util) { - return util.target.audioPlayer.currentTempo; +Scratch3SoundBlocks.prototype.getTempo = function () { + if (typeof this.runtime.audioEngine === 'undefined') return; + return this.runtime.audioEngine.currentTempo; }; Scratch3SoundBlocks.prototype.soundsMenu = function (args) { diff --git a/src/engine/adapter.js b/src/engine/adapter.js index e68cd3ea5..56275e1cb 100644 --- a/src/engine/adapter.js +++ b/src/engine/adapter.js @@ -6,7 +6,7 @@ var html = require('htmlparser2'); * to a usable form for the Scratch runtime. * This structure is based on Blockly xml.js:`domToWorkspace` and `domToBlock`. * @param {Element} blocksDOM DOM tree for this event. - * @return {Array.<Object>} Usable list of blocks from this CREATE event. + * @return {Array.<object>} Usable list of blocks from this CREATE event. */ var domToBlocks = function (blocksDOM) { // At this level, there could be multiple blocks adjacent in the DOM tree. @@ -32,8 +32,8 @@ var domToBlocks = function (blocksDOM) { /** * Adapter between block creation events and block representation which can be * used by the Scratch runtime. - * @param {Object} e `Blockly.events.create` - * @return {Array.<Object>} List of blocks from this CREATE event. + * @param {object} e `Blockly.events.create` + * @return {Array.<object>} List of blocks from this CREATE event. */ var adapter = function (e) { // Validate input @@ -47,8 +47,8 @@ var adapter = function (e) { * Convert and an individual block DOM to the representation tree. * Based on Blockly's `domToBlockHeadless_`. * @param {Element} blockDOM DOM tree for an individual block. - * @param {Object} blocks Collection of blocks to add to. - * @param {Boolean} isTopBlock Whether blocks at this level are "top blocks." + * @param {object} blocks Collection of blocks to add to. + * @param {boolean} isTopBlock Whether blocks at this level are "top blocks." * @param {?string} parent Parent block ID. * @return {undefined} */ diff --git a/src/engine/blocks.js b/src/engine/blocks.js index 84d09a6f9..ed03be324 100644 --- a/src/engine/blocks.js +++ b/src/engine/blocks.js @@ -34,7 +34,7 @@ Blocks.BRANCH_INPUT_PREFIX = 'SUBSTACK'; /** * Provide an object with metadata for the requested block ID. * @param {!string} blockId ID of block we have stored. - * @return {?Object} Metadata about the block, if it exists. + * @return {?object} Metadata about the block, if it exists. */ Blocks.prototype.getBlock = function (blockId) { return this._blocks[blockId]; @@ -54,8 +54,8 @@ Blocks.prototype.getScripts = function () { * @return {?string} ID of next block in the sequence */ Blocks.prototype.getNextBlock = function (id) { - if (typeof this._blocks[id] === 'undefined') return null; - return this._blocks[id].next; + var block = this._blocks[id]; + return (typeof block === 'undefined') ? null : block.next; }; /** @@ -85,33 +85,34 @@ Blocks.prototype.getBranch = function (id, branchNum) { * @return {?string} the opcode corresponding to that block */ Blocks.prototype.getOpcode = function (id) { - if (typeof this._blocks[id] === 'undefined') return null; - return this._blocks[id].opcode; + var block = this._blocks[id]; + return (typeof block === 'undefined') ? null : block.opcode; }; /** * Get all fields and their values for a block. * @param {?string} id ID of block to query. - * @return {!Object} All fields and their values. + * @return {!object} All fields and their values. */ Blocks.prototype.getFields = function (id) { - if (typeof this._blocks[id] === 'undefined') return null; - return this._blocks[id].fields; + var block = this._blocks[id]; + return (typeof block === 'undefined') ? null : block.fields; }; /** * Get all non-branch inputs for a block. * @param {?string} id ID of block to query. - * @return {!Object} All non-branch inputs and their associated blocks. + * @return {!object} All non-branch inputs and their associated blocks. */ Blocks.prototype.getInputs = function (id) { - if (typeof this._blocks[id] === 'undefined') return null; + var block = this._blocks[id]; + if (typeof block === 'undefined') return null; var inputs = {}; - for (var input in this._blocks[id].inputs) { + for (var input in block.inputs) { // Ignore blocks prefixed with branch prefix. if (input.substring(0, Blocks.BRANCH_INPUT_PREFIX.length) !== Blocks.BRANCH_INPUT_PREFIX) { - inputs[input] = this._blocks[id].inputs[input]; + inputs[input] = block.inputs[input]; } } return inputs; @@ -120,11 +121,11 @@ Blocks.prototype.getInputs = function (id) { /** * Get mutation data for a block. * @param {?string} id ID of block to query. - * @return {!Object} Mutation for the block. + * @return {!object} Mutation for the block. */ Blocks.prototype.getMutation = function (id) { - if (typeof this._blocks[id] === 'undefined') return null; - return this._blocks[id].mutation; + var block = this._blocks[id]; + return (typeof block === 'undefined') ? null : block.mutation; }; /** @@ -133,8 +134,8 @@ Blocks.prototype.getMutation = function (id) { * @return {?string} ID of top-level script block. */ Blocks.prototype.getTopLevelScript = function (id) { - if (typeof this._blocks[id] === 'undefined') return null; var block = this._blocks[id]; + if (typeof block === 'undefined') return null; while (block.parent !== null) { block = this._blocks[block.parent]; } @@ -246,7 +247,7 @@ Blocks.prototype.blocklyListen = function (e, optRuntime) { /** * Block management: create blocks and scripts from a `create` event - * @param {!Object} block Blockly create event to be processed + * @param {!object} block Blockly create event to be processed */ Blocks.prototype.createBlock = function (block) { // Does the block already exist? @@ -266,25 +267,26 @@ Blocks.prototype.createBlock = function (block) { /** * Block management: change block field values - * @param {!Object} args Blockly change event to be processed + * @param {!object} args Blockly change event to be processed */ Blocks.prototype.changeBlock = function (args) { // Validate if (args.element !== 'field' && args.element !== 'mutation') return; - if (typeof this._blocks[args.id] === 'undefined') return; + var block = this._blocks[args.id]; + if (typeof block === 'undefined') return; if (args.element === 'field') { // Update block value - if (!this._blocks[args.id].fields[args.name]) return; - this._blocks[args.id].fields[args.name].value = args.value; + if (!block.fields[args.name]) return; + block.fields[args.name].value = args.value; } else if (args.element === 'mutation') { - this._blocks[args.id].mutation = mutationAdapter(args.value); + block.mutation = mutationAdapter(args.value); } }; /** * Block management: move blocks from parent to parent - * @param {!Object} e Blockly move event to be processed + * @param {!object} e Blockly move event to be processed */ Blocks.prototype.moveBlock = function (e) { if (!this._blocks.hasOwnProperty(e.id)) { @@ -340,7 +342,7 @@ Blocks.prototype.moveBlock = function (e) { /** * Block management: delete blocks and their associated scripts. - * @param {!Object} e Blockly delete event to be processed. + * @param {!object} e Blockly delete event to be processed. */ Blocks.prototype.deleteBlock = function (e) { // @todo In runtime, stop threads running on this script. @@ -447,7 +449,7 @@ Blocks.prototype.blockToXML = function (blockId) { /** * Recursively encode a mutation object to XML. - * @param {!Object} mutation Object representing a mutation. + * @param {!object} mutation Object representing a mutation. * @return {string} XML string representing a mutation. */ Blocks.prototype.mutationToXML = function (mutation) { diff --git a/src/engine/execute.js b/src/engine/execute.js index a8967eb1f..44951aba3 100644 --- a/src/engine/execute.js +++ b/src/engine/execute.js @@ -4,7 +4,7 @@ var Thread = require('./thread'); /** * Utility function to determine if a value is a Promise. * @param {*} value Value to check for a Promise. - * @return {Boolean} True if the value appears to be a Promise. + * @return {boolean} True if the value appears to be a Promise. */ var isPromise = function (value) { return value && value.then && typeof value.then === 'function'; diff --git a/src/engine/mutation-adapter.js b/src/engine/mutation-adapter.js index 9277cc150..54bf7b4ee 100644 --- a/src/engine/mutation-adapter.js +++ b/src/engine/mutation-adapter.js @@ -2,8 +2,8 @@ var html = require('htmlparser2'); /** * Convert a part of a mutation DOM to a mutation VM object, recursively. - * @param {Object} dom DOM object for mutation tag. - * @return {Object} Object representing useful parts of this mutation. + * @param {object} dom DOM object for mutation tag. + * @return {object} Object representing useful parts of this mutation. */ var mutatorTagToObject = function (dom) { var obj = Object.create(null); @@ -24,8 +24,8 @@ var mutatorTagToObject = function (dom) { /** * Adapter between mutator XML or DOM and block representation which can be * used by the Scratch runtime. - * @param {(Object|string)} mutation Mutation XML string or DOM. - * @return {Object} Object representing the mutation. + * @param {(object|string)} mutation Mutation XML string or DOM. + * @return {object} Object representing the mutation. */ var mutationAdpater = function (mutation) { var mutationParsed; diff --git a/src/engine/runtime.js b/src/engine/runtime.js index 3a1967baa..f196a6a69 100644 --- a/src/engine/runtime.js +++ b/src/engine/runtime.js @@ -276,7 +276,7 @@ Runtime.prototype.getOpcodeFunction = function (opcode) { /** * Return whether an opcode represents a hat block. * @param {!string} opcode The opcode to look up. - * @return {Boolean} True if the op is known to be a hat. + * @return {boolean} True if the op is known to be a hat. */ Runtime.prototype.getIsHat = function (opcode) { return this._hats.hasOwnProperty(opcode); @@ -285,7 +285,7 @@ Runtime.prototype.getIsHat = function (opcode) { /** * Return whether an opcode represents an edge-activated hat block. * @param {!string} opcode The opcode to look up. - * @return {Boolean} True if the op is known to be a edge-activated hat. + * @return {boolean} True if the op is known to be a edge-activated hat. */ Runtime.prototype.getIsEdgeActivatedHat = function (opcode) { return this._hats.hasOwnProperty(opcode) && @@ -379,7 +379,7 @@ Runtime.prototype._restartThread = function (thread) { /** * Return whether a thread is currently active/running. * @param {?Thread} thread Thread object to check. - * @return {Boolean} True if the thread is active/running. + * @return {boolean} True if the thread is active/running. */ Runtime.prototype.isActiveThread = function (thread) { return this.threads.indexOf(thread) > -1; @@ -427,7 +427,7 @@ Runtime.prototype.allScriptsDo = function (f, optTarget) { /** * Start all relevant hats. * @param {!string} requestedHatOpcode Opcode of hats to start. - * @param {Object=} optMatchFields Optionally, fields to match on the hat. + * @param {object=} optMatchFields Optionally, fields to match on the hat. * @param {Target=} optTarget Optionally, a target to restrict to. * @return {Array.<Thread>} List of threads started by this function. */ diff --git a/src/engine/sequencer.js b/src/engine/sequencer.js index 16e07110e..06deb38f9 100644 --- a/src/engine/sequencer.js +++ b/src/engine/sequencer.js @@ -131,12 +131,17 @@ Sequencer.prototype.stepThread = function (thread) { // If no next block has been found at this point, look on the stack. while (!thread.peekStack()) { thread.popStack(); + if (thread.stack.length === 0) { // No more stack to run! thread.status = Thread.STATUS_DONE; return; } - if (thread.peekStackFrame().isLoop) { + + var stackFrame = thread.peekStackFrame(); + isWarpMode = stackFrame.warpMode; + + if (stackFrame.isLoop) { // The current level of the stack is marked as a loop. // Return to yield for the frame/tick in general. // Unless we're in warp mode - then only return if the @@ -151,7 +156,7 @@ Sequencer.prototype.stepThread = function (thread) { // since loops need to be re-executed. continue; } - } else if (thread.peekStackFrame().waitingReporter) { + } else if (stackFrame.waitingReporter) { // This level of the stack was waiting for a value. // This means a reporter has just returned - so don't go // to the next block for this level of the stack. @@ -166,8 +171,8 @@ Sequencer.prototype.stepThread = function (thread) { /** * Step a thread into a block's branch. * @param {!Thread} thread Thread object to step to branch. - * @param {Number} branchNum Which branch to step to (i.e., 1, 2). - * @param {Boolean} isLoop Whether this block is a loop. + * @param {number} branchNum Which branch to step to (i.e., 1, 2). + * @param {boolean} isLoop Whether this block is a loop. */ Sequencer.prototype.stepToBranch = function (thread, branchNum, isLoop) { if (!branchNum) { diff --git a/src/engine/thread.js b/src/engine/thread.js index 765f48b71..9693b26f1 100644 --- a/src/engine/thread.js +++ b/src/engine/thread.js @@ -101,7 +101,7 @@ Thread.prototype.pushStack = function (blockId) { if (this.stack.length > this.stackFrames.length) { // Copy warp mode from any higher level. var warpMode = false; - if (this.stackFrames[this.stackFrames.length - 1]) { + if (this.stackFrames.length > 0 && this.stackFrames[this.stackFrames.length - 1]) { warpMode = this.stackFrames[this.stackFrames.length - 1].warpMode; } this.stackFrames.push({ @@ -115,6 +115,22 @@ Thread.prototype.pushStack = function (blockId) { } }; +/** + * Reset the stack frame for use by the next block. + * (avoids popping and re-pushing a new stack frame - keeps the warpmode the same + * @param {string} blockId Block ID to push to stack. + */ +Thread.prototype.reuseStackForNextBlock = function (blockId) { + this.stack[this.stack.length - 1] = blockId; + var frame = this.stackFrames[this.stackFrames.length - 1]; + frame.isLoop = false; + // frame.warpMode = warpMode; // warp mode stays the same when reusing the stack frame. + frame.reported = {}; + frame.waitingReporter = null; + frame.params = {}; + frame.executionContext = {}; +}; + /** * Pop last block on the stack and its stack frame. * @return {string} Block ID popped from the stack. @@ -129,24 +145,24 @@ Thread.prototype.popStack = function () { * @return {?string} Block ID on top of stack. */ Thread.prototype.peekStack = function () { - return this.stack[this.stack.length - 1]; + return this.stack.length > 0 ? this.stack[this.stack.length - 1] : null; }; /** * Get top stack frame. - * @return {?Object} Last stack frame stored on this thread. + * @return {?object} Last stack frame stored on this thread. */ Thread.prototype.peekStackFrame = function () { - return this.stackFrames[this.stackFrames.length - 1]; + return this.stackFrames.length > 0 ? this.stackFrames[this.stackFrames.length - 1] : null; }; /** * Get stack frame above the current top. - * @return {?Object} Second to last stack frame stored on this thread. + * @return {?object} Second to last stack frame stored on this thread. */ Thread.prototype.peekParentStackFrame = function () { - return this.stackFrames[this.stackFrames.length - 2]; + return this.stackFrames.length > 1 ? this.stackFrames[this.stackFrames.length - 2] : null; }; /** @@ -189,7 +205,7 @@ Thread.prototype.getParam = function (paramName) { /** * Whether the current execution of a thread is at the top of the stack. - * @return {Boolean} True if execution is at top of the stack. + * @return {boolean} True if execution is at top of the stack. */ Thread.prototype.atStackTop = function () { return this.peekStack() === this.topBlock; @@ -203,15 +219,7 @@ Thread.prototype.atStackTop = function () { */ Thread.prototype.goToNextBlock = function () { var nextBlockId = this.target.blocks.getNextBlock(this.peekStack()); - // Copy warp mode to next block. - var warpMode = this.peekStackFrame().warpMode; - // The current block is on the stack - pop it and push the next. - // Note that this could push `null` - that is handled by the sequencer. - this.popStack(); - this.pushStack(nextBlockId); - if (this.peekStackFrame()) { - this.peekStackFrame().warpMode = warpMode; - } + this.reuseStackForNextBlock(nextBlockId); }; /** diff --git a/src/engine/variable.js b/src/engine/variable.js index 65fc21c45..3f2dcc5de 100644 --- a/src/engine/variable.js +++ b/src/engine/variable.js @@ -5,7 +5,7 @@ /** * @param {!string} name Name of the variable. - * @param {(string|Number)} value Value of the variable. + * @param {(string|number)} value Value of the variable. * @param {boolean} isCloud Whether the variable is stored in the cloud. * @constructor */ diff --git a/src/serialization/sb2.js b/src/serialization/sb2.js index 32685fab5..a07bb7232 100644 --- a/src/serialization/sb2.js +++ b/src/serialization/sb2.js @@ -17,7 +17,7 @@ var List = require('../engine/list'); /** * Parse a single "Scratch object" and create all its in-memory VM objects. - * @param {!Object} object From-JSON "Scratch object:" sprite, stage, watcher. + * @param {!object} object From-JSON "Scratch object:" sprite, stage, watcher. * @param {!Runtime} runtime Runtime object to load all structures into. * @param {boolean} topLevel Whether this is the top-level object (stage). * @return {?Target} Target created (stage or sprite). @@ -61,7 +61,8 @@ var parseScratchObject = function (object, runtime, topLevel) { rate: sound.rate, sampleCount: sound.sampleCount, soundID: sound.soundID, - name: sound.soundName + name: sound.soundName, + md5: sound.md5 }); } } @@ -138,7 +139,7 @@ var parseScratchObject = function (object, runtime, topLevel) { * and process the top-level object (the stage object). * @param {!string} json SB2-format JSON to load. * @param {!Runtime} runtime Runtime object to load all structures into. - * @param {Boolean=} optForceSprite If set, treat as sprite (Sprite2). + * @param {boolean=} optForceSprite If set, treat as sprite (Sprite2). * @return {?Target} Top-level target created (stage or sprite). */ var sb2import = function (json, runtime, optForceSprite) { @@ -152,7 +153,7 @@ var sb2import = function (json, runtime, optForceSprite) { /** * Parse a Scratch object's scripts into VM blocks. * This should only handle top-level scripts that include X, Y coordinates. - * @param {!Object} scripts Scripts object from SB2 JSON. + * @param {!object} scripts Scripts object from SB2 JSON. * @param {!Blocks} blocks Blocks object to load parsed blocks into. */ var parseScripts = function (scripts, blocks) { @@ -166,8 +167,8 @@ var parseScripts = function (scripts, blocks) { // Adjust script coordinates to account for // larger block size in scratch-blocks. // @todo: Determine more precisely the right formulas here. - parsedBlockList[0].x = scriptX * 1.1; - parsedBlockList[0].y = scriptY * 1.1; + parsedBlockList[0].x = scriptX * 1.5; + parsedBlockList[0].y = scriptY * 2.2; parsedBlockList[0].topLevel = true; parsedBlockList[0].parent = null; } @@ -184,8 +185,8 @@ var parseScripts = function (scripts, blocks) { * Could be used to parse a top-level script, * a list of blocks in a branch (e.g., in forever), * or a list of blocks in an argument (e.g., move [pick random...]). - * @param {Array.<Object>} blockList SB2 JSON-format block list. - * @return {Array.<Object>} Scratch VM-format block list. + * @param {Array.<object>} blockList SB2 JSON-format block list. + * @return {Array.<object>} Scratch VM-format block list. */ var parseBlockList = function (blockList) { var resultingList = []; @@ -193,6 +194,7 @@ var parseBlockList = function (blockList) { for (var i = 0; i < blockList.length; i++) { var block = blockList[i]; var parsedBlock = parseBlock(block); + if (typeof parsedBlock === 'undefined') continue; if (previousBlock) { parsedBlock.parent = previousBlock.id; previousBlock.next = parsedBlock.id; @@ -206,8 +208,8 @@ var parseBlockList = function (blockList) { /** * Flatten a block tree into a block list. * Children are temporarily stored on the `block.children` property. - * @param {Array.<Object>} blocks list generated by `parseBlockList`. - * @return {Array.<Object>} Flattened list to be passed to `blocks.createBlock`. + * @param {Array.<object>} blocks list generated by `parseBlockList`. + * @return {Array.<object>} Flattened list to be passed to `blocks.createBlock`. */ var flatten = function (blocks) { var finalBlocks = []; @@ -227,7 +229,7 @@ var flatten = function (blocks) { * into an argument map. This allows us to provide the expected inputs * to a mutated procedure call. * @param {string} procCode Scratch 2.0 procedure string. - * @return {Object} Argument map compatible with those in sb2specmap. + * @return {object} Argument map compatible with those in sb2specmap. */ var parseProcedureArgMap = function (procCode) { var argMap = [ @@ -258,8 +260,8 @@ var parseProcedureArgMap = function (procCode) { /** * Parse a single SB2 JSON-formatted block and its children. - * @param {!Object} sb2block SB2 JSON-formatted block. - * @return {Object} Scratch VM format block. + * @param {!object} sb2block SB2 JSON-formatted block. + * @return {object} Scratch VM format block. */ var parseBlock = function (sb2block) { // First item in block object is the old opcode (e.g., 'forward:'). diff --git a/src/serialization/sb2_specmap.js b/src/serialization/sb2_specmap.js index 95aad5019..5ff494059 100644 --- a/src/serialization/sb2_specmap.js +++ b/src/serialization/sb2_specmap.js @@ -408,7 +408,7 @@ var specMap = { { type: 'input', inputOp: 'math_number', - inputName: 'DRUMTYPE' + inputName: 'DRUM' }, { type: 'input', diff --git a/src/sprites/rendered-target.js b/src/sprites/rendered-target.js index 1f432e615..effc48897 100644 --- a/src/sprites/rendered-target.js +++ b/src/sprites/rendered-target.js @@ -69,10 +69,9 @@ RenderedTarget.prototype.initDrawable = function () { this.audioPlayer = null; if (this.runtime && this.runtime.audioEngine) { if (this.isOriginal) { - this.sprite.audioPlayer = this.runtime.audioEngine.createPlayer(); - this.sprite.audioPlayer.loadSounds(this.sprite.sounds); + this.runtime.audioEngine.loadSounds(this.sprite.sounds); } - this.audioPlayer = this.sprite.audioPlayer; + this.audioPlayer = this.runtime.audioEngine.createPlayer(); } }; @@ -184,7 +183,7 @@ RenderedTarget.prototype.setXY = function (x, y) { /** * Get the rendered direction and scale, after applying rotation style. - * @return {Object<string, number>} Direction and scale to render. + * @return {object<string, number>} Direction and scale to render. */ RenderedTarget.prototype._getRenderedDirectionAndScale = function () { // Default: no changes to `this.direction` or `this.scale`. @@ -399,20 +398,6 @@ RenderedTarget.prototype.getCostumeIndexByName = function (costumeName) { return -1; }; -/** - * Get a sound index of this rendered target, by name of the sound. - * @param {?string} soundName Name of a sound. - * @return {number} Index of the named sound, or -1 if not present. - */ -RenderedTarget.prototype.getSoundIndexByName = function (soundName) { - for (var i = 0; i < this.sprite.sounds.length; i++) { - if (this.sprite.sounds[i].name === soundName) { - return i; - } - } - return -1; -}; - /** * Get a costume of this rendered target by id. * @return {object} current costume @@ -437,7 +422,7 @@ RenderedTarget.prototype.updateAllDrawableProperties = function () { if (this.renderer) { var renderedDirectionScale = this._getRenderedDirectionAndScale(); var costume = this.sprite.costumes[this.currentCostume]; - this.renderer.updateDrawableProperties(this.drawableID, { + var props = { position: [this.x, this.y], direction: renderedDirectionScale.direction, scale: renderedDirectionScale.scale, @@ -448,7 +433,11 @@ RenderedTarget.prototype.updateAllDrawableProperties = function () { costume.rotationCenterX / costume.bitmapResolution, costume.rotationCenterY / costume.bitmapResolution ] - }); + }; + for (var effectID in this.effects) { + props[effectID] = this.effects[effectID]; + } + this.renderer.updateDrawableProperties(this.drawableID, props); if (this.visible) { this.runtime.requestRedraw(); } @@ -476,7 +465,7 @@ RenderedTarget.prototype.isSprite = function () { /** * Return the rendered target's tight bounding box. * Includes top, left, bottom, right attributes in Scratch coordinates. - * @return {?Object} Tight bounding box, or null. + * @return {?object} Tight bounding box, or null. */ RenderedTarget.prototype.getBounds = function () { if (this.renderer) { @@ -489,7 +478,7 @@ RenderedTarget.prototype.getBounds = function () { * Return whether touching a point. * @param {number} x X coordinate of test point. * @param {number} y Y coordinate of test point. - * @return {Boolean} True iff the rendered target is touching the point. + * @return {boolean} True iff the rendered target is touching the point. */ RenderedTarget.prototype.isTouchingPoint = function (x, y) { if (this.renderer) { @@ -509,7 +498,7 @@ RenderedTarget.prototype.isTouchingPoint = function (x, y) { /** * Return whether touching a stage edge. - * @return {Boolean} True iff the rendered target is touching the stage edge. + * @return {boolean} True iff the rendered target is touching the stage edge. */ RenderedTarget.prototype.isTouchingEdge = function () { if (this.renderer) { @@ -529,7 +518,7 @@ RenderedTarget.prototype.isTouchingEdge = function () { /** * Return whether touching any of a named sprite's clones. * @param {string} spriteName Name of the sprite. - * @return {Boolean} True iff touching a clone of the sprite. + * @return {boolean} True iff touching a clone of the sprite. */ RenderedTarget.prototype.isTouchingSprite = function (spriteName) { var firstClone = this.runtime.getSpriteTargetByName(spriteName); @@ -546,7 +535,7 @@ RenderedTarget.prototype.isTouchingSprite = function (spriteName) { /** * Return whether touching a color. * @param {Array.<number>} rgb [r,g,b], values between 0-255. - * @return {Promise.<Boolean>} True iff the rendered target is touching the color. + * @return {Promise.<boolean>} True iff the rendered target is touching the color. */ RenderedTarget.prototype.isTouchingColor = function (rgb) { if (this.renderer) { @@ -557,9 +546,9 @@ RenderedTarget.prototype.isTouchingColor = function (rgb) { /** * Return whether rendered target's color is touching a color. - * @param {Object} targetRgb {Array.<number>} [r,g,b], values between 0-255. - * @param {Object} maskRgb {Array.<number>} [r,g,b], values between 0-255. - * @return {Promise.<Boolean>} True iff the color is touching the color. + * @param {object} targetRgb {Array.<number>} [r,g,b], values between 0-255. + * @param {object} maskRgb {Array.<number>} [r,g,b], values between 0-255. + * @return {Promise.<boolean>} True iff the color is touching the color. */ RenderedTarget.prototype.colorIsTouchingColor = function (targetRgb, maskRgb) { if (this.renderer) { @@ -607,7 +596,7 @@ RenderedTarget.prototype.goBehindOther = function (other) { * Keep a desired position within a fence. * @param {number} newX New desired X position. * @param {number} newY New desired Y position. - * @param {Object=} optFence Optional fence with left, right, top bottom. + * @param {object=} optFence Optional fence with left, right, top bottom. * @return {Array.<number>} Fenced X and Y coordinates. */ RenderedTarget.prototype.keepInFence = function (newX, newY, optFence) { diff --git a/src/util/cast.js b/src/util/cast.js index 0723b5ecd..a8a1535b8 100644 --- a/src/util/cast.js +++ b/src/util/cast.js @@ -95,7 +95,7 @@ Cast.toRgbColorObject = function (value) { * In Scratch 2.0, this is captured by `interp.compare.` * @param {*} v1 First value to compare. * @param {*} v2 Second value to compare. - * @returns {Number} Negative number if v1 < v2; 0 if equal; positive otherwise. + * @returns {number} Negative number if v1 < v2; 0 if equal; positive otherwise. */ Cast.compare = function (v1, v2) { var n1 = Number(v1); diff --git a/src/util/color.js b/src/util/color.js index 951cb270a..11082d660 100644 --- a/src/util/color.js +++ b/src/util/color.js @@ -40,10 +40,11 @@ Color.decimalToHex = function (decimal) { * @return {RGBObject} rgb - {r: red [0,255], g: green [0,255], b: blue [0,255]}. */ Color.decimalToRgb = function (decimal) { + var a = (decimal >> 24) & 0xFF; var r = (decimal >> 16) & 0xFF; var g = (decimal >> 8) & 0xFF; var b = decimal & 0xFF; - return {r: r, g: g, b: b}; + return {r: r, g: g, b: b, a: a > 0 ? a : 255}; }; /** diff --git a/src/util/math-util.js b/src/util/math-util.js index c27e730bb..6191fdec5 100644 --- a/src/util/math-util.js +++ b/src/util/math-util.js @@ -45,4 +45,24 @@ MathUtil.wrapClamp = function (n, min, max) { return n - (Math.floor((n - min) / range) * range); }; + +/** + * Convert a value from tan function in degrees. + * @param {!number} angle in degrees + * @return {!number} Correct tan value + */ +MathUtil.tan = function (angle) { + angle = angle % 360; + switch (angle) { + case -270: + case 90: + return Infinity; + case -90: + case 270: + return -Infinity; + default: + return parseFloat(Math.tan((Math.PI * angle) / 180).toFixed(10)); + } +}; + module.exports = MathUtil; diff --git a/src/virtual-machine.js b/src/virtual-machine.js index 28fbeaefc..7aa5d6654 100644 --- a/src/virtual-machine.js +++ b/src/virtual-machine.js @@ -77,7 +77,7 @@ VirtualMachine.prototype.greenFlag = function () { /** * Set whether the VM is in "turbo mode." * When true, loops don't yield to redraw. - * @param {Boolean} turboModeOn Whether turbo mode should be set. + * @param {boolean} turboModeOn Whether turbo mode should be set. */ VirtualMachine.prototype.setTurboMode = function (turboModeOn) { this.runtime.turboMode = !!turboModeOn; @@ -86,7 +86,7 @@ VirtualMachine.prototype.setTurboMode = function (turboModeOn) { /** * Set whether the VM is in 2.0 "compatibility mode." * When true, ticks go at 2.0 speed (30 TPS). - * @param {Boolean} compatibilityModeOn Whether compatibility mode is set. + * @param {boolean} compatibilityModeOn Whether compatibility mode is set. */ VirtualMachine.prototype.setCompatibilityMode = function (compatibilityModeOn) { this.runtime.setCompatibilityMode(!!compatibilityModeOn); @@ -131,7 +131,7 @@ VirtualMachine.prototype.getPlaygroundData = function () { /** * Post I/O data to the virtual devices. * @param {?string} device Name of virtual I/O device. - * @param {Object} data Any data object to post to the I/O device. + * @param {object} data Any data object to post to the I/O device. */ VirtualMachine.prototype.postIOData = function (device, data) { if (this.runtime.ioDevices[device]) { @@ -219,7 +219,7 @@ VirtualMachine.prototype.addSprite2 = function (json) { /** * Add a costume to the current editing target. - * @param {!Object} costumeObject Object representing the costume. + * @param {!object} costumeObject Object representing the costume. */ VirtualMachine.prototype.addCostume = function (costumeObject) { this.editingTarget.sprite.costumes.push(costumeObject); @@ -231,7 +231,7 @@ VirtualMachine.prototype.addCostume = function (costumeObject) { /** * Add a backdrop to the stage. - * @param {!Object} backdropObject Object representing the backdrop. + * @param {!object} backdropObject Object representing the backdrop. */ VirtualMachine.prototype.addBackdrop = function (backdropObject) { var stage = this.runtime.getTargetForStage(); diff --git a/test/fixtures/sound.sb2 b/test/fixtures/sound.sb2 new file mode 100644 index 000000000..dce594b37 Binary files /dev/null and b/test/fixtures/sound.sb2 differ diff --git a/test/integration/sound.js b/test/integration/sound.js new file mode 100644 index 000000000..dc1a58eee --- /dev/null +++ b/test/integration/sound.js @@ -0,0 +1,35 @@ +var path = require('path'); +var test = require('tap').test; +var extract = require('../fixtures/extract'); +var VirtualMachine = require('../../src/index'); + +var uri = path.resolve(__dirname, '../fixtures/sound.sb2'); +var project = extract(uri); + +test('sound', 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); + 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); +}); diff --git a/test/unit/blocks_operators.js b/test/unit/blocks_operators.js index b88ff0f77..dc4110d25 100644 --- a/test/unit/blocks_operators.js +++ b/test/unit/blocks_operators.js @@ -160,9 +160,13 @@ test('mathop', function (t) { t.strictEqual(blocks.mathop({OPERATOR: 'floor', NUM: 1.5}), 1); t.strictEqual(blocks.mathop({OPERATOR: 'ceiling', NUM: 0.1}), 1); t.strictEqual(blocks.mathop({OPERATOR: 'sqrt', NUM: 1}), 1); - t.strictEqual(blocks.mathop({OPERATOR: 'sin', NUM: 1}), 0.01745240643728351); - t.strictEqual(blocks.mathop({OPERATOR: 'cos', NUM: 1}), 0.9998476951563913); - t.strictEqual(blocks.mathop({OPERATOR: 'tan', NUM: 1}), 0.017455064928217585); + t.strictEqual(blocks.mathop({OPERATOR: 'sin', NUM: 1}), 0.0174524064); + t.strictEqual(blocks.mathop({OPERATOR: 'sin', NUM: 90}), 1); + t.strictEqual(blocks.mathop({OPERATOR: 'cos', NUM: 1}), 0.9998476952); + t.strictEqual(blocks.mathop({OPERATOR: 'cos', NUM: 180}), -1); + t.strictEqual(blocks.mathop({OPERATOR: 'tan', NUM: 1}), 0.0174550649); + t.strictEqual(blocks.mathop({OPERATOR: 'tan', NUM: 90}), Infinity); + t.strictEqual(blocks.mathop({OPERATOR: 'tan', NUM: 180}), 0); t.strictEqual(blocks.mathop({OPERATOR: 'asin', NUM: 1}), 90); t.strictEqual(blocks.mathop({OPERATOR: 'acos', NUM: 1}), 0); t.strictEqual(blocks.mathop({OPERATOR: 'atan', NUM: 1}), 45); diff --git a/test/unit/util_cast.js b/test/unit/util_cast.js index 4f15d16bb..861373bb2 100644 --- a/test/unit/util_cast.js +++ b/test/unit/util_cast.js @@ -99,13 +99,14 @@ test('toRbgColorObject', function (t) { t.deepEqual(cast.toRgbColorObject('#ffffff'), {r: 255, g: 255, b: 255}); // Decimal (minimal, see "color" util tests) - t.deepEqual(cast.toRgbColorObject(0), {r: 0, g: 0, b: 0}); - t.deepEqual(cast.toRgbColorObject(1), {r: 0, g: 0, b: 1}); - t.deepEqual(cast.toRgbColorObject(16777215), {r: 255, g: 255, b: 255}); + t.deepEqual(cast.toRgbColorObject(0), {a: 255, r: 0, g: 0, b: 0}); + t.deepEqual(cast.toRgbColorObject(1), {a: 255, r: 0, g: 0, b: 1}); + t.deepEqual(cast.toRgbColorObject(16777215), {a: 255, r: 255, g: 255, b: 255}); + t.deepEqual(cast.toRgbColorObject('0x80010203'), {a: 128, r: 1, g: 2, b: 3}); // Malformed - t.deepEqual(cast.toRgbColorObject('ffffff'), {r: 0, g: 0, b: 0}); - t.deepEqual(cast.toRgbColorObject('foobar'), {r: 0, g: 0, b: 0}); + t.deepEqual(cast.toRgbColorObject('ffffff'), {a: 255, r: 0, g: 0, b: 0}); + t.deepEqual(cast.toRgbColorObject('foobar'), {a: 255, r: 0, g: 0, b: 0}); t.end(); }); diff --git a/test/unit/util_color.js b/test/unit/util_color.js index 1ffb737eb..ea68b786d 100644 --- a/test/unit/util_color.js +++ b/test/unit/util_color.js @@ -47,11 +47,11 @@ test('decimalToHex', function (t) { }); test('decimalToRgb', function (t) { - t.deepEqual(color.decimalToRgb(0), {r: 0, g: 0, b: 0}); - t.deepEqual(color.decimalToRgb(1), {r: 0, g: 0, b: 1}); - t.deepEqual(color.decimalToRgb(16777215), {r: 255, g: 255, b: 255}); - t.deepEqual(color.decimalToRgb(-16777215), {r: 0, g: 0, b: 1}); - t.deepEqual(color.decimalToRgb(99999999), {r: 245, g: 224, b: 255}); + t.deepEqual(color.decimalToRgb(0), {a: 255, r: 0, g: 0, b: 0}); + t.deepEqual(color.decimalToRgb(1), {a: 255, r: 0, g: 0, b: 1}); + t.deepEqual(color.decimalToRgb(16777215), {a: 255, r: 255, g: 255, b: 255}); + t.deepEqual(color.decimalToRgb(-16777215), {a: 255, r: 0, g: 0, b: 1}); + t.deepEqual(color.decimalToRgb(99999999), {a: 5, r: 245, g: 224, b: 255}); t.end(); }); diff --git a/test/unit/util_math.js b/test/unit/util_math.js index b198ef1d3..4c728dce1 100644 --- a/test/unit/util_math.js +++ b/test/unit/util_math.js @@ -34,3 +34,11 @@ test('wrapClamp', function (t) { t.strictEqual(math.wrapClamp(100, 0, 10), 1); t.end(); }); + +test('tan', function (t) { + t.strictEqual(math.tan(90), Infinity); + t.strictEqual(math.tan(180), 0); + t.strictEqual(math.tan(-90), -Infinity); + t.strictEqual(math.tan(33), 0.6494075932); + t.end(); +}); diff --git a/webpack.config.js b/webpack.config.js index d01c7d7a6..f58b7a1bf 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -10,21 +10,10 @@ var base = { port: process.env.PORT || 8073 }, devtool: 'source-map', - module: { - loaders: [ - { - test: /\.json$/, - loader: 'json-loader' - } - ] - }, plugins: [ new webpack.optimize.UglifyJsPlugin({ include: /\.min\.js$/, - minimize: true, - compress: { - warnings: false - } + minimize: true }) ] }; @@ -42,12 +31,12 @@ module.exports = [ filename: '[name].js' }, module: { - loaders: base.module.loaders.concat([ + rules: [ { test: require.resolve('./src/index.js'), - loader: 'expose?VirtualMachine' + loader: 'expose-loader?VirtualMachine' } - ]) + ] } }), // Node-compatible @@ -86,32 +75,32 @@ module.exports = [ filename: '[name].js' }, module: { - loaders: base.module.loaders.concat([ + loaders: [ { test: require.resolve('./src/index.js'), - loader: 'expose?VirtualMachine' + loader: 'expose-loader?VirtualMachine' }, { test: require.resolve('stats.js/build/stats.min.js'), - loader: 'script' + loader: 'script-loader' }, { test: require.resolve('highlightjs/highlight.pack.min.js'), - loader: 'script' + loader: 'script-loader' }, { test: require.resolve('scratch-blocks/dist/vertical.js'), - loader: 'expose?Blockly' + loader: 'expose-loader?Blockly' }, { test: require.resolve('scratch-render'), - loader: 'expose?RenderWebGL' + loader: 'expose-loader?RenderWebGL' }, { test: require.resolve('scratch-audio'), - loader: 'expose?AudioEngine' + loader: 'expose-loader?AudioEngine' } - ]) + ] }, plugins: base.plugins.concat([ new CopyWebpackPlugin([{