	// <![CDATA[
		//config:
		
		/*
			TO DO:
			* auto-build image height/width
			* controls for rows/cols
		*/
		
		
		var gRows = 3;
		var gColumns = 3;
		var gWidth = 60; //of each piece
		var gHeight = 60; //of each piece
		var gImgSrc = "http://www.juaraturtleproject.com/images/puzzle.jpg";
		var gCorrectState = []; //in sequential order, the DivIDs of each piece (0,1,2...)
		var gCurrentState = [];	//the DivIDs as the are currently laid out	
		var gEmptyIndex = 0; //where, in the current state, the empty square is
		var gMoves = []; //each element is an index that represents where in the current state the tile is move to the empty state is
		var gAnimantionTime = 1000;
		var gRandomness = 2980; //max number of (unique, ideally) steps to take to originally shuffle the puzzle

		//overloaded function that will handle a list of positions to move sequentially...(so it can handle solve...)
		function swap(moverIndices) {

			if (moverIndices.length == 0) {
				//unbind all the now stale locations
				$("div.piece").unbind("click");
				$("div.piece").unbind("hover");
		
				bindClickFunctions();
				
				return;
			}
			
			var moverIndex = moverIndices.pop();
			var mover = gCurrentState[moverIndex];
			var empty = gCurrentState[gEmptyIndex];
			var moverPos = mover.position();
			var emptyPos = empty.position();
			
			mover.animate({ 
        		left: emptyPos.left,
        		top: emptyPos.top
        	}, gAnimantionTime, "swing", 
        		function() {
					empty.css({ 
						left: moverPos.left,
						top: moverPos.top
					})
					
					//now swap the indices
					gCurrentState[gEmptyIndex] = mover;
					gCurrentState[moverIndex] = empty;
					gEmptyIndex = moverIndex;

					swap(moverIndices); //recursive call to keep moving...
		
				}
        	);
		}
		
		function solve() {						
			swap(gMoves);  //recursive...
		}
		
		//TODO: don't repeat the most recent step
		function randomizeAndDraw (steps) {
			
			if (typeof( steps ) == 'undefined' ) { 
				//first time through - set it all back up...
				steps = gRandomness;
				gMoves = []; //start over

				//now clone this into the current state...
				gCurrentState = [];
				for (var index = 0; index < gCorrectState.length ; index ++) {
					gCurrentState.push(gCorrectState[index]);
				}

				//null out the last value - TODO: make this configurable...right now, just make it the last tile
				gEmptyIndex = gRows * gColumns - 1;
				gCurrentState[gEmptyIndex].text(""); //remove the picture
				gCurrentState[gEmptyIndex].css("z-index", -100); //make sure it's behind the pictures


			}
			
			if (steps < 1) {

				//then draw the current state...
				for (var index = 0; index < gCurrentState.length ; index ++) {
					var coords = indexToRowCol(index);
					piece = gCurrentState[index];
					piece.css({
							left: gWidth * (coords.col - 1) + "px",
							top: gHeight * (coords.row - 1) + "px",
						});
					piece.show();
				}
			
				bindClickFunctions();  

				return;

			}

			gMoves.push(gEmptyIndex); //b/c we're about to move a real piece there...
			
			//find all candidates for a move
			var all_moveables = findMoveables();
			
			//let's not repeat ourselves - don't take the last move or the move before that...
			moveables = jQuery.grep(all_moveables, function(value) {
				if (gMoves[gMoves.length - 1] && value == gMoves[gMoves.length - 1]) {
					//don't return
				} else if (gMoves[gMoves.length - 2] && value == gMoves[gMoves.length - 2]) {
					//don't return
				} else {
					return value;
				}				
			});
			
			var moverIndex = moveables[Math.floor(Math.random() * moveables.length)];

			var mover = gCurrentState[moverIndex];
			var empty = gCurrentState[gEmptyIndex];

			gCurrentState[gEmptyIndex] = mover;
			gCurrentState[moverIndex] = empty;
			gEmptyIndex = moverIndex;

			return randomizeAndDraw(steps - 1);
						
		
		}
				
		
		function solved() {

			//var solved = true;
			var solved = false;

			for (row = 1 ; row <= gRows ; row++) {
				for (col = 1 ; col <= gColumns ; col++) {
					index = (row - 1) * gColumns + col - 1;
					if (gCurrentState[index] != gCorrectState[index]) {
						solved = false;
					}
				}
			}

			return solved;

		}
		
		function findMoveables() {
			var indices = [];
			
			//look for a tile that can slide right - make sure not in left-most row
			if ( ( gEmptyIndex + 1) % gColumns != 1 ) {
				//we're NOT on an left edge - like 1 % 5
				indices.push(gEmptyIndex - 1);
			}

			//look for a tile that can slide right - make sure not in left-most row
			if ( ( gEmptyIndex + 1) % gColumns != 0 ) {
				//we're NOT on an right edge - like 5 % 5
				indices.push(gEmptyIndex + 1);
			}

			//look for a tile that can slide down - need to be in the second row
			if ( ( gEmptyIndex + 1) > gColumns ) {
				indices.push(gEmptyIndex - gColumns);
			}

			//look for a tile that can slide up - need not be in last row
			if ( ( gEmptyIndex + 1) <= gRows * gColumns - gColumns ) {
				indices.push(gEmptyIndex + gColumns); 
			}
			
			return indices;
		}
				
		function bindClickFunctions() {			
			//make sure to handle the 0 indexing!
			var indices = findMoveables();
			
			$.each(indices, function() {
				var index = this;
				var obj = gCurrentState[index];
				obj.click(function(e) {
		      		gMoves.push(gEmptyIndex) //KEY - we push the new location...and if we ever walk back down the solve path, we move this index back to the current empty index
		      		swap([index]);

    			});
				obj.hover(
				  function () {
					$(this).addClass("hovering")
				  }, 
				  function () {
					$(this).removeClass("hovering");
				  }
				);
			})

		}
		
		//given an array location, return
		function indexToRowCol(index) {
			var row = Math.floor(index / gColumns) + 1; 
			var col = Math.floor(index % gColumns) + 1; 
			return {"row": row, "col": col};
		}
		
		function initPuzzle() {
			$("#puzzle > *").remove();
			gCorrectState = [];
			gCurrentState = [];
			gMoves = [];

			$("#puzzle").css({
				width: gWidth * gColumns + "px",
				height: gHeight * gRows + "px",
			});
	
			for (var row = 1 ; row <= gRows ; row++) {
				for (var col = 1 ; col <= gColumns ; col++) {
					
					var left = gWidth * col;
					var top = gHeight * row;
					var id = (row - 1) * gColumns + col;
					
					var piece = $('<div id="' + id + '" class="piece"></div>').appendTo($("#puzzle"));
					piece.css({
						left: gWidth * col + "px",
						top: gHeight * row + "px",
						width: gWidth,
						height: gHeight,
					});
					
					
					var img = $("<img></img>").attr({ 
						src: gImgSrc,
						title: "",
						alt: (row - 1) * gColumns + col //hack to know the correct order
					}).appendTo(piece);
					
					//this sets up the clipping...
					img.css({
						left: -1 * gWidth * (col - 1) + "px",
						top: -1 * gHeight * (row - 1) + "px",
						position: "absolute"
					});
					
					gCorrectState.push(piece);
					
				}
			}
			
			//...and shuffle the current state...
			randomizeAndDraw();
		}
