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 <