├── .gitignore ├── default.sb ├── icons.png ├── README.md ├── js ├── jscembed.js ├── scratchio.js ├── jdataview.js ├── blocks.js ├── datatypes.js └── jsscratch.js ├── LICENSE ├── index.html └── player.css /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.sb 3 | *.sb2 4 | *.ypr 5 | /bin -------------------------------------------------------------------------------- /default.sb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trumank/JsScratch/HEAD/default.sb -------------------------------------------------------------------------------- /icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trumank/JsScratch/HEAD/icons.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JsScratch 2 | 3 | JsScratch is a project player in JavaScript and HTML for MIT Scratch. -------------------------------------------------------------------------------- /js/jscembed.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var done = []; 3 | 4 | function update() { 5 | var divs = document.querySelectorAll('div[class=project]'); 6 | var i = divs.length; 7 | while (i--) { 8 | if (done.indexOf(divs[i]) === -1) { 9 | done.push(divs[i]); 10 | var project = jsc.createPlayer(divs[i].getAttribute('project'), divs[i].getAttribute('autoplay') === 'true'); 11 | window.player = project[1]; 12 | divs[i].innerHTML = ''; 13 | divs[i].appendChild(project[0]); 14 | } 15 | } 16 | } 17 | 18 | document.addEventListener('DOMContentLoaded', update, false); 19 | document.addEventListener("DOMNodeInserted", update, false); 20 | 21 | update(); 22 | }) (); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Truman Kilen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JsScratch 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 30 | 31 | -------------------------------------------------------------------------------- /player.css: -------------------------------------------------------------------------------- 1 | .player { 2 | cursor: default; 3 | overflow: hidden; 4 | } 5 | 6 | .player .header { 7 | width: 482px; 8 | height: 23px; 9 | background-color: #EEE; 10 | background-image: -moz-repeating-linear-gradient(90deg, rgba(0, 0, 0, 0.5), transparent 50%, transparent 100%); 11 | background-image: -webkit-repeating-linear-gradient(90deg, rgba(0, 0, 0, 0.5), transparent 50%, transparent 100%); 12 | border-radius: 10px 10px 0px 0px; 13 | } 14 | 15 | .player .subcon { 16 | position: relative; 17 | width: 480px; 18 | height: 360px; 19 | border: solid 1px black; 20 | border-top: none; 21 | overflow: hidden; 22 | } 23 | 24 | .player .title { 25 | float: left; 26 | font: 12px Arial, Helvetica, sans-serif; 27 | margin: 3px 3px; 28 | } 29 | 30 | .player .progress { 31 | position: absolute; 32 | top: 130px; 33 | left: 40px; 34 | width: 400px; 35 | height: 100px; 36 | z-index: 10000; 37 | 38 | background-color: grey; 39 | border: 1px solid grey; 40 | box-shadow: inset 0 0 6px #000000; 41 | border-radius: 12px; 42 | 43 | -webkit-transition: opacity 1s ease-in; 44 | -moz-transition: opacity 1s ease-in; 45 | } 46 | 47 | .player .progress .message { 48 | color: red; 49 | font-family: Verdana; 50 | font-size: 2em; 51 | font-weight: bold; 52 | position: absolute; 53 | top: 28px; 54 | left: 0px; 55 | width: 100%; 56 | text-align: center; 57 | } 58 | 59 | .player .fade { 60 | opacity: 0; 61 | } 62 | 63 | .player .progress .bar { 64 | width: 0px; 65 | height: 98px; 66 | 67 | margin: 1px 1px; 68 | 69 | border-radius: 12px; 70 | box-shadow: inset 0 0 6px #000000; 71 | 72 | background-color: orange; 73 | background-image: -moz-repeating-linear-gradient(45deg, rgba(255,255,255,.5), rgba(255,255,255,.5) 10px, transparent 10px, transparent 20px); 74 | background-image: -webkit-repeating-linear-gradient(45deg, rgba(255,255,255,.5), rgba(255,255,255,.5) 10px, transparent 10px, transparent 20px); 75 | background-size: 28px 100%; 76 | 77 | -moz-animation-duration: 1s; 78 | -moz-animation-name: loading; 79 | -moz-animation-iteration-count: infinite; 80 | -moz-animation-timing-function: linear; 81 | 82 | -webkit-animation-duration: 1s; 83 | -webkit-animation-name: loading; 84 | -webkit-animation-iteration-count: infinite; 85 | -webkit-animation-timing-function: linear; 86 | 87 | -webkit-transition: width 1s ease-in-out; 88 | } 89 | 90 | @-moz-keyframes loading { 91 | from { 92 | background-position: 0px 0px; 93 | } 94 | 95 | to { 96 | background-position: 28px 0px; 97 | } 98 | } 99 | 100 | @-webkit-keyframes loading { 101 | from { 102 | background-position: 0px 0px; 103 | } 104 | 105 | to { 106 | background-position: 28px 0px; 107 | } 108 | } 109 | 110 | .player .button { 111 | position: relative; 112 | float: right; 113 | margin: 0px 5px; 114 | background: url('icons.png'); 115 | } 116 | 117 | .player .stop { 118 | background-position: -17px 0px; 119 | width: 17px; 120 | height: 17px; 121 | top: 3px; 122 | } 123 | 124 | .player .stop:hover { 125 | background-position: -17px -17px; 126 | } 127 | 128 | .player .start { 129 | background-position: -17px -34px; 130 | width: 20px; 131 | height: 17px; 132 | top: 3px; 133 | } 134 | 135 | .player .start:hover { 136 | background-position: -17px -51px; 137 | } 138 | 139 | .player input[type="checkbox"] { 140 | width: 17px; 141 | height: 17px; 142 | top: 3px; 143 | background-position: 0px -34px; 144 | -webkit-appearance: none; 145 | -moz-appearance: none; 146 | appearance: none; 147 | } 148 | 149 | .player input[type="checkbox"]:hover { 150 | background-position: 0px -51px; 151 | } 152 | 153 | .player input[type="checkbox"]:checked { 154 | background-position: 0px 0px; 155 | } 156 | 157 | .player input[type="checkbox"]:checked:hover { 158 | background-position: 0px -17px; 159 | } 160 | 161 | .player .status { 162 | float: right; 163 | font: bold 10px Arial, Helvetica, sans-serif bold; 164 | color: #FFFF00; 165 | margin: 5px 3px; 166 | } 167 | 168 | .player canvas { 169 | position: relative; 170 | } -------------------------------------------------------------------------------- /js/scratchio.js: -------------------------------------------------------------------------------- 1 | (function (jsc) { 2 | // FieldStream //////////////////////////////////////////// 3 | jsc.FieldStream = function (fields) { 4 | this.fields = fields; 5 | this.index = -1; 6 | } 7 | 8 | jsc.FieldStream.prototype.nextField = function () { 9 | this.index += 1; 10 | return this.fields[this.index]; 11 | }; 12 | 13 | 14 | // Ref //////////////////////////////////////////////////// 15 | jsc.Ref = function (index) { 16 | this.index = index - 1; 17 | } 18 | 19 | 20 | // ObjectStream /////////////////////////////////////////// 21 | jsc.ObjectStream = function (stream) { 22 | this.stream = stream; 23 | 24 | var version = this.readFileHeader(); 25 | if (version === -1) { 26 | throw 'Not a Scratch project'; 27 | } else if (version < 1) { 28 | throw 'Project is too old: ' + version; 29 | } 30 | 31 | this.endOfInfo = stream.getUint32(); 32 | }; 33 | 34 | // read the file header (the version of the file) 35 | jsc.ObjectStream.prototype.readFileHeader = function () { 36 | return this.stream.getString(8) === 'ScratchV' ? parseFloat(this.stream.getString(2)) : -1; 37 | }; 38 | 39 | // read the next object's header 40 | jsc.ObjectStream.prototype.readObjectHeader = function () { 41 | this.temp = this.stream.index; 42 | return this.stream.getString(10) === 'ObjS\x01Stch\x01'; 43 | }; 44 | 45 | // get the next object in the stream 46 | jsc.ObjectStream.prototype.nextObject = function () { 47 | if (!this.readObjectHeader()) { 48 | throw ('Corrupt File'); 49 | } 50 | 51 | var objectSize = this.stream.getUint32(), 52 | fields = [], 53 | i; 54 | 55 | for (i = 0; i < objectSize; i += 1) { 56 | fields[i] = this.readField(); 57 | } 58 | return this.fixReferences(fields); 59 | }; 60 | 61 | jsc.ObjectStream.prototype.readField = function () { 62 | var id = this.stream.getUint8(); 63 | 64 | if (id === 99) { 65 | return new jsc.Ref((this.stream.getUint8() << 16) | (this.stream.getUint8() << 8) | (this.stream.getUint8())); 66 | } else if (id <= 8) { 67 | return this.readFixedFormat(id); 68 | } else if (id < 99) { 69 | return [id, this.readFixedFormat(id)]; 70 | } 71 | 72 | var version = this.stream.getUint8(), 73 | size = this.stream.getUint8(), 74 | arr = []; 75 | 76 | for (var i = 0; i < size; i++) { 77 | arr[i] = this.readField(); 78 | } 79 | 80 | return [id, arr, version, size]; 81 | }; 82 | 83 | jsc.ObjectStream.prototype.readFixedFormat = function (id) { 84 | switch (id) { 85 | case 1: //nil 86 | return null; 87 | case 2: //True 88 | return true; 89 | case 3: //False 90 | return false; 91 | case 4: //SmallInteger 92 | return this.stream.getInt32(); 93 | case 5: //SmallInteger16 94 | return this.stream.getInt16(); 95 | case 6: //LargePositiveInteger 96 | case 7: //LargeNegativeInteger 97 | var d1 = 0; 98 | var d2 = 1; 99 | var i = this.stream.getInt16(); 100 | for (var j = 0; j < i; j++) { 101 | var k = this.stream.getUint8(); 102 | d1 += d2 * k; 103 | d2 *= 256; 104 | } 105 | return id == 7 ? -d1 : d1; 106 | case 8: //Float 107 | return this.stream.getFloat64(); 108 | case 9: //String 109 | return this.stream.getString(this.stream.getUint32()); 110 | case 10: //Symbol 111 | return this.stream.getString(this.stream.getUint32()); 112 | case 11: //ByteArray 113 | var size = this.stream.getUint32(); 114 | var arr = []; 115 | for (var i = 0; i < size; i++) { 116 | arr[i] = this.stream.getUint8(); 117 | } 118 | return arr; 119 | case 12: //SoundBuffer 120 | var size = this.stream.getUint32() * 2; 121 | var arr = new Array(size); 122 | for (var i = 0; i < size; i += 2) { 123 | arr[i] = this.stream.getUint8(); 124 | arr[i + 1] = this.stream.getUint8(); 125 | } 126 | return arr; 127 | case 13: //Bitmap 128 | var size = this.stream.getUint32(); 129 | var arr = []; 130 | for (var i = 0; i < size; i++) { 131 | arr[i] = this.stream.getUint32(); 132 | } 133 | arr.isBitmap = true; 134 | return arr; 135 | case 14: //UTF8 136 | var istr = this.stream.getString(this.stream.getUint32()); 137 | var str = ''; 138 | var i = 0, c, d, e; 139 | while (i < istr.length) { 140 | c = istr.charCodeAt(i); 141 | if (c < 128) { 142 | str += String.fromCharCode(c); 143 | i++; 144 | } else if ((c > 191) && (c < 224)) { 145 | d = istr.charCodeAt(i + 1); 146 | str += String.fromCharCode(((c & 31) << 6) | (d & 63)); 147 | i += 2; 148 | } else { 149 | d = istr.charCodeAt(i + 1); 150 | e = istr.charCodeAt(i + 2); 151 | str += String.fromCharCode(((c & 15) << 12) | ((d & 63) << 6) | (e & 63)); 152 | i += 3; 153 | } 154 | } 155 | return str; 156 | case 20: //Array 157 | case 21: //OrderedCollection 158 | case 24: //Dictionary 159 | case 25: //IdentityDictionary 160 | var arr = [], 161 | size = (id == 24 || id == 25) ? this.stream.getUint32() * 2 : this.stream.getUint32(), 162 | i; 163 | for (i = 0; i < size; i++) { 164 | arr[i] = this.readField(); 165 | } 166 | return arr; 167 | case 30: //Color 168 | var color = this.stream.getUint32(); 169 | return new jsc.Color(color >> 22 & 0xFF, color >> 12 & 0xFF, color >> 2 & 0xFF); 170 | case 31: //TranslucentColor 171 | var color = this.stream.getUint32(); 172 | return new jsc.Color(color >> 22 & 0xFF, color >> 12 & 0xFF, color >> 2 & 0xFF, this.stream.getUint8()); 173 | case 32: //Point 174 | return [this.readField(), this.readField()]; 175 | case 33: //Rectangle 176 | return [this.readField(), this.readField(), this.readField(), this.readField()]; 177 | case 34: //Form 178 | return [this.readField(), this.readField(), this.readField(), this.readField(), this.readField()]; 179 | case 35: //ColorForm 180 | return [this.readField(), this.readField(), this.readField(), this.readField(), this.readField(), this.readField()]; 181 | } 182 | throw 'Unknown object: ' + id; 183 | }; 184 | 185 | jsc.ObjectStream.prototype.fixReferences = function (objTable) { 186 | var newObj = []; 187 | for (var i = 0; i < objTable.length; i++) { 188 | newObj[i] = this.classForObject(objTable[i]); 189 | } 190 | 191 | for (var i = 0; i < newObj.length; i++) { 192 | var obj = objTable[i]; 193 | 194 | var os = obj[1]; 195 | 196 | for (var j = 0; j < os.length; j++) { 197 | if (os[j] instanceof jsc.Ref) { 198 | os[j] = newObj[os[j].index]; 199 | } 200 | } 201 | 202 | switch (obj[0]) { 203 | case 20: 204 | case 21: 205 | for (var j = 0; j < obj[1].length; j++) 206 | newObj[i].push(os[j]); 207 | break; 208 | case 24: 209 | case 25: 210 | for (var j = 0; j < obj[1].length; j += 2) 211 | newObj[i][os[j]] = os[j + 1]; 212 | break; 213 | case 32: 214 | newObj[i].x = os[0]; 215 | newObj[i].y = os[1]; 216 | break; 217 | case 33: 218 | newObj[i].origin = new jsc.Point(os[0], os[1]); 219 | newObj[i].corner = new jsc.Point(os[2], os[3]); 220 | break; 221 | case 34: 222 | case 35: 223 | newObj[i].width = os[0]; 224 | newObj[i].height = os[1]; 225 | newObj[i].depth = os[2]; 226 | newObj[i].offset = os[3]; 227 | newObj[i].bits = os[4]; 228 | if (obj[0] == 35) { 229 | newObj[i].colors = os[5]; 230 | } 231 | default: 232 | if (obj[0] > 99) { 233 | if (newObj[i] instanceof Array) { 234 | [].push.apply(newObj[i], os); 235 | } else { 236 | newObj[i].VARS = os; 237 | if (newObj[i].initFields) { 238 | newObj[i].initFields(new jsc.FieldStream(newObj[i].VARS), obj[2]); 239 | } 240 | } 241 | } 242 | } 243 | } 244 | for (var i = newObj.length - 1; i >= 0; i--) { 245 | if (newObj[i].initBeforeLoad) { 246 | newObj[i].initBeforeLoad(); 247 | } 248 | } 249 | return newObj[0]; 250 | }; 251 | 252 | jsc.ObjectStream.prototype.classForObject = function (obj) { 253 | if (obj[0] <= 14) { 254 | return obj[1]; 255 | } 256 | switch (obj[0]) { 257 | case 30: 258 | case 31: 259 | return obj[1]; 260 | case 20: 261 | case 21: 262 | return []; 263 | case 23: 264 | case 24: 265 | return {}; 266 | case 32: 267 | return new jsc.Point(); 268 | case 33: 269 | return new jsc.Rectangle(); 270 | case 34: 271 | case 35: 272 | return new jsc.Form(); 273 | case 109: 274 | return new jsc.SampledSound(); 275 | case 124: 276 | return new jsc.Sprite(); 277 | case 125: 278 | return new jsc.Stage(); 279 | case 155: 280 | return new jsc.Watcher(); 281 | case 162: 282 | return new jsc.ImageMedia(); 283 | case 164: 284 | return new jsc.SoundMedia(); 285 | default: 286 | return []; 287 | } 288 | }; 289 | }) (jsc); 290 | -------------------------------------------------------------------------------- /js/jdataview.js: -------------------------------------------------------------------------------- 1 | // 2 | // jDataView by Vjeux - Jan 2010 3 | // 4 | // A unique way to read a binary file in the browser 5 | // http://github.com/vjeux/jDataView 6 | // http://blog.vjeux.com/ 7 | // 8 | 9 | (function (global) { 10 | 11 | var compatibility = { 12 | ArrayBuffer: typeof ArrayBuffer !== 'undefined', 13 | DataView: typeof DataView !== 'undefined' && 'getFloat64' in DataView.prototype, 14 | NodeBuffer: typeof Buffer !== 'undefined', 15 | // 0.6.0 -> readInt8LE(offset) 16 | NodeBufferFull: typeof Buffer !== 'undefined' && 'readInt8LE' in Buffer, 17 | // 0.5.0 -> readInt8(offset, endian) 18 | NodeBufferEndian: typeof Buffer !== 'undefined' && 'readInt8' in Buffer 19 | }; 20 | 21 | var jDataView = function (buffer, byteOffset, byteLength, littleEndian) { 22 | if (!(this instanceof arguments.callee)) { 23 | throw new Error("jDataView constructor may not be called as a function"); 24 | } 25 | 26 | this.buffer = buffer; 27 | 28 | // Handle Type Errors 29 | if (!(compatibility.NodeBuffer && buffer instanceof Buffer) && 30 | !(compatibility.ArrayBuffer && buffer instanceof ArrayBuffer) && 31 | typeof buffer !== 'string') { 32 | throw new TypeError('jDataView buffer has an incompatible type'); 33 | } 34 | 35 | // Check parameters and existing functionnalities 36 | this._isArrayBuffer = compatibility.ArrayBuffer && buffer instanceof ArrayBuffer; 37 | this._isDataView = compatibility.DataView && this._isArrayBuffer; 38 | this._isNodeBuffer = compatibility.NodeBuffer && buffer instanceof Buffer; 39 | 40 | // Default Values 41 | this._littleEndian = littleEndian === undefined ? true : littleEndian; 42 | 43 | var bufferLength = this._isArrayBuffer ? buffer.byteLength : buffer.length; 44 | if (byteOffset === undefined) { 45 | byteOffset = 0; 46 | } 47 | this.byteOffset = byteOffset; 48 | 49 | if (byteLength === undefined) { 50 | byteLength = bufferLength - byteOffset; 51 | } 52 | this.byteLength = byteLength; 53 | 54 | if (!this._isDataView) { 55 | // Do additional checks to simulate DataView 56 | if (typeof byteOffset !== 'number') { 57 | throw new TypeError('jDataView byteOffset is not a number'); 58 | } 59 | if (typeof byteLength !== 'number') { 60 | throw new TypeError('jDataView byteLength is not a number'); 61 | } 62 | if (typeof byteOffset < 0) { 63 | throw new Error('jDataView byteOffset is negative'); 64 | } 65 | if (typeof byteLength < 0) { 66 | throw new Error('jDataView byteLength is negative'); 67 | } 68 | } 69 | 70 | // Instanciate 71 | if (this._isDataView) { 72 | this._view = new DataView(buffer, byteOffset, byteLength); 73 | this._start = 0; 74 | } 75 | this._start = byteOffset; 76 | if (byteOffset + byteLength > bufferLength) { 77 | throw new Error("jDataView (byteOffset+byteLength) value is out of bounds"); 78 | } 79 | 80 | this._offset = 0; 81 | }; 82 | 83 | jDataView.createBuffer = function (data) { 84 | if (compatibility.NodeBuffer) { 85 | var buffer = new Buffer(data.length); 86 | for (var i = 0; i < data.length; ++i) { 87 | buffer[i] = data[i]; 88 | } 89 | return buffer; 90 | } 91 | if (compatibility.ArrayBuffer) { 92 | var buffer = new ArrayBuffer(data.length); 93 | var view = new Int8Array(buffer); 94 | for (var i = 0; i < data.length; ++i) { 95 | view[i] = data[i]; 96 | } 97 | return buffer; 98 | } 99 | 100 | return data.reduce(function(str, charIndex) { 101 | return str += String.fromCharCode(charIndex); 102 | }, ''); 103 | }; 104 | 105 | jDataView.prototype = { 106 | 107 | // Helpers 108 | 109 | getString: function (length, byteOffset) { 110 | var value; 111 | 112 | // Handle the lack of byteOffset 113 | if (byteOffset === undefined) { 114 | byteOffset = this._offset; 115 | } 116 | 117 | // Error Checking 118 | if (typeof byteOffset !== 'number') { 119 | throw new TypeError('jDataView byteOffset is not a number'); 120 | } 121 | if (length < 0 || byteOffset + length > this.byteLength) { 122 | throw new Error('jDataView length or (byteOffset+length) value is out of bounds'); 123 | } 124 | 125 | if (this._isNodeBuffer) { 126 | value = this.buffer.toString('ascii', this._start + byteOffset, this._start + byteOffset + length); 127 | } 128 | else { 129 | value = ''; 130 | for (var i = 0; i < length; ++i) { 131 | var c = this.getUint8(byteOffset + i); 132 | value += String.fromCharCode(c > 127 ? 65533 : c); 133 | } 134 | } 135 | 136 | this._offset = byteOffset + length; 137 | return value; 138 | }, 139 | 140 | getChar: function (byteOffset) { 141 | return this.getString(1, byteOffset); 142 | }, 143 | 144 | tell: function () { 145 | return this._offset; 146 | }, 147 | 148 | seek: function (byteOffset) { 149 | if (typeof byteOffset !== 'number') { 150 | throw new TypeError('jDataView byteOffset is not a number'); 151 | } 152 | if (byteOffset < 0 || byteOffset > this.byteLength) { 153 | throw new Error('jDataView byteOffset value is out of bounds'); 154 | } 155 | 156 | return this._offset = byteOffset; 157 | }, 158 | 159 | // Compatibility functions on a String Buffer 160 | 161 | _endianness: function (byteOffset, pos, max, littleEndian) { 162 | return byteOffset + (littleEndian ? max - pos - 1 : pos); 163 | }, 164 | 165 | _getFloat64: function (byteOffset, littleEndian) { 166 | var b0 = this._getUint8(this._endianness(byteOffset, 0, 8, littleEndian)), 167 | b1 = this._getUint8(this._endianness(byteOffset, 1, 8, littleEndian)), 168 | b2 = this._getUint8(this._endianness(byteOffset, 2, 8, littleEndian)), 169 | b3 = this._getUint8(this._endianness(byteOffset, 3, 8, littleEndian)), 170 | b4 = this._getUint8(this._endianness(byteOffset, 4, 8, littleEndian)), 171 | b5 = this._getUint8(this._endianness(byteOffset, 5, 8, littleEndian)), 172 | b6 = this._getUint8(this._endianness(byteOffset, 6, 8, littleEndian)), 173 | b7 = this._getUint8(this._endianness(byteOffset, 7, 8, littleEndian)), 174 | 175 | sign = 1 - (2 * (b0 >> 7)), 176 | exponent = ((((b0 << 1) & 0xff) << 3) | (b1 >> 4)) - (Math.pow(2, 10) - 1), 177 | 178 | // Binary operators such as | and << operate on 32 bit values, using + and Math.pow(2) instead 179 | mantissa = ((b1 & 0x0f) * Math.pow(2, 48)) + (b2 * Math.pow(2, 40)) + (b3 * Math.pow(2, 32)) + 180 | (b4 * Math.pow(2, 24)) + (b5 * Math.pow(2, 16)) + (b6 * Math.pow(2, 8)) + b7; 181 | 182 | if (exponent === 1024) { 183 | if (mantissa !== 0) { 184 | return NaN; 185 | } else { 186 | return sign * Infinity; 187 | } 188 | } 189 | 190 | if (exponent === -1023) { // Denormalized 191 | return sign * mantissa * Math.pow(2, -1022 - 52); 192 | } 193 | 194 | return sign * (1 + mantissa * Math.pow(2, -52)) * Math.pow(2, exponent); 195 | }, 196 | 197 | _getFloat32: function (byteOffset, littleEndian) { 198 | var b0 = this._getUint8(this._endianness(byteOffset, 0, 4, littleEndian)), 199 | b1 = this._getUint8(this._endianness(byteOffset, 1, 4, littleEndian)), 200 | b2 = this._getUint8(this._endianness(byteOffset, 2, 4, littleEndian)), 201 | b3 = this._getUint8(this._endianness(byteOffset, 3, 4, littleEndian)), 202 | 203 | sign = 1 - (2 * (b0 >> 7)), 204 | exponent = (((b0 << 1) & 0xff) | (b1 >> 7)) - 127, 205 | mantissa = ((b1 & 0x7f) << 16) | (b2 << 8) | b3; 206 | 207 | if (exponent === 128) { 208 | if (mantissa !== 0) { 209 | return NaN; 210 | } else { 211 | return sign * Infinity; 212 | } 213 | } 214 | 215 | if (exponent === -127) { // Denormalized 216 | return sign * mantissa * Math.pow(2, -126 - 23); 217 | } 218 | 219 | return sign * (1 + mantissa * Math.pow(2, -23)) * Math.pow(2, exponent); 220 | }, 221 | 222 | _getInt32: function (byteOffset, littleEndian) { 223 | var b = this._getUint32(byteOffset, littleEndian); 224 | return b > Math.pow(2, 31) - 1 ? b - Math.pow(2, 32) : b; 225 | }, 226 | 227 | _getUint32: function (byteOffset, littleEndian) { 228 | var b3 = this._getUint8(this._endianness(byteOffset, 0, 4, littleEndian)), 229 | b2 = this._getUint8(this._endianness(byteOffset, 1, 4, littleEndian)), 230 | b1 = this._getUint8(this._endianness(byteOffset, 2, 4, littleEndian)), 231 | b0 = this._getUint8(this._endianness(byteOffset, 3, 4, littleEndian)); 232 | 233 | return (b3 * Math.pow(2, 24)) + (b2 << 16) + (b1 << 8) + b0; 234 | }, 235 | 236 | _getInt16: function (byteOffset, littleEndian) { 237 | var b = this._getUint16(byteOffset, littleEndian); 238 | return b > Math.pow(2, 15) - 1 ? b - Math.pow(2, 16) : b; 239 | }, 240 | 241 | _getUint16: function (byteOffset, littleEndian) { 242 | var b1 = this._getUint8(this._endianness(byteOffset, 0, 2, littleEndian)), 243 | b0 = this._getUint8(this._endianness(byteOffset, 1, 2, littleEndian)); 244 | 245 | return (b1 << 8) + b0; 246 | }, 247 | 248 | _getInt8: function (byteOffset) { 249 | var b = this._getUint8(byteOffset); 250 | return b > Math.pow(2, 7) - 1 ? b - Math.pow(2, 8) : b; 251 | }, 252 | 253 | _getUint8: function (byteOffset) { 254 | if (this._isArrayBuffer) { 255 | return new Uint8Array(this.buffer, byteOffset, 1)[0]; 256 | } 257 | else if (this._isNodeBuffer) { 258 | return this.buffer[byteOffset]; 259 | } else { 260 | return this.buffer.charCodeAt(byteOffset) & 0xff; 261 | } 262 | } 263 | }; 264 | 265 | // Create wrappers 266 | 267 | var dataTypes = { 268 | 'Int8': 1, 269 | 'Int16': 2, 270 | 'Int32': 4, 271 | 'Uint8': 1, 272 | 'Uint16': 2, 273 | 'Uint32': 4, 274 | 'Float32': 4, 275 | 'Float64': 8 276 | }; 277 | var nodeNaming = { 278 | 'Int8': 'Int8', 279 | 'Int16': 'Int16', 280 | 'Int32': 'Int32', 281 | 'Uint8': 'UInt8', 282 | 'Uint16': 'UInt16', 283 | 'Uint32': 'UInt32', 284 | 'Float32': 'Float', 285 | 'Float64': 'Double' 286 | }; 287 | 288 | for (var type in dataTypes) { 289 | if (!dataTypes.hasOwnProperty(type)) { 290 | continue; 291 | } 292 | 293 | // Bind the variable type 294 | (function (type) { 295 | var size = dataTypes[type]; 296 | 297 | // Create the function 298 | jDataView.prototype['get' + type] = 299 | function (byteOffset, littleEndian) { 300 | var value; 301 | 302 | // Handle the lack of endianness 303 | if (littleEndian === undefined) { 304 | littleEndian = this._littleEndian; 305 | } 306 | 307 | // Handle the lack of byteOffset 308 | if (byteOffset === undefined) { 309 | byteOffset = this._offset; 310 | } 311 | 312 | // Dispatch on the good method 313 | if (this._isDataView) { 314 | // DataView: we use the direct method 315 | value = this._view['get' + type](byteOffset, littleEndian); 316 | } 317 | // ArrayBuffer: we use a typed array of size 1 if the alignment is good 318 | // ArrayBuffer does not support endianess flag (for size > 1) 319 | else if (this._isArrayBuffer && (this._start + byteOffset) % size === 0 && (size === 1 || littleEndian)) { 320 | value = new global[type + 'Array'](this.buffer, this._start + byteOffset, 1)[0]; 321 | } 322 | // NodeJS Buffer 323 | else if (this._isNodeBuffer && compatibility.NodeBufferFull) { 324 | if (littleEndian) { 325 | value = this.buffer['read' + nodeNaming[type] + 'LE'](this._start + byteOffset); 326 | } else { 327 | value = this.buffer['read' + nodeNaming[type] + 'BE'](this._start + byteOffset); 328 | } 329 | } else if (this._isNodeBuffer && compatibility.NodeBufferEndian) { 330 | value = this.buffer['read' + nodeNaming[type]](this._start + byteOffset, littleEndian); 331 | } 332 | else { 333 | // Error Checking 334 | if (typeof byteOffset !== 'number') { 335 | throw new TypeError('jDataView byteOffset is not a number'); 336 | } 337 | if (byteOffset + size > this.byteLength) { 338 | throw new Error('jDataView (byteOffset+size) value is out of bounds'); 339 | } 340 | value = this['_get' + type](this._start + byteOffset, littleEndian); 341 | } 342 | 343 | // Move the internal offset forward 344 | this._offset = byteOffset + size; 345 | 346 | return value; 347 | }; 348 | })(type); 349 | } 350 | 351 | if (typeof jQuery !== 'undefined' && jQuery.fn.jquery >= "1.6.2") { 352 | var convertResponseBodyToText = function (byteArray) { 353 | // http://jsperf.com/vbscript-binary-download/6 354 | var scrambledStr; 355 | try { 356 | scrambledStr = IEBinaryToArray_ByteStr(byteArray); 357 | } catch (e) { 358 | // http://stackoverflow.com/questions/1919972/how-do-i-access-xhr-responsebody-for-binary-data-from-javascript-in-ie 359 | // http://miskun.com/javascript/internet-explorer-and-binary-files-data-access/ 360 | var IEBinaryToArray_ByteStr_Script = 361 | "Function IEBinaryToArray_ByteStr(Binary)\r\n"+ 362 | " IEBinaryToArray_ByteStr = CStr(Binary)\r\n"+ 363 | "End Function\r\n"+ 364 | "Function IEBinaryToArray_ByteStr_Last(Binary)\r\n"+ 365 | " Dim lastIndex\r\n"+ 366 | " lastIndex = LenB(Binary)\r\n"+ 367 | " if lastIndex mod 2 Then\r\n"+ 368 | " IEBinaryToArray_ByteStr_Last = AscB( MidB( Binary, lastIndex, 1 ) )\r\n"+ 369 | " Else\r\n"+ 370 | " IEBinaryToArray_ByteStr_Last = -1\r\n"+ 371 | " End If\r\n"+ 372 | "End Function\r\n"; 373 | 374 | // http://msdn.microsoft.com/en-us/library/ms536420(v=vs.85).aspx 375 | // proprietary IE function 376 | window.execScript(IEBinaryToArray_ByteStr_Script, 'vbscript'); 377 | 378 | scrambledStr = IEBinaryToArray_ByteStr(byteArray); 379 | } 380 | 381 | var lastChr = IEBinaryToArray_ByteStr_Last(byteArray), 382 | result = "", 383 | i = 0, 384 | l = scrambledStr.length % 8, 385 | thischar; 386 | while (i < l) { 387 | thischar = scrambledStr.charCodeAt(i++); 388 | result += String.fromCharCode(thischar & 0xff, thischar >> 8); 389 | } 390 | l = scrambledStr.length 391 | while (i < l) { 392 | result += String.fromCharCode( 393 | (thischar = scrambledStr.charCodeAt(i++), thischar & 0xff), thischar >> 8, 394 | (thischar = scrambledStr.charCodeAt(i++), thischar & 0xff), thischar >> 8, 395 | (thischar = scrambledStr.charCodeAt(i++), thischar & 0xff), thischar >> 8, 396 | (thischar = scrambledStr.charCodeAt(i++), thischar & 0xff), thischar >> 8, 397 | (thischar = scrambledStr.charCodeAt(i++), thischar & 0xff), thischar >> 8, 398 | (thischar = scrambledStr.charCodeAt(i++), thischar & 0xff), thischar >> 8, 399 | (thischar = scrambledStr.charCodeAt(i++), thischar & 0xff), thischar >> 8, 400 | (thischar = scrambledStr.charCodeAt(i++), thischar & 0xff), thischar >> 8); 401 | } 402 | if (lastChr > -1) { 403 | result += String.fromCharCode(lastChr); 404 | } 405 | return result; 406 | }; 407 | 408 | jQuery.ajaxSetup({ 409 | converters: { 410 | '* dataview': function(data) { 411 | return new jDataView(data); 412 | } 413 | }, 414 | accepts: { 415 | dataview: "text/plain; charset=x-user-defined" 416 | }, 417 | responseHandler: { 418 | dataview: function (responses, options, xhr) { 419 | // Array Buffer Firefox 420 | if ('mozResponseArrayBuffer' in xhr) { 421 | responses.text = xhr.mozResponseArrayBuffer; 422 | } 423 | // Array Buffer Chrome 424 | else if ('responseType' in xhr && xhr.responseType === 'arraybuffer' && xhr.response) { 425 | responses.text = xhr.response; 426 | } 427 | // Internet Explorer (Byte array accessible through VBScript -- convert to text) 428 | else if ('responseBody' in xhr) { 429 | responses.text = convertResponseBodyToText(xhr.responseBody); 430 | } 431 | // Older Browsers 432 | else { 433 | responses.text = xhr.responseText; 434 | } 435 | } 436 | } 437 | }); 438 | 439 | jQuery.ajaxPrefilter('dataview', function(options, originalOptions, jqXHR) { 440 | // trying to set the responseType on IE 6 causes an error 441 | if (jQuery.support.ajaxResponseType) { 442 | if (!options.hasOwnProperty('xhrFields')) { 443 | options.xhrFields = {}; 444 | } 445 | options.xhrFields.responseType = 'arraybuffer'; 446 | } 447 | options.mimeType = 'text/plain; charset=x-user-defined'; 448 | }); 449 | } 450 | 451 | global.jDataView = (global.module || {}).exports = jDataView; 452 | if (typeof module !== 'undefined') { 453 | module.exports = jDataView; 454 | } 455 | 456 | })(window); 457 | -------------------------------------------------------------------------------- /js/blocks.js: -------------------------------------------------------------------------------- 1 | (function (jsc) { 2 | // BOTH 3 | 4 | // OPERATORS //////////// 5 | jsc.Scriptable.prototype.add = function (n1, n2) { 6 | return jsc.castNumber(n1) + jsc.castNumber(n2); 7 | }; 8 | 9 | // CONTROL ////////////// 10 | jsc.Scriptable.prototype.scratchBroadcast = function (message) { 11 | this.stage.addBroadcastToQueue(message.toString()); 12 | }; 13 | 14 | jsc.Scriptable.prototype.stopAllScripts = function () { 15 | this.stage.stopAll(); 16 | }; 17 | 18 | // LOOKS //////////////// 19 | jsc.Scriptable.prototype.lookLike = function (c) { 20 | var costume; 21 | 22 | var index; 23 | var cast = jsc.castNumberOrNull(c); 24 | if (cast !== null) { 25 | index = (Math.round(cast) - 1).mod(this.costumes.length); 26 | costume = this.costumes[index]; 27 | } else { 28 | var name = c.toString(); 29 | for (var i = 0; i < this.costumes.length; i++) { 30 | if (this.costumes[i].name === name) { 31 | costume = this.costumes[i]; 32 | index = i; 33 | } 34 | } 35 | } 36 | if (costume) { 37 | this.costume = costume; 38 | this.costumeIndex = index; 39 | } 40 | }; 41 | 42 | jsc.Scriptable.prototype.nextCostume = function () { 43 | this.costumeIndex = (this.costumeIndex + 1).mod(this.costumes.length); 44 | this.costume = this.costumes[this.costumeIndex]; 45 | }; 46 | 47 | jsc.Scriptable.prototype.getCostumeIndex = function () { 48 | return (this.costumeIndex).mod(this.costumes.length - 1) + 1; 49 | }; 50 | 51 | jsc.Scriptable.prototype.changeGraphicEffectby = function (effect, delta) { 52 | this.filters[effect.toString()] += jsc.castNumber(delta); 53 | }; 54 | 55 | jsc.Scriptable.prototype.setGraphicEffectto = function (effect, value) { 56 | this.filters[effect.toString()] = jsc.castNumber(value); 57 | }; 58 | 59 | jsc.Scriptable.prototype.filterReset = function () { 60 | this.filters = {}; 61 | }; 62 | 63 | // SENSING ////////////// 64 | jsc.Scriptable.prototype.mouseX = function () { 65 | return this.stage.mouse.x - this.stage.origin().x; 66 | }; 67 | 68 | jsc.Scriptable.prototype.mouseY = function () { 69 | return this.stage.origin().y - this.stage.mouse.y; 70 | }; 71 | jsc.Scriptable.prototype.mousePressed = function () { 72 | return this.stage.mouseDown; 73 | }; 74 | 75 | jsc.Scriptable.prototype.keyPressed = function (key) { 76 | var keys = { 77 | "space": 32, 78 | "up arrow": 38, 79 | "down arrow": 40, 80 | "right arrow": 39, 81 | "left arrow": 37, 82 | "up": 38, 83 | "down": 40, 84 | "right": 39, 85 | "left": 37 86 | }; 87 | var str = key.toString().toLowerCase(); 88 | if (keys[str]) { 89 | return this.stage.keys[keys[str]]; 90 | } 91 | return this.stage.keys[key.toString().toUpperCase().charCodeAt(0)]; 92 | }; 93 | 94 | jsc.Scriptable.prototype.timerReset = function () { 95 | this.stage.timer.reset(); 96 | }; 97 | 98 | jsc.Scriptable.prototype.getTimer = function () { 99 | return this.stage.timer.getElapsed() / 1000; 100 | }; 101 | 102 | jsc.Scriptable.prototype.getAttributeof = function (attribute, object) { 103 | var s = this.coerceSprite(object); 104 | if (s) { 105 | var a = attribute.toString(); 106 | if (typeof s.variables[a] !== 'undefined') { 107 | return s.variables[a].val; 108 | } 109 | return s.getAttribute(a); 110 | } 111 | return 0; 112 | }; 113 | 114 | // SOUNDS /////////////// 115 | jsc.Scriptable.prototype.playSound = function (sound) { 116 | var sound = this.getSound(sound); 117 | if (sound !== null) { 118 | sound.play(this.volume); 119 | } 120 | }; 121 | 122 | jsc.Scriptable.prototype.stopAllSounds = function () { 123 | this.stage.stopAllSounds(); 124 | }; 125 | 126 | jsc.Scriptable.prototype.setVolumeTo = function (volume) { 127 | this.volume = volume; 128 | }; 129 | 130 | // OPERATORS //////////// 131 | jsc.Scriptable.prototype.add = function (n1, n2) { 132 | return jsc.castNumber(n1) + jsc.castNumber(n2); 133 | }; 134 | 135 | jsc.Scriptable.prototype.subtract = function (n1, n2) { 136 | return jsc.castNumber(n1) - jsc.castNumber(n2); 137 | }; 138 | 139 | jsc.Scriptable.prototype.multiply = function (n1, n2) { 140 | return jsc.castNumber(n1) * jsc.castNumber(n2); 141 | }; 142 | 143 | jsc.Scriptable.prototype.divide = function (n1, n2) { 144 | return jsc.castNumber(n1) / jsc.castNumber(n2); 145 | }; 146 | 147 | jsc.Scriptable.prototype.randomFromto = function (from, to) { 148 | var n1 = jsc.castNumber(from); 149 | var n2 = jsc.castNumber(to); 150 | return Math.round(Math.random() * (n2 - n1) + n1); 151 | }; 152 | 153 | jsc.Scriptable.prototype.lessThan = function (o1, o2) { 154 | return jsc.castNumber(o1) < jsc.castNumber(o2); 155 | }; 156 | 157 | jsc.Scriptable.prototype.equals = function (o1, o2) { 158 | /*if (typeof o1 === typeof o2 === 'number') { 159 | return o1 === o2; 160 | } else if (typeof o1 === typeof o2 === 'string') { 161 | return o1.toLowerCase() === o2.toLowerCase(); 162 | } 163 | return o1.toString().toLowerCase() === o2.toString().toLowerCase();*/ 164 | return o1 == o2; 165 | }; 166 | 167 | jsc.Scriptable.prototype.greatorThan = function (o1, o2) { 168 | return jsc.castNumber(o1) > jsc.castNumber(o2); 169 | }; 170 | 171 | jsc.Scriptable.prototype.and = function (b1, b2) { 172 | return b1 && b2; 173 | }; 174 | 175 | jsc.Scriptable.prototype.or = function (b1, b2) { 176 | return b1 || b2; 177 | }; 178 | 179 | jsc.Scriptable.prototype.not = function (b) { 180 | return !b; 181 | }; 182 | 183 | jsc.Scriptable.prototype.concatenatewith = function (s1, s2) { 184 | return s1.toString() + s2.toString(); 185 | }; 186 | 187 | jsc.Scriptable.prototype.letterof = function (i, string) { 188 | return string.toString()[(jsc.castNumber(i)) - 1] || ''; 189 | }; 190 | 191 | jsc.Scriptable.prototype.stringLength = function (string) { 192 | return string.toString().length; 193 | }; 194 | 195 | jsc.Scriptable.prototype.mod = function (n1, n2) { 196 | return jsc.castNumber(n1).mod(jsc.castNumber(n2)); 197 | }; 198 | 199 | jsc.Scriptable.prototype.rounded = function (n) { 200 | return Math.round(jsc.castNumber(n)); 201 | }; 202 | 203 | jsc.Scriptable.prototype.computeFunctionof = function (f, n) { 204 | var n = jsc.castNumber(n); 205 | switch (f.toString().toLowerCase()) { 206 | case 'abs': return Math.abs(n); 207 | case 'sqrt': return Math.sqrt(n); 208 | case 'sin': return Math.sin(Math.PI/180 * n); 209 | case 'cos': return Math.cos(Math.PI/180 * n); 210 | case 'tan': return Math.tan(Math.PI/180 * n); 211 | case 'asin': return 180/Math.PI * Math.asin(Math.max(-1, Math.min(1, n))); 212 | case 'acos': return 180/Math.PI * Math.acos(Math.max(-1, Math.min(1, n))); 213 | case 'atan': return 180/Math.PI * Math.atan(n); 214 | case 'ln': return Math.log(n); 215 | case 'log': return Math.log(n); 216 | case 'e ^': return Math.pow(Math.E, n); 217 | case '10 ^': return Math.pow(10, n); 218 | } 219 | return 0; 220 | }; 221 | 222 | // PEN ////////////////// 223 | jsc.Scriptable.prototype.clearPenTrails = function () { 224 | this.stage.penCtx.stroke(); 225 | this.stage.penCtx.clearRect(0, 0, this.stage.penCanvas.width, this.stage.penCanvas.height); 226 | }; 227 | 228 | // VARIABLES //////////// 229 | jsc.Scriptable.prototype.getVariable = function (name) { 230 | return this.allVariables[name].val; 231 | }; 232 | 233 | jsc.Scriptable.prototype.changeVariable = function (name, relative, value) { 234 | if (relative) { 235 | this.allVariables[name].val = jsc.castNumber(this.allVariables[name].val) + jsc.castNumber(value); 236 | } else { 237 | this.allVariables[name].val = value; 238 | } 239 | }; 240 | 241 | jsc.Scriptable.prototype.hideVariable = function (variable) { 242 | var children = this.stage.children; 243 | var child; 244 | for (var i = 0; i < children.length; i++) { 245 | child = children[i]; 246 | if (child instanceof jsc.Watcher && child.command === 'getVariable' && child.arg === variable) { 247 | child.hidden = true; 248 | } 249 | } 250 | }; 251 | 252 | jsc.Scriptable.prototype.showVariable = function (variable) { 253 | var children = this.stage.children; 254 | var child; 255 | for (var i = 0; i < children.length; i++) { 256 | child = children[i]; 257 | if (child instanceof jsc.Watcher && child.command === 'getVariable' && child.arg === variable) { 258 | child.hidden = false; 259 | } 260 | } 261 | }; 262 | 263 | // LISTS //////////////// 264 | jsc.Scriptable.prototype.contentsOfList = function (list) { 265 | var list = this.getList(list.toString()); 266 | if (!list) { 267 | return; 268 | } 269 | 270 | var string = ''; 271 | 272 | var space = ''; 273 | 274 | for (var i = 0; i < list.length; i++) { 275 | if (list[i].length > 1) { 276 | space = ' '; 277 | break; 278 | } 279 | } 280 | 281 | return list.join(space); 282 | }; 283 | 284 | jsc.Scriptable.prototype.appendtoList = function (item, list) { 285 | var list = this.getList(list.toString()); 286 | if (!list) { 287 | return; 288 | } 289 | return list.push(item); 290 | }; 291 | 292 | jsc.Scriptable.prototype.deleteLineofList = function (line, list) { 293 | var list = this.getList(list.toString()); 294 | if (!list) { 295 | return; 296 | } 297 | var i = -1; 298 | if (line === 'last') { 299 | i = list.length - 1; 300 | } else if (line === 'all') { 301 | list.splice(0, list.length); 302 | return; 303 | } else { 304 | i = Math.round(jsc.castNumber(line)) - 1; 305 | } 306 | if (i && i !== -1) { 307 | list.splice(i, 1); 308 | } 309 | }; 310 | jsc.Scriptable.prototype.insertatofList = function (item, line, list) { 311 | var list = this.getList(list.toString()); 312 | if (!list) { 313 | return; 314 | } 315 | if (line === 'last') { 316 | list.push(item); 317 | return; 318 | } else if (line === 'any') { 319 | list.splice(Math.floor(Math.random() * list.length), 0, item); 320 | return; 321 | } 322 | var i = Math.round(jsc.castNumber(line) - 1); 323 | if (i > 0) { 324 | if (i < list.length) { 325 | list.splice(i, 0, item); 326 | } else if (i === list.length) { 327 | list.push(item); 328 | } 329 | } 330 | }; 331 | 332 | jsc.Scriptable.prototype.setLineofListto = function (line, list, item) { 333 | var list = this.getList(list.toString()); 334 | if (!list) { 335 | return; 336 | } 337 | return list[this.toListLine(line, list)] = item; 338 | }; 339 | 340 | jsc.Scriptable.prototype.getLineofList = function (line, list) { 341 | var list = this.getList(list.toString()); 342 | if (!list) { 343 | return 0; 344 | } 345 | return list[this.toListLine(line, list)] || 0; 346 | }; 347 | 348 | jsc.Scriptable.prototype.lineCountOfList = function (list) { 349 | var list = this.getList(list.toString()); 350 | if (!list) { 351 | return 0; 352 | } 353 | return list.length; 354 | }; 355 | 356 | jsc.Scriptable.prototype.listcontains = function (list, item) { 357 | var list = this.getList(list.toString()); 358 | if (!list) { 359 | return false; 360 | } 361 | if (list.indexOf(item) === -1) { 362 | if (list.indexOf(item.toString()) === -1) { 363 | return false; 364 | } 365 | } 366 | return true; 367 | }; 368 | 369 | 370 | // STAGE ////////////////////////////////////////////// 371 | 372 | 373 | 374 | 375 | // SPRITES //////////////////////////////////////////// 376 | 377 | // MOTION /////////////// 378 | jsc.Sprite.prototype.forward = function (dist) { 379 | var rad = Math.PI/180 * (this.direction + 90); 380 | var v = jsc.castNumber(dist); 381 | this.setRelativePosition(this.getRelativePosition().add(new jsc.Point(Math.sin(rad) * v, Math.cos(rad) * v))); 382 | }; 383 | 384 | jsc.Sprite.prototype.setHeading = function (heading) { 385 | this.direction = (jsc.castNumber(heading)) - 90; 386 | this.boundingChanged = true; 387 | }; 388 | 389 | jsc.Sprite.prototype.pointTowards = function (object) { 390 | var coords; 391 | if (object.toString() === 'mouse') { 392 | coords = this.stage.mouse; 393 | } else { 394 | var s = this.coerceSprite(object); 395 | if (!s) { 396 | return; 397 | } 398 | coords = s.position; 399 | } 400 | var p = this.position.subtract(coords); 401 | this.direction = Math.atan2(p.x, -p.y) * 180/Math.PI + 90; 402 | this.boundingChanged = true; 403 | }; 404 | 405 | jsc.Sprite.prototype.turnRight = function (angle) { 406 | this.direction += (jsc.castNumber(angle)); 407 | this.boundingChanged = true; 408 | }; 409 | 410 | jsc.Sprite.prototype.turnLeft = function (angle) { 411 | this.direction -= (jsc.castNumber(angle)); 412 | this.boundingChanged = true; 413 | }; 414 | 415 | jsc.Sprite.prototype.gotoXy = function (x, y) { 416 | this.setRelativePosition(new jsc.Point(jsc.castNumber(x), jsc.castNumber(y))); 417 | }; 418 | 419 | jsc.Sprite.prototype.gotoSpriteOrMouse = function (object) { 420 | var stage = this.stage; 421 | if (object === null) { 422 | return; 423 | } 424 | if (object.toString() === 'mouse') { 425 | this.setRelativePosition(stage.toScratchCoords(stage.mouse)); 426 | return; 427 | } 428 | var sprite = this.coerceSprite(object); 429 | if (!sprite) { 430 | return; 431 | } 432 | this.setRelativePosition(sprite.getRelativePosition()); 433 | }; 434 | 435 | jsc.Sprite.prototype.changeXposBy = function (delta) { 436 | this.setRelativePosition(new jsc.Point(this.getRelativePosition().x + (jsc.castNumber(delta)), this.getRelativePosition().y)); 437 | }; 438 | 439 | jsc.Sprite.prototype.setXPos = function (x) { 440 | this.setRelativePosition(new jsc.Point(jsc.castNumber(x), this.getRelativePosition().y)); 441 | }; 442 | 443 | jsc.Sprite.prototype.changeYposBy = function (delta) { 444 | this.setRelativePosition(new jsc.Point(this.getRelativePosition().x, this.getRelativePosition().y + (jsc.castNumber(delta)))); 445 | }; 446 | 447 | jsc.Sprite.prototype.setYPos = function (y) { 448 | return this.setRelativePosition(new jsc.Point(this.getRelativePosition().x, jsc.castNumber(y))); 449 | }; 450 | 451 | jsc.Sprite.prototype.bounceOffEdge = function () { 452 | var tb = this.getBoundingBox(); 453 | var sb = this.stage.bounds; 454 | 455 | tb.origin.x = Math.ceil(tb.origin.x); 456 | tb.origin.y = Math.ceil(tb.origin.y); 457 | tb.corner.x = Math.floor(tb.corner.x); 458 | tb.corner.y = Math.floor(tb.corner.y); 459 | 460 | if (sb.containsRectangle(tb)) { 461 | return; 462 | } 463 | 464 | var rad = Math.PI/180 * (this.direction + 90); 465 | var cos = Math.cos(rad); 466 | var sin = -Math.sin(rad); 467 | 468 | var dx = 0, dy = 0; 469 | 470 | if (tb.right() > sb.right()) { 471 | dx = sb.right() - tb.right(); 472 | cos = -Math.abs(cos); 473 | } 474 | if (tb.bottom() > sb.bottom()) { 475 | dy = sb.bottom() - tb.bottom(); 476 | sin = -cos; 477 | } 478 | if ((tb.left() + dx) < sb.left()) { 479 | dx = sb.left() - tb.left(); 480 | cos = Math.abs(cos); 481 | } 482 | if ((tb.top() + dy) < sb.top()) { 483 | dy = sb.top() - tb.top(); 484 | sin = -Math.abs(sin); 485 | } 486 | 487 | this.direction = 180/Math.PI * Math.atan2(cos, sin) - 90; 488 | 489 | this.setPosition(this.position.add(new jsc.Point(dx, dy))); 490 | }; 491 | 492 | jsc.Sprite.prototype.getXPos = function () { 493 | return this.getRelativePosition().x; 494 | }; 495 | 496 | jsc.Sprite.prototype.getYPos = function () { 497 | return this.getRelativePosition().y; 498 | }; 499 | 500 | jsc.Sprite.prototype.getHeading = function () { 501 | return (this.direction + 90 + 179).mod(360) - 179; 502 | }; 503 | 504 | // LOOKS //////////////// 505 | jsc.Sprite.prototype.say = function (string) { 506 | console.log(string); 507 | }; 508 | 509 | jsc.Sprite.prototype.changeSizeBy = function (delta) { 510 | var size = ((jsc.castNumber(delta)) / 100 + Math.max(this.scalePoint.x, this.scalePoint.y)); 511 | this.scalePoint = new jsc.Point(size, size); 512 | this.boundingChanged = true; 513 | }; 514 | 515 | jsc.Sprite.prototype.setSizeTo = function (size) { 516 | size = (jsc.castNumber(size)) / 100; 517 | this.scalePoint = new jsc.Point(size, size); 518 | this.boundingChanged = true; 519 | }; 520 | 521 | jsc.Sprite.prototype.changeStretchBy = function (delta) { 522 | this.scalePoint.x += (jsc.castNumber(delta)) / 100; 523 | this.boundingChanged = true; 524 | }; 525 | 526 | jsc.Sprite.prototype.setStretchTo = function (stretch) { 527 | this.scalePoint.x = (jsc.castNumber(stretch)) / 100 * this.scalePoint.y; 528 | this.boundingChanged = true; 529 | }; 530 | 531 | jsc.Sprite.prototype.scale = function () { 532 | return Math.round(100 * this.scalePoint.x); 533 | }; 534 | 535 | jsc.Sprite.prototype.show = function () { 536 | this.hidden = false; 537 | }; 538 | 539 | jsc.Sprite.prototype.hide = function () { 540 | this.hidden = true; 541 | }; 542 | 543 | jsc.Sprite.prototype.comeToFront = function () { 544 | var children = this.stage.children; 545 | children.unshift(children.splice(children.indexOf(this), 1)[0]); 546 | }; 547 | 548 | jsc.Sprite.prototype.goBackByLayers = function (layers) { 549 | var children = this.stage.children; 550 | var i = children.indexOf(this); 551 | var layer = Math.min(i + Math.round(jsc.castNumber(layers)), children.length - 1); 552 | children.splice(i, 1) 553 | children.splice(layer, 0, this); 554 | }; 555 | 556 | // SENSING ////////////// 557 | jsc.Sprite.prototype.touching = function (object) { 558 | return this.isTouching(object); 559 | }; 560 | 561 | jsc.Sprite.prototype.touchingColor = function (color) { 562 | return this.isTouchingColor(color); 563 | }; 564 | 565 | jsc.Sprite.prototype.colorsees = function (color1, color2) { 566 | return this.isColorTouchingColor(color1, color2); 567 | }; 568 | 569 | jsc.Sprite.prototype.distanceTo = function (object) { 570 | var coords; 571 | if (object.toString() === 'mouse') { 572 | coords = this.stage.mouse; 573 | } else { 574 | var s = this.coerceSprite(object); 575 | if (!s) { 576 | return 10000; 577 | } 578 | coords = s.position; 579 | } 580 | return this.position.distanceTo(coords); 581 | }; 582 | 583 | // PEN ////////////////// 584 | jsc.Sprite.prototype.putPenDown = function () { 585 | this.hasMoved = false; 586 | 587 | this.penDown = true; 588 | }; 589 | 590 | jsc.Sprite.prototype.putPenUp = function () { 591 | if (!this.hasMoved) { 592 | var ctx = this.stage.penCtx; 593 | ctx.fillStyle = this.pen.color.toString(); 594 | if (this.pen.size === 1) { 595 | ctx.fillRect(this.position.x, this.position.y, 1, 1); 596 | } else { 597 | ctx.beginPath(); 598 | ctx.arc(this.position.x, this.position.y, this.pen.size / 2, 0, 2 * Math.PI, false); 599 | ctx.fill(); 600 | } 601 | } 602 | this.hasMoved = false; 603 | this.penDown = false; 604 | }; 605 | 606 | jsc.Sprite.prototype.penColor = function (color) { 607 | this.pen.color = color; 608 | this.pen.hsl = this.pen.color.getHSL(); 609 | this.updatePen(); 610 | }; 611 | 612 | jsc.Sprite.prototype.changePenHueBy = function (delta) { 613 | this.pen.hsl[0] += delta / 200; 614 | this.updatePen(); 615 | }; 616 | 617 | jsc.Sprite.prototype.setPenHueTo = function (hue) { 618 | this.pen.hsl[0] = hue / 200; 619 | this.updatePen(); 620 | }; 621 | 622 | jsc.Sprite.prototype.changePenShadeBy = function (delta) { 623 | this.pen.hsl[2] += delta / 100; 624 | this.updatePen(); 625 | }; 626 | 627 | jsc.Sprite.prototype.setPenShadeTo = function (shade) { 628 | this.pen.hsl[2] = shade / 100; 629 | this.updatePen(); 630 | }; 631 | 632 | jsc.Sprite.prototype.changePenSizeBy = function (delta) { 633 | var n = Math.max(this.pen.size + jsc.castNumber(delta), 1); 634 | if (this.pen.size !== n) { 635 | this.pen.size = n; 636 | this.updatePen(); 637 | } 638 | }; 639 | 640 | jsc.Sprite.prototype.penSize = function (size) { 641 | var n = Math.max(jsc.castNumber(size), 1); 642 | if (this.pen.size !== n) { 643 | this.pen.size = n; 644 | this.updatePen(); 645 | } 646 | }; 647 | 648 | jsc.Sprite.prototype.stampCostume = function () { 649 | var h = this.hidden; 650 | this.hidden = false; 651 | var g = this.filters.ghost; 652 | this.filters.ghost = 0; 653 | this.drawOn(this.stage.penCtx); 654 | this.filters.ghost = g; 655 | this.hidden = h; 656 | }; 657 | }) (jsc); 658 | -------------------------------------------------------------------------------- /js/datatypes.js: -------------------------------------------------------------------------------- 1 | var jsc = new (function JsScratch() {}); 2 | 3 | (function (jsc) { 4 | // Form /////////////////////////////////////////////////// 5 | jsc.Form = function (width, height, depth, offset, bits, colors) { 6 | this.width = width; 7 | this.height = height; 8 | this.depth = depth; 9 | this.offset = offset; 10 | //this.bits = bits; 11 | if (colors) { 12 | this.colors = colors; 13 | } 14 | }; 15 | 16 | jsc.Form.prototype.getImage = function () { 17 | var canvas = jsc.newCanvas(this.width, this.height); 18 | var ctx = canvas.getContext('2d'); 19 | 20 | this.data = this.bits.isBitmap ? this.bits : this.decodePixels(); 21 | 22 | var data = ctx.createImageData(this.width, this.height); 23 | this.setImageData(data); 24 | ctx.putImageData(data, 0, 0); 25 | return canvas; 26 | }; 27 | 28 | jsc.Form.prototype.extent = function () { 29 | return new jsc.Point(this.width, this.height); 30 | }; 31 | 32 | jsc.Form.prototype.decodePixels = function () { 33 | var stream = new jDataView(jDataView.createBuffer(this.bits), undefined, undefined, false); 34 | var i = this.decodeInt(stream); 35 | var bitmap = []; 36 | var j = 0; 37 | while ((stream.tell() <= stream.byteLength - 1) && (j < i)) 38 | { 39 | var k = this.decodeInt(stream); 40 | var l = k >> 2; 41 | var i1 = k & 3; 42 | switch(i1) 43 | { 44 | case 0: 45 | j++; 46 | break; 47 | case 1: 48 | var j1 = stream.getUint8(); 49 | var k1 = (j1 * 16777216) + (j1 << 16) + (j1 << 8) + (j1); 50 | for (var j2 = 0; j2 < l; j2++) 51 | bitmap[j++] = k1; 52 | break; 53 | case 2: 54 | var l1 = stream.getUint32(); 55 | for (var k2 = 0; k2 < l; k2++) 56 | bitmap[j++] = l1; 57 | break; 58 | case 3: 59 | for (var l2 = 0; l2 < l; l2++) 60 | bitmap[j++] = stream.getUint32(); 61 | break; 62 | } 63 | } 64 | return bitmap; 65 | } 66 | 67 | jsc.Form.prototype.decodeInt = function (stream) { 68 | var i = stream.getUint8(); 69 | if (i <= 223) 70 | return i; 71 | if (i <= 254) 72 | return (i - 224) * 256 + stream.getUint8(); 73 | return stream.getUint32(); 74 | }; 75 | 76 | jsc.Form.prototype.setImageData = function (data) { 77 | var array = data.data; 78 | if (this.depth <= 8) { 79 | var colors = this.colors || jsc.squeakColors; 80 | var l = this.data.length / this.height; 81 | var i1 = (1 << this.depth) - 1; 82 | var j1 = 32 / this.depth; 83 | for(var y = 0; y < this.height; y++) { 84 | for(var x = 0; x < this.width; x++) { 85 | var i2 = this.data[y * l + (x - (x % j1)) / j1]; 86 | var j2 = this.depth * (j1 - x % j1 - 1); 87 | var pi = (y * this.width + x) * 4; 88 | var ci = (i2 / Math.pow(2, j2)) & i1; 89 | var c = colors[ci]; 90 | array[pi] = c.r; 91 | array[pi + 1] = c.g; 92 | array[pi + 2] = c.b; 93 | array[pi + 3] = c.a == 0 ? 0 : 255; 94 | } 95 | } 96 | } 97 | if (this.depth == 16) { 98 | var data = []; 99 | var hw = Math.round((this.width) / 2); 100 | var index = 0, i, j; 101 | for (var y = 0; y < this.height; y++) { 102 | i = 0; 103 | for (var x = 0; x < this.width; x++) { 104 | j = this.data[y * hw + Math.round(x / 2)] >> i & 0xFFFF; 105 | //index = (x + y * this.width) * 4; 106 | array[index++] = (j >> 10 & 0x1F) << 3; 107 | array[index++] = (j >> 5 & 0x1F) << 3; 108 | array[index++] = (j & 0x1F) << 3; 109 | array[index++] = 0xFF; 110 | i = i === 16 ? 0 : 16; 111 | } 112 | } 113 | this.data = data; 114 | } 115 | if (this.depth == 32) { 116 | for (var i = 0; i < array.length; i += 4) { 117 | var c = this.data[i / 4]; 118 | array[i] = (c >> 16) & 0xFF; 119 | array[i + 1] = (c >> 8) & 0xFF; 120 | array[i + 2] = c & 0xFF; 121 | array[i + 3] = c === 0 ? 0 : 255; 122 | } 123 | } 124 | } 125 | 126 | // Color ////////////////////////////////////////////////// 127 | jsc.Color = function (r, g, b, a) { 128 | this.r = r || 0; 129 | this.g = g || 0; 130 | this.b = b || 0; 131 | this.a = (a === 0) ? 0 : a || 255; 132 | }; 133 | 134 | jsc.Color.prototype.toString = function () { 135 | return 'rgba(' + (this.r | 0) + ',' + (this.g | 0) + ',' + (this.b | 0) + ',' + (this.a | 0) + ')'; 136 | }; 137 | 138 | jsc.Color.prototype.getHSL = function () { 139 | var r = this.r / 255; 140 | var g = this.g / 255; 141 | var b = this.b / 255; 142 | 143 | var max = Math.max(r, g, b), min = Math.min(r, g, b); 144 | var h, s, l = (max + min) / 2; 145 | 146 | if(max == min){ 147 | h = s = 0; 148 | }else{ 149 | var d = max - min; 150 | s = l > 0.5 ? d / (2 - max - min) : d / (max + min); 151 | switch(max){ 152 | case r: h = (g - b) / d + (g < b ? 6 : 0); break; 153 | case g: h = (b - r) / d + 2; break; 154 | case b: h = (r - g) / d + 4; break; 155 | } 156 | h /= 6; 157 | } 158 | 159 | return [h, s, l]; 160 | }; 161 | 162 | jsc.Color.prototype.setHSL = function (hsl) { 163 | var h = hsl[0]; 164 | var s = hsl[1]; 165 | var l = hsl[2]; 166 | 167 | var r, g, b; 168 | 169 | if (s == 0) { 170 | r = g = b = l; 171 | } else { 172 | function hue2rgb(p, q, t) { 173 | if (t < 0) t += 1; 174 | if (t > 1) t -= 1; 175 | if (t < 1/6) return p + (q - p) * 6 * t; 176 | if (t < 1/2) return q; 177 | if (t < 2/3) return p + (q - p) * (2/3 - t) * 6; 178 | return p; 179 | } 180 | 181 | var q = l < 0.5 ? l * (1 + s) : l + s - l * s; 182 | var p = 2 * l - q; 183 | r = hue2rgb(p, q, h + 1 / 3); 184 | g = hue2rgb(p, q, h); 185 | b = hue2rgb(p, q, h - 1 / 3); 186 | } 187 | 188 | this.r = r * 255; 189 | this.g = g * 255; 190 | this.b = b * 255; 191 | }; 192 | 193 | jsc.radians = function (degrees) { 194 | return degrees / (Math.PI * 180); 195 | }; 196 | 197 | jsc.degrees = function (radians) { 198 | return radians * Math.PI * 180; 199 | }; 200 | 201 | // jsc.Point ////////////////////////////////////////////////// 202 | jsc.Point = function (x, y) { 203 | this.x = x || 0; 204 | this.y = y || 0; 205 | }; 206 | 207 | jsc.Point.prototype.copy = function () { 208 | return new jsc.Point(this.x, this.y); 209 | }; 210 | 211 | jsc.Point.prototype.eq = function (aPoint) { 212 | return this.x === aPoint.x && this.y === aPoint.y; 213 | }; 214 | 215 | jsc.Point.prototype.lt = function (aPoint) { 216 | return this.x < aPoint.x && this.y < aPoint.y; 217 | }; 218 | 219 | jsc.Point.prototype.gt = function (aPoint) { 220 | return this.x > aPoint.x && this.y > aPoint.y; 221 | }; 222 | 223 | jsc.Point.prototype.ge = function (aPoint) { 224 | return this.x >= aPoint.x && this.y >= aPoint.y; 225 | }; 226 | 227 | jsc.Point.prototype.le = function (aPoint) { 228 | return this.x <= aPoint.x && this.y <= aPoint.y; 229 | }; 230 | 231 | jsc.Point.prototype.max = function (aPoint) { 232 | return new jsc.Point(Math.max(this.x, aPoint.x), 233 | Math.max(this.y, aPoint.y)); 234 | }; 235 | 236 | jsc.Point.prototype.min = function (aPoint) { 237 | return new jsc.Point(Math.min(this.x, aPoint.x), 238 | Math.min(this.y, aPoint.y)); 239 | }; 240 | 241 | jsc.Point.prototype.round = function () { 242 | return new jsc.Point(Math.round(this.x), Math.round(this.y)); 243 | }; 244 | 245 | jsc.Point.prototype.abs = function () { 246 | return new jsc.Point(Math.abs(this.x), Math.abs(this.y)); 247 | }; 248 | 249 | jsc.Point.prototype.neg = function () { 250 | return new jsc.Point(-this.x, -this.y); 251 | }; 252 | 253 | jsc.Point.prototype.mirror = function () { 254 | return new jsc.Point(this.y, this.x); 255 | }; 256 | 257 | jsc.Point.prototype.floor = function () { 258 | return new jsc.Point( 259 | Math.max(Math.floor(this.x), 0), 260 | Math.max(Math.floor(this.y), 0) 261 | ); 262 | }; 263 | 264 | jsc.Point.prototype.ceil = function () { 265 | return new jsc.Point(Math.ceil(this.x), Math.ceil(this.y)); 266 | }; 267 | 268 | jsc.Point.prototype.add = function (other) { 269 | if (other instanceof jsc.Point) { 270 | return new jsc.Point(this.x + other.x, this.y + other.y); 271 | } 272 | return new jsc.Point(this.x + other, this.y + other); 273 | }; 274 | 275 | jsc.Point.prototype.subtract = function (other) { 276 | if (other instanceof jsc.Point) { 277 | return new jsc.Point(this.x - other.x, this.y - other.y); 278 | } 279 | return new jsc.Point(this.x - other, this.y - other); 280 | }; 281 | 282 | jsc.Point.prototype.multiplyBy = function (other) { 283 | if (other instanceof jsc.Point) { 284 | return new jsc.Point(this.x * other.x, this.y * other.y); 285 | } 286 | return new jsc.Point(this.x * other, this.y * other); 287 | }; 288 | 289 | jsc.Point.prototype.divideBy = function (other) { 290 | if (other instanceof jsc.Point) { 291 | return new jsc.Point(this.x / other.x, this.y / other.y); 292 | } 293 | return new jsc.Point(this.x / other, this.y / other); 294 | }; 295 | 296 | jsc.Point.prototype.floorDivideBy = function (other) { 297 | if (other instanceof jsc.Point) { 298 | return new jsc.Point(Math.floor(this.x / other.x), 299 | Math.floor(this.y / other.y)); 300 | } 301 | return new jsc.Point(Math.floor(this.x / other), 302 | Math.floor(this.y / other)); 303 | }; 304 | 305 | jsc.Point.prototype.r = function () { 306 | var t = (this.multiplyBy(this)); 307 | return Math.sqrt(t.x + t.y); 308 | }; 309 | 310 | jsc.Point.prototype.degrees = function () { 311 | /* 312 | answer the angle I make with origin in degrees. 313 | Right is 0, down is 90 314 | */ 315 | var tan, theta; 316 | 317 | if (this.x === 0) { 318 | if (this.y >= 0) { 319 | return 90; 320 | } 321 | return 270; 322 | } 323 | tan = this.y / this.x; 324 | theta = Math.atan(tan); 325 | if (this.x >= 0) { 326 | if (this.y >= 0) { 327 | return degrees(theta); 328 | } 329 | return 360 + (degrees(theta)); 330 | } 331 | return 180 + degrees(theta); 332 | }; 333 | 334 | jsc.Point.prototype.theta = function () { 335 | /* 336 | answer the angle I make with origin in radians. 337 | Right is 0, down is 90 338 | */ 339 | var tan, theta; 340 | 341 | if (this.x === 0) { 342 | if (this.y >= 0) { 343 | return radians(90); 344 | } 345 | return radians(270); 346 | } 347 | tan = this.y / this.x; 348 | theta = Math.atan(tan); 349 | if (this.x >= 0) { 350 | if (this.y >= 0) { 351 | return theta; 352 | } 353 | return radians(360) + theta; 354 | } 355 | return radians(180) + theta; 356 | }; 357 | 358 | // jsc.Point functions: 359 | 360 | jsc.Point.prototype.crossProduct = function (aPoint) { 361 | return this.multiplyBy(aPoint.mirror()); 362 | }; 363 | 364 | jsc.Point.prototype.distanceTo = function (aPoint) { 365 | return (aPoint.subtract(this)).r(); 366 | }; 367 | 368 | jsc.Point.prototype.rotate = function (direction, center) { 369 | // direction must be 'right', 'left' or 'pi' 370 | var offset = this.subtract(center); 371 | if (direction === 'right') { 372 | return new jsc.Point(-offset.y, offset.y).add(center); 373 | } 374 | if (direction === 'left') { 375 | return new jsc.Point(offset.y, -offset.y).add(center); 376 | } 377 | // direction === 'pi' 378 | return center.subtract(offset); 379 | }; 380 | 381 | jsc.Point.prototype.flip = function (direction, center) { 382 | // direction must be 'vertical' or 'horizontal' 383 | if (direction === 'vertical') { 384 | return new jsc.Point(this.x, center.y * 2 - this.y); 385 | } 386 | // direction === 'horizontal' 387 | return new jsc.Point(center.x * 2 - this.x, this.y); 388 | }; 389 | 390 | jsc.Point.prototype.distanceAngle = function (dist, angle) { 391 | var deg = angle, x, y; 392 | if (deg > 270) { 393 | deg = deg - 360; 394 | } else if (deg < -270) { 395 | deg = deg + 360; 396 | } 397 | if (-90 <= deg && deg <= 90) { 398 | x = Math.sin(radians(deg)) * dist; 399 | y = Math.sqrt((dist * dist) - (x * x)); 400 | return new jsc.Point(x + this.x, this.y - y); 401 | } 402 | x = Math.sin(radians(180 - deg)) * dist; 403 | y = Math.sqrt((dist * dist) - (x * x)); 404 | return new jsc.Point(x + this.x, this.y + y); 405 | }; 406 | 407 | // jsc.Point transforming: 408 | 409 | jsc.Point.prototype.scaleBy = function (scalePoint) { 410 | return this.multiplyBy(scalePoint); 411 | }; 412 | 413 | jsc.Point.prototype.translateBy = function (deltaPoint) { 414 | return this.add(deltaPoint); 415 | }; 416 | 417 | jsc.Point.prototype.rotateBy = function (angle) { 418 | return new jsc.Point(this.x * Math.cos(angle) - this.y * Math.sin(angle), this.x * Math.sin(angle) + this.y * Math.cos(angle)); 419 | }; 420 | 421 | // jsc.Point conversion: 422 | 423 | jsc.Point.prototype.asArray = function () { 424 | return [this.x, this.y]; 425 | }; 426 | 427 | // Rectangle ////////////////////////////////////////////// 428 | jsc.Rectangle = function (left, top, right, bottom) { 429 | this.init(new jsc.Point((left || 0), (top || 0)), 430 | new jsc.Point((right || 0), (bottom || 0))); 431 | } 432 | 433 | jsc.Rectangle.prototype.init = function (originPoint, cornerPoint) { 434 | this.origin = originPoint; 435 | this.corner = cornerPoint; 436 | }; 437 | 438 | // Rectangle string representation: e.g. '[0@0 | 160@80]' 439 | 440 | jsc.Rectangle.prototype.toString = function () { 441 | return '[' + this.origin.toString() + ' | ' + 442 | this.extent().toString() + ']'; 443 | }; 444 | 445 | // Rectangle copying: 446 | 447 | jsc.Rectangle.prototype.copy = function () { 448 | return new jsc.Rectangle( 449 | this.left(), 450 | this.top(), 451 | this.right(), 452 | this.bottom() 453 | ); 454 | }; 455 | 456 | // creating Rectangle instances from jsc.Points: 457 | 458 | jsc.Point.prototype.corner = function (cornerPoint) { 459 | // answer a new jsc.Rectangle 460 | return new jsc.Rectangle( 461 | this.x, 462 | this.y, 463 | cornerPoint.x, 464 | cornerPoint.y 465 | ); 466 | }; 467 | 468 | jsc.Point.prototype.rectangle = function (aPoint) { 469 | // answer a new jsc.Rectangle 470 | var org, crn; 471 | org = this.min(aPoint); 472 | crn = this.max(aPoint); 473 | return new jsc.Rectangle(org.x, org.y, crn.x, crn.y); 474 | }; 475 | 476 | jsc.Point.prototype.extent = function (aPoint) { 477 | //answer a new jsc.Rectangle 478 | var crn = this.add(aPoint); 479 | return new jsc.Rectangle(this.x, this.y, crn.x, crn.y); 480 | }; 481 | 482 | // Rectangle accessing - setting: 483 | 484 | jsc.Rectangle.prototype.setTo = function (left, top, right, bottom) { 485 | // note: all inputs are optional and can be omitted 486 | 487 | this.origin = new jsc.Point( 488 | left || ((left === 0) ? 0 : this.left()), 489 | top || ((top === 0) ? 0 : this.top()) 490 | ); 491 | 492 | this.corner = new jsc.Point( 493 | right || ((right === 0) ? 0 : this.right()), 494 | bottom || ((bottom === 0) ? 0 : this.bottom()) 495 | ); 496 | }; 497 | 498 | // Rectangle accessing - getting: 499 | 500 | jsc.Rectangle.prototype.area = function () { 501 | //requires width() and height() to be defined 502 | var w = this.width(); 503 | if (w < 0) { 504 | return 0; 505 | } 506 | return Math.max(w * this.height(), 0); 507 | }; 508 | 509 | jsc.Rectangle.prototype.bottom = function () { 510 | return this.corner.y; 511 | }; 512 | 513 | jsc.Rectangle.prototype.bottomCenter = function () { 514 | return new jsc.Point(this.center().x, this.bottom()); 515 | }; 516 | 517 | jsc.Rectangle.prototype.bottomLeft = function () { 518 | return new jsc.Point(this.origin.x, this.corner.y); 519 | }; 520 | 521 | jsc.Rectangle.prototype.bottomRight = function () { 522 | return this.corner.copy(); 523 | }; 524 | 525 | jsc.Rectangle.prototype.boundingBox = function () { 526 | return this; 527 | }; 528 | 529 | jsc.Rectangle.prototype.center = function () { 530 | return this.origin.add( 531 | this.corner.subtract(this.origin).floorDivideBy(2) 532 | ); 533 | }; 534 | 535 | jsc.Rectangle.prototype.corners = function () { 536 | return [this.origin, 537 | this.bottomLeft(), 538 | this.corner, 539 | this.topRight()]; 540 | }; 541 | 542 | jsc.Rectangle.prototype.extent = function () { 543 | return this.corner.subtract(this.origin); 544 | }; 545 | 546 | jsc.Rectangle.prototype.height = function () { 547 | return this.corner.y - this.origin.y; 548 | }; 549 | 550 | jsc.Rectangle.prototype.left = function () { 551 | return this.origin.x; 552 | }; 553 | 554 | jsc.Rectangle.prototype.leftCenter = function () { 555 | return new jsc.Point(this.left(), this.center().y); 556 | }; 557 | 558 | jsc.Rectangle.prototype.right = function () { 559 | return this.corner.x; 560 | }; 561 | 562 | jsc.Rectangle.prototype.rightCenter = function () { 563 | return new jsc.Point(this.right(), this.center().y); 564 | }; 565 | 566 | jsc.Rectangle.prototype.top = function () { 567 | return this.origin.y; 568 | }; 569 | 570 | jsc.Rectangle.prototype.topCenter = function () { 571 | return new jsc.Point(this.center().x, this.top()); 572 | }; 573 | 574 | jsc.Rectangle.prototype.topLeft = function () { 575 | return this.origin; 576 | }; 577 | 578 | jsc.Rectangle.prototype.topRight = function () { 579 | return new jsc.Point(this.corner.x, this.origin.y); 580 | }; 581 | 582 | jsc.Rectangle.prototype.width = function () { 583 | return this.corner.x - this.origin.x; 584 | }; 585 | 586 | jsc.Rectangle.prototype.position = function () { 587 | return this.origin; 588 | }; 589 | 590 | // Rectangle comparison: 591 | 592 | jsc.Rectangle.prototype.eq = function (aRect) { 593 | return this.origin.eq(aRect.origin) && 594 | this.corner.eq(aRect.corner); 595 | }; 596 | 597 | jsc.Rectangle.prototype.abs = function () { 598 | var newOrigin, newCorner; 599 | 600 | newOrigin = this.origin.abs(); 601 | newCorner = this.corner.max(newOrigin); 602 | return newOrigin.corner(newCorner); 603 | }; 604 | 605 | // Rectangle functions: 606 | 607 | jsc.Rectangle.prototype.insetBy = function (delta) { 608 | // delta can be either a jsc.Point or a Number 609 | var result = new jsc.Rectangle(); 610 | result.origin = this.origin.add(delta); 611 | result.corner = this.corner.subtract(delta); 612 | return result; 613 | }; 614 | 615 | jsc.Rectangle.prototype.expandBy = function (delta) { 616 | // delta can be either a jsc.Point or a Number 617 | var result = new jsc.Rectangle(); 618 | result.origin = this.origin.subtract(delta); 619 | result.corner = this.corner.add(delta); 620 | return result; 621 | }; 622 | 623 | jsc.Rectangle.prototype.intersect = function (aRect) { 624 | var result = new jsc.Rectangle(); 625 | result.origin = this.origin.max(aRect.origin); 626 | result.corner = this.corner.min(aRect.corner); 627 | return result; 628 | }; 629 | 630 | jsc.Rectangle.prototype.merge = function (aRect) { 631 | var result = new jsc.Rectangle(); 632 | result.origin = this.origin.min(aRect.origin); 633 | result.corner = this.corner.max(aRect.corner); 634 | return result; 635 | }; 636 | 637 | jsc.Rectangle.prototype.round = function () { 638 | return this.origin.round().corner(this.corner.round()); 639 | }; 640 | 641 | jsc.Rectangle.prototype.spread = function () { 642 | // round me by applying floor() to my origin and ceil() to my corner 643 | return this.origin.floor().corner(this.corner.ceil()); 644 | }; 645 | 646 | jsc.Rectangle.prototype.amountToTranslateWithin = function (aRect) { 647 | /* 648 | Answer a jsc.Point, delta, such that self + delta is forced within 649 | aRectangle. when all of me cannot be made to fit, prefer to keep 650 | my topLeft inside. Taken from Squeak. 651 | */ 652 | var dx = 0, dy = 0; 653 | 654 | if (this.right() > aRect.right()) { 655 | dx = aRect.right() - this.right(); 656 | } 657 | if (this.bottom() > aRect.bottom()) { 658 | dy = aRect.bottom() - this.bottom(); 659 | } 660 | if ((this.left() + dx) < aRect.left()) { 661 | dx = aRect.left() - this.left(); 662 | } 663 | if ((this.top() + dy) < aRect.top()) { 664 | dy = aRect.top() - this.top(); 665 | } 666 | return new jsc.Point(dx, dy); 667 | }; 668 | 669 | // Rectangle testing: 670 | 671 | jsc.Rectangle.prototype.containsPoint = function (aPoint) { 672 | return this.origin.le(aPoint) && aPoint.lt(this.corner); 673 | }; 674 | 675 | jsc.Rectangle.prototype.containsRectangle = function (aRect) { 676 | return aRect.origin.gt(this.origin) && 677 | aRect.corner.lt(this.corner); 678 | }; 679 | 680 | jsc.Rectangle.prototype.intersects = function (aRect) { 681 | var ro = aRect.origin, rc = aRect.corner; 682 | return (rc.x >= this.origin.x) && 683 | (rc.y >= this.origin.y) && 684 | (ro.x <= this.corner.x) && 685 | (ro.y <= this.corner.y); 686 | }; 687 | 688 | // Rectangle transforming: 689 | 690 | jsc.Rectangle.prototype.scaleBy = function (scale) { 691 | // scale can be either a jsc.Point or a scalar 692 | var o = this.origin.multiplyBy(scale), 693 | c = this.corner.multiplyBy(scale); 694 | return new jsc.Rectangle(Math.min(o.x, c.x), Math.min(o.y, c.y), Math.max(o.x, c.x), Math.max(o.y, c.y)); 695 | }; 696 | 697 | jsc.Rectangle.prototype.translateBy = function (factor) { 698 | // factor can be either a jsc.Point or a scalar 699 | var o = this.origin.add(factor), 700 | c = this.corner.add(factor); 701 | return new jsc.Rectangle(o.x, o.y, c.x, c.y); 702 | }; 703 | 704 | // Rectangle converting: 705 | 706 | jsc.Rectangle.prototype.asArray = function () { 707 | return [this.left(), this.top(), this.right(), this.bottom()]; 708 | }; 709 | 710 | jsc.Rectangle.prototype.asArray_xywh = function () { 711 | return [this.left(), this.top(), this.width(), this.height()]; 712 | }; 713 | }) (jsc); 714 | -------------------------------------------------------------------------------- /js/jsscratch.js: -------------------------------------------------------------------------------- 1 | (function (jsc) { 2 | // Player ///////////////////////////////////////////////// 3 | jsc.Player = function (url, canvas, autoplay, message, progress, load) { 4 | this.canvas = canvas; 5 | this.url = url; 6 | 7 | // download the project 8 | var xhr = new XMLHttpRequest(); 9 | var self = this; 10 | xhr.onprogress = function (e) { 11 | progress(e.lengthComputable ? e.loaded / e.total: 1); 12 | }; 13 | 14 | xhr.onload = function (e) { 15 | if (xhr.readyState === 4 && xhr.status === 200) { 16 | try { 17 | self.read(window.VBArray ? new VBArray(xhr.responseBody).toArray().reduce(function(str, charIndex) { 18 | return str += String.fromCharCode(charIndex); 19 | }, '') : xhr.responseText); 20 | if (autoplay) { 21 | self.start(); 22 | } 23 | load(); 24 | } catch (e) { 25 | message.innerHTML = e.message; 26 | } 27 | } else { 28 | message.innerHTML = xhr.status + ': ' + xhr.statusText; 29 | } 30 | }; 31 | xhr.open('GET', url, true); 32 | if (xhr.overrideMimeType) { 33 | xhr.overrideMimeType("text/plain; charset=x-user-defined"); 34 | } 35 | try { 36 | xhr.send(); 37 | } catch (e) { 38 | xhr.onload(); 39 | message.innerHTML = e.message; 40 | } 41 | } 42 | 43 | jsc.Player.prototype.read = function (data) { 44 | var objectStream = new jsc.ObjectStream(new jDataView(data, undefined, undefined, false)); 45 | this.info = objectStream.nextObject(); 46 | this.stage = objectStream.nextObject(); 47 | this.stage.canvas = this.canvas; 48 | if (this.info['penTrails']) { 49 | this.stage.penCanvas = this.info['penTrails'].getImage(); 50 | } 51 | this.stage.setup(); 52 | }; 53 | 54 | jsc.Player.prototype.start = function () { 55 | this.stage.start(); 56 | }; 57 | 58 | jsc.Player.prototype.stop = function () { 59 | this.stage.stopAll(); 60 | }; 61 | 62 | jsc.Player.prototype.setTurbo = function (turbo) { 63 | this.stage.turbo = turbo; 64 | }; 65 | 66 | jsc.Player.prototype.isTurbo = function () { 67 | return this.stage.turbo; 68 | }; 69 | 70 | 71 | jsc.castNumber = function (object) { 72 | if (typeof object === 'number') { 73 | return object; 74 | } 75 | var string = object.toString(); 76 | /*if (/^(\+|\-)?[0-9]+((\.|\,)[0-9]+)*$/.test(string)) { 77 | return Number(string.match(/^\-?[0-9]+((\.|\,)[0-9]+)?/g)[0].replace(',', '.')); 78 | }*/ 79 | 80 | var num = Number(string); 81 | 82 | if (num === num) { 83 | return num; 84 | } 85 | 86 | return 0; 87 | }; 88 | 89 | jsc.castNumberOrNull = function (object) { 90 | if (typeof object === 'number') { 91 | return object; 92 | } 93 | var string = object.toString(); 94 | if (/^(\+|\-)?[0-9]+((\.|\,)[0-9]+)*$/.test(string)) { 95 | return parseFloat(string.match(/^\-?[0-9]+((\.|\,)[0-9]+)?/g)[0].replace(',', '.')); 96 | } 97 | 98 | return null; 99 | }; 100 | 101 | jsc.newCanvas = function (width, height) { 102 | var canvas = document.createElement('canvas'); 103 | canvas.width = width; 104 | canvas.height = height; 105 | return canvas; 106 | } 107 | 108 | jsc.newImage = function (src, callback) { 109 | var img = new Image(); 110 | img.loaded = false; 111 | img.onload = function() { 112 | img.loaded = true; 113 | callback && callback(); 114 | }; 115 | img.src = src; 116 | return img; 117 | } 118 | 119 | jsc.initFieldsNamed = function (fields, fieldStream) { 120 | for (var i = 0; i < fields.length; i++) { 121 | if (fields[i]) { 122 | this[fields[i]] = fieldStream.nextField(); 123 | } 124 | } 125 | } 126 | 127 | jsc.createWave = function(samples, sampleRate, bitsPerSample) { 128 | var string = 'RIFF' + u32(36 + samples.length) + 'WAVEfmt \x10\x00\x00\x00\x01\x00\x01\x00' + u32(sampleRate) + u32(sampleRate * bitsPerSample / 8) + u16(bitsPerSample / 8) + u16(bitsPerSample) + 'data' + u32(samples.length); 129 | 130 | function u32(i) { 131 | return String.fromCharCode(i & 0xFF, (i >> 8) & 0xFF, (i >> 16) & 0xFF, (i >> 24) & 0xFF); 132 | } 133 | 134 | function u16(i) { 135 | return String.fromCharCode(i & 0xFF, (i >> 8) & 0xFF); 136 | } 137 | 138 | for (var i = 0; i < samples.length; i++) { 139 | string += String.fromCharCode(samples[i]); 140 | } 141 | return 'data:audio/wav;base64,' + btoa(string); 142 | }; 143 | 144 | /*window.addEventListener('error', function (e) { 145 | alert('Error:\n' + e.message + (e.lineno ? ('\nLine number: ' + e.lineno) : '') + (e.filename && e.filename !== 'undefined' ? ('\nFile: ' + e.filename) : '')); 146 | }, false);*/ 147 | 148 | if (!window.btoa) { 149 | window.btoa = function btoa2(str) { 150 | var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; 151 | var encoded = []; 152 | var c = 0; 153 | while (c < str.length) { 154 | var b0 = str.charCodeAt(c++); 155 | var b1 = str.charCodeAt(c++); 156 | var b2 = str.charCodeAt(c++); 157 | var buf = (b0 << 16) + ((b1 || 0) << 8) + (b2 || 0); 158 | var i0 = (buf & (63 << 18)) >> 18; 159 | var i1 = (buf & (63 << 12)) >> 12; 160 | var i2 = isNaN(b1) ? 64 : (buf & (63 << 6)) >> 6; 161 | var i3 = isNaN(b2) ? 64 : (buf & 63); 162 | encoded[encoded.length] = chars.charAt(i0); 163 | encoded[encoded.length] = chars.charAt(i1); 164 | encoded[encoded.length] = chars.charAt(i2); 165 | encoded[encoded.length] = chars.charAt(i3); 166 | } 167 | return encoded.join(''); 168 | }; 169 | } 170 | 171 | jsc.createPlayer = function (url, autoplay) { 172 | var container = document.createElement('div'); 173 | container.setAttribute('class', 'player'); 174 | 175 | container.addEventListener('selectstart', function (e) { 176 | e.preventDefault(); 177 | }); 178 | 179 | var header = document.createElement('div'); 180 | header.setAttribute('class', 'header'); 181 | 182 | var title = document.createElement('span'); 183 | title.setAttribute('class', 'title'); 184 | title.innerHTML = 'JsScratch'; 185 | header.appendChild(title); 186 | 187 | var stop = document.createElement('div'); 188 | stop.setAttribute('class', 'button stop'); 189 | header.appendChild(stop); 190 | 191 | var start = document.createElement('div'); 192 | start.setAttribute('class', 'button start'); 193 | header.appendChild(start); 194 | 195 | var turbo = document.createElement('input'); 196 | turbo.setAttribute('type', 'checkbox'); 197 | turbo.setAttribute('class', 'button'); 198 | header.appendChild(turbo); 199 | 200 | container.appendChild(header); 201 | 202 | var subcon = document.createElement('div'); 203 | subcon.setAttribute('class', 'subcon'); 204 | container.appendChild(subcon); 205 | 206 | var canvas = document.createElement('canvas'); 207 | canvas.setAttribute('width', '480'); 208 | canvas.setAttribute('height', '360'); 209 | canvas.setAttribute('tabindex', '1'); 210 | canvas.innerHTML = 'Sorry, your browser does not support the canvas tag! Get Chrome!'; 211 | subcon.appendChild(canvas); 212 | 213 | var progress = document.createElement('div'); 214 | progress.setAttribute('class', 'progress'); 215 | subcon.appendChild(progress); 216 | 217 | var bar = document.createElement('div'); 218 | bar.setAttribute('class', 'bar'); 219 | progress.appendChild(bar); 220 | 221 | var message = document.createElement('span'); 222 | message.setAttribute('class', 'message'); 223 | bar.appendChild(message); 224 | 225 | var flag = true; 226 | 227 | var player = new jsc.Player(url, canvas, autoplay, message, function (s) { 228 | bar.style.width = (parseFloat(getComputedStyle(progress).width) - 2) * s + 'px'; 229 | }, function () { 230 | progress.addEventListener('webkitTransitionEnd', function () { 231 | if (flag) { 232 | subcon.removeChild(progress); 233 | flag = false; 234 | } 235 | }, false); 236 | progress.addEventListener('transitionend', function () { 237 | if (flag) { 238 | subcon.removeChild(progress); 239 | flag = false; 240 | } 241 | }, false); 242 | progress.className += ' fade'; 243 | 244 | turbo.onclick = function () { 245 | player.setTurbo(turbo.checked); 246 | }; 247 | start.onclick = function () { 248 | player.start(); 249 | }; 250 | stop.onclick = function () { 251 | player.stop(); 252 | }; 253 | }); 254 | 255 | return [container, player]; 256 | } 257 | 258 | Number.prototype.mod = function (n) { 259 | return ((this % n) + n) % n; 260 | }; 261 | 262 | 263 | // Scriptable //////////////////////////////////////// 264 | jsc.Scriptable = function () { 265 | this.init(); 266 | } 267 | 268 | jsc.Scriptable.prototype.init = function () { 269 | this.threads = []; 270 | this.filters = {}; 271 | this.volume = 100; 272 | }; 273 | 274 | jsc.Scriptable.prototype.initFields = function (fields, version) { 275 | jsc.initFieldsNamed.call(this, ['bounds', 'stage', 'children', 'color', 'flags'], fields); 276 | fields.nextField(); 277 | jsc.initFieldsNamed.call(this, ['objName', 'variables', 'blocksBin', 'isClone', 'media', 'costume'], fields); 278 | }; 279 | 280 | jsc.Scriptable.prototype.initBeforeLoad = function () { 281 | for (var i = 0; i < this.blocksBin.length; i++) { 282 | if (['EventHatMorph', 'KeyEventHatMorph', 'MouseClickEventHatMorph'].indexOf(this.blocksBin[i][1][0][0]) != -1) { 283 | this.threads.push(new jsc.Thread(this, this.blocksBin[i][1])); 284 | } 285 | } 286 | 287 | var key, list, val, num; 288 | if (this.lists) { 289 | for (key in this.lists) { 290 | list = this.lists[key][9]; 291 | for (var i = 0; i < list.length; i++) { 292 | val = list[i]; 293 | num = jsc.castNumberOrNull(val); 294 | list[i] = ((num !== null && num.toString() === val) ? num : val); 295 | } 296 | this.lists[key] = list; 297 | } 298 | } else { 299 | this.lists = {}; 300 | } 301 | 302 | if (this.variables) { 303 | for (key in this.variables) { 304 | variable = this.variables[key]; 305 | 306 | num = jsc.castNumberOrNull(variable); 307 | val = ((num === null) ? variable : num); 308 | 309 | this.variables[key] = {val:val}; 310 | } 311 | } else { 312 | this.variables = {}; 313 | } 314 | 315 | this.costumes = this.media.filter(function (e) { 316 | return e instanceof jsc.ImageMedia; 317 | }); 318 | this.sounds = this.media.filter(function (e) { 319 | return e instanceof jsc.SoundMedia; 320 | }); 321 | 322 | this.costumeIndex = this.costumes.indexOf(this.costume); 323 | 324 | if (!this.stage) { 325 | this.stage = this.getStage(); 326 | } 327 | }; 328 | 329 | jsc.Scriptable.prototype.setup = function () { 330 | this.allVariables = {}; 331 | 332 | var key; 333 | 334 | for (key in this.variables) { 335 | this.allVariables[key] = this.variables[key]; 336 | } 337 | for (key in this.stage.variables) { 338 | this.allVariables[key] = this.stage.variables[key]; 339 | } 340 | 341 | for (key in this.stage.variables) { 342 | this.variables[key] = this.stage.variables[key]; 343 | } 344 | 345 | for (key in this.stage.lists) { 346 | this.lists[key] = this.stage.lists[key]; 347 | } 348 | }; 349 | 350 | jsc.Scriptable.prototype.getStage = function () { 351 | if (this.parent instanceof jsc.Stage) { 352 | return this.parent; 353 | } else if (this instanceof jsc.Stage) { 354 | return this; 355 | } 356 | return null; 357 | }; 358 | 359 | jsc.Scriptable.prototype.step = function () { 360 | this.stepThreads(); 361 | }; 362 | 363 | jsc.Scriptable.prototype.stepThreads = function () { 364 | for (var i = 0; i < this.threads.length; i++) { 365 | this.threads[i].step(); 366 | } 367 | }; 368 | 369 | jsc.Scriptable.prototype.isRunning = function () { 370 | for (var i = 0; i < this.threads.length; i++) { 371 | if (!this.threads[i].done) { 372 | return true; 373 | } 374 | } 375 | return false; 376 | }; 377 | 378 | jsc.Scriptable.prototype.broadcast = function (broadcast) { 379 | for (var i = 0; i < this.threads.length; i++) { 380 | if (this.threads[i].hat[0] === 'EventHatMorph' && this.threads[i].hat[1].toLowerCase() === broadcast.toLowerCase() && this.threads[i].done) { 381 | this.threads[i].start(); 382 | } 383 | } 384 | }; 385 | 386 | jsc.Scriptable.prototype.stopAll = function () { 387 | for (var i = 0; i < this.threads.length; i++) { 388 | this.threads[i].stop(); 389 | } 390 | }; 391 | 392 | jsc.Scriptable.prototype.getCommandFunctionName = function (selector) { 393 | var special = { 394 | "xpos:":"setXPos", 395 | "ypos:":"setYPos", 396 | "heading:":"setHeading", 397 | 398 | "showBackground:":"lookLike", 399 | "nextBackground":"nextCostume", 400 | 401 | "broadcast:":"scratchBroadcast", 402 | "stopAll":"stopAllScripts" 403 | }; 404 | if (special[selector]) { 405 | return special[selector]; 406 | } 407 | var str = selector.replace(/\:/g, ''); 408 | if (typeof this[str] === 'function') { 409 | return str; 410 | } 411 | 412 | console.log('Unknown command block \'' + selector + '\', ignoring...'); 413 | return null; 414 | }; 415 | 416 | jsc.Scriptable.prototype.getReporterFunctionName = function (selector) { 417 | var special = { 418 | "xpos":"getXPos", 419 | "ypos":"getYPos", 420 | "heading":"getHeading", 421 | 422 | "costumeIndex":"getCostumeIndex", 423 | "backgroundIndex":"getCostumeIndex", 424 | 425 | "timer":"getTimer", 426 | 427 | "=":"equals", 428 | ">":"greatorThan", 429 | "<":"lessThan", 430 | 431 | "|":"or", 432 | "&":"and", 433 | 434 | "readVariable":"getVariable" 435 | }; 436 | if (special[selector]) { 437 | return special[selector]; 438 | } 439 | var str = selector.replace(/\:/g, ''); 440 | if (typeof this[str] === 'function') { 441 | return str; 442 | } 443 | 444 | console.log('Unknown reporter block \'' + selector + '\', ignoring...'); 445 | return null; 446 | }; 447 | 448 | jsc.Scriptable.prototype.isStage = function () { 449 | return false; 450 | }; 451 | 452 | jsc.Scriptable.prototype.getAttribute = function (attribute) { 453 | switch (attribute) { 454 | case 'volume': 455 | return this.volume; 456 | } 457 | return 0; 458 | }; 459 | 460 | jsc.Scriptable.prototype.getSound = function (sound) { 461 | var cast = jsc.castNumberOrNull(sound); 462 | if (cast === null) { 463 | for (var i = 0; i < this.sounds.length; i++) { 464 | if (this.sounds[i].name.toLowerCase() === sound.toString().toLowerCase()) { 465 | return this.sounds[i]; 466 | } 467 | } 468 | } else { 469 | return this.sounds[(Math.round(cast) - 1).mod(this.sounds.length)]; 470 | } 471 | return null; 472 | }; 473 | 474 | jsc.Scriptable.prototype.getList = function (name) { 475 | return this.lists[name]; 476 | }; 477 | 478 | jsc.Scriptable.prototype.toListLine = function (arg, list) { 479 | if (typeof arg === 'string') { 480 | switch (arg.toString()) { 481 | case 'first': 482 | return 0; 483 | case 'last': 484 | return list.length - 1; 485 | case 'any': 486 | return Math.floor(Math.random() * list.length); 487 | default: 488 | i = jsc.castNumberOrNull(arg) 489 | if (i === null) { 490 | return -1; 491 | } 492 | i = Math.round(i); 493 | } 494 | } else if (typeof arg === 'number') { 495 | i = arg; 496 | } else { 497 | return -1; 498 | } 499 | 500 | if (i >= 1 && i <= list.length) { 501 | return i - 1; 502 | } else { 503 | return -1; 504 | } 505 | }; 506 | 507 | jsc.Scriptable.prototype.coerceSprite = function (sprite) { 508 | if (sprite instanceof jsc.Scriptable) { 509 | return sprite; 510 | } 511 | return this.stage.getSprite(sprite.toString()); 512 | }; 513 | 514 | jsc.Scriptable.prototype.stopAllMySounds = function () { 515 | for (var i = 0; i < this.sounds.length; i++) { 516 | this.sounds[i].stop(); 517 | } 518 | }; 519 | 520 | 521 | // jsc.Stage ///////////////////////////////////////////// 522 | jsc.Stage = function () { 523 | this.init(); 524 | } 525 | 526 | jsc.Stage.prototype = new jsc.Scriptable(); 527 | jsc.Stage.prototype.constructor = jsc.Stage; 528 | jsc.Stage.uber = jsc.Scriptable.prototype; 529 | 530 | jsc.Stage.prototype.init = function () { 531 | jsc.Stage.uber.init.call(this); 532 | this.turbo = false; 533 | this.broadcastQueue = []; 534 | this.timer = new jsc.Stopwatch(); 535 | this.keys = []; 536 | this.mouse = new jsc.Point(0, 0); 537 | this.mouseDown = false; 538 | for (var i = 0; i < 255; i++) { 539 | this.keys.push(false); 540 | } 541 | }; 542 | 543 | jsc.Stage.prototype.initFields = function (fields, version) { 544 | jsc.Stage.uber.initFields.call(this, fields, version); 545 | jsc.initFieldsNamed.call(this, ['zoom', 'hPan', 'vPan'], fields); 546 | if (version == 1) return; 547 | jsc.initFieldsNamed.call(this, ['obsoleteSavedState'], fields); 548 | if (version == 2) return; 549 | jsc.initFieldsNamed.call(this, ['sprites'], fields); 550 | if (version == 3) return; 551 | jsc.initFieldsNamed.call(this, ['volume', 'tempoBPM'], fields); 552 | if (version == 4) return; 553 | jsc.initFieldsNamed.call(this, ['sceneStates', 'lists'], fields); 554 | }; 555 | 556 | jsc.Stage.prototype.initBeforeLoad = function () { 557 | jsc.Stage.uber.initBeforeLoad.call(this); 558 | }; 559 | 560 | jsc.Stage.prototype.drawOn = function (ctx) { 561 | ctx.drawImage(this.costume.getImage(), 0, 0); 562 | 563 | this.penCtx.stroke(); 564 | ctx.drawImage(this.penCanvas, 0, 0); 565 | this.penCtx.beginPath(); 566 | 567 | for (var i = this.children.length - 1; i >= 0; i--) { 568 | this.children[i].drawOn && this.children[i].drawOn(ctx); 569 | } 570 | }; 571 | 572 | jsc.Stage.prototype.drawAllButOn = function (ctx, sprite) { 573 | ctx.drawImage(this.costume.getImage(), 0, 0); 574 | 575 | this.penCtx.stroke(); 576 | ctx.drawImage(this.penCanvas, 0, 0); 577 | this.penCtx.beginPath(); 578 | 579 | for (var i = this.sprites.length - 1; i >= 0; i--) { 580 | if (this.sprites[i] !== sprite) { 581 | this.sprites[i].drawOn && this.sprites[i].drawOn(ctx); 582 | } 583 | } 584 | }; 585 | 586 | jsc.Stage.prototype.setup = function () { 587 | this.ctx = this.canvas.getContext('2d'); 588 | if (!this.penCanvas) { 589 | this.penCanvas = jsc.newCanvas(this.bounds.width(), this.bounds.height()) 590 | } 591 | this.penCtx = this.penCanvas.getContext('2d'); 592 | this.penCtx.lineCap = 'round'; 593 | 594 | this.buffer1 = jsc.newCanvas(this.width(), this.height()); 595 | this.bufferCtx1 = this.buffer1.getContext('2d'); 596 | 597 | this.buffer2 = jsc.newCanvas(this.width(), this.height()); 598 | this.bufferCtx2 = this.buffer2.getContext('2d'); 599 | 600 | this.allVariables = this.variables; 601 | 602 | this.canvas.onclick = function () { 603 | 604 | }; 605 | 606 | var self = this; 607 | this.canvas.addEventListener('keydown', function (e) { 608 | self.keydown(e); 609 | }, false); 610 | this.canvas.addEventListener('keyup', function (e) { 611 | self.keyup(e); 612 | }, false); 613 | 614 | this.canvas.addEventListener('mousemove', function (e) { 615 | self.mousemove(e); 616 | }, false); 617 | this.canvas.addEventListener('mouseup', function (e) { 618 | self.mouseup(e); 619 | }, false); 620 | this.canvas.addEventListener('mousedown', function (e) { 621 | self.mousedown(e); 622 | }, false); 623 | 624 | this.canvas.addEventListener('touchmove', function (e) { 625 | self.mousemove(e); 626 | }, false); 627 | this.canvas.addEventListener('touchend', function (e) { 628 | self.mouseup(e); 629 | }, false); 630 | this.canvas.addEventListener('touchstart', function (e) { 631 | self.mousedown(e); 632 | }, false); 633 | 634 | for (var i = 0; i < this.sprites.length; i++) { 635 | this.sprites[i].setup(); 636 | } 637 | 638 | this.eventsByName = {}; 639 | 640 | var threads = this.getAllThreads(); 641 | 642 | for (var i = 0; i < threads.length; i++) { 643 | if (threads[i].hat[0] === 'EventHatMorph') { 644 | var name = threads[i].hat[1].toLowerCase(); 645 | if (!this.eventsByName[name]) { 646 | this.eventsByName[name] = []; 647 | } 648 | this.eventsByName[name].push(threads[i]); 649 | } 650 | } 651 | 652 | setInterval(function () { 653 | self.step(); 654 | }, 1000 / 30); 655 | }; 656 | 657 | jsc.Stage.prototype.width = function () { 658 | return this.bounds.width(); 659 | }; 660 | 661 | jsc.Stage.prototype.height = function () { 662 | return this.bounds.height(); 663 | }; 664 | 665 | jsc.Stage.prototype.step = function () { 666 | var stopwatch; 667 | if (this.turbo) { 668 | stopwatch = new jsc.Stopwatch(); 669 | } 670 | 671 | if (this.animationFrame) { 672 | this.addBroadcastToQueue('animationframe'); 673 | this.animationFrame = false; 674 | } 675 | 676 | do { 677 | jsc.Stage.uber.step.call(this); 678 | for (var i = 0; i < this.sprites.length; i++) { 679 | this.sprites[i].step(); 680 | } 681 | } while (this.turbo && stopwatch.getElapsed() < 30) 682 | 683 | this.ctx.clearRect(0, 0, this.bounds.width(), this.bounds.height()) 684 | this.drawOn(this.ctx); 685 | }; 686 | 687 | jsc.Stage.prototype.stepThreads = function () { 688 | for (var i = 0; i < this.broadcastQueue.length; i++) { 689 | if (this.broadcastQueue[i] === 'requestanimationframe') { 690 | this.animationFrame = true; 691 | } 692 | var events = this.eventsByName[this.broadcastQueue[i]]; 693 | if (events) { 694 | for (var j = 0; j < events.length; j++) { 695 | if (events[j].done) { 696 | events[j].start(); 697 | } 698 | } 699 | } 700 | } 701 | this.broadcastQueue = []; 702 | jsc.Stage.uber.stepThreads.call(this); 703 | }; 704 | 705 | jsc.Stage.prototype.isRunning = function () { 706 | var running = jsc.Stage.uber.isRunning.call(this); 707 | for (var i = 0; i < this.sprites.length; i++) { 708 | running = running || this.sprites[i].isRunning(); 709 | } 710 | return running; 711 | }; 712 | 713 | jsc.Stage.prototype.isStage = function () { 714 | return true; 715 | }; 716 | 717 | jsc.Stage.prototype.getAllThreads = function () { 718 | if (this.allThreads) { 719 | return this.allThreads; 720 | } 721 | var threads = this.threads; 722 | for (var i = 0; i < this.sprites.length; i++) { 723 | threads = threads.concat(this.sprites[i].threads); 724 | } 725 | return this.allThreads = threads; 726 | }; 727 | 728 | jsc.Stage.prototype.addBroadcastToQueue = function (broadcast) { 729 | this.broadcastQueue.push(broadcast.toLowerCase()); 730 | }; 731 | 732 | jsc.Stage.prototype.stopAll = function () { 733 | jsc.Stage.uber.stopAll.call(this); 734 | for (var i = 0; i < this.sprites.length; i++) { 735 | this.sprites[i].stopAll(); 736 | } 737 | this.stopAllSounds(); 738 | }; 739 | 740 | jsc.Stage.prototype.start = function () { 741 | this.stopAll(); 742 | this.addBroadcastToQueue('Scratch-StartClicked'); 743 | }; 744 | 745 | jsc.Stage.prototype.origin = function () { 746 | return this.bounds.center(); 747 | }; 748 | 749 | jsc.Stage.prototype.toScratchCoords = function (point) { 750 | return point.subtract(this.bounds.center()).multiplyBy(new jsc.Point(1, -1)); 751 | }; 752 | 753 | jsc.Stage.prototype.fromScratchCoords = function (point) { 754 | return point.multiplyBy(new jsc.Point(1, -1)).add(this.bounds.center()); 755 | }; 756 | 757 | jsc.Stage.prototype.getSprite = function (name) { 758 | return this.sprites.filter(function (sprite) { 759 | return sprite.objName === name; 760 | })[0]; 761 | }; 762 | 763 | jsc.Stage.prototype.getAttribute = function (attribute) { 764 | switch (attribute) { 765 | case 'background #': 766 | return this.costumeIndex + 1; 767 | } 768 | return jsc.Stage.uber.getAttribute.call(attribute); 769 | }; 770 | 771 | jsc.Stage.prototype.keydown = function (e) { 772 | e.preventDefault(); 773 | this.keys[e.keyCode] = true; 774 | var threads = this.getAllThreads(); 775 | var thread; 776 | for (var i = 0; i < threads.length; i++) { 777 | thread = threads[i]; 778 | if (thread.done && thread.hat[0] === 'KeyEventHatMorph' && thread.hat[1] === e.keyCode) { 779 | thread.start(); 780 | } 781 | } 782 | }; 783 | 784 | jsc.Stage.prototype.keyup = function (e) { 785 | e.preventDefault(); 786 | this.keys[e.keyCode] = false; 787 | }; 788 | 789 | jsc.Stage.prototype.mousemove = function (e) { 790 | e.preventDefault(); 791 | var bounds = this.canvas.getBoundingClientRect(); 792 | this.mouse = new jsc.Point((e.offsetX || e.layerX) * this.canvas.width / bounds.width, (e.offsetY || e.layerY) * this.canvas.height / bounds.height); 793 | }; 794 | jsc.Stage.prototype.mouseup = function (e) { 795 | e.preventDefault(); 796 | this.mouseDown = false; 797 | }; 798 | jsc.Stage.prototype.mousedown = function (e) { 799 | e.preventDefault(); 800 | this.mouseDown = true; 801 | 802 | for (var i = 0; i < this.children.length; i++) { 803 | var sprite = this.children[i]; 804 | if (sprite instanceof jsc.Sprite && sprite.isTouching('mouse') && sprite.filters.ghost < 100) { 805 | var threads = sprite.threads 806 | for (var j = 0; j < threads.length; j++) { 807 | if (threads[j].hat[0] === 'MouseClickEventHatMorph') { 808 | threads[j].start(); 809 | } 810 | } 811 | break; 812 | } 813 | } 814 | }; 815 | 816 | jsc.Stage.prototype.stopAllSounds = function () { 817 | jsc.Stage.uber.stopAllMySounds.call(this); 818 | for (var i = 0; i < this.sprites.length; i++) { 819 | this.sprites[i].stopAllMySounds(); 820 | } 821 | }; 822 | 823 | 824 | // Sprite //////////////////////////////////////////// 825 | jsc.Sprite = function () { 826 | this.init(); 827 | } 828 | 829 | jsc.Sprite.prototype = new jsc.Scriptable(); 830 | jsc.Sprite.prototype.constructor = jsc.Sprite; 831 | jsc.Sprite.uber = jsc.Scriptable.prototype; 832 | 833 | 834 | jsc.Sprite.prototype.init = function () { 835 | jsc.Sprite.uber.init.call(this); 836 | }; 837 | 838 | jsc.Sprite.prototype.initFields = function (fields, version) { 839 | jsc.Sprite.uber.initFields.call(this, fields, version); 840 | jsc.initFieldsNamed.call(this, ['visibility', 'scalePoint', 'direction', 'rotationStyle'], fields); 841 | if (version == 1) return; 842 | jsc.initFieldsNamed.call(this, ['volume', 'tempoBPM', 'draggable'], fields); 843 | if (version == 2) return; 844 | jsc.initFieldsNamed.call(this, ['sceneStates', 'lists'], fields); 845 | }; 846 | 847 | jsc.Sprite.prototype.initBeforeLoad = function () { 848 | jsc.Sprite.uber.initBeforeLoad.call(this); 849 | this.position = this.bounds.origin.add(this.costume.rotationCenter); 850 | this.hidden = (this.flags & 1) === 1; 851 | this.pen = {}; 852 | this.pen.color = new jsc.Color(0, 0, 255); 853 | this.pen.hsl = this.pen.color.getHSL(); 854 | this.pen.size = 1; 855 | this.boundingChanged = true; 856 | }; 857 | 858 | jsc.Sprite.prototype.step = function () { 859 | this.stage.penCtx.moveTo(this.position.x - 0.5, this.position.y - 0.5); 860 | jsc.Sprite.uber.step.call(this); 861 | }; 862 | 863 | jsc.Sprite.prototype.drawOn = function (ctx, debug) { 864 | if (this.hidden) { 865 | return; 866 | } 867 | var rc = this.costume.rotationCenter; 868 | var angle = this.direction.mod(360) * Math.PI / 180; 869 | 870 | ctx.save(); 871 | ctx.translate(Math.round(this.position.x), Math.round(this.position.y)); 872 | if (this.rotationStyle === 'normal') { 873 | ctx.rotate(angle); 874 | } 875 | ctx.scale(this.rotationStyle === 'leftRight' && (this.direction - 90).mod(360) < 180 ? -this.scalePoint.x : this.scalePoint.x, this.scalePoint.y); 876 | ctx.translate(-rc.x, -rc.y); 877 | var t = this.filters.ghost; 878 | if (typeof t !== 'undefined') { 879 | ctx.globalAlpha = Math.max(Math.min(1 - t / 100, 1), 0); 880 | } 881 | 882 | ctx.drawImage(this.costume.getImage(), 0, 0); 883 | 884 | ctx.globalAlpha = 1; 885 | if (debug) { 886 | ctx.lineWidth = 2; 887 | ctx.strokeStyle = '#FF0000'; 888 | ctx.strokeRect(0, 0, this.costume.extent().x, this.costume.extent().y); 889 | } 890 | 891 | ctx.restore(); 892 | 893 | if (debug) { 894 | ctx.strokeStyle = '#00FF00'; 895 | var b = this.getBoundingBox(); 896 | ctx.strokeRect(b.left(), b.top(), b.width(), b.height()); 897 | } 898 | } 899 | 900 | jsc.Sprite.prototype.getBoundingBox = function () { 901 | if (!this.boundingChanged) { 902 | return this.boundingBox; 903 | } 904 | this.boundingChanged = false; 905 | var p = this.position; 906 | var rc = this.costume.rotationCenter; 907 | 908 | var xp1 = -rc.x; 909 | var yp1 = -rc.y; 910 | 911 | var xp2 = this.costume.extent().x - rc.x; 912 | var yp2 = this.costume.extent().y - rc.y; 913 | 914 | if (this.rotationStyle !== 'normal' || this.scratchHeading() === 90) { 915 | return this.boundingBox = new jsc.Rectangle(xp1, yp1, xp2, yp2).scaleBy(this.scalePoint.multiplyBy(new jsc.Point(this.rotationStyle === 'leftRight' && (this.direction - 90).mod(360) < 180 ? -1 : 1, 1))).translateBy(this.position).expandBy(1); 916 | } 917 | 918 | var rad = Math.PI/180 * (this.direction); 919 | 920 | var cos = Math.cos(rad); 921 | var sin = Math.sin(rad); 922 | 923 | var x1 = xp1 * cos - yp1 * sin; 924 | var y1 = xp1 * sin + yp1 * cos; 925 | 926 | var x2 = xp1 * cos - yp2 * sin; 927 | var y2 = xp1 * sin + yp2 * cos; 928 | 929 | var x3 = xp2 * cos - yp2 * sin; 930 | var y3 = xp2 * sin + yp2 * cos; 931 | 932 | var x4 = xp2 * cos - yp1 * sin; 933 | var y4 = xp2 * sin + yp1 * cos; 934 | 935 | var rx1 = Math.floor(p.x + Math.min(x1, x2, x3, x4) * this.scalePoint.x); 936 | var ry1 = Math.floor(p.y + Math.min(y1, y2, y3, y4) * this.scalePoint.y); 937 | 938 | var rx2 = Math.ceil(p.x + Math.max(x1, x2, x3, x4) * this.scalePoint.x); 939 | var ry2 = Math.ceil(p.y + Math.max(y1, y2, y3, y4) * this.scalePoint.y); 940 | 941 | return this.boundingBox = new jsc.Rectangle(rx1, ry1, rx2, ry2); 942 | }; 943 | 944 | jsc.Sprite.prototype.isTouching = function (obj) { 945 | var stage = this.stage; 946 | if (obj === 'edge') { 947 | return !stage.bounds.containsRectangle(this.getBoundingBox().expandBy(-2).translateBy(1)); 948 | } 949 | if (this.hidden) { 950 | return false; 951 | } 952 | 953 | var w = stage.width(); 954 | var h = stage.height(); 955 | 956 | var b1 = this.getBoundingBox(); 957 | 958 | if (obj === 'mouse') { 959 | if (!b1.containsPoint(stage.mouse)) { 960 | return false; 961 | } 962 | 963 | var rc = this.costume.rotationCenter; 964 | var angle = this.direction.mod(360) * Math.PI / 180; 965 | 966 | var p = this.stage.mouse; 967 | 968 | p = p.translateBy(new jsc.Point(-Math.round(this.position.x), -Math.round(this.position.y))); 969 | 970 | p = p.scaleBy(new jsc.Point(1 / (this.rotationStyle === 'leftRight' && (this.direction - 90).mod(360) < 180 ? -this.scalePoint.x : this.scalePoint.x), 1 / this.scalePoint.y)); 971 | 972 | if (this.rotationStyle === 'normal') { 973 | p = p.rotateBy(-angle); 974 | } 975 | 976 | p = p.translateBy(new jsc.Point(rc.x - 1, rc.y - 1)); 977 | 978 | return this.costume.getImage().getContext('2d').getImageData(p.x, p.y, 1, 1).data[3] > 0; 979 | } else { 980 | var other = this.coerceSprite(obj); 981 | if (!other || other.hidden) { 982 | return false; 983 | } 984 | 985 | var b2 = other.getBoundingBox(); 986 | 987 | if (!b1.intersects(b2)) { 988 | return false; 989 | } 990 | 991 | var bufferCtx1 = stage.bufferCtx1; 992 | bufferCtx1.clearRect(0, 0, w, h); 993 | var g = this.filters.ghost || 0; 994 | this.filters.ghost = 0; 995 | this.drawOn(bufferCtx1); 996 | this.filters.ghost = g; 997 | 998 | var bufferCtx2 = stage.bufferCtx2; 999 | bufferCtx2.clearRect(0, 0, w, h); 1000 | g = other.filters.ghost || 0; 1001 | other.filters.ghost = 0; 1002 | other.drawOn(bufferCtx2); 1003 | other.filters.ghost = g; 1004 | 1005 | var b = b1.intersect(b2); 1006 | 1007 | if (b.width() <= 0 || b.height() <= 0) { 1008 | return false; 1009 | } 1010 | 1011 | var t = bufferCtx1.getImageData(b.origin.x, b.origin.y, b.width(), b.height()).data; 1012 | var s = bufferCtx2.getImageData(b.origin.x, b.origin.y, b.width(), b.height()).data; 1013 | for (var i = 0; i < s.length; i += 4) { 1014 | if (t[i + 3] > 0 && s[i + 3] > 0) { 1015 | return true; 1016 | } 1017 | } 1018 | } 1019 | return false; 1020 | }; 1021 | 1022 | jsc.Sprite.prototype.isTouchingColor = function (color) { 1023 | var stage = this.stage; 1024 | var w = stage.width(); 1025 | var h = stage.height(); 1026 | 1027 | var bufferCtx1 = stage.bufferCtx1; 1028 | bufferCtx1.clearRect(0, 0, w, h); 1029 | this.drawOn(bufferCtx1); 1030 | 1031 | var bufferCtx2 = stage.bufferCtx2; 1032 | bufferCtx2.clearRect(0, 0, w, h); 1033 | stage.drawAllButOn(bufferCtx2, this); 1034 | 1035 | var b = this.getBoundingBox(); 1036 | 1037 | var t = bufferCtx1.getImageData(b.origin.x, b.origin.y, b.width(), b.height()).data; 1038 | var s = bufferCtx2.getImageData(b.origin.x, b.origin.y, b.width(), b.height()).data; 1039 | 1040 | var r = color.r; 1041 | var g = color.g; 1042 | var b = color.b; 1043 | 1044 | for (var i = 0; i < s.length; i += 4) { 1045 | if (t[i + 3] > 0 && s[i] === r && s[i + 1] === g && s[i + 2] === b) { 1046 | return true; 1047 | } 1048 | } 1049 | return false; 1050 | }; 1051 | 1052 | jsc.Sprite.prototype.isColorTouchingColor = function (color1, color2) { 1053 | var stage = this.stage; 1054 | var w = stage.width(); 1055 | var h = stage.height(); 1056 | 1057 | var bufferCtx1 = stage.bufferCtx1; 1058 | bufferCtx1.clearRect(0, 0, w, h); 1059 | this.drawOn(bufferCtx1); 1060 | 1061 | var bufferCtx2 = stage.bufferCtx2; 1062 | bufferCtx2.clearRect(0, 0, w, h); 1063 | stage.drawAllButOn(bufferCtx2, this); 1064 | 1065 | var b = this.getBoundingBox(); 1066 | 1067 | var t = bufferCtx1.getImageData(b.origin.x, b.origin.y, b.width(), b.height()).data; 1068 | var s = bufferCtx2.getImageData(b.origin.x, b.origin.y, b.width(), b.height()).data; 1069 | 1070 | var r1 = color1.r; 1071 | var g1 = color1.g; 1072 | var b1 = color1.b; 1073 | 1074 | var r2 = color2.r; 1075 | var g2 = color2.g; 1076 | var b2 = color2.b; 1077 | 1078 | var cs = color1.toString(); 1079 | 1080 | var cc = this.costume.colorCache[cs]; 1081 | 1082 | if (!cc) { 1083 | cc = this.costume.colorCache[cs] = []; 1084 | var f = false; 1085 | for (var i = 0; i < s.length; i += 4) { 1086 | if (t[i] === r1 && t[i + 1] === g1 && t[i + 2] === b1 && t[i + 3] > 0) { 1087 | cc.push(i); 1088 | if (s[i] === r2 && s[i + 1] === g2 && s[i + 2] === b2 && s[i + 3] > 0) { 1089 | f = true; 1090 | } 1091 | } 1092 | } 1093 | return f; 1094 | } 1095 | 1096 | var i; 1097 | for (var j = 0; j < cc.length; j++) { 1098 | i = cc[j]; 1099 | if (s[i] === r2 && s[i + 1] === g2 && s[i + 2] === b2 && s[i + 3] > 0) { 1100 | return true; 1101 | } 1102 | } 1103 | return false; 1104 | }; 1105 | 1106 | jsc.Sprite.prototype.getRelativePosition = function () { 1107 | return this.position.subtract(this.stage.origin()).multiplyBy(new jsc.Point(1, -1)); 1108 | }; 1109 | 1110 | jsc.Sprite.prototype.setRelativePosition = function (point) { 1111 | this.setPosition(point.multiplyBy(new jsc.Point(1, -1)).add(this.stage.origin())); 1112 | }; 1113 | 1114 | jsc.Sprite.prototype.setPosition = function (point) { 1115 | var ctx = this.stage.penCtx; 1116 | this.position = point; 1117 | if (this.penDown) { 1118 | ctx.lineTo(this.position.x - 0.5, this.position.y - 0.5); 1119 | } else { 1120 | ctx.moveTo(this.position.x - 0.5, this.position.y - 0.5); 1121 | } 1122 | 1123 | this.boundingChanged = this.hasMoved = true; 1124 | }; 1125 | 1126 | jsc.Sprite.prototype.extent = function () { 1127 | return new jsc.Point(this.bounds.corner.x - this.bounds.origin.x, this.bounds.corner.y - this.bounds.origin.y); 1128 | }; 1129 | 1130 | jsc.Sprite.prototype.getAttribute = function (attribute) { 1131 | var stage = this.stage; 1132 | switch (attribute) { 1133 | case 'x position': 1134 | return stage.toScratchCoords(this.position).x; 1135 | case 'y position': 1136 | return stage.toScratchCoords(this.position).y; 1137 | case 'direction': 1138 | return this.scratchHeading(); 1139 | case 'costume #': 1140 | return this.costumeIndex + 1; 1141 | case 'size': 1142 | return Math.round(this.scalePoint.x * 100); 1143 | } 1144 | return jsc.Sprite.uber.getAttribute.call(attribute); 1145 | }; 1146 | 1147 | jsc.Sprite.prototype.scratchHeading = function () { 1148 | return (this.direction + 90 + 179).mod(360) - 179; 1149 | }; 1150 | 1151 | jsc.Sprite.prototype.updatePen = function () { 1152 | var penCtx = this.stage.penCtx; 1153 | 1154 | penCtx.stroke(); 1155 | 1156 | penCtx.lineWidth = this.pen.size; 1157 | var hsl = this.pen.hsl; 1158 | penCtx.strokeStyle = 'hsl(' + (hsl[0] * 360) + ', ' + (hsl[1] * 100) + '%, ' + (hsl[2] * 100) + '%)'; 1159 | 1160 | penCtx.beginPath(); 1161 | penCtx.moveTo(this.position.x - 0.5, this.position.y - 0.5); 1162 | }; 1163 | 1164 | 1165 | // Watcher //////////////////////////////////////////// 1166 | jsc.Watcher = function () { 1167 | this.init(); 1168 | } 1169 | 1170 | jsc.Watcher.prototype.constructor = jsc.Watcher; 1171 | 1172 | jsc.Watcher.prototype.init = function () { 1173 | this.hidden = false; 1174 | }; 1175 | 1176 | jsc.Watcher.prototype.initFields = function (fields) { 1177 | this.fields = fields; 1178 | }; 1179 | 1180 | jsc.Watcher.prototype.initBeforeLoad = function () { 1181 | jsc.initFieldsNamed.call(this, ['bounds', 'parent'], this.fields); 1182 | this.mode = 0; 1183 | if (this.fields.fields[19]) { 1184 | this.mode = 1; 1185 | } 1186 | if (this.fields.fields[16] !== null) { 1187 | this.mode = 2; 1188 | } 1189 | this.sliderMin = this.fields.fields[20]; 1190 | this.sliderMax = this.fields.fields[21]; 1191 | 1192 | this.color = this.fields.fields[15][3]; 1193 | 1194 | this.label = this.fields.fields[13][8]; 1195 | 1196 | this.object = this.fields.fields[14][10]; 1197 | 1198 | this.command = this.commandLookup[this.fields.fields[14][11]]; 1199 | this.arg = this.fields.fields[14][13]; 1200 | 1201 | this.value = 'watcher'; 1202 | }; 1203 | 1204 | jsc.Watcher.prototype.commandLookup = { 1205 | "getVar:":"getVariable", 1206 | "timer":"getTimer" 1207 | }; 1208 | 1209 | jsc.Watcher.prototype.updateValue = function () { 1210 | this.value = this.object[this.command](this.arg); 1211 | if (typeof this.value === 'number') { 1212 | this.value = (Math.round(this.value * 1000) / 1000).toString(); 1213 | } 1214 | }; 1215 | 1216 | jsc.Watcher.prototype.drawOn = function (ctx) { 1217 | if (this.hidden) { 1218 | return; 1219 | } 1220 | this.updateValue(); 1221 | ctx.font = 'bold 8pt Verdana'; 1222 | var w = ctx.measureText(this.label).width + 30; 1223 | 1224 | ctx.font = '8pt Verdana'; 1225 | w += Math.max(ctx.measureText(this.value).width, 30); 1226 | 1227 | this.bounds.corner.x = this.bounds.origin.x + w; 1228 | 1229 | 1230 | var x1 = this.bounds.origin.x + 0.5; 1231 | var y1 = this.bounds.origin.y + 0.5; 1232 | var x2 = this.bounds.corner.x + 0.5; 1233 | var y2 = this.bounds.corner.y + 0.5; 1234 | 1235 | var th = 21; 1236 | 1237 | var r = 7; 1238 | 1239 | this.drawRoundedRect(ctx, x1, y1, x2, y2, r); 1240 | ctx.fillStyle = 'rgba(193, 196, 199, 255)'; 1241 | ctx.fill(); 1242 | ctx.strokeStyle = 'rgba(148, 145, 145, 255)'; 1243 | ctx.stroke(); 1244 | 1245 | x1 += 5; 1246 | 1247 | ctx.fillStyle = 'black'; 1248 | ctx.font = 'bold 8pt Verdana'; 1249 | ctx.textBaseline = 'middle'; 1250 | ctx.fillText(this.label, x1, y1 + th / 2); 1251 | 1252 | x1 += ctx.measureText(this.label).width + 5; 1253 | 1254 | r = 4; 1255 | 1256 | this.drawRoundedRect(ctx, x1, y1 + 2, x2 - 4, y1 + th - 2, r); 1257 | ctx.fillStyle = this.color.toString(); 1258 | ctx.fill(); 1259 | ctx.strokeStyle = 'white'; 1260 | ctx.stroke(); 1261 | 1262 | ctx.fillStyle = 'white'; 1263 | ctx.font = '8pt Verdana'; 1264 | w = ctx.measureText(this.value).width; 1265 | ctx.fillText(this.value, x1 + ((x2 - x1 - 4) / 2 - (w / 2)), y1 + th / 2); 1266 | }; 1267 | 1268 | jsc.Watcher.prototype.drawRoundedRect = function (ctx, x1, y1, x2, y2, r) { 1269 | ctx.beginPath(); 1270 | ctx.moveTo(x1 + r, y1); 1271 | ctx.arcTo(x2, y1, x2, y2, r); 1272 | ctx.lineTo(x2, y2 - r); 1273 | ctx.arcTo(x2, y2, x1, y2, r); 1274 | ctx.lineTo(x1 + r, y2); 1275 | ctx.arcTo(x1, y2, x1, y1, r); 1276 | ctx.lineTo(x1, y1 + r); 1277 | ctx.arcTo(x1, y1, x2, y1, r); 1278 | ctx.closePath(); 1279 | }; 1280 | 1281 | 1282 | // Thread ///////////////////////////////////////////////// 1283 | jsc.Thread = function (object, script) { 1284 | this.init(object, script); 1285 | } 1286 | 1287 | jsc.Thread.prototype.init = function (object, script) { 1288 | this.object = object; 1289 | this.hat = script[0]; 1290 | if (this.hat[0] === 'KeyEventHatMorph') { 1291 | var keys = { 1292 | "space": 32, 1293 | "up arrow": 38, 1294 | "down arrow": 40, 1295 | "right arrow": 39, 1296 | "left arrow": 37, 1297 | "up": 38, 1298 | "down": 40, 1299 | "right": 39, 1300 | "left": 37 1301 | }; 1302 | this.hat[1] = keys[this.hat[1]] || this.hat[1].toUpperCase().charCodeAt(0); 1303 | } 1304 | this.colors = []; 1305 | this.wholeScript = this.script = this.compile(script.slice(1, script.length)); 1306 | this.done = true; 1307 | }; 1308 | 1309 | jsc.Thread.prototype.eval = function (script) { 1310 | var self = this.object; 1311 | var colors = this.colors; 1312 | var c = jsc.castNumber; 1313 | return eval(script); 1314 | }; 1315 | 1316 | jsc.Thread.prototype.compile = function (script) { 1317 | if (script === null) { 1318 | return null; 1319 | } 1320 | var self = this.object; 1321 | var compiled = []; 1322 | var string = null; 1323 | var selector; 1324 | for (var i = 0; i < script.length; i++) { 1325 | selector = script[i][0]; 1326 | if (this.specialBlocks.indexOf(selector) !== -1) { 1327 | if (string !== null) { 1328 | compiled.push(this.eval(string + '})')); 1329 | } 1330 | string = null; 1331 | compiled.push(this.compileSpecial(script[i])); 1332 | continue; 1333 | } else if (string === null) { 1334 | string = '(function(){'; 1335 | } 1336 | 1337 | var special = this.compileSpecialCommand(script[i]); 1338 | if (special !== null) { 1339 | string += special; 1340 | } else { 1341 | var command = this.object.getCommandFunctionName(selector); 1342 | 1343 | if (command === null) { 1344 | continue; 1345 | } 1346 | 1347 | string += 'self.' + command + '('; 1348 | for (var j = 1; j < script[i].length; j++) { 1349 | string += this.compileArg(script[i][j]); 1350 | if (j !== script[i].length - 1) { 1351 | string += ','; 1352 | } 1353 | } 1354 | string += ');'; 1355 | } 1356 | } 1357 | if (string !== null) { 1358 | compiled.push(this.eval(string + '})')); 1359 | } 1360 | return compiled; 1361 | }; 1362 | 1363 | jsc.Thread.prototype.compileSpecialCommand = function (command) { 1364 | switch (command[0]) { 1365 | case 'changeVariable': 1366 | return 'self.changeVariable(' + this.compileArg(command[1]) + ',' + ((command[2] === 'changeVar:by:') ? 'true' : 'false') + ',' + this.compileArg(command[3], true) + ');'; 1367 | case 'comment:': 1368 | return ''; 1369 | case 'readVariable': 1370 | return 'self.allVariables[' + this.compileArg(command[1]) + '].val;'; 1371 | } 1372 | return null; 1373 | }; 1374 | 1375 | jsc.Thread.prototype.compileArg = function (arg, preferNumber) { 1376 | if (typeof arg === 'number') { 1377 | return arg; 1378 | } 1379 | if (typeof arg === 'string') { 1380 | /*if (preferNumber) { 1381 | var num = jsc.castNumberOrNull(arg); 1382 | if (num !== null) { 1383 | return arg; 1384 | } 1385 | }*/ 1386 | return '\'' + this.escapeString(arg) + '\''; 1387 | } 1388 | if (arg instanceof jsc.Sprite) { 1389 | return '\'' + arg.objName + '\''; 1390 | } 1391 | if (arg instanceof jsc.Stage) { 1392 | return 'self.stage'; 1393 | } 1394 | if (arg instanceof jsc.Color) { 1395 | //return 'new jsc.Color(' + arg.r + ',' + arg.g + ',' + arg.b + ',' + arg.a + ')'; 1396 | this.colors.push(arg); 1397 | return 'colors[' + (this.colors.length - 1) + ']'; 1398 | } 1399 | if (!(arg instanceof Array)) { 1400 | return arg; 1401 | } 1402 | 1403 | var special = this.compileSpecialArg(arg); 1404 | if (special !== null) { 1405 | return special; 1406 | } 1407 | 1408 | var reporter = this.object.getReporterFunctionName(arg[0]); 1409 | 1410 | if (reporter === null) { 1411 | return '0'; 1412 | } 1413 | 1414 | var string = 'self.' + reporter + '('; 1415 | for (var i = 1; i < arg.length; i++) { 1416 | string += this.compileArg(arg[i]); 1417 | if (i !== arg.length - 1) { 1418 | string += ','; 1419 | } 1420 | } 1421 | return string + ')'; 1422 | }; 1423 | 1424 | jsc.Thread.prototype.compileSpecialArg = function (arg) { 1425 | switch (arg[0]) { 1426 | case '+': 1427 | return '(c(' + this.compileArg(arg[1]) + ') + c(' + this.compileArg(arg[2]) + '))'; 1428 | case '-': 1429 | return '(c(' + this.compileArg(arg[1]) + ') - c(' + this.compileArg(arg[2]) + '))'; 1430 | case '*': 1431 | return '(c(' + this.compileArg(arg[1]) + ') * c(' + this.compileArg(arg[2]) + '))'; 1432 | case '/': 1433 | return '(c(' + this.compileArg(arg[1]) + ') / c(' + this.compileArg(arg[2]) + '))'; 1434 | 1435 | case '\\\\': 1436 | return '(c(' + this.compileArg(arg[1]) + ').mod(c(' + this.compileArg(arg[2]) + ')))'; 1437 | 1438 | case 'abs': 1439 | return 'Math.abs(' + this.compileArg(arg[1], true) + ')'; 1440 | 1441 | case 'computeFunction:of:': 1442 | var f = arg[1]; 1443 | var v = this.compileArg(arg[2], true); 1444 | 1445 | switch (f.toString().toLowerCase()) { 1446 | case 'abs': 1447 | return 'Math.abs(' + v + ')'; 1448 | case 'sqrt': 1449 | return 'Math.sqrt(' + v + ')'; 1450 | case 'sin': 1451 | return 'Math.sin(Math.PI/180*' + v + ')'; 1452 | case 'cos': 1453 | return 'Math.cos(Math.PI/180*' + v + ')'; 1454 | case 'tan': 1455 | return 'Math.tan(Math.PI/180*' + v + ')'; 1456 | case 'asin': 1457 | return '180/Math.PI*Math.asin(Math.max(-1,Math.min(1,' + v + ')))'; 1458 | case 'acos': 1459 | return '180/Math.PI*Math.acos(Math.max(-1,Math.min(1,' + v + ')))'; 1460 | case 'atan': 1461 | return '180/Math.PI*Math.atan(' + v + ')'; 1462 | case 'ln': 1463 | return 'Math.log(' + v + ')'; 1464 | case 'log': 1465 | return 'Math.log(' + v + ')'; 1466 | case 'e ^': 1467 | return 'Math.pow(Math.E,' + v + ')'; 1468 | case '10 ^': 1469 | return 'Math.pow(10,' + v + ')'; 1470 | } 1471 | 1472 | case 'answer': 1473 | return 'this.stage.answer'; 1474 | } 1475 | return null; 1476 | }; 1477 | 1478 | jsc.Thread.prototype.escapeString = function (string) { 1479 | return string.replace(/\\/g, '\\\\'). 1480 | replace(/\u0008/g, '\\b'). 1481 | replace(/\t/g, '\\t'). 1482 | replace(/\n/g, '\\n'). 1483 | replace(/\f/g, '\\f'). 1484 | replace(/\r/g, '\\r'). 1485 | replace(/'/g, '\\\''). 1486 | replace(/"/g, '\\"'); 1487 | }; 1488 | 1489 | jsc.Thread.prototype.compileReporter = function (predicate) { 1490 | var self = this.object; 1491 | return this.eval('(function(){return ' + this.compileArg(predicate) + '})'); 1492 | }; 1493 | 1494 | jsc.Thread.prototype.specialBlocks = ['doIf', 'doPlaySoundAndWait', 'doBroadcastAndWait', 'doIfElse', 'doRepeat', 'doUntil', 'doForever', 'doForeverIf', 'doReturn', 'doWaitUntil', 'wait:elapsed:from:', 'glideSecs:toX:y:elapsed:from:', 'doAsk']; 1495 | 1496 | jsc.Thread.prototype.compileSpecial = function (special) { 1497 | var compiled; 1498 | switch (special[0]) { 1499 | case 'wait:elapsed:from:': 1500 | compiled = [this.compileReporter(special[1])]; 1501 | break; 1502 | case 'doForever': 1503 | compiled = [this.compile(special[1])]; 1504 | break; 1505 | case 'doIf': 1506 | compiled = [this.compileReporter(special[1]), this.compile(special[2])]; 1507 | break; 1508 | case 'doIfElse': 1509 | compiled = [this.compileReporter(special[1]), this.compile(special[2]), this.compile(special[3])]; 1510 | break; 1511 | case 'doUntil': 1512 | compiled = [this.compileReporter(special[1]), this.compile(special[2])]; 1513 | break; 1514 | case 'doRepeat': 1515 | compiled = [this.compileReporter(special[1]), this.compile(special[2])]; 1516 | break; 1517 | case 'doWaitUntil': 1518 | compiled = [this.compileReporter(special[1])]; 1519 | break; 1520 | case 'doBroadcastAndWait': 1521 | compiled = [this.compileReporter(special[1])]; 1522 | break; 1523 | case 'doForeverIf': 1524 | compiled = [this.compileReporter(special[1]), this.compile(special[2])]; 1525 | break; 1526 | case 'doReturn': 1527 | compiled = []; 1528 | break; 1529 | case 'doPlaySoundAndWait': 1530 | compiled = [this.compileReporter(special[1])]; 1531 | break; 1532 | case 'glideSecs:toX:y:elapsed:from:': 1533 | compiled = [this.compileReporter(special[1]), this.compileReporter(special[2]), this.compileReporter(special[3])]; 1534 | break; 1535 | case 'doAsk': 1536 | 1537 | } 1538 | return [this.specialBlocks.indexOf(special[0])].concat(compiled); 1539 | }; 1540 | 1541 | jsc.Thread.prototype.start = function () { 1542 | this.index = 0; 1543 | this.stack = []; 1544 | this.done = this.yield = false; 1545 | this.timer = null; 1546 | this.temp = null; 1547 | this.script = this.wholeScript; 1548 | }; 1549 | 1550 | jsc.Thread.prototype.stop = function () { 1551 | this.index = 0; 1552 | this.stack = []; 1553 | this.done = this.yield = true; 1554 | this.timer = null; 1555 | this.temp = null; 1556 | this.script = this.wholeScript; 1557 | }; 1558 | 1559 | jsc.Thread.prototype.step = function () { 1560 | if (this.done) { 1561 | return; 1562 | } 1563 | 1564 | this.yield = false; 1565 | 1566 | while (!this.yield && !this.done) { 1567 | if (this.index >= this.script.length) { 1568 | if (this.stack.length == 0) { 1569 | this.done = true; 1570 | } else { 1571 | this.popState(); 1572 | } 1573 | } else { 1574 | this.evalCommand(this.script[this.index]); 1575 | this.index++; 1576 | } 1577 | } 1578 | }; 1579 | 1580 | jsc.Thread.prototype.evalCommand = function (block) { 1581 | if (typeof block === 'function') { 1582 | block(); 1583 | return; 1584 | } 1585 | 1586 | this[block[0]](block); 1587 | }; 1588 | 1589 | jsc.Thread.prototype[0] = function (block) { 1590 | if (block[1]()) { 1591 | this.evalCommandList(false, block[2]); 1592 | } 1593 | }; 1594 | 1595 | jsc.Thread.prototype[1] = function (block) { 1596 | if (this.temp === null) { 1597 | this.temp = this.object.getSound(block[1]()); 1598 | this.temp.play(this.object.volume); 1599 | this.evalCommandList(true); 1600 | return; 1601 | } 1602 | if (this.temp.playing) { 1603 | this.evalCommandList(true); 1604 | } else { 1605 | this.reset(); 1606 | } 1607 | }; 1608 | 1609 | jsc.Thread.prototype[2] = function (block) { 1610 | var self = this; 1611 | if (this.temp === null) { 1612 | this.temp = block[1]().toString().toLowerCase(); 1613 | this.object.stage.addBroadcastToQueue(this.temp); 1614 | this.evalCommandList(true); 1615 | return; 1616 | } 1617 | 1618 | var threads = this.object.stage.eventsByName[this.temp]; 1619 | 1620 | if (threads) { 1621 | for (var i = 0; i < threads.length; i++) { 1622 | if (!threads[i].done) { 1623 | this.evalCommandList(true); 1624 | return; 1625 | } 1626 | } 1627 | } 1628 | 1629 | this.reset(); 1630 | }; 1631 | 1632 | jsc.Thread.prototype[3] = function (block) { 1633 | this.evalCommandList(false, block[1]() ? block[2] : block[3]); 1634 | }; 1635 | 1636 | jsc.Thread.prototype[4] = function (block) { 1637 | if (this.temp === null) { 1638 | this.temp = Math.round(jsc.castNumber(block[1]())); 1639 | } 1640 | if (this.temp <= 0) { 1641 | this.reset(); 1642 | return; 1643 | } 1644 | 1645 | this.temp--; 1646 | this.evalCommandList(true, block[2]); 1647 | }; 1648 | 1649 | jsc.Thread.prototype[5] = function (block) { 1650 | if (!block[1]()) { 1651 | this.evalCommandList(true, block[2]); 1652 | } 1653 | }; 1654 | 1655 | jsc.Thread.prototype[6] = function (block) { 1656 | this.evalCommandList(true, block[1]); 1657 | }; 1658 | 1659 | jsc.Thread.prototype[7] = function (block) { 1660 | this.evalCommandList(true, block[1]() ? block[2] : null); 1661 | }; 1662 | 1663 | jsc.Thread.prototype[8] = function (block) { 1664 | this.stop(); 1665 | }; 1666 | 1667 | jsc.Thread.prototype[9] = function (block) { 1668 | if (!block[1]()) { 1669 | this.evalCommandList(true); 1670 | } 1671 | }; 1672 | 1673 | jsc.Thread.prototype[10] = function (block) { 1674 | if (!this.timer) { 1675 | this.timer = new jsc.Stopwatch(); 1676 | this.evalCommandList(true); 1677 | return; 1678 | } else if (this.timer.getElapsed() < jsc.castNumber(block[1]()) * 1000) { 1679 | this.evalCommandList(true); 1680 | return; 1681 | } 1682 | this.reset(); 1683 | }; 1684 | 1685 | jsc.Thread.prototype[11] = function (block) { 1686 | if (!this.temp) { 1687 | this.timer = new jsc.Stopwatch(); 1688 | this.temp = [this.object.position, this.object.stage.fromScratchCoords(new jsc.Point(jsc.castNumber(block[2]()), jsc.castNumber(block[3]()))), jsc.castNumber(block[1]())]; 1689 | } else if (this.timer.getElapsed() < this.temp[2] * 1000) { 1690 | this.object.position = this.temp[0].subtract(this.temp[1]).multiplyBy(this.timer.getElapsed() / -1000 / this.temp[2]).add(this.temp[0]); 1691 | } else { 1692 | this.object.position = this.temp[1]; 1693 | this.reset(); 1694 | return; 1695 | } 1696 | this.evalCommandList(true); 1697 | }; 1698 | 1699 | jsc.Thread.prototype.evalCommandList = function (repeat, commands) { 1700 | if (repeat) { 1701 | this.yield = true; 1702 | } else { 1703 | this.index++; 1704 | this.timer = null; 1705 | this.temp = null; 1706 | } 1707 | this.pushState(); 1708 | this.yield = false; 1709 | this.script = commands || []; 1710 | this.index = -1; 1711 | this.timer = null; 1712 | this.temp = null; 1713 | }; 1714 | 1715 | jsc.Thread.prototype.reset = function () { 1716 | this.timer = null; 1717 | this.temp = null; 1718 | }; 1719 | 1720 | jsc.Thread.prototype.pushState = function () { 1721 | this.stack.push({ 1722 | yield: this.yield, 1723 | script: this.script, 1724 | index: this.index, 1725 | timer: this.timer, 1726 | temp: this.temp 1727 | }); 1728 | }; 1729 | 1730 | jsc.Thread.prototype.popState = function () { 1731 | if (this.stack.length == 0) { 1732 | this.script = []; 1733 | this.index = 0; 1734 | this.done = this.yield = true; 1735 | return; 1736 | } 1737 | 1738 | var oldState = this.stack.pop(); 1739 | this.yield = oldState.yield; 1740 | this.script = oldState.script; 1741 | this.index = oldState.index; 1742 | this.timer = oldState.timer; 1743 | this.temp = oldState.temp; 1744 | }; 1745 | 1746 | 1747 | // Stopwatch ////////////////////////////////////////////// 1748 | jsc.Stopwatch = function () { 1749 | this.init(); 1750 | } 1751 | 1752 | jsc.Stopwatch.prototype.init = function () { 1753 | this.startTime = Date.now(); 1754 | }; 1755 | 1756 | jsc.Stopwatch.prototype.reset = function () { 1757 | this.startTime = Date.now(); 1758 | }; 1759 | 1760 | jsc.Stopwatch.prototype.getElapsed = function () { 1761 | return Date.now() - this.startTime; 1762 | }; 1763 | 1764 | 1765 | // ScratchMedia /////////////////////////////////////////// 1766 | jsc.ScratchMedia = function () { 1767 | this.name; 1768 | } 1769 | 1770 | jsc.ScratchMedia.prototype.initFields = function (fields, version) { 1771 | jsc.initFieldsNamed.call(this, ['name'], fields); 1772 | }; 1773 | 1774 | 1775 | // ImageMedia ///////////////////////////////////////////// 1776 | jsc.ImageMedia = function () { 1777 | this.colorCache = {}; 1778 | } 1779 | 1780 | jsc.ImageMedia.prototype = new jsc.ScratchMedia(); 1781 | jsc.ImageMedia.prototype.constructor = jsc.ImageMedia; 1782 | jsc.ImageMedia.uber = jsc.ScratchMedia.prototype; 1783 | 1784 | jsc.ImageMedia.prototype.initFields = function (fields, version) { 1785 | jsc.ImageMedia.uber.initFields.call(this, fields, version); 1786 | jsc.initFieldsNamed.call(this, ['form', 'rotationCenter'], fields); 1787 | if (version == 1) return; 1788 | jsc.initFieldsNamed.call(this, ['textBox'], fields); 1789 | if (version == 2) return; 1790 | jsc.initFieldsNamed.call(this, ['jpegBytes'], fields); 1791 | if (version == 3) return; 1792 | this.form = fields.nextField() || this.form; 1793 | }; 1794 | 1795 | jsc.ImageMedia.prototype.initBeforeLoad = function () { 1796 | if(this.jpegBytes) { 1797 | var str = ''; 1798 | for (var i = 0; i < this.jpegBytes.length; i++) { 1799 | str += String.fromCharCode(this.jpegBytes[i]); 1800 | } 1801 | this.base64 = 'data:image/jpeg;base64,' + btoa(str); 1802 | } 1803 | if (this.base64) { 1804 | this.image = jsc.newImage(this.base64); 1805 | } else { 1806 | this.image = null; 1807 | } 1808 | }; 1809 | 1810 | jsc.ImageMedia.prototype.getImage = function () { 1811 | if (!this.image) { 1812 | this.image = this.form.getImage(); 1813 | } 1814 | return this.image; 1815 | }; 1816 | 1817 | jsc.ImageMedia.prototype.extent = function () { 1818 | return this.form.extent(); 1819 | }; 1820 | 1821 | jsc.ImageMedia.prototype.center = function () { 1822 | this.getImage(); 1823 | return new jsc.Point(this.image.width / 2, this.image.height / 2); 1824 | }; 1825 | 1826 | 1827 | // SoundMedia ///////////////////////////////////////////// 1828 | jsc.SoundMedia = function () { 1829 | 1830 | } 1831 | 1832 | jsc.SoundMedia.prototype = new jsc.ScratchMedia(); 1833 | jsc.SoundMedia.prototype.constructor = jsc.SoundMedia; 1834 | jsc.SoundMedia.uber = jsc.ScratchMedia.prototype; 1835 | 1836 | jsc.SoundMedia.prototype.initFields = function (fields, version) { 1837 | jsc.SoundMedia.uber.initFields.call(this, fields, version); 1838 | jsc.initFieldsNamed.call(this, ['originalSound', 'volume', 'balance'], fields); 1839 | if (version == 1) return; 1840 | jsc.initFieldsNamed.call(this, ['compressedSampleRate', 'compressedBitsPerSample', 'compressedData'], fields); 1841 | }; 1842 | 1843 | jsc.SoundMedia.prototype.initBeforeLoad = function () { 1844 | var self = this; 1845 | this.audio = new Audio(); 1846 | this.audio.addEventListener('ended', function () { 1847 | self.playing = false; 1848 | }, false); 1849 | 1850 | if (this.compressedData) { 1851 | this.decompress(); 1852 | this.sampleRate = this.compressedSampleRate; 1853 | } else { 1854 | this.samples = this.originalSound.samples; 1855 | for (var i = 0; i < this.samples.length; i += 2) { 1856 | var swap = this.samples[i]; 1857 | this.samples[i] = this.samples[i + 1]; 1858 | this.samples[i + 1] = swap; 1859 | } 1860 | this.sampleRate = 22050; 1861 | } 1862 | 1863 | this.bitsPerSample = 16 1864 | 1865 | this.audio.src = jsc.createWave(this.samples, this.sampleRate, this.bitsPerSample); 1866 | this.playing = false; 1867 | }; 1868 | 1869 | jsc.SoundMedia.prototype.stop = function () { 1870 | this.audio.pause(); 1871 | try { 1872 | this.audio.currentTime = 0; 1873 | } catch (e) {} 1874 | this.playing = false; 1875 | }; 1876 | 1877 | jsc.SoundMedia.prototype.play = function (volume) { 1878 | this.stop(); 1879 | this.audio.volume = Math.max(Math.min(volume / 100, 1), 0); 1880 | this.audio.play(); 1881 | this.playing = true; 1882 | }; 1883 | 1884 | jsc.SoundMedia.prototype.setVolume = function (volume) { 1885 | this.audio.volume = volume; 1886 | }; 1887 | 1888 | jsc.SoundMedia.prototype.decompress = function () { 1889 | var stepSizeTable = [7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767]; 1890 | 1891 | var indices = [ 1892 | [-1, 2], 1893 | [-1, -1, 2, 4], 1894 | [-1, -1, -1, -1, 2, 4, 6, 8], 1895 | [-1, -1, -1, -1, -1, -1, -1, -1, 1, 2, 4, 6, 8, 10, 13, 16] 1896 | ]; 1897 | 1898 | var indexTable = indices[this.compressedBitsPerSample - 2]; 1899 | var soundData = this.compressedData; 1900 | var bitsPerSample = this.compressedBitsPerSample; 1901 | 1902 | var l5 = 0; 1903 | var l6 = 0; 1904 | var l7 = 0; 1905 | var l8 = 0; 1906 | var l2 = []; 1907 | var l3 = 0; 1908 | var l4 = 0; 1909 | 1910 | var index = 0; 1911 | 1912 | var bitPosition = 0; 1913 | var currentByte = 0; 1914 | 1915 | var signMask = 1 << (bitsPerSample - 1); 1916 | var valueMask = signMask - 1; 1917 | var valueHighBit = signMask >> 1; 1918 | 1919 | while (true) { 1920 | l5 = nextCode.call(this); 1921 | if (l5 < 0) { 1922 | break; 1923 | } 1924 | l6 = stepSizeTable[l4]; 1925 | l7 = 0; 1926 | l8 = valueHighBit; 1927 | while (l8 > 0) { 1928 | if ((l5 & l8) !== 0) { 1929 | l7 += l6; 1930 | } 1931 | l6 = l6 >> 1; 1932 | l8 = l8 >> 1; 1933 | } 1934 | l7 += l6; 1935 | l3 += ((l5 & signMask) === 0) ? l7 : -l7 1936 | l4 += indexTable[l5 & valueMask]; 1937 | l4 = Math.min(Math.max(l4, 0), 88); 1938 | l3 = Math.min(Math.max(l3, -32768), 32767); 1939 | l2.push(l3 & 255); 1940 | l2.push((l3 >> 8) & 255); 1941 | } 1942 | 1943 | function nextCode() { 1944 | var j4 = 0; 1945 | var j2 = 0; 1946 | var j3 = bitsPerSample; 1947 | 1948 | while (true) { 1949 | j4 = j3 - bitPosition; 1950 | j2 += j4 < 0 ? currentByte >> -j4 : currentByte << j4; 1951 | if (j4 > 0) { 1952 | j3 -= bitPosition; 1953 | if (index < soundData.length) { 1954 | currentByte = soundData[index++]; 1955 | bitPosition = 8; 1956 | } else { 1957 | currentByte = 0; 1958 | bitPosition = 0; 1959 | return -1; 1960 | } 1961 | } else { 1962 | bitPosition -= j3; 1963 | currentByte = currentByte & (255 >> (8 - bitPosition)); 1964 | break; 1965 | } 1966 | } 1967 | return j2; 1968 | } 1969 | this.samples = l2; 1970 | }; 1971 | 1972 | // SampledSound /////////////////////////////////////////// 1973 | jsc.SampledSound = function () { 1974 | 1975 | } 1976 | 1977 | jsc.SampledSound.prototype = new jsc.ScratchMedia(); 1978 | jsc.SampledSound.prototype.constructor = jsc.SampledSound; 1979 | 1980 | jsc.SampledSound.prototype.initFields = function (fields, version) { 1981 | jsc.initFieldsNamed.call(this, ['envelopes', 'scaledVol', 'initialCount', 'samples', 'originalSamplingRate', 'samplesSize', 'scaledIncrement', 'scaledInitialIndex'], fields); 1982 | }; 1983 | 1984 | 1985 | jsc.squeakColors = [new jsc.Color(255, 255, 255), 1986 | new jsc.Color(0, 0, 0), 1987 | new jsc.Color(255, 255, 255), 1988 | new jsc.Color(128, 128, 128), 1989 | new jsc.Color(255, 0, 0), 1990 | new jsc.Color(0, 255, 0), 1991 | new jsc.Color(0, 0, 255), 1992 | new jsc.Color(0, 255, 255), 1993 | new jsc.Color(255, 255, 0), 1994 | new jsc.Color(255, 0, 255), 1995 | new jsc.Color(32, 32, 32), 1996 | new jsc.Color(64, 64, 64), 1997 | new jsc.Color(96, 96, 96), 1998 | new jsc.Color(159, 159, 159), 1999 | new jsc.Color(191, 191, 191), 2000 | new jsc.Color(223, 223, 223), 2001 | new jsc.Color(8, 8, 8), 2002 | new jsc.Color(16, 16, 16), 2003 | new jsc.Color(24, 24, 24), 2004 | new jsc.Color(40, 40, 40), 2005 | new jsc.Color(48, 48, 48), 2006 | new jsc.Color(56, 56, 56), 2007 | new jsc.Color(72, 72, 72), 2008 | new jsc.Color(80, 80, 80), 2009 | new jsc.Color(88, 88, 88), 2010 | new jsc.Color(104, 104, 104), 2011 | new jsc.Color(112, 112, 112), 2012 | new jsc.Color(120, 120, 120), 2013 | new jsc.Color(135, 135, 135), 2014 | new jsc.Color(143, 143, 143), 2015 | new jsc.Color(151, 151, 151), 2016 | new jsc.Color(167, 167, 167), 2017 | new jsc.Color(175, 175, 175), 2018 | new jsc.Color(183, 183, 183), 2019 | new jsc.Color(199, 199, 199), 2020 | new jsc.Color(207, 207, 207), 2021 | new jsc.Color(215, 215, 215), 2022 | new jsc.Color(231, 231, 231), 2023 | new jsc.Color(239, 239, 239), 2024 | new jsc.Color(247, 247, 247), 2025 | new jsc.Color(0, 0, 0), 2026 | new jsc.Color(0, 51, 0), 2027 | new jsc.Color(0, 102, 0), 2028 | new jsc.Color(0, 153, 0), 2029 | new jsc.Color(0, 204, 0), 2030 | new jsc.Color(0, 255, 0), 2031 | new jsc.Color(0, 0, 51), 2032 | new jsc.Color(0, 51, 51), 2033 | new jsc.Color(0, 102, 51), 2034 | new jsc.Color(0, 153, 51), 2035 | new jsc.Color(0, 204, 51), 2036 | new jsc.Color(0, 255, 51), 2037 | new jsc.Color(0, 0, 102), 2038 | new jsc.Color(0, 51, 102), 2039 | new jsc.Color(0, 102, 102), 2040 | new jsc.Color(0, 153, 102), 2041 | new jsc.Color(0, 204, 102), 2042 | new jsc.Color(0, 255, 102), 2043 | new jsc.Color(0, 0, 153), 2044 | new jsc.Color(0, 51, 153), 2045 | new jsc.Color(0, 102, 153), 2046 | new jsc.Color(0, 153, 153), 2047 | new jsc.Color(0, 204, 153), 2048 | new jsc.Color(0, 255, 153), 2049 | new jsc.Color(0, 0, 204), 2050 | new jsc.Color(0, 51, 204), 2051 | new jsc.Color(0, 102, 204), 2052 | new jsc.Color(0, 153, 204), 2053 | new jsc.Color(0, 204, 204), 2054 | new jsc.Color(0, 255, 204), 2055 | new jsc.Color(0, 0, 255), 2056 | new jsc.Color(0, 51, 255), 2057 | new jsc.Color(0, 102, 255), 2058 | new jsc.Color(0, 153, 255), 2059 | new jsc.Color(0, 204, 255), 2060 | new jsc.Color(0, 255, 255), 2061 | new jsc.Color(51, 0, 0), 2062 | new jsc.Color(51, 51, 0), 2063 | new jsc.Color(51, 102, 0), 2064 | new jsc.Color(51, 153, 0), 2065 | new jsc.Color(51, 204, 0), 2066 | new jsc.Color(51, 255, 0), 2067 | new jsc.Color(51, 0, 51), 2068 | new jsc.Color(51, 51, 51), 2069 | new jsc.Color(51, 102, 51), 2070 | new jsc.Color(51, 153, 51)]; 2071 | 2072 | //[16777215, 0, 16777215, 8421504, 16711680, 65280, 255, 65535, 16776960, 16711935, 2105376, 4210752, 6316128, 10461087, 12566463, 14671839, 526344, 1052688, 1579032, 2631720, 3158064, 3684408, 4737096, 5263440, 5789784, 6842472, 7368816, 7895160, 8882055, 9408399, 9934743, 10987431, 11513775, 12040119, 13092807, 13619151, 14145495, 15198183, 15724527, 16250871, 0, 13056, 26112, 39168, 52224, 65280, 51, 13107, 26163, 39219, 52275, 65331, 102, 13158, 26214, 39270, 52326, 65382, 153, 13209, 26265, 39321, 52377, 65433, 204, 13260, 26316, 39372, 52428, 65484, 255, 13311, 26367, 39423, 52479, 65535, 3342336, 3355392, 3368448, 3381504, 3394560, 3407616, 3342387, 3355443, 3368499, 3381555] 2073 | }) (jsc); 2074 | --------------------------------------------------------------------------------