--- /dev/null
+// ps2.1.js for Perlenspiel 2.1\r
+\r
+/*\r
+Perlenspiel is a scheme by Professor Moriarty (bmoriarty@wpi.edu).\r
+Perlenspiel is Copyright © 2009-12 Worcester Polytechnic Institute.\r
+This file is part of Perlenspiel.\r
+\r
+Perlenspiel is free software: you can redistribute it and/or modify\r
+it under the terms of the GNU Lesser General Public License as published\r
+by the Free Software Foundation, either version 3 of the License, or\r
+(at your option) any later version.\r
+\r
+Perlenspiel is distributed in the hope that it will be useful,\r
+but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
+GNU Lesser General Public License for more details.\r
+\r
+You may have received a copy of the GNU Lesser General Public License\r
+along with Perlenspiel. If not, see <http://www.gnu.org/licenses/>.\r
+*/\r
+\r
+// The following comments are for JSLint\r
+\r
+/*global document, window, Audio, Image */\r
+\r
+// Global namespace variable\r
+\r
+var PS = {\r
+ \r
+ // Constants\r
+ \r
+ VERSION: "2.1.00",\r
+ DEFAULT: -1, // use default value\r
+ CURRENT: -2, // use current value\r
+ ALL: -3, // Use all rows or columns\r
+ ERROR: "*ERROR*", // generic error return value\r
+ CANVAS_SIZE: 480, // max width/height of canvas\r
+ GRID_MAX: 32, // max x/y dimensions of grid\r
+ GRID_DEFAULT_WIDTH: 8,\r
+ GRID_DEFAULT_HEIGHT: 8,\r
+ DEFAULT_BEAD_COLOR: 0x000000,\r
+ DEFAULT_BEAD_RED: 0x00,\r
+ DEFAULT_BEAD_GREEN: 0x00,\r
+ DEFAULT_BEAD_BLUE: 0x00,\r
+ DEFAULT_BG_COLOR: 0xFFFFFF,\r
+ DEFAULT_BG_RED: 0xFF,\r
+ DEFAULT_BG_GREEN: 0xFF,\r
+ DEFAULT_BG_BLUE: 0xFF,\r
+ DEFAULT_BORDER_COLOR: 0x808080,\r
+ DEFAULT_BORDER_RED: 0x80,\r
+ DEFAULT_BORDER_GREEN: 0x80,\r
+ DEFAULT_BORDER_BLUE: 0x80,\r
+ DEFAULT_BORDER_WIDTH: 1,\r
+ BORDER_WIDTH_MAX: 8,\r
+ DEFAULT_GLYPH_COLOR: 0xFFFFFF,\r
+ DEFAULT_GLYPH_RED: 0xFF,\r
+ DEFAULT_GLYPH_GREEN: 0xFF,\r
+ DEFAULT_GLYPH_BLUE: 0xFF,\r
+ DEFAULT_FLASH_COLOR: 0xFFFFFF,\r
+ DEFAULT_FLASH_RED: 0xFF,\r
+ DEFAULT_FLASH_GREEN: 0xFF,\r
+ DEFAULT_FLASH_BLUE: 0xFF,\r
+ DEFAULT_ALPHA: 100, // must be between 0 and 100\r
+ DEFAULT_VOLUME: 1.0, // must be between 0 and 1.0\r
+ DEFAULT_LOOP: false,\r
+ DEFAULT_FPS: 10, // frame rate in milliseconds (1/100 sec)\r
+ REDSHIFT: 256 * 256, // used to decode rgb\r
+ FLASH_STEP: 10, // percent for each flash\r
+ STATUS_FLASH_STEP: 5, // percent for each step\r
+ FLASH_INTERVAL: 5, // number of ticks per flash step\r
+ DEFAULT_TEXT_COLOR: 0x000000,\r
+\r
+ // Color constants\r
+\r
+ COLOR_BLACK: 0x000000,\r
+ COLOR_WHITE: 0xFFFFFF,\r
+ COLOR_GRAY_LIGHT: 0xC0C0C0,\r
+ COLOR_GRAY: 0x808080,\r
+ COLOR_GRAY_DARK: 0x404040,\r
+ COLOR_RED: 0xFF0000,\r
+ COLOR_ORANGE: 0xFF8000,\r
+ COLOR_YELLOW: 0xFFFF00,\r
+ COLOR_GREEN: 0x00FF00,\r
+ COLOR_BLUE: 0x0000FF,\r
+ COLOR_INDIGO: 0x4000FF,\r
+ COLOR_VIOLET: 0x8000FF,\r
+ COLOR_MAGENTA: 0xFF00FF,\r
+ COLOR_CYAN: 0x00FFFF,\r
+\r
+ // Key and mouse wheel constants\r
+\r
+ ARROW_LEFT: 37,\r
+ ARROW_RIGHT: 39,\r
+ ARROW_UP: 38,\r
+ ARROW_DOWN: 40,\r
+ KEYPAD_0: 96,\r
+ KEYPAD_1: 97,\r
+ KEYPAD_2: 98,\r
+ KEYPAD_3: 99,\r
+ KEYPAD_4: 100,\r
+ KEYPAD_5: 101,\r
+ KEYPAD_6: 102,\r
+ KEYPAD_7: 103,\r
+ KEYPAD_8: 104,\r
+ KEYPAD_9: 105,\r
+ F1: 112,\r
+ F2: 113,\r
+ F3: 114,\r
+ F4: 115,\r
+ F5: 116,\r
+ F6: 117,\r
+ F7: 118,\r
+ F8: 119,\r
+ F9: 120,\r
+ F10: 121,\r
+ FORWARD: 1,\r
+ BACKWARD: -1,\r
+\r
+ Grid: null, // main grid\r
+ DebugWindow: null, // debugger window\r
+ ImageCanvas: null, // offscreen canvas for image manipulation\r
+\r
+ // Coordinates of current and previous beads, -1 if none\r
+\r
+ MouseX: -1,\r
+ MouseY: -1,\r
+ LastX: -1,\r
+ LastY: -1,\r
+\r
+ // Delay and clock settings\r
+\r
+ FlashDelay: 0,\r
+ UserDelay: 0,\r
+ UserClock: 0,\r
+\r
+ // Status line\r
+\r
+ Status: "Perlenspiel",\r
+ StatusHue: 0, // target hue\r
+ StatusRed: 0,\r
+ StatusGreen: 0,\r
+ StatusBlue: 0,\r
+ StatusPhase: 0, // 100: done fading\r
+ StatusFading: true\r
+};\r
+\r
+// Improved typeof that distinguishes arrays\r
+\r
+PS.TypeOf = function (value)\r
+{\r
+ "use strict";\r
+ var s;\r
+\r
+ s = typeof value;\r
+ if ( s === "object" )\r
+ {\r
+ if ( value )\r
+ {\r
+ if ( value instanceof Array )\r
+ {\r
+ s = "array";\r
+ }\r
+ }\r
+ else\r
+ {\r
+ s = "null";\r
+ }\r
+ }\r
+ return s;\r
+};\r
+\r
+// Get the canvas context\r
+\r
+PS.Context = function ()\r
+{\r
+ "use strict";\r
+ var cv, ctx;\r
+\r
+ ctx = null;\r
+ cv = document.getElementById("screen");\r
+ if ( cv && cv.getContext )\r
+ {\r
+ ctx = cv.getContext("2d");\r
+ }\r
+\r
+ return ctx;\r
+};\r
+\r
+// Takes a multiplexed rgb value and a function name\r
+// Returns floored rgb value, or -1 if invalid\r
+\r
+PS.ValidRGB = function ( rgb, fn )\r
+{\r
+ "use strict";\r
+\r
+ if ( typeof rgb !== "number" )\r
+ {\r
+ PS.Oops( fn + "rgb parameter not a number" );\r
+ return -1;\r
+ }\r
+ rgb = Math.floor(rgb);\r
+ if ( rgb < 0 )\r
+ {\r
+ PS.Oops( fn + "rgb parameter negative" );\r
+ return -1;\r
+ }\r
+ if ( rgb > 0xFFFFFF )\r
+ {\r
+ PS.Oops( fn + "rgb parameter out of range" );\r
+ return -1;\r
+ }\r
+ return rgb;\r
+};\r
+\r
+// Construct a color string with optional alpha\r
+\r
+PS.RGBString = function (r, g, b, a)\r
+{\r
+ "use strict";\r
+ var str;\r
+\r
+ if ( a === undefined )\r
+ {\r
+ str = "rgb(" + r + "," + g + "," + b + ")";\r
+ }\r
+ else\r
+ {\r
+ str = "rgba(" + r + "," + g + "," + b + "," + a + ")";\r
+ }\r
+ return str;\r
+};\r
+\r
+// Takes a multiplexed rgb value and creates an object with\r
+// separate r, g and b values, or null if error\r
+\r
+PS.UnmakeRGB = function ( rgb )\r
+{\r
+ "use strict";\r
+ var fn, red, green, blue, rval, gval;\r
+\r
+ fn = "[PS.DecodeRGB] ";\r
+\r
+ if ( typeof rgb !== "number" )\r
+ {\r
+ PS.Oops(fn + "RGB parameter not a number");\r
+ return PS.ERROR;\r
+ }\r
+ rgb = Math.floor(rgb);\r
+ if ( rgb < 0 )\r
+ {\r
+ PS.Oops(fn + "RGB parameter negative");\r
+ return PS.ERROR;\r
+ }\r
+ if ( rgb > 0xFFFFFF )\r
+ {\r
+ PS.Oops(fn + "RGB parameter out of range");\r
+ return PS.ERROR;\r
+ }\r
+\r
+ red = rgb / PS.REDSHIFT;\r
+ red = Math.floor(red);\r
+ rval = red * PS.REDSHIFT;\r
+\r
+ green = (rgb - rval) / 256;\r
+ green = Math.floor(green);\r
+ gval = green * 256;\r
+\r
+ blue = rgb - rval - gval;\r
+\r
+ return { r: red, g: green, b: blue };\r
+};\r
+\r
+// PS.Dissolve\r
+// Returns a color that is x% between c1 and c2\r
+\r
+PS.Dissolve = function ( c1, c2, x )\r
+{\r
+ "use strict";\r
+ var delta;\r
+\r
+ if ( c1 > c2 )\r
+ {\r
+ delta = c1 - c2;\r
+ delta = ( x * delta ) / 100;\r
+ delta = Math.floor(delta);\r
+ return ( c1 - delta );\r
+ }\r
+ else\r
+ {\r
+ delta = c2 - c1;\r
+ delta = ( x * delta ) / 100;\r
+ delta = Math.floor(delta);\r
+ return ( c1 + delta );\r
+ }\r
+};\r
+\r
+// Bead constuctor\r
+\r
+PS.InitBead = function (xpos, ypos, size, bgcolor)\r
+{\r
+ "use strict";\r
+ var bead;\r
+\r
+ bead = {};\r
+\r
+ bead.left = xpos;\r
+ bead.right = xpos + size;\r
+ bead.top = ypos;\r
+ bead.bottom = ypos + size;\r
+\r
+ bead.size = size;\r
+\r
+ bead.visible = true; // bead visible?\r
+\r
+ // target color\r
+\r
+ bead.dirty = false; // bead color touched?\r
+\r
+ // base colors\r
+\r
+ bead.red = PS.DEFAULT_BEAD_RED;\r
+ bead.green = PS.DEFAULT_BEAD_GREEN;\r
+ bead.blue = PS.DEFAULT_BEAD_BLUE;\r
+ bead.color = PS.RGBString (bead.red, bead.green, bead.blue); // default color\r
+ bead.colorNow = bead.color; // actual color while drawing\r
+\r
+ // pre-calculated alpha colors\r
+\r
+ bead.alpha = PS.DEFAULT_ALPHA;\r
+ bead.alphaRed = PS.DEFAULT_BEAD_RED;\r
+ bead.alphaGreen = PS.DEFAULT_BEAD_GREEN;\r
+ bead.alphaBlue = PS.DEFAULT_BEAD_BLUE;\r
+\r
+ // glyph params\r
+\r
+ bead.glyph = 0; // glyph code (zero if none)\r
+ bead.glyphStr = ""; // actual string to print\r
+ bead.glyphRed = PS.DEFAULT_GLYPH_RED;\r
+ bead.glyphGreen = PS.DEFAULT_GLYPH_GREEN;\r
+ bead.glyphBlue = PS.DEFAULT_GLYPH_BLUE;\r
+ bead.glyphColor = PS.RGBString (PS.DEFAULT_GLYPH_RED, PS.DEFAULT_GLYPH_GREEN, PS.DEFAULT_GLYPH_BLUE);\r
+\r
+ // flash params\r
+\r
+ bead.flash = true; // flashing enabled?\r
+ bead.flashPhase = 0; // phase of flash animation\r
+ bead.flashRed = PS.DEFAULT_FLASH_RED;\r
+ bead.flashGreen = PS.DEFAULT_FLASH_GREEN;\r
+ bead.flashBlue = PS.DEFAULT_FLASH_BLUE;\r
+ bead.flashColor = PS.RGBString (PS.DEFAULT_FLASH_RED, PS.DEFAULT_FLASH_GREEN, PS.DEFAULT_FLASH_BLUE);\r
+\r
+ // border params\r
+\r
+ bead.borderWidth = PS.DEFAULT_BORDER_WIDTH; // border width; 0 if none\r
+ bead.borderRed = PS.DEFAULT_BORDER_RED;\r
+ bead.borderGreen = PS.DEFAULT_BORDER_GREEN;\r
+ bead.borderBlue = PS.DEFAULT_BORDER_BLUE;\r
+ bead.borderAlpha = PS.DEFAULT_ALPHA; // border alpha\r
+ bead.borderColor = PS.RGBString (PS.DEFAULT_BORDER_RED, PS.DEFAULT_BORDER_GREEN, PS.DEFAULT_BORDER_BLUE);\r
+\r
+ // data, sound, exec params\r
+\r
+ bead.data = 0; // data value\r
+\r
+ bead.audio = null; // sound (null = none)\r
+ bead.volume = PS.DEFAULT_VOLUME; // volume\r
+ bead.loop = PS.DEFAULT_LOOP; // loop flag\r
+\r
+ bead.exec = null; // on-click function (null = none)\r
+\r
+ // give each bead its own offscreen canvas and context\r
+\r
+ bead.off = document.createElement("canvas");\r
+ bead.off.width = size;\r
+ bead.off.height = size;\r
+ bead.off.backgroundColor = bgcolor;\r
+ bead.offContext = bead.off.getContext("2d");\r
+\r
+ // set up font info for offscreen context\r
+\r
+ bead.offContext.font = Math.floor(size / 2) + "pt sans-serif";\r
+ bead.offContext.textAlign = "center";\r
+ bead.offContext.textBaseline = "middle";\r
+\r
+ return bead;\r
+};\r
+\r
+// Draws bead [bead] in (optional) context [ctx]\r
+\r
+PS.DrawBead = function (bead, ctx)\r
+{\r
+ "use strict";\r
+ var offctx, left, top, size, width;\r
+\r
+ // get destination context if not provided\r
+\r
+ if ( ctx === undefined )\r
+ {\r
+ ctx = PS.Context();\r
+ }\r
+\r
+ left = 0;\r
+ top = 0;\r
+ size = bead.size; \r
+\r
+ offctx = bead.offContext; // the offscreen canvas context\r
+\r
+ // draw border if needed\r
+\r
+ width = bead.borderWidth;\r
+ if ( width > 0 )\r
+ {\r
+ offctx.fillStyle = bead.borderColor;\r
+ offctx.fillRect(0, 0, size, size);\r
+\r
+ // adjust position and size of bead rect \r
+\r
+ left += width;\r
+ top += width;\r
+ size -= (width + width);\r
+ }\r
+\r
+ // draw bead body if dirty (has had color explicitly set)\r
+\r
+ if ( bead.dirty )\r
+ {\r
+ offctx.fillStyle = bead.colorNow;\r
+ }\r
+\r
+ // otherwise fill with background color\r
+\r
+ else\r
+ {\r
+ offctx.fillStyle = PS.Grid.bgColor;\r
+ }\r
+\r
+ offctx.fillRect(left, top, size, size);\r
+\r
+ if ( bead.glyph > 0 )\r
+ {\r
+ offctx.fillStyle = bead.glyphColor;\r
+ offctx.fillText (bead.glyphStr, PS.Grid.glyphX, PS.Grid.glyphY);\r
+ }\r
+\r
+ // blit offscreen canvas to main canvas\r
+\r
+ ctx.drawImage(bead.off, bead.left, bead.top);\r
+};\r
+\r
+// Erase bead [bead] in (optional) context [ctx]\r
+\r
+PS.EraseBead = function (bead, ctx)\r
+{\r
+ "use strict";\r
+ var size, left, top, width;\r
+\r
+ // get destination context if not provided\r
+\r
+ if ( ctx === undefined )\r
+ {\r
+ ctx = PS.Context();\r
+ }\r
+\r
+ left = bead.left;\r
+ top = bead.top;\r
+ size = bead.size; \r
+\r
+ // draw border if needed\r
+\r
+ width = bead.borderWidth;\r
+ if ( width > 0 )\r
+ {\r
+ ctx.fillStyle = bead.borderColor;\r
+ ctx.fillRect(left, top, size, size);\r
+\r
+ // adjust position and size of bead rect \r
+\r
+ left += width;\r
+ top += width;\r
+ size -= (width + width);\r
+ }\r
+\r
+ ctx.fillStyle = PS.Grid.bgColor;\r
+ ctx.fillRect(left, top, size, size);\r
+};\r
+\r
+// Grid constructor\r
+// Call with x/y dimensions of grid\r
+// Returns initialized grid object or null if error\r
+\r
+PS.InitGrid = function (x, y)\r
+{\r
+ "use strict";\r
+ var grid, i, j, size, xpos, ypos;\r
+\r
+ grid = {};\r
+\r
+ grid.x = x; // x dimensions of grid\r
+ grid.y = y; // y dimensions of grid\r
+ grid.count = x * y; // number of beads in grid\r
+\r
+ // calc size of beads, position/dimensions of centered grid on canvas\r
+\r
+ if ( x >= y )\r
+ {\r
+ grid.beadSize = size = Math.floor(PS.CANVAS_SIZE / x);\r
+ grid.width = size * x;\r
+ grid.height = size * y;\r
+ grid.left = 0;\r
+ }\r
+ else\r
+ {\r
+ grid.beadSize = size = Math.floor(PS.CANVAS_SIZE / y);\r
+ grid.width = size * x;\r
+ grid.height = size * y;\r
+ grid.left = Math.floor( (PS.CANVAS_SIZE - grid.width) / 2 );\r
+ }\r
+ \r
+ grid.top = 0;\r
+\r
+ grid.right = grid.left + grid.width;\r
+ grid.bottom = grid.top + grid.height;\r
+\r
+ grid.bgRed = PS.DEFAULT_BG_RED;\r
+ grid.bgGreen = PS.DEFAULT_BG_GREEN;\r
+ grid.bgBlue = PS.DEFAULT_BG_BLUE;\r
+ grid.bgColor = PS.RGBString (grid.bgRed, grid.bgGreen, grid.bgBlue);\r
+\r
+ grid.borderRed = PS.DEFAULT_BORDER_RED;\r
+ grid.borderGreen = PS.DEFAULT_BORDER_GREEN;\r
+ grid.borderBlue = PS.DEFAULT_BORDER_BLUE;\r
+ grid.borderColor = PS.RGBString (grid.borderRed, grid.borderGreen, grid.borderBlue);\r
+\r
+// grid.borderWidth = PS.DEFAULT_BORDER_WIDTH;\r
+ grid.borderMax = PS.BORDER_WIDTH_MAX; // for now; should be calculated\r
+\r
+ grid.pointing = -1; // bead cursor is pointing at (-1 if none)\r
+\r
+ grid.flash = true; // flash globally enabled?\r
+ grid.flashList = []; // array of currently flashing beads\r
+\r
+ grid.glyphX = Math.floor(size / 2);\r
+ grid.glyphY = Math.floor((size / 7) * 4);\r
+\r
+ // init beads \r
+\r
+ grid.beads = [];\r
+ ypos = grid.top;\r
+ for ( j = 0; j < y; j += 1 )\r
+ {\r
+ xpos = grid.left;\r
+ for ( i = 0; i < x; i += 1 )\r
+ {\r
+ grid.beads.push( PS.InitBead(xpos, ypos, size, grid.bgColor) );\r
+ xpos += size;\r
+ }\r
+ ypos += size;\r
+ }\r
+\r
+ return grid;\r
+};\r
+\r
+PS.DrawGrid = function ()\r
+{\r
+ "use strict";\r
+ var ctx, beads, cnt, i, bead;\r
+\r
+ ctx = PS.Context();\r
+ beads = PS.Grid.beads;\r
+ cnt = PS.Grid.count;\r
+\r
+ for ( i = 0; i < cnt; i += 1 )\r
+ {\r
+ bead = beads[i];\r
+ if ( bead.visible )\r
+ {\r
+ PS.DrawBead(bead, ctx);\r
+ }\r
+ else\r
+ {\r
+ PS.EraseBead(bead, ctx);\r
+ }\r
+ }\r
+};\r
+\r
+// Returns true if x parameter is valid, else false\r
+\r
+PS.CheckX = function ( x, fn )\r
+{\r
+ "use strict";\r
+\r
+ if ( typeof x !== "number" )\r
+ {\r
+ PS.Oops(fn + "x parameter not a number");\r
+ return false;\r
+ }\r
+ x = Math.floor(x);\r
+ if ( x < 0 )\r
+ {\r
+ PS.Oops(fn + "x parameter negative");\r
+ return false;\r
+ }\r
+ if ( x >= PS.Grid.x )\r
+ {\r
+ PS.Oops(fn + "x parameter exceeds grid width");\r
+ return false;\r
+ }\r
+\r
+ return true;\r
+};\r
+\r
+// Returns true if y parameter is valid, else false\r
+\r
+PS.CheckY = function ( y, fn )\r
+{\r
+ "use strict";\r
+\r
+ if ( typeof y !== "number" )\r
+ {\r
+ PS.Oops(fn + "y parameter not a number");\r
+ return false;\r
+ }\r
+ y = Math.floor(y);\r
+ if ( y < 0 )\r
+ {\r
+ PS.Oops(fn + "y parameter negative");\r
+ return false;\r
+ }\r
+ if ( y >= PS.Grid.y )\r
+ {\r
+ PS.Oops(fn + "y parameter exceeds grid height");\r
+ return false;\r
+ }\r
+\r
+ return true;\r
+};\r
+\r
+// PS.GetBead(x, y)\r
+// Takes x/y coords of bead and function name\r
+// Returns the bead object at (x, y), or null if error\r
+\r
+PS.GetBead = function (x, y, fn)\r
+{\r
+ "use strict";\r
+ var i;\r
+\r
+ if ( !PS.CheckX( x, fn ) || !PS.CheckY( y, fn ) )\r
+ {\r
+ return null;\r
+ }\r
+\r
+ i = x + (y * PS.Grid.x); // get index of bead\r
+\r
+ return PS.Grid.beads[i];\r
+};\r
+\r
+// API Functions\r
+\r
+PS.GridSize = function (w, h)\r
+{\r
+ "use strict";\r
+ var fn, i, cnt, beads, cv;\r
+\r
+ fn = "[PS.GridSize] ";\r
+\r
+ if ( typeof w !== "number" )\r
+ {\r
+ PS.Oops(fn + "Width param not a number");\r
+ return;\r
+ }\r
+ if ( typeof h !== "number" )\r
+ {\r
+ PS.Oops(fn + "Height param not a number");\r
+ return;\r
+ }\r
+\r
+ w = Math.floor(w);\r
+ if ( w === PS.DEFAULT )\r
+ {\r
+ w = PS.GRID_DEFAULT_WIDTH;\r
+ }\r
+ else if ( w < 1 )\r
+ {\r
+ PS.Oops(fn + "width parameter < 1");\r
+ w = 1;\r
+ }\r
+ else if ( w > PS.GRID_MAX )\r
+ {\r
+ PS.Oops(fn + "width parameter > " + PS.GRID_MAX);\r
+ w = PS.GRID_MAX;\r
+ }\r
+\r
+ h = Math.floor(h);\r
+ if ( h === PS.DEFAULT )\r
+ {\r
+ h = PS.GRID_DEFAULT_HEIGHT;\r
+ }\r
+ else if ( h < 1 )\r
+ {\r
+ PS.Oops(fn + "height parameter < 1");\r
+ h = 1;\r
+ }\r
+ else if ( h > PS.GRID_MAX )\r
+ {\r
+ PS.Oops(fn + "height parameter > " + PS.GRID_MAX);\r
+ h = PS.GRID_MAX;\r
+ }\r
+\r
+ // If a grid already exists, null out its arrays and then itself\r
+\r
+ if ( PS.Grid )\r
+ {\r
+ beads = PS.Grid.beads;\r
+ if ( beads )\r
+ {\r
+ cnt = PS.Grid.count;\r
+ for ( i = 0; i < cnt; i += 1 )\r
+ {\r
+ beads[i] = null;\r
+ }\r
+ }\r
+\r
+ PS.Grid.beads = null;\r
+ PS.Grid.flashList = null;\r
+ PS.Grid = null;\r
+ }\r
+\r
+ PS.Grid = PS.InitGrid(w, h);\r
+ \r
+ // Reset mouse coordinates\r
+ \r
+ PS.MouseX = -1;\r
+ PS.MouseY = -1;\r
+ PS.LastX = -1;\r
+ PS.LastY = -1;\r
+\r
+ // Erase the canvas\r
+\r
+ if ( PS.Grid )\r
+ {\r
+ cv = document.getElementById("screen");\r
+ if ( cv )\r
+ {\r
+ cv.height = PS.Grid.height; // setting height erases canvas\r
+ PS.DrawGrid();\r
+ }\r
+ }\r
+};\r
+\r
+PS.GridBGColor = function ( rgb )\r
+{\r
+ "use strict";\r
+ var fn, current, colors, e;\r
+\r
+ fn = "[PS.GridBGColor] ";\r
+ current = (PS.Grid.bgRed * PS.REDSHIFT) + (PS.Grid.bgGreen * 256) + PS.Grid.bgBlue;\r
+\r
+ // if param or PS.CURRENT, just return current color\r
+\r
+ if ( (rgb === undefined) || (rgb === PS.CURRENT) )\r
+ {\r
+ return current;\r
+ }\r
+\r
+ if ( rgb === PS.DEFAULT )\r
+ {\r
+ rgb = PS.DEFAULT_BG_COLOR;\r
+ }\r
+ else\r
+ {\r
+ rgb = PS.ValidRGB( rgb, fn );\r
+ if ( rgb < 0 )\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ }\r
+\r
+ colors = PS.UnmakeRGB(rgb);\r
+ if ( colors )\r
+ {\r
+ PS.Grid.bgRed = colors.r;\r
+ PS.Grid.bgGreen = colors.g;\r
+ PS.Grid.bgBlue = colors.b;\r
+ PS.Grid.bgColor = PS.RGBString(colors.r, colors.g, colors.b);\r
+\r
+ // Reset browser background\r
+\r
+ e = document.body;\r
+ e.style.backgroundColor = PS.Grid.bgColor;\r
+ \r
+ // reset status line background\r
+\r
+ e = document.getElementById("status");\r
+ if ( e )\r
+ {\r
+ e.style.backgroundColor = PS.Grid.bgColor;\r
+ }\r
+\r
+ // redraw canvas\r
+\r
+ e = document.getElementById("screen");\r
+ if ( e )\r
+ {\r
+ e.width = PS.CANVAS_SIZE; // setting width erases\r
+ PS.DrawGrid();\r
+ }\r
+ }\r
+\r
+ return rgb;\r
+};\r
+\r
+// PS.MakeRGB (r, g, b)\r
+// Takes three colors and returns multiplexed rgb value, or 0 (black) if error\r
+\r
+PS.MakeRGB = function (r, g, b)\r
+{\r
+ "use strict";\r
+ var fn, rgb;\r
+\r
+ fn = "[PS.MakeRGB] ";\r
+\r
+ if ( typeof r !== "number" )\r
+ {\r
+ PS.Oops(fn + "R parameter not a number");\r
+ return PS.ERROR;\r
+ }\r
+ r = Math.floor(r);\r
+ if ( r < 0 )\r
+ {\r
+ r = 0;\r
+ }\r
+ else if ( r > 255 )\r
+ {\r
+ r = 255;\r
+ }\r
+\r
+ if ( typeof g !== "number" )\r
+ {\r
+ PS.Oops(fn + "G parameter not a number");\r
+ return PS.ERROR;\r
+ }\r
+ g = Math.floor(g);\r
+ if ( g < 0 )\r
+ {\r
+ g = 0;\r
+ }\r
+ else if ( g > 255 )\r
+ {\r
+ g = 255;\r
+ }\r
+\r
+ if ( typeof b !== "number" )\r
+ {\r
+ PS.Oops(fn + "B parameter not a number");\r
+ return PS.ERROR;\r
+ }\r
+ b = Math.floor(b);\r
+ if ( b < 0 )\r
+ {\r
+ b = 0;\r
+ }\r
+ else if ( b > 255 )\r
+ {\r
+ b = 255;\r
+ }\r
+\r
+ rgb = (r * PS.REDSHIFT) + (g * 256) + b;\r
+\r
+ return rgb;\r
+};\r
+\r
+// Bead API\r
+\r
+// PS.BeadShow(x, y, flag)\r
+// Returns a bead's display status and optionally changes it\r
+// [x, y] are grid position\r
+// Optional [flag] must be 1/true or 0/false\r
+\r
+PS.DoBeadShow = function (x, y, flag)\r
+{\r
+ "use strict";\r
+ var i, bead;\r
+\r
+ // Assume x/y params are already verified\r
+\r
+ i = x + (y * PS.Grid.x); // get index of bead\r
+ bead = PS.Grid.beads[i];\r
+\r
+ if ( (flag === undefined) || (flag === PS.CURRENT) || (flag === bead.visible) )\r
+ {\r
+ return bead.visible;\r
+ }\r
+\r
+ bead.visible = flag;\r
+ if ( flag )\r
+ {\r
+ if ( PS.Grid.flash && bead.flash )\r
+ {\r
+ PS.FlashStart(x, y);\r
+ }\r
+ else\r
+ {\r
+ bead.colorNow = bead.color;\r
+ PS.DrawBead(bead);\r
+ }\r
+ }\r
+ else\r
+ {\r
+ PS.EraseBead(bead);\r
+ }\r
+\r
+ return flag;\r
+};\r
+\r
+PS.BeadShow = function (x, y, flag)\r
+{\r
+ "use strict";\r
+ var fn, i, j;\r
+\r
+ fn = "[PS.BeadShow] ";\r
+\r
+ // normalize flag value to t/f if defined\r
+\r
+ if ( (flag !== undefined) && (flag !== PS.CURRENT) )\r
+ {\r
+ if ( flag === PS.DEFAULT )\r
+ {\r
+ flag = true;\r
+ }\r
+ else if ( flag )\r
+ {\r
+ flag = true;\r
+ }\r
+ else\r
+ {\r
+ flag = false;\r
+ }\r
+ }\r
+\r
+ if ( x === PS.ALL )\r
+ {\r
+ if ( y === PS.ALL ) // do entire grid\r
+ {\r
+ for ( j = 0; j < PS.Grid.y; j += 1 )\r
+ {\r
+ for ( i = 0; i < PS.Grid.x; i += 1 )\r
+ {\r
+ flag = PS.DoBeadShow( i, j, flag );\r
+ }\r
+ }\r
+ }\r
+ else if ( !PS.CheckY( y, fn ) ) // verify y param\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ else\r
+ {\r
+ for ( i = 0; i < PS.Grid.x; i += 1 ) // do entire row\r
+ {\r
+ flag = PS.DoBeadShow( i, y, flag );\r
+ }\r
+ }\r
+ }\r
+ else if ( y === PS.ALL )\r
+ {\r
+ if ( !PS.CheckX( x, fn ) ) // verify x param\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ for ( j = 0; j < PS.Grid.y; j += 1 ) // do entire column\r
+ {\r
+ flag = PS.DoBeadShow( x, j, flag );\r
+ }\r
+ }\r
+ else if ( !PS.CheckX( x, fn ) || !PS.CheckY( y, fn ) ) // verify both params\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ else\r
+ {\r
+ flag = PS.DoBeadShow( x, y, flag ); // do one bead\r
+ }\r
+\r
+ return flag;\r
+};\r
+\r
+// PS.BeadColor (x, y, rgb)\r
+// Returns and optionally sets a bead's color\r
+// [x, y] are grid position\r
+// Optional [rgb] must be a multiplexed rgb value (0xRRGGBB)\r
+\r
+PS.DoBeadColor = function ( x, y, rgb, r, g, b )\r
+{\r
+ "use strict";\r
+ var i, bead;\r
+\r
+ // Assume x/y params are already verified\r
+\r
+ i = x + (y * PS.Grid.x); // get index of bead\r
+ bead = PS.Grid.beads[i];\r
+\r
+ if ( (rgb === undefined) || (rgb === PS.CURRENT) ) // if no rgb or PS.CURRENT, return current color\r
+ {\r
+ return (bead.red * PS.REDSHIFT) + (bead.green * 256) + bead.blue;\r
+ }\r
+\r
+ bead.dirty = true; // mark this bead as explicitly colored\r
+\r
+ bead.red = r;\r
+ bead.green = g;\r
+ bead.blue = b;\r
+ if ( bead.alpha < PS.DEFAULT_ALPHA ) // Calc new color based on alpha\r
+ {\r
+ bead.alphaRed = PS.Dissolve( PS.Grid.bgRed, r, bead.alpha );\r
+ bead.alphaGreen = PS.Dissolve( PS.Grid.bgGreen, g, bead.alpha );\r
+ bead.alphaBlue = PS.Dissolve( PS.Grid.bgBlue, b, bead.alpha );\r
+ bead.color = PS.RGBString( bead.alphaRed, bead.alphaGreen, bead.alphaBlue );\r
+ }\r
+ else\r
+ {\r
+ bead.alphaRed = r;\r
+ bead.alphaGreen = g;\r
+ bead.alphaBlue = b;\r
+ bead.color = PS.RGBString( r, g, b );\r
+ }\r
+\r
+ if ( bead.visible )\r
+ {\r
+ if ( PS.Grid.flash && bead.flash )\r
+ {\r
+ PS.FlashStart(x, y);\r
+ }\r
+ else\r
+ {\r
+ bead.colorNow = bead.color;\r
+ PS.DrawBead(bead);\r
+ }\r
+ }\r
+\r
+ return rgb;\r
+};\r
+\r
+PS.BeadColor = function (x, y, rgb)\r
+{\r
+ "use strict";\r
+ var fn, colors, r, g, b, i, j;\r
+\r
+ fn = "[PS.BeadColor] ";\r
+\r
+ // if no rgb specified, just return current color\r
+\r
+ if ( rgb === PS.DEFAULT )\r
+ {\r
+ rgb = PS.DEFAULT_BEAD_COLOR;\r
+ r = PS.DEFAULT_BG_RED;\r
+ g = PS.DEFAULT_BG_GREEN;\r
+ b = PS.DEFAULT_BG_BLUE;\r
+ }\r
+ else if ( (rgb !== undefined) && (rgb !== PS.CURRENT) )\r
+ {\r
+ rgb = PS.ValidRGB( rgb, fn );\r
+ if ( rgb < 0 )\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ colors = PS.UnmakeRGB( rgb );\r
+ r = colors.r;\r
+ g = colors.g;\r
+ b = colors.b;\r
+ }\r
+\r
+ if ( x === PS.ALL )\r
+ {\r
+ if ( y === PS.ALL ) // do entire grid\r
+ {\r
+ for ( j = 0; j < PS.Grid.y; j += 1 )\r
+ {\r
+ for ( i = 0; i < PS.Grid.x; i += 1 )\r
+ {\r
+ rgb = PS.DoBeadColor( i, j, rgb, r, g, b );\r
+ }\r
+ }\r
+ }\r
+ else if ( !PS.CheckY( y, fn ) ) // verify y param\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ else\r
+ {\r
+ for ( i = 0; i < PS.Grid.x; i += 1 ) // do entire row\r
+ {\r
+ rgb = PS.DoBeadColor( i, y, rgb, r, g, b );\r
+ }\r
+ }\r
+ }\r
+ else if ( y === PS.ALL )\r
+ {\r
+ if ( !PS.CheckX( x, fn ) ) // verify x param\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ for ( j = 0; j < PS.Grid.y; j += 1 ) // do entire column\r
+ {\r
+ rgb = PS.DoBeadColor( x, j, rgb, r, g, b );\r
+ }\r
+ }\r
+ else if ( !PS.CheckX( x, fn ) || !PS.CheckY( y, fn ) ) // verify both params\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ else\r
+ {\r
+ rgb = PS.DoBeadColor( x, y, rgb, r, g, b ); // do one bead\r
+ }\r
+\r
+ return rgb;\r
+};\r
+\r
+// PS.BeadAlpha(x, y, a)\r
+// Returns and optionally sets a bead's alpha\r
+// [x, y] are grid position\r
+// Optional [a] must be a number between 0.0 and 1.0\r
+\r
+PS.DoBeadAlpha = function ( x, y, a )\r
+{\r
+ "use strict";\r
+ var i, bead;\r
+\r
+ // Assume x/y params are already verified\r
+\r
+ i = x + (y * PS.Grid.x); // get index of bead\r
+ bead = PS.Grid.beads[i];\r
+\r
+ if ( (a === undefined) || (a === PS.CURRENT) || (a === bead.alpha) )\r
+ {\r
+ return bead.alpha;\r
+ }\r
+\r
+ // Calc new color between background and base\r
+\r
+ bead.alpha = a;\r
+ bead.dirty = true;\r
+ if ( bead.alpha < PS.DEFAULT_ALPHA ) // Calc new color based on alpha\r
+ {\r
+ bead.alphaRed = PS.Dissolve( PS.Grid.bgRed, bead.red, a );\r
+ bead.alphaGreen = PS.Dissolve( PS.Grid.bgGreen, bead.green, a );\r
+ bead.alphaBlue = PS.Dissolve( PS.Grid.bgBlue, bead.blue, a );\r
+ bead.color = PS.RGBString( bead.alphaRed, bead.alphaGreen, bead.alphaBlue );\r
+ }\r
+ else\r
+ {\r
+ bead.alphaRed = bead.red;\r
+ bead.alphaGreen = bead.green;\r
+ bead.alphaBlue = bead.blue;\r
+ bead.color = PS.RGBString( bead.red, bead.green, bead.blue );\r
+ }\r
+ if ( bead.visible )\r
+ {\r
+ if ( PS.Grid.flash && bead.flash )\r
+ {\r
+ PS.FlashStart(x, y);\r
+ }\r
+ else\r
+ {\r
+ bead.colorNow = bead.color;\r
+ PS.DrawBead(bead);\r
+ }\r
+ }\r
+\r
+ return a;\r
+};\r
+\r
+PS.BeadAlpha = function (x, y, a)\r
+{\r
+ "use strict";\r
+ var fn, i, j;\r
+\r
+ fn = "[PS.BeadAlpha] ";\r
+\r
+ if ( a !== undefined )\r
+ {\r
+ if ( typeof a !== "number" )\r
+ {\r
+ PS.Oops(fn + "alpha param is not a number");\r
+ return PS.ERROR;\r
+ }\r
+\r
+ // clamp value\r
+\r
+ a = Math.floor(a);\r
+ if ( a === PS.DEFAULT )\r
+ {\r
+ a = PS.DEFAULT_ALPHA;\r
+ }\r
+ else if ( a !== PS.CURRENT )\r
+ {\r
+ if ( a < 0 )\r
+ {\r
+ a = 0;\r
+ }\r
+ else if ( a > PS.DEFAULT_ALPHA )\r
+ {\r
+ a = PS.DEFAULT_ALPHA;\r
+ }\r
+ }\r
+ }\r
+\r
+ if ( x === PS.ALL )\r
+ {\r
+ if ( y === PS.ALL ) // do entire grid\r
+ {\r
+ for ( j = 0; j < PS.Grid.y; j += 1 )\r
+ {\r
+ for ( i = 0; i < PS.Grid.x; i += 1 )\r
+ {\r
+ a = PS.DoBeadAlpha( i, j, a );\r
+ }\r
+ }\r
+ }\r
+ else if ( !PS.CheckY( y, fn ) ) // verify y param\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ else\r
+ {\r
+ for ( i = 0; i < PS.Grid.x; i += 1 ) // do entire row\r
+ {\r
+ a = PS.DoBeadAlpha( i, y, a );\r
+ }\r
+ }\r
+ }\r
+ else if ( y === PS.ALL )\r
+ {\r
+ if ( !PS.CheckX( x, fn ) ) // verify x param\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ for ( j = 0; j < PS.Grid.y; j += 1 ) // do entire column\r
+ {\r
+ a = PS.DoBeadAlpha( x, j, a );\r
+ }\r
+ }\r
+ else if ( !PS.CheckX( x, fn ) || !PS.CheckY( y, fn ) ) // verify both params\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ else\r
+ {\r
+ a = PS.DoBeadAlpha( x, y, a ); // do one bead\r
+ }\r
+\r
+ return a;\r
+};\r
+\r
+// PS.BeadBorderWidth (x, y, width)\r
+// Returns and optionally sets a bead's border width\r
+// [x, y] are grid position\r
+// Optional [width] must be a number\r
+\r
+PS.DoBeadBorderWidth = function (x, y, width)\r
+{\r
+ "use strict";\r
+ var i, bead;\r
+\r
+ // Assume x/y params are already verified\r
+\r
+ i = x + (y * PS.Grid.x); // get index of bead\r
+ bead = PS.Grid.beads[i];\r
+\r
+ if ( (width === undefined) || (width === PS.CURRENT) ) // if no width or PS.CURRENT, return current width\r
+ {\r
+ return bead.borderWidth;\r
+ }\r
+\r
+ bead.borderWidth = width;\r
+\r
+ if ( bead.visible )\r
+ {\r
+ PS.DrawBead(bead);\r
+ }\r
+\r
+ return width;\r
+};\r
+\r
+PS.BeadBorderWidth = function (x, y, width)\r
+{\r
+ "use strict";\r
+ var fn, i, j;\r
+\r
+ fn = "[PS.BeadBorderWidth] ";\r
+\r
+ if ( width === PS.DEFAULT )\r
+ {\r
+ width = PS.DEFAULT_BORDER_WIDTH;\r
+ }\r
+ else if ( (width !== undefined) && (width !== PS.CURRENT) )\r
+ {\r
+ if ( typeof width !== "number" )\r
+ {\r
+ PS.Oops(fn + "width param is not a number");\r
+ return PS.ERROR;\r
+ }\r
+ width = Math.floor(width);\r
+ if ( width < 0 )\r
+ {\r
+ width = 0;\r
+ }\r
+ else if ( width > PS.BORDER_WIDTH_MAX )\r
+ {\r
+ width = PS.BORDER_WIDTH_MAX;\r
+ }\r
+ }\r
+\r
+ if ( x === PS.ALL )\r
+ {\r
+ if ( y === PS.ALL ) // do entire grid\r
+ {\r
+ for ( j = 0; j < PS.Grid.y; j += 1 )\r
+ {\r
+ for ( i = 0; i < PS.Grid.x; i += 1 )\r
+ {\r
+ width = PS.DoBeadBorderWidth( i, j, width );\r
+ }\r
+ }\r
+ }\r
+ else if ( !PS.CheckY( y, fn ) ) // verify y param\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ else\r
+ {\r
+ for ( i = 0; i < PS.Grid.x; i += 1 ) // do entire row\r
+ {\r
+ width = PS.DoBeadBorderWidth( i, y, width );\r
+ }\r
+ }\r
+ }\r
+ else if ( y === PS.ALL )\r
+ {\r
+ if ( !PS.CheckX( x, fn ) ) // verify x param\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ for ( j = 0; j < PS.Grid.y; j += 1 ) // do entire column\r
+ {\r
+ width = PS.DoBeadBorderWidth( x, j, width );\r
+ }\r
+ }\r
+ else if ( !PS.CheckX( x, fn ) || !PS.CheckY( y, fn ) ) // verify both params\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ else\r
+ {\r
+ width = PS.DoBeadBorderWidth( x, y, width ); // do one bead\r
+ }\r
+\r
+ return width;\r
+};\r
+\r
+// PS.BeadBorderColor (x, y, rgb)\r
+// Returns and optionally sets a bead's border color\r
+// [x, y] are grid position\r
+// Optional [rgb] must be a multiplexed rgb value (0xRRGGBB)\r
+\r
+PS.DoBeadBorderColor = function (x, y, rgb, r, g, b)\r
+{\r
+ "use strict";\r
+ var i, bead;\r
+\r
+ // Assume x/y params are already verified\r
+\r
+ i = x + (y * PS.Grid.x); // get index of bead\r
+ bead = PS.Grid.beads[i];\r
+\r
+ if ( (rgb === undefined) || (rgb === PS.CURRENT) ) // if no rgb or PS.CURRENT, return current color\r
+ {\r
+ return (bead.borderRed * PS.REDSHIFT) + (bead.borderGreen * 256) + bead.borderBlue;\r
+ }\r
+\r
+ bead.borderRed = r;\r
+ bead.borderGreen = g;\r
+ bead.borderBlue = b;\r
+ if ( bead.borderAlpha < PS.DEFAULT_ALPHA )\r
+ {\r
+ r = PS.Dissolve( PS.Grid.bgRed, r, bead.borderAlpha );\r
+ g = PS.Dissolve( PS.Grid.bgGreen, g, bead.borderAlpha );\r
+ b = PS.Dissolve( PS.Grid.bgBlue, b, bead.borderAlpha );\r
+ }\r
+ bead.borderColor = PS.RGBString( r, g, b );\r
+\r
+ if ( bead.visible )\r
+ {\r
+ PS.DrawBead(bead);\r
+ }\r
+\r
+ return rgb;\r
+};\r
+\r
+PS.BeadBorderColor = function (x, y, rgb)\r
+{\r
+ "use strict";\r
+ var fn, colors, r, g, b, i, j;\r
+\r
+ fn = "[PS.BeadBorderColor] ";\r
+\r
+ if ( rgb === PS.DEFAULT )\r
+ {\r
+ rgb = PS.DEFAULT_BORDER_COLOR;\r
+ r = PS.DEFAULT_BORDER_RED;\r
+ g = PS.DEFAULT_BORDER_GREEN;\r
+ b = PS.DEFAULT_BORDER_BLUE;\r
+ }\r
+ else if ( (rgb !== undefined) && (rgb !== PS.CURRENT) )\r
+ {\r
+ rgb = PS.ValidRGB( rgb, fn );\r
+ if ( rgb < 0 )\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ colors = PS.UnmakeRGB( rgb );\r
+ r = colors.r;\r
+ g = colors.g;\r
+ b = colors.b;\r
+ }\r
+\r
+ if ( x === PS.ALL )\r
+ {\r
+ if ( y === PS.ALL ) // do entire grid\r
+ {\r
+ for ( j = 0; j < PS.Grid.y; j += 1 )\r
+ {\r
+ for ( i = 0; i < PS.Grid.x; i += 1 )\r
+ {\r
+ rgb = PS.DoBeadBorderColor( i, j, rgb, r, g, b );\r
+ }\r
+ }\r
+ }\r
+ else if ( !PS.CheckY( y, fn ) ) // verify y param\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ else\r
+ {\r
+ for ( i = 0; i < PS.Grid.x; i += 1 ) // do entire row\r
+ {\r
+ rgb = PS.DoBeadBorderColor( i, y, rgb, r, g, b );\r
+ }\r
+ }\r
+ }\r
+ else if ( y === PS.ALL )\r
+ {\r
+ if ( !PS.CheckX( x, fn ) ) // verify x param\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ for ( j = 0; j < PS.Grid.y; j += 1 ) // do entire column\r
+ {\r
+ rgb = PS.DoBeadBorderColor( x, j, rgb, r, g, b );\r
+ }\r
+ }\r
+ else if ( !PS.CheckX( x, fn ) || !PS.CheckY( y, fn ) ) // verify both params\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ else\r
+ {\r
+ rgb = PS.DoBeadBorderColor( x, y, rgb, r, g, b ); // do one bead\r
+ }\r
+\r
+ return rgb;\r
+};\r
+\r
+// PS.BeadBorderAlpha(x, y, a)\r
+// Returns a bead's border alpha and optionally changes it\r
+// [x, y] are grid position\r
+// Optional [a] must be a number between 0.0 and 1.0\r
+\r
+PS.DoBeadBorderAlpha = function (x, y, a)\r
+{\r
+ "use strict";\r
+ var i, bead, r, g, b;\r
+\r
+ // Assume x/y params are already verified\r
+\r
+ i = x + (y * PS.Grid.x); // get index of bead\r
+ bead = PS.Grid.beads[i];\r
+\r
+ if ( (a === undefined) || (a === PS.CURRENT) || (a === bead.borderAlpha) )\r
+ {\r
+ return bead.borderAlpha;\r
+ }\r
+\r
+ bead.borderAlpha = a;\r
+ if ( a < PS.DEFAULT_ALPHA )\r
+ {\r
+ r = PS.Dissolve( PS.Grid.bgRed, bead.borderRed, a );\r
+ g = PS.Dissolve( PS.Grid.bgGreen, bead.borderGreen, a );\r
+ b = PS.Dissolve( PS.Grid.bgBlue, bead.borderBlue, a );\r
+ bead.borderColor = PS.RGBString( r, g, b );\r
+ }\r
+ else\r
+ {\r
+ bead.borderColor = PS.RGBString( bead.borderRed, bead.borderGreen, bead.borderBlue );\r
+ }\r
+ if ( bead.visible )\r
+ {\r
+ PS.DrawBead(bead);\r
+ }\r
+ return a;\r
+};\r
+\r
+PS.BeadBorderAlpha = function (x, y, a)\r
+{\r
+ "use strict";\r
+ var fn, i, j;\r
+\r
+ fn = "[PS.BeadBorderAlpha] ";\r
+\r
+ if ( a !== undefined )\r
+ {\r
+ if ( typeof a !== "number" )\r
+ {\r
+ PS.Oops(fn + "alpha param is not a number");\r
+ return PS.ERROR;\r
+ }\r
+\r
+ // clamp value\r
+\r
+ a = Math.floor(a);\r
+ if ( a === PS.DEFAULT )\r
+ {\r
+ a = PS.DEFAULT_ALPHA;\r
+ }\r
+ else if ( a !== PS.CURRENT )\r
+ {\r
+ if ( a < 0 )\r
+ {\r
+ a = 0;\r
+ }\r
+ else if ( a > PS.DEFAULT_ALPHA )\r
+ {\r
+ a = PS.DEFAULT_ALPHA;\r
+ }\r
+ }\r
+ }\r
+\r
+ if ( x === PS.ALL )\r
+ {\r
+ if ( y === PS.ALL ) // do entire grid\r
+ {\r
+ for ( j = 0; j < PS.Grid.y; j += 1 )\r
+ {\r
+ for ( i = 0; i < PS.Grid.x; i += 1 )\r
+ {\r
+ a = PS.DoBeadBorderAlpha( i, j, a );\r
+ }\r
+ }\r
+ }\r
+ else if ( !PS.CheckY( y, fn ) ) // verify y param\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ else\r
+ {\r
+ for ( i = 0; i < PS.Grid.x; i += 1 ) // do entire row\r
+ {\r
+ a = PS.DoBeadBorderAlpha( i, y, a );\r
+ }\r
+ }\r
+ }\r
+ else if ( y === PS.ALL )\r
+ {\r
+ if ( !PS.CheckX( x, fn ) ) // verify x param\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ for ( j = 0; j < PS.Grid.y; j += 1 ) // do entire column\r
+ {\r
+ a = PS.DoBeadBorderAlpha( x, j, a );\r
+ }\r
+ }\r
+ else if ( !PS.CheckX( x, fn ) || !PS.CheckY( y, fn ) ) // verify both params\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ else\r
+ {\r
+ a = PS.DoBeadBorderAlpha( x, y, a ); // do one bead\r
+ }\r
+\r
+ return a;\r
+};\r
+\r
+// PS.BeadGlyph(x, y, g)\r
+// Returns a bead's glyph and optionally changes it\r
+// [x, y] are grid position\r
+// Optional [g] must be either a string or a number\r
+\r
+PS.DoBeadGlyph = function (x, y, g)\r
+{\r
+ "use strict";\r
+ var i, bead;\r
+\r
+ // Assume x/y params are already verified\r
+\r
+ i = x + (y * PS.Grid.x); // get index of bead\r
+ bead = PS.Grid.beads[i];\r
+\r
+ if ( (g === undefined) || (g === PS.CURRENT) || (g === bead.glyph) )\r
+ {\r
+ return bead.glyph;\r
+ }\r
+\r
+ bead.glyph = g;\r
+ bead.glyphStr = String.fromCharCode(g);\r
+ if ( bead.visible )\r
+ {\r
+ if ( PS.Grid.flash && bead.flash )\r
+ {\r
+ PS.FlashStart(x, y);\r
+ }\r
+ else\r
+ {\r
+ bead.colorNow = bead.color;\r
+ PS.DrawBead(bead);\r
+ }\r
+ }\r
+\r
+ return g;\r
+};\r
+\r
+PS.BeadGlyph = function (x, y, g)\r
+{\r
+ "use strict";\r
+ var fn, type, i, j;\r
+\r
+ fn = "[PS.BeadGlyph] ";\r
+\r
+ // if no glyph specified, just return current border status\r
+\r
+ type = typeof g;\r
+ if ( type !== "undefined" )\r
+ {\r
+ if ( type === "string" )\r
+ {\r
+ if ( g.length < 1 )\r
+ {\r
+ PS.Oops(fn + "glyph param is empty string");\r
+ return 0;\r
+ }\r
+ g = g.charCodeAt(0); // use only first character\r
+ }\r
+ else if ( type === "number" )\r
+ {\r
+ g = Math.floor(g);\r
+ if ( g === PS.DEFAULT )\r
+ {\r
+ g = 0;\r
+ }\r
+ else if ( g !== PS.CURRENT )\r
+ {\r
+ if ( g < 0 )\r
+ {\r
+ g = 0;\r
+ }\r
+ }\r
+ }\r
+ else\r
+ {\r
+ PS.Oops(fn + "glyph param not a string or number");\r
+ return PS.ERROR;\r
+ }\r
+ }\r
+\r
+ if ( x === PS.ALL )\r
+ {\r
+ if ( y === PS.ALL ) // do entire grid\r
+ {\r
+ for ( j = 0; j < PS.Grid.y; j += 1 )\r
+ {\r
+ for ( i = 0; i < PS.Grid.x; i += 1 )\r
+ {\r
+ g = PS.DoBeadGlyph( i, j, g );\r
+ }\r
+ }\r
+ }\r
+ else if ( !PS.CheckY( y, fn ) ) // verify y param\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ else\r
+ {\r
+ for ( i = 0; i < PS.Grid.x; i += 1 ) // do entire row\r
+ {\r
+ g = PS.DoBeadGlyph( i, y, g );\r
+ }\r
+ }\r
+ }\r
+ else if ( y === PS.ALL )\r
+ {\r
+ if ( !PS.CheckX( x, fn ) ) // verify x param\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ for ( j = 0; j < PS.Grid.y; j += 1 ) // do entire column\r
+ {\r
+ g = PS.DoBeadGlyph( x, j, g );\r
+ }\r
+ }\r
+ else if ( !PS.CheckX( x, fn ) || !PS.CheckY( y, fn ) ) // verify both params\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ else\r
+ {\r
+ g = PS.DoBeadGlyph( x, y, g ); // do one bead\r
+ }\r
+\r
+ return g;\r
+};\r
+\r
+// PS.BeadGlyphColor (x, y, rgb)\r
+// Returns and optionally sets a bead's glyph color\r
+// [x, y] are grid position\r
+// Optional [rgb] must be a multiplexed rgb value (0xRRGGBB)\r
+\r
+PS.DoBeadGlyphColor = function (x, y, rgb, r, g, b)\r
+{\r
+ "use strict";\r
+ var i, bead;\r
+\r
+ // Assume x/y params are already verified\r
+\r
+ i = x + (y * PS.Grid.x); // get index of bead\r
+ bead = PS.Grid.beads[i];\r
+\r
+ if ( (rgb === undefined) || (rgb === PS.CURRENT) ) // if no rgb or PS.CURRENT, return current color\r
+ {\r
+ return (bead.glyphRed * PS.REDSHIFT) + (bead.glyphGreen * 256) + bead.glyphBlue;\r
+ }\r
+\r
+ bead.glyphRed = r;\r
+ bead.glyphGreen = g;\r
+ bead.glyphBlue = b;\r
+ if ( bead.alpha < PS.DEFAULT_ALPHA ) // Calc new color based on alpha\r
+ {\r
+ r = PS.Dissolve( PS.Grid.bgRed, r, bead.alpha );\r
+ g = PS.Dissolve( PS.Grid.bgGreen, g, bead.alpha );\r
+ b = PS.Dissolve( PS.Grid.bgBlue, b, bead.alpha );\r
+ }\r
+ bead.glyphColor = PS.RGBString( r, g, b );\r
+\r
+ if ( bead.visible && (bead.glyph > 0) )\r
+ {\r
+ PS.DrawBead(bead);\r
+ }\r
+ return rgb;\r
+};\r
+\r
+PS.BeadGlyphColor = function (x, y, rgb)\r
+{\r
+ "use strict";\r
+ var fn, colors, r, g, b, i, j;\r
+\r
+ fn = "[PS.BeadGlyphColor] ";\r
+\r
+ if ( rgb === PS.DEFAULT )\r
+ {\r
+ rgb = PS.DEFAULT_GLYPH_COLOR;\r
+ r = PS.DEFAULT_GLYPH_RED;\r
+ g = PS.DEFAULT_GLYPH_GREEN;\r
+ b = PS.DEFAULT_GLYPH_BLUE;\r
+ }\r
+ else if ( (rgb !== undefined) && (rgb !== PS.CURRENT) )\r
+ {\r
+ rgb = PS.ValidRGB( rgb, fn );\r
+ if ( rgb < 0 )\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ colors = PS.UnmakeRGB( rgb );\r
+ r = colors.r;\r
+ g = colors.g;\r
+ b = colors.b;\r
+ }\r
+\r
+ if ( x === PS.ALL )\r
+ {\r
+ if ( y === PS.ALL ) // do entire grid\r
+ {\r
+ for ( j = 0; j < PS.Grid.y; j += 1 )\r
+ {\r
+ for ( i = 0; i < PS.Grid.x; i += 1 )\r
+ {\r
+ rgb = PS.DoBeadGlyphColor( i, j, rgb, r, g, b );\r
+ }\r
+ }\r
+ }\r
+ else if ( !PS.CheckY( y, fn ) ) // verify y param\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ else\r
+ {\r
+ for ( i = 0; i < PS.Grid.x; i += 1 ) // do entire row\r
+ {\r
+ rgb = PS.DoBeadGlyphColor( i, y, rgb, r, g, b );\r
+ }\r
+ }\r
+ }\r
+ else if ( y === PS.ALL )\r
+ {\r
+ if ( !PS.CheckX( x, fn ) ) // verify x param\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ for ( j = 0; j < PS.Grid.y; j += 1 ) // do entire column\r
+ {\r
+ rgb = PS.DoBeadGlyphColor( x, j, rgb, r, g, b );\r
+ }\r
+ }\r
+ else if ( !PS.CheckX( x, fn ) || !PS.CheckY( y, fn ) ) // verify both params\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ else\r
+ {\r
+ rgb = PS.DoBeadGlyphColor( x, y, rgb, r, g, b ); // do one bead\r
+ }\r
+\r
+ return rgb;\r
+};\r
+\r
+// PS.BeadFlash(x, y, flag)\r
+// Returns a bead's flash status and optionally changes it\r
+// [x, y] are grid position\r
+// Optional [flag] must be 1/true or 0/false\r
+\r
+PS.DoBeadFlash = function (x, y, flag)\r
+{\r
+ "use strict";\r
+ var i, bead;\r
+\r
+ // Assume x/y params are already verified\r
+\r
+ i = x + (y * PS.Grid.x); // get index of bead\r
+ bead = PS.Grid.beads[i];\r
+\r
+ if ( (flag === undefined) || (flag === PS.CURRENT) )\r
+ {\r
+ return bead.flash;\r
+ }\r
+\r
+ bead.flash = flag;\r
+ return flag;\r
+};\r
+\r
+PS.BeadFlash = function (x, y, flag)\r
+{\r
+ "use strict";\r
+ var fn, i, j;\r
+\r
+ fn = "[PS.BeadFlash] ";\r
+\r
+ // normalize flag value to t/f if defined\r
+\r
+ if ( (flag !== undefined) && (flag !== PS.CURRENT) )\r
+ {\r
+ if ( flag === PS.DEFAULT )\r
+ {\r
+ flag = true;\r
+ }\r
+ else if ( flag )\r
+ {\r
+ flag = true;\r
+ }\r
+ else\r
+ {\r
+ flag = false;\r
+ }\r
+ }\r
+\r
+ if ( x === PS.ALL )\r
+ {\r
+ if ( y === PS.ALL ) // do entire grid\r
+ {\r
+ for ( j = 0; j < PS.Grid.y; j += 1 )\r
+ {\r
+ for ( i = 0; i < PS.Grid.x; i += 1 )\r
+ {\r
+ flag = PS.DoBeadFlash( i, j, flag );\r
+ }\r
+ }\r
+ }\r
+ else if ( !PS.CheckY( y, fn ) ) // verify y param\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ else\r
+ {\r
+ for ( i = 0; i < PS.Grid.x; i += 1 ) // do entire row\r
+ {\r
+ flag = PS.DoBeadFlash( i, y, flag );\r
+ }\r
+ }\r
+ }\r
+ else if ( y === PS.ALL )\r
+ {\r
+ if ( !PS.CheckX( x, fn ) ) // verify x param\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ for ( j = 0; j < PS.Grid.y; j += 1 ) // do entire column\r
+ {\r
+ flag = PS.DoBeadFlash( x, j, flag );\r
+ }\r
+ }\r
+ else if ( !PS.CheckX( x, fn ) || !PS.CheckY( y, fn ) ) // verify both params\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ else\r
+ {\r
+ flag = PS.DoBeadFlash( x, y, flag ); // do one bead\r
+ }\r
+\r
+ return flag;\r
+};\r
+\r
+// PS.BeadFlashColor (x, y, rgb)\r
+// Returns and optionally sets a bead's flash color\r
+// [x, y] are grid position\r
+// Optional [rgb] must be a multiplexed rgb value (0xRRGGBB)\r
+\r
+PS.DoBeadFlashColor = function (x, y, rgb, r, g, b)\r
+{\r
+ "use strict";\r
+ var i, bead;\r
+\r
+ // Assume x/y params are already verified\r
+\r
+ i = x + (y * PS.Grid.x); // get index of bead\r
+ bead = PS.Grid.beads[i];\r
+\r
+ if ( (rgb === undefined) || (rgb === PS.CURRENT) ) // if no rgb or PS.CURRENT, return current color\r
+ {\r
+ return (bead.flashRed * PS.REDSHIFT) + (bead.flashGreen * 256) + bead.flashBlue;\r
+ }\r
+\r
+ bead.flashRed = r;\r
+ bead.flashGreen = g;\r
+ bead.flashBlue = b;\r
+ bead.flashColor = PS.RGBString(r, g, b);\r
+\r
+ return rgb;\r
+};\r
+\r
+PS.BeadFlashColor = function (x, y, rgb)\r
+{\r
+ "use strict";\r
+ var fn, r, g, b, colors, i, j;\r
+\r
+ fn = "[PS.BeadFlashColor] ";\r
+\r
+ if ( rgb === PS.DEFAULT )\r
+ {\r
+ rgb = PS.DEFAULT_FLASH_COLOR;\r
+ r = PS.DEFAULT_FLASH_RED;\r
+ g = PS.DEFAULT_FLASH_GREEN;\r
+ b = PS.DEFAULT_FLASH_BLUE;\r
+ }\r
+ else if ( (rgb !== undefined) && (rgb !== PS.CURRENT) )\r
+ {\r
+ rgb = PS.ValidRGB( rgb, fn );\r
+ if ( rgb < 0 )\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ colors = PS.UnmakeRGB( rgb );\r
+ r = colors.r;\r
+ g = colors.g;\r
+ b = colors.b;\r
+ }\r
+\r
+ if ( x === PS.ALL )\r
+ {\r
+ if ( y === PS.ALL ) // do entire grid\r
+ {\r
+ for ( j = 0; j < PS.Grid.y; j += 1 )\r
+ {\r
+ for ( i = 0; i < PS.Grid.x; i += 1 )\r
+ {\r
+ rgb = PS.DoBeadFlashColor( i, j, rgb, r, g, b );\r
+ }\r
+ }\r
+ }\r
+ else if ( !PS.CheckY( y, fn ) ) // verify y param\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ else\r
+ {\r
+ for ( i = 0; i < PS.Grid.x; i += 1 ) // do entire row\r
+ {\r
+ rgb = PS.DoBeadFlashColor( i, y, rgb, r, g, b );\r
+ }\r
+ }\r
+ }\r
+ else if ( y === PS.ALL )\r
+ {\r
+ if ( !PS.CheckX( x, fn ) ) // verify x param\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ for ( j = 0; j < PS.Grid.y; j += 1 ) // do entire column\r
+ {\r
+ rgb = PS.DoBeadFlashColor( x, j, rgb, r, g, b );\r
+ }\r
+ }\r
+ else if ( !PS.CheckX( x, fn ) || !PS.CheckY( y, fn ) ) // verify both params\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ else\r
+ {\r
+ rgb = PS.DoBeadFlashColor( x, y, rgb, r, g, b ); // do one bead\r
+ }\r
+\r
+ return rgb;\r
+};\r
+\r
+// PS.BeadData(x, y, data)\r
+// Returns a bead's data and optionally changes it\r
+// [x, y] are grid position\r
+// Optional [data] can be any data type\r
+\r
+PS.DoBeadData = function (x, y, data)\r
+{\r
+ "use strict";\r
+ var i, bead;\r
+\r
+ // Assume x/y params are already verified\r
+\r
+ i = x + (y * PS.Grid.x); // get index of bead\r
+ bead = PS.Grid.beads[i];\r
+\r
+ if ( data !== undefined )\r
+ {\r
+ bead.data = data;\r
+ }\r
+\r
+ return bead.data;\r
+};\r
+\r
+PS.BeadData = function (x, y, data)\r
+{\r
+ "use strict";\r
+ var fn, i, j;\r
+\r
+ fn = "[PS.BeadData] ";\r
+\r
+ if ( x === PS.ALL )\r
+ {\r
+ if ( y === PS.ALL ) // do entire grid\r
+ {\r
+ for ( j = 0; j < PS.Grid.y; j += 1 )\r
+ {\r
+ for ( i = 0; i < PS.Grid.x; i += 1 )\r
+ {\r
+ data = PS.DoBeadData( i, j, data );\r
+ }\r
+ }\r
+ }\r
+ else if ( !PS.CheckY( y, fn ) ) // verify y param\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ else\r
+ {\r
+ for ( i = 0; i < PS.Grid.x; i += 1 ) // do entire row\r
+ {\r
+ data = PS.DoBeadData( i, y, data );\r
+ }\r
+ }\r
+ }\r
+ else if ( y === PS.ALL )\r
+ {\r
+ if ( !PS.CheckX( x, fn ) ) // verify x param\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ for ( j = 0; j < PS.Grid.y; j += 1 ) // do entire column\r
+ {\r
+ data = PS.DoBeadData( x, j, data );\r
+ }\r
+ }\r
+ else if ( !PS.CheckX( x, fn ) || !PS.CheckY( y, fn ) ) // verify both params\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ else\r
+ {\r
+ data = PS.DoBeadData( x, y, data ); // do one bead\r
+ }\r
+\r
+ return data;\r
+};\r
+\r
+// PS.BeadAudio(x, y, audio, volume)\r
+// Returns a bead's audio file and optionally changes it (and its volume)\r
+// [x, y] are grid position\r
+// Optional [audio] must be a string\r
+// Optional [volume] should be between 0 and 100 inclusive\r
+\r
+PS.DoBeadAudio = function (x, y, audio, volume)\r
+{\r
+ "use strict";\r
+ var i, bead;\r
+\r
+ // Assume x/y params are already verified\r
+\r
+ i = x + (y * PS.Grid.x); // get index of bead\r
+ bead = PS.Grid.beads[i];\r
+\r
+ if ( (audio !== undefined) && (audio !== PS.CURRENT) )\r
+ {\r
+ bead.audio = audio;\r
+ }\r
+\r
+ if ( (volume !== undefined) && (volume !== PS.CURRENT) )\r
+ {\r
+ bead.volume = volume;\r
+ }\r
+\r
+ return bead.audio;\r
+};\r
+\r
+PS.BeadAudio = function (x, y, audio, volume)\r
+{\r
+ "use strict";\r
+ var fn, i, j;\r
+\r
+ fn = "[PS.BeadAudio] ";\r
+\r
+ // check audio file param\r
+\r
+ if ( (audio !== undefined) && (audio !== PS.CURRENT) )\r
+ {\r
+ if ( audio === PS.DEFAULT )\r
+ {\r
+ audio = null;\r
+ }\r
+ else if ( typeof audio !== "string" )\r
+ {\r
+ PS.Oops(fn + "audio param is not a string");\r
+ return PS.ERROR;\r
+ }\r
+ else if ( audio.length < 1 )\r
+ {\r
+ audio = null;\r
+ }\r
+ }\r
+\r
+ // check volume param\r
+\r
+ if ( (volume !== undefined) && (volume !== PS.CURRENT) )\r
+ {\r
+ if ( volume === PS.DEFAULT )\r
+ {\r
+ volume = PS.DEFAULT_VOLUME;\r
+ }\r
+ else if ( typeof volume !== "number" )\r
+ {\r
+ PS.Oops(fn + "volume param is not a number");\r
+ return PS.ERROR;\r
+ }\r
+ else\r
+ {\r
+ if ( volume < 0 )\r
+ {\r
+ volume = 0;\r
+ }\r
+ else if ( volume > PS.DEFAULT_VOLUME )\r
+ {\r
+ volume = PS.DEFAULT_VOLUME;\r
+ }\r
+ }\r
+ }\r
+ \r
+ if ( x === PS.ALL )\r
+ {\r
+ if ( y === PS.ALL ) // do entire grid\r
+ {\r
+ for ( j = 0; j < PS.Grid.y; j += 1 )\r
+ {\r
+ for ( i = 0; i < PS.Grid.x; i += 1 )\r
+ {\r
+ audio = PS.DoBeadAudio( i, j, audio, volume );\r
+ }\r
+ }\r
+ }\r
+ else if ( !PS.CheckY( y, fn ) ) // verify y param\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ else\r
+ {\r
+ for ( i = 0; i < PS.Grid.x; i += 1 ) // do entire row\r
+ {\r
+ audio = PS.DoBeadAudio( i, y, audio, volume );\r
+ }\r
+ }\r
+ }\r
+ else if ( y === PS.ALL )\r
+ {\r
+ if ( !PS.CheckX( x, fn ) ) // verify x param\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ for ( j = 0; j < PS.Grid.y; j += 1 ) // do entire column\r
+ {\r
+ audio = PS.DoBeadAudio( x, j, audio, volume );\r
+ }\r
+ }\r
+ else if ( !PS.CheckX( x, fn ) || !PS.CheckY( y, fn ) ) // verify both params\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ else\r
+ {\r
+ audio = PS.DoBeadAudio( x, y, audio, volume ); // do one bead\r
+ }\r
+\r
+ return audio;\r
+};\r
+\r
+// PS.BeadFunction(x, y, func)\r
+// Returns a bead's exec function and optionally changes it\r
+// [x, y] are grid position\r
+// Optional [func] must be a JavaScript function\r
+\r
+PS.DoBeadFunction = function (x, y, exec)\r
+{\r
+ "use strict";\r
+ var i, bead;\r
+\r
+ // Assume x/y params are already verified\r
+\r
+ i = x + (y * PS.Grid.x); // get index of bead\r
+ bead = PS.Grid.beads[i];\r
+\r
+ if ( (exec !== undefined) && (exec !== PS.CURRENT) )\r
+ {\r
+ bead.exec = exec;\r
+ }\r
+\r
+ return bead.exec;\r
+};\r
+\r
+PS.BeadFunction = function (x, y, exec)\r
+{\r
+ "use strict";\r
+ var fn, i, j;\r
+\r
+ fn = "[PS.BeadFunction] ";\r
+\r
+ if ( (exec !== undefined) || (exec !== PS.CURRENT) )\r
+ {\r
+ if ( exec === PS.DEFAULT )\r
+ {\r
+ exec = null;\r
+ }\r
+ else if ( typeof exec !== "function" )\r
+ {\r
+ PS.Oops(fn + "exec param not a valid function");\r
+ return PS.ERROR;\r
+ }\r
+ }\r
+\r
+ if ( x === PS.ALL )\r
+ {\r
+ if ( y === PS.ALL ) // do entire grid\r
+ {\r
+ for ( j = 0; j < PS.Grid.y; j += 1 )\r
+ {\r
+ for ( i = 0; i < PS.Grid.x; i += 1 )\r
+ {\r
+ exec = PS.DoBeadFunction( i, j, exec );\r
+ }\r
+ }\r
+ }\r
+ else if ( !PS.CheckY( y, fn ) ) // verify y param\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ else\r
+ {\r
+ for ( i = 0; i < PS.Grid.x; i += 1 ) // do entire row\r
+ {\r
+ exec = PS.DoBeadFunction( i, y, exec );\r
+ }\r
+ }\r
+ }\r
+ else if ( y === PS.ALL )\r
+ {\r
+ if ( !PS.CheckX( x, fn ) ) // verify x param\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ for ( j = 0; j < PS.Grid.y; j += 1 ) // do entire column\r
+ {\r
+ exec = PS.DoBeadFunction( x, j, exec );\r
+ }\r
+ }\r
+ else if ( !PS.CheckX( x, fn ) || !PS.CheckY( y, fn ) ) // verify both params\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ else\r
+ {\r
+ exec = PS.DoBeadFunction( x, y, exec ); // do one bead\r
+ }\r
+\r
+ return exec;\r
+};\r
+\r
+// PS.BeadTouch(x, y, mask)\r
+// Simulates effect of clicking on a bead\r
+// [x, y] are grid position\r
+\r
+PS.DoBeadTouch = function (x, y)\r
+{\r
+ "use strict";\r
+ var i, bead;\r
+\r
+ // Assume x/y params are already verified\r
+\r
+ i = x + (y * PS.Grid.x); // get index of bead\r
+ bead = PS.Grid.beads[i];\r
+\r
+ // Play bead audio\r
+\r
+ if ( typeof bead.audio === "string" )\r
+ {\r
+ PS.AudioPlay(bead.audio, bead.volume);\r
+ }\r
+\r
+ // Run bead exec\r
+\r
+ if ( typeof bead.exec === "function" )\r
+ {\r
+ bead.exec(x, y, bead.data);\r
+ }\r
+\r
+ // Simulate click\r
+\r
+ PS.Click(x, y, bead.data);\r
+};\r
+\r
+PS.BeadTouch = function (x, y)\r
+{\r
+ "use strict";\r
+ var fn, i, j;\r
+\r
+ fn = "[PS.BeadTouch] ";\r
+\r
+ if ( x === PS.ALL )\r
+ {\r
+ if ( y === PS.ALL ) // do entire grid\r
+ {\r
+ for ( j = 0; j < PS.Grid.y; j += 1 )\r
+ {\r
+ for ( i = 0; i < PS.Grid.x; i += 1 )\r
+ {\r
+ PS.DoBeadTouch( i, j );\r
+ }\r
+ }\r
+ }\r
+ else if ( !PS.CheckY( y, fn ) ) // verify y param\r
+ {\r
+ return;\r
+ }\r
+ else\r
+ {\r
+ for ( i = 0; i < PS.Grid.x; i += 1 ) // do entire row\r
+ {\r
+ PS.DoBeadTouch( i, y );\r
+ }\r
+ }\r
+ }\r
+ else if ( y === PS.ALL )\r
+ {\r
+ if ( !PS.CheckX( x, fn ) ) // verify x param\r
+ {\r
+ return;\r
+ }\r
+ for ( j = 0; j < PS.Grid.y; j += 1 ) // do entire column\r
+ {\r
+ PS.DoBeadTouch( x, j );\r
+ }\r
+ }\r
+ else if ( !PS.CheckX( x, fn ) || !PS.CheckY( y, fn ) ) // verify both params\r
+ {\r
+ return;\r
+ }\r
+ else\r
+ {\r
+ PS.DoBeadTouch( x, y ); // do one bead\r
+ }\r
+};\r
+\r
+// Set message text\r
+\r
+PS.Status = "Perlenspiel";\r
+PS.StatusHue = 0;\r
+\r
+PS.StatusText = function (str)\r
+{\r
+ "use strict";\r
+ var fn, type, e;\r
+\r
+ fn = "[PS.StatusText] ";\r
+\r
+ type = typeof str;\r
+ if ( type !== "undefined" )\r
+ {\r
+ if ( type !== "string" )\r
+ {\r
+ PS.Oops(fn + "Parameter is not a string");\r
+ }\r
+ else\r
+ {\r
+ e = document.getElementById("status");\r
+ if ( e )\r
+ {\r
+ if ( PS.StatusFading ) // start the fade\r
+ {\r
+ e.style.color = PS.Grid.bgColor;\r
+ PS.StatusPhase = 0;\r
+ }\r
+ e.value = str; \r
+ }\r
+ PS.Status = str;\r
+ }\r
+ }\r
+ return PS.Status;\r
+};\r
+\r
+PS.StatusColor = function (rgb)\r
+{\r
+ "use strict";\r
+ var fn, colors, e;\r
+\r
+ fn = "[PS.StatusText] ";\r
+\r
+ if ( (rgb !== undefined) && (rgb !== PS.CURRENT) )\r
+ {\r
+ rgb = PS.ValidRGB( rgb, fn );\r
+ if ( rgb < 0 )\r
+ {\r
+ return PS.ERROR;\r
+ }\r
+ if ( rgb === PS.DEFAULT )\r
+ {\r
+ rgb = PS.DEFAULT_TEXT_COLOR;\r
+ }\r
+ colors = PS.UnmakeRGB(rgb);\r
+ PS.StatusRed = colors.r;\r
+ PS.StatusGreen = colors.g;\r
+ PS.StatusBlue = colors.b;\r
+ PS.StatusHue = PS.RGBString(colors.r, colors.g, colors.b);\r
+ PS.StatusPhase = 100; // stops fades in progress\r
+ \r
+ e = document.getElementById("status");\r
+ if ( e )\r
+ {\r
+ e.style.color = PS.StatusHue;\r
+ }\r
+ e = document.getElementById("footer");\r
+ if ( e )\r
+ {\r
+ e.style.color = PS.StatusHue;\r
+ }\r
+ }\r
+\r
+ return PS.StatusHue;\r
+};\r
+\r
+// Turn status line fading on and off\r
+\r
+PS.StatusFade = function (flag)\r
+{\r
+ "use strict";\r
+ var fn, e;\r
+\r
+ fn = "[PS.StatusFade] ";\r
+\r
+ if ( (flag !== undefined) && (flag !== PS.CURRENT) )\r
+ {\r
+ if ( flag || (flag === PS.DEFAULT) )\r
+ {\r
+ flag = true;\r
+ }\r
+ else\r
+ {\r
+ flag = false;\r
+ PS.StatusPhase = 100;\r
+ e = document.getElementById("status");\r
+ if ( e )\r
+ {\r
+ e.style.color = PS.StatusHue;\r
+ } \r
+ }\r
+ PS.StatusFading = flag;\r
+ }\r
+\r
+ return PS.StatusFading;\r
+};\r
+\r
+// Debugger API\r
+\r
+// Open debugger if not already open\r
+\r
+PS.DebugOpen = function ()\r
+{\r
+ "use strict";\r
+ var div, e;\r
+\r
+ if ( !PS.DebugWindow )\r
+ {\r
+ div = document.getElementById("debug");\r
+ div.style.display = "inline";\r
+\r
+ // clear it\r
+\r
+ e = document.getElementById("monitor");\r
+ if ( e )\r
+ {\r
+ e.value = "";\r
+ }\r
+\r
+ PS.DebugWindow = true;\r
+ }\r
+};\r
+\r
+// Close debugger if not already closed\r
+\r
+PS.DebugClose = function ()\r
+{\r
+ "use strict";\r
+ var e;\r
+\r
+ if ( PS.DebugWindow )\r
+ {\r
+ e = document.getElementById("debug");\r
+ e.style.display = "none";\r
+ PS.DebugWindow = false;\r
+ }\r
+};\r
+\r
+// Add line to debugger (does not include CR)\r
+\r
+PS.Debug = function (str)\r
+{\r
+ "use strict";\r
+ var e;\r
+\r
+ if ( typeof str !== "string" )\r
+ {\r
+ return;\r
+ }\r
+\r
+ PS.DebugOpen();\r
+\r
+ e = document.getElementById("monitor");\r
+ if ( e )\r
+ {\r
+ e.value += str; // add it\r
+ e.scrollTop = e.scrollHeight; // keep it scrolled down\r
+ }\r
+};\r
+\r
+// Clear footer and debugger\r
+\r
+PS.DebugClear = function ()\r
+{\r
+ "use strict";\r
+ var e;\r
+\r
+ e = document.getElementById("footer");\r
+ if ( e )\r
+ {\r
+ e.style.color="#000000"; // change to black\r
+ e.innerHTML = "Version 2.0.0";\r
+ }\r
+\r
+ if ( PS.DebugWindow )\r
+ {\r
+ e = document.getElementById("monitor");\r
+ if ( e )\r
+ {\r
+ e.value = "";\r
+ }\r
+ }\r
+};\r
+\r
+// Send error message to footer and debugger if open (includes CR)\r
+\r
+PS.Oops = function (str)\r
+{\r
+ "use strict";\r
+ var e;\r
+\r
+ if ( typeof str !== "string" )\r
+ {\r
+ return;\r
+ }\r
+\r
+ e = document.getElementById("footer");\r
+ if ( e )\r
+ {\r
+ e.innerHTML = str;\r
+ }\r
+\r
+ // Also display on debugger if open\r
+\r
+// if ( PS.DebugWindow )\r
+// {\r
+// e = document.getElementById("monitor");\r
+// if ( e )\r
+// {\r
+// e.value += ("ERROR: " + str + "\n");\r
+// e.scrollTop = e.scrollHeight; // keep it scrolled down \r
+// }\r
+// }\r
+\r
+ PS.Debug( "ERROR: " + str + "\n" );\r
+\r
+ PS.AudioPlay("fx_uhoh");\r
+};\r
+\r
+// Set up user clock\r
+\r
+PS.Clock = function ( ticks )\r
+{\r
+ "use strict";\r
+ var fn;\r
+\r
+ fn = "[PS.Clock] ";\r
+\r
+ if ( ticks !== undefined )\r
+ {\r
+ if ( typeof ticks !== "number" )\r
+ {\r
+ PS.Oops(fn + "ticks parameter not a number");\r
+ return PS.ERROR;\r
+ }\r
+ ticks = Math.floor(ticks);\r
+ if ( ticks < 1 )\r
+ {\r
+ PS.UserClock = 0;\r
+ }\r
+ else if ( typeof PS.Tick !== "function" )\r
+ {\r
+ PS.Oops(fn + "PS.Tick function undefined");\r
+ }\r
+ else\r
+ {\r
+ PS.UserDelay = 0;\r
+ PS.UserClock = ticks;\r
+ }\r
+ }\r
+\r
+ return PS.UserClock;\r
+};\r
+\r
+// General system timer\r
+\r
+PS.Timer = function ()\r
+{\r
+ "use strict";\r
+ var phase, hue, r, g, b, e;\r
+\r
+ // Handle bead flashing and status text fading\r
+\r
+ PS.FlashDelay += 1;\r
+ if ( PS.FlashDelay >= PS.FLASH_INTERVAL )\r
+ {\r
+ PS.FlashDelay = 0;\r
+ PS.FlashNext();\r
+ \r
+ // Handle status text fading\r
+ \r
+ if ( PS.StatusFading && (PS.StatusPhase < 100) )\r
+ {\r
+ phase = PS.StatusPhase + PS.STATUS_FLASH_STEP;\r
+\r
+ if ( phase >= 100 )\r
+ {\r
+ phase = 100;\r
+ hue = PS.StatusHue;\r
+ }\r
+ else\r
+ { \r
+ r = PS.Dissolve( PS.Grid.bgRed, PS.StatusRed, phase );\r
+ g = PS.Dissolve( PS.Grid.bgGreen, PS.StatusGreen, phase );\r
+ b = PS.Dissolve( PS.Grid.bgBlue, PS.StatusBlue, phase );\r
+ hue = PS.RGBString(r, g, b);\r
+ }\r
+ PS.StatusPhase = phase; \r
+ e = document.getElementById("status");\r
+ if ( e )\r
+ {\r
+ e.style.color = hue;\r
+ }\r
+ }\r
+ }\r
+\r
+ // Handle user clock\r
+\r
+ if ( PS.UserClock > 0 )\r
+ {\r
+ PS.UserDelay += 1;\r
+ if ( PS.UserDelay >= PS.UserClock )\r
+ {\r
+ PS.UserDelay = 0;\r
+ if ( PS.Tick )\r
+ {\r
+ try\r
+ {\r
+ PS.Tick(); // call user function\r
+ }\r
+ catch (err)\r
+ {\r
+ PS.Oops("PS.Tick() failed [" + err.message + "]" );\r
+ PS.UserClock = 0; // stop the timer\r
+ } \r
+ }\r
+ }\r
+ }\r
+};\r
+\r
+// PS.StartFlash(bead)\r
+// Initiates flashing of bead\r
+\r
+PS.FlashStart = function (x, y)\r
+{\r
+ "use strict";\r
+ var which, bead, i, len;\r
+\r
+ which = x + (y * PS.Grid.x); // index of bead\r
+\r
+ bead = PS.Grid.beads[which];\r
+\r
+ bead.flashPhase = 0; // init flash step\r
+\r
+ // draw first step\r
+\r
+ bead.colorNow = bead.flashColor;\r
+ PS.DrawBead(bead);\r
+\r
+ // if this bead is already in flash queue, exit\r
+\r
+ len = PS.Grid.flashList.length;\r
+ for ( i = 0; i < len; i += 1 )\r
+ {\r
+ if ( PS.Grid.flashList[i] === which )\r
+ {\r
+ return;\r
+ }\r
+ }\r
+\r
+ // else add this bead to queue\r
+\r
+ PS.Grid.flashList.push(which);\r
+};\r
+\r
+// PS.NextFlash(bead)\r
+// Flash all beads in queue\r
+\r
+PS.FlashNext = function ()\r
+{\r
+ "use strict";\r
+ var ctx, len, i, which, bead, phase, r, g, b;\r
+\r
+ ctx = PS.Context();\r
+ len = PS.Grid.flashList.length;\r
+ i = 0;\r
+ while ( i < len )\r
+ {\r
+ which = PS.Grid.flashList[i];\r
+ bead = PS.Grid.beads[which];\r
+ phase = bead.flashPhase + PS.FLASH_STEP;\r
+\r
+ // If flash is done, set normal color and remove bead from queue\r
+\r
+ if ( phase >= 100 )\r
+ {\r
+ bead.colorNow = bead.color;\r
+ bead.flashPhase = 0;\r
+ PS.Grid.flashList.splice(i, 1);\r
+ len -= 1;\r
+ }\r
+ else\r
+ {\r
+ bead.flashPhase = phase;\r
+ r = PS.Dissolve( bead.flashRed, bead.alphaRed, phase );\r
+ g = PS.Dissolve( bead.flashGreen, bead.alphaGreen, phase );\r
+ b = PS.Dissolve( bead.flashBlue, bead.alphaBlue, phase );\r
+ bead.colorNow = PS.RGBString(r, g, b);\r
+ i += 1;\r
+ }\r
+ PS.DrawBead(bead, ctx);\r
+ }\r
+};\r
+\r
+// System initialization\r
+\r
+// Records the x/y of mouse over grid, -1 if not over grid\r
+\r
+PS.MouseXY = function (event)\r
+{\r
+ "use strict";\r
+ var canvas, x, y, beads, bead, row, col, i;\r
+\r
+ if ( PS.Grid )\r
+ {\r
+ canvas = document.getElementById("screen");\r
+\r
+ if ( event.x && event.y )\r
+ {\r
+ x = event.x;\r
+ y = event.y;\r
+ }\r
+ else // Firefox method to get the position\r
+ {\r
+ x = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;\r
+ y = event.clientY + document.body.scrollTop + document.documentElement.scrollTop;\r
+ }\r
+\r
+ x -= canvas.offsetLeft;\r
+ y -= canvas.offsetTop;\r
+\r
+ // Over the grid?\r
+\r
+ if ( (x >= PS.Grid.left) && (x < PS.Grid.right) && (y >= PS.Grid.top) && (y < PS.Grid.bottom) )\r
+ {\r
+ // Which bead are we over?\r
+\r
+ beads = PS.Grid.beads;\r
+ i = 0; // init index\r
+ for ( row = 0; row < PS.Grid.y; row += 1 )\r
+ {\r
+ bead = beads[i]; // get the first bead in this row\r
+\r
+ // Is mouse over this row?\r
+\r
+ if ( (y >= bead.top) && (y < bead.bottom) )\r
+ {\r
+ // Find column\r
+\r
+ for ( col = 0; col < PS.Grid.x; col += 1 )\r
+ {\r
+ bead = beads[i];\r
+ if ( (x >= bead.left) && (x < bead.right) )\r
+ {\r
+ PS.MouseX = col;\r
+ PS.MouseY = row;\r
+ return;\r
+ }\r
+ i += 1;\r
+ }\r
+ }\r
+ else\r
+ {\r
+ i += PS.Grid.x; // try next row \r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ PS.MouseX = -1;\r
+ PS.MouseY = -1;\r
+};\r
+\r
+// Called when mouse is clicked over canvas\r
+\r
+PS.MouseDown = function (event)\r
+{\r
+ "use strict";\r
+ var bead;\r
+\r
+ PS.MouseXY(event);\r
+ if ( PS.MouseX >= 0 )\r
+ {\r
+ bead = PS.Grid.beads[PS.MouseX + (PS.MouseY * PS.Grid.x)];\r
+\r
+ // play audio if assigned to bead\r
+\r
+ if ( bead.audio )\r
+ {\r
+ PS.AudioPlay(bead.audio, bead.volume);\r
+ }\r
+\r
+ // Call function if assigned to bead\r
+\r
+ if ( typeof bead.exec === "function" )\r
+ { \r
+ try\r
+ {\r
+ bead.exec(PS.MouseX, PS.MouseY, bead.data); \r
+ }\r
+ catch (err1)\r
+ {\r
+ PS.Oops("Bead " + PS.MouseX + ", " + PS.MouseY + " function failed [" + err1.message + "]" );\r
+ } \r
+ }\r
+\r
+ if ( PS.Click ) // only if function exists\r
+ { \r
+ try\r
+ {\r
+ PS.Click(PS.MouseX, PS.MouseY, bead.data); \r
+ }\r
+ catch (err2)\r
+ {\r
+ PS.Oops("PS.Click() failed [" + err2.message + "]" );\r
+ } \r
+ }\r
+ }\r
+};\r
+\r
+// Called when mouse is released over canvas\r
+\r
+PS.MouseUp = function (event)\r
+{\r
+ "use strict";\r
+ var bead;\r
+\r
+ if ( PS.Grid && PS.Release ) // only if grid and function exist\r
+ {\r
+ PS.MouseXY(event);\r
+ if ( PS.MouseX >= 0 )\r
+ {\r
+ bead = PS.Grid.beads[PS.MouseX + (PS.MouseY * PS.Grid.x)]; \r
+ try\r
+ {\r
+ PS.Release(PS.MouseX, PS.MouseY, bead.data); \r
+ }\r
+ catch (err)\r
+ {\r
+ PS.Oops("PS.Release() failed [" + err.message + "]" );\r
+ } \r
+ }\r
+ }\r
+};\r
+\r
+// Called when mouse moves over canvas\r
+\r
+PS.MouseMove = function (event)\r
+{\r
+ "use strict";\r
+ var bead, last;\r
+\r
+ PS.MouseXY(event);\r
+\r
+ if ( PS.MouseX >= 0 )\r
+ {\r
+ bead = PS.Grid.beads[PS.MouseX + (PS.MouseY * PS.Grid.x)];\r
+ if ( (PS.MouseX !== PS.LastX) || (PS.MouseY !== PS.LastY) )\r
+ {\r
+ if ( PS.Leave ) // only if function exists\r
+ {\r
+ if ( PS.LastX >= 0 )\r
+ {\r
+ last = PS.Grid.beads[PS.LastX + (PS.LastY * PS.Grid.x)]; \r
+ try\r
+ {\r
+ PS.Leave(PS.LastX, PS.LastY, last.data); \r
+ }\r
+ catch (err1)\r
+ {\r
+ PS.Oops("PS.Leave() failed [" + err1.message + "]" );\r
+ } \r
+ }\r
+ }\r
+ if ( PS.Enter ) // only if function exists\r
+ { \r
+ try\r
+ {\r
+ PS.Enter(PS.MouseX, PS.MouseY, bead.data); \r
+ }\r
+ catch (err2)\r
+ {\r
+ PS.Oops("PS.Enter() failed [" + err2.message + "]" );\r
+ } \r
+ }\r
+ PS.LastX = PS.MouseX;\r
+ PS.LastY = PS.MouseY;\r
+ }\r
+ }\r
+ else if ( PS.LastX >= 0 )\r
+ {\r
+ if ( PS.Leave ) // only if function exists\r
+ {\r
+ last = PS.Grid.beads[PS.LastX + (PS.LastY * PS.Grid.x)]; \r
+ try\r
+ {\r
+ PS.Leave(PS.LastX, PS.LastY, last.data); \r
+ }\r
+ catch (err3)\r
+ {\r
+ PS.Oops("PS.Leave() failed [" + err3.message + "]" );\r
+ } \r
+ }\r
+ PS.LastX = -1;\r
+ PS.LastY = -1;\r
+ }\r
+};\r
+\r
+// Called when mouse leaves canvas\r
+\r
+PS.MouseOut = function (event)\r
+{\r
+ "use strict";\r
+ var last;\r
+\r
+ PS.MouseBead = -1;\r
+ if ( PS.Grid && PS.Leave ) // only if grid and function exist\r
+ {\r
+ if ( PS.LastBead >= 0 )\r
+ {\r
+ last = PS.Grid.beads[PS.LastBead]; \r
+ try\r
+ {\r
+ PS.Leave(last.x, last.y, last.data); \r
+ }\r
+ catch (err)\r
+ {\r
+ PS.Oops("PS.Leave() failed [" + err.message + "]" );\r
+ } \r
+ }\r
+ }\r
+ PS.LastBead = -1;\r
+};\r
+\r
+PS.KeyFilter = function (key, shift)\r
+{\r
+ "use strict";\r
+ \r
+ // convert lower-case alpha to upper-case if shift key is down\r
+ \r
+ if ( (key >= 65) && (key <= 90) )\r
+ {\r
+ if ( shift )\r
+ {\r
+ key += 32; \r
+ }\r
+ return key;\r
+ }\r
+ \r
+ // Convert weird keycodes to ASCII\r
+ \r
+ switch ( key )\r
+ {\r
+ case 188:\r
+ key = 44; // ,\r
+ break;\r
+ case 190:\r
+ key = 46; // .\r
+ break;\r
+ case 191:\r
+ key = 47; // /\r
+ break;\r
+ case 222:\r
+ key = 39; // '\r
+ break;\r
+ case 219:\r
+ key = 91; // [\r
+ break;\r
+ case 221:\r
+ key = 93; // ]\r
+ break;\r
+ case 220:\r
+ key = 92; // \\r
+ break;\r
+ default:\r
+ break;\r
+ }\r
+ \r
+ // Translate shifted keys\r
+ \r
+ if ( shift )\r
+ {\r
+ switch ( key )\r
+ {\r
+ case 96: // `\r
+ key = 126; // ~\r
+ break;\r
+ case 49: // 1\r
+ key = 33; // !\r
+ break;\r
+ case 50: // 2\r
+ key = 64; // @\r
+ break;\r
+ case 51: // 3\r
+ key = 35; // #\r
+ break;\r
+ case 52: // 4\r
+ key = 36; // !\r
+ break;\r
+ case 53: // 5\r
+ key = 37; // %\r
+ break;\r
+ case 54: // 6\r
+ key = 94; // ^\r
+ break;\r
+ case 55: // 7\r
+ key = 38; // &\r
+ break;\r
+ case 56: // 8\r
+ key = 42; // *\r
+ break;\r
+ case 57: // 9\r
+ key = 40; // (\r
+ break;\r
+ case 48: // 0\r
+ key = 41; // )\r
+ break;\r
+ case 45: // -\r
+ key = 95; // _\r
+ break;\r
+ case 61: // =\r
+ key = 43; // +\r
+ break; \r
+ case 91: // [\r
+ key = 123; // {\r
+ break;\r
+ case 93: // ]\r
+ key = 125; // }\r
+ break;\r
+ case 92: // \\r
+ key = 124; // |\r
+ break; \r
+ case 59: // ;\r
+ key = 58; // :\r
+ break;\r
+ case 39: // '\r
+ key = 34; // "\r
+ break; \r
+ case 44: // ,\r
+ key = 60; // <\r
+ break;\r
+ case 46: // .\r
+ key = 62; // >\r
+ break;\r
+ case 47: // /\r
+ key = 63; // ?\r
+ break;\r
+ default:\r
+ break;\r
+ } \r
+ }\r
+\r
+ return key;\r
+};\r
+\r
+// Called when a key is pressed\r
+\r
+PS.SysKeyDown = function (event)\r
+{\r
+ "use strict";\r
+ var key;\r
+\r
+ if ( PS.KeyDown ) // only if function exists\r
+ {\r
+ if ( event.which === null )\r
+ {\r
+ key = event.keyCode; // IE\r
+ }\r
+ else\r
+ {\r
+ key = event.which; // Others\r
+ }\r
+ key = PS.KeyFilter(key, event.shiftKey);\r
+ try\r
+ {\r
+ PS.KeyDown(key, event.shiftKey, event.ctrlKey); \r
+ }\r
+ catch (err)\r
+ {\r
+ PS.Oops("PS.KeyDown() failed [" + err.message + "]" );\r
+ }\r
+ }\r
+ return false;\r
+};\r
+\r
+// Called when a key is released\r
+\r
+PS.SysKeyUp = function (event)\r
+{\r
+ "use strict";\r
+ var key;\r
+\r
+ if ( PS.KeyUp ) // only if function exists\r
+ {\r
+ if ( event.which === null )\r
+ {\r
+ key = event.keyCode; // IE\r
+ }\r
+ else\r
+ {\r
+ key = event.which; // Others\r
+ }\r
+ key = PS.KeyFilter(key, event.shiftKey);\r
+ try\r
+ {\r
+ PS.KeyUp(key, event.shiftKey, event.ctrlKey); \r
+ }\r
+ catch (err)\r
+ {\r
+ PS.Oops("PS.KeyUp() failed [" + err.message + "]" );\r
+ }\r
+ }\r
+ return false;\r
+};\r
+\r
+// Called when mouse wheel is moved\r
+\r
+PS.SysWheel = function (event)\r
+{\r
+ "use strict";\r
+ var delta;\r
+\r
+ if ( PS.Wheel ) // only if function exists\r
+ {\r
+ delta = 0;\r
+\r
+ // for IE\r
+\r
+ if ( !event )\r
+ {\r
+ event = window.event;\r
+ }\r
+\r
+ // IE and Opera\r
+\r
+ if ( event.wheelDelta )\r
+ {\r
+ delta = event.wheelDelta / 120;\r
+ if ( window.opera )\r
+ {\r
+ delta = -delta;\r
+ }\r
+ }\r
+\r
+ // Firefox and Chrome?\r
+\r
+ else if ( event.detail )\r
+ {\r
+ delta = -( event.detail / 3 );\r
+ }\r
+\r
+ if ( event.preventDefault )\r
+ {\r
+ event.preventDefault();\r
+ }\r
+\r
+ // clamp\r
+ \r
+ if ( delta >= PS.FORWARD )\r
+ {\r
+ delta = PS.FORWARD;\r
+ }\r
+ else\r
+ {\r
+ delta = PS.BACKWARD;\r
+ }\r
+ \r
+ // Send delta to user\r
+ \r
+ try\r
+ {\r
+ PS.Wheel (delta);\r
+ }\r
+ catch (err)\r
+ {\r
+ PS.Oops("PS.Wheel() failed [" + err.message + "]" );\r
+ }\r
+ }\r
+\r
+ event.returnValue = false;\r
+};\r
+\r
+// Initialization\r
+\r
+PS.Sys = function ()\r
+{\r
+ "use strict";\r
+ var fn, e;\r
+\r
+ fn = "[PS.Sys] ";\r
+\r
+ // Init audio support, preload error sound\r
+\r
+ PS.AudioInit();\r
+ PS.AudioLoad("fx_uhoh");\r
+\r
+ // Make sure all required game functions exist\r
+\r
+ if ( typeof PS.Init !== "function" )\r
+ {\r
+ PS.Init = null;\r
+ PS.Oops(fn + "WARNING: PS.Init function undefined");\r
+ }\r
+\r
+ if ( typeof PS.Click !== "function" )\r
+ {\r
+ PS.Click = null;\r
+ PS.Oops(fn + "WARNING: PS.Click function undefined");\r
+ }\r
+\r
+ if ( typeof PS.Release !== "function" )\r
+ {\r
+ PS.Release = null;\r
+ PS.Oops(fn + "WARNING: PS.Release function undefined");\r
+ }\r
+\r
+ if ( typeof PS.Enter !== "function" )\r
+ {\r
+ PS.Enter = null;\r
+ PS.Oops(fn + "WARNING: PS.Enter function undefined");\r
+ }\r
+\r
+ if ( typeof PS.Leave !== "function" )\r
+ {\r
+ PS.Leave = null;\r
+ PS.Oops(fn + "WARNING: PS.Leave function undefined");\r
+ }\r
+\r
+ if ( typeof PS.KeyDown !== "function" )\r
+ {\r
+ PS.KeyDown = null;\r
+ PS.Oops(fn + "WARNING: PS.KeyDown function undefined");\r
+ }\r
+\r
+ if ( typeof PS.KeyUp !== "function" )\r
+ {\r
+ PS.KeyUp = null;\r
+ PS.Oops(fn + "WARNING: PS.KeyUp function undefined");\r
+ }\r
+\r
+ if ( typeof PS.Wheel !== "function" )\r
+ {\r
+ PS.Wheel = null;\r
+ PS.Oops(fn + "WARNING: PS.Wheel function undefined");\r
+ }\r
+\r
+ // Set up mouse/keyboard events\r
+\r
+ e = document.getElementById("screen");\r
+ if ( e )\r
+ {\r
+ e.addEventListener("mousedown", PS.MouseDown, false);\r
+ e.addEventListener("mouseup", PS.MouseUp, false);\r
+ e.addEventListener("mouseout", PS.MouseOut, false);\r
+ e.addEventListener("mousemove", PS.MouseMove, false);\r
+ }\r
+\r
+ // Set up mouse wheel events\r
+\r
+ if ( window.addEventListener )\r
+ {\r
+ window.addEventListener('DOMMouseScroll', PS.SysWheel, false); // for Firefox\r
+ window.addEventListener('mousewheel', PS.SysWheel, false); // for others\r
+ }\r
+ else\r
+ {\r
+ window.onmousewheel = document.onmousewheel = PS.SysWheel; // for IE, maybe\r
+ }\r
+ \r
+ // Setup offscreen canvas for image manipulation\r
+ \r
+ PS.ImageCanvas = document.createElement("canvas");\r
+ PS.ImageCanvas.width = PS.GRID_MAX;\r
+ PS.ImageCanvas.height = PS.GRID_MAX;\r
+\r
+ // Start the timer\r
+\r
+ window.setInterval (PS.Timer, PS.DEFAULT_FPS);\r
+\r
+ // Print version number\r
+\r
+ e = document.getElementById("footer");\r
+ if ( e )\r
+ {\r
+ e.innerHTML = "Version " + PS.VERSION;\r
+ }\r
+\r
+ if ( PS.Init )\r
+ {\r
+ try\r
+ {\r
+ PS.Init(); // call user initializer\r
+ }\r
+ catch (err)\r
+ {\r
+ PS.Oops("PS.Init() failed [" + err.message + "]" );\r
+ } \r
+ }\r
+ else\r
+ {\r
+ PS.GridSize(PS.GRID_DEFAULT_WIDTH, PS.GRID_DEFAULT_HEIGHT);\r
+ }\r
+};\r
+\r
+// Library stuff\r
+\r
+PS.Random = function (val)\r
+{\r
+ "use strict";\r
+ var fn;\r
+\r
+ fn = "[PS.Random] ";\r
+ if ( typeof val !== "number" )\r
+ {\r
+ PS.Oops(fn + "Parameter is not a number");\r
+ return PS.ERROR;\r
+ } \r
+ val = Math.floor(val);\r
+ if ( val < 2 )\r
+ {\r
+ return 1;\r
+ }\r
+\r
+ val = Math.random() * val;\r
+ val = Math.floor(val) + 1;\r
+ return val;\r
+};\r
+\r
+PS.PianoFiles = [\r
+ "a0", "bb0", "b0",\r
+ "c1", "db1", "d1", "eb1", "e1", "f1", "gb1", "g1", "ab1", "a1", "bb1", "b1",\r
+ "c2", "db2", "d2", "eb2", "e2", "f2", "gb2", "g2", "ab2", "a2", "bb2", "b2",\r
+ "c3", "db3", "d3", "eb3", "e3", "f3", "gb3", "g3", "ab3", "a3", "bb3", "b3",\r
+ "c4", "db4", "d4", "eb4", "e4", "f4", "gb4", "g4", "ab4", "a4", "bb4", "b4",\r
+ "c5", "db5", "d5", "eb5", "e5", "f5", "gb5", "g5", "ab5", "a5", "bb5", "b5",\r
+ "c6", "db6", "d6", "eb6", "e6", "f6", "gb6", "g6", "ab6", "a6", "bb6", "b6",\r
+ "c7", "db7", "d7", "eb7", "e7", "f7", "gb7", "g7", "ab7", "a7", "bb7", "b7",\r
+ "c8"\r
+];\r
+\r
+PS.Piano = function ( val, flag )\r
+{\r
+ "use strict";\r
+ var fn, str;\r
+\r
+ fn = "[PS.Piano] ";\r
+\r
+ if ( typeof val !== "number" )\r
+ {\r
+ PS.Oops(fn + "Parameter is not a number");\r
+ return PS.ERROR;\r
+ }\r
+ val = Math.floor(val);\r
+ if ( val < 1 )\r
+ {\r
+ val = 1;\r
+ }\r
+ else if ( val > 88 )\r
+ {\r
+ val = 88;\r
+ }\r
+\r
+ str = "piano_" + PS.PianoFiles[val - 1];\r
+ if ( flag )\r
+ {\r
+ str = "l_" + str;\r
+ }\r
+ return str;\r
+};\r
+\r
+PS.HchordFiles = [\r
+ "a2", "bb2", "b2",\r
+ "c3", "db3", "d3", "eb3", "e3", "f3", "gb3", "g3", "ab3", "a3", "bb3", "b3",\r
+ "c4", "db4", "d4", "eb4", "e4", "f4", "gb4", "g4", "ab4", "a4", "bb4", "b4",\r
+ "c5", "db5", "d5", "eb5", "e5", "f5", "gb5", "g5", "ab5", "a5", "bb5", "b5",\r
+ "c6", "db6", "d6", "eb6", "e6", "f6", "gb6", "g6", "ab6", "a6", "bb6", "b6",\r
+ "c7", "db7", "d7", "eb7", "e7", "f7"\r
+];\r
+\r
+PS.Harpsichord = function ( val, flag )\r
+{\r
+ "use strict";\r
+ var fn, str;\r
+\r
+ fn = "[PS.Harpsichord] ";\r
+\r
+ if ( typeof val !== "number" )\r
+ {\r
+ PS.Oops(fn + "Parameter is not a number");\r
+ return PS.ERROR;\r
+ }\r
+ val = Math.floor(val);\r
+ if ( val < 1 )\r
+ {\r
+ val = 1;\r
+ }\r
+ else if ( val > 57 )\r
+ {\r
+ val = 57;\r
+ }\r
+\r
+ str = "hchord_" + PS.HchordFiles[val - 1];\r
+ if ( flag )\r
+ {\r
+ str = "l_" + str;\r
+ }\r
+ return str;\r
+};\r
+\r
+PS.XyloFiles = [\r
+ "a4", "bb4", "b4",\r
+ "c5", "db5", "d5", "eb5", "e5", "f5", "gb5", "g5", "ab5", "a5", "bb5", "b5",\r
+ "c6", "db6", "d6", "eb6", "e6", "f6", "gb6", "g6", "ab6", "a6", "bb6", "b6",\r
+ "c7", "db7", "d7", "eb7", "e7", "f7", "gb7", "g7", "ab7", "a7", "bb7", "b7"\r
+];\r
+\r
+PS.Xylophone = function ( val )\r
+{\r
+ "use strict";\r
+ var fn, str;\r
+\r
+ fn = "[PS.Xylophone] ";\r
+\r
+ if ( typeof val !== "number" )\r
+ {\r
+ PS.Oops(fn + "Parameter is not a number");\r
+ return PS.ERROR;\r
+ }\r
+ val = Math.floor(val);\r
+ if ( val < 1 )\r
+ {\r
+ val = 1;\r
+ }\r
+ else if ( val > 39 )\r
+ {\r
+ val = 39;\r
+ }\r
+\r
+ str = "xylo_" + PS.XyloFiles[val - 1];\r
+ return str;\r
+};\r
+\r
+// Audio functions\r
+\r
+PS.AUDIO_PATH_DEFAULT = "http://users.wpi.edu/~bmoriarty/ps/audio/"; // case sensitive!\r
+PS.AUDIO_PATH = PS.AUDIO_PATH_DEFAULT;\r
+PS.AUDIO_MAX_CHANNELS = 32;\r
+PS.AudioChannels = [];\r
+\r
+PS.AudioInit = function ()\r
+{\r
+ "use strict";\r
+ var i;\r
+\r
+ for ( i = 0; i < PS.AUDIO_MAX_CHANNELS; i += 1 )\r
+ {\r
+ PS.AudioChannels[i] = {};\r
+ PS.AudioChannels[i].audio = new Audio();\r
+ PS.AudioChannels[i].done = -1;\r
+ PS.AudioChannels[i].id = "";\r
+ }\r
+};\r
+\r
+PS.AudioError = function (obj)\r
+{\r
+ "use strict";\r
+ var c, str;\r
+\r
+ c = obj.error.code;\r
+ switch ( c )\r
+ {\r
+ case 1:\r
+ str = "MEDIA_ERR_ABORTED";\r
+ break;\r
+ case 2:\r
+ str = "MEDIA_ERR_NETWORK";\r
+ break;\r
+ case 3:\r
+ str = "MEDIA_ERR_DECODE";\r
+ break;\r
+ case 4:\r
+ str = "MEDIA_ERR_SRC_NOT_SUPPORTED";\r
+ break;\r
+ default:\r
+ str = "UNKNOWN";\r
+ break;\r
+ }\r
+\r
+ PS.Oops("[Audio Error: " + str + "]\n"); \r
+};\r
+\r
+PS.AudioPath = function (path)\r
+{\r
+ "use strict";\r
+ var fn;\r
+ \r
+ fn = "[PS.AudioPath] ";\r
+\r
+ if ( path === PS.DEFAULT )\r
+ {\r
+ PS.AUDIO_PATH = PS.AUDIO_PATH_DEFAULT;\r
+ }\r
+ else if ( typeof path !== "string" )\r
+ {\r
+ PS.Oops(fn + "path parameter is not a string");\r
+ return PS.ERROR; \r
+ }\r
+ else\r
+ {\r
+ PS.AUDIO_PATH = path; \r
+ }\r
+ \r
+ return PS.AUDIO_PATH;\r
+};\r
+\r
+PS.AudioLoad = function (id, path)\r
+{\r
+ "use strict";\r
+ var fn, snd;\r
+\r
+ fn = "[PS.AudioLoad] ";\r
+\r
+ if ( typeof id !== "string" )\r
+ {\r
+ PS.Oops(fn + "id parameter is not a string");\r
+ return PS.ERROR; \r
+ }\r
+ if ( id.length < 1 )\r
+ {\r
+ PS.Oops(fn + "id parameter is an empty string");\r
+ return PS.ERROR; \r
+ }\r
+\r
+ if ( path === undefined )\r
+ {\r
+ path = PS.AUDIO_PATH;\r
+ }\r
+ else if ( path === PS.DEFAULT )\r
+ {\r
+ path = PS.AUDIO_PATH_DEFAULT; \r
+ }\r
+ else if ( typeof path !== "string" )\r
+ {\r
+ PS.Oops(fn + "path parameter is not a string");\r
+ return PS.ERROR; \r
+ }\r
+\r
+ // Already got this? Clone it\r
+\r
+ snd = document.getElementById(id);\r
+ if ( snd )\r
+ {\r
+ return snd;\r
+ }\r
+\r
+ path = path + id + ".wav";\r
+\r
+ snd = document.createElement("audio");\r
+ snd.setAttribute("src", path);\r
+ snd.setAttribute("id", id); \r
+ snd.setAttribute("preload", "auto");\r
+ snd.setAttribute("onerror", "PS.AudioError(this)");\r
+\r
+// src = document.createElement("source");\r
+// src.setAttribute("src", path + ".ogg");\r
+// src.setAttribute("type", "audio/ogg");\r
+// snd.appendChild(src);\r
+ \r
+// src = document.createElement("source");\r
+// src.setAttribute("src", path + ".mp3");\r
+// src.setAttribute("type", "audio/mpeg");\r
+// src.setAttribute("onerror", "PS.AudioError()");\r
+// snd.appendChild(src);\r
+\r
+// src = document.createElement("source");\r
+// src.setAttribute("src", path + ".wav");\r
+// src.setAttribute("type", "audio/x-wav");\r
+// snd.appendChild(src);\r
+\r
+ document.body.appendChild(snd);\r
+ snd.load();\r
+\r
+ return snd;\r
+};\r
+\r
+// Returns a channel number\r
+\r
+PS.AudioPlay = function (id, volume, func, path)\r
+{\r
+ "use strict";\r
+ var fn, i, snd, d, t, channel;\r
+\r
+ fn = "[PS.AudioPlay] ";\r
+\r
+ if ( (volume === undefined) || (volume === PS.DEFAULT) )\r
+ {\r
+ volume = PS.DEFAULT_VOLUME;\r
+ }\r
+ else if ( typeof volume !== "number" )\r
+ {\r
+ PS.Oops(fn + "volume parameter is not a number");\r
+ return PS.ERROR; \r
+ }\r
+ else if ( volume < 0 )\r
+ {\r
+ volume = 0;\r
+ }\r
+ else if ( volume > PS.DEFAULT_VOLUME )\r
+ {\r
+ volume = PS.DEFAULT_VOLUME;\r
+ }\r
+ \r
+ if ( (func !== undefined) && (typeof func !== "function") )\r
+ {\r
+ PS.Oops(fn + "func parameter is not a function");\r
+ return PS.ERROR; \r
+ } \r
+\r
+ snd = PS.AudioLoad(id, path);\r
+ if ( snd !== PS.ERROR )\r
+ { \r
+ snd.volume = volume;\r
+ if ( func !== undefined )\r
+ {\r
+ snd.addEventListener("ended", func);\r
+ }\r
+ for ( i = 0; i < PS.AUDIO_MAX_CHANNELS; i += 1 )\r
+ {\r
+ d = new Date();\r
+ t = d.getTime();\r
+ channel = PS.AudioChannels[i];\r
+ if ( channel.done < t )\r
+ {\r
+ channel.done = t + ( snd.duration * 1000 );\r
+ channel.audio = snd;\r
+ channel.id = id;\r
+ snd.load(); // WHY???\r
+ snd.play();\r
+ return i + 1; // channel id\r
+ }\r
+ }\r
+ }\r
+\r
+ return PS.ERROR; // error\r
+};\r
+\r
+// Stops playback of channel number\r
+\r
+PS.AudioStop = function (c)\r
+{\r
+ "use strict";\r
+ var fn, d, t, channel;\r
+\r
+ fn = "[PS.AudioStop] ";\r
+\r
+ if ( typeof c !== "number" )\r
+ {\r
+ PS.Oops(fn + "Parameter is not a number");\r
+ return PS.ERROR; \r
+ }\r
+ c = Math.floor(c);\r
+ if ( (c < 1) || (c > PS.AUDIO_MAX_CHANNELS) )\r
+ {\r
+ PS.Oops(fn + "Invalid channel id");\r
+ return PS.ERROR; \r
+ }\r
+\r
+ channel = PS.AudioChannels[c - 1];\r
+ d = new Date();\r
+ t = d.getTime(); \r
+\r
+ channel.done = t; // mark as done playing\r
+ channel.audio.pause();\r
+ channel.audio.currentTime = 0;\r
+\r
+ return c;\r
+};\r
+\r
+// Pauses/unpauses playback of channel number\r
+\r
+PS.AudioPause = function (c)\r
+{\r
+ "use strict";\r
+ var fn, channel, audio;\r
+\r
+ fn = "[PS.AudioPause] ";\r
+\r
+ if ( typeof c !== "number" )\r
+ {\r
+ PS.Oops(fn + "Parameter is not a number");\r
+ return PS.ERROR; \r
+ }\r
+ c = Math.floor(c);\r
+ if ( (c < 1) || (c > PS.AUDIO_MAX_CHANNELS) )\r
+ {\r
+ PS.Oops(fn + "Invalid channel id");\r
+ return PS.ERROR; \r
+ }\r
+\r
+ channel = PS.AudioChannels[c - 1];\r
+ audio = channel.audio; \r
+ if ( audio.paused )\r
+ {\r
+ audio.play();\r
+ }\r
+ else\r
+ {\r
+ audio.pause();\r
+ }\r
+\r
+ return c;\r
+};\r
+\r
+// Image functions\r
+\r
+PS.ImageLoad = function ( file, func )\r
+{\r
+ "use strict";\r
+ var fn, img;\r
+ \r
+ fn = "[PS.ImageLoad] ";\r
+ \r
+ if ( typeof file !== "string" )\r
+ {\r
+ PS.Oops(fn + "Parameter 1 is not a string");\r
+ return PS.ERROR;\r
+ }\r
+ if ( file.length < 1 )\r
+ {\r
+ PS.Oops(fn + "Parameter 1 is an empty string");\r
+ return PS.ERROR; \r
+ }\r
+ if ( (func !== undefined) && (typeof func !== "function") )\r
+ {\r
+ PS.Oops(fn + "Parameter 2 is not a function");\r
+ return PS.ERROR; \r
+ } \r
+ try\r
+ {\r
+ img = new Image();\r
+ if ( func !== undefined )\r
+ {\r
+ img.onload = func;\r
+ }\r
+ img.src = file;\r
+ return img;\r
+ }\r
+ catch (err)\r
+ {\r
+ PS.Oops(fn + "failed [" + err.message + "]");\r
+ return PS.ERROR;\r
+ }\r
+};\r
+\r
+// Extract an imageData table from an image file\r
+// optional [alpha] determines if alpha data if included\r
+\r
+PS.ImageData = function ( img, alpha )\r
+{\r
+ "use strict";\r
+ var fn, w, h, ctx, imgData, imgData2, i, j, len, d1, d2;\r
+ \r
+ fn = "[PS.ImageMap] ";\r
+ if ( (alpha === undefined) || !alpha )\r
+ {\r
+ alpha = false; \r
+ }\r
+ else\r
+ {\r
+ alpha = true;\r
+ }\r
+ \r
+ w = img.width;\r
+ if ( (typeof w !== "number") || (w < 0) )\r
+ {\r
+ PS.Oops(fn + "Invalid width parameter");\r
+ return PS.ERROR; \r
+ }\r
+ w = Math.floor(w);\r
+ if ( w > PS.GRID_MAX )\r
+ {\r
+ w = PS.GRID_MAX;\r
+ }\r
+ \r
+ h = img.height;\r
+ if ( (typeof h !== "number") || (h < 0) )\r
+ {\r
+ PS.Oops(fn + "Invalid height parameter");\r
+ return PS.ERROR; \r
+ }\r
+ h = Math.floor(h);\r
+ if ( h > PS.GRID_MAX )\r
+ {\r
+ h = PS.GRID_MAX;\r
+ }\r
+\r
+ // draw the image onto the offscreen canvas\r
+\r
+ try\r
+ {\r
+ ctx = PS.ImageCanvas.getContext("2d");\r
+ ctx.drawImage(img, 0, 0);\r
+ }\r
+ catch (err)\r
+ {\r
+ PS.Oops(fn + "failed @ 1 [" + err.message + "]");\r
+ return PS.ERROR;\r
+ } \r
+ \r
+ // fetch the data and return it\r
+\r
+ try\r
+ {\r
+ imgData = ctx.getImageData(0, 0, w, h);\r
+ }\r
+ catch (err2)\r
+ {\r
+ PS.Oops(fn + "failed @ 2 [" + err2.message + "]");\r
+ return PS.ERROR;\r
+ }\r
+ \r
+ // imgData is read-only for some reason\r
+ // so make a copy of it\r
+ \r
+ imgData2 = {};\r
+ imgData2.width = imgData.width;\r
+ imgData2.height = imgData.height;\r
+ \r
+ d1 = imgData.data;\r
+ len = d1.length;\r
+ d2 = []; // new data array\r
+ i = 0;\r
+ j = 0; \r
+ \r
+ // if alpha data is not wanted, remove it\r
+ \r
+ if ( !alpha )\r
+ { \r
+ d2.length = (len / 4) * 3;\r
+ while ( i < len )\r
+ {\r
+ d2[j] = d1[i];\r
+ i += 1;\r
+ j += 1;\r
+ d2[j] = d1[i];\r
+ i += 1;\r
+ j += 1;\r
+ d2[j] = d1[i];\r
+ i += 2; // skip alpha\r
+ j += 1; \r
+ }\r
+ imgData2.pixelSize = 3; \r
+ }\r
+ else\r
+ {\r
+ d2.length = len;\r
+ while ( i < len )\r
+ {\r
+ d2[j] = d1[i];\r
+ i += 1;\r
+ j += 1;\r
+ d2[j] = d1[i];\r
+ i += 1;\r
+ j += 1;\r
+ d2[j] = d1[i];\r
+ i += 1;\r
+ j += 1;\r
+ d2[j] = d1[i];\r
+ i += 1;\r
+ j += 1; \r
+ } \r
+ imgData2.pixelSize = 4;\r
+ }\r
+ \r
+ imgData2.data = d2;\r
+ return imgData2; \r
+};\r
+\r
+PS.ImageBlit = function ( imgdata, xpos, ypos )\r
+{\r
+ "use strict";\r
+ var fn, size, bytes, w, h, pixsize, ptr, drawx, drawy, i, j, r, g, b, a, k, bead;\r
+\r
+ fn = "[PS.ImageBlit] ";\r
+\r
+ // verify data format\r
+\r
+ if ( typeof imgdata !== "object" )\r
+ {\r
+ PS.Oops(fn + "parameter 1 is not a table");\r
+ return PS.ERROR;\r
+ }\r
+ \r
+ bytes = imgdata.data; // check this?\r
+ \r
+ w = imgdata.width;\r
+ if ( typeof w !== "number" )\r
+ {\r
+ PS.Oops(fn + "imgdata.width is not a number");\r
+ return PS.ERROR; \r
+ }\r
+ w = Math.floor(w);\r
+ if ( w < 1 )\r
+ {\r
+ PS.Oops(fn + "imgdata.width < 1");\r
+ return PS.ERROR; \r
+ }\r
+\r
+ h = imgdata.height;\r
+ if ( typeof h !== "number" )\r
+ {\r
+ PS.Oops(fn + "imgdata.height is not a number");\r
+ return PS.ERROR; \r
+ }\r
+ h = Math.floor(h);\r
+ if ( h < 1 )\r
+ {\r
+ PS.Oops(fn + "imgdata.height < 1");\r
+ return PS.ERROR; \r
+ }\r
+ \r
+ pixsize = imgdata.pixelSize;\r
+ if ( (pixsize !== 3) && (pixsize !== 4) )\r
+ {\r
+ PS.Oops(fn + "invalid pixelSize");\r
+ return PS.ERROR; \r
+ }\r
+ \r
+ size = w * h * pixsize;\r
+ if ( bytes.length !== size )\r
+ {\r
+ PS.Oops(fn + "invalid data length [" + imgdata.data.length + "]");\r
+ return PS.ERROR; \r
+ }\r
+ \r
+ if ( (xpos === undefined) || (xpos === PS.DEFAULT) )\r
+ {\r
+ xpos = 0;\r
+ }\r
+ else if ( typeof xpos !== "number" )\r
+ {\r
+ PS.Oops(fn + "parameter 2 is not a number");\r
+ return PS.ERROR; \r
+ }\r
+ else\r
+ {\r
+ xpos = Math.floor(xpos);\r
+\r
+ // exit if drawing off grid\r
+\r
+ if ( (xpos >= PS.Grid.x) || ((xpos + w) < 1) ) \r
+ {\r
+ return true;\r
+ }\r
+ }\r
+\r
+ if ( (ypos === undefined) || (ypos === PS.DEFAULT) )\r
+ {\r
+ ypos = 0;\r
+ }\r
+ else if ( typeof ypos !== "number" )\r
+ {\r
+ PS.Oops(fn + "parameter 3 is not a number");\r
+ return PS.ERROR; \r
+ }\r
+ else\r
+ {\r
+ ypos = Math.floor(ypos);\r
+ \r
+ // exit if drawing off grid\r
+ \r
+ if ( (ypos >= PS.Grid.y) || ((ypos + h) < 1) ) \r
+ {\r
+ return true;\r
+ }\r
+ }\r
+ \r
+ ptr = 0; \r
+ drawy = ypos;\r
+ for ( j = 0; j < h; j += 1 )\r
+ {\r
+ drawx = xpos;\r
+ if ( drawy >= PS.Grid.y ) // exit if off bottom of grid\r
+ {\r
+ break;\r
+ }\r
+ if ( drawy >= 0 ) // is this row visible?\r
+ {\r
+ for ( i = 0; i < w; i += 1 )\r
+ {\r
+ if ( (drawx >= 0) && (drawx < PS.Grid.x) )\r
+ {\r
+ r = bytes[ptr];\r
+ if ( typeof r !== "number" )\r
+ {\r
+ PS.Oops(fn + "non-numeric red at position " + ptr);\r
+ return PS.ERROR;\r
+ }\r
+ r = Math.floor(r);\r
+ if ( r < 1 )\r
+ { \r
+ r = 0;\r
+ }\r
+ else if ( r > 255 )\r
+ {\r
+ r = 255;\r
+ }\r
+ \r
+ g = bytes[ptr + 1];\r
+ if ( typeof g !== "number" )\r
+ {\r
+ PS.Oops(fn + "non-numeric green at position " + ptr);\r
+ return PS.ERROR;\r
+ }\r
+ g = Math.floor(g);\r
+ if ( g < 1 )\r
+ { \r
+ g = 0;\r
+ }\r
+ else if ( g > 255 )\r
+ {\r
+ g = 255;\r
+ }\r
+ \r
+ b = bytes[ptr + 2];\r
+ if ( typeof b !== "number" )\r
+ {\r
+ PS.Oops(fn + "non-numeric blue at position " + ptr);\r
+ return PS.ERROR;\r
+ }\r
+ b = Math.floor(b);\r
+ if ( b < 1 )\r
+ { \r
+ b = 0;\r
+ }\r
+ else if ( b > 255 )\r
+ {\r
+ b = 255;\r
+ }\r
+ \r
+ if ( pixsize === 4 )\r
+ {\r
+ a = bytes[ptr + 3];\r
+ if ( typeof a !== "number" )\r
+ {\r
+ PS.Oops(fn + "non-numeric alpha at position " + ptr);\r
+ return PS.ERROR;\r
+ }\r
+ a = Math.floor(a / 2.55); // convert 0-255 range to 0-100\r
+ if ( a < 1 )\r
+ { \r
+ a = 0;\r
+ }\r
+ else if ( a > PS.DEFAULT_ALPHA )\r
+ {\r
+ a = PS.DEFAULT_ALPHA;\r
+ }\r
+ }\r
+ else\r
+ {\r
+ a = PS.DEFAULT_ALPHA; \r
+ }\r
+ \r
+ k = drawx + (drawy * PS.Grid.x); // get index of bead\r
+ bead = PS.Grid.beads[k];\r
+ \r
+ bead.dirty = true; // mark this bead as explicitly colored\r
+ if ( a < PS.DEFAULT_ALPHA ) // Calc new color based on alpha-adjusted color of existing bead\r
+ {\r
+ bead.alphaRed = PS.Dissolve( PS.Grid.bgRed, bead.alphaRed, a );\r
+ bead.alphaGreen = PS.Dissolve( PS.Grid.bgGreen, bead.alphaGreen, a );\r
+ bead.alphaBlue = PS.Dissolve( PS.Grid.bgBlue, bead.alphaBlue, a );\r
+ bead.color = PS.RGBString( bead.alphaRed, bead.alphaGreen, bead.alphaBlue );\r
+ }\r
+ else\r
+ {\r
+ bead.alphaRed = r;\r
+ bead.alphaGreen = g;\r
+ bead.alphaBlue = b;\r
+ bead.color = PS.RGBString( r, g, b );\r
+ }\r
+ \r
+ // Do NOT change alpha value of existing bead!\r
+ \r
+ bead.red = r;\r
+ bead.green = g;\r
+ bead.blue = b;\r
+ \r
+ if ( bead.visible )\r
+ {\r
+ bead.flashPhase = 100; // stops flash in progress\r
+ bead.colorNow = bead.color;\r
+ PS.DrawBead(bead);\r
+ } \r
+ }\r
+ ptr += pixsize;\r
+ drawx += 1;\r
+ }\r
+ }\r
+ else\r
+ {\r
+ ptr += (w * pixsize); // skip this row\r
+ } \r
+ drawy += 1; \r
+ }\r
+ \r
+ return true; \r
+};\r