kopia lustrzana https://github.com/backface/turtlestitch
				
				
				
			
		
			
				
	
	
		
			1445 wiersze
		
	
	
		
			48 KiB
		
	
	
	
		
			JavaScript
		
	
	
			
		
		
	
	
			1445 wiersze
		
	
	
		
			48 KiB
		
	
	
	
		
			JavaScript
		
	
	
| /*
 | |
| 
 | |
| Copyright 2012 Nathan Dinsmore
 | |
| 
 | |
| Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 | |
| 
 | |
| The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 | |
| 
 | |
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 | |
| 
 | |
| Last changed 2013-04-03 by Jens Moenig (disabled text area overlay)
 | |
| 
 | |
| */
 | |
| 
 | |
| var sb = (function (sb) {
 | |
|     'use strict';
 | |
| 
 | |
|     function extend(o, p) {
 | |
|         var key;
 | |
|         for (key in p) if (p.hasOwnProperty(key)) {
 | |
|             o[key] = p[key];
 | |
|         }
 | |
|     }
 | |
|     sb.$extend = extend;
 | |
| 
 | |
|     extend((sb.Ref = function (id) {
 | |
|         this.id = id;
 | |
|     }).prototype, {
 | |
|         isRef: true
 | |
|     });
 | |
| 
 | |
|     extend((sb.Dictionary = function (keys, values) {
 | |
|         this.keys = keys;
 | |
|         this.values = values;
 | |
|     }).prototype, {
 | |
|         get: function (key) {
 | |
|             return this.values[this.keys.indexOf(key)];
 | |
|         },
 | |
|         set: function (key, value) {
 | |
|             var i = this.keys.indexOf(key);
 | |
|             if (i === -1) {
 | |
|                 this.keys.push(key);
 | |
|                 this.values.push(value);
 | |
|             } else {
 | |
|                 this.values[i] = value;
 | |
|             }
 | |
|         }
 | |
|     });
 | |
| 
 | |
|     sb.Color = function (rgb, a) {
 | |
|         this.r = (rgb / 0x100000 | 0) % 0x400 / (0x400 - 1);
 | |
|         this.g = (rgb / 0x400 | 0) % 0x400 / (0x400 - 1);
 | |
|         this.b = (rgb % 0x400) / (0x400 - 1);
 | |
|         this.a = a / (0x100 - 1);
 | |
|     };
 | |
|     sb.Point = function (x, y) {
 | |
|         this.x = x;
 | |
|         this.y = y;
 | |
|     };
 | |
|     sb.Rectangle = function (x, y, x2, y2) {
 | |
|         this.origin = new sb.Point(x, y);
 | |
|         this.corner = new sb.Point(x2, y2);
 | |
|     };
 | |
| 
 | |
|     sb.indexedColors = [
 | |
|         [1, 1, 1, 1],
 | |
|         [0, 0, 0, 1],
 | |
|         [1, 1, 1, 1],
 | |
|         [.5, .5, .5, 1],
 | |
|         [1, 0, 0, 1],
 | |
|         [0, 1, 0, 1],
 | |
|         [0, 0, 1, 1],
 | |
|         [0, 1, 1, 1],
 | |
|         [1, 1, 0, 1],
 | |
|         [1, 0, 1, 1],
 | |
|         [.125, .125, .125, 1],
 | |
|         [.25, .25, .25, 1],
 | |
|         [.375, .375, .375, 1],
 | |
|         [.625, .625, .625, 1],
 | |
|         [.75, .75, .75, 1],
 | |
|         [.875, .875, .875, 1]
 | |
|     ];
 | |
|     (function () {
 | |
|         var i, r, g, b, grayVal;
 | |
|         for (i = 1; i <= 31; ++i) {
 | |
|             if (i % 4 != 0) {
 | |
|                 grayVal = i / 32;
 | |
|                 sb.indexedColors[i + 15] = [grayVal, grayVal, grayVal, 1];
 | |
|             }
 | |
|         }
 | |
|         for (r = 0; r < 6; ++r) {
 | |
|             for (g = 0; g < 6; ++g) {
 | |
|                 for (b = 0; b < 6; ++b) {
 | |
|                     i = 40 + 36 * r + 6 * b + g;
 | |
|                     sb.indexedColors[i] = [r / 5, g / 5, b / 5, 1];
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     })();
 | |
| 
 | |
|     sb.colorDepthLengths = {
 | |
|         1: 1,
 | |
|         2: 2,
 | |
|         4: 4,
 | |
|         8: 8,
 | |
|         15: 5,
 | |
|         16: 5,
 | |
|         12: 4,
 | |
|         9: 3
 | |
|     };
 | |
|     extend((sb.ColorStream = function (bitmap, depth) {
 | |
|         var i = 0,
 | |
|             l = bitmap.length,
 | |
|             b = this.bits = [],
 | |
|             n;
 | |
|         this.length = sb.colorDepthLengths[this.depth = depth];
 | |
|         this.position = 0;
 | |
|         while (i < l) {
 | |
|             n = bitmap[i++];
 | |
|             b.push(n / 0x80 & 1);
 | |
|             b.push(n / 0x40 & 1);
 | |
|             b.push(n / 0x20 & 1);
 | |
|             b.push(n / 0x10 & 1);
 | |
|             b.push(n / 0x8 & 1);
 | |
|             b.push(n / 0x4 & 1);
 | |
|             b.push(n / 0x2 & 1);
 | |
|             b.push(n & 1);
 | |
|         }
 | |
|     }).prototype, {
 | |
|         read: function () {
 | |
|             var i = this.length,
 | |
|                 b = this.bits,
 | |
|                 n = 0;
 | |
|             while (i--) {
 | |
|                 n = n * 2 + b[this.position++];
 | |
|             }
 | |
|             return n;
 | |
|         }
 | |
|     });
 | |
| 
 | |
|     extend((sb.ColorStreamIndexed = function (bitmap, depth) {
 | |
|         sb.ColorStream.call(this, bitmap, depth);
 | |
|     }).prototype = Object.create(sb.ColorStream.prototype), {
 | |
|         next: function (b) {
 | |
|             var c = sb.indexedColors[this.read()];
 | |
|             if (!c) return c;
 | |
|             b[0] = c[0];
 | |
|             b[1] = c[1];
 | |
|             b[2] = c[2];
 | |
|             b[3] = c[3];
 | |
|         }
 | |
|     });
 | |
| 
 | |
|     extend((sb.ColorStreamRGB = function (bitmap, depth) {
 | |
|         sb.ColorStream.call(this, bitmap, depth);
 | |
|         this.max = Math.pow(2, this.length) - 1;
 | |
|     }).prototype = Object.create(sb.ColorStream.prototype), {
 | |
|         next: function (b) {
 | |
|             var c;
 | |
|             switch (this.depth) {
 | |
|             case 16:
 | |
|                 ++this.position;
 | |
|                 c = [this.read() / this.max, this.read() / this.max, this.read() / this.max, 1];
 | |
|                 if (c[0] + c[1] + c[2] === 0) {
 | |
|                     return b[0] = b[1] = b[2] = b[3] = 0;
 | |
|                 }
 | |
|                 b[0] = c[0];
 | |
|                 b[1] = c[1];
 | |
|                 b[2] = c[2];
 | |
|                 b[3] = c[3];
 | |
|                 return;
 | |
|             case 12:
 | |
|                 this.position += 3;
 | |
|                 break;
 | |
|             }
 | |
|             b[0] = this.read() / this.max;
 | |
|             b[1] = this.read() / this.max;
 | |
|             b[2] = this.read() / this.max;
 | |
|             b[3] = 1;
 | |
|         }
 | |
|     });
 | |
| 
 | |
|     extend((sb.ColorStream32 = function (bitmap, depth) {
 | |
|         this.bytes = bitmap;
 | |
|         if (depth === 32) {
 | |
|             this.next = this.nextAlpha;
 | |
|         }
 | |
|         this.position = 0;
 | |
|     }).prototype, {
 | |
|         next: function (b) {
 | |
|             b[0] = this.read() / 255;
 | |
|             b[1] = this.read() / 255;
 | |
|             b[2] = this.read() / 255;
 | |
|             b[3] = 1;
 | |
|         },
 | |
|         nextAlpha: function (b) {
 | |
|             var a = this.read() / 255;
 | |
|             b[0] = this.read() / 255;
 | |
|             b[1] = this.read() / 255;
 | |
|             b[2] = this.read() / 255
 | |
|             b[3] = a;
 | |
|         },
 | |
|         read: function () {
 | |
|             return this.bytes[this.position++];
 | |
|         }
 | |
|     });
 | |
| 
 | |
|     sb.colorStreams = {
 | |
|         1: sb.ColorStreamIndexed,
 | |
|         2: sb.ColorStreamIndexed,
 | |
|         4: sb.ColorStreamIndexed,
 | |
|         8: sb.ColorStreamIndexed,
 | |
|         15: sb.ColorStreamRGB,
 | |
|         16: sb.ColorStreamRGB,
 | |
|         32: sb.ColorStream32,
 | |
|         24: sb.ColorStream32,
 | |
|         12: sb.ColorStreamRGB,
 | |
|         9: sb.ColorStreamRGB
 | |
|     };
 | |
| 
 | |
|     sb.getColorStream = function (bitmap, depth) {
 | |
|         return new sb.colorStreams[depth](bitmap, depth);
 | |
|     };
 | |
| 
 | |
|     extend((sb.Form = function (w, h, d, o, b) {
 | |
|         this.width = w;
 | |
|         this.height = h;
 | |
|         this.depth = d;
 | |
|         this.offset = o;
 | |
|         this.bits = b;
 | |
|     }).prototype, {
 | |
|         init: function (bm) {
 | |
|             if (this.bits.isBitmap) {
 | |
|                 this.bitmap = this.bits;
 | |
|             } else {
 | |
|                 this.decompress(bm);
 | |
|             }
 | |
|             this.image = document.createElement('canvas');
 | |
|             this.image.width = this.width;
 | |
|             this.image.height = this.height;
 | |
|         },
 | |
|         decompress: function (bm) {
 | |
|             var b = this.bits,
 | |
|                 p = 0,
 | |
|                 q = 0,
 | |
|                 r = !!bm,
 | |
|                 length = (i = b[p++], i <= 223 ? i : i < 255 ? (i - 224) * 256 + b[p++] : (b[p++] << 24) + (b[p++] << 16) + (b[p++] << 8) + b[p++]),
 | |
|                 bm, i, n, d, e, f, g;
 | |
|             // stream = new sb.Reader().on(this.bits),
 | |
|             this.bitmap = bm || (bm = []);
 | |
|             while (p < b.length) {
 | |
|                 i = b[p++];
 | |
|                 if (i > 223) {
 | |
|                     i = i < 255 ? (i - 224) * 256 + b[p++] : (b[p++] << 24) + (b[p++] << 16) + (b[p++] << 8) + b[p++];
 | |
|                 }
 | |
|                 n = i >> 2;
 | |
|                 switch (i & 3) {
 | |
|                 case 1:
 | |
|                     d = b[p++];
 | |
|                     n *= 4;
 | |
|                     while (n--) {
 | |
|                         bm[q++] = d;
 | |
|                     }
 | |
|                     break;
 | |
|                 case 2:
 | |
|                     // d = stream.readBytes(4);
 | |
|                     d = b[p++];
 | |
|                     e = b[p++];
 | |
|                     f = b[p++];
 | |
|                     g = b[p++];
 | |
|                     while (n--) {
 | |
|                         if (r) {
 | |
|                             bm[q++] = e;
 | |
|                             bm[q++] = f;
 | |
|                             bm[q++] = g;
 | |
|                             bm[q++] = d;
 | |
|                         } else {
 | |
|                             bm[q++] = d;
 | |
|                             bm[q++] = e;
 | |
|                             bm[q++] = f;
 | |
|                             bm[q++] = g;
 | |
|                         }
 | |
|                     }
 | |
|                     break;
 | |
|                 case 3:
 | |
|                     while (n--) {
 | |
|                         if (r) {
 | |
|                             d = b[p++];
 | |
|                             bm[q++] = b[p++];
 | |
|                             bm[q++] = b[p++];
 | |
|                             bm[q++] = b[p++];
 | |
|                             bm[q++] = d;
 | |
|                         } else {
 | |
|                             bm[q++] = b[p++];
 | |
|                             bm[q++] = b[p++];
 | |
|                             bm[q++] = b[p++];
 | |
|                             bm[q++] = b[p++];
 | |
|                         }
 | |
|                     }
 | |
|                     // bm.push.apply(bm, stream.readBytes(n * 4));
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
|         },
 | |
|         load: function () {
 | |
|             var w = this.width,
 | |
|                 h = this.height,
 | |
|                 x, y, i, imageData, data, context, colors, color, b;
 | |
|             if (this.depth === 32) {
 | |
|                 this.image = document.createElement('canvas');
 | |
|                 this.image.width = this.width;
 | |
|                 this.image.height = this.height;
 | |
|                 data = (imageData = (context = this.image.getContext('2d')).createImageData(this.width, this.height)).data;
 | |
|                 this.decompress(data);
 | |
|                 context.putImageData(imageData, 0, 0);
 | |
|                 return;
 | |
|             }
 | |
|             color = [0, 0, 0, 0];
 | |
|             if (this.depth === 16) {
 | |
|                 w = this.width += 1;
 | |
|             }
 | |
|             this.init();
 | |
|             colors = sb.getColorStream(this.bitmap, this.depth);
 | |
|             data = (imageData = (context = this.image.getContext('2d')).createImageData(this.width, this.height)).data;
 | |
|             for (x = 0; x < w; ++x) {
 | |
|                 for (y = 0; y < h; ++y) {
 | |
|                     colors.next(color);
 | |
|                     if (!color) continue;
 | |
|                     i = (x * h + y) * 4;
 | |
|                     data[i] = color[0] * 255;
 | |
|                     data[i + 1] = color[1] * 255;
 | |
|                     data[i + 2] = color[2] * 255;
 | |
|                     data[i + 3] = color[3] * 255;
 | |
|                 }
 | |
|             }
 | |
|             context.putImageData(imageData, 0, 0);
 | |
|         }
 | |
|     });
 | |
| 
 | |
|     extend((sb.ColorForm = function (w, h, d, o, b, c) {
 | |
|         this.width = w;
 | |
|         this.height = h;
 | |
|         this.depth = d;
 | |
|         this.offset = o;
 | |
|         this.bits = b;
 | |
|         this.colors = c;
 | |
|     }).prototype = Object.create(sb.Form.prototype), {
 | |
|         load: function () {
 | |
|             var w = this.width,
 | |
|                 h = this.height,
 | |
|                 colors = this.colors,
 | |
|                 bits, x, y, i, imageData, data, context, color;
 | |
|             this.init();
 | |
|             data = (imageData = (context = this.image.getContext('2d')).createImageData(this.width, this.height)).data;
 | |
|             bits = this.bitmap;
 | |
|             for (x = 0; x < w; ++x) {
 | |
|                 for (y = 0; y < h; ++y) {
 | |
|                     color = colors[bits[x * h + y]];
 | |
|                     if (!color) continue;
 | |
|                     i = (x * h + y) * 4;
 | |
|                     data[i] = color.r * 255;
 | |
|                     data[i + 1] = color.g * 255;
 | |
|                     data[i + 2] = color.b * 255;
 | |
|                     data[i + 3] = color.a * 255;
 | |
|                 }
 | |
|             }
 | |
|             context.putImageData(imageData, 0, 0);
 | |
|         }
 | |
|     });
 | |
| 
 | |
|     sb.fields = {};
 | |
|     sb.classIDs = {};
 | |
|     sb.classNames = {};
 | |
|     sb.addFields = function (id, name, base, fields) {
 | |
|         sb.classIDs[name] = id;
 | |
|         sb.classNames[id] = name;
 | |
|         fields = fields.length ? fields.split(',') : [];
 | |
|         if (base) {
 | |
|             base = sb.fields[sb.classIDs[base]];
 | |
|             if (!base) throw new Error('Initialization error');
 | |
|             fields = base.concat(fields);
 | |
|         }
 | |
|         sb.fields[id] = fields;
 | |
|     };
 | |
| 
 | |
| 	sb.addFields(100, 'Morph', '', 'bounds,owner,submorphs,color,flags,properties');
 | |
| 	sb.addFields(101, 'BorderedMorph', 'Morph', 'borderWidth,borderColor');
 | |
| 	sb.addFields(102, 'RectangleMorph', 'BorderedMorph', '');
 | |
| 	sb.addFields(103, 'EllipseMorph', 'BorderedMorph', '');
 | |
| 	sb.addFields(104, 'AlignmentMorph', 'RectangleMorph', 'orientation,centering,hResizing,vResizing,inset');
 | |
| 	sb.addFields(105, 'StringMorph', 'Morph', 'fontSpec,emphasis,contents');
 | |
| 
 | |
| 	sb.addFields(-1, 'Slider', 'BorderedMorph', 'slider,value,setValueSelector,sliderShadow,sliderColor,descending,model');
 | |
| 	sb.addFields(-2, 'AbstractSound', '', '');
 | |
| 	sb.addFields(-3, 'ScriptableScratchMorph', 'Morph', 'objName,vars,blocksBin,customBlocks,isClone,media,costume');
 | |
| 	sb.addFields(-4, 'ArgMorph', 'BorderedMorph', 'labelMorph');
 | |
| 	sb.addFields(-5, 'PasteUpMorph', 'BorderedMorph', '');
 | |
| 	sb.addFields(-6, 'ScratchMedia', '', 'mediaName');
 | |
| 	sb.addFields(-7, 'ScrollFrameMorph', 'BorderedMorph', '');
 | |
| 
 | |
| 	sb.addFields(106, 'UpdatingStringMorph', 'StringMorph', 'format,target,getSelector,putSelector,parameter,floatPrecision,growable,stepTime');
 | |
| 	sb.addFields(107, 'SimpleSliderMorph', 'Slider', 'target,arguments,minVal,maxVal,truncate,sliderThickness');
 | |
| 	sb.addFields(108, 'SimpleButtonMorph', 'RectangleMorph', 'target,actionSelector,arguments,actWhen');
 | |
| 	sb.addFields(109, 'SampledSound', 'AbstractSound', 'envelopes,scaledVol,initialCount,samples,originalSamplingRate,samplesSize,scaledIncrement,scaledInitialIndex');
 | |
| 	sb.addFields(110, 'ImageMorph', 'Morph', 'form,transparency');
 | |
| 	sb.addFields(111, 'SketchMorph', 'Morph', 'originalForm,rotationCenter,rotationDegrees,rotationStyle,scalePoint,offsetWhenRotated');
 | |
| 	sb.addFields(123, 'SensorBoardMorph', 'Morph', 'portNum');
 | |
| 	sb.addFields(124, 'ScratchSpriteMorph', 'ScriptableScratchMorph', 'visibility,scalePoint,rotationDegrees,rotationStyle,volume,tempoBPM,draggable,sceneStates,lists,virtualScale,ownerSprite,subsprites,rotateWithOwner,refPos,prototype,deletedAttributes');
 | |
| 	sb.addFields(125, 'ScratchStageMorph', 'ScriptableScratchMorph', 'zoom,hPan,vPan,obsoleteSavedState,sprites,volume,tempoBPM,sceneStates,lists');
 | |
| 	sb.addFields(140, 'ChoiceArgMorph', 'ArgMorph', 'isBoolean,options,choice,getOptionsSelector');
 | |
| 	sb.addFields(141, 'ColorArgMorph', 'ArgMorph', '');
 | |
| 	sb.addFields(142, 'ExpressionArgMorph', 'ArgMorph', 'isNumber');
 | |
| 	sb.addFields(145, 'SpriteArgMorph', 'ArgMorph', 'morph');
 | |
| 	sb.addFields(147, 'BlockMorph', 'Morph', 'isSpecialForm,oldColor');
 | |
| 	sb.addFields(148, 'CommandBlockMorph', 'BlockMorph', 'commandSpec,argMorphs,titleMorph,receiver,selector,isReporter,isTimed,wantsName,wantsPossession');
 | |
| 	sb.addFields(149, 'CBlockMorph', 'CommandBlockMorph', 'nestedBlock,nextBlock');
 | |
| 	sb.addFields(151, 'HatBlockMorph', 'CommandBlockMorph', 'scriptNameMorph,indicatorMorph,scriptOwner,parameters,isClickable');
 | |
| 	sb.addFields(153, 'ScratchScriptsMorph', 'PasteUpMorph', '');
 | |
| 	sb.addFields(154, 'ScratchSliderMorph', 'AlignmentMorph', 'slider,sliderMin,sliderMax,variable');
 | |
| 	sb.addFields(155, 'WatcherMorph', 'AlignmentMorph', 'titleMorph,readout,readoutFrame,scratchSlider,watcher,isSpriteSpecific,unused,sliderMin,sliderMax,isLarge');
 | |
| 	sb.addFields(157, 'SetterBlockMorph', 'CommandBlockMorph', 'variable');
 | |
| 	sb.addFields(158, 'EventHatMorph', 'HatBlockMorph', '');
 | |
| 	sb.addFields(170, 'ReporterBlockMorph', 'CommandBlockMorph', 'isBoolean');
 | |
| 	sb.addFields(160, 'VariableBlockMorph', 'ReporterBlockMorph', '');
 | |
| 	sb.addFields(162, 'ImageMedia', 'ScratchMedia', 'form,rotationCenter,textBox,jpegBytes,compositeForm');
 | |
| 	sb.addFields(163, 'MovieMedia', 'ScratchMedia', 'fileName,fade,fadeColor,zoom,hPan,vPan,msecsPerFrame,currentFrame,moviePlaying');
 | |
| 	sb.addFields(164, 'SoundMedia', 'ScratchMedia', 'originalSound,volume,balance,compressedSampleRate,compressedBitsPerSample,compressedData');
 | |
| 	sb.addFields(165, 'KeyEventHatMorph', 'HatBlockMorph', '');
 | |
| 	sb.addFields(166, 'BooleanArgMorph', 'ArgMorph', '');
 | |
| 	sb.addFields(167, 'EventTitleMorph', 'ArgMorph', '');
 | |
| 	sb.addFields(168, 'MouseClickEventHatMorph', 'HatBlockMorph', '');
 | |
| 	sb.addFields(169, 'ExpressionArgMorphWithMenu', 'ExpressionArgMorph', 'menuMorph,getMenuSelector,specialValue');
 | |
| 	sb.addFields(171, 'MultilineStringMorph', 'BorderedMorph', 'fontSpec,textColor,selectionColor,lines');
 | |
| 	sb.addFields(172, 'ToggleButton', 'SimpleButtonMorph', 'onForm,offForm,overForm,disabledForm,isMomentary,toggleMode,isOn,isDisabled');
 | |
| 	sb.addFields(173, 'WatcherReadoutFrameMorph', 'BorderedMorph', '');
 | |
| 	sb.addFields(174, 'WatcherSliderMorph', 'SimpleSliderMorph', '');
 | |
| 	sb.addFields(175, 'ScratchListMorph', 'BorderedMorph', 'listName,strings,target,complex');
 | |
| 	sb.addFields(176, 'ScrollingStringMorph', 'BorderedMorph', 'fontSpec,showScrollbar,firstVisibleLine,textColor,selectionColor,lines');
 | |
| 
 | |
| 	sb.addFields(180, 'ScrollFrameMorph2', 'ScrollFrameMorph', '');
 | |
| 	sb.addFields(181, 'ListMultilineStringMorph', 'MultilineStringMorph', '');
 | |
| 	sb.addFields(182, 'ScratchScrollBar', 'Morph', '');
 | |
| 
 | |
| 	sb.addFields(200, 'CustomCommandBlockMorph', 'CommandBlockMorph', 'userSpec');
 | |
| 	sb.addFields(201, 'CustomBlockDefinition', '', 'userSpec,blockVars,isAtomic,isReporter,isBoolean,body,answer,type,category,declarations,defaults,isGlobal');
 | |
| 	sb.addFields(203, 'ReporterScriptBlockMorph', 'ReporterBlockMorph', '');
 | |
| 	sb.addFields(202, 'CommandScriptBlockMorph', 'ReporterScriptBlockMorph', '');
 | |
| 	sb.addFields(205, 'VariableFrame', '', 'vars');
 | |
| 	sb.addFields(206, 'CustomReporterBlockMorph', 'ReporterBlockMorph', 'userSpec');
 | |
| 
 | |
| 	sb.addFields(207, 'CReporterSlotMorph', 'ReporterScriptBlockMorph', '');
 | |
| 
 | |
| 	sb.addFields(300, 'StringFieldMorph', 'BorderedMorph', '');
 | |
| 	sb.addFields(301, 'MultiArgReporterBlockMorph', 'ReporterBlockMorph', '');
 | |
| 
 | |
| 	(function (C, p) {
 | |
| 
 | |
| 		p.on = function (bytes) {
 | |
| 			this.bytes = bytes;
 | |
| 			if (!bytes.subarray) bytes.subarray = bytes.slice;
 | |
| 			this.position = 0;
 | |
| 			return this;
 | |
| 		};
 | |
| 		p.readYPR = function (bytes) {
 | |
| 			var version, infoSize, info, stage;
 | |
| 			console.time('readSB');
 | |
| 			this.on(bytes);
 | |
| 
 | |
| 			// skip header
 | |
| 			this.matchBytes([66, 108, 111, 120, 69, 120, 112, 86]);
 | |
| 			version = +(String.fromCharCode(this.next()) + String.fromCharCode(this.next()));
 | |
| 			if (version < 1) {
 | |
| 				throw new Error('Invalid version');
 | |
| 			}
 | |
| 
 | |
| 			// read info
 | |
| 			infoSize = this.uint32();
 | |
| 			info = this.read();
 | |
| 
 | |
| 			this.position = infoSize + 14; // header + uint32
 | |
| 			stage = this.read();
 | |
| 
 | |
| 			this.onload({
 | |
| 				reader: this,
 | |
| 				info: info,
 | |
| 				stage: stage
 | |
| 			});
 | |
| 			console.timeEnd('readSB');
 | |
| 		};
 | |
| 		p.readInfo = function (bytes) {
 | |
| 			var version, infoSize, info;
 | |
| 			this.on(bytes);
 | |
| 
 | |
| 			this.matchBytes([66, 108, 111, 120, 69, 120, 112, 86]);
 | |
| 			version = +(String.fromCharCode(this.next()) + String.fromCharCode(this.next()));
 | |
| 			if (version < 1) {
 | |
| 				throw new Error('Invalid version');
 | |
| 			}
 | |
| 
 | |
| 			// read info
 | |
| 			infoSize = this.uint32();
 | |
| 			info = this.read();
 | |
| 
 | |
| 			this.onload({
 | |
| 				reader: this,
 | |
| 				info: info
 | |
| 			});
 | |
| 		};
 | |
| 		p.read = function () {
 | |
| 			var i, objectCount;
 | |
| 			this.objects = [];
 | |
| 			this.readHeader();
 | |
| 			objectCount = this.uint32();
 | |
| 
 | |
| 			i = objectCount;
 | |
| 			while (i--) {
 | |
| 				this.objects.push(this.readObject());
 | |
| 			}
 | |
| 			i = objectCount;
 | |
| 			while (i--) {
 | |
| 				this.fixReferences(this.objects[i]);
 | |
| 			}
 | |
| 			return this.objects[0][1];
 | |
| 		};
 | |
| 		p.fixReferences = function (object) {
 | |
| 			var classID = object[0],
 | |
| 				value = object[1],
 | |
| 				i = 0,
 | |
| 				fields, source;
 | |
| 			if (classID < 99) {
 | |
| 				this.fixFixedFormat(classID, value);
 | |
| 			} else {
 | |
| 				this.fixArray(object[3]);
 | |
| 				fields = sb.fields[classID];
 | |
| 				if (!fields)
 | |
| 					throw new Error('Invalid class ID ' + classID);
 | |
| 				source = value.fields;
 | |
| 				delete value.fields;
 | |
| 				value.className = sb.classNames[classID];
 | |
| 				i = fields.length;
 | |
| 				while (i--) {
 | |
| 					value[fields[i]] = source[i];
 | |
| 				}
 | |
| 			}
 | |
| 		};
 | |
| 		p.fixArray = function (a) {
 | |
| 			var i = a.length,
 | |
| 				o;
 | |
| 			while (i--) {
 | |
| 				if ((o = a[i]) && o.isRef) {
 | |
| 					if (o.id > this.objects.length) {
 | |
| 						throw new Error('Invalid object reference');
 | |
| 					}
 | |
| 					a[i] = this.objects[o.id - 1][1];
 | |
| 				}
 | |
| 			}
 | |
| 		};
 | |
| 		p.targetObjectFor = function (o) {
 | |
| 			if (o && o.isRef) {
 | |
| 				if (o.id > this.objects.length)
 | |
| 					throw new Error('Invalid object reference');
 | |
| 				return this.objects[o.id - 1][1];
 | |
| 			}
 | |
| 			return o;
 | |
| 		};
 | |
| 		p.fixFixedFormat = function (classID, object) {
 | |
| 			switch (classID) {
 | |
| 			case 20: // Array
 | |
| 			case 21: // OrderedCollection
 | |
| 			case 22: // Set
 | |
| 			case 23: // IdentitySet
 | |
| 				this.fixArray(object);
 | |
| 				return object;
 | |
| 			case 24: // Dictionary
 | |
| 			case 25: // IdentityDictionary
 | |
| 				this.fixArray(object.keys);
 | |
| 				this.fixArray(object.values);
 | |
| 				break;
 | |
| 			case 32: // Point
 | |
| 				object.x = this.targetObjectFor(object.x);
 | |
| 				object.y = this.targetObjectFor(object.y);
 | |
| 				break;
 | |
| 			case 33: // Rectangle
 | |
| 				object.origin.x = this.targetObjectFor(object.origin.x);
 | |
| 				object.origin.y = this.targetObjectFor(object.origin.y);
 | |
| 				object.corner.x = this.targetObjectFor(object.corner.x);
 | |
| 				object.corner.y = this.targetObjectFor(object.corner.y);
 | |
| 				break;
 | |
| 			case 34: // Form
 | |
| 				object.offset = this.targetObjectFor(object.offset);
 | |
| 				object.bits = this.targetObjectFor(object.bits);
 | |
| 				object.load();
 | |
| 				break;
 | |
| 			case 35: // ColorForm
 | |
| 				object.offset = this.targetObjectFor(object.offset);
 | |
| 				object.bits = this.targetObjectFor(object.bits);
 | |
| 				object.colors = this.targetObjectFor(object.colors);
 | |
| 				object.load();
 | |
| 				break;
 | |
| 			}
 | |
| 			return object;
 | |
| 		};
 | |
| 		p.readObject = function () {
 | |
| 			var classID = this.next(),
 | |
| 				version, fieldCount, fields;
 | |
| 			if (classID > 99) {
 | |
| 				version = this.next();
 | |
| 				fieldCount = this.next();
 | |
| 				fields = [];
 | |
| 				while (fieldCount--) {
 | |
| 					fields.push(this.readField());
 | |
| 				}
 | |
| 				return [classID, { fields: fields }, version, fields];
 | |
| 			}
 | |
| 			return [classID, this.readFixedFormat(classID)];
 | |
| 		};
 | |
| 		p.readField = function () {
 | |
| 			var classID = this.next();
 | |
| 			if (classID === 99) {
 | |
| 				return new sb.Ref(this.uint24());
 | |
| 			}
 | |
| 			return this.readFixedFormat(classID);
 | |
| 		};
 | |
| 		p.readFixedFormat = function (classID) {
 | |
| 			var a, n;
 | |
| 			switch (classID) {
 | |
| 			case 1: // UndefinedObject
 | |
| 				return null;
 | |
| 			case 2: // True
 | |
| 				return true;
 | |
| 			case 3: // False
 | |
| 				return false;
 | |
| 			case 4: // SmallInteger
 | |
| 				return this.int32();
 | |
| 			case 5: // SmallInteger16
 | |
| 				return this.int16();
 | |
| 			case 6: // LargePositiveInteger
 | |
| 			case 7: // LargeNegativeInteger
 | |
| 				n = this.uint16();
 | |
| 				a = 0;
 | |
| 				while (n--) {
 | |
| 					a *= 0x100;
 | |
| 					a += this.next();
 | |
| 				}
 | |
| 				return a;
 | |
| 			case 8: // Float
 | |
| 				return this.float64();
 | |
| 			case 9: // String
 | |
| 			case 10: // Symbol
 | |
| 			case 14: // UTF8
 | |
| 				a = [].slice.call(this.readBytes(n = this.uint32()));
 | |
| 				while (n--) {
 | |
| 					a[n] = String.fromCharCode(a[n]);
 | |
| 				}
 | |
| 				return a.join('');
 | |
| 			case 11: // ByteArray
 | |
| 				return this.readBytes(this.uint32());
 | |
| 			case 12: // SoundBuffer
 | |
| 				a = [];
 | |
| 				n = this.uint32();
 | |
| 				while (n--) {
 | |
| 					a.push(this.int16());
 | |
| 				}
 | |
| 				return a;
 | |
| 			case 13: // Bitmap
 | |
| 				a = [];
 | |
| 				a.isBitmap = true;
 | |
| 				n = this.uint32();
 | |
| 				while (n--) {
 | |
| 					a.push(this.uint32());
 | |
| 				}
 | |
| 				return a;
 | |
| 			case 20: // Array
 | |
| 			case 21: // OrderedCollection
 | |
| 			case 22: // Set
 | |
| 			case 23: // IdentitySet
 | |
| 				a = [];
 | |
| 				n = this.uint32();
 | |
| 				while (n--) {
 | |
| 					a.push(this.readField());
 | |
| 				}
 | |
| 				return a;
 | |
| 			case 24: // Dictionary
 | |
| 			case 25: // IdentityDictionary
 | |
| 				a = new sb.Dictionary([], []);
 | |
| 				n = this.uint32();
 | |
| 				while (n--) {
 | |
| 					a.keys.push(this.readField());
 | |
| 					a.values.push(this.readField());
 | |
| 				}
 | |
| 				return a;
 | |
| 			case 30: // Color
 | |
| 				return new sb.Color(this.uint32(), 255);
 | |
| 			case 31: // TranslucentColor
 | |
| 				return new sb.Color(this.uint32(), this.uint8());
 | |
| 			case 32: // Point
 | |
| 				return new sb.Point(this.readField(), this.readField());
 | |
| 			case 33: // Rectangle
 | |
| 				return new sb.Rectangle(this.readField(), this.readField(), this.readField(), this.readField());
 | |
| 			case 34: // Form
 | |
| 				return new sb.Form(this.readField(), this.readField(), this.readField(), this.readField(), this.readField());
 | |
| 			case 35: // ColorForm
 | |
| 				return new sb.ColorForm(this.readField(), this.readField(), this.readField(), this.readField(), this.readField(), this.readField());
 | |
| 			}
 | |
| 			throw new Error('Invalid fixed-format class ID');
 | |
| 		};
 | |
| 		p.readHeader = function () {
 | |
| 			this.matchBytes([79, 98, 106, 83, 1, 83, 116, 99, 104, 1]);
 | |
| 		};
 | |
| 		p.readBytes = function (length) {
 | |
| 			return this.bytes.subarray(this.position, this.position += length);
 | |
| 		};
 | |
| 		p.matchBytes = function (bytes) {
 | |
| 			var i = bytes.length,
 | |
| 				r = this.readBytes(i);
 | |
| 			while (i--) {
 | |
| 				if (r[i] !== bytes[i]) {
 | |
| 					throw new Error('Invalid format');
 | |
| 				}
 | |
| 			}
 | |
| 		};
 | |
| 		p.skip = function (length) {
 | |
| 			this.position += length;
 | |
| 		};
 | |
| 		p.next = p.uint8 = function () {
 | |
| 			return this.bytes[this.position++];
 | |
| 		};
 | |
| 		p.hasNext = function () {
 | |
| 			return this.position < this.bytes.length;
 | |
| 		};
 | |
| 		p.uint16 = function () {
 | |
| 			return this.next() * 0x100 + this.next();
 | |
| 		};
 | |
| 		p.uint24 = function () {
 | |
| 			return this.next() * 0x10000 + this.next() * 0x100 + this.next();
 | |
| 		};
 | |
| 		p.uint32 = function () {
 | |
| 			return this.next() * 0x1000000 + this.next() * 0x10000 + this.next() * 0x100 + this.next();
 | |
| 		};
 | |
| 		p.int8 = function () {
 | |
| 			var v = this.bytes[++this.position];
 | |
| 			return v >= 0x80 ? v - 0x100 : v;
 | |
| 		};
 | |
| 		p.int16 = function () {
 | |
| 			var d = this.next(),
 | |
| 				v = d * 0x100 + this.next();
 | |
| 			return d >= 0x80 ? v - 0x10000 : v;
 | |
| 		};
 | |
| 		p.int24 = function () {
 | |
| 			var d = this.next(),
 | |
| 				v = d * 0x10000 + this.next() * 0x100 + this.next();
 | |
| 			return d >= 0x80 ? v - 0x1000000 : v;
 | |
| 		};
 | |
| 		p.int32 = function () {
 | |
| 			var d = this.next(),
 | |
| 				v = d * 0x1000000 + this.next() * 0x10000 + this.next() * 0x100 + this.next();
 | |
| 			return d >= 0x80 ? v - 0x100000000 : v;
 | |
| 		};
 | |
| 		p.string = function () {
 | |
| 			var length = this.uint16(),
 | |
| 				bytes = this.readBytes(length),
 | |
| 				i = length;
 | |
| 			while (i--) {
 | |
| 				bytes[i] = String.fromCharCode(bytes[i]);
 | |
| 			}
 | |
| 			return bytes.join('');
 | |
| 		};
 | |
| 		p.float64 = function () {
 | |
| 			return this.ieee(8, 11, 52, 1023);
 | |
| 		};
 | |
| 		p.ieee = function (n, ebits, mbits) {
 | |
| 			var bias = (1 << (ebits - 1)) - 1,
 | |
| 				string = '',
 | |
| 				i = n,
 | |
| 				b, sign, exponent, mantissa, result;
 | |
| 			while (i--) {
 | |
| 				b = this.next().toString(2);
 | |
| 				string = string + Array(9 - b.length).join('0') + b;
 | |
| 			}
 | |
| 			sign = string.charAt(0) === '0' ? 1 : -1;
 | |
| 			exponent = parseInt(string.substr(1, ebits), 2);
 | |
| 			mantissa = parseInt(string.substr(ebits + 1), 2);
 | |
| 			if (exponent === 0) {
 | |
| 				return mantissa === 0 ? sign * 0 : sign * Math.pow(2, 1 - bias) * mantissa / Math.pow(2, mbits);
 | |
| 			}
 | |
| 			if (exponent === (1 << ebits) - 1) {
 | |
| 				return mantissa === 0 ? sign / 0 : NaN;
 | |
| 			}
 | |
| 			return sign * Math.pow(2, exponent - bias) * (1 + mantissa / Math.pow(2, mbits));
 | |
| 		};
 | |
| 
 | |
| 	})(sb.Reader = function () {}, sb.Reader.prototype);
 | |
| 
 | |
| 	(function (C, p) {
 | |
| 
 | |
| 		var rotationStyles = {
 | |
| 			normal: 1,
 | |
| 			leftRight: 2,
 | |
| 			none: 0
 | |
| 		}, customBlockInputs = {
 | |
| 			object: '%obj',
 | |
| 			objectList: '%mult%obj',
 | |
| 			number: '%n',
 | |
| 			numberList: '%mult%n',
 | |
| 			text: '%txt',
 | |
| 			textList: '%mult%txt',
 | |
| 			list: '%l',
 | |
| 			listList: '%mult%l',
 | |
| 			any: '%s',
 | |
| 			anyList: '%mult%s',
 | |
| 			'boolean': '%b',
 | |
| 			booleanList: '%mult%b',
 | |
| 			command: '%cmdRing',
 | |
| 			commandList: '%mult%cmdRing',
 | |
| 			reporter: '%repRing',
 | |
| 			reporterList: '%mult%repRing',
 | |
| 			predicate: '%predRing',
 | |
| 			predicateList: '%mult%predRing',
 | |
| 			loop: '%cs',
 | |
| 			loopList: '%mult%cs',
 | |
| 			unevaluated: '%anyUE',
 | |
| 			unevaluatedList: '%mult%anyUE',
 | |
| 			unevaluatedBoolean: '%boolUE',
 | |
| 			unevaluatedBooleanList: '%mult%boolUE',
 | |
| 			template: '%upvar'
 | |
| 		}, blockSelectors = {
 | |
| 			// Motion': '',
 | |
| 			'forward:': 'forward',
 | |
| 			'turnLeft:': 'turnLeft',
 | |
| 			'turnRight:': 'turn',
 | |
| 			'heading:': 'setHeading',
 | |
| 			'pointTowards:': 'doFaceTowards',
 | |
| 			'gotoX:y:': 'gotoXY',
 | |
| 			'gotoSpriteOrMouse:': 'doGotoObject',
 | |
| 			'glideSecs:toX:y:elapsed:from:': 'doGlide',
 | |
| 			'changeXposBy:': 'changeXPosition',
 | |
| 			'xpos:': 'setXPosition',
 | |
| 			'changeYposBy:': 'changeYPosition',
 | |
| 			'ypos:': 'setYPosition',
 | |
| 			'bounceOffEdge': 'bounceOffEdge',
 | |
| 			'xpos': 'xPosition',
 | |
| 			'ypos': 'yPosition',
 | |
| 			'heading': 'direction',
 | |
| 
 | |
| 			// Looks
 | |
| 			'lookLike:': 'doSwitchToCostume',
 | |
|             'showBackground:': 'doSwitchToCostume',
 | |
| 			'nextCostume': 'doWearNextCostume',
 | |
| 			'nextBackground': 'doWearNextCostume',
 | |
| 			'costumeIndex': 'getCostumeIdx',
 | |
| 			'say:duration:elapsed:from:': 'doSayFor',
 | |
| 			'say:': 'bubble',
 | |
| 			'think:duration:elapsed:from:': 'doThinkFor',
 | |
| 			'think:': 'doThink',
 | |
| 			'changeGraphicEffect:by:': 'changeEffect',
 | |
| 			'setGraphicEffect:to:': 'setEffect',
 | |
| 			'filterReset': 'clearEffects',
 | |
| 			'setSizeTo:': 'setScale',
 | |
|             'changeSizeBy:': 'changeScale',
 | |
| 			'scale': 'getScale',
 | |
| 			'show': 'show',
 | |
| 			'hide': 'hide',
 | |
| 			'comeToFront': 'comeToFront',
 | |
| 			'goBackByLayers:': 'goBack',
 | |
| 
 | |
| 			// Sound
 | |
| 			'playSound:': 'playSound',
 | |
| 			'doPlaySoundAndWait': 'doPlaySoundUntilDone',
 | |
| 			'stopAllSounds': 'doStopAllSounds',
 | |
| 			// 'drum:duration:elapsed:from:': '',
 | |
| 			'rest:elapsed:from:': 'doRest',
 | |
| 			'noteOn:duration:elapsed:from:': 'doPlayNote',
 | |
| 			// 'midiInstrument:': '',
 | |
| 			// 'changeVolumeBy:': '',
 | |
| 			// 'setVolumeTo:': '',
 | |
| 			// 'volume': '',
 | |
| 			'changeTempoBy:': 'doChangeTempo',
 | |
| 			'setTempoTo:': 'doSetTempo',
 | |
| 			'tempo': 'getTempo',
 | |
| 
 | |
| 			// Pen
 | |
| 			'clearPenTrails': 'clear',
 | |
| 			'putPenDown': 'down',
 | |
| 			'putPenUp': 'up',
 | |
| 			'penColor:': 'setColor',
 | |
| 			'changePenHueBy:': 'changeHue',
 | |
| 			'setPenHueTo:': 'setHue',
 | |
|             '_changePenHueBy:': 'changeHue',
 | |
|             '_setPenHueTo:': 'setHue',
 | |
| 			'changePenShadeBy:': 'changeBrightness',
 | |
| 			'setPenShadeTo:': 'setBrightness',
 | |
| 			'changePenSizeBy:': 'changeSize',
 | |
| 			'penSize:': 'setSize',
 | |
| 			'stampCostume': 'doStamp',
 | |
| 
 | |
| 			// Control
 | |
| 			// 'whenStartClicked': '',
 | |
| 			// 'whenKeyPressed:': '',
 | |
| 			// 'whenSpriteClicked': '',
 | |
| 			'wait:elapsed:from:': 'doWait',
 | |
| 			'doForever': 'doForever',
 | |
| 			'doRepeat': 'doRepeat',
 | |
| 			'broadcast:': 'doBroadcast',
 | |
| 			'doBroadcastAndWait': 'doBroadcastAndWait',
 | |
| 			// 'whenMessageReceived:': '',
 | |
| 			// TODO 'doForeverIf': '',
 | |
| 			'doIf': 'doIf',
 | |
| 			'doIfElse': 'doIfElse',
 | |
| 			'doWaitUntil': 'doWaitUntil',
 | |
| 			'doUntil': 'doUntil',
 | |
| 			'doReturn': 'doStop',
 | |
| 			'stopAll': 'doStopAll',
 | |
| 			'doRun': 'doRun',
 | |
| 			'doRunBlockWithArgs': 'doRun',
 | |
| 			'doRunBlockWithArgList': 'doRun',
 | |
| 			'doFork': 'fork',
 | |
| 			'doForkBlockWithArgs': 'fork',
 | |
| 			'doForkBlockWithArgList': 'fork',
 | |
| 			'doReport': 'evaluate',
 | |
| 			'doCallBlockWithArgs': 'evaluate',
 | |
| 			'doCallBlockWithArgList': 'evaluate',
 | |
| 			'doAnswer': 'doReport',
 | |
| 			'doStopBlock': 'doStopBlock',
 | |
| 			// 'doPauseThread': '',
 | |
| 			// 'doPauseThreadReporter': '',
 | |
| 
 | |
| 			// Sensing
 | |
| 			'touching:': 'reportTouchingObject',
 | |
| 			'touchingColor:': 'reportTouchingColor',
 | |
| 			'color:sees:': 'reportColorIsTouchingColor',
 | |
| 			'doAsk': 'doAsk',
 | |
| 			'answer': 'reportLastAnswer',
 | |
| 			'mouseX': 'reportMouseX',
 | |
| 			'mouseY': 'reportMouseY',
 | |
| 			'mousePressed': 'reportMouseDown',
 | |
| 			'keyPressed:': 'reportKeyPressed',
 | |
| 			'distanceTo:': 'reportDistanceTo',
 | |
| 			'timerReset': 'doResetTimer',
 | |
| 			'timer': 'reportTimer',
 | |
| 			'getAttribute:of:': 'reportAttributeOf',
 | |
| 			// 'attribute:of:': '',
 | |
| 			// 'soundLevel': '',
 | |
| 			// 'isLoud': '',
 | |
| 			// 'sensor:': '',
 | |
| 			// 'sensorPressed:': '',
 | |
| 			// 'getObject:': '',
 | |
| 			// 'get:': '',
 | |
| 
 | |
| 			// Operators
 | |
| 			'+': 'reportSum',
 | |
| 			'-': 'reportDifference',
 | |
| 			'*': 'reportProduct',
 | |
| 			'/': 'reportQuotient',
 | |
| 			'randomFrom:to:': 'reportRandom',
 | |
| 			'<': 'reportLessThan',
 | |
| 			'=': 'reportEquals',
 | |
| 			'>': 'reportGreaterThan',
 | |
| 			'&': 'reportAnd',
 | |
| 			'|': 'reportOr',
 | |
| 			'not': 'reportNot',
 | |
| 			'getTrue': 'reportTrue',
 | |
| 			'getFalse': 'reportFalse',
 | |
| 			'concatenate:with:': 'reportJoinWords',
 | |
| 			'letter:of:': 'reportLetter',
 | |
| 			'stringLength:': 'reportStringSize',
 | |
| 			'asciiCodeOf:': 'reportUnicode',
 | |
| 			'asciiLetter:': 'reportUnicodeAsLetter',
 | |
| 			'\\\\': 'reportModulus',
 | |
| 			'rounded': 'reportRound',
 | |
| 			'computeFunction:of:': 'reportMonadic',
 | |
| 			'isObject:type:': 'reportIsA',
 | |
| 			// 'procedure': 'reifyScript',
 | |
| 			// 'procedureWithArgs': 'reifyScript',
 | |
| 			// 'function': 'reifyReporter',
 | |
| 			// 'functionWithArgs': 'reifyReporter',
 | |
| 			// 'spawn': '',
 | |
| 
 | |
| 			// Variables
 | |
| 			'setVar:to:': 'doSetVar',
 | |
| 			'changeVar:by:': 'doChangeVar',
 | |
| 			// 'deleteObject:': '',
 | |
| 			'showVariable:': 'doShowVar',
 | |
| 			'hideVariable:': 'doHideVar',
 | |
| 			'doDeclareVariables': 'doDeclareVariables',
 | |
| 			'newList:': 'reportNewList',
 | |
| 			'append:toList:': 'doAddToList',
 | |
| 			'deleteLine:ofList:': 'doDeleteFromList',
 | |
| 			'insert:at:ofList:': 'doInsertInList',
 | |
| 			'setLine:ofList:to:': 'doReplaceInList',
 | |
| 			'getLine:ofList:': 'reportListItem',
 | |
| 			'lineCountOfList:': 'reportListLength',
 | |
| 			'list:contains:': 'reportListContainsItem',
 | |
| 			// 'contentsOfList:': '',
 | |
| 			// 'copyOfList:': ''
 | |
| 
 | |
| 			// Kludge
 | |
| 			_warp: 'doWarp'
 | |
| 		};
 | |
| 
 | |
| 		function escapeXML(string) {
 | |
|             // over-cautious due to some morphic bugs
 | |
| 			return string.replace(/&/g, '&')/*.replace(/[\n\r]|\r\n/g, '
')*/.replace(/</g, '<').replace(/"/g, '"');
 | |
| 		}
 | |
| 		function costumeIndex(object) {
 | |
| 			return object.media.filter(function (m) { return m.className === 'ImageMedia' }).indexOf(object.costume) + 1;
 | |
| 		}
 | |
| 
 | |
| 		function Text(value) {
 | |
| 			this.value = '' + value;
 | |
| 		}
 | |
| 		Text.prototype.toXML = function (string) {
 | |
| 			string.push(escapeXML(this.value));
 | |
| 		};
 | |
|         Text.prototype.toDOM = function () {
 | |
|             return document.createTextNode(this.value);
 | |
|         };
 | |
| 
 | |
| 		function Element(tagName) {
 | |
| 			this.tagName = tagName;
 | |
| 		}
 | |
| 		Element.prototype.toXML = function (string) {
 | |
| 			var attributes = this.attributes, key;
 | |
| 			string.push('<', this.tagName);
 | |
| 			for (key in attributes) if (attributes.hasOwnProperty(key)) {
 | |
| 				string.push(' ', key, '="', escapeXML('' + attributes[key]), '"');
 | |
| 			}
 | |
| 			if (this.children.length) {
 | |
| 				string.push('>');
 | |
| 				this.children.forEach(function (child) {
 | |
| 					if (!child.toXML) {
 | |
| 						console.error('Invalid', child);
 | |
| 					}
 | |
| 					child.toXML(string);
 | |
| 				});
 | |
| 				string.push('</', this.tagName, '>');
 | |
| 			} else {
 | |
| 				string.push('/>');
 | |
| 			}
 | |
| 		};
 | |
|         Element.prototype.toDOM = function () {
 | |
|             var el = document.createElement(this.tagName),
 | |
|                 attributes = this.attributes, key,
 | |
|                 children = this.children, i = 0, child;
 | |
|             for (key in attributes) if (attributes.hasOwnProperty(key)) {
 | |
|                 el.setAttribute(key, attributes[key]);
 | |
|             }
 | |
|             while (child = children[i++]) {
 | |
|                 el.appendChild(child.toDOM());
 | |
|             }
 | |
|             return el;
 | |
|         };
 | |
| 
 | |
| 		function n(tagName, attributes, children) {
 | |
| 			var el = new Element(tagName), key;
 | |
| 			el.attributes = attributes || {};
 | |
| 			el.children = children || [];
 | |
| 			return el;
 | |
| 		}
 | |
| 		function t(value) {
 | |
| 			return new Text(value);
 | |
| 		}
 | |
| 
 | |
| 		p.write = function (projectName, project) {
 | |
| 			var a, xml, out, objectID = 0, customBlocks = {};
 | |
| 			function id(n) {
 | |
| 				return n.attributes.id ? n('ref', { id: n.attributes.id }) : n.attributes.id = ++objectID, n;
 | |
| 			}
 | |
| 			function preCustomBlocks(scope, blocks) {
 | |
| 				var sc = customBlocks[scope] = {};
 | |
| 				if (!blocks) return;
 | |
| 				blocks.forEach(function (ypr) {
 | |
| 					sc[ypr.userSpec] = ypr;
 | |
| 					ypr.userSpec = ypr.userSpec.replace(/^\s+|\s+$/g, '').replace(/\s{2,}/g, ' ').replace(/'/g, '');
 | |
| 					ypr.__blockSpec__ = ypr.userSpec.replace(/%(\S+)/g, function (_, name) {
 | |
| 						var type = customBlockInputs[ypr.declarations.get(name)];
 | |
| 						if (type === undefined) {
 | |
| 							if (ypr.declarations.get(name) === undefined) {
 | |
| 								type = '%s';
 | |
| 							} else {
 | |
| 								console.warn('Missing input type ' + ypr.declarations.get(name));
 | |
| 							}
 | |
| 						}
 | |
| 						return type;
 | |
| 					});
 | |
| 					ypr.__types__ = (ypr.userSpec.match(/%\S+/g) || []).map(function (arg) {
 | |
| 						return ypr.declarations.get(arg.substr(1)) || 'any';
 | |
| 					});
 | |
| 				});
 | |
| 			}
 | |
| 			function nsCustomBlocks(blocks, globals) {
 | |
| 				if (!blocks) return [];
 | |
| 				return (globals ? blocks : blocks.filter(function (b) { return !b.isGlobal })).map(function (ypr) {
 | |
| 					var args = (ypr.userSpec.match(/%\S+/g) || []).map(function (arg) {
 | |
| 							var name = arg.substr(1),
 | |
| 								type = customBlockInputs[ypr.declarations.get(name)];
 | |
| 							if (type === undefined) {
 | |
| 								type = '%s';
 | |
| 							}
 | |
| 							return n('input', {
 | |
| 								type: type
 | |
| 							}, [t(ypr.defaults.get(name))]);
 | |
| 						}),
 | |
| 						spec = ypr.userSpec.replace(/%(\S+)/g, '%\'$1\'');
 | |
| 					ypr.body = ypr.body || [];
 | |
| 					if (ypr.answer && ypr.answer.pop) {
 | |
| 						ypr.body.push(['byob', '', 'doAnswer', ypr.answer[0]]);
 | |
| 					}
 | |
|                     if (ypr.blockVars.length) {
 | |
|                         ypr.body.unshift(['byob', '', 'doDeclareVariables'].concat(ypr.blockVars.map(function (n) {
 | |
|                             return [0, 0, 0, n];
 | |
|                         })));
 | |
|                     }
 | |
| 					if (ypr.isAtomic) {
 | |
| 						ypr.body = [['byob', '', '_warp', ypr.body]];
 | |
| 					}
 | |
| 					return n('block-definition', {
 | |
| 						s: spec,
 | |
| 						// type: ypr.isBoolean ? 'predicate' : ypr.isReporter ? 'reporter' : 'command',
 | |
| 						type: ypr.type === 'boolean' ? 'predicate' : ypr.answer !== null ? 'reporter' : 'command',
 | |
| 						category:
 | |
| 							ypr.category === 'none' ? 'other' :
 | |
| 							ypr.category === 'list' ? 'lists' : ypr.category
 | |
| 					}, [
 | |
| 						n('inputs', {}, args),
 | |
| 						n('script', {}, nsScript(ypr.body))
 | |
| 					]);
 | |
| 				});
 | |
| 			}
 | |
| 			function nsScript(script) {
 | |
| 				return script.map(function (block) {
 | |
| 					return nBlock(block);
 | |
| 				});
 | |
| 			}
 | |
| 			function nBlock(block) {
 | |
| 				var node, a;
 | |
| 				node = n('block');
 | |
| 				switch (block[2]) {
 | |
| 				case 'EventHatMorph':
 | |
| 					if (block[3] === 'Scratch-StartClicked') {
 | |
| 						node.attributes.s = 'receiveGo';
 | |
| 					} else {
 | |
| 						node.attributes.s = 'receiveMessage';
 | |
| 						node.children.push(n('l', {}, [t(block[3])]));
 | |
| 					}
 | |
| 					return node;
 | |
| 				case 'MouseClickEventHatMorph':
 | |
| 					node.attributes.s = 'receiveClick';
 | |
| 					return node;
 | |
| 				case 'KeyEventHatMorph':
 | |
| 					node.attributes.s = 'receiveKey';
 | |
| 					node.children.push(n('l', {}, [t(block[3])]));
 | |
| 					return node;
 | |
| 				case 'doRunBlockWithArgs':
 | |
| 				case 'doCallBlockWithArgs':
 | |
| 				case 'doForkBlockWithArgs':
 | |
| 					a = block.length - 5;
 | |
| 					while (a--) {
 | |
| 						if (block[5 + a].pop) {
 | |
| 							block[5 + a] = ['block', '', [block[5 + a]]];
 | |
| 						}
 | |
| 					}
 | |
| 					a = {
 | |
| 						className: 'ScratchListMorph',
 | |
| 						complex: block.slice(5)
 | |
| 					};
 | |
| 					block = block.slice(0, 4);
 | |
| 					block.push(a);
 | |
| 					break;
 | |
| 				case 'doRunBlockWithArgList':
 | |
| 				case 'doCallBlockWithArgList':
 | |
| 				case 'doForkBlockWithArgList':
 | |
| 					a = block[5];
 | |
| 					block = block.slice(0, 4);
 | |
| 					block.push(a);
 | |
| 					break;
 | |
| 				case 'doDeclareVariables':
 | |
| 					block = ['byob', '', 'doDeclareVariables', {
 | |
| 						className: 'ScratchListMorph',
 | |
| 						complex: block.slice(3).map(function (block) {
 | |
| 							return block[3];
 | |
| 						})
 | |
| 					}];
 | |
| 					break;
 | |
| 				case 'getLine:ofList:':
 | |
| 				case 'append:toList:':
 | |
| 				case 'deleteLine:ofList:':
 | |
| 				case 'setLine:ofList:to:':
 | |
| 				case 'getLine:ofList:':
 | |
| 				case 'insert:at:ofList:':
 | |
| 				case 'lineCountOfList:':
 | |
| 				case 'list:contains:':
 | |
| 					a = block[2] === 'insert:at:ofList:' ? 5 :
 | |
| 						block[2] === 'lineCountOfList:' || block[2] === 'list:contains:' ? 3 : 4;
 | |
| 					if (typeof block[a] === 'string') {
 | |
| 						block[a] = block[a] === '' ? null : ['byob', '', 'readVariable', block[a]];
 | |
| 					}
 | |
| 					break;
 | |
| 				case 'readBlockVariable':
 | |
| 				case 'readVariable':
 | |
| 				case 'listNamed:':
 | |
| 					node.attributes['var'] = block[3];
 | |
| 					return node;
 | |
| 				case 'function':
 | |
| 				case 'functionWithArgs':
 | |
| 					node.attributes.s = 'reifyReporter';
 | |
| 					block.pop();
 | |
| 					node.children.push(n('autolambda', {}, [nBlock(block[8])]));
 | |
| 					if (block[2] === 'functionWithArgs') {
 | |
| 						node.children.push(n('list', {}, block.slice(9).map(function (arg) {
 | |
| 							return n('l', {}, [t(arg[3])]);
 | |
| 						})));
 | |
| 					}
 | |
| 					return node;
 | |
| 				case 'procedure':
 | |
| 				case 'procedureWithArgs':
 | |
| 					node.attributes.s = 'reifyScript';
 | |
| 					node.children.push(n('script', {}, nsScript(block.pop())));
 | |
| 					node.children.push(n('list', {}, block.slice(8).map(function (arg) {
 | |
| 						return n('l', {}, [t(arg[3])]);
 | |
| 					})));
 | |
| 					return node;
 | |
| 				case 'autoBlock':
 | |
| 				case 'autoPredicate':
 | |
| 					node.attributes.s = block[2] === 'autoPredicate' ? 'reifyPredicate' : 'reifyReporter';
 | |
| 					node.children.push(n('autolambda', {}, block[3] ? [nBlock(block[3][0])] : []));
 | |
| 					return node;
 | |
| 				case 'concatenate:with:':
 | |
| 					block = block.slice(0, 3).concat({
 | |
| 						className: 'ScratchListMorph',
 | |
| 						complex: block.slice(3)
 | |
| 					});
 | |
| 					a = 2;
 | |
| 					while (a--) {
 | |
| 						if (block[3].complex[a].pop) {
 | |
| 							block[3].complex[a] = ['block', '', [block[3].complex[a]]];
 | |
| 						}
 | |
| 					}
 | |
| 					break;
 | |
| 				case 'loopLambda':
 | |
| 					return n('script', {}, block[8] ? nsScript(block[8]) : []);
 | |
| 				case 'autoLambda':
 | |
| 					// node.attributes.s = block[2] === 'loopLambda' ? 'reifyScript' : 'reifyReporter';
 | |
|                     node.attributes.s = 'reifyScript';
 | |
| 					node.children.push(n('script', {}, block[8] ? nsScript(block[8]) : []));
 | |
| 					return node;
 | |
| 				case 'changeVariable':
 | |
| 				case 'changeBlockVariable':
 | |
| 					block = [0, 0, block[4], block[3], block[block[2] === 'changeBlockVariable' ? 6 : 5]];
 | |
| 					break;
 | |
|                 case 'distanceTo:':
 | |
|                 case 'gotoSpriteOrMouse:':
 | |
|                 case 'pointTowards:':
 | |
|                 case 'touching:':
 | |
|                     if (block[3] === 'mouse') {
 | |
|                         block[3] = 'mouse-pointer';
 | |
|                     } else {
 | |
|                         block[3] = block[3].objName;
 | |
|                     }
 | |
|                     break;
 | |
|                 case 'doForeverIf':
 | |
|                     block = [0, 0, 'doForever', [[0, 0, 'doIf', block[3], block[4]]]]
 | |
|                     break;
 | |
|                 case '\\\\':
 | |
|                 case '+':
 | |
|                 case '-':
 | |
|                 case '*':
 | |
|                 case '/':
 | |
|                     if (block[3] === ' ') {
 | |
|                         block[3] = '';
 | |
|                     }
 | |
|                     if (block[4] === ' ') {
 | |
|                         block[4] = '';
 | |
|                     }
 | |
|                     break;
 | |
|                 case 'setPenHueTo:':
 | |
|                 case 'changePenHueBy:':
 | |
|                     block[3] = ['byob', 0, '/', block[3], '2'];
 | |
|                     break;
 | |
| 				}
 | |
| 				if (block[2] === 'doCustomBlock') {
 | |
| 					a = customBlocks[block[1] === 'Stage' ? '__global__' : block[1]][block[3]];
 | |
| 					node = n('custom-block', {
 | |
| 						s: a.__blockSpec__
 | |
| 					});
 | |
| 					if (block[1] !== 'Stage' && !a.isGlobal) {
 | |
| 						node.attributes.scope = block[1];
 | |
| 					}
 | |
| 					a.__types__.forEach(function (type, i) {
 | |
| 						if (type === 'template') {
 | |
| 							block[4 + i] = block[4 + i][3];
 | |
| 						}
 | |
| 					});
 | |
| 					block.shift();
 | |
| 				} else {
 | |
| 					if ((node.attributes.s = blockSelectors[block[2]]) === undefined) {
 | |
| 						console.warn('Missing selector mapping for "' + block[2] + '"', block);
 | |
| 					}
 | |
| 				}
 | |
| 				block.slice(3).forEach(function (arg) {
 | |
| 					if (arg && arg.pop) {
 | |
| 						if (typeof arg[0] === 'string') {
 | |
| 							node.children.push(nBlock(arg));
 | |
| 						} else {
 | |
| 							node.children.push(n('script', {}, nsScript(arg)));
 | |
| 						}
 | |
| 					} else {
 | |
|                         a = nValue(arg);
 | |
|                         delete a.attributes.id;
 | |
| 						node.children.push(a);
 | |
| 					}
 | |
| 				});
 | |
| 				return node;
 | |
| 			}
 | |
| 			function nsVariables(vars, lists) {
 | |
| 				return vars.keys.map(function (key, i) {
 | |
| 					return n('variable', { name: key }, [nValue(vars.values[i], true)]);
 | |
| 				}).concat(lists.keys.map(function (key, i) {
 | |
| 					return n('variable', { name: key }, [nValue(lists.values[i], true)]);
 | |
| 				}));
 | |
| 			}
 | |
| 			function nValue(value, listHasItems) {
 | |
| 				return typeof value === 'string' || typeof value === 'number' ? n('l', {}, [t(value)]) :
 | |
| 					value == null || typeof value === 'boolean' ? n('l') :
 | |
| 					value.className === 'ScratchListMorph' ? id(n('list', {}, value.complex.map(function (object, i) {
 | |
| 						var item = object == null ? n('l', {}, [t((item = value.strings[i]) === 'nil' ? '' : item)]) :
 | |
| 							typeof object === 'string' ? nValue(object) :
 | |
| 							object && object[0] === 'block' ? nsScript(object[2])[0] : n('l');
 | |
| 						return listHasItems ? n('item', {}, [item]) : item;
 | |
| 					}))) :
 | |
| 					// (function () { throw 'Unimplemented' })();
 | |
| 					value.constructor === sb.Color ? n('color', {}, [t([value.r * 255, value.g * 255, value.b * 255, value.a * 255])]) :
 | |
| 					value.constructor === sb.Dictionary ? id(n('list')) :
 | |
| 					(function () { console.warn('No serializer for', value); return n('__undefined__') })();
 | |
| 			}
 | |
| 			function nsCostumes(media) {
 | |
| 				return [id(n('list', {}, media.filter(function (m) { return m.className === 'ImageMedia' }).map(function (c) {
 | |
| 					return n('item', {}, [
 | |
| 						n('costume', {
 | |
| 							name: c.mediaName,
 | |
| 							'center-x': c.rotationCenter.x,
 | |
| 							'center-y': c.rotationCenter.y,
 | |
| 							image: c.form.image.toDataURL()
 | |
| 						})
 | |
| 					]);
 | |
| 				})))];
 | |
| 			}
 | |
| 			function nsSounds(media) {
 | |
| 				if (media.filter(function (m) { return m.className === 'SoundMedia' }).length > 0) {
 | |
| 					console.warn('Sounds are unimplemented');
 | |
| 				}
 | |
| 				return [id(n('list'))];
 | |
| 			}
 | |
| 			function nsScripts(scripts) {
 | |
| 				return scripts.map(function (script) {
 | |
| 					if (script[1][0] && script[1][0][2] === 'scratchComment') {
 | |
| 						return n('comment', {
 | |
| 							x: script[0].x,
 | |
| 							y: script[0].y,
 | |
| 							w: script[1][0][5],
 | |
| 							collapsed: !script[1][0][4]
 | |
| 						}, [t(script[1][0][3])]);
 | |
| 					}
 | |
| 					return n('script', {
 | |
| 						x: script[0].x,
 | |
| 						y: script[0].y
 | |
| 					}, nsScript(script[1]));
 | |
| 				});
 | |
| 			}
 | |
| 			function nsSprites(sprites, vars, lists) {
 | |
| 				return sprites.map(function (sprite, i) {
 | |
| 					return id(n('sprite', {
 | |
| 						name: sprite.objName,
 | |
| 						x: (sprite.bounds.origin.x + sprite.bounds.corner.x) / 2 - 240,
 | |
| 						y: 180 - (sprite.bounds.origin.y + sprite.bounds.corner.y) / 2,
 | |
|                         hidden: sprite.flags === 1,
 | |
| 						heading: sprite.rotationDegrees + 90,
 | |
| 						scale: sprite.scalePoint.x,
 | |
| 						rotation: rotationStyles[sprite.rotationStyle],
 | |
| 						draggable: sprite.draggable,
 | |
| 						costume: costumeIndex(sprite),
 | |
| 						color: (sprite.color.r * 255 | 0) + ',' + (sprite.color.g * 255 | 0) + ',' + (sprite.color.b * 255 | 0),
 | |
|                         pen: 'tip',
 | |
|                         idx: i
 | |
| 					}, [
 | |
| 						n('variables', {}, nsVariables(sprite.vars, sprite.lists)),
 | |
| 						n('costumes', {}, nsCostumes(sprite.media)),
 | |
| 						n('sounds', {}, nsSounds(sprite.media)),
 | |
| 						n('blocks', {}, nsCustomBlocks(sprite.customBlocks, false)),
 | |
| 						n('scripts', {}, nsScripts(sprite.blocksBin))
 | |
| 					]));
 | |
| 				}).concat(vars.keys.concat(lists.keys).map(function (name) {
 | |
| 					return n('watcher', {
 | |
| 						'var': name,
 | |
| 						style: 'normal',
 | |
| 						x: 10,
 | |
| 						y: 10,
 | |
| 						color: '243,118,29',
 | |
| 						hidden: true
 | |
| 					});
 | |
| 				}));
 | |
| 			}
 | |
| 
 | |
| 			console.log(project);
 | |
| 			preCustomBlocks('__global__', project.stage.customBlocks);
 | |
| 			project.stage.sprites.forEach(function (sprite) {
 | |
| 				preCustomBlocks(sprite.objName, sprite.customBlocks);
 | |
| 			});
 | |
| 			xml = n('project', {
 | |
| 				name: projectName,
 | |
| 				app: 'Snap! 4.0, http://snap.berkeley.edu; serialized by yprxml, http://dl.dropbox.com/u/10715865/Web/snap/app/yprxml.html',
 | |
| 				version: '1'
 | |
| 			}, [
 | |
| 				n('notes', {}, [t(project.info.get('comment') || '')]),
 | |
| 				n('thumbnail', {}, [t((a = project.info.get('thumbnail')) ? a.image.toDataURL() : '')]),
 | |
| 				id(n('stage', {
 | |
|                     name: 'Stage',
 | |
|                     tempo: project.stage.tempoBPM,
 | |
| 					costume: costumeIndex(project.stage),
 | |
| 					threadsafe: 'false',
 | |
|                     scheduled: true
 | |
| 				}, [
 | |
| 					n('pentrails', {}, [t((a = project.info.get('penTrails')) ? a.image.toDataURL() : '')]),
 | |
| 					n('costumes', {}, nsCostumes(project.stage.media)),
 | |
| 					n('sounds', {}, nsSounds(project.stage.media)),
 | |
| 					n('blocks', {}, []),
 | |
| 					n('variables', {}, []),
 | |
| 					n('scripts', {}, nsScripts(project.stage.blocksBin)),
 | |
| 					n('sprites', {}, nsSprites(project.stage.sprites, project.stage.vars, project.stage.lists))
 | |
| 				])),
 | |
| 				n('variables', {}, nsVariables(project.stage.vars, project.stage.lists)),
 | |
| 				n('blocks', {}, nsCustomBlocks(project.stage.customBlocks, true))
 | |
| 			]);
 | |
| 			xml.toXML(out = []);
 | |
|             // var d = document.createElement('textarea');
 | |
|             // d.style.position = 'absolute';
 | |
|             // document.body.appendChild(d).value = out.join('');
 | |
| 			return out.join('');
 | |
| 		};
 | |
| 
 | |
| 	})(sb.XMLWriter = function () {}, sb.XMLWriter.prototype);
 | |
| 
 | |
| 	return sb;
 | |
| 
 | |
| })({});
 |