Initial import.
[matrixcreatrix.git] / ps2.1.js
1 // ps2.1.js for Perlenspiel 2.1
2
3 /*
4 Perlenspiel is a scheme by Professor Moriarty (bmoriarty@wpi.edu).
5 Perlenspiel is Copyright © 2009-12 Worcester Polytechnic Institute.
6 This file is part of Perlenspiel.
7
8 Perlenspiel is free software: you can redistribute it and/or modify
9 it under the terms of the GNU Lesser General Public License as published
10 by the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
12
13 Perlenspiel is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU Lesser General Public License for more details.
17
18 You may have received a copy of the GNU Lesser General Public License
19 along with Perlenspiel. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22 // The following comments are for JSLint
23
24 /*global document, window, Audio, Image */
25
26 // Global namespace variable
27
28 var PS = {
29
30 // Constants
31
32 VERSION: "2.1.00",
33 DEFAULT: -1, // use default value
34 CURRENT: -2, // use current value
35 ALL: -3, // Use all rows or columns
36 ERROR: "*ERROR*", // generic error return value
37 CANVAS_SIZE: 480, // max width/height of canvas
38 GRID_MAX: 32, // max x/y dimensions of grid
39 GRID_DEFAULT_WIDTH: 8,
40 GRID_DEFAULT_HEIGHT: 8,
41 DEFAULT_BEAD_COLOR: 0x000000,
42 DEFAULT_BEAD_RED: 0x00,
43 DEFAULT_BEAD_GREEN: 0x00,
44 DEFAULT_BEAD_BLUE: 0x00,
45 DEFAULT_BG_COLOR: 0xFFFFFF,
46 DEFAULT_BG_RED: 0xFF,
47 DEFAULT_BG_GREEN: 0xFF,
48 DEFAULT_BG_BLUE: 0xFF,
49 DEFAULT_BORDER_COLOR: 0x808080,
50 DEFAULT_BORDER_RED: 0x80,
51 DEFAULT_BORDER_GREEN: 0x80,
52 DEFAULT_BORDER_BLUE: 0x80,
53 DEFAULT_BORDER_WIDTH: 1,
54 BORDER_WIDTH_MAX: 8,
55 DEFAULT_GLYPH_COLOR: 0xFFFFFF,
56 DEFAULT_GLYPH_RED: 0xFF,
57 DEFAULT_GLYPH_GREEN: 0xFF,
58 DEFAULT_GLYPH_BLUE: 0xFF,
59 DEFAULT_FLASH_COLOR: 0xFFFFFF,
60 DEFAULT_FLASH_RED: 0xFF,
61 DEFAULT_FLASH_GREEN: 0xFF,
62 DEFAULT_FLASH_BLUE: 0xFF,
63 DEFAULT_ALPHA: 100, // must be between 0 and 100
64 DEFAULT_VOLUME: 1.0, // must be between 0 and 1.0
65 DEFAULT_LOOP: false,
66 DEFAULT_FPS: 10, // frame rate in milliseconds (1/100 sec)
67 REDSHIFT: 256 * 256, // used to decode rgb
68 FLASH_STEP: 10, // percent for each flash
69 STATUS_FLASH_STEP: 5, // percent for each step
70 FLASH_INTERVAL: 5, // number of ticks per flash step
71 DEFAULT_TEXT_COLOR: 0x000000,
72
73 // Color constants
74
75 COLOR_BLACK: 0x000000,
76 COLOR_WHITE: 0xFFFFFF,
77 COLOR_GRAY_LIGHT: 0xC0C0C0,
78 COLOR_GRAY: 0x808080,
79 COLOR_GRAY_DARK: 0x404040,
80 COLOR_RED: 0xFF0000,
81 COLOR_ORANGE: 0xFF8000,
82 COLOR_YELLOW: 0xFFFF00,
83 COLOR_GREEN: 0x00FF00,
84 COLOR_BLUE: 0x0000FF,
85 COLOR_INDIGO: 0x4000FF,
86 COLOR_VIOLET: 0x8000FF,
87 COLOR_MAGENTA: 0xFF00FF,
88 COLOR_CYAN: 0x00FFFF,
89
90 // Key and mouse wheel constants
91
92 ARROW_LEFT: 37,
93 ARROW_RIGHT: 39,
94 ARROW_UP: 38,
95 ARROW_DOWN: 40,
96 KEYPAD_0: 96,
97 KEYPAD_1: 97,
98 KEYPAD_2: 98,
99 KEYPAD_3: 99,
100 KEYPAD_4: 100,
101 KEYPAD_5: 101,
102 KEYPAD_6: 102,
103 KEYPAD_7: 103,
104 KEYPAD_8: 104,
105 KEYPAD_9: 105,
106 F1: 112,
107 F2: 113,
108 F3: 114,
109 F4: 115,
110 F5: 116,
111 F6: 117,
112 F7: 118,
113 F8: 119,
114 F9: 120,
115 F10: 121,
116 FORWARD: 1,
117 BACKWARD: -1,
118
119 Grid: null, // main grid
120 DebugWindow: null, // debugger window
121 ImageCanvas: null, // offscreen canvas for image manipulation
122
123 // Coordinates of current and previous beads, -1 if none
124
125 MouseX: -1,
126 MouseY: -1,
127 LastX: -1,
128 LastY: -1,
129
130 // Delay and clock settings
131
132 FlashDelay: 0,
133 UserDelay: 0,
134 UserClock: 0,
135
136 // Status line
137
138 Status: "Perlenspiel",
139 StatusHue: 0, // target hue
140 StatusRed: 0,
141 StatusGreen: 0,
142 StatusBlue: 0,
143 StatusPhase: 0, // 100: done fading
144 StatusFading: true
145 };
146
147 // Improved typeof that distinguishes arrays
148
149 PS.TypeOf = function (value)
150 {
151 "use strict";
152 var s;
153
154 s = typeof value;
155 if ( s === "object" )
156 {
157 if ( value )
158 {
159 if ( value instanceof Array )
160 {
161 s = "array";
162 }
163 }
164 else
165 {
166 s = "null";
167 }
168 }
169 return s;
170 };
171
172 // Get the canvas context
173
174 PS.Context = function ()
175 {
176 "use strict";
177 var cv, ctx;
178
179 ctx = null;
180 cv = document.getElementById("screen");
181 if ( cv && cv.getContext )
182 {
183 ctx = cv.getContext("2d");
184 }
185
186 return ctx;
187 };
188
189 // Takes a multiplexed rgb value and a function name
190 // Returns floored rgb value, or -1 if invalid
191
192 PS.ValidRGB = function ( rgb, fn )
193 {
194 "use strict";
195
196 if ( typeof rgb !== "number" )
197 {
198 PS.Oops( fn + "rgb parameter not a number" );
199 return -1;
200 }
201 rgb = Math.floor(rgb);
202 if ( rgb < 0 )
203 {
204 PS.Oops( fn + "rgb parameter negative" );
205 return -1;
206 }
207 if ( rgb > 0xFFFFFF )
208 {
209 PS.Oops( fn + "rgb parameter out of range" );
210 return -1;
211 }
212 return rgb;
213 };
214
215 // Construct a color string with optional alpha
216
217 PS.RGBString = function (r, g, b, a)
218 {
219 "use strict";
220 var str;
221
222 if ( a === undefined )
223 {
224 str = "rgb(" + r + "," + g + "," + b + ")";
225 }
226 else
227 {
228 str = "rgba(" + r + "," + g + "," + b + "," + a + ")";
229 }
230 return str;
231 };
232
233 // Takes a multiplexed rgb value and creates an object with
234 // separate r, g and b values, or null if error
235
236 PS.UnmakeRGB = function ( rgb )
237 {
238 "use strict";
239 var fn, red, green, blue, rval, gval;
240
241 fn = "[PS.DecodeRGB] ";
242
243 if ( typeof rgb !== "number" )
244 {
245 PS.Oops(fn + "RGB parameter not a number");
246 return PS.ERROR;
247 }
248 rgb = Math.floor(rgb);
249 if ( rgb < 0 )
250 {
251 PS.Oops(fn + "RGB parameter negative");
252 return PS.ERROR;
253 }
254 if ( rgb > 0xFFFFFF )
255 {
256 PS.Oops(fn + "RGB parameter out of range");
257 return PS.ERROR;
258 }
259
260 red = rgb / PS.REDSHIFT;
261 red = Math.floor(red);
262 rval = red * PS.REDSHIFT;
263
264 green = (rgb - rval) / 256;
265 green = Math.floor(green);
266 gval = green * 256;
267
268 blue = rgb - rval - gval;
269
270 return { r: red, g: green, b: blue };
271 };
272
273 // PS.Dissolve
274 // Returns a color that is x% between c1 and c2
275
276 PS.Dissolve = function ( c1, c2, x )
277 {
278 "use strict";
279 var delta;
280
281 if ( c1 > c2 )
282 {
283 delta = c1 - c2;
284 delta = ( x * delta ) / 100;
285 delta = Math.floor(delta);
286 return ( c1 - delta );
287 }
288 else
289 {
290 delta = c2 - c1;
291 delta = ( x * delta ) / 100;
292 delta = Math.floor(delta);
293 return ( c1 + delta );
294 }
295 };
296
297 // Bead constuctor
298
299 PS.InitBead = function (xpos, ypos, size, bgcolor)
300 {
301 "use strict";
302 var bead;
303
304 bead = {};
305
306 bead.left = xpos;
307 bead.right = xpos + size;
308 bead.top = ypos;
309 bead.bottom = ypos + size;
310
311 bead.size = size;
312
313 bead.visible = true; // bead visible?
314
315 // target color
316
317 bead.dirty = false; // bead color touched?
318
319 // base colors
320
321 bead.red = PS.DEFAULT_BEAD_RED;
322 bead.green = PS.DEFAULT_BEAD_GREEN;
323 bead.blue = PS.DEFAULT_BEAD_BLUE;
324 bead.color = PS.RGBString (bead.red, bead.green, bead.blue); // default color
325 bead.colorNow = bead.color; // actual color while drawing
326
327 // pre-calculated alpha colors
328
329 bead.alpha = PS.DEFAULT_ALPHA;
330 bead.alphaRed = PS.DEFAULT_BEAD_RED;
331 bead.alphaGreen = PS.DEFAULT_BEAD_GREEN;
332 bead.alphaBlue = PS.DEFAULT_BEAD_BLUE;
333
334 // glyph params
335
336 bead.glyph = 0; // glyph code (zero if none)
337 bead.glyphStr = ""; // actual string to print
338 bead.glyphRed = PS.DEFAULT_GLYPH_RED;
339 bead.glyphGreen = PS.DEFAULT_GLYPH_GREEN;
340 bead.glyphBlue = PS.DEFAULT_GLYPH_BLUE;
341 bead.glyphColor = PS.RGBString (PS.DEFAULT_GLYPH_RED, PS.DEFAULT_GLYPH_GREEN, PS.DEFAULT_GLYPH_BLUE);
342
343 // flash params
344
345 bead.flash = true; // flashing enabled?
346 bead.flashPhase = 0; // phase of flash animation
347 bead.flashRed = PS.DEFAULT_FLASH_RED;
348 bead.flashGreen = PS.DEFAULT_FLASH_GREEN;
349 bead.flashBlue = PS.DEFAULT_FLASH_BLUE;
350 bead.flashColor = PS.RGBString (PS.DEFAULT_FLASH_RED, PS.DEFAULT_FLASH_GREEN, PS.DEFAULT_FLASH_BLUE);
351
352 // border params
353
354 bead.borderWidth = PS.DEFAULT_BORDER_WIDTH; // border width; 0 if none
355 bead.borderRed = PS.DEFAULT_BORDER_RED;
356 bead.borderGreen = PS.DEFAULT_BORDER_GREEN;
357 bead.borderBlue = PS.DEFAULT_BORDER_BLUE;
358 bead.borderAlpha = PS.DEFAULT_ALPHA; // border alpha
359 bead.borderColor = PS.RGBString (PS.DEFAULT_BORDER_RED, PS.DEFAULT_BORDER_GREEN, PS.DEFAULT_BORDER_BLUE);
360
361 // data, sound, exec params
362
363 bead.data = 0; // data value
364
365 bead.audio = null; // sound (null = none)
366 bead.volume = PS.DEFAULT_VOLUME; // volume
367 bead.loop = PS.DEFAULT_LOOP; // loop flag
368
369 bead.exec = null; // on-click function (null = none)
370
371 // give each bead its own offscreen canvas and context
372
373 bead.off = document.createElement("canvas");
374 bead.off.width = size;
375 bead.off.height = size;
376 bead.off.backgroundColor = bgcolor;
377 bead.offContext = bead.off.getContext("2d");
378
379 // set up font info for offscreen context
380
381 bead.offContext.font = Math.floor(size / 2) + "pt sans-serif";
382 bead.offContext.textAlign = "center";
383 bead.offContext.textBaseline = "middle";
384
385 return bead;
386 };
387
388 // Draws bead [bead] in (optional) context [ctx]
389
390 PS.DrawBead = function (bead, ctx)
391 {
392 "use strict";
393 var offctx, left, top, size, width;
394
395 // get destination context if not provided
396
397 if ( ctx === undefined )
398 {
399 ctx = PS.Context();
400 }
401
402 left = 0;
403 top = 0;
404 size = bead.size;
405
406 offctx = bead.offContext; // the offscreen canvas context
407
408 // draw border if needed
409
410 width = bead.borderWidth;
411 if ( width > 0 )
412 {
413 offctx.fillStyle = bead.borderColor;
414 offctx.fillRect(0, 0, size, size);
415
416 // adjust position and size of bead rect
417
418 left += width;
419 top += width;
420 size -= (width + width);
421 }
422
423 // draw bead body if dirty (has had color explicitly set)
424
425 if ( bead.dirty )
426 {
427 offctx.fillStyle = bead.colorNow;
428 }
429
430 // otherwise fill with background color
431
432 else
433 {
434 offctx.fillStyle = PS.Grid.bgColor;
435 }
436
437 offctx.fillRect(left, top, size, size);
438
439 if ( bead.glyph > 0 )
440 {
441 offctx.fillStyle = bead.glyphColor;
442 offctx.fillText (bead.glyphStr, PS.Grid.glyphX, PS.Grid.glyphY);
443 }
444
445 // blit offscreen canvas to main canvas
446
447 ctx.drawImage(bead.off, bead.left, bead.top);
448 };
449
450 // Erase bead [bead] in (optional) context [ctx]
451
452 PS.EraseBead = function (bead, ctx)
453 {
454 "use strict";
455 var size, left, top, width;
456
457 // get destination context if not provided
458
459 if ( ctx === undefined )
460 {
461 ctx = PS.Context();
462 }
463
464 left = bead.left;
465 top = bead.top;
466 size = bead.size;
467
468 // draw border if needed
469
470 width = bead.borderWidth;
471 if ( width > 0 )
472 {
473 ctx.fillStyle = bead.borderColor;
474 ctx.fillRect(left, top, size, size);
475
476 // adjust position and size of bead rect
477
478 left += width;
479 top += width;
480 size -= (width + width);
481 }
482
483 ctx.fillStyle = PS.Grid.bgColor;
484 ctx.fillRect(left, top, size, size);
485 };
486
487 // Grid constructor
488 // Call with x/y dimensions of grid
489 // Returns initialized grid object or null if error
490
491 PS.InitGrid = function (x, y)
492 {
493 "use strict";
494 var grid, i, j, size, xpos, ypos;
495
496 grid = {};
497
498 grid.x = x; // x dimensions of grid
499 grid.y = y; // y dimensions of grid
500 grid.count = x * y; // number of beads in grid
501
502 // calc size of beads, position/dimensions of centered grid on canvas
503
504 if ( x >= y )
505 {
506 grid.beadSize = size = Math.floor(PS.CANVAS_SIZE / x);
507 grid.width = size * x;
508 grid.height = size * y;
509 grid.left = 0;
510 }
511 else
512 {
513 grid.beadSize = size = Math.floor(PS.CANVAS_SIZE / y);
514 grid.width = size * x;
515 grid.height = size * y;
516 grid.left = Math.floor( (PS.CANVAS_SIZE - grid.width) / 2 );
517 }
518
519 grid.top = 0;
520
521 grid.right = grid.left + grid.width;
522 grid.bottom = grid.top + grid.height;
523
524 grid.bgRed = PS.DEFAULT_BG_RED;
525 grid.bgGreen = PS.DEFAULT_BG_GREEN;
526 grid.bgBlue = PS.DEFAULT_BG_BLUE;
527 grid.bgColor = PS.RGBString (grid.bgRed, grid.bgGreen, grid.bgBlue);
528
529 grid.borderRed = PS.DEFAULT_BORDER_RED;
530 grid.borderGreen = PS.DEFAULT_BORDER_GREEN;
531 grid.borderBlue = PS.DEFAULT_BORDER_BLUE;
532 grid.borderColor = PS.RGBString (grid.borderRed, grid.borderGreen, grid.borderBlue);
533
534 // grid.borderWidth = PS.DEFAULT_BORDER_WIDTH;
535 grid.borderMax = PS.BORDER_WIDTH_MAX; // for now; should be calculated
536
537 grid.pointing = -1; // bead cursor is pointing at (-1 if none)
538
539 grid.flash = true; // flash globally enabled?
540 grid.flashList = []; // array of currently flashing beads
541
542 grid.glyphX = Math.floor(size / 2);
543 grid.glyphY = Math.floor((size / 7) * 4);
544
545 // init beads
546
547 grid.beads = [];
548 ypos = grid.top;
549 for ( j = 0; j < y; j += 1 )
550 {
551 xpos = grid.left;
552 for ( i = 0; i < x; i += 1 )
553 {
554 grid.beads.push( PS.InitBead(xpos, ypos, size, grid.bgColor) );
555 xpos += size;
556 }
557 ypos += size;
558 }
559
560 return grid;
561 };
562
563 PS.DrawGrid = function ()
564 {
565 "use strict";
566 var ctx, beads, cnt, i, bead;
567
568 ctx = PS.Context();
569 beads = PS.Grid.beads;
570 cnt = PS.Grid.count;
571
572 for ( i = 0; i < cnt; i += 1 )
573 {
574 bead = beads[i];
575 if ( bead.visible )
576 {
577 PS.DrawBead(bead, ctx);
578 }
579 else
580 {
581 PS.EraseBead(bead, ctx);
582 }
583 }
584 };
585
586 // Returns true if x parameter is valid, else false
587
588 PS.CheckX = function ( x, fn )
589 {
590 "use strict";
591
592 if ( typeof x !== "number" )
593 {
594 PS.Oops(fn + "x parameter not a number");
595 return false;
596 }
597 x = Math.floor(x);
598 if ( x < 0 )
599 {
600 PS.Oops(fn + "x parameter negative");
601 return false;
602 }
603 if ( x >= PS.Grid.x )
604 {
605 PS.Oops(fn + "x parameter exceeds grid width");
606 return false;
607 }
608
609 return true;
610 };
611
612 // Returns true if y parameter is valid, else false
613
614 PS.CheckY = function ( y, fn )
615 {
616 "use strict";
617
618 if ( typeof y !== "number" )
619 {
620 PS.Oops(fn + "y parameter not a number");
621 return false;
622 }
623 y = Math.floor(y);
624 if ( y < 0 )
625 {
626 PS.Oops(fn + "y parameter negative");
627 return false;
628 }
629 if ( y >= PS.Grid.y )
630 {
631 PS.Oops(fn + "y parameter exceeds grid height");
632 return false;
633 }
634
635 return true;
636 };
637
638 // PS.GetBead(x, y)
639 // Takes x/y coords of bead and function name
640 // Returns the bead object at (x, y), or null if error
641
642 PS.GetBead = function (x, y, fn)
643 {
644 "use strict";
645 var i;
646
647 if ( !PS.CheckX( x, fn ) || !PS.CheckY( y, fn ) )
648 {
649 return null;
650 }
651
652 i = x + (y * PS.Grid.x); // get index of bead
653
654 return PS.Grid.beads[i];
655 };
656
657 // API Functions
658
659 PS.GridSize = function (w, h)
660 {
661 "use strict";
662 var fn, i, cnt, beads, cv;
663
664 fn = "[PS.GridSize] ";
665
666 if ( typeof w !== "number" )
667 {
668 PS.Oops(fn + "Width param not a number");
669 return;
670 }
671 if ( typeof h !== "number" )
672 {
673 PS.Oops(fn + "Height param not a number");
674 return;
675 }
676
677 w = Math.floor(w);
678 if ( w === PS.DEFAULT )
679 {
680 w = PS.GRID_DEFAULT_WIDTH;
681 }
682 else if ( w < 1 )
683 {
684 PS.Oops(fn + "width parameter < 1");
685 w = 1;
686 }
687 else if ( w > PS.GRID_MAX )
688 {
689 PS.Oops(fn + "width parameter > " + PS.GRID_MAX);
690 w = PS.GRID_MAX;
691 }
692
693 h = Math.floor(h);
694 if ( h === PS.DEFAULT )
695 {
696 h = PS.GRID_DEFAULT_HEIGHT;
697 }
698 else if ( h < 1 )
699 {
700 PS.Oops(fn + "height parameter < 1");
701 h = 1;
702 }
703 else if ( h > PS.GRID_MAX )
704 {
705 PS.Oops(fn + "height parameter > " + PS.GRID_MAX);
706 h = PS.GRID_MAX;
707 }
708
709 // If a grid already exists, null out its arrays and then itself
710
711 if ( PS.Grid )
712 {
713 beads = PS.Grid.beads;
714 if ( beads )
715 {
716 cnt = PS.Grid.count;
717 for ( i = 0; i < cnt; i += 1 )
718 {
719 beads[i] = null;
720 }
721 }
722
723 PS.Grid.beads = null;
724 PS.Grid.flashList = null;
725 PS.Grid = null;
726 }
727
728 PS.Grid = PS.InitGrid(w, h);
729
730 // Reset mouse coordinates
731
732 PS.MouseX = -1;
733 PS.MouseY = -1;
734 PS.LastX = -1;
735 PS.LastY = -1;
736
737 // Erase the canvas
738
739 if ( PS.Grid )
740 {
741 cv = document.getElementById("screen");
742 if ( cv )
743 {
744 cv.height = PS.Grid.height; // setting height erases canvas
745 PS.DrawGrid();
746 }
747 }
748 };
749
750 PS.GridBGColor = function ( rgb )
751 {
752 "use strict";
753 var fn, current, colors, e;
754
755 fn = "[PS.GridBGColor] ";
756 current = (PS.Grid.bgRed * PS.REDSHIFT) + (PS.Grid.bgGreen * 256) + PS.Grid.bgBlue;
757
758 // if param or PS.CURRENT, just return current color
759
760 if ( (rgb === undefined) || (rgb === PS.CURRENT) )
761 {
762 return current;
763 }
764
765 if ( rgb === PS.DEFAULT )
766 {
767 rgb = PS.DEFAULT_BG_COLOR;
768 }
769 else
770 {
771 rgb = PS.ValidRGB( rgb, fn );
772 if ( rgb < 0 )
773 {
774 return PS.ERROR;
775 }
776 }
777
778 colors = PS.UnmakeRGB(rgb);
779 if ( colors )
780 {
781 PS.Grid.bgRed = colors.r;
782 PS.Grid.bgGreen = colors.g;
783 PS.Grid.bgBlue = colors.b;
784 PS.Grid.bgColor = PS.RGBString(colors.r, colors.g, colors.b);
785
786 // Reset browser background
787
788 e = document.body;
789 e.style.backgroundColor = PS.Grid.bgColor;
790
791 // reset status line background
792
793 e = document.getElementById("status");
794 if ( e )
795 {
796 e.style.backgroundColor = PS.Grid.bgColor;
797 }
798
799 // redraw canvas
800
801 e = document.getElementById("screen");
802 if ( e )
803 {
804 e.width = PS.CANVAS_SIZE; // setting width erases
805 PS.DrawGrid();
806 }
807 }
808
809 return rgb;
810 };
811
812 // PS.MakeRGB (r, g, b)
813 // Takes three colors and returns multiplexed rgb value, or 0 (black) if error
814
815 PS.MakeRGB = function (r, g, b)
816 {
817 "use strict";
818 var fn, rgb;
819
820 fn = "[PS.MakeRGB] ";
821
822 if ( typeof r !== "number" )
823 {
824 PS.Oops(fn + "R parameter not a number");
825 return PS.ERROR;
826 }
827 r = Math.floor(r);
828 if ( r < 0 )
829 {
830 r = 0;
831 }
832 else if ( r > 255 )
833 {
834 r = 255;
835 }
836
837 if ( typeof g !== "number" )
838 {
839 PS.Oops(fn + "G parameter not a number");
840 return PS.ERROR;
841 }
842 g = Math.floor(g);
843 if ( g < 0 )
844 {
845 g = 0;
846 }
847 else if ( g > 255 )
848 {
849 g = 255;
850 }
851
852 if ( typeof b !== "number" )
853 {
854 PS.Oops(fn + "B parameter not a number");
855 return PS.ERROR;
856 }
857 b = Math.floor(b);
858 if ( b < 0 )
859 {
860 b = 0;
861 }
862 else if ( b > 255 )
863 {
864 b = 255;
865 }
866
867 rgb = (r * PS.REDSHIFT) + (g * 256) + b;
868
869 return rgb;
870 };
871
872 // Bead API
873
874 // PS.BeadShow(x, y, flag)
875 // Returns a bead's display status and optionally changes it
876 // [x, y] are grid position
877 // Optional [flag] must be 1/true or 0/false
878
879 PS.DoBeadShow = function (x, y, flag)
880 {
881 "use strict";
882 var i, bead;
883
884 // Assume x/y params are already verified
885
886 i = x + (y * PS.Grid.x); // get index of bead
887 bead = PS.Grid.beads[i];
888
889 if ( (flag === undefined) || (flag === PS.CURRENT) || (flag === bead.visible) )
890 {
891 return bead.visible;
892 }
893
894 bead.visible = flag;
895 if ( flag )
896 {
897 if ( PS.Grid.flash && bead.flash )
898 {
899 PS.FlashStart(x, y);
900 }
901 else
902 {
903 bead.colorNow = bead.color;
904 PS.DrawBead(bead);
905 }
906 }
907 else
908 {
909 PS.EraseBead(bead);
910 }
911
912 return flag;
913 };
914
915 PS.BeadShow = function (x, y, flag)
916 {
917 "use strict";
918 var fn, i, j;
919
920 fn = "[PS.BeadShow] ";
921
922 // normalize flag value to t/f if defined
923
924 if ( (flag !== undefined) && (flag !== PS.CURRENT) )
925 {
926 if ( flag === PS.DEFAULT )
927 {
928 flag = true;
929 }
930 else if ( flag )
931 {
932 flag = true;
933 }
934 else
935 {
936 flag = false;
937 }
938 }
939
940 if ( x === PS.ALL )
941 {
942 if ( y === PS.ALL ) // do entire grid
943 {
944 for ( j = 0; j < PS.Grid.y; j += 1 )
945 {
946 for ( i = 0; i < PS.Grid.x; i += 1 )
947 {
948 flag = PS.DoBeadShow( i, j, flag );
949 }
950 }
951 }
952 else if ( !PS.CheckY( y, fn ) ) // verify y param
953 {
954 return PS.ERROR;
955 }
956 else
957 {
958 for ( i = 0; i < PS.Grid.x; i += 1 ) // do entire row
959 {
960 flag = PS.DoBeadShow( i, y, flag );
961 }
962 }
963 }
964 else if ( y === PS.ALL )
965 {
966 if ( !PS.CheckX( x, fn ) ) // verify x param
967 {
968 return PS.ERROR;
969 }
970 for ( j = 0; j < PS.Grid.y; j += 1 ) // do entire column
971 {
972 flag = PS.DoBeadShow( x, j, flag );
973 }
974 }
975 else if ( !PS.CheckX( x, fn ) || !PS.CheckY( y, fn ) ) // verify both params
976 {
977 return PS.ERROR;
978 }
979 else
980 {
981 flag = PS.DoBeadShow( x, y, flag ); // do one bead
982 }
983
984 return flag;
985 };
986
987 // PS.BeadColor (x, y, rgb)
988 // Returns and optionally sets a bead's color
989 // [x, y] are grid position
990 // Optional [rgb] must be a multiplexed rgb value (0xRRGGBB)
991
992 PS.DoBeadColor = function ( x, y, rgb, r, g, b )
993 {
994 "use strict";
995 var i, bead;
996
997 // Assume x/y params are already verified
998
999 i = x + (y * PS.Grid.x); // get index of bead
1000 bead = PS.Grid.beads[i];
1001
1002 if ( (rgb === undefined) || (rgb === PS.CURRENT) ) // if no rgb or PS.CURRENT, return current color
1003 {
1004 return (bead.red * PS.REDSHIFT) + (bead.green * 256) + bead.blue;
1005 }
1006
1007 bead.dirty = true; // mark this bead as explicitly colored
1008
1009 bead.red = r;
1010 bead.green = g;
1011 bead.blue = b;
1012 if ( bead.alpha < PS.DEFAULT_ALPHA ) // Calc new color based on alpha
1013 {
1014 bead.alphaRed = PS.Dissolve( PS.Grid.bgRed, r, bead.alpha );
1015 bead.alphaGreen = PS.Dissolve( PS.Grid.bgGreen, g, bead.alpha );
1016 bead.alphaBlue = PS.Dissolve( PS.Grid.bgBlue, b, bead.alpha );
1017 bead.color = PS.RGBString( bead.alphaRed, bead.alphaGreen, bead.alphaBlue );
1018 }
1019 else
1020 {
1021 bead.alphaRed = r;
1022 bead.alphaGreen = g;
1023 bead.alphaBlue = b;
1024 bead.color = PS.RGBString( r, g, b );
1025 }
1026
1027 if ( bead.visible )
1028 {
1029 if ( PS.Grid.flash && bead.flash )
1030 {
1031 PS.FlashStart(x, y);
1032 }
1033 else
1034 {
1035 bead.colorNow = bead.color;
1036 PS.DrawBead(bead);
1037 }
1038 }
1039
1040 return rgb;
1041 };
1042
1043 PS.BeadColor = function (x, y, rgb)
1044 {
1045 "use strict";
1046 var fn, colors, r, g, b, i, j;
1047
1048 fn = "[PS.BeadColor] ";
1049
1050 // if no rgb specified, just return current color
1051
1052 if ( rgb === PS.DEFAULT )
1053 {
1054 rgb = PS.DEFAULT_BEAD_COLOR;
1055 r = PS.DEFAULT_BG_RED;
1056 g = PS.DEFAULT_BG_GREEN;
1057 b = PS.DEFAULT_BG_BLUE;
1058 }
1059 else if ( (rgb !== undefined) && (rgb !== PS.CURRENT) )
1060 {
1061 rgb = PS.ValidRGB( rgb, fn );
1062 if ( rgb < 0 )
1063 {
1064 return PS.ERROR;
1065 }
1066 colors = PS.UnmakeRGB( rgb );
1067 r = colors.r;
1068 g = colors.g;
1069 b = colors.b;
1070 }
1071
1072 if ( x === PS.ALL )
1073 {
1074 if ( y === PS.ALL ) // do entire grid
1075 {
1076 for ( j = 0; j < PS.Grid.y; j += 1 )
1077 {
1078 for ( i = 0; i < PS.Grid.x; i += 1 )
1079 {
1080 rgb = PS.DoBeadColor( i, j, rgb, r, g, b );
1081 }
1082 }
1083 }
1084 else if ( !PS.CheckY( y, fn ) ) // verify y param
1085 {
1086 return PS.ERROR;
1087 }
1088 else
1089 {
1090 for ( i = 0; i < PS.Grid.x; i += 1 ) // do entire row
1091 {
1092 rgb = PS.DoBeadColor( i, y, rgb, r, g, b );
1093 }
1094 }
1095 }
1096 else if ( y === PS.ALL )
1097 {
1098 if ( !PS.CheckX( x, fn ) ) // verify x param
1099 {
1100 return PS.ERROR;
1101 }
1102 for ( j = 0; j < PS.Grid.y; j += 1 ) // do entire column
1103 {
1104 rgb = PS.DoBeadColor( x, j, rgb, r, g, b );
1105 }
1106 }
1107 else if ( !PS.CheckX( x, fn ) || !PS.CheckY( y, fn ) ) // verify both params
1108 {
1109 return PS.ERROR;
1110 }
1111 else
1112 {
1113 rgb = PS.DoBeadColor( x, y, rgb, r, g, b ); // do one bead
1114 }
1115
1116 return rgb;
1117 };
1118
1119 // PS.BeadAlpha(x, y, a)
1120 // Returns and optionally sets a bead's alpha
1121 // [x, y] are grid position
1122 // Optional [a] must be a number between 0.0 and 1.0
1123
1124 PS.DoBeadAlpha = function ( x, y, a )
1125 {
1126 "use strict";
1127 var i, bead;
1128
1129 // Assume x/y params are already verified
1130
1131 i = x + (y * PS.Grid.x); // get index of bead
1132 bead = PS.Grid.beads[i];
1133
1134 if ( (a === undefined) || (a === PS.CURRENT) || (a === bead.alpha) )
1135 {
1136 return bead.alpha;
1137 }
1138
1139 // Calc new color between background and base
1140
1141 bead.alpha = a;
1142 bead.dirty = true;
1143 if ( bead.alpha < PS.DEFAULT_ALPHA ) // Calc new color based on alpha
1144 {
1145 bead.alphaRed = PS.Dissolve( PS.Grid.bgRed, bead.red, a );
1146 bead.alphaGreen = PS.Dissolve( PS.Grid.bgGreen, bead.green, a );
1147 bead.alphaBlue = PS.Dissolve( PS.Grid.bgBlue, bead.blue, a );
1148 bead.color = PS.RGBString( bead.alphaRed, bead.alphaGreen, bead.alphaBlue );
1149 }
1150 else
1151 {
1152 bead.alphaRed = bead.red;
1153 bead.alphaGreen = bead.green;
1154 bead.alphaBlue = bead.blue;
1155 bead.color = PS.RGBString( bead.red, bead.green, bead.blue );
1156 }
1157 if ( bead.visible )
1158 {
1159 if ( PS.Grid.flash && bead.flash )
1160 {
1161 PS.FlashStart(x, y);
1162 }
1163 else
1164 {
1165 bead.colorNow = bead.color;
1166 PS.DrawBead(bead);
1167 }
1168 }
1169
1170 return a;
1171 };
1172
1173 PS.BeadAlpha = function (x, y, a)
1174 {
1175 "use strict";
1176 var fn, i, j;
1177
1178 fn = "[PS.BeadAlpha] ";
1179
1180 if ( a !== undefined )
1181 {
1182 if ( typeof a !== "number" )
1183 {
1184 PS.Oops(fn + "alpha param is not a number");
1185 return PS.ERROR;
1186 }
1187
1188 // clamp value
1189
1190 a = Math.floor(a);
1191 if ( a === PS.DEFAULT )
1192 {
1193 a = PS.DEFAULT_ALPHA;
1194 }
1195 else if ( a !== PS.CURRENT )
1196 {
1197 if ( a < 0 )
1198 {
1199 a = 0;
1200 }
1201 else if ( a > PS.DEFAULT_ALPHA )
1202 {
1203 a = PS.DEFAULT_ALPHA;
1204 }
1205 }
1206 }
1207
1208 if ( x === PS.ALL )
1209 {
1210 if ( y === PS.ALL ) // do entire grid
1211 {
1212 for ( j = 0; j < PS.Grid.y; j += 1 )
1213 {
1214 for ( i = 0; i < PS.Grid.x; i += 1 )
1215 {
1216 a = PS.DoBeadAlpha( i, j, a );
1217 }
1218 }
1219 }
1220 else if ( !PS.CheckY( y, fn ) ) // verify y param
1221 {
1222 return PS.ERROR;
1223 }
1224 else
1225 {
1226 for ( i = 0; i < PS.Grid.x; i += 1 ) // do entire row
1227 {
1228 a = PS.DoBeadAlpha( i, y, a );
1229 }
1230 }
1231 }
1232 else if ( y === PS.ALL )
1233 {
1234 if ( !PS.CheckX( x, fn ) ) // verify x param
1235 {
1236 return PS.ERROR;
1237 }
1238 for ( j = 0; j < PS.Grid.y; j += 1 ) // do entire column
1239 {
1240 a = PS.DoBeadAlpha( x, j, a );
1241 }
1242 }
1243 else if ( !PS.CheckX( x, fn ) || !PS.CheckY( y, fn ) ) // verify both params
1244 {
1245 return PS.ERROR;
1246 }
1247 else
1248 {
1249 a = PS.DoBeadAlpha( x, y, a ); // do one bead
1250 }
1251
1252 return a;
1253 };
1254
1255 // PS.BeadBorderWidth (x, y, width)
1256 // Returns and optionally sets a bead's border width
1257 // [x, y] are grid position
1258 // Optional [width] must be a number
1259
1260 PS.DoBeadBorderWidth = function (x, y, width)
1261 {
1262 "use strict";
1263 var i, bead;
1264
1265 // Assume x/y params are already verified
1266
1267 i = x + (y * PS.Grid.x); // get index of bead
1268 bead = PS.Grid.beads[i];
1269
1270 if ( (width === undefined) || (width === PS.CURRENT) ) // if no width or PS.CURRENT, return current width
1271 {
1272 return bead.borderWidth;
1273 }
1274
1275 bead.borderWidth = width;
1276
1277 if ( bead.visible )
1278 {
1279 PS.DrawBead(bead);
1280 }
1281
1282 return width;
1283 };
1284
1285 PS.BeadBorderWidth = function (x, y, width)
1286 {
1287 "use strict";
1288 var fn, i, j;
1289
1290 fn = "[PS.BeadBorderWidth] ";
1291
1292 if ( width === PS.DEFAULT )
1293 {
1294 width = PS.DEFAULT_BORDER_WIDTH;
1295 }
1296 else if ( (width !== undefined) && (width !== PS.CURRENT) )
1297 {
1298 if ( typeof width !== "number" )
1299 {
1300 PS.Oops(fn + "width param is not a number");
1301 return PS.ERROR;
1302 }
1303 width = Math.floor(width);
1304 if ( width < 0 )
1305 {
1306 width = 0;
1307 }
1308 else if ( width > PS.BORDER_WIDTH_MAX )
1309 {
1310 width = PS.BORDER_WIDTH_MAX;
1311 }
1312 }
1313
1314 if ( x === PS.ALL )
1315 {
1316 if ( y === PS.ALL ) // do entire grid
1317 {
1318 for ( j = 0; j < PS.Grid.y; j += 1 )
1319 {
1320 for ( i = 0; i < PS.Grid.x; i += 1 )
1321 {
1322 width = PS.DoBeadBorderWidth( i, j, width );
1323 }
1324 }
1325 }
1326 else if ( !PS.CheckY( y, fn ) ) // verify y param
1327 {
1328 return PS.ERROR;
1329 }
1330 else
1331 {
1332 for ( i = 0; i < PS.Grid.x; i += 1 ) // do entire row
1333 {
1334 width = PS.DoBeadBorderWidth( i, y, width );
1335 }
1336 }
1337 }
1338 else if ( y === PS.ALL )
1339 {
1340 if ( !PS.CheckX( x, fn ) ) // verify x param
1341 {
1342 return PS.ERROR;
1343 }
1344 for ( j = 0; j < PS.Grid.y; j += 1 ) // do entire column
1345 {
1346 width = PS.DoBeadBorderWidth( x, j, width );
1347 }
1348 }
1349 else if ( !PS.CheckX( x, fn ) || !PS.CheckY( y, fn ) ) // verify both params
1350 {
1351 return PS.ERROR;
1352 }
1353 else
1354 {
1355 width = PS.DoBeadBorderWidth( x, y, width ); // do one bead
1356 }
1357
1358 return width;
1359 };
1360
1361 // PS.BeadBorderColor (x, y, rgb)
1362 // Returns and optionally sets a bead's border color
1363 // [x, y] are grid position
1364 // Optional [rgb] must be a multiplexed rgb value (0xRRGGBB)
1365
1366 PS.DoBeadBorderColor = function (x, y, rgb, r, g, b)
1367 {
1368 "use strict";
1369 var i, bead;
1370
1371 // Assume x/y params are already verified
1372
1373 i = x + (y * PS.Grid.x); // get index of bead
1374 bead = PS.Grid.beads[i];
1375
1376 if ( (rgb === undefined) || (rgb === PS.CURRENT) ) // if no rgb or PS.CURRENT, return current color
1377 {
1378 return (bead.borderRed * PS.REDSHIFT) + (bead.borderGreen * 256) + bead.borderBlue;
1379 }
1380
1381 bead.borderRed = r;
1382 bead.borderGreen = g;
1383 bead.borderBlue = b;
1384 if ( bead.borderAlpha < PS.DEFAULT_ALPHA )
1385 {
1386 r = PS.Dissolve( PS.Grid.bgRed, r, bead.borderAlpha );
1387 g = PS.Dissolve( PS.Grid.bgGreen, g, bead.borderAlpha );
1388 b = PS.Dissolve( PS.Grid.bgBlue, b, bead.borderAlpha );
1389 }
1390 bead.borderColor = PS.RGBString( r, g, b );
1391
1392 if ( bead.visible )
1393 {
1394 PS.DrawBead(bead);
1395 }
1396
1397 return rgb;
1398 };
1399
1400 PS.BeadBorderColor = function (x, y, rgb)
1401 {
1402 "use strict";
1403 var fn, colors, r, g, b, i, j;
1404
1405 fn = "[PS.BeadBorderColor] ";
1406
1407 if ( rgb === PS.DEFAULT )
1408 {
1409 rgb = PS.DEFAULT_BORDER_COLOR;
1410 r = PS.DEFAULT_BORDER_RED;
1411 g = PS.DEFAULT_BORDER_GREEN;
1412 b = PS.DEFAULT_BORDER_BLUE;
1413 }
1414 else if ( (rgb !== undefined) && (rgb !== PS.CURRENT) )
1415 {
1416 rgb = PS.ValidRGB( rgb, fn );
1417 if ( rgb < 0 )
1418 {
1419 return PS.ERROR;
1420 }
1421 colors = PS.UnmakeRGB( rgb );
1422 r = colors.r;
1423 g = colors.g;
1424 b = colors.b;
1425 }
1426
1427 if ( x === PS.ALL )
1428 {
1429 if ( y === PS.ALL ) // do entire grid
1430 {
1431 for ( j = 0; j < PS.Grid.y; j += 1 )
1432 {
1433 for ( i = 0; i < PS.Grid.x; i += 1 )
1434 {
1435 rgb = PS.DoBeadBorderColor( i, j, rgb, r, g, b );
1436 }
1437 }
1438 }
1439 else if ( !PS.CheckY( y, fn ) ) // verify y param
1440 {
1441 return PS.ERROR;
1442 }
1443 else
1444 {
1445 for ( i = 0; i < PS.Grid.x; i += 1 ) // do entire row
1446 {
1447 rgb = PS.DoBeadBorderColor( i, y, rgb, r, g, b );
1448 }
1449 }
1450 }
1451 else if ( y === PS.ALL )
1452 {
1453 if ( !PS.CheckX( x, fn ) ) // verify x param
1454 {
1455 return PS.ERROR;
1456 }
1457 for ( j = 0; j < PS.Grid.y; j += 1 ) // do entire column
1458 {
1459 rgb = PS.DoBeadBorderColor( x, j, rgb, r, g, b );
1460 }
1461 }
1462 else if ( !PS.CheckX( x, fn ) || !PS.CheckY( y, fn ) ) // verify both params
1463 {
1464 return PS.ERROR;
1465 }
1466 else
1467 {
1468 rgb = PS.DoBeadBorderColor( x, y, rgb, r, g, b ); // do one bead
1469 }
1470
1471 return rgb;
1472 };
1473
1474 // PS.BeadBorderAlpha(x, y, a)
1475 // Returns a bead's border alpha and optionally changes it
1476 // [x, y] are grid position
1477 // Optional [a] must be a number between 0.0 and 1.0
1478
1479 PS.DoBeadBorderAlpha = function (x, y, a)
1480 {
1481 "use strict";
1482 var i, bead, r, g, b;
1483
1484 // Assume x/y params are already verified
1485
1486 i = x + (y * PS.Grid.x); // get index of bead
1487 bead = PS.Grid.beads[i];
1488
1489 if ( (a === undefined) || (a === PS.CURRENT) || (a === bead.borderAlpha) )
1490 {
1491 return bead.borderAlpha;
1492 }
1493
1494 bead.borderAlpha = a;
1495 if ( a < PS.DEFAULT_ALPHA )
1496 {
1497 r = PS.Dissolve( PS.Grid.bgRed, bead.borderRed, a );
1498 g = PS.Dissolve( PS.Grid.bgGreen, bead.borderGreen, a );
1499 b = PS.Dissolve( PS.Grid.bgBlue, bead.borderBlue, a );
1500 bead.borderColor = PS.RGBString( r, g, b );
1501 }
1502 else
1503 {
1504 bead.borderColor = PS.RGBString( bead.borderRed, bead.borderGreen, bead.borderBlue );
1505 }
1506 if ( bead.visible )
1507 {
1508 PS.DrawBead(bead);
1509 }
1510 return a;
1511 };
1512
1513 PS.BeadBorderAlpha = function (x, y, a)
1514 {
1515 "use strict";
1516 var fn, i, j;
1517
1518 fn = "[PS.BeadBorderAlpha] ";
1519
1520 if ( a !== undefined )
1521 {
1522 if ( typeof a !== "number" )
1523 {
1524 PS.Oops(fn + "alpha param is not a number");
1525 return PS.ERROR;
1526 }
1527
1528 // clamp value
1529
1530 a = Math.floor(a);
1531 if ( a === PS.DEFAULT )
1532 {
1533 a = PS.DEFAULT_ALPHA;
1534 }
1535 else if ( a !== PS.CURRENT )
1536 {
1537 if ( a < 0 )
1538 {
1539 a = 0;
1540 }
1541 else if ( a > PS.DEFAULT_ALPHA )
1542 {
1543 a = PS.DEFAULT_ALPHA;
1544 }
1545 }
1546 }
1547
1548 if ( x === PS.ALL )
1549 {
1550 if ( y === PS.ALL ) // do entire grid
1551 {
1552 for ( j = 0; j < PS.Grid.y; j += 1 )
1553 {
1554 for ( i = 0; i < PS.Grid.x; i += 1 )
1555 {
1556 a = PS.DoBeadBorderAlpha( i, j, a );
1557 }
1558 }
1559 }
1560 else if ( !PS.CheckY( y, fn ) ) // verify y param
1561 {
1562 return PS.ERROR;
1563 }
1564 else
1565 {
1566 for ( i = 0; i < PS.Grid.x; i += 1 ) // do entire row
1567 {
1568 a = PS.DoBeadBorderAlpha( i, y, a );
1569 }
1570 }
1571 }
1572 else if ( y === PS.ALL )
1573 {
1574 if ( !PS.CheckX( x, fn ) ) // verify x param
1575 {
1576 return PS.ERROR;
1577 }
1578 for ( j = 0; j < PS.Grid.y; j += 1 ) // do entire column
1579 {
1580 a = PS.DoBeadBorderAlpha( x, j, a );
1581 }
1582 }
1583 else if ( !PS.CheckX( x, fn ) || !PS.CheckY( y, fn ) ) // verify both params
1584 {
1585 return PS.ERROR;
1586 }
1587 else
1588 {
1589 a = PS.DoBeadBorderAlpha( x, y, a ); // do one bead
1590 }
1591
1592 return a;
1593 };
1594
1595 // PS.BeadGlyph(x, y, g)
1596 // Returns a bead's glyph and optionally changes it
1597 // [x, y] are grid position
1598 // Optional [g] must be either a string or a number
1599
1600 PS.DoBeadGlyph = function (x, y, g)
1601 {
1602 "use strict";
1603 var i, bead;
1604
1605 // Assume x/y params are already verified
1606
1607 i = x + (y * PS.Grid.x); // get index of bead
1608 bead = PS.Grid.beads[i];
1609
1610 if ( (g === undefined) || (g === PS.CURRENT) || (g === bead.glyph) )
1611 {
1612 return bead.glyph;
1613 }
1614
1615 bead.glyph = g;
1616 bead.glyphStr = String.fromCharCode(g);
1617 if ( bead.visible )
1618 {
1619 if ( PS.Grid.flash && bead.flash )
1620 {
1621 PS.FlashStart(x, y);
1622 }
1623 else
1624 {
1625 bead.colorNow = bead.color;
1626 PS.DrawBead(bead);
1627 }
1628 }
1629
1630 return g;
1631 };
1632
1633 PS.BeadGlyph = function (x, y, g)
1634 {
1635 "use strict";
1636 var fn, type, i, j;
1637
1638 fn = "[PS.BeadGlyph] ";
1639
1640 // if no glyph specified, just return current border status
1641
1642 type = typeof g;
1643 if ( type !== "undefined" )
1644 {
1645 if ( type === "string" )
1646 {
1647 if ( g.length < 1 )
1648 {
1649 PS.Oops(fn + "glyph param is empty string");
1650 return 0;
1651 }
1652 g = g.charCodeAt(0); // use only first character
1653 }
1654 else if ( type === "number" )
1655 {
1656 g = Math.floor(g);
1657 if ( g === PS.DEFAULT )
1658 {
1659 g = 0;
1660 }
1661 else if ( g !== PS.CURRENT )
1662 {
1663 if ( g < 0 )
1664 {
1665 g = 0;
1666 }
1667 }
1668 }
1669 else
1670 {
1671 PS.Oops(fn + "glyph param not a string or number");
1672 return PS.ERROR;
1673 }
1674 }
1675
1676 if ( x === PS.ALL )
1677 {
1678 if ( y === PS.ALL ) // do entire grid
1679 {
1680 for ( j = 0; j < PS.Grid.y; j += 1 )
1681 {
1682 for ( i = 0; i < PS.Grid.x; i += 1 )
1683 {
1684 g = PS.DoBeadGlyph( i, j, g );
1685 }
1686 }
1687 }
1688 else if ( !PS.CheckY( y, fn ) ) // verify y param
1689 {
1690 return PS.ERROR;
1691 }
1692 else
1693 {
1694 for ( i = 0; i < PS.Grid.x; i += 1 ) // do entire row
1695 {
1696 g = PS.DoBeadGlyph( i, y, g );
1697 }
1698 }
1699 }
1700 else if ( y === PS.ALL )
1701 {
1702 if ( !PS.CheckX( x, fn ) ) // verify x param
1703 {
1704 return PS.ERROR;
1705 }
1706 for ( j = 0; j < PS.Grid.y; j += 1 ) // do entire column
1707 {
1708 g = PS.DoBeadGlyph( x, j, g );
1709 }
1710 }
1711 else if ( !PS.CheckX( x, fn ) || !PS.CheckY( y, fn ) ) // verify both params
1712 {
1713 return PS.ERROR;
1714 }
1715 else
1716 {
1717 g = PS.DoBeadGlyph( x, y, g ); // do one bead
1718 }
1719
1720 return g;
1721 };
1722
1723 // PS.BeadGlyphColor (x, y, rgb)
1724 // Returns and optionally sets a bead's glyph color
1725 // [x, y] are grid position
1726 // Optional [rgb] must be a multiplexed rgb value (0xRRGGBB)
1727
1728 PS.DoBeadGlyphColor = function (x, y, rgb, r, g, b)
1729 {
1730 "use strict";
1731 var i, bead;
1732
1733 // Assume x/y params are already verified
1734
1735 i = x + (y * PS.Grid.x); // get index of bead
1736 bead = PS.Grid.beads[i];
1737
1738 if ( (rgb === undefined) || (rgb === PS.CURRENT) ) // if no rgb or PS.CURRENT, return current color
1739 {
1740 return (bead.glyphRed * PS.REDSHIFT) + (bead.glyphGreen * 256) + bead.glyphBlue;
1741 }
1742
1743 bead.glyphRed = r;
1744 bead.glyphGreen = g;
1745 bead.glyphBlue = b;
1746 if ( bead.alpha < PS.DEFAULT_ALPHA ) // Calc new color based on alpha
1747 {
1748 r = PS.Dissolve( PS.Grid.bgRed, r, bead.alpha );
1749 g = PS.Dissolve( PS.Grid.bgGreen, g, bead.alpha );
1750 b = PS.Dissolve( PS.Grid.bgBlue, b, bead.alpha );
1751 }
1752 bead.glyphColor = PS.RGBString( r, g, b );
1753
1754 if ( bead.visible && (bead.glyph > 0) )
1755 {
1756 PS.DrawBead(bead);
1757 }
1758 return rgb;
1759 };
1760
1761 PS.BeadGlyphColor = function (x, y, rgb)
1762 {
1763 "use strict";
1764 var fn, colors, r, g, b, i, j;
1765
1766 fn = "[PS.BeadGlyphColor] ";
1767
1768 if ( rgb === PS.DEFAULT )
1769 {
1770 rgb = PS.DEFAULT_GLYPH_COLOR;
1771 r = PS.DEFAULT_GLYPH_RED;
1772 g = PS.DEFAULT_GLYPH_GREEN;
1773 b = PS.DEFAULT_GLYPH_BLUE;
1774 }
1775 else if ( (rgb !== undefined) && (rgb !== PS.CURRENT) )
1776 {
1777 rgb = PS.ValidRGB( rgb, fn );
1778 if ( rgb < 0 )
1779 {
1780 return PS.ERROR;
1781 }
1782 colors = PS.UnmakeRGB( rgb );
1783 r = colors.r;
1784 g = colors.g;
1785 b = colors.b;
1786 }
1787
1788 if ( x === PS.ALL )
1789 {
1790 if ( y === PS.ALL ) // do entire grid
1791 {
1792 for ( j = 0; j < PS.Grid.y; j += 1 )
1793 {
1794 for ( i = 0; i < PS.Grid.x; i += 1 )
1795 {
1796 rgb = PS.DoBeadGlyphColor( i, j, rgb, r, g, b );
1797 }
1798 }
1799 }
1800 else if ( !PS.CheckY( y, fn ) ) // verify y param
1801 {
1802 return PS.ERROR;
1803 }
1804 else
1805 {
1806 for ( i = 0; i < PS.Grid.x; i += 1 ) // do entire row
1807 {
1808 rgb = PS.DoBeadGlyphColor( i, y, rgb, r, g, b );
1809 }
1810 }
1811 }
1812 else if ( y === PS.ALL )
1813 {
1814 if ( !PS.CheckX( x, fn ) ) // verify x param
1815 {
1816 return PS.ERROR;
1817 }
1818 for ( j = 0; j < PS.Grid.y; j += 1 ) // do entire column
1819 {
1820 rgb = PS.DoBeadGlyphColor( x, j, rgb, r, g, b );
1821 }
1822 }
1823 else if ( !PS.CheckX( x, fn ) || !PS.CheckY( y, fn ) ) // verify both params
1824 {
1825 return PS.ERROR;
1826 }
1827 else
1828 {
1829 rgb = PS.DoBeadGlyphColor( x, y, rgb, r, g, b ); // do one bead
1830 }
1831
1832 return rgb;
1833 };
1834
1835 // PS.BeadFlash(x, y, flag)
1836 // Returns a bead's flash status and optionally changes it
1837 // [x, y] are grid position
1838 // Optional [flag] must be 1/true or 0/false
1839
1840 PS.DoBeadFlash = function (x, y, flag)
1841 {
1842 "use strict";
1843 var i, bead;
1844
1845 // Assume x/y params are already verified
1846
1847 i = x + (y * PS.Grid.x); // get index of bead
1848 bead = PS.Grid.beads[i];
1849
1850 if ( (flag === undefined) || (flag === PS.CURRENT) )
1851 {
1852 return bead.flash;
1853 }
1854
1855 bead.flash = flag;
1856 return flag;
1857 };
1858
1859 PS.BeadFlash = function (x, y, flag)
1860 {
1861 "use strict";
1862 var fn, i, j;
1863
1864 fn = "[PS.BeadFlash] ";
1865
1866 // normalize flag value to t/f if defined
1867
1868 if ( (flag !== undefined) && (flag !== PS.CURRENT) )
1869 {
1870 if ( flag === PS.DEFAULT )
1871 {
1872 flag = true;
1873 }
1874 else if ( flag )
1875 {
1876 flag = true;
1877 }
1878 else
1879 {
1880 flag = false;
1881 }
1882 }
1883
1884 if ( x === PS.ALL )
1885 {
1886 if ( y === PS.ALL ) // do entire grid
1887 {
1888 for ( j = 0; j < PS.Grid.y; j += 1 )
1889 {
1890 for ( i = 0; i < PS.Grid.x; i += 1 )
1891 {
1892 flag = PS.DoBeadFlash( i, j, flag );
1893 }
1894 }
1895 }
1896 else if ( !PS.CheckY( y, fn ) ) // verify y param
1897 {
1898 return PS.ERROR;
1899 }
1900 else
1901 {
1902 for ( i = 0; i < PS.Grid.x; i += 1 ) // do entire row
1903 {
1904 flag = PS.DoBeadFlash( i, y, flag );
1905 }
1906 }
1907 }
1908 else if ( y === PS.ALL )
1909 {
1910 if ( !PS.CheckX( x, fn ) ) // verify x param
1911 {
1912 return PS.ERROR;
1913 }
1914 for ( j = 0; j < PS.Grid.y; j += 1 ) // do entire column
1915 {
1916 flag = PS.DoBeadFlash( x, j, flag );
1917 }
1918 }
1919 else if ( !PS.CheckX( x, fn ) || !PS.CheckY( y, fn ) ) // verify both params
1920 {
1921 return PS.ERROR;
1922 }
1923 else
1924 {
1925 flag = PS.DoBeadFlash( x, y, flag ); // do one bead
1926 }
1927
1928 return flag;
1929 };
1930
1931 // PS.BeadFlashColor (x, y, rgb)
1932 // Returns and optionally sets a bead's flash color
1933 // [x, y] are grid position
1934 // Optional [rgb] must be a multiplexed rgb value (0xRRGGBB)
1935
1936 PS.DoBeadFlashColor = function (x, y, rgb, r, g, b)
1937 {
1938 "use strict";
1939 var i, bead;
1940
1941 // Assume x/y params are already verified
1942
1943 i = x + (y * PS.Grid.x); // get index of bead
1944 bead = PS.Grid.beads[i];
1945
1946 if ( (rgb === undefined) || (rgb === PS.CURRENT) ) // if no rgb or PS.CURRENT, return current color
1947 {
1948 return (bead.flashRed * PS.REDSHIFT) + (bead.flashGreen * 256) + bead.flashBlue;
1949 }
1950
1951 bead.flashRed = r;
1952 bead.flashGreen = g;
1953 bead.flashBlue = b;
1954 bead.flashColor = PS.RGBString(r, g, b);
1955
1956 return rgb;
1957 };
1958
1959 PS.BeadFlashColor = function (x, y, rgb)
1960 {
1961 "use strict";
1962 var fn, r, g, b, colors, i, j;
1963
1964 fn = "[PS.BeadFlashColor] ";
1965
1966 if ( rgb === PS.DEFAULT )
1967 {
1968 rgb = PS.DEFAULT_FLASH_COLOR;
1969 r = PS.DEFAULT_FLASH_RED;
1970 g = PS.DEFAULT_FLASH_GREEN;
1971 b = PS.DEFAULT_FLASH_BLUE;
1972 }
1973 else if ( (rgb !== undefined) && (rgb !== PS.CURRENT) )
1974 {
1975 rgb = PS.ValidRGB( rgb, fn );
1976 if ( rgb < 0 )
1977 {
1978 return PS.ERROR;
1979 }
1980 colors = PS.UnmakeRGB( rgb );
1981 r = colors.r;
1982 g = colors.g;
1983 b = colors.b;
1984 }
1985
1986 if ( x === PS.ALL )
1987 {
1988 if ( y === PS.ALL ) // do entire grid
1989 {
1990 for ( j = 0; j < PS.Grid.y; j += 1 )
1991 {
1992 for ( i = 0; i < PS.Grid.x; i += 1 )
1993 {
1994 rgb = PS.DoBeadFlashColor( i, j, rgb, r, g, b );
1995 }
1996 }
1997 }
1998 else if ( !PS.CheckY( y, fn ) ) // verify y param
1999 {
2000 return PS.ERROR;
2001 }
2002 else
2003 {
2004 for ( i = 0; i < PS.Grid.x; i += 1 ) // do entire row
2005 {
2006 rgb = PS.DoBeadFlashColor( i, y, rgb, r, g, b );
2007 }
2008 }
2009 }
2010 else if ( y === PS.ALL )
2011 {
2012 if ( !PS.CheckX( x, fn ) ) // verify x param
2013 {
2014 return PS.ERROR;
2015 }
2016 for ( j = 0; j < PS.Grid.y; j += 1 ) // do entire column
2017 {
2018 rgb = PS.DoBeadFlashColor( x, j, rgb, r, g, b );
2019 }
2020 }
2021 else if ( !PS.CheckX( x, fn ) || !PS.CheckY( y, fn ) ) // verify both params
2022 {
2023 return PS.ERROR;
2024 }
2025 else
2026 {
2027 rgb = PS.DoBeadFlashColor( x, y, rgb, r, g, b ); // do one bead
2028 }
2029
2030 return rgb;
2031 };
2032
2033 // PS.BeadData(x, y, data)
2034 // Returns a bead's data and optionally changes it
2035 // [x, y] are grid position
2036 // Optional [data] can be any data type
2037
2038 PS.DoBeadData = function (x, y, data)
2039 {
2040 "use strict";
2041 var i, bead;
2042
2043 // Assume x/y params are already verified
2044
2045 i = x + (y * PS.Grid.x); // get index of bead
2046 bead = PS.Grid.beads[i];
2047
2048 if ( data !== undefined )
2049 {
2050 bead.data = data;
2051 }
2052
2053 return bead.data;
2054 };
2055
2056 PS.BeadData = function (x, y, data)
2057 {
2058 "use strict";
2059 var fn, i, j;
2060
2061 fn = "[PS.BeadData] ";
2062
2063 if ( x === PS.ALL )
2064 {
2065 if ( y === PS.ALL ) // do entire grid
2066 {
2067 for ( j = 0; j < PS.Grid.y; j += 1 )
2068 {
2069 for ( i = 0; i < PS.Grid.x; i += 1 )
2070 {
2071 data = PS.DoBeadData( i, j, data );
2072 }
2073 }
2074 }
2075 else if ( !PS.CheckY( y, fn ) ) // verify y param
2076 {
2077 return PS.ERROR;
2078 }
2079 else
2080 {
2081 for ( i = 0; i < PS.Grid.x; i += 1 ) // do entire row
2082 {
2083 data = PS.DoBeadData( i, y, data );
2084 }
2085 }
2086 }
2087 else if ( y === PS.ALL )
2088 {
2089 if ( !PS.CheckX( x, fn ) ) // verify x param
2090 {
2091 return PS.ERROR;
2092 }
2093 for ( j = 0; j < PS.Grid.y; j += 1 ) // do entire column
2094 {
2095 data = PS.DoBeadData( x, j, data );
2096 }
2097 }
2098 else if ( !PS.CheckX( x, fn ) || !PS.CheckY( y, fn ) ) // verify both params
2099 {
2100 return PS.ERROR;
2101 }
2102 else
2103 {
2104 data = PS.DoBeadData( x, y, data ); // do one bead
2105 }
2106
2107 return data;
2108 };
2109
2110 // PS.BeadAudio(x, y, audio, volume)
2111 // Returns a bead's audio file and optionally changes it (and its volume)
2112 // [x, y] are grid position
2113 // Optional [audio] must be a string
2114 // Optional [volume] should be between 0 and 100 inclusive
2115
2116 PS.DoBeadAudio = function (x, y, audio, volume)
2117 {
2118 "use strict";
2119 var i, bead;
2120
2121 // Assume x/y params are already verified
2122
2123 i = x + (y * PS.Grid.x); // get index of bead
2124 bead = PS.Grid.beads[i];
2125
2126 if ( (audio !== undefined) && (audio !== PS.CURRENT) )
2127 {
2128 bead.audio = audio;
2129 }
2130
2131 if ( (volume !== undefined) && (volume !== PS.CURRENT) )
2132 {
2133 bead.volume = volume;
2134 }
2135
2136 return bead.audio;
2137 };
2138
2139 PS.BeadAudio = function (x, y, audio, volume)
2140 {
2141 "use strict";
2142 var fn, i, j;
2143
2144 fn = "[PS.BeadAudio] ";
2145
2146 // check audio file param
2147
2148 if ( (audio !== undefined) && (audio !== PS.CURRENT) )
2149 {
2150 if ( audio === PS.DEFAULT )
2151 {
2152 audio = null;
2153 }
2154 else if ( typeof audio !== "string" )
2155 {
2156 PS.Oops(fn + "audio param is not a string");
2157 return PS.ERROR;
2158 }
2159 else if ( audio.length < 1 )
2160 {
2161 audio = null;
2162 }
2163 }
2164
2165 // check volume param
2166
2167 if ( (volume !== undefined) && (volume !== PS.CURRENT) )
2168 {
2169 if ( volume === PS.DEFAULT )
2170 {
2171 volume = PS.DEFAULT_VOLUME;
2172 }
2173 else if ( typeof volume !== "number" )
2174 {
2175 PS.Oops(fn + "volume param is not a number");
2176 return PS.ERROR;
2177 }
2178 else
2179 {
2180 if ( volume < 0 )
2181 {
2182 volume = 0;
2183 }
2184 else if ( volume > PS.DEFAULT_VOLUME )
2185 {
2186 volume = PS.DEFAULT_VOLUME;
2187 }
2188 }
2189 }
2190
2191 if ( x === PS.ALL )
2192 {
2193 if ( y === PS.ALL ) // do entire grid
2194 {
2195 for ( j = 0; j < PS.Grid.y; j += 1 )
2196 {
2197 for ( i = 0; i < PS.Grid.x; i += 1 )
2198 {
2199 audio = PS.DoBeadAudio( i, j, audio, volume );
2200 }
2201 }
2202 }
2203 else if ( !PS.CheckY( y, fn ) ) // verify y param
2204 {
2205 return PS.ERROR;
2206 }
2207 else
2208 {
2209 for ( i = 0; i < PS.Grid.x; i += 1 ) // do entire row
2210 {
2211 audio = PS.DoBeadAudio( i, y, audio, volume );
2212 }
2213 }
2214 }
2215 else if ( y === PS.ALL )
2216 {
2217 if ( !PS.CheckX( x, fn ) ) // verify x param
2218 {
2219 return PS.ERROR;
2220 }
2221 for ( j = 0; j < PS.Grid.y; j += 1 ) // do entire column
2222 {
2223 audio = PS.DoBeadAudio( x, j, audio, volume );
2224 }
2225 }
2226 else if ( !PS.CheckX( x, fn ) || !PS.CheckY( y, fn ) ) // verify both params
2227 {
2228 return PS.ERROR;
2229 }
2230 else
2231 {
2232 audio = PS.DoBeadAudio( x, y, audio, volume ); // do one bead
2233 }
2234
2235 return audio;
2236 };
2237
2238 // PS.BeadFunction(x, y, func)
2239 // Returns a bead's exec function and optionally changes it
2240 // [x, y] are grid position
2241 // Optional [func] must be a JavaScript function
2242
2243 PS.DoBeadFunction = function (x, y, exec)
2244 {
2245 "use strict";
2246 var i, bead;
2247
2248 // Assume x/y params are already verified
2249
2250 i = x + (y * PS.Grid.x); // get index of bead
2251 bead = PS.Grid.beads[i];
2252
2253 if ( (exec !== undefined) && (exec !== PS.CURRENT) )
2254 {
2255 bead.exec = exec;
2256 }
2257
2258 return bead.exec;
2259 };
2260
2261 PS.BeadFunction = function (x, y, exec)
2262 {
2263 "use strict";
2264 var fn, i, j;
2265
2266 fn = "[PS.BeadFunction] ";
2267
2268 if ( (exec !== undefined) || (exec !== PS.CURRENT) )
2269 {
2270 if ( exec === PS.DEFAULT )
2271 {
2272 exec = null;
2273 }
2274 else if ( typeof exec !== "function" )
2275 {
2276 PS.Oops(fn + "exec param not a valid function");
2277 return PS.ERROR;
2278 }
2279 }
2280
2281 if ( x === PS.ALL )
2282 {
2283 if ( y === PS.ALL ) // do entire grid
2284 {
2285 for ( j = 0; j < PS.Grid.y; j += 1 )
2286 {
2287 for ( i = 0; i < PS.Grid.x; i += 1 )
2288 {
2289 exec = PS.DoBeadFunction( i, j, exec );
2290 }
2291 }
2292 }
2293 else if ( !PS.CheckY( y, fn ) ) // verify y param
2294 {
2295 return PS.ERROR;
2296 }
2297 else
2298 {
2299 for ( i = 0; i < PS.Grid.x; i += 1 ) // do entire row
2300 {
2301 exec = PS.DoBeadFunction( i, y, exec );
2302 }
2303 }
2304 }
2305 else if ( y === PS.ALL )
2306 {
2307 if ( !PS.CheckX( x, fn ) ) // verify x param
2308 {
2309 return PS.ERROR;
2310 }
2311 for ( j = 0; j < PS.Grid.y; j += 1 ) // do entire column
2312 {
2313 exec = PS.DoBeadFunction( x, j, exec );
2314 }
2315 }
2316 else if ( !PS.CheckX( x, fn ) || !PS.CheckY( y, fn ) ) // verify both params
2317 {
2318 return PS.ERROR;
2319 }
2320 else
2321 {
2322 exec = PS.DoBeadFunction( x, y, exec ); // do one bead
2323 }
2324
2325 return exec;
2326 };
2327
2328 // PS.BeadTouch(x, y, mask)
2329 // Simulates effect of clicking on a bead
2330 // [x, y] are grid position
2331
2332 PS.DoBeadTouch = function (x, y)
2333 {
2334 "use strict";
2335 var i, bead;
2336
2337 // Assume x/y params are already verified
2338
2339 i = x + (y * PS.Grid.x); // get index of bead
2340 bead = PS.Grid.beads[i];
2341
2342 // Play bead audio
2343
2344 if ( typeof bead.audio === "string" )
2345 {
2346 PS.AudioPlay(bead.audio, bead.volume);
2347 }
2348
2349 // Run bead exec
2350
2351 if ( typeof bead.exec === "function" )
2352 {
2353 bead.exec(x, y, bead.data);
2354 }
2355
2356 // Simulate click
2357
2358 PS.Click(x, y, bead.data);
2359 };
2360
2361 PS.BeadTouch = function (x, y)
2362 {
2363 "use strict";
2364 var fn, i, j;
2365
2366 fn = "[PS.BeadTouch] ";
2367
2368 if ( x === PS.ALL )
2369 {
2370 if ( y === PS.ALL ) // do entire grid
2371 {
2372 for ( j = 0; j < PS.Grid.y; j += 1 )
2373 {
2374 for ( i = 0; i < PS.Grid.x; i += 1 )
2375 {
2376 PS.DoBeadTouch( i, j );
2377 }
2378 }
2379 }
2380 else if ( !PS.CheckY( y, fn ) ) // verify y param
2381 {
2382 return;
2383 }
2384 else
2385 {
2386 for ( i = 0; i < PS.Grid.x; i += 1 ) // do entire row
2387 {
2388 PS.DoBeadTouch( i, y );
2389 }
2390 }
2391 }
2392 else if ( y === PS.ALL )
2393 {
2394 if ( !PS.CheckX( x, fn ) ) // verify x param
2395 {
2396 return;
2397 }
2398 for ( j = 0; j < PS.Grid.y; j += 1 ) // do entire column
2399 {
2400 PS.DoBeadTouch( x, j );
2401 }
2402 }
2403 else if ( !PS.CheckX( x, fn ) || !PS.CheckY( y, fn ) ) // verify both params
2404 {
2405 return;
2406 }
2407 else
2408 {
2409 PS.DoBeadTouch( x, y ); // do one bead
2410 }
2411 };
2412
2413 // Set message text
2414
2415 PS.Status = "Perlenspiel";
2416 PS.StatusHue = 0;
2417
2418 PS.StatusText = function (str)
2419 {
2420 "use strict";
2421 var fn, type, e;
2422
2423 fn = "[PS.StatusText] ";
2424
2425 type = typeof str;
2426 if ( type !== "undefined" )
2427 {
2428 if ( type !== "string" )
2429 {
2430 PS.Oops(fn + "Parameter is not a string");
2431 }
2432 else
2433 {
2434 e = document.getElementById("status");
2435 if ( e )
2436 {
2437 if ( PS.StatusFading ) // start the fade
2438 {
2439 e.style.color = PS.Grid.bgColor;
2440 PS.StatusPhase = 0;
2441 }
2442 e.value = str;
2443 }
2444 PS.Status = str;
2445 }
2446 }
2447 return PS.Status;
2448 };
2449
2450 PS.StatusColor = function (rgb)
2451 {
2452 "use strict";
2453 var fn, colors, e;
2454
2455 fn = "[PS.StatusText] ";
2456
2457 if ( (rgb !== undefined) && (rgb !== PS.CURRENT) )
2458 {
2459 rgb = PS.ValidRGB( rgb, fn );
2460 if ( rgb < 0 )
2461 {
2462 return PS.ERROR;
2463 }
2464 if ( rgb === PS.DEFAULT )
2465 {
2466 rgb = PS.DEFAULT_TEXT_COLOR;
2467 }
2468 colors = PS.UnmakeRGB(rgb);
2469 PS.StatusRed = colors.r;
2470 PS.StatusGreen = colors.g;
2471 PS.StatusBlue = colors.b;
2472 PS.StatusHue = PS.RGBString(colors.r, colors.g, colors.b);
2473 PS.StatusPhase = 100; // stops fades in progress
2474
2475 e = document.getElementById("status");
2476 if ( e )
2477 {
2478 e.style.color = PS.StatusHue;
2479 }
2480 e = document.getElementById("footer");
2481 if ( e )
2482 {
2483 e.style.color = PS.StatusHue;
2484 }
2485 }
2486
2487 return PS.StatusHue;
2488 };
2489
2490 // Turn status line fading on and off
2491
2492 PS.StatusFade = function (flag)
2493 {
2494 "use strict";
2495 var fn, e;
2496
2497 fn = "[PS.StatusFade] ";
2498
2499 if ( (flag !== undefined) && (flag !== PS.CURRENT) )
2500 {
2501 if ( flag || (flag === PS.DEFAULT) )
2502 {
2503 flag = true;
2504 }
2505 else
2506 {
2507 flag = false;
2508 PS.StatusPhase = 100;
2509 e = document.getElementById("status");
2510 if ( e )
2511 {
2512 e.style.color = PS.StatusHue;
2513 }
2514 }
2515 PS.StatusFading = flag;
2516 }
2517
2518 return PS.StatusFading;
2519 };
2520
2521 // Debugger API
2522
2523 // Open debugger if not already open
2524
2525 PS.DebugOpen = function ()
2526 {
2527 "use strict";
2528 var div, e;
2529
2530 if ( !PS.DebugWindow )
2531 {
2532 div = document.getElementById("debug");
2533 div.style.display = "inline";
2534
2535 // clear it
2536
2537 e = document.getElementById("monitor");
2538 if ( e )
2539 {
2540 e.value = "";
2541 }
2542
2543 PS.DebugWindow = true;
2544 }
2545 };
2546
2547 // Close debugger if not already closed
2548
2549 PS.DebugClose = function ()
2550 {
2551 "use strict";
2552 var e;
2553
2554 if ( PS.DebugWindow )
2555 {
2556 e = document.getElementById("debug");
2557 e.style.display = "none";
2558 PS.DebugWindow = false;
2559 }
2560 };
2561
2562 // Add line to debugger (does not include CR)
2563
2564 PS.Debug = function (str)
2565 {
2566 "use strict";
2567 var e;
2568
2569 if ( typeof str !== "string" )
2570 {
2571 return;
2572 }
2573
2574 PS.DebugOpen();
2575
2576 e = document.getElementById("monitor");
2577 if ( e )
2578 {
2579 e.value += str; // add it
2580 e.scrollTop = e.scrollHeight; // keep it scrolled down
2581 }
2582 };
2583
2584 // Clear footer and debugger
2585
2586 PS.DebugClear = function ()
2587 {
2588 "use strict";
2589 var e;
2590
2591 e = document.getElementById("footer");
2592 if ( e )
2593 {
2594 e.style.color="#000000"; // change to black
2595 e.innerHTML = "Version 2.0.0";
2596 }
2597
2598 if ( PS.DebugWindow )
2599 {
2600 e = document.getElementById("monitor");
2601 if ( e )
2602 {
2603 e.value = "";
2604 }
2605 }
2606 };
2607
2608 // Send error message to footer and debugger if open (includes CR)
2609
2610 PS.Oops = function (str)
2611 {
2612 "use strict";
2613 var e;
2614
2615 if ( typeof str !== "string" )
2616 {
2617 return;
2618 }
2619
2620 e = document.getElementById("footer");
2621 if ( e )
2622 {
2623 e.innerHTML = str;
2624 }
2625
2626 // Also display on debugger if open
2627
2628 // if ( PS.DebugWindow )
2629 // {
2630 // e = document.getElementById("monitor");
2631 // if ( e )
2632 // {
2633 // e.value += ("ERROR: " + str + "\n");
2634 // e.scrollTop = e.scrollHeight; // keep it scrolled down
2635 // }
2636 // }
2637
2638 PS.Debug( "ERROR: " + str + "\n" );
2639
2640 PS.AudioPlay("fx_uhoh");
2641 };
2642
2643 // Set up user clock
2644
2645 PS.Clock = function ( ticks )
2646 {
2647 "use strict";
2648 var fn;
2649
2650 fn = "[PS.Clock] ";
2651
2652 if ( ticks !== undefined )
2653 {
2654 if ( typeof ticks !== "number" )
2655 {
2656 PS.Oops(fn + "ticks parameter not a number");
2657 return PS.ERROR;
2658 }
2659 ticks = Math.floor(ticks);
2660 if ( ticks < 1 )
2661 {
2662 PS.UserClock = 0;
2663 }
2664 else if ( typeof PS.Tick !== "function" )
2665 {
2666 PS.Oops(fn + "PS.Tick function undefined");
2667 }
2668 else
2669 {
2670 PS.UserDelay = 0;
2671 PS.UserClock = ticks;
2672 }
2673 }
2674
2675 return PS.UserClock;
2676 };
2677
2678 // General system timer
2679
2680 PS.Timer = function ()
2681 {
2682 "use strict";
2683 var phase, hue, r, g, b, e;
2684
2685 // Handle bead flashing and status text fading
2686
2687 PS.FlashDelay += 1;
2688 if ( PS.FlashDelay >= PS.FLASH_INTERVAL )
2689 {
2690 PS.FlashDelay = 0;
2691 PS.FlashNext();
2692
2693 // Handle status text fading
2694
2695 if ( PS.StatusFading && (PS.StatusPhase < 100) )
2696 {
2697 phase = PS.StatusPhase + PS.STATUS_FLASH_STEP;
2698
2699 if ( phase >= 100 )
2700 {
2701 phase = 100;
2702 hue = PS.StatusHue;
2703 }
2704 else
2705 {
2706 r = PS.Dissolve( PS.Grid.bgRed, PS.StatusRed, phase );
2707 g = PS.Dissolve( PS.Grid.bgGreen, PS.StatusGreen, phase );
2708 b = PS.Dissolve( PS.Grid.bgBlue, PS.StatusBlue, phase );
2709 hue = PS.RGBString(r, g, b);
2710 }
2711 PS.StatusPhase = phase;
2712 e = document.getElementById("status");
2713 if ( e )
2714 {
2715 e.style.color = hue;
2716 }
2717 }
2718 }
2719
2720 // Handle user clock
2721
2722 if ( PS.UserClock > 0 )
2723 {
2724 PS.UserDelay += 1;
2725 if ( PS.UserDelay >= PS.UserClock )
2726 {
2727 PS.UserDelay = 0;
2728 if ( PS.Tick )
2729 {
2730 try
2731 {
2732 PS.Tick(); // call user function
2733 }
2734 catch (err)
2735 {
2736 PS.Oops("PS.Tick() failed [" + err.message + "]" );
2737 PS.UserClock = 0; // stop the timer
2738 }
2739 }
2740 }
2741 }
2742 };
2743
2744 // PS.StartFlash(bead)
2745 // Initiates flashing of bead
2746
2747 PS.FlashStart = function (x, y)
2748 {
2749 "use strict";
2750 var which, bead, i, len;
2751
2752 which = x + (y * PS.Grid.x); // index of bead
2753
2754 bead = PS.Grid.beads[which];
2755
2756 bead.flashPhase = 0; // init flash step
2757
2758 // draw first step
2759
2760 bead.colorNow = bead.flashColor;
2761 PS.DrawBead(bead);
2762
2763 // if this bead is already in flash queue, exit
2764
2765 len = PS.Grid.flashList.length;
2766 for ( i = 0; i < len; i += 1 )
2767 {
2768 if ( PS.Grid.flashList[i] === which )
2769 {
2770 return;
2771 }
2772 }
2773
2774 // else add this bead to queue
2775
2776 PS.Grid.flashList.push(which);
2777 };
2778
2779 // PS.NextFlash(bead)
2780 // Flash all beads in queue
2781
2782 PS.FlashNext = function ()
2783 {
2784 "use strict";
2785 var ctx, len, i, which, bead, phase, r, g, b;
2786
2787 ctx = PS.Context();
2788 len = PS.Grid.flashList.length;
2789 i = 0;
2790 while ( i < len )
2791 {
2792 which = PS.Grid.flashList[i];
2793 bead = PS.Grid.beads[which];
2794 phase = bead.flashPhase + PS.FLASH_STEP;
2795
2796 // If flash is done, set normal color and remove bead from queue
2797
2798 if ( phase >= 100 )
2799 {
2800 bead.colorNow = bead.color;
2801 bead.flashPhase = 0;
2802 PS.Grid.flashList.splice(i, 1);
2803 len -= 1;
2804 }
2805 else
2806 {
2807 bead.flashPhase = phase;
2808 r = PS.Dissolve( bead.flashRed, bead.alphaRed, phase );
2809 g = PS.Dissolve( bead.flashGreen, bead.alphaGreen, phase );
2810 b = PS.Dissolve( bead.flashBlue, bead.alphaBlue, phase );
2811 bead.colorNow = PS.RGBString(r, g, b);
2812 i += 1;
2813 }
2814 PS.DrawBead(bead, ctx);
2815 }
2816 };
2817
2818 // System initialization
2819
2820 // Records the x/y of mouse over grid, -1 if not over grid
2821
2822 PS.MouseXY = function (event)
2823 {
2824 "use strict";
2825 var canvas, x, y, beads, bead, row, col, i;
2826
2827 if ( PS.Grid )
2828 {
2829 canvas = document.getElementById("screen");
2830
2831 if ( event.x && event.y )
2832 {
2833 x = event.x;
2834 y = event.y;
2835 }
2836 else // Firefox method to get the position
2837 {
2838 x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
2839 y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop;
2840 }
2841
2842 x -= canvas.offsetLeft;
2843 y -= canvas.offsetTop;
2844
2845 // Over the grid?
2846
2847 if ( (x >= PS.Grid.left) && (x < PS.Grid.right) && (y >= PS.Grid.top) && (y < PS.Grid.bottom) )
2848 {
2849 // Which bead are we over?
2850
2851 beads = PS.Grid.beads;
2852 i = 0; // init index
2853 for ( row = 0; row < PS.Grid.y; row += 1 )
2854 {
2855 bead = beads[i]; // get the first bead in this row
2856
2857 // Is mouse over this row?
2858
2859 if ( (y >= bead.top) && (y < bead.bottom) )
2860 {
2861 // Find column
2862
2863 for ( col = 0; col < PS.Grid.x; col += 1 )
2864 {
2865 bead = beads[i];
2866 if ( (x >= bead.left) && (x < bead.right) )
2867 {
2868 PS.MouseX = col;
2869 PS.MouseY = row;
2870 return;
2871 }
2872 i += 1;
2873 }
2874 }
2875 else
2876 {
2877 i += PS.Grid.x; // try next row
2878 }
2879 }
2880 }
2881 }
2882
2883 PS.MouseX = -1;
2884 PS.MouseY = -1;
2885 };
2886
2887 // Called when mouse is clicked over canvas
2888
2889 PS.MouseDown = function (event)
2890 {
2891 "use strict";
2892 var bead;
2893
2894 PS.MouseXY(event);
2895 if ( PS.MouseX >= 0 )
2896 {
2897 bead = PS.Grid.beads[PS.MouseX + (PS.MouseY * PS.Grid.x)];
2898
2899 // play audio if assigned to bead
2900
2901 if ( bead.audio )
2902 {
2903 PS.AudioPlay(bead.audio, bead.volume);
2904 }
2905
2906 // Call function if assigned to bead
2907
2908 if ( typeof bead.exec === "function" )
2909 {
2910 try
2911 {
2912 bead.exec(PS.MouseX, PS.MouseY, bead.data);
2913 }
2914 catch (err1)
2915 {
2916 PS.Oops("Bead " + PS.MouseX + ", " + PS.MouseY + " function failed [" + err1.message + "]" );
2917 }
2918 }
2919
2920 if ( PS.Click ) // only if function exists
2921 {
2922 try
2923 {
2924 PS.Click(PS.MouseX, PS.MouseY, bead.data);
2925 }
2926 catch (err2)
2927 {
2928 PS.Oops("PS.Click() failed [" + err2.message + "]" );
2929 }
2930 }
2931 }
2932 };
2933
2934 // Called when mouse is released over canvas
2935
2936 PS.MouseUp = function (event)
2937 {
2938 "use strict";
2939 var bead;
2940
2941 if ( PS.Grid && PS.Release ) // only if grid and function exist
2942 {
2943 PS.MouseXY(event);
2944 if ( PS.MouseX >= 0 )
2945 {
2946 bead = PS.Grid.beads[PS.MouseX + (PS.MouseY * PS.Grid.x)];
2947 try
2948 {
2949 PS.Release(PS.MouseX, PS.MouseY, bead.data);
2950 }
2951 catch (err)
2952 {
2953 PS.Oops("PS.Release() failed [" + err.message + "]" );
2954 }
2955 }
2956 }
2957 };
2958
2959 // Called when mouse moves over canvas
2960
2961 PS.MouseMove = function (event)
2962 {
2963 "use strict";
2964 var bead, last;
2965
2966 PS.MouseXY(event);
2967
2968 if ( PS.MouseX >= 0 )
2969 {
2970 bead = PS.Grid.beads[PS.MouseX + (PS.MouseY * PS.Grid.x)];
2971 if ( (PS.MouseX !== PS.LastX) || (PS.MouseY !== PS.LastY) )
2972 {
2973 if ( PS.Leave ) // only if function exists
2974 {
2975 if ( PS.LastX >= 0 )
2976 {
2977 last = PS.Grid.beads[PS.LastX + (PS.LastY * PS.Grid.x)];
2978 try
2979 {
2980 PS.Leave(PS.LastX, PS.LastY, last.data);
2981 }
2982 catch (err1)
2983 {
2984 PS.Oops("PS.Leave() failed [" + err1.message + "]" );
2985 }
2986 }
2987 }
2988 if ( PS.Enter ) // only if function exists
2989 {
2990 try
2991 {
2992 PS.Enter(PS.MouseX, PS.MouseY, bead.data);
2993 }
2994 catch (err2)
2995 {
2996 PS.Oops("PS.Enter() failed [" + err2.message + "]" );
2997 }
2998 }
2999 PS.LastX = PS.MouseX;
3000 PS.LastY = PS.MouseY;
3001 }
3002 }
3003 else if ( PS.LastX >= 0 )
3004 {
3005 if ( PS.Leave ) // only if function exists
3006 {
3007 last = PS.Grid.beads[PS.LastX + (PS.LastY * PS.Grid.x)];
3008 try
3009 {
3010 PS.Leave(PS.LastX, PS.LastY, last.data);
3011 }
3012 catch (err3)
3013 {
3014 PS.Oops("PS.Leave() failed [" + err3.message + "]" );
3015 }
3016 }
3017 PS.LastX = -1;
3018 PS.LastY = -1;
3019 }
3020 };
3021
3022 // Called when mouse leaves canvas
3023
3024 PS.MouseOut = function (event)
3025 {
3026 "use strict";
3027 var last;
3028
3029 PS.MouseBead = -1;
3030 if ( PS.Grid && PS.Leave ) // only if grid and function exist
3031 {
3032 if ( PS.LastBead >= 0 )
3033 {
3034 last = PS.Grid.beads[PS.LastBead];
3035 try
3036 {
3037 PS.Leave(last.x, last.y, last.data);
3038 }
3039 catch (err)
3040 {
3041 PS.Oops("PS.Leave() failed [" + err.message + "]" );
3042 }
3043 }
3044 }
3045 PS.LastBead = -1;
3046 };
3047
3048 PS.KeyFilter = function (key, shift)
3049 {
3050 "use strict";
3051
3052 // convert lower-case alpha to upper-case if shift key is down
3053
3054 if ( (key >= 65) && (key <= 90) )
3055 {
3056 if ( shift )
3057 {
3058 key += 32;
3059 }
3060 return key;
3061 }
3062
3063 // Convert weird keycodes to ASCII
3064
3065 switch ( key )
3066 {
3067 case 188:
3068 key = 44; // ,
3069 break;
3070 case 190:
3071 key = 46; // .
3072 break;
3073 case 191:
3074 key = 47; // /
3075 break;
3076 case 222:
3077 key = 39; // '
3078 break;
3079 case 219:
3080 key = 91; // [
3081 break;
3082 case 221:
3083 key = 93; // ]
3084 break;
3085 case 220:
3086 key = 92; // \
3087 break;
3088 default:
3089 break;
3090 }
3091
3092 // Translate shifted keys
3093
3094 if ( shift )
3095 {
3096 switch ( key )
3097 {
3098 case 96: // `
3099 key = 126; // ~
3100 break;
3101 case 49: // 1
3102 key = 33; // !
3103 break;
3104 case 50: // 2
3105 key = 64; // @
3106 break;
3107 case 51: // 3
3108 key = 35; // #
3109 break;
3110 case 52: // 4
3111 key = 36; // !
3112 break;
3113 case 53: // 5
3114 key = 37; // %
3115 break;
3116 case 54: // 6
3117 key = 94; // ^
3118 break;
3119 case 55: // 7
3120 key = 38; // &
3121 break;
3122 case 56: // 8
3123 key = 42; // *
3124 break;
3125 case 57: // 9
3126 key = 40; // (
3127 break;
3128 case 48: // 0
3129 key = 41; // )
3130 break;
3131 case 45: // -
3132 key = 95; // _
3133 break;
3134 case 61: // =
3135 key = 43; // +
3136 break;
3137 case 91: // [
3138 key = 123; // {
3139 break;
3140 case 93: // ]
3141 key = 125; // }
3142 break;
3143 case 92: // \
3144 key = 124; // |
3145 break;
3146 case 59: // ;
3147 key = 58; // :
3148 break;
3149 case 39: // '
3150 key = 34; // "
3151 break;
3152 case 44: // ,
3153 key = 60; // <
3154 break;
3155 case 46: // .
3156 key = 62; // >
3157 break;
3158 case 47: // /
3159 key = 63; // ?
3160 break;
3161 default:
3162 break;
3163 }
3164 }
3165
3166 return key;
3167 };
3168
3169 // Called when a key is pressed
3170
3171 PS.SysKeyDown = function (event)
3172 {
3173 "use strict";
3174 var key;
3175
3176 if ( PS.KeyDown ) // only if function exists
3177 {
3178 if ( event.which === null )
3179 {
3180 key = event.keyCode; // IE
3181 }
3182 else
3183 {
3184 key = event.which; // Others
3185 }
3186 key = PS.KeyFilter(key, event.shiftKey);
3187 try
3188 {
3189 PS.KeyDown(key, event.shiftKey, event.ctrlKey);
3190 }
3191 catch (err)
3192 {
3193 PS.Oops("PS.KeyDown() failed [" + err.message + "]" );
3194 }
3195 }
3196 return false;
3197 };
3198
3199 // Called when a key is released
3200
3201 PS.SysKeyUp = function (event)
3202 {
3203 "use strict";
3204 var key;
3205
3206 if ( PS.KeyUp ) // only if function exists
3207 {
3208 if ( event.which === null )
3209 {
3210 key = event.keyCode; // IE
3211 }
3212 else
3213 {
3214 key = event.which; // Others
3215 }
3216 key = PS.KeyFilter(key, event.shiftKey);
3217 try
3218 {
3219 PS.KeyUp(key, event.shiftKey, event.ctrlKey);
3220 }
3221 catch (err)
3222 {
3223 PS.Oops("PS.KeyUp() failed [" + err.message + "]" );
3224 }
3225 }
3226 return false;
3227 };
3228
3229 // Called when mouse wheel is moved
3230
3231 PS.SysWheel = function (event)
3232 {
3233 "use strict";
3234 var delta;
3235
3236 if ( PS.Wheel ) // only if function exists
3237 {
3238 delta = 0;
3239
3240 // for IE
3241
3242 if ( !event )
3243 {
3244 event = window.event;
3245 }
3246
3247 // IE and Opera
3248
3249 if ( event.wheelDelta )
3250 {
3251 delta = event.wheelDelta / 120;
3252 if ( window.opera )
3253 {
3254 delta = -delta;
3255 }
3256 }
3257
3258 // Firefox and Chrome?
3259
3260 else if ( event.detail )
3261 {
3262 delta = -( event.detail / 3 );
3263 }
3264
3265 if ( event.preventDefault )
3266 {
3267 event.preventDefault();
3268 }
3269
3270 // clamp
3271
3272 if ( delta >= PS.FORWARD )
3273 {
3274 delta = PS.FORWARD;
3275 }
3276 else
3277 {
3278 delta = PS.BACKWARD;
3279 }
3280
3281 // Send delta to user
3282
3283 try
3284 {
3285 PS.Wheel (delta);
3286 }
3287 catch (err)
3288 {
3289 PS.Oops("PS.Wheel() failed [" + err.message + "]" );
3290 }
3291 }
3292
3293 event.returnValue = false;
3294 };
3295
3296 // Initialization
3297
3298 PS.Sys = function ()
3299 {
3300 "use strict";
3301 var fn, e;
3302
3303 fn = "[PS.Sys] ";
3304
3305 // Init audio support, preload error sound
3306
3307 PS.AudioInit();
3308 PS.AudioLoad("fx_uhoh");
3309
3310 // Make sure all required game functions exist
3311
3312 if ( typeof PS.Init !== "function" )
3313 {
3314 PS.Init = null;
3315 PS.Oops(fn + "WARNING: PS.Init function undefined");
3316 }
3317
3318 if ( typeof PS.Click !== "function" )
3319 {
3320 PS.Click = null;
3321 PS.Oops(fn + "WARNING: PS.Click function undefined");
3322 }
3323
3324 if ( typeof PS.Release !== "function" )
3325 {
3326 PS.Release = null;
3327 PS.Oops(fn + "WARNING: PS.Release function undefined");
3328 }
3329
3330 if ( typeof PS.Enter !== "function" )
3331 {
3332 PS.Enter = null;
3333 PS.Oops(fn + "WARNING: PS.Enter function undefined");
3334 }
3335
3336 if ( typeof PS.Leave !== "function" )
3337 {
3338 PS.Leave = null;
3339 PS.Oops(fn + "WARNING: PS.Leave function undefined");
3340 }
3341
3342 if ( typeof PS.KeyDown !== "function" )
3343 {
3344 PS.KeyDown = null;
3345 PS.Oops(fn + "WARNING: PS.KeyDown function undefined");
3346 }
3347
3348 if ( typeof PS.KeyUp !== "function" )
3349 {
3350 PS.KeyUp = null;
3351 PS.Oops(fn + "WARNING: PS.KeyUp function undefined");
3352 }
3353
3354 if ( typeof PS.Wheel !== "function" )
3355 {
3356 PS.Wheel = null;
3357 PS.Oops(fn + "WARNING: PS.Wheel function undefined");
3358 }
3359
3360 // Set up mouse/keyboard events
3361
3362 e = document.getElementById("screen");
3363 if ( e )
3364 {
3365 e.addEventListener("mousedown", PS.MouseDown, false);
3366 e.addEventListener("mouseup", PS.MouseUp, false);
3367 e.addEventListener("mouseout", PS.MouseOut, false);
3368 e.addEventListener("mousemove", PS.MouseMove, false);
3369 }
3370
3371 // Set up mouse wheel events
3372
3373 if ( window.addEventListener )
3374 {
3375 window.addEventListener('DOMMouseScroll', PS.SysWheel, false); // for Firefox
3376 window.addEventListener('mousewheel', PS.SysWheel, false); // for others
3377 }
3378 else
3379 {
3380 window.onmousewheel = document.onmousewheel = PS.SysWheel; // for IE, maybe
3381 }
3382
3383 // Setup offscreen canvas for image manipulation
3384
3385 PS.ImageCanvas = document.createElement("canvas");
3386 PS.ImageCanvas.width = PS.GRID_MAX;
3387 PS.ImageCanvas.height = PS.GRID_MAX;
3388
3389 // Start the timer
3390
3391 window.setInterval (PS.Timer, PS.DEFAULT_FPS);
3392
3393 // Print version number
3394
3395 e = document.getElementById("footer");
3396 if ( e )
3397 {
3398 e.innerHTML = "Version " + PS.VERSION;
3399 }
3400
3401 if ( PS.Init )
3402 {
3403 try
3404 {
3405 PS.Init(); // call user initializer
3406 }
3407 catch (err)
3408 {
3409 PS.Oops("PS.Init() failed [" + err.message + "]" );
3410 }
3411 }
3412 else
3413 {
3414 PS.GridSize(PS.GRID_DEFAULT_WIDTH, PS.GRID_DEFAULT_HEIGHT);
3415 }
3416 };
3417
3418 // Library stuff
3419
3420 PS.Random = function (val)
3421 {
3422 "use strict";
3423 var fn;
3424
3425 fn = "[PS.Random] ";
3426 if ( typeof val !== "number" )
3427 {
3428 PS.Oops(fn + "Parameter is not a number");
3429 return PS.ERROR;
3430 }
3431 val = Math.floor(val);
3432 if ( val < 2 )
3433 {
3434 return 1;
3435 }
3436
3437 val = Math.random() * val;
3438 val = Math.floor(val) + 1;
3439 return val;
3440 };
3441
3442 PS.PianoFiles = [
3443 "a0", "bb0", "b0",
3444 "c1", "db1", "d1", "eb1", "e1", "f1", "gb1", "g1", "ab1", "a1", "bb1", "b1",
3445 "c2", "db2", "d2", "eb2", "e2", "f2", "gb2", "g2", "ab2", "a2", "bb2", "b2",
3446 "c3", "db3", "d3", "eb3", "e3", "f3", "gb3", "g3", "ab3", "a3", "bb3", "b3",
3447 "c4", "db4", "d4", "eb4", "e4", "f4", "gb4", "g4", "ab4", "a4", "bb4", "b4",
3448 "c5", "db5", "d5", "eb5", "e5", "f5", "gb5", "g5", "ab5", "a5", "bb5", "b5",
3449 "c6", "db6", "d6", "eb6", "e6", "f6", "gb6", "g6", "ab6", "a6", "bb6", "b6",
3450 "c7", "db7", "d7", "eb7", "e7", "f7", "gb7", "g7", "ab7", "a7", "bb7", "b7",
3451 "c8"
3452 ];
3453
3454 PS.Piano = function ( val, flag )
3455 {
3456 "use strict";
3457 var fn, str;
3458
3459 fn = "[PS.Piano] ";
3460
3461 if ( typeof val !== "number" )
3462 {
3463 PS.Oops(fn + "Parameter is not a number");
3464 return PS.ERROR;
3465 }
3466 val = Math.floor(val);
3467 if ( val < 1 )
3468 {
3469 val = 1;
3470 }
3471 else if ( val > 88 )
3472 {
3473 val = 88;
3474 }
3475
3476 str = "piano_" + PS.PianoFiles[val - 1];
3477 if ( flag )
3478 {
3479 str = "l_" + str;
3480 }
3481 return str;
3482 };
3483
3484 PS.HchordFiles = [
3485 "a2", "bb2", "b2",
3486 "c3", "db3", "d3", "eb3", "e3", "f3", "gb3", "g3", "ab3", "a3", "bb3", "b3",
3487 "c4", "db4", "d4", "eb4", "e4", "f4", "gb4", "g4", "ab4", "a4", "bb4", "b4",
3488 "c5", "db5", "d5", "eb5", "e5", "f5", "gb5", "g5", "ab5", "a5", "bb5", "b5",
3489 "c6", "db6", "d6", "eb6", "e6", "f6", "gb6", "g6", "ab6", "a6", "bb6", "b6",
3490 "c7", "db7", "d7", "eb7", "e7", "f7"
3491 ];
3492
3493 PS.Harpsichord = function ( val, flag )
3494 {
3495 "use strict";
3496 var fn, str;
3497
3498 fn = "[PS.Harpsichord] ";
3499
3500 if ( typeof val !== "number" )
3501 {
3502 PS.Oops(fn + "Parameter is not a number");
3503 return PS.ERROR;
3504 }
3505 val = Math.floor(val);
3506 if ( val < 1 )
3507 {
3508 val = 1;
3509 }
3510 else if ( val > 57 )
3511 {
3512 val = 57;
3513 }
3514
3515 str = "hchord_" + PS.HchordFiles[val - 1];
3516 if ( flag )
3517 {
3518 str = "l_" + str;
3519 }
3520 return str;
3521 };
3522
3523 PS.XyloFiles = [
3524 "a4", "bb4", "b4",
3525 "c5", "db5", "d5", "eb5", "e5", "f5", "gb5", "g5", "ab5", "a5", "bb5", "b5",
3526 "c6", "db6", "d6", "eb6", "e6", "f6", "gb6", "g6", "ab6", "a6", "bb6", "b6",
3527 "c7", "db7", "d7", "eb7", "e7", "f7", "gb7", "g7", "ab7", "a7", "bb7", "b7"
3528 ];
3529
3530 PS.Xylophone = function ( val )
3531 {
3532 "use strict";
3533 var fn, str;
3534
3535 fn = "[PS.Xylophone] ";
3536
3537 if ( typeof val !== "number" )
3538 {
3539 PS.Oops(fn + "Parameter is not a number");
3540 return PS.ERROR;
3541 }
3542 val = Math.floor(val);
3543 if ( val < 1 )
3544 {
3545 val = 1;
3546 }
3547 else if ( val > 39 )
3548 {
3549 val = 39;
3550 }
3551
3552 str = "xylo_" + PS.XyloFiles[val - 1];
3553 return str;
3554 };
3555
3556 // Audio functions
3557
3558 PS.AUDIO_PATH_DEFAULT = "http://users.wpi.edu/~bmoriarty/ps/audio/"; // case sensitive!
3559 PS.AUDIO_PATH = PS.AUDIO_PATH_DEFAULT;
3560 PS.AUDIO_MAX_CHANNELS = 32;
3561 PS.AudioChannels = [];
3562
3563 PS.AudioInit = function ()
3564 {
3565 "use strict";
3566 var i;
3567
3568 for ( i = 0; i < PS.AUDIO_MAX_CHANNELS; i += 1 )
3569 {
3570 PS.AudioChannels[i] = {};
3571 PS.AudioChannels[i].audio = new Audio();
3572 PS.AudioChannels[i].done = -1;
3573 PS.AudioChannels[i].id = "";
3574 }
3575 };
3576
3577 PS.AudioError = function (obj)
3578 {
3579 "use strict";
3580 var c, str;
3581
3582 c = obj.error.code;
3583 switch ( c )
3584 {
3585 case 1:
3586 str = "MEDIA_ERR_ABORTED";
3587 break;
3588 case 2:
3589 str = "MEDIA_ERR_NETWORK";
3590 break;
3591 case 3:
3592 str = "MEDIA_ERR_DECODE";
3593 break;
3594 case 4:
3595 str = "MEDIA_ERR_SRC_NOT_SUPPORTED";
3596 break;
3597 default:
3598 str = "UNKNOWN";
3599 break;
3600 }
3601
3602 PS.Oops("[Audio Error: " + str + "]\n");
3603 };
3604
3605 PS.AudioPath = function (path)
3606 {
3607 "use strict";
3608 var fn;
3609
3610 fn = "[PS.AudioPath] ";
3611
3612 if ( path === PS.DEFAULT )
3613 {
3614 PS.AUDIO_PATH = PS.AUDIO_PATH_DEFAULT;
3615 }
3616 else if ( typeof path !== "string" )
3617 {
3618 PS.Oops(fn + "path parameter is not a string");
3619 return PS.ERROR;
3620 }
3621 else
3622 {
3623 PS.AUDIO_PATH = path;
3624 }
3625
3626 return PS.AUDIO_PATH;
3627 };
3628
3629 PS.AudioLoad = function (id, path)
3630 {
3631 "use strict";
3632 var fn, snd;
3633
3634 fn = "[PS.AudioLoad] ";
3635
3636 if ( typeof id !== "string" )
3637 {
3638 PS.Oops(fn + "id parameter is not a string");
3639 return PS.ERROR;
3640 }
3641 if ( id.length < 1 )
3642 {
3643 PS.Oops(fn + "id parameter is an empty string");
3644 return PS.ERROR;
3645 }
3646
3647 if ( path === undefined )
3648 {
3649 path = PS.AUDIO_PATH;
3650 }
3651 else if ( path === PS.DEFAULT )
3652 {
3653 path = PS.AUDIO_PATH_DEFAULT;
3654 }
3655 else if ( typeof path !== "string" )
3656 {
3657 PS.Oops(fn + "path parameter is not a string");
3658 return PS.ERROR;
3659 }
3660
3661 // Already got this? Clone it
3662
3663 snd = document.getElementById(id);
3664 if ( snd )
3665 {
3666 return snd;
3667 }
3668
3669 path = path + id + ".wav";
3670
3671 snd = document.createElement("audio");
3672 snd.setAttribute("src", path);
3673 snd.setAttribute("id", id);
3674 snd.setAttribute("preload", "auto");
3675 snd.setAttribute("onerror", "PS.AudioError(this)");
3676
3677 // src = document.createElement("source");
3678 // src.setAttribute("src", path + ".ogg");
3679 // src.setAttribute("type", "audio/ogg");
3680 // snd.appendChild(src);
3681
3682 // src = document.createElement("source");
3683 // src.setAttribute("src", path + ".mp3");
3684 // src.setAttribute("type", "audio/mpeg");
3685 // src.setAttribute("onerror", "PS.AudioError()");
3686 // snd.appendChild(src);
3687
3688 // src = document.createElement("source");
3689 // src.setAttribute("src", path + ".wav");
3690 // src.setAttribute("type", "audio/x-wav");
3691 // snd.appendChild(src);
3692
3693 document.body.appendChild(snd);
3694 snd.load();
3695
3696 return snd;
3697 };
3698
3699 // Returns a channel number
3700
3701 PS.AudioPlay = function (id, volume, func, path)
3702 {
3703 "use strict";
3704 var fn, i, snd, d, t, channel;
3705
3706 fn = "[PS.AudioPlay] ";
3707
3708 if ( (volume === undefined) || (volume === PS.DEFAULT) )
3709 {
3710 volume = PS.DEFAULT_VOLUME;
3711 }
3712 else if ( typeof volume !== "number" )
3713 {
3714 PS.Oops(fn + "volume parameter is not a number");
3715 return PS.ERROR;
3716 }
3717 else if ( volume < 0 )
3718 {
3719 volume = 0;
3720 }
3721 else if ( volume > PS.DEFAULT_VOLUME )
3722 {
3723 volume = PS.DEFAULT_VOLUME;
3724 }
3725
3726 if ( (func !== undefined) && (typeof func !== "function") )
3727 {
3728 PS.Oops(fn + "func parameter is not a function");
3729 return PS.ERROR;
3730 }
3731
3732 snd = PS.AudioLoad(id, path);
3733 if ( snd !== PS.ERROR )
3734 {
3735 snd.volume = volume;
3736 if ( func !== undefined )
3737 {
3738 snd.addEventListener("ended", func);
3739 }
3740 for ( i = 0; i < PS.AUDIO_MAX_CHANNELS; i += 1 )
3741 {
3742 d = new Date();
3743 t = d.getTime();
3744 channel = PS.AudioChannels[i];
3745 if ( channel.done < t )
3746 {
3747 channel.done = t + ( snd.duration * 1000 );
3748 channel.audio = snd;
3749 channel.id = id;
3750 snd.load(); // WHY???
3751 snd.play();
3752 return i + 1; // channel id
3753 }
3754 }
3755 }
3756
3757 return PS.ERROR; // error
3758 };
3759
3760 // Stops playback of channel number
3761
3762 PS.AudioStop = function (c)
3763 {
3764 "use strict";
3765 var fn, d, t, channel;
3766
3767 fn = "[PS.AudioStop] ";
3768
3769 if ( typeof c !== "number" )
3770 {
3771 PS.Oops(fn + "Parameter is not a number");
3772 return PS.ERROR;
3773 }
3774 c = Math.floor(c);
3775 if ( (c < 1) || (c > PS.AUDIO_MAX_CHANNELS) )
3776 {
3777 PS.Oops(fn + "Invalid channel id");
3778 return PS.ERROR;
3779 }
3780
3781 channel = PS.AudioChannels[c - 1];
3782 d = new Date();
3783 t = d.getTime();
3784
3785 channel.done = t; // mark as done playing
3786 channel.audio.pause();
3787 channel.audio.currentTime = 0;
3788
3789 return c;
3790 };
3791
3792 // Pauses/unpauses playback of channel number
3793
3794 PS.AudioPause = function (c)
3795 {
3796 "use strict";
3797 var fn, channel, audio;
3798
3799 fn = "[PS.AudioPause] ";
3800
3801 if ( typeof c !== "number" )
3802 {
3803 PS.Oops(fn + "Parameter is not a number");
3804 return PS.ERROR;
3805 }
3806 c = Math.floor(c);
3807 if ( (c < 1) || (c > PS.AUDIO_MAX_CHANNELS) )
3808 {
3809 PS.Oops(fn + "Invalid channel id");
3810 return PS.ERROR;
3811 }
3812
3813 channel = PS.AudioChannels[c - 1];
3814 audio = channel.audio;
3815 if ( audio.paused )
3816 {
3817 audio.play();
3818 }
3819 else
3820 {
3821 audio.pause();
3822 }
3823
3824 return c;
3825 };
3826
3827 // Image functions
3828
3829 PS.ImageLoad = function ( file, func )
3830 {
3831 "use strict";
3832 var fn, img;
3833
3834 fn = "[PS.ImageLoad] ";
3835
3836 if ( typeof file !== "string" )
3837 {
3838 PS.Oops(fn + "Parameter 1 is not a string");
3839 return PS.ERROR;
3840 }
3841 if ( file.length < 1 )
3842 {
3843 PS.Oops(fn + "Parameter 1 is an empty string");
3844 return PS.ERROR;
3845 }
3846 if ( (func !== undefined) && (typeof func !== "function") )
3847 {
3848 PS.Oops(fn + "Parameter 2 is not a function");
3849 return PS.ERROR;
3850 }
3851 try
3852 {
3853 img = new Image();
3854 if ( func !== undefined )
3855 {
3856 img.onload = func;
3857 }
3858 img.src = file;
3859 return img;
3860 }
3861 catch (err)
3862 {
3863 PS.Oops(fn + "failed [" + err.message + "]");
3864 return PS.ERROR;
3865 }
3866 };
3867
3868 // Extract an imageData table from an image file
3869 // optional [alpha] determines if alpha data if included
3870
3871 PS.ImageData = function ( img, alpha )
3872 {
3873 "use strict";
3874 var fn, w, h, ctx, imgData, imgData2, i, j, len, d1, d2;
3875
3876 fn = "[PS.ImageMap] ";
3877 if ( (alpha === undefined) || !alpha )
3878 {
3879 alpha = false;
3880 }
3881 else
3882 {
3883 alpha = true;
3884 }
3885
3886 w = img.width;
3887 if ( (typeof w !== "number") || (w < 0) )
3888 {
3889 PS.Oops(fn + "Invalid width parameter");
3890 return PS.ERROR;
3891 }
3892 w = Math.floor(w);
3893 if ( w > PS.GRID_MAX )
3894 {
3895 w = PS.GRID_MAX;
3896 }
3897
3898 h = img.height;
3899 if ( (typeof h !== "number") || (h < 0) )
3900 {
3901 PS.Oops(fn + "Invalid height parameter");
3902 return PS.ERROR;
3903 }
3904 h = Math.floor(h);
3905 if ( h > PS.GRID_MAX )
3906 {
3907 h = PS.GRID_MAX;
3908 }
3909
3910 // draw the image onto the offscreen canvas
3911
3912 try
3913 {
3914 ctx = PS.ImageCanvas.getContext("2d");
3915 ctx.drawImage(img, 0, 0);
3916 }
3917 catch (err)
3918 {
3919 PS.Oops(fn + "failed @ 1 [" + err.message + "]");
3920 return PS.ERROR;
3921 }
3922
3923 // fetch the data and return it
3924
3925 try
3926 {
3927 imgData = ctx.getImageData(0, 0, w, h);
3928 }
3929 catch (err2)
3930 {
3931 PS.Oops(fn + "failed @ 2 [" + err2.message + "]");
3932 return PS.ERROR;
3933 }
3934
3935 // imgData is read-only for some reason
3936 // so make a copy of it
3937
3938 imgData2 = {};
3939 imgData2.width = imgData.width;
3940 imgData2.height = imgData.height;
3941
3942 d1 = imgData.data;
3943 len = d1.length;
3944 d2 = []; // new data array
3945 i = 0;
3946 j = 0;
3947
3948 // if alpha data is not wanted, remove it
3949
3950 if ( !alpha )
3951 {
3952 d2.length = (len / 4) * 3;
3953 while ( i < len )
3954 {
3955 d2[j] = d1[i];
3956 i += 1;
3957 j += 1;
3958 d2[j] = d1[i];
3959 i += 1;
3960 j += 1;
3961 d2[j] = d1[i];
3962 i += 2; // skip alpha
3963 j += 1;
3964 }
3965 imgData2.pixelSize = 3;
3966 }
3967 else
3968 {
3969 d2.length = len;
3970 while ( i < len )
3971 {
3972 d2[j] = d1[i];
3973 i += 1;
3974 j += 1;
3975 d2[j] = d1[i];
3976 i += 1;
3977 j += 1;
3978 d2[j] = d1[i];
3979 i += 1;
3980 j += 1;
3981 d2[j] = d1[i];
3982 i += 1;
3983 j += 1;
3984 }
3985 imgData2.pixelSize = 4;
3986 }
3987
3988 imgData2.data = d2;
3989 return imgData2;
3990 };
3991
3992 PS.ImageBlit = function ( imgdata, xpos, ypos )
3993 {
3994 "use strict";
3995 var fn, size, bytes, w, h, pixsize, ptr, drawx, drawy, i, j, r, g, b, a, k, bead;
3996
3997 fn = "[PS.ImageBlit] ";
3998
3999 // verify data format
4000
4001 if ( typeof imgdata !== "object" )
4002 {
4003 PS.Oops(fn + "parameter 1 is not a table");
4004 return PS.ERROR;
4005 }
4006
4007 bytes = imgdata.data; // check this?
4008
4009 w = imgdata.width;
4010 if ( typeof w !== "number" )
4011 {
4012 PS.Oops(fn + "imgdata.width is not a number");
4013 return PS.ERROR;
4014 }
4015 w = Math.floor(w);
4016 if ( w < 1 )
4017 {
4018 PS.Oops(fn + "imgdata.width < 1");
4019 return PS.ERROR;
4020 }
4021
4022 h = imgdata.height;
4023 if ( typeof h !== "number" )
4024 {
4025 PS.Oops(fn + "imgdata.height is not a number");
4026 return PS.ERROR;
4027 }
4028 h = Math.floor(h);
4029 if ( h < 1 )
4030 {
4031 PS.Oops(fn + "imgdata.height < 1");
4032 return PS.ERROR;
4033 }
4034
4035 pixsize = imgdata.pixelSize;
4036 if ( (pixsize !== 3) && (pixsize !== 4) )
4037 {
4038 PS.Oops(fn + "invalid pixelSize");
4039 return PS.ERROR;
4040 }
4041
4042 size = w * h * pixsize;
4043 if ( bytes.length !== size )
4044 {
4045 PS.Oops(fn + "invalid data length [" + imgdata.data.length + "]");
4046 return PS.ERROR;
4047 }
4048
4049 if ( (xpos === undefined) || (xpos === PS.DEFAULT) )
4050 {
4051 xpos = 0;
4052 }
4053 else if ( typeof xpos !== "number" )
4054 {
4055 PS.Oops(fn + "parameter 2 is not a number");
4056 return PS.ERROR;
4057 }
4058 else
4059 {
4060 xpos = Math.floor(xpos);
4061
4062 // exit if drawing off grid
4063
4064 if ( (xpos >= PS.Grid.x) || ((xpos + w) < 1) )
4065 {
4066 return true;
4067 }
4068 }
4069
4070 if ( (ypos === undefined) || (ypos === PS.DEFAULT) )
4071 {
4072 ypos = 0;
4073 }
4074 else if ( typeof ypos !== "number" )
4075 {
4076 PS.Oops(fn + "parameter 3 is not a number");
4077 return PS.ERROR;
4078 }
4079 else
4080 {
4081 ypos = Math.floor(ypos);
4082
4083 // exit if drawing off grid
4084
4085 if ( (ypos >= PS.Grid.y) || ((ypos + h) < 1) )
4086 {
4087 return true;
4088 }
4089 }
4090
4091 ptr = 0;
4092 drawy = ypos;
4093 for ( j = 0; j < h; j += 1 )
4094 {
4095 drawx = xpos;
4096 if ( drawy >= PS.Grid.y ) // exit if off bottom of grid
4097 {
4098 break;
4099 }
4100 if ( drawy >= 0 ) // is this row visible?
4101 {
4102 for ( i = 0; i < w; i += 1 )
4103 {
4104 if ( (drawx >= 0) && (drawx < PS.Grid.x) )
4105 {
4106 r = bytes[ptr];
4107 if ( typeof r !== "number" )
4108 {
4109 PS.Oops(fn + "non-numeric red at position " + ptr);
4110 return PS.ERROR;
4111 }
4112 r = Math.floor(r);
4113 if ( r < 1 )
4114 {
4115 r = 0;
4116 }
4117 else if ( r > 255 )
4118 {
4119 r = 255;
4120 }
4121
4122 g = bytes[ptr + 1];
4123 if ( typeof g !== "number" )
4124 {
4125 PS.Oops(fn + "non-numeric green at position " + ptr);
4126 return PS.ERROR;
4127 }
4128 g = Math.floor(g);
4129 if ( g < 1 )
4130 {
4131 g = 0;
4132 }
4133 else if ( g > 255 )
4134 {
4135 g = 255;
4136 }
4137
4138 b = bytes[ptr + 2];
4139 if ( typeof b !== "number" )
4140 {
4141 PS.Oops(fn + "non-numeric blue at position " + ptr);
4142 return PS.ERROR;
4143 }
4144 b = Math.floor(b);
4145 if ( b < 1 )
4146 {
4147 b = 0;
4148 }
4149 else if ( b > 255 )
4150 {
4151 b = 255;
4152 }
4153
4154 if ( pixsize === 4 )
4155 {
4156 a = bytes[ptr + 3];
4157 if ( typeof a !== "number" )
4158 {
4159 PS.Oops(fn + "non-numeric alpha at position " + ptr);
4160 return PS.ERROR;
4161 }
4162 a = Math.floor(a / 2.55); // convert 0-255 range to 0-100
4163 if ( a < 1 )
4164 {
4165 a = 0;
4166 }
4167 else if ( a > PS.DEFAULT_ALPHA )
4168 {
4169 a = PS.DEFAULT_ALPHA;
4170 }
4171 }
4172 else
4173 {
4174 a = PS.DEFAULT_ALPHA;
4175 }
4176
4177 k = drawx + (drawy * PS.Grid.x); // get index of bead
4178 bead = PS.Grid.beads[k];
4179
4180 bead.dirty = true; // mark this bead as explicitly colored
4181 if ( a < PS.DEFAULT_ALPHA ) // Calc new color based on alpha-adjusted color of existing bead
4182 {
4183 bead.alphaRed = PS.Dissolve( PS.Grid.bgRed, bead.alphaRed, a );
4184 bead.alphaGreen = PS.Dissolve( PS.Grid.bgGreen, bead.alphaGreen, a );
4185 bead.alphaBlue = PS.Dissolve( PS.Grid.bgBlue, bead.alphaBlue, a );
4186 bead.color = PS.RGBString( bead.alphaRed, bead.alphaGreen, bead.alphaBlue );
4187 }
4188 else
4189 {
4190 bead.alphaRed = r;
4191 bead.alphaGreen = g;
4192 bead.alphaBlue = b;
4193 bead.color = PS.RGBString( r, g, b );
4194 }
4195
4196 // Do NOT change alpha value of existing bead!
4197
4198 bead.red = r;
4199 bead.green = g;
4200 bead.blue = b;
4201
4202 if ( bead.visible )
4203 {
4204 bead.flashPhase = 100; // stops flash in progress
4205 bead.colorNow = bead.color;
4206 PS.DrawBead(bead);
4207 }
4208 }
4209 ptr += pixsize;
4210 drawx += 1;
4211 }
4212 }
4213 else
4214 {
4215 ptr += (w * pixsize); // skip this row
4216 }
4217 drawy += 1;
4218 }
4219
4220 return true;
4221 };