// JavaScript Document
/******************************************
TODO:



		
* after the warning messages when you switch a mode, if you switch back, it just plows through where you were before.		
		
	


	

 
				
* Fix the non-showing disabled play controls in IE.
	*It has something to do with the picture, somehow, because putting in a background color works fine.
	*It appears to be taht IE only handles one ID.class rule, whichever it comes upon first.
	*It's a known bug.  No work arounds?
		* http://jan.moesen.nu/code/html-css/ie-id-class-combo-bug/
	
	



*Ideal: add support for keyboard shortcuts?
	*One function would add them, depending on if they are standards compliant or... IE.
	*The functions would call interfaceCommand("COMMAND"), which would determine if they were allowed to be used
	*The problem with this is that event functions don't accept arguments--so I'd have to make a lot of new functions, right?
	*wait, no. just two functions--once for standards-style events, another for IE style.  these would function as dispatchers
	or neccesary go betweens, and could call the correct functions from a simple switch statement. 
	*BUT this feature gets around an important safety control--the idea that you can't do something at some points.
		*like, when cards are invisible, you can't click on the showState button, but if there were a keyboard shortcut, all hell would break lose. 
	
*Fix the printlayout problem where the form is offset by 25px automatically (even when body margin is 0!)



*Ideal: fix that annoying scrollbar in the instruction div (using iframes? shudder.)
	*but really, iframes don't require that much to change--it just requires that the private card update function correctly accesses it.
	*so really maybe not a HUGE deal
	*low priority
	*THIS WAS DONE by me--check out the iframe folder in the old section.  the problem is, however, that it a)doesn't fix the problem
		and b) even when you do fix the problem, it's much too slow. oh well.


******************************************/



/*
Spaces are numbered as follows:
 ___ ___ ___ ___ ___ ___ ___
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|___|___|___|___|___|___|___|
| 8 | 9 | 10| 11| 12| 13| 14|
|___|___|___|___|___|___|___|
*Spaces 1,2,3,4,5 and 8,9,10,11,12 are occupied when the puzzle is in home position
*Spaces 3,4,5 and 10,11,12 are in the flip area.
*All arrays that represent a puzzle arrangement also have an index of zero
*Unfilled spaces are filled with -1, while filled spaces are filled with the number of the occupying token.
*/
//The target solution is what the puzzle array should look like when it's done--in other words, which tokens should occupy which
//spaces when the puzzle is complete. This allows for easy variations from the standard 0-9 theme, and gives the puzzle replay value.
var targetSolution = new Array(-1,0,1,2,3,4,-1,-1,5,6,7,8,9,-1,-1);
//this array holds all the pre-defined moves to accomplish a task.  For example, in step 7, there are is one algorithm to get the target token from position 7 to where it needs to be.
//this array is filled auotmatically by a function call within init.  Without this array filled, the solver cannot figure out what to do to solve the puzzle.
var moveSolutionArray = new Array();
//These hold the card objects that hold the properties of the cards displayed in the instruction area.
var instructionCards = new Array();
//The workingPuzzle is the main puzzle that the game works with.  It is iniated to the solved position.
var workingPuzzle = new Array(-1,0,1,2,3,4,-1,-1,5,6,7,8,9,-1,-1);
//The originalPuzzle is the puzzle that they started from.  This allows us to reset back to the beginning easily.
var originalPuzzle = new Array(-1,0,1,2,3,4,-1,-1,5,6,7,8,9,-1,-1);
/*
IMPORTANT: In almost all cases when a function asks for a puzzleArray, it really wants a string with the values delimited by a pipe character.
The reason for this is that if you send an array as an argument, the function receives a reference to the array, and any temporary manipulations
would show up in the real copy of the array.  For this reason, before sending an array, join it using the command array.join("|") 
Each function that accepts what appears to be an array as an argument actually takes a pipe-delimited string representation of the array.
This allows the array to easily be copied; each function manipulates the array it receives before returning the result, so it would 
mess up other proccesses if all calculations were performed on a single copy of the puzzle.
var tempPuzzle = targetSolution.join("|");
var workingPuzzle= tempPuzzle.split("|");
*/
//snapshot simply holds a temporary copy of the workingpuzzle from a previous state.  It is used by the animate methods to figure out where the 
//pieces used to be and set offsets.
var snapShotPuzzle= new Array();
//saves the state of the playPuzzle right before going into play mode, so if a user gives up they can keep up where they left off.
var playPuzzle = new Array();
//the goal area supports all the animation methods of the actual puzzle, so it has many of the same variables.
var snapShotSolution=new Array();
//The puzzle offsets are used extensively in the animation routines.  Technically, what happens is:
//before the puzzle is transformed (by a rowshift or a flip), a snapshot is taken.
//the puzzle is transformed, so the pieces all rest in their final locations
//a function is called that sees where the pieces are now compared to where they used to be, and sets the offset so the pieces, in their new locations
//display as being at their old locations.  As the offsets are gradually decayed, the piece moves closer to its true final location.
//this has the added benefit of, if an animation is cancelled, all that you must do is set all the offsets to 0.
//offsets are natively taken into account by the funciton renderGame which is the only function called to position the pieces.
var puzzleOffset = new Array();
var solutionOffset=new Array();

//this variable is useful for when you switch to solve mode from play mode (assuming you've got some things set correctly)
//or for when it's determined earlier that the puzzle is already done.
//generally, this is set to true by any presection or section message;
//the idea is that from the point you set it until the next section message, it will skip.
var enableSkippedMessages=true;
//this function is used once to suppress the showing of a prepresection message (to be implemented in other types?);
var enableNormalMessage=true;
//used to skip a single pause (used in conjunction with a skipped message, normally.
var enablePausing=true;

//Neccesary for showing card state:
//set to true when the animation has been initalized, and before it has been returned to trueState.
var outOfState=false;
var returningState=false;
var settingState=false;
var trueStateSnapShot= new Array();
//step count is a counter used by AppendCards to enable showState functionality for cards added with something other than fillCardsSection.
var currentStepCount=0;


//This function helps keep track of which function (runNextStep or animateChange) should be set when resumePlayback was called.
//0=None set
//1=Step function
//2=animate function
var callbackFunctionID=0;


//GLOBAL SWITCHES
//these switches are never set programmatically but still retain their function.
//if doAnimate is false, smooth animation is disabled (and all calls to the animateChange function are disabled, too), and the puzzle will appear to jump
//from state to state (there are still delays between states,otherwise we wouldn't be able to understand what was going on!)
var doAnimate=true;
//if suppress messages is true, then the only messages that will display are ones flagged important.  this is realistically deprecated because the message system
//now uses much more complex rules to determine when to show messages, but this flag still retains all of its old functionality.
var suppressMessages=false;
//we can set it so that whenever we do an animation section, all of the movements end at the same time.  this would mean that a row that had to move to the left 
//two positions would move at normal speed, but in the same animation section, if the other row had to move only one position, it would travel at half the speed.
//this change is effected totally by the decayOffset function.  The animation looks cool but can be distracting, so it can be turned off either here,
//or temporarily (programmatically) by different functions calling it.
//this one's the working value--the one that the functions actually read. However, sometimes some functions will set it to true for just one cycle,
//at which point it will be returned to...
var animateSyncFinish=false;
//the default value.  This is the important one to set how you want it. 
//NOTE: currently, animateSyncFinish is always used for beginPuzzle transitions.
var defaultAnimateSyncFinish=false;
//although beginPuzzle accepts the parameter doSmoothTransition as an argument (which is very useful), for many functions calling it, it's simply a global value
//using this global value sets sort of the default, and makes using smooth transition easy to turn on or off in one place for anything that does the default.
var useSmoothTransition=true;
//the following global switch turns on or off automatic highlighting/visual cues for when a space is "locked".  It's very useful for beginners to see
// how each stage has a discrete goal, but it can be annoying and also can make the puzzle less hard for more advanced users.  therefore, it should be
//able to be set by the user.
var doHighlight=true;
//dimming the interface gives users a better idea of what is going on.  however, on some computers, it can be a little slow.
//also, becuase IE is horrible, it's very hard to get it to look good on all browsers.
var enableInterfaceDimming=true;
//The interface can automatically mark old cards as done for bystep mode.  the switch is here:
var autoMarkCardsDone=true;
//Keyboard shortcuts are an experimental feature, so it's possible to turn them off.  Note that this flag currently only
//forces them to not be set in the init() function; modifying this flag after the page has loaded does nothing.
var enableKeyboardShortcuts=false;

//showState allows us to animate and show the state that the puzzle should be in after a given card.
//it'll be very cool but is not yet functional.  here is the switch:
var enableShowState=true;
//***********


//currentUseMode:
// PLAY
// SOLVE (default)

var currentUseMode="SOLVE";

//currentDisplayMode has to do with how messages are displayed and when we stop.
/*Valid values are:
BYSECTION (Normal (default))
BYSTEP (Beginner)
NOHELP (Auto)
*/

//the following are the arguments that can be set from URL.
//can be set by args in the URL.
var startMode="SOLVE";
var startPuzzle="";
var startGoal="";
var startLockNewPuzzle=false;
var startLockNewGoal=false;
var startLockMode=false;
var startLockDisplayMode=false;
var startDisplayMode=false;


var currentDisplayMode="BYSECTION";
//Because sometimes if you switch between modes in the middle of a section, things will mess up, we only set a new mode when the instruction set allows us.
//So, when you choose a new display option, you actually set a new value for switchDisplayMode, which is actually like a temporary displayMode buffer.
//When the instruciton set calls SWITCH, currentDisplayMode is set to switchDisplayMode.
var switchDisplayMode="BYSECTION";
//the currentSolutionString is the raw solution string that contains the steps needed to solve the puzzle.  
//it is formatted as so: 
//1,2;2,0~#~2,0~2,0;2,1;1;2
//where a ~ separates different sections, a # denotes an empty (skipped) section that was unnecessary because the token was already there, a ; delimits
//different steps within a section, and a , delimits the different row positions in a step.  Each step consists of positioning the two rows and flipping.
var currentSolutionString="#NOTHING#";
//the instructionSet is the solutionString translated into something that runNextStep can parse.  Messages and other flags are inserted, and it contains
//details for what should be done to show the puzzle being solved.
//whole actions are delimited by a pipe character, while arguments (and action types!) are delimited by colons.
var instructionSet="#NOTHING#";
//this flag is used by pausePlayback to tell runNextStep and animateChange that the next time they run (whichever is first) it should not set the callback function
//but rather should call the pausePlayback function telling it which function should be called on resume.
var doSetCallback=true;
//a flag that is set by pausePlayback.  the actual pausing functionality, however, is achived by merely not having runNextStep or animateChange call themselves back.
var isPaused=false;
//a temporary value set by inputPuzzle when it opens.  right before it opens, it sees if playback is paused or not and saves it to this variable.  Then, it
//pauses playback no matter what.  If the inputPuzzle window is canceled, then it will return the playback state to whatever it was before, by reading this variable.
var resumeOnCancel=false;
//sometimes you switch to playmode for only a single stage, with the goal being that stage. if it's -1, it's not in that  mode.  if it's not -1, then
//the step will end when you achieve that goal. 
//(because of the way it compares, it needs to be -2 to be valid)
var playModeStepGoal=-2;
//we only want to display the opening playback message once
var firstInstructionSet=true;
//when you set a timeout, you get an ID back.  this ID is used to cancel the timeout if you ever need to.
//the two functions that actually setTimeOuts are runNextStep and animateChange--and they save their interval ID's to these values, so it's easy to stop animation 
//quickly, without using pausePlayback (which requires that another cycle runs)
var callNextStepInterval;
var callAnimateInterval;
//These next two values are written to by each prepresection and prestep message, and are used for replaySegment functionality.
var savedStep="#NOTHING#";
var savedSection="#NOTHING#";
var savedStepSnapShot= new Array();
var savedSectionSnapShot = new Array();
//the next value is used to restore the messageArea to the correct state after a replay.
var lastMessageCommand="";
//the following values are all used for animation/rendering.  animateInterval is most important in terms of speed.
//to keep the apparent motion of the tokens the same, make sure you keep the animateInterval and decayInterval proportional
//e.g. if you make animateinterval 60, (calling animation less, making performance better) then you'd likewise double decay interval to 12.
var tokenOffsetTop=0;
var tokenOffsetLeft=0;
var tokenWidth=40;
var tokenHeight=40;
var solutionTokenOffsetTop=0;
var solutionTokenOffsetLeft=0;
var solutionTokenWidth=20;
var solutionTokenHeight=20;

//holds the loaded images. 
var preLoadImg=new Array();



var currentInterval=500;
var animateInterval=30;
var decayInterval=6;

//this variable is automatically added to the beginning of the instructionSet when it is created (and subsequently set to "VOID" again).  
//If you want to add something, make sure to append a pipe character and the command, just in case there's already a command.
//It's set to VOID so that you don't have to worry about if you're the first action in it or not--you just always append a pipe then your action when writing to it.
var prependAction="VOID";
//this is a type that simply helps keep offset values for each token together.
//an array of these, one for each piece, is created in init().
function OffsetValue(top,left) {
	this.top=top;
	this.left=left;
	this.highlighted=false;
	this.locked=false;
}

//INITIALIZE
//the browser type helps us figure out what the user's browser can and can't do, allowing work arounds.
 var browser= new BrowserType();
init();


//Code to detect user's browser type:
//it has no methods because the information it checks never changes.
function BrowserType () {
	this.isIE=false;
	this.isNN=false;
	
	this.canAlphaPNG=true;
	this.version="0.0";
	var userAgent, counter;
	userAgent=navigator.userAgent;
	counter=userAgent.indexOf("MSIE");
	if (counter>=0) {
		this.isIE=true;
		this.canAlphaPNG=false;
		this.version = parseFloat(userAgent.substr(counter + 4)); //Parse the first float that comes after the location of MSIE (which is four characters)
		//We've set everything, we're done
		return;
	}
	counter=userAgent.indexOf("Gecko");
	if (counter>=0) {
		this.isNN=true;
		this.version=6.1;
		return;
	}
	counter=userAgent.indexOf("Netscape6/");
	if(counter>=0) {
		this.isNN=true;
		this.version=parseFloat(userAgent.substr(counter + 10));
		return;
	}
	//IF it's anything else, then we won't know it, but we'll assume they have support for Alpha PNG
}

function init() {
	//all the neccesary function calls go here.
	//this is only called once.
	//fills the pre-defined solutions into the array.
	fillMoveSolutionArray();
	//makes an offset register for each piece
	makeOffsetRegister();
	//makes the card objects to go along with all the nodes on the page.
	makeCards();
	//preloads images.
	preLoadImages();
	attachKeyboardShortcuts();
	parseURLArgs();
	
	
	
}

function parseURLArgs() {
	//looks at the url, splits it at "?", then interprets the strings that it knows ater that.
	var workingURL=window.location.href;
	workingURL=workingURL.split("?");
	var argPairs;
	var result;
	var row;
	var workingPair;
	//everything should be set by default to default values. only override if we have an arg. 
	
	if  (workingURL.length>1) {
		//args given, parse them.
		workingURL[1]=unescape(workingURL[1]);
		argPairs=workingURL[1].split("&");
		
		for (s=0;s<argPairs.length;s++) {
			
			workingPair=argPairs[s].split("=");
			switch (workingPair[0].toUpperCase() ) {
				case "MODE":
					
					switch(workingPair[1].toUpperCase()) {
						case "SOLVE":
							startMode=workingPair[1].toUpperCase();
							break;
						case "PLAY":
							startMode=workingPair[1].toUpperCase();
							break;
						default:
							alert("Unexpected value for argument type " + workingPair[0] + ": " + workingPair[1]);
							//automatically falls back on default value, by not writing to it. 
							break;
						
					}
					break;
				case "PUZZLE":
					//given a puzzle in the format 0,1,2,3,4;5,6,7,8,9,0
					//we have to fill in the unknowns and get it looking like:
					//"-1|0|1|2|3|4|-1|-1|5|6|7|8|9|0|-1|-1";
					workingPair[1]=workingPair[1].replace(/,/g, "|");
					
					row=workingPair[1].split(";");
					result="-1|" + row[0] + "|-1|-1|" + row[1] + "|-1|-1";
					
					startPuzzle=result;
					break;
				case "GOAL":
					//given a puzzle in the format 0,1,2,3,4;5,6,7,8,9,0
					//we have to fill in the unknowns and get it looking like:
					//"-1|0|1|2|3|4|-1|-1|5|6|7|8|9|0|-1|-1";
					workingPair[1]=workingPair[1].replace(/,/g, "|");
					
					row=workingPair[1].split(";");
					result="-1|" + row[0] + "|-1|-1|" + row[1] + "|-1|-1";
					startGoal=result;
					break;
				case "LOCK":
					//accepts a comma delimited list of things to lock.  the only things allowed as of right now are
					//startLockNewPuzzle and startLockNewGoal.
					workingPair[1]=workingPair[1].split(",");
					for(b=0;b<workingPair[1].length;b++) {
						switch(workingPair[1][b].toUpperCase()) {
							case "NEWPUZZLE":
								startLockNewPuzzle=true;
								break;
							case "NEWGOAL":
								startLockNewGoal=true;
								break;
							case "MODE":
								startLockMode=true;
								break;
							case "DISPLAYMODE":
								startLockDisplayMode=true;
								break;
							default:
								alert("Error: unexpected value for argument type " + workingPair[0] + ": " + workingPair[1]);
								break;
						}
					}
					
					break;
				case "":
					//someone just forgot to add the argpair.
					//technically, not supported, but just pretend like it didn't happen.
					break;
				case "DISPLAYMODE":
					startDisplayMode=workingPair[1].toUpperCase();
					break;
				default:
					alert("Unexpected argument in URL: " + workingPair[0]);
					break;
			}
		}
	}
	
	
	
}

function attachKeyboardShortcuts() {
	//called once
	
	//currently absolutely non-functioning.
	//see brainjar for help on this subject.
	if (!enableKeyboardShortcuts) {
		return;
	}
	
	if (browser.isIE) {
		
		
		
	}
	else {
		//use the standards-compliant way
		
	}
	
	
}

function preLoadImages() {
	//called once.
	//this function simply preloads all of the card images and other images that do not start out pre-loaded.
	//imgArray is the list of pathnames. 
	var imgArray= new Array();
	//uses the global var preLoadImg[] to hold the images. 
	
	//automatically add all the move cards to the list.
	for (s=0;s<3;s++) {
		for(d=0;d<3;d++) {
			//to get the next unused index, use imgArray.length
			imgArray[imgArray.length]="images/Card_" + s + "_" + d + "_F.png";
			imgArray[imgArray.length]="images/Card_" + s + "_" + d + "_N.png";
		}
	}
	imgArray[imgArray.length]="images/Card_HOME_N.png";
	imgArray[imgArray.length]="images/Card_HOME_F.png";
	imgArray[imgArray.length]="images/Card_INFO.png";
	imgArray[imgArray.length]="images/Card_INFO_2.png";
	//now, preload all the images in the array.
	for(s=0;s<imgArray.length;s++) {
		preLoadImg[s]=new Image();
		preLoadImg[s].src=imgArray[s];
	}
}



function resetPuzzle() {
	//this is called whenever a new solution set is being used.
	//it basically just re-initializes all values.
	
	
	 currentSolutionString="#NOTHING#";
 instructionSet="#NOTHING#";
 clearCards();
clearHighlights();
 //clear offsets
 for (g=0;g<puzzleOffset.length;g++) {
	 puzzleOffset[g].top=0;
	 puzzleOffset[g].left=0;
 }
 doSetCallback=true;
 isPaused=false;
 resumeOnCancel=false;
//the id of the interval that calls runNextStep()
 callNextStepInterval=-1;
 callAnimateInterval=-1;
 workingPuzzle=positionRow(1, 2, workingPuzzle.join("|"));
  workingPuzzle=positionRow(2, 2, workingPuzzle.join("|"));
 
}

function makeCards() {
	for (j=0;j<16;j++) {
		instructionCards[j]= new InstructionCard(j);
	}
	
}

function makeOffsetRegister() {
	//This function is very simple. 
	//it creates an offsetvalue object for each non-empty token.
	
	for (i=0;i<10;i++) {
		puzzleOffset[i]=new OffsetValue(0,0);
		solutionOffset[i]=new OffsetValue(0,0);
	}
}

function startGame() {
	//this function is called once by the body onLoad event to start the game.
	//the firefox rendering bug (which leaves the displayMode controls disabled  even on refresh, it combatted by calling the following:
	//(this takes advantage of the fact that this is like init, only called after the body has loaded.)
	//this funciton is where most of the startVariables from the URL args are applied.
	enablePlayControls(false);	
	
	document.ControlBarForm.reset();
	//we'll take advantage of the fact that this function is called by the body's onLoad event, after the forms are loaded
	//make sure the control bar form is reset (this must happen after the body has loaded)
	
	renderGame(workingPuzzle.join("|"));
	renderSolution(targetSolution.join("|"));
	takeSnapShot(workingPuzzle.join("|"));
	takeSolutionSnapShot(targetSolution.join("|"));
	//IE has an annoying bug where select boxes are on top of every other element no matter what.  Do 
	//minimize the effect of this, the box is simply invisible whenever it should be hidden (or dimmed!)
	//It starts out invisible, so we need to make it visible.
	//HOWEVER, as long as dim interface is called by inputPuzzle (and it always is), that will take care of it for us.
	
	//now we try to enable the default interface elements.  if they are locked, enable interface won't do it.
	enableInterface("NewGoalButton,NewPuzzleButton,StartOverButton,FreePlayButton,DisplayBySection,DisplayNoHelp,DisplayByStep,DisplayPrint",false);
	if (startGoal) {
		//load the goal to goal.
		takeSolutionSnapShot(targetSolution.join("|"));
		loadPuzzleToInput(startGoal);
		burnInputsToPuzzle(true);
	}
	if (startPuzzle) {
		//loadpuzzle to puzzle
		takeSnapShot(workingPuzzle.join("|"));
		loadPuzzleToInput(startPuzzle);
		burnInputsToPuzzle(false);
	}
	
	if (startDisplayMode) {
		//set it to what is specified.
		for (q=0; q<document.getElementById("ControlBarForm").DisplayMode.length;q++) {
			if(document.getElementById("ControlBarForm").DisplayMode[q].value.toUpperCase()==startDisplayMode.toUpperCase()) {
			document.getElementById("ControlBarForm").DisplayMode[q].checked=true;
		
			break;
			}
		}
		
	
		//now that we've selected it, go. 
		checkDisplayModeInput(true);
	}
	
	if (startMode.toUpperCase()=="PLAY") {
		
		startFreePlay();
		
		
		//but the problem is that the puzzle starts out solved by default. 
		//if a startGoal is not defined, make a random one: 
		
		if (!startPuzzle) {
			//load a random puzzle automatically, using some of inputPuzzle's hidden functions. 
			loadRandomPuzzle();
			//now we've got the random puzzle loaded to the (hidden) puzzle inputs, but we need to get it to the working puzzle
			burnInputsToPuzzle(false);
		}
		
		//and render game
		renderGame(workingPuzzle.join("|"));
		
		//and show the display speed box
		document.getElementById("DisplaySpeed").style.visibility="visible";
	}	
	else {
		//we got to solve mode by default, and inputPuzzle handles everything for us.
		if(startPuzzle) {
			//they've provided a puzzle, so we're going to skip the first input.
			//but we still need everything to think we did input it.
			//remember that the puzzle is still loaded in the inputs, but just hidden.
			submitPuzzle(false);
		}
		else {
			//they haven't provided a puzzle, so we're going to do the DEFAULT and show the inputpuzzle screen. 
		inputPuzzle(false,true);
		}
	
	}
	//now that everything's set up, do the final reveal
	//(we never touch the gameLoadingScreen again)
	document.getElementById("GameLoadingScreen").style.visibility="hidden";
	
	
}

function burnInputsToPuzzle(isTargetSolution) {
	//the following code is the important bits ripped from submitPuzzle(we don't want all those features!)
	var currentObject;
	if (isTargetSolution) {
		for(t=0;t<targetSolution.length;t++) {
			if(t==0 ||t==6 ||t==7 ||t==13 ||t==14) {
				//just drop in a negative one.
				targetSolution[t]=-1;
			}
			else {
				//drop in what is in the form.
				currentObject=document.getElementById("TokenInput" + t);
				
				
				targetSolution[t]=currentObject.options[currentObject.selectedIndex].value;
				
			}
		}
	}
	else {
		for(t=0;t<targetSolution.length;t++) {
			if(t==0 ||t==6 ||t==7 ||t==13 ||t==14) {
				//just drop in a negative one.
				workingPuzzle[t]=-1;
			}
			else {
				//drop in what is in the form.
				currentObject=document.getElementById("TokenInput" + t);
				
				
				workingPuzzle[t]=currentObject.options[currentObject.selectedIndex].value;
				
			}
		}
	}
	
}
function beginPuzzle(doRunNextStep,doSmoothTransition, fromUser) {
		//in the future PLAY use mode may be more all-encompassing, but for now all it is is the short bursts of goal-oriented stage solving.
		//for that reason, make sure that we're back in "Solve Mode"
		//luckily, the switchCurrentUseMode exits if the mode is already set, so this won't go into an infinite loop.
		//NEW fix on top of this one: inputPuzzle, startPuzzleOver are simply turned off in Play mode. 
		if(currentUseMode.toUpperCase()=="PLAY" && playModeStepGoal>=-1) {
			//only switch to solve mode if somehow we're in goalplaymode and got here; if we're in freeplaymode, don't switch.
			switchCurrentUseMode("SOLVE");
		}
		
		//this function is called by inputPuzzle when either targetSolution or workingPuzzle has successfully been set.
		//it's also called by startPuzzleOver.
		resetPuzzle();
		
		
		//it also copies workingPuzzle to originalPuzzle, if we are given the flag that this puzzle was input by the user
		//(that is, it wasn't automatically loaded after play mode)
		if (fromUser) {
		originalPuzzle=workingPuzzle.join("|");
		originalPuzzle=originalPuzzle.split("|");
		}
		
	renderGame(workingPuzzle.join("|"));
	renderSolution(targetSolution.join("|"));
	if (currentUseMode.toUpperCase()=="SOLVE") {
		
		currentSolutionString=makeSolutionString(workingPuzzle.join("|"));
		if (currentDisplayMode=="PRINT") {
			preparePrintText(currentSolutionString);
		}
		instructionSet=convertSolutionToInstructionSet(currentSolutionString, firstInstructionSet);
	}
	else {
		//if anything was in the prependAction section, it wasn't reset like we'd expect, so reset it now
		prependAction="VOID";
	}
	
	if(firstInstructionSet) {
		firstInstructionSet=false;
	}
	//start the chain of solving.
	//the run next step will take care of animating if animating is enabled.
	//before initiating an animation, you should always call allowAction to ensure that action may take place.

	//About the action chain that starts here: 
	//runNextStep takes care of most things.  Other functions are in charge of calling it after it ends.  
	//The other functions are either runNextStep itself, or, if it initated an animation, the animation function
	//is responsible for calling the next step when the animation terminates.
	if(doSmoothTransition && doAnimate) {
		animateSmoothChange(snapShotPuzzle.join("|"),snapShotSolution.join("|"));
		//animateSmoothChange computes offsets (among other things) and then calls animateChange--which, when it's done,
		//automatically calls runNextStep.  Therefore, animateSmoothChange is all that's neccesary.
		
	}
	else {
		if(doRunNextStep && currentUseMode.toUpperCase()=="SOLVE") {
		runNextStep();
		}
	}
	
	
}

function animateSmoothChange(gameSnapShot,goalSnapShot) {
	//gameSnapShot and goalSnapShot are pipe-delimed puzzle arrays.
	
	//we're going to call animateChange to do the smooth transition, and when it's done it automatically calls runNextStep, starting the 
		//run next step animation chain automatically; in effect, if doSmoothTransition is true than doRunNextStep is also set to true no matter what.
		//obviously, we don't do a smooth transition if doAnimate is false.
		computeOffset(targetSolution.join("|"), goalSnapShot, solutionOffset,true);
		computeOffset(workingPuzzle.join("|"), gameSnapShot, puzzleOffset);
		renderSolution(targetSolution.join("|"));
		renderGame(workingPuzzle.join("|"));
		//set it to true just for this
		animateSyncFinish=true;
		animateChange();
		animateSolutionChange();
}
function newGame() {
	
	//this function is called whenever a new game is started.
	stopPlayback();
	inputPuzzle(false,false);
	
	
}


//The following code handles user interface issues:
function toggleAnimate(objectIs) {
	//this function is no longer used, but is still supported.
	if(objectIs){
		doAnimate=objectIs.checked;
		
	}
	else {
		if(doAnimate) {
			doAnimate=false;
		}
		else {
			doAnimate=true;
		}
	}
}

function inputPuzzle(isNewTarget,isFirstTime) {
	//this function gets a new puzzle array from the user.  It handles all user interface matters and proccesses and assigns the array itself.
	//check if this is in PLAY mode. if it is, don't do anything
	if(currentUseMode.toUpperCase()=="PLAY" && playModeStepGoal>=-1) {
		//only exit if it's goalplaymode. 
		return;
	}
	
	//switches:
	//isNewTarget: this function can be used to get the current input from a user, or to set the current solution target (defaults to 0,1,2,3,4,5,6,7,8,9)
	//isFirstTime: a different, more introductory message is displayed the first time the window is shown (normally in the init function) (NewTarget mode disregards firsttime switch)
	//the functions that load the puzzle expect it to be in home positon. do that now.
	if (!isFirstTime) {
	resumeOnCancel= !isPaused;
	pausePlayback();
	}
	dimInterface(true);
	if(isNewTarget) {
		//set a new targetsolution
		//the functions that load the puzzle expect it to be in home positon. do that now.
		targetSolution=positionRow(1, 2, targetSolution.join("|"));
		targetSolution=positionRow(2, 2, targetSolution.join("|"));
		loadPuzzleToInput(targetSolution.join("|"));
		displayText("TEXT::This utility is used to set a new goal (what the puzzle should look like when it is finished). The default goal is 0,1,2,3, and 4 in the top row, and 5,6,7,8, and 9 in the bottom row, but you can change it to anything.  Keep in mind that if you change the target solution, it will stay at what you change it to unless you use this tool again or refresh the page.|br::void|b::Be sure to use each number only once.|br::void|b::IMPORTANT: When you hit 'OK', the puzzle will automatically be solved from its current state to the new solution.  If you want to change the puzzle's current state before solving with the new solution, after you submit this dialog, click 'New Puzzle'.","PuzzleInputIntroText");
		//the displayText function actually does so much more.  here, we use it to add the form buttons.
		displayText("input>type=button>value=Default Solution>onClick=loadDefaultPuzzle()::void|br::void|input>type=button>value=Random Solution>onClick=loadRandomPuzzle()::void","PuzzleInputExtraButtonRow");
		displayText("input>type=button>value=          OK          >onClick=submitPuzzle(true)::void|input>type=button>value=          Cancel          >onClick=cancelInputPuzzle()::void","PuzzleInputButtonRow");
		displayText("TEXT::Set New Puzzle Target", "PuzzleInputTitleText");
	}
	else {
		//set the current state of the puzzle.
		var tempPuzzle=positionRow(1, 2, workingPuzzle.join("|"));
		//positionRow(1, 2, workingPuzzle.join("|"));
		tempPuzzle=positionRow(2, 2, tempPuzzle.join("|"));
		loadPuzzleToInput(tempPuzzle.join("|"));
		displayText("input>type=button>value=          OK          >onClick=submitPuzzle(false)::void|input>type=button>value=          Cancel          >onClick=cancelInputPuzzle()::void","PuzzleInputButtonRow");
		displayText("input>type=button>value=Random Puzzle>onClick=loadRandomPuzzle()::void","PuzzleInputExtraButtonRow");
		displayText("TEXT::New Puzzle","PuzzleInputTitleText");
		if(isFirstTime) {
			displayText("TEXT::Welcome to the FlipSide puzzle solver!  This utility will show you how to automatically solve |i::any |TEXT::FlipSide puzzle, no matter how confusing it is!  To begin, input your puzzle as it currently looks.  If the first row of your puzzle read 2,5,6,3,4, you would put 2 in the first drop down box, 5 in the second, etc.  The second row is for the second row of the puzzle.|br::void|TEXT::Click 'OK' when you are finished.","PuzzleInputIntroText");
		}
		else {
			//repeat input
			displayText("TEXT::Please input your puzzle as it currently looks.  If the first row of your puzzle read 2,5,6,3,4, you would put 2 in the first drop down box, 5 in the second, etc.  The second row is for the second row of the puzzle.|br::void|TEXT::Click 'OK' when you are finished.","PuzzleInputIntroText");
		}
	}
	//make the window visible.
	document.getElementById("PuzzleInputWindow").style.visibility="visible";
}

/*******
The following functions are used only by the inputPuzzle module
*******/

function loadDefaultPuzzle() {
	var puzzleString="-1|0|1|2|3|4|-1|-1|5|6|7|8|9|-1|-1";
	loadPuzzleToInput(puzzleString);
}

function loadRandomPuzzle() {
	//loads a random puzzle,using loadPuzzleToInput
	
	var unusedTokens=new Array(0,1,2,3,4,5,6,7,8,9);
	var tempPuzzle="-1";
	var randomIndex=0;
	for (p=0;p<5;p++) {
		//generate a random number that's within the bounds of the length of the current array
		//add that one to the string
		randomIndex=parseInt(Math.random() * unusedTokens.length);
		
		tempPuzzle=tempPuzzle + "|" + unusedTokens[randomIndex];
		//remove the token we just used, so we don't repeat
		unusedTokens.splice(randomIndex,1);
		
	}
	tempPuzzle=tempPuzzle + "|-1|-1";
	for(p=8;p<13;p++) {
		//generate a random number that's within the bounds of the length of the current array
		//add that one to the string
		randomIndex=parseInt(Math.random() * unusedTokens.length);
		
		tempPuzzle=tempPuzzle + "|" + unusedTokens[randomIndex];
		//remove the token we just used, so we don't repeat
		unusedTokens.splice(randomIndex,1);
	}
	tempPuzzle=tempPuzzle + "|-1|-1";
//var tempPuzzle="-1|1|8|5|7|9|-1|-1|6|0|3|4|2|-1|-1";


//this funciton does not close the window.  it just puts the randomly generated puzzle into the boxes.
loadPuzzleToInput(tempPuzzle);

}

function loadPuzzleToInput(puzzleArray) {
	//this function loads the current values from the given puzzle array into the input window.
	//puzzleArray is a pipe-delimited puzzle string.
	puzzleArray=puzzleArray.split("|");
	for (y=1;y<puzzleArray.length;y++) {
		//spaces in the array 0, 6,7,13,14 are not what we're looking for. skip them.
		if(y==0 || y==6 || y==7 || y==13 ||y==14) {
			//skip 
		}
		else {
			if(puzzleArray[y]==-1) {
				alert("Error loading puzzle to inputs: The puzzle is not in the home position: " + y);
				document.getElementById("TokenInput" + y).options.selectedIndex=0;
			}
			else {
				//this assumes that the value=the selected index.  this is currently a valid assumption, but might not always be.
				document.getElementById("TokenInput" + y).options.selectedIndex=puzzleArray[y];
			}
		}
	}
}



function submitPuzzle(isNewTarget) {
	//isNewTarget is boolean specifiying if the input from the boxes should be saved to solutionTarget or to workingPuzzle
	//this handles closing the input window.
	if(!validateInputPuzzle()) {
		//the puzzle is invalid.  the other function already alerted the user.
		//don't set the puzzle yet.
		return;
	}
	
	var currentObject;
	//reset goalStep, because now it's invalid.
	playModeStepGoal=-2;
	takeSolutionSnapShot(targetSolution.join("|"));
	takeSnapShot(workingPuzzle.join("|"));
	if (isNewTarget) {
		//save to targetSolution
		
		for(t=0;t<targetSolution.length;t++) {
			if(t==0 ||t==6 ||t==7 ||t==13 ||t==14) {
				//just drop in a negative one.
				targetSolution[t]=-1;
			}
			else {
				//drop in what is in the form.
				currentObject=document.getElementById("TokenInput" + t);
				targetSolution[t]=currentObject.options[currentObject.selectedIndex].value;
			}
		}
		isPaused=false;
		callbackFunctionID=0;
		
		prependAction += "|MESSAGE:0:DEFAULT:" + escape("b::The puzzle is now set to be solved from its current state to the new solution.|br::void|br::void|TEXT::If you want to use the new target solution but also want to define a new starting point (something other than the current state), click on |a>href=javascript:interfaceCommand('NEWPUZZLE')::'New Puzzle'|TEXT::.|br::void|br::void|i::Reminder: the target solution will remain at what you set it until you set a new target with the 'Enter New Goal' dialog or refresh the page.|br::void|br::void|b::Click 'Continue' to go on.") + "|PLAYBACK:BYSTEP,BYSECTION,NOHELP,PRINT:PAUSE";
		
		firstInstructionSet=true;
		beginPuzzle(true,useSmoothTransition,true);
	}
	else {
		//save to workingPuzzle
		//take a snapshot of current puzzle, in case we want to animate transition.
		//(the puzzle snapshot is only used immediately before calling computeOffset in the begining of an animation; after that, it's free to be used
		 //however we want, so it's fine to set this even if we aren't doing smooth transition.)
		
		for(t=0;t<targetSolution.length;t++) {
			if(t==0 ||t==6 ||t==7 ||t==13 ||t==14) {
				//just drop in a negative one.
				workingPuzzle[t]=-1;
			}
			else {
				//drop in what is in the form.
				currentObject=document.getElementById("TokenInput" + t);
				
				
				workingPuzzle[t]=currentObject.options[currentObject.selectedIndex].value;
				
			}
		}
		isPaused=false;
		callbackFunctionID=0;
		firstInstructionSet=true;
		beginPuzzle(true,useSmoothTransition,true); //useSmooth Transition is the global value, which we respect.
	}
	
	if (currentUseMode.toUpperCase()=="PLAY" && playModeStepGoal==-2) {
	
			//we can only get to here if we're in FreePlayMode. 
			enablePlayControls(true);
			//also enable newpuzzle, new goal
			enableInterface("NewGoalButton,NewPuzzleButton",false);
			//technically, this should put the original message up again. 
			pushSolveModeCommand("CHECK:FREEPLAYCOMPLETE");
	}
	else {
		//non-play mode, display the deafult message.
		displayText("b::Please wait while the puzzle is rearranged...");
	}
	document.getElementById("PuzzleInputWindow").style.visibility="hidden";
	// set all the interface buttons to disabled.
	enableInterface(false,"ResumeButton,PauseButton,ReplayButton");	
	
	dimInterface(false);
}

function startPuzzleOver(doSmoothTransition) {
	//check if this is in PLAY mode. if it is, don't do anything
	if(currentUseMode.toUpperCase()=="PLAY") {
		return;
	}
	//copies originalPuzzle into workingPuzzle, begins game again (which, incidentally, handles everything else for us.)
	//make sure the puzzle is paused before continuing (otherwise pause/resume functionality breaks until next restart)
		if (!doSmoothTransition) {
			doSmoothTransition=false;
		}
		//there's no harm in taking this no matter what, so we might as well.
		takeSnapShot(workingPuzzle.join("|"));
		takeSolutionSnapShot(targetSolution.join("|"));
		stopPlayback();
		//this is the real meat of this function: copying the saved puzzle back into the working puzzle
		workingPuzzle=originalPuzzle.join("|");
		workingPuzzle=workingPuzzle.split("|");
		beginPuzzle(true,doSmoothTransition);

}

function replaySegment() {
	//this function pushes a re-translated simple set of the current string
	//looks in either savedSection or savedStep, depending on which mode we're in (if we're in PLAY mode or print/text or demo, this is disabled)
	//the savedbuffer holds strings in format: 2,2;1,2;3,2.
	//this pushes everything to the instructionSet, adding a pause (for the normal pause modes), and doesn't add a message.
	//this makes sure that no other text is printed to message area, so the right text should be displayed.
	//calls resumePlayback after it pushes.
	if(outOfState) {
		return;
	}
	
	if (currentUseMode.toUpperCase()=="PLAY") {
		//quit this mode, invalid.
		return;
	}
	if (currentDisplayMode.toUpperCase()=="PRINT" || currentDisplayMode.toUpperCase()=="NOHELP") {
		//not  valid in these modes, return;
		return;
	}
	//if we get to here, then the mode (both use and display) is valid.
	var pushString="";
	
	
	 var pushResult="MESSAGE:0:DEFAULT:" + escape("b::Press 'Continue' to watch this section replay.") + "|PLAYBACK:BYSTEP,BYSECTION:PAUSE|MESSAGE:0:DEFAULT:" + escape("b::Watch as the computer performs these moves...");
	var historyPuzzleSnapShot="";
	var returnHome=false;
	if(currentDisplayMode.toUpperCase()=="BYSTEP") {
		//load the savedStep to pushString buffer.
		pushString=savedStep;
		historyPuzzleSnapShot=workingPuzzle.join("|");
		if(isHome(historyPuzzleSnapShot)) {
			returnHome=true;
		}
		workingPuzzle=savedStepSnapShot.join("|");
		workingPuzzle=workingPuzzle.split("|");
	}
	else {
		//it's probably BYSECTION, and if it's not, then bySection is default anyway.
		pushString=savedSection;
		historyPuzzleSnapShot=workingPuzzle.join("|");
		
		workingPuzzle=savedSectionSnapShot.join("|");
		workingPuzzle=workingPuzzle.split("|");
	}
	//make sure we've got something to replay!
	if(pushString.toUpperCase()=="#NOTHING#"){
		//we've got nothing
		return;
	}
	//translate pushString to instructionSet
	pushString=pushString.split(";");
	for(u=0;u<pushString.length;u++) {
		//conver the comma to a colon
		pushString[u]=pushString[u].replace(",",":");
		//add to pushResult
		pushResult += "|SHIFT:" + pushString[u] + "|FLIP";
		
	}
	//now, return home if it's section play.... or the array we were given was in the home position.
	if (currentDisplayMode.toUpperCase()=="BYSECTION" || returnHome==true) {
		pushResult += "|SHIFT:2:2";
	}
	//now, append whatever message was last.
	pushResult += "|" + lastMessageCommand;
	//now, allow ReplayButton
	pushResult += "|INTERFACE:ALL:ENABLE:ReplayButton:";
	//now, append the pause message.
	pushResult += "|PLAYBACK:BYSTEP,BYSECTION:PAUSE";
	//prepend pushResult to instructionSet
	//what if instructionSet is empty?
	
	if (instructionSet!="#NOTHING#") {
	instructionSet= pushResult + "|" + instructionSet;
	}
	else {
		instructionSet=pushResult;
	}
	//now we restore correct snapshots, and call animateChange to do smoothTransitions.
	displayText("b::Wait while the puzzle resets to the old state...");
	enableInterface(false,"PauseButton,ResumeButton,ReplayButton");
	animateSmoothChange(historyPuzzleSnapShot,snapShotSolution.join("|")); //don't worry about pause state; runNextStep will take care of that for us.
}

function isHome(puzzleArray) {
	//given a pipe-delimited array, this function returns true if it is in the home position, or false if it is not.
	puzzleArray=puzzleArray.split("|");
	
	//check the first row
	if(puzzleArray[1]==-1 || puzzleArray[2]==-1 || puzzleArray[8]==-1 || puzzleArray[9]==-1) {
		return false;
	}
	//if we get to here, it is home
	return true;
	
}

function validateInputPuzzle() {
	//this function checks to make sure the puzzle inputs don't use any token more than once. if it does, it returns false.
	//it also handles messaging in the case that the input puzzle uses numbers more than once.
	var tokensToCheck=new Array(2,3,4,5,8,9,10,11,12);
	var puzzleOkay=true;
	var valueA, valueB;
	for (a=1;a<6;a++) {
		valueA=document.getElementById("TokenInput" + a).options.selectedIndex;
		for(b=0;b<tokensToCheck.length;b++) {
			valueB=document.getElementById("TokenInput" + tokensToCheck[b]).options.selectedIndex;
			if(valueA==valueB) { //two select boxes have the same thing selected.
				
				puzzleOkay=false;
			}
			
		}
		tokensToCheck.shift();
	}
	if(puzzleOkay) { //we don't need to start this loop if we already know the puzzle's not valid.
		for (a=8;a<13;a++) {
			valueA=document.getElementById("TokenInput" + a).options.selectedIndex;
			for(b=0;b<tokensToCheck.length;b++) {
				valueB=document.getElementById("TokenInput" + tokensToCheck[b]).options.selectedIndex;
				if(valueA==valueB) { //two select boxes have the same thing selected.
				
					puzzleOkay=false;
				}
				
			}
			tokensToCheck.shift();
		}
	}
	if(!puzzleOkay) {
		alert("You have selected at least one number more than once.  Please correct this and try again.");
	}
	return puzzleOkay;
}

function cancelInputPuzzle() {
	//this handles closing the input window on a cancel.
	
	if (resumeOnCancel) {
		resumePlayback();
	}
	document.getElementById("PuzzleInputWindow").style.visibility="hidden";
	dimInterface(false);
}













//************************************************




function puzzleIsFinished(currentPuzzle,puzzleSolution, doNotHome) {
	//as long as doNotHome is empty or false, this function will return true even if the puzzle is not in home position. 
	currentPuzzle=currentPuzzle.split("|");
	puzzleSolution=puzzleSolution.split("|");
	if(!doNotHome) {
		currentPuzzle=positionRow(1, 2, currentPuzzle.join("|"));
		currentPuzzle=positionRow(2, 2, currentPuzzle.join("|"));
	}
	var isDone=true;
	for (k=0;k<currentPuzzle.length;k++) {
		if(currentPuzzle[k]!=puzzleSolution[k]) {
			isDone=false;
		}
	}
	return isDone;
}

function puzzleCurrentStep(currentPuzzle,puzzleSolution) {
	//this puzzle should probably only be called if puzzleIsFinished returns false.
	//it returns the current step the puzzle is on currently.  That is, it figures out which things have been completed and 
	//returns one step index higher than that.  It should automatically make sure it returns a valid step index (not 9), but
	//it's not guaranteed; call puzzleIsFinished first.
	currentPuzzle=currentPuzzle.split("|");
	puzzleSolution=puzzleSolution.split("|");
	for (h=0;h<9;h++) {
		if (currentPuzzle[returnStepLocation(h)]==puzzleSolution[returnStepLocation(h)]) {
			//it's good
		}
		else {
			//this step is not complete; this is our step.
			
			return h;
		}
	}
	//if we get to here, everything is complete (why'd they call us, then? so, return -1:
	return -1;
}
function toggleMessage(objectIs) {
	//this function is no longer called by anything.
	if(objectIs) {
		suppressMessages=!objectIs.checked;
	}
	else {
		if(suppressMessages) {
			suppressMessages=false;
		}
		else {
			suppressMessages=true;
		}
	}
}






function addCard( cardImage, cardTitle, cardType, enableFinished) {
	//TODO: this function doesn't need to call anything about text, title, or even really type--those aren't used anymore. by anything.
	//figures out which card is not yet visible, sets up that card's top,height, visiblity, text, and image appropriately
	var cardIndex=getFirstFreeCard();
	if (cardIndex==-1) {
		alert("Error: There are no more cards free!");
		return false;
	}
	instructionCards[cardIndex].Finished=false;

	instructionCards[cardIndex].ImageSRC="";
	instructionCards[cardIndex].Title="-1";
	instructionCards[cardIndex].Type="";
	instructionCards[cardIndex].EnableFinished=false;

	if(cardImage) {
		instructionCards[cardIndex].ImageSRC=cardImage;
	}
	
	if(cardTitle) {
		
		
		instructionCards[cardIndex].Title=cardTitle;
	}
	if(cardType) {
		instructionCards[cardIndex].Type=cardType;
	}
	if(enableFinished) {
		instructionCards[cardIndex].EnableFinished=true;
	}
	
	instructionCards[cardIndex].Occupied=true;
}
function getFirstFreeCard() {
	for (l=0;l<instructionCards.length;l++) {
		if (instructionCards[l].Occupied==false) {
			return l;
		}
	}
	return -1; // no cards are free!
}

function showCards(adjustTop) {
	//adjustTop is bool value meaning that it should 
	//shows all cards that have been flagged as occupied, going through and adujsting top values so they're touching
	
	var cardCount=0;
	if (adjustTop) { //adjust top values before displaying
		
		var nextTop=0;
		var holder;
		for(k=0;k<instructionCards.length;k++) {
			if (instructionCards[k].Occupied) {
				holder=instructionCards[k].Visible;
				instructionCards[k].Visible=false;
				instructionCards[k].Top=nextTop;
				instructionCards[k].Update();//this ensures that the height is right that we're using
				instructionCards[k].Visible=holder;
				
				nextTop=nextTop + instructionCards[k].Height;
			}
		}
	}
	for (k=0;k<instructionCards.length;k++) {
		if(instructionCards[k].Occupied) {
			cardCount=cardCount+1;
			instructionCards[k].Visible=true;
			instructionCards[k].Update();
		}
	}
	return cardCount; //return the number of cards we showed.  //some message routines use this to determine if some cards must be scrolled to.
}

function toggleCardFinished(cardIndex) {
	instructionCards[cardIndex].Finished=!instructionCards[cardIndex].Finished;
	instructionCards[cardIndex].Update();
}

function hideCards() {
	//merely makes all cards invisible, but does not mark them unoccupied.
	for (k=0;k<instructionCards.length;k++) {
		if (insructionCards[k].Occupied) {
			if(instructionCards[k].Visible) {
				instrutionCards[k].Visible=false;
				instructionCards[k].Update();
			}
		}
	}
}
function clearCards() {
	//makes all cards unoccupied, and thus invisible
	for (k=0;k<instructionCards.length;k++) {
		if (instructionCards[k].Occupied) { //only update the ones taht actually need it.
			instructionCards[k].Occupied=false;
			instructionCards[k].ImageSRC="images/Card_LOADING.png";
			instructionCards[k].EnableFinished=false;
			instructionCards[k].Update();
		}
	}
}

function InstructionCard(cardIndex) { //constructor function
	this.Name="";
	this.Index=cardIndex;
	this.Occupied=false;
	this.Visible=false;
	
	this.Title="-1";
	this.Top=0;
	this.Type="";
	this.AutoHeight=true;
	this.Finished=false;
	this.EnableFinished=false;
	this.Height=83;
	this.ImageSRC="";
	this.Update=privateUpdateCard;

	
}

function privateUpdateCard() {
	var cardObject = document.getElementById("InstructionCard" + this.Index);
	var imageObject=document.getElementById("InstructionCardImage" + this.Index);
	var textObject; //as of yet not functional.
	var tempSRC=this.ImageSRC;
	var tempExt="";
	
	
	

	
	if (this.EnableFinished) {
		tempSRC=tempSRC.split(".");
		var tempExt=tempSRC[1];
		var tempSRC=tempSRC[0];
		
		if (this.Finished==true) {
			tempSRC=tempSRC + "_F." + tempExt;
		}
		else {
			tempSRC=tempSRC + "_N." + tempExt;
		}
	}
	//set image
	imageObject.src=tempSRC;
	// automatically resize container to be size of image.
	if ( this.AutoHeight==true) {
		
		this.Height=parseInt(imageObject.height);
		
	}
	//size object according to properties
	cardObject.style.top = this.Top + "px";
		//check to see if it should be made visible.
	if (this.Occupied==false) {
		//no matter what, it should not be visible
		cardObject.style.height="0px";
		cardObject.style.visibility="hidden";
	}
	else {
		if(this.Visible==false) {
			//should be inivisble
			cardObject.style.height="0px";
			cardObject.style.visibility="hidden";
		}
		else {
			//should be visible
			cardObject.style.height= this.Height + "px";

			cardObject.style.visibility="visible";
		}
	}
}


function fillCardsSection(instructionString,showInfo,showHome) {
	//the instruciton string contains the datasets: Row1,Row2;Row1,Row2
	//automatically calls clearCards	clearCards();
	//reset the stepCount counter (if we're in step mode, this is only called to add InfoCard(s)
	currentStepCount=0;
	if (showInfo) {
		addCard("images/Card_INFO.png",false,false,false);
		addCard("images/Card_INFO_2.png",false,false,false);
	}
	if(instructionString!="##") {
		if(instructionString.charAt(0)=="+") {
			//send this instruction string (minus + ) to appendCards
			instructionString=instructionString.replace("+","");
		}
		else {
			//process it here.
			instructionString=instructionString.split(";");
			var instruction, imageName;
			for(g=0;g<instructionString.length;g++) {
				instruction=instructionString[g].split(",");
				imageName="images/Card_" + instruction[0] + "_" + instruction[1] + ".png";
				addCard(imageName, (parseInt(g)+1), false,true)
				//NOTE: because 0 and false are logically the same in javascript, EVERY non-staging card gets the 0 if you send false.
				//for that reason, 0 is not a valid step index; 0 is.  it is retransformed when it is interpreted.
			}
		}
	}
	if(showHome) {
		addCard("images/Card_HOME.png",false,false,true);
	}
}

function markCardsDone() {
	//marks all currently visible cards as done.
	if(!autoMarkCardsDone) {
		//quit now
		return;
	}
	for(k=0;k<getFirstFreeCard();k++) {
		//mark each card done... no need to change or update if it's already marked!
		if(!instructionCards[k].Finished) {
			instructionCards[k].Finished=true;
			instructionCards[k].Update();
		}
	}
	
	
}

function appendCards(instructionString) {
	//this function is called by message type preStep.
	//it adds a card to the list, but does not clear out the others.
	//you want a home card or info card?  Call fillCardsSection with a "+" at the beginning of your instructionString.
	//make sure we take out a + if we have one.
	//this function can take many cards or a single card (format: 2,2;1,2;2,0)
	
	
	instructionString=instructionString.replace("+","");
	instructionString=instructionString.split(";");
		var instruction, imageName, titleValue;
		for(g=0;g<instructionString.length;g++) {
			if (instructionString[g].indexOf("<<")>-1) {
				//add a home card.
				imageName="images/Card_HOME.png";
				titleValue=false;
			}
			else {
				instruction=instructionString[g].split(",");
				imageName="images/Card_" + instruction[0] + "_" + instruction[1] + ".png";
				currentStepCount=currentStepCount+1;
				titleValue=currentStepCount;
			}
			addCard(imageName, titleValue, false,true)
		}
	
}

function displayText(textToDisplay,targetID) {
	//this function simply displays the given text in the message box, using javascript and DOM.
	//this funciton accepts text marked up with a special markup.
	//syntax:
	//TAGNAME>attributename=value>attributename=value::nodeValue|TAGNAME>attributename=value::nodeValue
	//node value does NOT require being in quotes.
	//use TEXT for TagName if it's just unformatted Text.
	//if the function is handed unformatted text, it will convert it to a simple text element and render it that way.
	
	//because the delimiters this function uses are also used elsewhere, they are escaped when they are written.
	textToDisplay=unescape(textToDisplay);
	
	if(!targetID) {
		//target ID not supplied; use the message area readout by default.
		//(most calls to displayText in the doMessage function don't send the targetID)
		targetID="MessageAreaDynamicText";
	}
	//check to see if we have special formattign
	if(textToDisplay.indexOf("::")<0) {
		//we don't, but it's neccesary to display it.  make a simple text node.
		textToDisplay="TEXT::" + textToDisplay;
	}
	var targetNode=document.getElementById(targetID);
	//check to see if the target has child nodes.  if it does, clear them.
	while(targetNode.hasChildNodes()==true) {
		targetNode.removeChild(targetNode.lastChild);
	}
	var nodeArray=textToDisplay.split("|");
	var tagValuePair;
	var tagArray;
	var attributePair;
	var newElement;
	var textElement;
	
	//IE has some weird quirks, so we do it entirely differet for it.
	if(!browser.isIE) {
		//split it into the different tags.
		for (c=0;c<nodeArray.length;c++) {
			tagValuePair=nodeArray[c].split("::");
			tagArray=tagValuePair[0].split(">");
			
			if (tagArray[0].toUpperCase()=="TEXT") {
				
				//it's a text node.  no attributes possible.
				newElement=document.createTextNode(unescape(tagValuePair[1]));
			}
			else {
				//it's a normal node. 
				//first, create the element
				newElement=document.createElement(tagArray[0]);
				newElement.appendChild(document.createTextNode(unescape(tagValuePair[1])));
				if (tagArray.length>1) {
					//add the attributes
					for(b=1;b<tagArray.length;b++) {
						attributePair=tagArray[b].split("=");
						newElement.setAttribute(unescape(attributePair[0]),unescape(attributePair[1]));
					} 
				}
				
			}//end morethanonetag else
			targetNode.appendChild(newElement);
		}//end non-IE for loop
	}//end non-IE
	else { //IS IE
		//this loop simply converts our markup to normal HTML and sets that to the targetID's innerHtml property.
		//is IE loop
		var htmlToWrite="";
		var nodeText="";
		for (c=0;c<nodeArray.length;c++) {
			tagValuePair=nodeArray[c].split("::");
			tagArray=tagValuePair[0].split(">");
			if (tagArray[0].toUpperCase()=="TEXT") {
				//it's a text node.  no attributes possible.
				//newElement=document.createTextNode(tagValuePair[1]);
				htmlToWrite=htmlToWrite + tagValuePair[1];
			}
			else {
				//it's a normal node. 
				//first, create the element
				nodeText="";
				nodeText="<" + tagArray[0];
				//newElement=document.createElement(tagArray[0]);
				//newElement.appendChild(document.createTextNode(tagValuePair[1]));
				if (tagArray.length>1) {
					//add the attributes
					for(b=1;b<tagArray.length;b++) {
						attributePair=tagArray[b].split("=");
						nodeText=nodeText + " " + attributePair[0] + "=\"" + attributePair[1] + "\"";
						//newElement.setAttribute(attributePair[0],attributePair[1]);
					} 
					//close the tag
					nodeText=nodeText + ">";
					
				}
				else {
					//no attributes 
					//close tag
					nodeText=nodeText + ">";
					
				}
				//fill the node, then close it.
				
				if(tagValuePair[1].toUpperCase()!="VOID") { //VOID as the tag's value means that it does not have a closing tag (like br or input)
				
					nodeText=nodeText + tagValuePair[1] + "</" + tagArray[0] + ">";
				}
				htmlToWrite=htmlToWrite+nodeText;
				
				
			}//end morethanonetag else
			
			
		}//end for
		//finally write it.
		
		targetNode.innerHTML=unescape(htmlToWrite);
	}//end IE
	
	
}//end function


function doMessage(messageString, importanceFlag, messageType, displayMode, stepNumber) {
	//this function takes care of reproducing the message.  it interprets the message as one that is literal or needs to be rendered.
	//this function would more properly be called doInterface.  The message is really just something that needs to be communicated through the interface in some way.
	//this function decides for itself whether or not the message is important enough to show.
	//the importanceflag is of the individual message.  Simply, 1 will display no matter what, 0 will display according to normal rules.
	//suppressMessages is a global flag that disables the displaying of any message not flagged important.  It is totally seperate from the mode value.
		//therefore, it is the first conditional.
	
	/*valid display modes:
		NOHELP
		BYSECTION
		BYSTEP
		PRINT (which is a non-animated mode so does not have many commands associated)
	*/
	
	/* valid Message types:
	DEFAULT 
	PRESTEP (has fifth arg: step number)
	STEP (has fifth arg: step number)
	PRESECTION (has fifth arg: step number)
	SECTION (has fifth arg: step number)
	EXPLANATORY
	SKIPPED (has fifth arg: step number)
	ERROR
	DONE
	ALERT (uses Alert box)
	*/
	
	if (!stepNumber) { //step number is only sent by some message types.
		stepNumber=-1;
	}
	var tempMessage;
	if (suppressMessages==false || importanceFlag==1) {
		displayMode=displayMode.toUpperCase();
		switch(messageType.toUpperCase()) {
			case "DEFAULT":
				//same for all modes
				
				displayText(messageString);
				break;
			case "ALERT":
				
				alert(messageString);
				break;
			case "PREPRESECTION":
				clearCards();
				if(enableNormalMessage) {
				highlightPieces(returnStepLocation(stepNumber),false,returnStepLocation(stepNumber),puzzleOffset);
				highlightPieces(returnStepLocation(stepNumber),returnStepLockedLocations(stepNumber),returnStepLocation(stepNumber),solutionOffset);
				renderGame(workingPuzzle.join("|"));
				renderSolution(targetSolution.join("|"));

				
					switch(displayMode) {
						case "BYSECTION":
							tempMessage="TEXT::We are now on stage " + (parseInt(stepNumber) + 1) + ".|br::void|br::void|TEXT::Our goal for this stage is to put the token with the number " + targetSolution[returnStepLocation(stepNumber)] + " in the " + returnPrettyNameLocation(returnStepLocation(stepNumber))  + returnStepPrettyLockedTokens(stepNumber) + ".|br::void|br::void|TEXT::Think you can do it yourself?|a>href=javascript:startGoalPlay(" + stepNumber + ")::  Click here to try it.|br::void|br::void|b::Otherwise, click 'Continue' to have the computer help you solve this stage.";
							displayText(tempMessage);
							
							break;
						case "BYSTEP":
							//this should display message like, now, we're going to get the 7 to the 5th position.
							tempMessage="TEXT::We are now on stage " + (parseInt(stepNumber) + 1) + ".|br::void|br::void|TEXT::Our goal for this stage is to put the token with the number " + targetSolution[returnStepLocation(stepNumber)] + " in the " + returnPrettyNameLocation(returnStepLocation(stepNumber)) +  returnStepPrettyLockedTokens(stepNumber) + ".|br::void|br::void|TEXT::Think you can do it yourself?|a>href=javascript:startGoalPlay(" + stepNumber + "):: Click here to try it yourself.|br::void|br::void|b::Otherwise, click 'Continue' to have the computer help you solve this stage.";
							//clearCards();
							displayText(tempMessage);
							break;
						case "NOHELP":
							displayText("b::Watch as the puzzle is solved...");
							
						default:
							//do nothing; this displayMode does not accept this type of message.
							break;
					}
				}
				else {
					//we're only supposed to skip ONE normal message, then switch back.
					enableNormalMessage=true;
					//and skip the pause right after this message that we're skipping.
					enablePausing=false;		
				}
				
				break;
			case "PRESECTION":
				//save the messageString to the savedSection buffer, for replaySegment functionality.
				
				savedSection=messageString;
				savedSectionSnapShot=workingPuzzle.join("|");
				savedSectionSnapShot=savedSectionSnapShot.split("|");
				//this is the switch to set enableSkippedMessages back to true if it had been false
				enableSkippedMessages=true;
				//every display  mode uses highlighting if it is turned on. (doHighlight generally includes highlighting and locking.
				if(doHighlight) {
				highlightPieces(returnStepLocation(stepNumber),false,returnStepLocation(stepNumber),puzzleOffset);
				highlightPieces(returnStepLocation(stepNumber),returnStepLockedLocations(stepNumber),returnStepLocation(stepNumber),solutionOffset);
				//the solution won't be rendered again until it is reset, so we  need to rerender it to get the highlighting to show.
				renderSolution(targetSolution.join("|"));
				}
				switch(displayMode) {
					case "BYSECTION":
						displayText("b::Watch as the computer performs these moves...");
						fillCardsSection(messageString,true,true);
						showCards(true);
						break;
					case "BYSTEP":
						
						fillCardsSection("##",true,false);
						break;
					case "NOHELP":
						
						fillCardsSection(messageString,false,true);
						showCards(true);
					default:
						//do nothing; this displayMode does not accept this type of message.
						break;
				}
				break;
			case "PRESTEP":
				//save messageString to savedStep buffer for replaySegment functionality.
				savedStep=messageString;
				savedStepSnapShot=workingPuzzle.join("|");
				savedStepSnapShot=savedStepSnapShot.split("|");
				switch(displayMode) {
					
					case "BYSTEP":
						//this should display message like, now, we're going to get the 7 to the 5th position.
						displayText("b::Watch as the computer performs the next move...");
						//fillCardsSection(messageString,true,false);
						//markCardsDone respects the global switch itself.
						markCardsDone();
						appendCards(messageString);
						showCards(true);
						break;
					
					default:
						//do nothing; this displayMode does not accept this type of message.
						break;
				}
				break;
			case "DONE":
				//as of right now, EVERY mode displays this message.
				clearCards();
				//it will probably be taken care of by now, but make sure that the highlights are cleared
				clearHighlights();
				displayText(escape("b::Finished!|TEXT::  The puzzle is now solved.|br::void|br::void|TEXT::Click either |a>href=javascript:interfaceCommand('NEWPUZZLE')::'New Puzzle'|TEXT:: or |a>href=javascript:interfaceCommand('NEWGOAL')::'Enter New Goal'|TEXT:: to keep playing."));
				//we need to pause playback for various reasons
				//however, if all is going as planned, neither runNextStep nor animateChange is being called, so pause won't do anythign til it runs next.
				//so we pretend like we're runNextStep calling back.
				pausePlayback();
				pausePlayback(1);
				//and of course the continue button is now highlighted; turn that off.
				//(note that the disabling of the interface buttons used to be called by a command, but we're paused so now we just do it here.)
				enableInterface(false,"ReplayButton,PauseButton,ResumeButton");
				break;
			case "SECTION":
				//check to see if the puzzle is finished. if it is, then skip skipped messages.
				if(puzzleIsFinished(workingPuzzle.join("|"),targetSolution.join("|"))) {
					enableSkippedMessages=false;
				}
				switch(displayMode) {
					case "BYSECTION":
						//this message comes after the section has been shown performed.
						//so, will display text in the textbox to the effect of, "Now, perform the steps to the right and click 'Continue'".
						tempMessage="TEXT::The computer successfully put the token with the number " + targetSolution[returnStepLocation(stepNumber)] + " in the " + returnPrettyNameLocation(returnStepLocation(stepNumber)) + ".|br::void|br::void";
						var shownCards=showCards(false); //returns the number of cards visible
						if (shownCards>6) {
							tempMessage=tempMessage + "|b::Now, perform the steps shown to the right to catch up and then click 'Continue'.|br::void|br::void|b::Make sure you scroll to see all the steps.  As always, to make it easier to keep track of your progress, you can click on a step's card to visually mark it as done.";
						}
						else {
						tempMessage=tempMessage + "|b::Now, perform the steps shown to the right to catch up and then click 'Continue'.";
						}
						displayText(tempMessage);
						//fillCardsSection(messageString);
						//showCards(true);
						break;
					case "BYSTEP":
						//this should display text about done with this section.
						//fillCardsSection("##",true,true);
						//markCardsDone respects the global switch itself.
						markCardsDone();
						appendCards("<<");
						showCards(true);
						tempMessage="TEXT::We successfully put the token with the number " + targetSolution[returnStepLocation(stepNumber)] + " in the " + returnPrettyNameLocation(returnStepLocation(stepNumber)) + ".  We're done with this section.|br::void|TEXT::Return the puzzle to the home position (both rows pushed to the left).|br::void|br::void|b::Click 'Continue' to go on.";

						displayText(tempMessage);
						break;
					default:
						//do nothing; this displayMode does not accept this type of message.
						break;
				}
				break;
			case "STEP":
				switch(displayMode) {
					case "BYSTEP":
					
						messageString=messageString.split(",");
						switch(parseInt(messageString[0])) {
							case 0:
								messageString[0]="no tokens are";
								break;
							case 1:
								messageString[0]="one token is";
								break;
							case 2:
								messageString[0]="two tokens are";
								break;
							default:
								alert("Error: unexpected position: " + messageString[0]);
								break;
							
						}
						switch(parseInt(messageString[1])) {
							case 0:
								messageString[1]="no tokens are";
								break;
							case 1:
								messageString[1]="one token is";
								break;
							case 2:
								messageString[1]="two tokens are";
								break;
							default:
								alert("Error: unexpected position: " + messageString[0]);
								break;
							
						}

						
						tempMessage="b::Now it's your turn. To catch up, do the following:|br::void|br::void|TEXT::First, move the top row so that " + messageString[0] + " to the left of the flip area.|br::void|TEXT::Next, move the bottom row so that " + messageString[1] + " to the left of the flip area.|br::void|TEXT::Finally, rotate the flip area.|br::void|br::void|b::Click 'Continue' to go on.";

							
						
						displayText(tempMessage);
						break;
					default:
						//do nothing; this displaymode does not accept this kind of message.
						break;
				}
				break;
			case "SKIPPED":
				//skipped can be thought of like a section message.  highlight appropriately, in all modes
				if (enableSkippedMessages) {
					if(doHighlight) {
					highlightPieces(returnStepLocation(stepNumber),false, returnStepLocation(stepNumber), puzzleOffset);
					highlightPieces(returnStepLocation(stepNumber),returnStepLockedLocations(stepNumber), returnStepLocation(stepNumber), solutionOffset);
					//the solution won't be rendered again until it is reset, so we  need to rerender it to get the highlighting to show.
					renderSolution(targetSolution.join("|"));
					//we won't be rendering game otherwise (it's skipped! no animation routines are being called!)
					// so, call it ourselves.
					renderGame(workingPuzzle.join("|"));
					}
					switch(displayMode) {
						case "BYSTEP":
							clearCards();
							tempMessage="TEXT::Normally, we would now put the token with the number " + targetSolution[returnStepLocation(stepNumber)] + " in the " + returnPrettyNameLocation(returnStepLocation(stepNumber)) + ", but the token is already there!|br::void|br::void|b::Click 'Continue' to go on.";
							displayText(tempMessage);
							break;
						case "BYSECTION":
							clearCards();
							tempMessage="TEXT::Normally, we would now put the token with the number " + targetSolution[returnStepLocation(stepNumber)] + " in the " + returnPrettyNameLocation(returnStepLocation(stepNumber)) + ", but the token is already there!|br::void|br::void|b::Click 'Continue' to go on.";
	
							displayText(tempMessage);
							break;
						default:
							//do nothing; this displayMode does not accept this kind of message.
							break;
					}
				}
				else {
					enablePausing=false;					
				}
				break;
			default:
				alert ("Error: Unknown MessageType: " + messageType);
				break;
		}//end switch
	} //end if
			
}

function returnPrettyNameLocation (locationIndex) {
	//returns string formatted in the method of:
	// seventh from the left in the first row
	var row=1;
	var result="";
	if (locationIndex>7) {
		row=2;
		locationIndex=locationIndex-7;
	}
	switch(locationIndex) {
		case 1:
			result="first";
			break;
		case 2:
			result="second";
			break;
		case 3:
			result="third";
			break;
		case 4: 
			result="fourth";
			break;
		case 5: 
			result="fifth";
			break;
		case 6: 
			result="sixth";
			break;
		case 7:
			result="seventh";
			break
		default:
			alert("Error: Invalid locationIndex.");
			break;
	}
	result=result + " spot from the left in the ";
	switch(row) {
		case 1:
			result= result + "first";
			break;
		case 2:
			result=result+"second";
			break;
		default:
			alert("Error: invalid row.");
			break;
	}
	result=result + " row";
	return result;
}
//The following code handles displaying the data:


function takeSnapShot(puzzleArray) {
	//this function accepts an array and stores its state in a buffer array, to be used by animation methods.
	snapShotPuzzle=puzzleArray.split("|");
}

function takeSolutionSnapShot(solutionArray) {
	snapShotSolution=solutionArray.split("|");
}



function animateChange() {
	//ideally, this function would accept these arguments: puzzleArray, offsetArray, functionToRun
	//but that presents problems for the callback.  Until then, it just uses default registers.
	
	//this function calls itself until after decayOffset returns false.
	if (settingState && returningState) {
		//this happens when returnState is called before showState is done animating.
		//cancel settingState; we're just returning now.
		settingState=false;
	}
	var notDone=true;
	notDone=decayOffset(puzzleOffset,decayInterval);
	renderGame(workingPuzzle.join("|"));
	if(notDone) {
		//set a call back for this function.
		//check to see if we're allowed to call back.
		if(doSetCallback){
		callAnimateInterval=setTimeout("animateChange()",animateInterval);
		}
		else {
			pausePlayback(2);
		}
	}
	else {
		//if this was temporarily set, set it back!
		 animateSyncFinish=defaultAnimateSyncFinish;
		//we're done, we should do the next step immediately.
		//check to see if we're allowed to call back.
		if(!returningState && !outOfState && !settingState) {
			//do the normal.
			if (doSetCallback) {
			runNextStep();
			}
			else {
				pausePlayback(1);
			}
		}
		else {
			if(returningState) {
				//we apparently just finished return the state to normal. 
				// we're in charge of resetting the flags to normal, and then doing nothing.
				returningState=false;
				outOfState=false;
			}
			if (settingState) {
				//we apparently just finished setting state; we're now ready to deset state if need be.
				settingState=false;
			}
		}
	}
	
}
function animateSolutionChange() {
	//this function does almost exactly what animateChange does, althougth it is simpler because it does not support runNextStep or any
	//instruction sets, and also does not respect pause commansds.
	//this function calls itself until after decayOffset returns false.
	if (!useSmoothTransition) {
		//this is a smooth transition--that's all this is used for. if it's not on, quit
		return true;
	}
	var notDone=true;
	
	notDone=decaySolutionOffset(solutionOffset,decayInterval);
	renderSolution(targetSolution.join("|"));
	if(notDone) {
		//set a call back for this function.
		//check to see if we're allowed to call back.
		
		//this is a remnant of the pausing system,right? which we don't respect, right?
		//if(doSetCallback){
		setTimeout("animateSolutionChange()",animateInterval);
		//}
		
	}
	else {
		//if this was temporarily set, set it back!
		 //animateSyncFinish=defaultAnimateSyncFinish;
		//we're done, we should do the next step immediately.
		//check to see if we're allowed to call back.
		//we don't call anyone when we're done; we're just done.
		//fill the solutionSnapShot with a good, current picture!
		takeSolutionSnapShot(targetSolution.join("|"));
	}
	
}
function computeOffset(currentPuzzleArray, snapShotArray, offsetRegister, isMiniToken) {
	//currentpuzzle array is a copy (pipedelimited string)
	//snapshotArray is a a copy (pipe delimited string)
	//offsetRegister is the actual reference to the register
	//miniToken is an optional boolean set to true when it should use the solution Width.
	currentPuzzleArray=currentPuzzleArray.split("|");
	snapShotArray=snapShotArray.split("|");
	var currentIndex = -1;
	var currentRow, currentColumn, snapShotRow, snapShotColumn;
	var snapShotIndex = -1;
	var i;
	for (i=0;i<10;i++) {
		//locate the token number in each array
		
		for (j=0;j<currentPuzzleArray.length;j++) {
			if (currentPuzzleArray[j]==i) {
				currentIndex=j;
			}
			if (snapShotArray[j]==i) {
				snapShotIndex=j;
			}
		}
		//now that we know the two indexes, figure out how far away they are.
		if (currentIndex>7) {
			//it's in the second row
			currentRow=2;
			currentColumn=currentIndex-7;
		}
		else {
			currentRow=1;
			currentColumn=currentIndex;
		}
		if (snapShotIndex>7) {
			//it's in the second row
			snapShotRow=2;
			snapShotColumn=snapShotIndex-7;
		}
		else {
			snapShotRow=1;
			snapShotColumn=snapShotIndex;
		}
		//by adding the computed value to the new value, we retain the current offset--in other words, if the thing isn't done animating yet,
		//it doesn't appear to jump to its old finish position and then animate from there, but rather, it animated smoothly FROM ITS CURRENT
		//PHYSICAL LOCATION.  a BIG improvement. 
		if (isMiniToken) {
			offsetRegister[i].top=(snapShotRow-currentRow) * solutionTokenHeight + offsetRegister[i].top;
			offsetRegister[i].left=(snapShotColumn-currentColumn) * solutionTokenWidth + offsetRegister[i].left;
		}
		else {
			offsetRegister[i].top=((snapShotRow-currentRow) * tokenHeight + offsetRegister[i].top);
			offsetRegister[i].left=((snapShotColumn-currentColumn) * tokenWidth + offsetRegister[i].left);
			
		}
	}
	
}
function decaySolutionOffset(offsetRegister, decayAmount) {
	//functionally identical to decayOffset, except that it automatically changes decay amount by right about, and always uses the same ending time.
	
	decayAmount=decayAmount * 0.4;
	//we are given a reference to, in most cases, the puzzleOffset array.
	var factor=1;
	
	var maxOffset=0;
	var updatesLeft=0;
	var decayActive=false;
	var offsetAmount=0;
	
		//we're doing something special.
		//go through all the offsets (all of them, top and left) and see what the biggest one is.
		for(t=0;t<10;t++) {
			if(Math.abs(offsetRegister[t].top)>maxOffset) {
				maxOffset=Math.abs(offsetRegister[t].top);
			}
			if (Math.abs(offsetRegister[t].left)>maxOffset) {
				maxOffset=Math.abs(offsetRegister[t].left);
			}
		}
		//now maxoffset holds the largest change.
		//this next line assumes that decayAmount will be constant across all calls to decayOffset (which it normally is)
		updatesLeft=parseInt(maxOffset/decayAmount) + 1;
		//updatesLeft now contains the number of frames left before the animation completes.
		
	
	for(i=0;i<10;i++) {
		if(offsetRegister[i].top != 0) {
			decayActive=true;
			//the top offset should be decayed.
			if(offsetRegister[i].top > 0 ) {
				//we should decay down
				factor=-1;
			}
			else {
				//we should decay up
				factor=1;
			}
			
				//compute offsetAmount specially
				offsetAmount=(offsetRegister[i].top/updatesLeft) * -1;
			
			
			offsetRegister[i].top=offsetRegister[i].top + (offsetAmount);
			if (factor>0 && offsetRegister[i].top>0) {
				//we're decaying up and passed 0 in this pass. set offset to 0.
				offsetRegister[i].top=0;
			}
			if (factor<0 && offsetRegister[i].top<0) {
				//we're decaying down and passed 0 in this pass. set offset to 0.
				offsetRegister[i].top=0;
			}
			
		}
		if(offsetRegister[i].left !=0) {
			decayActive=true;
			//the left offset should be decayed.
			if(offsetRegister[i].left > 0 ) {
				//we should decay down
				factor=-1;
			}
			else {
				//we should decay up
				factor=1;
			}
			
				//compute offsetAmount specially
				offsetAmount=(offsetRegister[i].left/updatesLeft) * -1;
			
			
			offsetRegister[i].left=offsetRegister[i].left + (offsetAmount);
			if (factor>0 && offsetRegister[i].left>0) {
				//we're decaying up and passed 0 in this pass. set offset to 0.
				offsetRegister[i].left=0;
			}
			if (factor<0 && offsetRegister[i].top<0) {
				//we're decaying down and passed 0 in this pass. set offset to 0.
				offsetRegister[i].left=0;
			}
		}
	}
	//returns false if all of offsets are at 0, returns true if at least one offset was changed.
	return decayActive;
}
function decayOffset(offsetRegister, decayAmount) {
	//we are given a reference to, in most cases, the puzzleOffset array.
	var factor=1;
	
	var maxOffset=0;
	var updatesLeft=0;
	var decayActive=false;
	var offsetAmount=0;
	if (animateSyncFinish) {
		//we're doing something special.
		//go through all the offsets (all of them, top and left) and see what the biggest one is.
		for(t=0;t<10;t++) {
			if(Math.abs(offsetRegister[t].top)>maxOffset) {
				maxOffset=Math.abs(offsetRegister[t].top);
			}
			if (Math.abs(offsetRegister[t].left)>maxOffset) {
				maxOffset=Math.abs(offsetRegister[t].left);
			}
		}
		//now maxoffset holds the largest change.
		//this next line assumes that decayAmount will be constant across all calls to decayOffset (which it normally is)
		updatesLeft=parseInt(maxOffset/decayAmount) + 1;
		//updatesLeft now contains the number of frames left before the animation completes.
		
	}
	for(i=0;i<10;i++) {
		if(offsetRegister[i].top != 0) {
			decayActive=true;
			//the top offset should be decayed.
			if(offsetRegister[i].top > 0 ) {
				//we should decay down
				factor=-1;
			}
			else {
				//we should decay up
				factor=1;
			}
			if (animateSyncFinish) {
				//compute offsetAmount specially
				offsetAmount=(offsetRegister[i].top/updatesLeft) * -1;
			}
			else {
				//do it the normal way
				offsetAmount=factor * decayAmount;
			}
			offsetRegister[i].top=offsetRegister[i].top + (offsetAmount);
			if (factor>0 && offsetRegister[i].top>0) {
				//we're decaying up and passed 0 in this pass. set offset to 0.
				offsetRegister[i].top=0;
			}
			if (factor<0 && offsetRegister[i].top<0) {
				//we're decaying down and passed 0 in this pass. set offset to 0.
				offsetRegister[i].top=0;
			}
			
		}
		if(offsetRegister[i].left !=0) {
			decayActive=true;
			//the left offset should be decayed.
			if(offsetRegister[i].left > 0 ) {
				//we should decay down
				factor=-1;
			}
			else {
				//we should decay up
				factor=1;
			}
			if (animateSyncFinish) {
				//compute offsetAmount specially
				offsetAmount=(offsetRegister[i].left/updatesLeft) * -1;
			}
			else {
				//do it the normal way
				offsetAmount=factor * decayAmount;
			}
			offsetRegister[i].left=offsetRegister[i].left + (offsetAmount);
			if (factor>0 && offsetRegister[i].left>0) {
				//we're decaying up and passed 0 in this pass. set offset to 0.
				offsetRegister[i].left=0;
			}
			if (factor<0 && offsetRegister[i].left<0) {
				//we're decaying down and passed 0 in this pass. set offset to 0.
				offsetRegister[i].left=0;
			}
		}
	}
	//returns false if all of offsets are at 0, returns true if at least one offset was changed.
	return decayActive;
}

function switchDisplayModeNow() {
	//switch modes if neccesary
	//if no switch is neccesary, switchDisplayMode should equal currentDisplayMode;
	//this function is called by the SWITCH command in the instructionset exclusively, but could be called directly in other cases.
	var oldMode;
	
	//this function is called a lot, and sometimes twice in a row by the instruction set, so it's important that it can quit easily
	//if it doesn't need to do anything.
	
	
	
	if (currentDisplayMode.toUpperCase()!=switchDisplayMode.toUpperCase()) {
		//the mode has changed.
		if(currentDisplayMode=="PRINT") {
			//switching from the non-animated mode to any other requires that we start over.
			prependAction += "|MESSAGE:0:DEFAULT:" + escape("b::The puzzle is starting over from the beginning.|br::void|br::void|TEXT::If your puzzle doesn't still look like the one on the screen, click |a>href=javascript:interfaceCommand{'NEWPUZZLE')::'New Puzzle'|TEXT:: to set it correctly.|br::void|br::void|b::Otherwise, click 'Continue' to go on.") + "|PLAYBACK:BYSTEP,BYSECTION,NOHELP,PRINT:PAUSE";
			startPuzzleOver(useSmoothTransition);
			document.getElementById("MessageArea").style.visibility="visible";
			//document.getElementById("PlayControls").style.visibility="visible";
			enableInterface("StartOverButton,FreePlayButton",false);
			//until we fix the controlbar offset in print mode, problem, we should jsut do this:
			document.getElementById("FreePlayButtonArea").style.visibility="visible";
			document.getElementById("InterfaceBackground").style.height="550px";
			document.getElementById("InterfaceCap").style.visibility="hidden";
			document.getElementById("InstructionArea").style.visibility="visible";
			document.getElementById("ControlBar").style.marginLeft="0px";
			document.getElementById("FlipSideGame").style.position="absolute";
			document.getElementById("PrintBox").style.visibility="hidden";
			
		
		}
		
		
		
		//first, set the currentDisplaymode to the new mode (what runNextStep and doMessage look at)
		oldMode=currentDisplayMode;
		
		switch(switchDisplayMode) {
			
			case "NOHELP":
				// clear text
				//switch now.
				//already switched, do nothing.
				displayText("b::Watch as the puzzle is solved...");
				//resumePlayback();
				break;
			case "PRINT":
				//this mode is special in that it does not have animation.
				//ensure that we're paused, for god's sake!
				
				firstInstructionSet=true;
				startPuzzleOver(useSmoothTransition);
				preparePrintText(currentSolutionString);
				document.getElementById("MessageArea").style.visibility="hidden";
				enableInterface(false,"StartOverButton,FreePlayButton");
				//until we fix the controlbar offset in print mode, problem, we should jsut do this:
				document.getElementById("FreePlayButtonArea").style.visibility="hidden";
				//document.getElementById("PlayControls").style.visibility="hidden";
				document.getElementById("InstructionArea").style.visibility="hidden";
				document.getElementById("InterfaceBackground").style.height="280px";
				document.getElementById("InterfaceCap").style.visibility="visible";
				document.getElementById("FlipSideGame").style.position="static";
				document.getElementById("ControlBar").style.marginLeft="25px";
				document.getElementById("PrintBox").style.visibility="visible";
				

				
				break;
			case "BYSTEP":
				//go ahead and switch; done
				break;
			case "BYSECTION":
				//go ahead and switch; done
				break;
			default:
				//nothing.
				break;
		}
		currentDisplayMode=switchDisplayMode;
	}
	
			
}

function playbackCommand(commandType) {
	//Valid command types:
	// PAUSE
	// RESUME
	// STOP
	commandType=commandType.toUpperCase();
	switch (commandType) {
		case "PAUSE":
		
			if (!enablePausing) {
				//this flag is only to be used once.
			
				enablePausing=true;
			}
			else {
			enablePausing=true;
			pausePlayback();
			}
			break;
		case "RESUME":
			resumePlayback();
			break;
		case "STOP":
			stopPlayback();
			break;
		default:
			alert("Error: Unknown playback command: " + commandType);
			break;
	}
	
}

function pushSolveModeCommand(commandToPush) {
	if(!commandToPush) {
		//we're given nothing, exit
		return false;
	}
	if(instructionSet=="#NOTHING#") {
		//add the command, run the next step.
		instructionSet=commandToPush;
		runNextStep();
	}
	else {
		//append the next instruction... don't do anything?
		instructionSet =  commandToPush + "|" + instructionSet;
		//make sure it's unpaused
		resumePlayback();
	}
}

function pushPlayModeCommand(commandToPush) {
	//this function adds the command to the instructionSet, and figures out if it needs to be called or not.
	if (!commandToPush) {
		//we're given nothing, exit
		return false;
	}
	//a success in PLAYGOALCOMPLETE sets the goal to -2, which enables reading of FREEPLAYCOMPLETE, which would disagree and say it's NOT finished/
	//that's why FREEPLAYCOMPLETE must go first. 
	commandToPush=commandToPush + "|CHECK:FREEPLAYCOMPLETE|CHECK:PLAYGOALCOMPLETE";
	if(instructionSet=="#NOTHING#") {
		
		//add the command, run the next step
		
		instructionSet=commandToPush;
		runNextStep();
	}
	else {
		//append the command, don't call anything
		instructionSet=instructionSet + "|" + commandToPush;
		
		
	}
}

function playFlip() {
	//flips the puzzle.
	if (currentUseMode.toUpperCase()=="PLAY") {	
		pushPlayModeCommand("FLIP");
	}
}

function playShiftRow(row, shiftAmount) {
	//shift amount is either 1 or -1 or 0 -- positive moves to right, negative moves to left.
	//first, we figure out the current row position
	if (currentUseMode.toUpperCase()=="PLAY") {
		shiftAmount = shiftAmount * -1;
		var currentPosition;
		var otherCurrentPosition;
		var offset=0;
		if (row==2) {
			offset=7;
		}
		else {
			offset=0;
		}
		//figure out our row's position
		if (workingPuzzle[1 + offset]==-1) {
			//either 0 or 1
			if (workingPuzzle[2+offset]==-1) {
				//in o
				currentPosition=0;
			}
			else {
				//in 1
				currentPosition=1;
			}
		}
		else {
			//it's in position 2
			currentPosition=2;
		}
		if (row==1) {
			offset=7;
		}
		else {
			offset=0;
		}
		//figure out other row's position(it makes sense; i swear)
			if (workingPuzzle[1 + offset]==-1) {
			//either 0 or 1
			if (workingPuzzle[2+offset]==-1) {
				//in o
				otherCurrentPosition=0;
			}
			else {
				//in 1
				otherCurrentPosition=1;
			}
		}
		else {
			//it's in position 2
			otherCurrentPosition=2;
		}
		
		if(currentPosition+shiftAmount>-1 && currentPosition+shiftAmount<3) {
			//the shift is valid.
			currentPosition=currentPosition + shiftAmount;
		}
		else {
			//it's invalid, return current.
		}
		var rowPosOne, rowPosTwo;
		if (row==1) {
			rowPosOne=currentPosition;
			rowPosTwo=otherCurrentPosition;
		}
		else {
			rowPosOne=otherCurrentPosition;
			rowPosTwo=currentPosition;
		}
		pushPlayModeCommand("SHIFT:" + rowPosOne + ":" + rowPosTwo);
	}
}

function startGoalPlay(goalStep) {
	playModeStepGoal=goalStep;
	enableInterface(false,"FreePlayButton");
	switchCurrentUseMode("PLAY",goalStep);
}



function startFreePlay() {
	//do stuff similar to endGoalPlay, except, after switchCurre... is called, reenable new puzzle and set goal.
	//this should also handle interface issues (like switching the text of the button that switches between modes.
	//also, the printed message needs to be different. 
	playModeStepGoal=-2;
	switchCurrentUseMode("PLAY",-2);
	//re-enable newgoal, newpuzzle buttons. 
	enableInterface("NewGoalButton,NewPuzzleButton",false);
	displayText("a>href=javascript:interfaceCommand('FREEPLAY')>class=" + document.getElementById("FreePlayButton").className + ">id=FreePlayButton::Solve Mode","FreePlayButtonArea");
	
}

function endFreePlay() {
	//we never use a buffer; we just burn in what we have right now.
	if (currentUseMode.toUpperCase()=="PLAY") {
		takeSolutionSnapShot(targetSolution.join("|"));
		takeSnapShot(workingPuzzle.join("|"));
		
		switchCurrentUseMode("SOLVE");
	}
	//switch back free play button.
	displayText("a>href=javascript:interfaceCommand('FREEPLAY')>class=" + document.getElementById("FreePlayButton").className + ">id=FreePlayButton::Play Mode","FreePlayButtonArea");
}

function endGoalPlay(useBuffer) {
	//you'd use buffer if you hadn't followed along with the physical copy
	
	if (currentUseMode.toUpperCase()=="PLAY") {
		takeSolutionSnapShot(targetSolution.join("|"));
		takeSnapShot(workingPuzzle.join("|"));
		if (useBuffer) {
			//load up buffer
			workingPuzzle=playPuzzle.join("|");
			workingPuzzle=workingPuzzle.split("|");
			//and don't tell me about it, it's confusing! just show me how to solve this section that I couldn't.
			enableNormalMessage=false;
		}
		enableInterface("FreePlayButton",false);
		
		switchCurrentUseMode("SOLVE");
	}
	
}

function switchCurrentUseMode(newMode, stepNumber) {
	newMode=newMode.toUpperCase();
	if(newMode==currentUseMode) {
		//don't need to switch
		return;
	}
	switch (newMode) {
		case "SOLVE":
			//do stuff
			//make sure play mode controls are hidden
			//**********************
			
			currentUseMode="SOLVE";
			instructionSet="#NOTHING#";
			//home the array
			workingPuzzle=positionRow(1, 2, workingPuzzle.join("|"));
			workingPuzzle=positionRow(2, 2, workingPuzzle.join("|"));
			//use beginPuzzle just like we had just submitted a puzzle. (but override the first already done messages)
			//takeSolutionSnapShot(targetSolution.join("|"));
			//takeSnapShot(workingPuzzle.join("|"));
			enablePlayControls(false);
			//we already know a lot of the skipped messages (at the beginning, any way)
			if (playModeStepGoal==-2) {
				//we're exiting from FreePlay.
				enableSkippedMessages=true;
				firstInstructionSet=true;
				beginPuzzle(true,useSmoothTransition,true);
			}
			else {
				//we're exiting from GoalPlay.
				playModeStepGoal=-2;
				enableSkippedMessages=false;
				
				beginPuzzle(true,useSmoothTransition); //useSmooth Transition is the global value, which we respect.
			}
			break;
		case "PLAY":
			//do stuff
			//*******************
			currentUseMode="PLAY";
			playPuzzle=workingPuzzle.join("|");
			playPuzzle=playPuzzle.split("|");
			enablePlayControls(true);
			resetPuzzle();
			
			var textToDisplay="b::Use the blue buttons next to the puzzle display to manipulate the puzzle.|br::void|br::void";
			if ((stepNumber || stepNumber==0) && stepNumber>=0) {
				
				highlightPieces(returnStepLocation(stepNumber),false,returnStepLocation(stepNumber),puzzleOffset);
				highlightPieces(returnStepLocation(stepNumber),returnStepLockedLocations(stepNumber),returnStepLocation(stepNumber),solutionOffset);
				renderGame(workingPuzzle.join("|"));
				renderSolution(targetSolution.join("|"));
				textToDisplay+="|TEXT::Don't worry about keeping your physical copy of the puzzle in sync with what you manipulate on screen.  When you succeed (or give up!) the computer will give you the option to go back to where you were before you tried to solve it.|br::void|br::void";
				textToDisplay +="|b::Stuck? |a>href=javascript:endGoalPlay(true)::Click here|b:: to have the computer show you what to do, assuming that you left your physical copy of the puzzle out of sync with your on-screen manipulations.";
			}
			else {
				//prepare the message for freeplay
				textToDisplay += "|TEXT::To go back to solve mode, click on the 'Solve Mode' button. To set the puzzle to a new state, click the 'New Puzzle' mode.  To set a new solution goal, click 'Enter New Goal'.";
			}
			
			
			
			
			
			displayText(textToDisplay);
			break;
		default:
			alert("Unknown Use Mode Type: " + newMode);
			break;
	}
		
	
}

function resumePlayback() {
	//starts up where we should if we aren't already running.
	//check to see if we're out of state
	if(outOfState) {
		//the puzzle isn't where the instructionSet thinks it is, so we can't do anything.
		if(returningState) {
			//we've already told it to return, so we can't do anything.
		}
		else {
			//it's just sitting there! tell it to return!
			returnState();
		}
		return;
	}
	switch(callbackFunctionID) {
		case 1:
			//call runextstep;
			isPaused=false;
			enableInterface("PauseButton","ResumeButton");
			//document.getElementById("ResumeButton").className="DisabledControl";
			//document.getElementById("PauseButton").className="EnabledControl";
			callbackFunctionID=0;
			runNextStep();
			
			break;
		case 2: 
			//call animate change
			callbackFunctionID=0;
			enableInterface("PauseButton","ResumeButton");
			//document.getElementById("ResumeButton").className="DisabledControl";
			//document.getElementById("PauseButton").className="EnabledControl";
			isPaused=false;
			animateChange();
			break;
		default:
			//do nothing, because it's not paused!
			isPaused=false;
			break;
	}
}

function checkMode(modeList) {
	//mode list is a comma-delimited string of acceptable modes. ALL is also accepted.
	
	 modeList=modeList.split(",");
	for (u=0;u<modeList.length;u++) {
		if(modeList[u].toUpperCase()==currentDisplayMode.toUpperCase() || modeList[u].toUpperCase()=="ALL") {
			
			return true;
			
		}
	}
	//if we get to here, then the mode list is not correct.
	return false;
	
}

function pausePlayback(functionToCall) {
	//this function is called by three seperate functions.
	
	//if it's called without an argument, it means to set the pauseNow flag to true.
	//the next time either of the two step functions is about to set the interval, they will check the pausenow flag.
	//if it's true, instead of setting the interval, they'll call this function with their ID (1 or 2) to let it know what to call
	//on resume.
	
	
	if(isPaused==false) { //don't pause again if we're already paused!
		
		if (functionToCall) {
			
			isPaused=true;
			enableInterface("ResumeButton","PauseButton");
			//document.getElementById("PauseButton").className="DisabledControl";
			//document.getElementById("ResumeButton").className="EnabledControl";
			doSetCallback=true;
			callbackFunctionID=functionToCall;
			
		}
		else {
			// just set the do not continue flag.
			doSetCallback=false;
		}
	}
	
	
	
}

function stopPlayback() {
	//stops playback, clears instruction set, all intervals, etc.
	
	//emulates old stopAllNow functionality
	clearTimeout(callNextStepInterval);
	clearTimeout(callAnimateInterval);
	resetPuzzle();
	renderGame(workingPuzzle.join("|"));
	displayText("b::FlipSide Solver|br::void|br::void|a>href=javascript:inputPuzzle(false,false)::Click here|TEXT:: to start a new game, or |a>href=javascript:inputPuzzle(true,false)::click here|TEXT:: to set a new target solution.");
}

function runNextStep() {
	//IMPORTANT:
	//the way that pausing is accomplished is by simply ceasing to call runNextStep or AnimateChange.  Thus, if we hit here, then we're obviously unpaused!
	//(some other functions create a new instruction set and then initiate runNextStep automatically, and if we think that we're paused,
	  //then pausing functionality breaks until a reset.
	  //So, if we get here, verify that we are not paused.
doSetCallback=true;
 isPaused=false;
 callNextStepInterval=-1;
 callAnimateInterval=-1;
 //CONSIDER ADDING THIS TO ANIMATECHANGE, too?  Would that create any more problems?
	//This function converts the instruction set to an array, shifts off the first term, reconverts it to a string.
	//The term it shifts off is done, and, as long as the instruction set is not empty, it sets a call back interval

	/* *********************************
	Syntax: 
	INSTRUCTION:arg:arg:arg
	Valid instructions:
	MESSAGE:importantFlag(0 or 1):message type:Message text: (optional) associated step number
	SHIFT:rowNumber:rowPosition
	FLIP (no args)
	STEP: amount to add, subtract, or _ for reset to 0.
	SWITCH: (no args)
	CHECK:typetocheck
	PLAYBACK: modeList:command (where modelist is a commadelimited string of the different displaymodes that this command should apply to, and command is either STOP, PAUSE, or RESUME
	VOID: (no args; does nothing)
	INTERFACE:modeList:command:extraArgs:extraArgs Command refers to different modes of the command, where extraArgs for each mode are given below:
		* ENABLE commadelimListOfElementsToEnable:commadelimListOfElementsToDisable (leave elements to leave untouched off.) elements are reffered to by ID.
		* TEXT elementID>newText|elementID>newText
		* LINK elementID>newLink|elementID>newLink
	************************************** */
	if (instructionSet=="#NOTHING#" ||instructionSet.length<1) {
		//there's nothing to run!
		return;
	}
	
	instructionSet=instructionSet.split("|");
	var currentInstruction = instructionSet.shift();
	//reset the instruction set immediately so that if we quit out of this function, it will still be ready in a format we are expecting.
	instructionSet=instructionSet.join("|");
	var enableDelay=true;
	var callBack=false;
	var message="#NOTHING#"
	var modeList="";
	currentInstruction=currentInstruction.split(":");
	switch (currentInstruction[0].toUpperCase()) {
		case "":
			//the blank command is not technically valid, but it's easier to just ignore it than confuse the user. 
			callBack=true;
			enableDelay=false;
			break;
		case "STEP":
			callBack=true;
			enableDelay=false;
			//this used to have other functionality; now it does nothing.  slated for REMOVAL.
			break;
		case "INTERFACE":
			callBack=true;
			enableDelay=false;
			if(checkMode(currentInstruction[1])) {
				//we're supposed to run, but what are we supposed to do?
				switch(currentInstruction[2].toUpperCase()) {
					case "ENABLE":
						enableInterface(unescape(currentInstruction[3]),unescape(currentInstruction[4]));
						break;
					case "TEXT":
						//set text
						interfaceText(unescape(currentInstruction[3]));
						break;
					case "LINK":
						//set link
						interfaceLink(unescape(currentInstruction[3]));
						break;
					default:
						alert("Unknown Interface Command Mode: " + currentInstruction[2]);
						break;
				}
			}
			break;
		case "CHECK":
			callBack=true;
			enableDelay=false;
			//this function is normally called in PLAY mode to check to see if the puzzle is done.
			switch(currentInstruction[1].toUpperCase()) {
				case "PLAYGOALCOMPLETE": 
					//check to see if it matches the playgoal. if it does, switch to solve mode.
					
					if(playModeStepGoal==-2) {
						//get outta here; it's not complete because there is no goal, so do nothing.
					}
					else {
						//let's check here
						//it's important that it's less than or equal to, becuase sometimes you'll have inadvertently solved the next segment, too.
						if(playModeStepGoal+1<=puzzleCurrentStep(workingPuzzle.join("|"),targetSolution.join("|"))) {
							//we're done
							//return to solve mode. 
							
							
							//don't let them change anything now! they're done!
							enableInterface(false,"FlipAreaButton,TopRowLeftButton,TopRowRightButton,BottomRowLeftButton,BottomRowRightButton,SwitchToSolveButton");
							//this is the only time we give them the option to go back to SOLVE mode saving the progress they did.
							displayText("b::Congratulations, you completed the stage!|br::void|br::void|a>href=javascript:endGoalPlay(true)::Click here|b:: to go back to Solve mode and have the computer show you how to do what you just did on your physical copy.|br::void|br::void|b::Or |a>href=javascript:endGoalPlay(false)::click here|b:: if your followed along with your physical copy, and have already done this step.");
							
						}
						else {
							//not done
							//they might not understand that all of the pieces have to be in the right place.
							if (workingPuzzle[returnStepLocation(playModeStepGoal)]==targetSolution[returnStepLocation(playModeStepGoal)]) {
								//they've got the highlighted piece in the right place, but not the other, locked pieces.
								displayText ("b::Don't forget that not only does the highlighted piece have to be in the correct location, but also the grayed-out pieces in the goal area should end up in the right places, too!|br::void|br::void|b::Give up? |a>href=javascript:endGoalPlay(true)::Click here|b:: to have the computer show you what to do, starting from where you left off.");
							}
						}
					}
					break;
				case "FREEPLAYCOMPLETE":
					if (playModeStepGoal!=-2) {
						//get outta here
					}
					else {
						
						if(puzzleIsFinished(workingPuzzle.join("|"),targetSolution.join("|"))) {
							//display the finished message.
							displayText("b::Congratulations! You've solved the puzzle!|br::void|br::void|TEXT::Now, click either 'New Puzzle' or 'Enter New Goal' to continue playing.");
							//lock the controls
							enablePlayControls(false);
							//however, this enables the resetpuzzle button, so disable that
							//but allow them to put in a new puzzle
							enableInterface("NewPuzzleButton,NewGoalButton","StartOverButton");
						}
						else {
							//display the default message, which shows up when you first go to play mode.
							displayText("b::Use the blue buttons next to the puzzle display to manipulate the puzzle.|br::void|br::void|TEXT::To go back to solve mode, click on the 'Solve Mode' button. To set the puzzle to a new state, click the 'New Puzzle' mode.  To set a new solution goal, click 'Enter New Goal'.");
				
						}
						
						
					}
				default: 
					break;
				
				
			}
			break;
		case "VOID":
			callBack=true;
			enableDelay=false;
			//this is just an action that does nothing but fill space (on purpose, of course);
			break;
			
		case "SWITCH":
			callBack=true;
			enableDelay=false;
			switchDisplayModeNow();
			break;
		case "PLAYBACK":
			//arg 1 accepts a comma-delimited list of applicable modes.
			callBack=true;
			enableDelay=false;
			currentInstruction[1]=currentInstruction[1].toUpperCase();
			var doPlaybackCommand=checkMode(currentInstruction[1]);
			
			
			if (doPlaybackCommand) {
				//call the  playback function
				
				playbackCommand(currentInstruction[2]);
			}
			break;
		case "MESSAGE":
			//reconstruct the command, and save it to lastMessageCommand, which is used by replaySegment. 
			lastMessageCommand="MESSAGE";
			for(r=1;r<currentInstruction.length;r++) {
				lastMessageCommand += ":" + currentInstruction[r];
			}
			if (currentInstruction[4]) {
				
				//we're also given a step number. send it.
				doMessage(currentInstruction[3], currentInstruction[1], currentInstruction[2], currentDisplayMode, currentInstruction[4]);
			}
			else {
				//we're not given the optional step number.
				doMessage(currentInstruction[3], currentInstruction[1], currentInstruction[2], currentDisplayMode);
			}
			//we just did a message; we didn't start any animation chains.  make sure this function calls itself back.
			callBack=true;
			//The message itself, which is modal, already behaves as a delay; we don't need another one.
			enableDelay=false;
			break;
		case "SHIFT":
			takeSnapShot(workingPuzzle.join("|"));
			workingPuzzle=positionRow(1, currentInstruction[1], workingPuzzle.join("|"));
			workingPuzzle=positionRow(2, currentInstruction[2], workingPuzzle.join("|"));
			if (doAnimate) {
				//start animate routine.
				computeOffset(workingPuzzle.join("|"), snapShotPuzzle.join("|"), puzzleOffset);
				animateChange();
			}
			else {
				//render it the old fashioned way
			renderGame(workingPuzzle.join("|"));
			}
			break;
		case "FLIP":
			takeSnapShot(workingPuzzle.join("|"));
			prepareFlipZIndex(workingPuzzle.join("|"));
			workingPuzzle=flipPuzzle(workingPuzzle.join("|"));
			if (doAnimate) {
				//start animate routine
				computeOffset(workingPuzzle.join("|"), snapShotPuzzle.join("|"), puzzleOffset);
				animateChange();
			}
			else {
				//render it the old fashioned way.
			renderGame(workingPuzzle.join("|"));
			}
			break;
		default:
			alert("Unknown command in instruction set: " + currentInstruction[0]);
			break;
	}
	
	if (instructionSet.length>0) {
		/*We should call back this function (to run the next step) ourselves if
		a) We're not using smooth animation
		b) The command in this iteration did not initiate an animation procedure (does not matter if using smooth animation or not)
		*/
		if (doAnimate==false || callBack==true) {
			//check to see if we're allowed to call back (we don't have a pause request active)
			if(doSetCallback) {
				if (enableDelay) {
					callNextStepInterval=setTimeout("runNextStep()", currentInterval);
				}
				else {
					callNextStepInterval=setTimeout("runNextStep()", 0);
				}
			}
			else {
				//tell the pauseplayback where to call on resume.
				pausePlayback(1);
			}
		}
	}
	else {
		//The instruction set is finished.
		  instructionSet="#NOTHING#";
	}
	
}

function interfaceText(commandString) {
	//This function is called by the INTERFACE command.
	//commandString is formatted as follows:
	//elementID>newText|elementID>newText
	var element, newText, pair;
	commandString=commandString.split("|");
	
	for(q=0;q<commandString.length;q++) {
		pair=commandString[q].split(">");
		element=document.getElementById(pair[0]).firstChild;
		newText=pair[1];
		element.nodeValue=newText;
		
	}
}

function interfaceLink(commandString) {
	//This function is called by the INTERFACE command.
	//commandString is formatted as follows:
	//elementID>newLink|elementID>newLink
	var element, newLink, pair;
	commandString=commandString.split("|");
	
	for(q=0;q<commandString.length;q++) {
		pair=commandString[q].split(">");
		element=document.getElementById(pair[0]);
		newLink=pair[1];
		
		element.href=newLink;
		
	}
}

function enableInterface(idsToEnable,idsToDisable) {
	//idsToEnabled and idsToDisable are comma delimited lists.
	var IDList="";
	if (idsToEnable && idsToEnable.toUpperCase()!="VOID") {
	 IDList=idsToEnable.split(",");
			 //elements to enable
			for (u=0;u<IDList.length;u++) {
				if((IDList[u].toUpperCase()=="NEWGOALBUTTON" && startLockNewGoal) || (IDList[u].toUpperCase()=="NEWPUZZLEBUTTON" && startLockNewPuzzle) || (IDList[u].toUpperCase()=="FREEPLAYBUTTON" && startLockMode) || ((IDList[u].toUpperCase()=="DISPLAYBYSECTION" || IDList[u].toUpperCase()=="DISPLAYNOHELP" || IDList[u].toUpperCase()=="DISPLAYBYSTEP" || IDList[u].toUpperCase()=="DISPLAYPRINT") && startLockDisplayMode)) {
					//don't enable.
				}
				else {
					//go ahead and enable.

					if(IDList[u].indexOf("Display")>-1) {
						//we're being given a display option
						document.getElementById(IDList[u]).disabled=false;
					}
					else {
						//we're beging given a normal control.
						document.getElementById(IDList[u]).className="EnabledControl";
					}
				}
			}
	}
	if (idsToDisable && idsToDisable.toUpperCase()!="VOID") {
			IDList=idsToDisable.split(",");
			//elements to disable
			for(u=0;u<IDList.length;u++) {
				if(IDList[u].indexOf("Display")>-1) {
					//we're being given a display option
					document.getElementById(IDList[u]).disabled=true;
				}
				else {
					//we're beging given a normal control.
					document.getElementById(IDList[u]).className="DisabledControl";
				}
			}
	}
}

function showStateButton(cardNumber, down) {
	//all of the showStateButtonAreas onmousedown event hits here, sending merely their number.
	//down is a boolean stating whether the mousebutton is down or not--on the mousedown event, it is true, on mouseup, false.
	//we are responsible for figuring out which moveID this is and calling it.
	
	
	if (down) {
		//the card.Title attribute holds the associated step, -1 for none.
		var workingMoveID;
		cardNumber=parseInt(cardNumber);
	 
		workingMoveID=instructionCards[cardNumber].Title;
		workingMoveID=parseInt(workingMoveID)-1;
		//NOTE: because 0 and false are logically the same in javascript, EVERY non-staging card gets the 0 if you send false.
			//for that reason, 0 is not a valid step index; when this step index was encoded, it had one added to it (if it was non-zero, that is). 
		
		if (workingMoveID<0) {
			//the card is not a step-associated. exit.
			return;
		}
		//showState
		showState(workingMoveID);
	}
	else {
		//returnState
		returnState();
	}
	
}

function showState(moveID) {
	//moveID starts at 0.

	
	if (!enableShowState || !isPaused || outOfState || returningState || settingState) {
		//don't allow show state! quit!
		//don't allow if:
		//* the global switch is off
		//* we're not paused; that is, the animation could already be moving
		//* we're ALREADY technically outOfState (like, if you pressed the mouse button, released, and pressed again, which includes returningSTate.
		return;
	}
	
	
		
		
	
	//this sets a global flag, called outOfState, to true.
	outOfState=true;
	settingState=true;
	var tempPuzzle = savedSectionSnapShot.join("|");
	tempPuzzle=tempPuzzle.split("|");
	if(savedSection.toUpperCase=="#NOTHING#") {
		//no section string for some reason
		return;
	}
	var computedMoveString="";
	
	var moveString=savedSection.split(";");
	
	//reconstruct the moveString up through moveID
	var computedMoveString=moveString[0];
	for (b=1;b<(moveID+1);b++) {
		computedMoveString+= ";" + moveString[b];
	}
	//so now we've got the sectionState, and the way to get to our target. do it.
	
		
		
		//do the moves but do not return home.
		tempPuzzle= performMoves(computedMoveString, tempPuzzle.join("|"), true);
	
	//now tempPuzzle holds the state we want to show. 
	//copies workingPuzzle to trueStateBuffer
	trueStateSnapShot=workingPuzzle.join("|");
	trueStateSnapShot=trueStateSnapShot.split("|");
	//take snapshot
	takeSnapShot(workingPuzzle.join("|"));
	workingPuzzle=tempPuzzle.join("|");
	workingPuzzle=workingPuzzle.split("|");
	
	computeOffset(workingPuzzle.join("|"), snapShotPuzzle.join("|"), puzzleOffset, false);
	
	//now animateChange.
	animateSyncFinish=true;
	animateChange();
	
	
	
	
	
}

function returnState() {
	//if outOfState is false, then quit!
	//otherwise, set it to the trueStateBuffer, animate change. 
	if(!outOfState || returningState) {
		//we're not out of state or we've already started the return, so there's no need to return! quit!
		return;
	}
	//tell everyone that we're working on returningState.
	returningState=true;
	takeSnapShot(workingPuzzle.join("|"));
	workingPuzzle=trueStateSnapShot.join("|");
	workingPuzzle=workingPuzzle.split("|");
	computeOffset(workingPuzzle.join("|"), snapShotPuzzle.join("|"), puzzleOffset, false);
	//animate change is the one who turns off returningState and outOfState flags when we're done.
	animateSyncFinish=true;
	if(settingState) {
		//animateChange is already being called.
	}
	else {
		animateChange();
	}
}

function interfaceCommand(commandName) {
	//this function stands as a go-between between the various interfaceCommands and what they apply to.  it determines if they're valid in this display/use mode
	//and if they are enabled.
	switch(commandName.toUpperCase()) {
		case "RESUME":
			if (document.getElementById("ResumeButton").className.toUpperCase()=="ENABLEDCONTROL") {
				resumePlayback();
			}
			break;
		case "PAUSE":
			if (document.getElementById("PauseButton").className.toUpperCase()=="ENABLEDCONTROL") {
				pausePlayback();
			}
			break;
		case "FREEPLAY":
			if (document.getElementById("FreePlayButton").className.toUpperCase()=="ENABLEDCONTROL") {
				//this is a toggle button; it's called to do two things.
				//we're going to assume that if we get to here and we're in play mode, that we're not in goalplaymode, because this should be disabled in goalplaymode.
				if(currentUseMode.toUpperCase()=="PLAY") {
					endFreePlay();
				}
				else {
					startFreePlay();
				}
				
			}
			break;
		case "REPLAY":
			if (document.getElementById("ReplayButton").className.toUpperCase()=="ENABLEDCONTROL") {
				replaySegment();
			}
			break;
		case "TOPROWLEFTBUTTON":
			if(document.getElementById("TopRowLeftButton").className.toUpperCase()=="ENABLEDCONTROL") {
				playShiftRow(1,-1);
			}
			break;
		case "TOPROWRIGHTBUTTON":
			if(document.getElementById("TopRowRightButton").className.toUpperCase()=="ENABLEDCONTROL") {
				playShiftRow(1,1);
			}
			break;
		case "BOTTOMROWLEFTBUTTON":
			if(document.getElementById("BottomRowLeftButton").className.toUpperCase()=="ENABLEDCONTROL") {
				playShiftRow(2,-1);
			}
			break;
		case "BOTTOMROWRIGHTBUTTON":
			if(document.getElementById("BottomRowRightButton").className.toUpperCase()=="ENABLEDCONTROL") {
				playShiftRow(2,1);
			}
			break;
		case "FLIPAREABUTTON":
			if(document.getElementById("FlipAreaButton").className.toUpperCase()=="ENABLEDCONTROL") {
				playFlip();
			}
			break;
		case "SWITCHTOSOLVEBUTTON":
			if(document.getElementById("SwitchToSolveButton").className.toUpperCase()=="ENABLEDCONTROL") {
				endGoalPlay(true);
			}
			break;
		case "NEWPUZZLE":
			if(document.getElementById("NewPuzzleButton").className.toUpperCase()=="ENABLEDCONTROL") {
				inputPuzzle(false,false);
			}
			break;
		case "NEWGOAL":
			if(document.getElementById("NewGoalButton").className.toUpperCase()=="ENABLEDCONTROL") {
				inputPuzzle(true,false);
			}
			break;
		case "STARTOVER":
			if(document.getElementById("StartOverButton").className.toUpperCase()=="ENABLEDCONTROL") {
				startPuzzleOver(useSmoothTransition);
			}
			break;
		default:
			alert("Unknown interfaceCommand: " + commandName);
			break;
		
	}
}
function prepareFlipZIndex(puzzleCopy) {
	//this function checks which tokens are currently in the flip area,
	//and puts the top row on z-index 100 and the bottom on z-index 99.
	//accepts a puzzle array joined with ("|");
	//does not manipulate the array, just the objects associated with those numbers
	puzzleCopy=puzzleCopy.split("|");
	var indexToPosition=new Array(3,4,5,10,11,12);
	var tokenIndex;
	var tokenObject;
	for (q=0;q<indexToPosition.length;q++) {
		tokenIndex=puzzleCopy[indexToPosition[q]];
		tokenObject=document.getElementById("token" + tokenIndex);
		if (indexToPosition[q]>9) {
			//bottom row
		
		tokenObject.style.zIndex=99;
		}
		else  {
			//top row
			tokenObject.style.zIndex=100;
		}
		
	}
}

function preparePrintText(workingSolutionString) {
	
	//prepares and outputs printText.
	//this makes use of the displaytext function, outputting simply to PrintBoxText.
	//we'll be parsing the raw currentSolutionString.
	var result="";
	var sectionString;
	var stepCounter=1;
	var sectionCounter=0;
	var stepString;
	
	if(workingSolutionString=="#~#~#~#~#~#~#~#~#") {
		//the puzzle's already solved!
		result="span>class=PrintDone::The puzzle is already solved!"
	}
	else {
		//parse this stuff
		result="span>class=PrintHeading::Instructions|br::void";
		sectionString=workingSolutionString.split("~");
		for (e=0;e<sectionString.length;e++) {
			if(sectionString[e]=="#") {
				//this step was skipped
				result+="|br::void|span>class=PrintSkipped::Normally, we'd now get the token with the number " + targetSolution[returnStepLocation(sectionCounter)] + " in the " + returnPrettyNameLocation(returnStepLocation(sectionCounter)) + ", but the token is already there!|br::void";
				
				sectionCounter += 1;
			}
			else {
				//send the string for further processing by ;
				result += "|br::void|span>class=PrintSection::The goal of this next section is to get the token with the number " + targetSolution[returnStepLocation(sectionCounter)] + " in the " + returnPrettyNameLocation(returnStepLocation(sectionCounter)) + ".|br::void";
				sectionCounter += 1;
				stepString=sectionString[e].split(";");
				for (w=0;w<stepString.length;w++) {
					
					result += "|br::void|span>class=PrintStepNumber::" + stepCounter + ") |span>class=PrintStep::" +stepString[w] + ": flip.";
					stepCounter += 1;
				}
				result += "|br::void";
			}
			
			
		}
		result += "|br::void|span>class=PrintDone::Finished!";
	}
	
	displayText(result,"PrintBoxText");
	
}

function dimInterface(doDim) {
	//IE has an annoying bug where select boxes appear above every other element, no matter what.
	//to minimze the visible effect of this, when we dim the interface, we simply make the box invisible.
	if (!enableInterfaceDimming || !doDim || currentDisplayMode.toUpperCase()=="PRINT") {
		//don't dim interface
		document.getElementById("DimmedInterface").style.visibility="hidden";
		document.getElementById("DisplaySpeed").style.visibility="visible";
	}
	else {
		//do dim interface 
		document.getElementById("DimmedInterface").style.visibility="visible";
		document.getElementById("DisplaySpeed").style.visibility="hidden";
	}
		
}

function changeDisplaySpeed() {
	//read temp value from DisplaySpeed select in Form ControlBarForm, set global flags accordingly.
	var value=document.getElementById("ControlBarForm").DisplaySpeed[document.getElementById("ControlBarForm").DisplaySpeed.selectedIndex].value;
	switch(parseInt(value)) {
		case 0:
			
			//slowest
			
			animateInterval=30;
			decayInterval=3;
			break;
		case 1:
		//slower
			animateIntervale=36;
			decayInterval=4;
			break;
		case 2: 
			
			//normal
			//doAnimate=true;
			//useSmoothTransition=false;
			animateInterval=30;
			 decayInterval=6;
			break;
		case 3:
			//faster
			animateInterval=30;
			decayInterval=9;
		case 4:
			//fastest
			//doAnimate=true;
			//useSmoothTransition=true;
			
			animateInterval=30;
			 decayInterval=12;
			break;
		default:
			alert("Error: Unknown Display Speed Setting: " + value);
			break;
	}
	
	
}


function checkDisplayModeInput(skipEnd) {
	//this function is called every time that a radio-button in the group is hit--this means that it's possible 
	//to have this called when the mode has actually not changed.
	//loops through the selectors, sets the displaymode to the value of the checked one.
	if(currentUseMode.toUpperCase()=="PLAY") {
		alert("Sorry, these controls are disabled in Play mode.");
		return;
	}
	//different cases require different messages, different times to switch modes, etc.
	var oldMode=currentDisplayMode;
	for (q=0; q<document.getElementById("ControlBarForm").DisplayMode.length;q++) {
		if(document.getElementById("ControlBarForm").DisplayMode[q].checked==true) {
			
			switchDisplayMode=document.getElementById("ControlBarForm").DisplayMode[q].value;
			break;
		}
		
	}
	if (skipEnd) {
		switchDisplayModeNow();
		return;
	}
	if(switchDisplayMode.toUpperCase()==currentDisplayMode.toUpperCase() ) {
		//don't need to do anythign!
		//make sure we're unpaused.
		//the way we probably got here is someone hit like demo, saw the warning message, and switched back immedieatley. 
		resumePlayback();
		return;
	}
	enableInterface(false,"ResumeButton,PauseButton,ReplayButton");
	switch(switchDisplayMode.toUpperCase()) {
		case "NOHELP":
			switch(oldMode.toUpperCase()) {
				case "PRINT":
					switchDisplayModeNow();
					break;
				default :
					//push the warning message
					pushSolveModeCommand("MESSAGE:0:DEFAULT:" + escape("b::WARNING|br::void|br::void|TEXT::Switching to this mode will start the puzzle automatically solving itself, and will not let you catch up, which will cause you to lose your place.  If you do not want to do this, choose a new display mode now.  Otherwise, click 'Continue' to use the Demo display mode.") + "|PLAYBACK:ALL:PAUSE|SWITCH");

					break;
			}
			break;
		case "PRINT": 
			//push the warning message
			pushSolveModeCommand("MESSAGE:0:DEFAULT:" + escape("b::WARNING|br::void|br::void|TEXT::Switching to this mode will start the puzzle over from the beginning, losing your current progress.  If you do not want to do this, choose a new display mode now.  Otherwise, click 'Continue' to use the Print display mode.") + "|PLAYBACK:ALL:PAUSE|SWITCH");

			break;
		case "BYSTEP":
			//push the warning message
			switch(oldMode.toUpperCase()) {
					case "NOHELP":
						pushSolveModeCommand("MESSAGE:0:DEFAULT:" + escape("TEXT::You have likely not been able to keep up with the Auto mode's solution.|br::void|TEXT::Click |a>href=javascript:startPuzzleOver()::'Reset To Start'|TEXT:: to start over using this display mode.|br::void|br::void|b::To continue from this point with this mode, click 'Continue'.") + "|PLAYBACK:ALL:PAUSE|SWITCH");
						
						break;
					case "BYSECTION":
						pushSolveModeCommand("MESSAGE:0:DEFAULT:" + escape("TEXT::To avoid missing steps, the application will remain in 'Normal' mode until the end of the next section.|br::void|br::void|b::Click 'Continue' to go on.") + "|PLAYBACK:ALL:PAUSE");

						
						break;
					case "PRINT":
						//go directly to switchCurrentModeNow
						switchDisplayModeNow();
						break;
					default:
						break;
			}
			break;
		case "BYSECTION":
			//push the warning message
			switch(oldMode.toUpperCase()) {
					case "NOHELP":
						pushSolveModeCommand("MESSAGE:0:DEFAULT:" + escape("TEXT::You have likely not been able to keep up with the Auto mode's solution.|br::void|TEXT::Click |a>href=javascript:startPuzzleOver()::'Reset To Start'|TEXT:: to start over using this display mode.|br::void|br::void|b::To continue from this point with this mode, click 'Continue'.") + "|PLAYBACK:ALL:PAUSE|SWITCH");
						
						break;
					case "BYSTEP":
						pushSolveModeCommand("MESSAGE:0:DEFAULT:" + escape("TEXT::To avoid missing steps, the application will remain in 'Beginner' mode until the end of the next section.|br::void|br::void|b::Click 'Continue' to go on.") + "|PLAYBACK:ALL:PAUSE");
						
						break;
					case "PRINT":
						switchDisplayModeNow();
						
						break;
					default:
						break;
				}
			break;
		default:
			//who knows
			break;
	}
		
		
	
	
}

function enablePlayControls(doEnable) {
	if(doEnable) {
		enableInterface("FlipAreaButton,TopRowLeftButton,TopRowRightButton,BottomRowLeftButton,BottomRowRightButton,SwitchToSolveButton","ReplayButton,ResumeButton,PauseButton,DisplayNoHelp,DisplayByStep,DisplayBySection,DisplayPrint,StartOverButton,NewGoalButton,NewPuzzleButton");
		
	}
	else {
		enableInterface("PauseButton,DisplayNoHelp,DisplayByStep,DisplayBySection,DisplayPrint,StartOverButton,NewGoalButton,NewPuzzleButton","FlipAreaButton,TopRowLeftButton,TopRowRightButton,BottomRowLeftButton,BottomRowRightButton,SwitchToSolveButton");
	
	}
	
}




function convertSolutionToInstructionSet(solutionString,skipOpening) {
//this function is incredibly important.  After the solver has returned the short form of the solution, it is up to this function
//to convert it into the instruction set that will drive interface interaction, animation, and allow users to switch modes mid-way through playback!
 var result, tempSet, shiftSet, message
	result=prependAction; //by default filled with VOID ('it has to be filled with something)
//set prepend back to default:

prependAction="VOID";
//we're going to put in a pause for only print mode; this helps verify that no matter what, the print mode will remain non-animated (even if skipOpening is false)
result=result + "|PLAYBACK:PRINT:PAUSE";
if(skipOpening) {
	result=result + "|MESSAGE:1:DEFAULT:" + escape("TEXT::Now it's time to watch as the puzzle is solved.  We will solve the puzzle in a series of organized stages.  First, we will get the correct token into the far left position in the top row.  Next we will get the correct token into the far left position in the bottom row, and so on.|br::void|b::Try some of the different display modes.|TEXT:: Demo solves the puzzle quickly, but without giving you a chance to follow along.  Beginner mode displays each move separately, and is good for people just starting out.  Finally, Print/Text-Only mode displays the solution in a way that's easy to print out.|br::void|b::All set? Click 'Continue' to go on.");
 //result= result + "|MESSAGE:1:DEFAULT:" + escape("TEXT::Watch as the puzzle is solved.  After each step, messages will tell you the moves to do to catch up.|br::void|TEXT::If you've never used this utility, change the display mode to \"Beginner\" before starting.  If you want to watch as the puzzle solves itself, choose the \"Demo\" display mode.  Finally, if you want to print the solution, choose the \"Print/Text-Only\" display mode for an easy-to-read text-only display.|br::void|br::void|b::Click the 'Continue' button to begin.");
 
 result=result + "|PLAYBACK:BYSTEP,BYSECTION,NOHELP,PRINT:PAUSE";
}

 result=result+ "|SWITCH";
 //result=result + "|STEP:_";
 solutionString=solutionString.split("~");
 for (i=0 ; i<solutionString.length;i++) {
	 if (solutionString[i]=="#") {
		 //The step was skipped because it was unneccesary. enter a message to that effect.
		 result=result +  "|MESSAGE:0:SKIPPED:" + escape("b::A step was skipped because it was unneccesary.") + ":" + i;
		
		 result=result + "|PLAYBACK:BYSTEP,BYSECTION:PAUSE";
		  result=result + "|SWITCH";
	 }
	 else {
		 // It's a normal step.
		 result=result + "|MESSAGE:0:PREPRESECTION:" + solutionString[i] + ":" + i;
		 result=result + "|PLAYBACK:BYSTEP,BYSECTION:PAUSE";
		 result=result + "|SWITCH";
		 result=result + "|MESSAGE:0:PRESECTION:" + solutionString[i] + ":" + i;
		 tempSet=solutionString[i].split(";");
		 for (j=0;j<tempSet.length;j++) {
			 // each step
			 result=result + "|MESSAGE:0:PRESTEP:" + tempSet[j] +":" + i;
			 shiftSet=tempSet[j].split(",");
			 result=result + "|SHIFT:" + shiftSet[0] + ":" + shiftSet[1];
			 result=result+"|FLIP";
			
			 result=result + "|INTERFACE:BYSTEP:ENABLE:ReplayButton:";
			 result=result + "|MESSAGE:0:STEP:" + tempSet[j] + ":" + i;
			 result=result + "|PLAYBACK:BYSTEP:PAUSE";
			 result=result + "|INTERFACE:BYSTEP:ENABLE::ReplayButton";
		 }
		 //each section
		 //return to home
		 result=result + "|SHIFT:2:2";
		 result=result + "|INTERFACE:BYSECTION:ENABLE:ReplayButton:";
		 result=result + "|MESSAGE:0:SECTION:" + solutionString[i] + ":" + i;
		
		 result=result + "|PLAYBACK:BYSTEP,BYSECTION:PAUSE";
		 result=result + "|INTERFACE:BYSECTION:ENABLE::ReplayButton";
		 result=result + "|SWITCH";
		 result=result + "|STEP:1"
	 }
 }
 result=result + "|MESSAGE:1:DONE:Done!"; //the Done message type actually uses its own text, but supply text just in case.
 //the standard is for the last command in the set to be the done message.  the done message handles tidying up of the interface,
 //which used to be handled below:
 
 //They're done; don't let them pause or continue or replay, or anything. 
	//result= result + "|INTERFACE:ALL:ENABLE::ReplayButton,PauseButton,ResumeButton";
 return result;
 
	
}


function renderSolution(puzzleArray) {
	//this function is functionally similar to the renderGame function, except that it renders its output to the goal tokens.
	//generally accepts the targetsolution as a pipe-delimited string.
	//to animate (not yet  realistic) decay the offsets.
	puzzleArray=puzzleArray.split("|");
	var tokenObject;
	var tokenIndex;
	var tokenRow=0;
	var tokenColumn=0;
	var tempClassName;
	for (i=0; i<puzzleArray.length ; i++) {
		if (puzzleArray[i]>-1) {
			//it's a piece, render it
			tokenIndex=puzzleArray[i];
			
			tokenObject=document.getElementById("goaltoken" + tokenIndex);
			if (i>7) {
				//it's in the second row
				tokenRow=2;
				tokenColumn=i-7;
			}
			else {
				tokenRow=1;
				tokenColumn=i;
			}
			tempClassName="MiniToken"
			if (solutionOffset[tokenIndex].locked) {
				tempClassName=tempClassName + "_Locked";
			}
			if(solutionOffset[tokenIndex].highlighted) {
				tempClassName=tempClassName + "_Highlighted";
			}
			if(tokenObject.className!=tempClassName) {
			tokenObject.className=tempClassName;
			}
			tokenObject.style.left=solutionOffset[tokenIndex].left + solutionTokenOffsetLeft + ((tokenColumn-1) * solutionTokenWidth) + "px";
			tokenObject.style.top=solutionOffset[tokenIndex].top + solutionTokenOffsetTop + ((tokenRow-1) * solutionTokenHeight) +"px";
			
		}
	}

}
function highlightSpace(spaceIndex) {
	//this function highlights the space in both the game and solution areas.
	var row, column
	if (spaceIndex) {
		if (spaceIndex>7) {
			row=2;
			column=spaceIndex-7;
		}
		else {
			row=1;
			column=spaceIndex;
		}
	document.getElementById("goaltokenHighlight").style.left=solutionTokenOffsetLeft + ((column-1) * solutionTokenWidth) + "px";
	document.getElementById("goaltokenHighlight").style.top=solutionTokenOffsetTop + ((row-1) * solutionTokenHeight) +"px";
	document.getElementById("goaltokenHighlight").style.visibility="visible";
	document.getElementById("tokenHighlight").style.left= tokenOffsetLeft + ((column-1) * tokenWidth) + "px";
	document.getElementById("tokenHighlight").style.top=tokenOffsetTop + ((row-1) * tokenHeight) +"px";
	document.getElementById("tokenHighlight").style.visibility="visible";

			
	}
	else {
		//hide them.
		document.getElementById("goaltokenHighlight").style.visibility="hidden";
		document.getElementById("tokenHighlight").style.visibility="hidden";
	}
	
}

function clearHighlights() {
	//this function simply calls highlightPieces with the appropriate arguments.  It's easier to use and remember than lookin up the args for highlight pieces.
	highlightPieces(false,false,false,puzzleOffset);
	highlightPieces(false,false,false,solutionOffset);
	
	//also handle the final render (lots of things calling this don't call render because they don't handle animation otherwise.  think of this as a reset function.);
	renderGame(workingPuzzle.join("|"));
	renderSolution(targetSolution.join("|"));
}

function highlightPieces(piecesToHighlight,piecesToLock,spaceToHighlight, offsetRegister) {
	//send false for either of two arguments to say that all should be reset to default.
	//first, clear all highlights and locks by default.
	//then, change them to true only  if neccesary.
	for(x=0;x<offsetRegister.length;x++) {
		offsetRegister[x].highlighted=false;
		offsetRegister[x].locked=false;
	}
	var tokenIndex;
	if(spaceToHighlight) {
		
		highlightSpace(spaceToHighlight);
	}
	else {
		highlightSpace(false);
	}
	
	if (piecesToHighlight) {
		piecesToHighlight=piecesToHighlight.toString();
		piecesToHighlight=piecesToHighlight.split(",");
		for (x=0;x<piecesToHighlight.length;x++) {
			tokenIndex=parseInt(targetSolution[piecesToHighlight[x]]);
			
			offsetRegister[tokenIndex].highlighted=true;
		}
	}
	if (piecesToLock) {
		piecesToLock=piecesToLock.toString();
		piecesToLock=piecesToLock.split(",");
		for(x=0;x<piecesToLock.length;x++) {
			tokenIndex=parseInt(targetSolution[piecesToLock[x]]);
			
			offsetRegister[tokenIndex].locked=true;
		}
	}
	
}

function renderGame(puzzleArray) {
	//This function positions all the pieces so they match what's currently in the array given to it (normally a copy of the working puzzle)
	//It does not manipulate the array.
	//It is used by every function that wants to render the game; it supports the default offset register.
	//To animate, use a decaying offset.
	puzzleArray=puzzleArray.split("|");
	var tokenObject;
	var tokenIndex;
	var tokenRow=0;
	var tokenColumn=0;
	var tempClassName;
	for (i=0; i<puzzleArray.length ; i++) {
		if (puzzleArray[i]>-1) {
			//it's a piece, render it
			tokenIndex=puzzleArray[i];
			
			tokenObject=document.getElementById("token" + tokenIndex);
			if (i>7) {
				//it's in the second row
				tokenRow=2;
				tokenColumn=i-7;
			}
			else {
				tokenRow=1;
				tokenColumn=i;
			}
			//set styling
			tempClassName="Token";
			
			if (puzzleOffset[tokenIndex].locked) {
				tempClassName=tempClassName + "_Locked";
			}
			if(puzzleOffset[tokenIndex].highlighted) {
				tempClassName=tempClassName + "_Highlighted";
			}
			if(tokenObject.className!=tempClassName) {
			tokenObject.className=tempClassName;
			}
			tokenObject.style.left=puzzleOffset[tokenIndex].left + tokenOffsetLeft + ((tokenColumn-1) * tokenWidth) + "px";
			tokenObject.style.top=puzzleOffset[tokenIndex].top + tokenOffsetTop + ((tokenRow-1) * tokenHeight) +"px";
			
		}
	}
	
}




//*************************




//The remaining code handles manipulating the array and figuring out how to solve it.

function fillMoveSolutionArray() {
	//These are the pre-determined moves to do various things.  They are the tools the solver uses to move the pieces
	//where they need to go.
	// step 0 through step 8 have hard-coded goals.
	var moveSolutionString = new Array();
	var tempMove;
	tempMove="2:1,2;0,2~3:2,1,;0,1~4:2,1;0,2~5:2,0;0,2~8:0,0~9:0,1~10:0,2~11:0,2;1,2;0,2~12:0,2;2,2;0,2";
	moveSolutionString[0]=tempMove.split("~");
	tempMove="2:1,0~3:2,0~4:2,0;2,1;2,0~5:2,0;2,2;2,0~9:2,1;2,0~10:2,2;2,0~11:1,2;2,0~12:1,2;2,0;2,1;2,0";
	moveSolutionString[1]=tempMove.split("~");
	tempMove="3:2,2;1,2~4:2,1;1,2~5:2,1;1,2;2,2;1,2~9:1,1~10:1,2~11:1,2;2,2;1,2~12:1,2;2,1;1,2";
	moveSolutionString[2]=tempMove.split("~");
	tempMove="3:2,1~4:2,1;2,2;2,1~5:2,1;2,2;2,1;2,2;2,1~10:2,2;2,1~11:2,2;2,1;2,2;2,1~12:2,2;2,1;2,2;2,1;2,2;2,1";
	moveSolutionString[3]=tempMove.split("~");
	
	tempMove="4:0,1;0,0;1,0;0,0~5:0,0;2,1;0,0;1,2~10:2,2~11:0,0;1,0;0,0;0,1~12:1,2;0,0;2,1;0,0";
	moveSolutionString[4]=tempMove.split("~");
	tempMove="4:0,0;0,1;0,0;1,0~5:2,1;0,0;1,2;0,0~11:1,0;0,0;0,1;0,0~12:0,0;1,2;0,0;2,1";
	moveSolutionString[5]=tempMove.split("~");
	tempMove="5:1,2;1,1;2,1;1,1~11:1,2;0,2;0,1;1,1;0,1;1,2~12:1,1;2,1;1,1;1,2";
	moveSolutionString[6]=tempMove.split("~");
	tempMove="5:1,1;1,2;1,1;2,1~12:2,1;1,1;1,2;1,1";
	moveSolutionString[7]=tempMove.split("~");
	tempMove="12:0,1;2,1;2,0;2,2;0,2;1,2;0,0;0,1;0,0;1,0;0,0";
	moveSolutionString[8]=tempMove.split("~");
	var i;
	for (i=0; i<moveSolutionString.length; i++) {
		moveSolutionArray[i]=moveSolutionString[i];
	}
	//Done
	
	// now the moveSolutionArray holds the method for the solutions.
	//Step zero is to get whatever's going to end up in position 1 there.  
	//each possible movestring is as follows: 2:2,0;1,2~3:1,2;0,1 (the tilde is already used to split)
	//Which would mean, when moving the piece that will end up in position 1, if it is current in 2, do that, but if it's currently in position 3, do that
	
	
}



function makeSolutionString(puzzleArray) { 
	//We must go through steps 0 to 8.  We'll be working on a copy of the puzzle array.
	//This function calls makeSolutionStep, then PERFORMS THAT STEP on its copy of the puzzle,then calls makeSolutionStep for the next index, etc.
	var puzzleCopy = puzzleArray.split("|");
	puzzleArray=puzzleArray.split("|");
	var solutionStringIs="#NOTHING#";
	var tempSolutionString="#NOTHING#";
	var i;
	for (i=0;i<9;i++) {
		//figure out what to do to solve this step
		tempSolutionString=makeSolutionStep( i , puzzleCopy);
		//if the step doesn't need to be performed, the makeSolutionStep function will return an empty string
		if (tempSolutionString=="#NOTHING#") {
			//we don't need to do this step.
			//However, this function needs to add the character # as a step to inform other functions that a step was unneccesary.
			if(solutionStringIs=="#NOTHING#") {
				solutionStringIs="#";
			}
			else {
				solutionStringIs=solutionStringIs + "~#";
			}
		}
		else {
			//each of these moves will transform the remaining pieces, which we must keep track of.  
			//so, perform the moves on a copy of the puzzle
			puzzleCopy=performMoves(tempSolutionString,puzzleCopy.join("|"));
			//add the step's solution to the total solution.
			//is this the first step?
			if (solutionStringIs=="#NOTHING#") {
				solutionStringIs=tempSolutionString;
			}
			else {
				solutionStringIs=solutionStringIs + "~" + tempSolutionString;
			}
		}
	}
	
	//it's possible, though unlikely, that the puzzle has been solved all along; check to make sure it hasn't been
	if (solutionStringIs=="#NOTHING#") {
		alert ("The puzzle is already solved!");
		return;
	}
		return solutionStringIs;



}

function returnStepPrettyLockedTokens(stepIndex) {
	//this function calls returnStepLockedLocations to get a list of the locked spaces,
	//the processes them so they have the token numbers, and then formats them prettily.
	
	//returns a string formatted like so: 
	// " and end up with the tokens with the numbers 2,4,5, and 6 in the same spots that they're currently in"
	var workString=returnStepLockedLocations(stepIndex);
	var result="";
	if (!workString) {
		return ""; 
	}
	workString=workString.split(",");
	for(z=0;z<workString.length;z++) {
		workString[z]=targetSolution[workString[z]];
	}
	//now the array has the token numbers, but they still need some formatting.
	switch (workString.length) {
		case 0:
			alert("Error: workString length of 0");
			break;
		case 1:
			result=" and end up with the token with the number ";
			result=result + workString[0];
			result=result + " in the spot that it's currently in";
			break;
		case 2:
			result= " and end up with the tokens with then numbers ";
			result = result + workString[0] + " and " + workString[1];
			result = result + " in the spots that they're currently in";
			break;
		default:
			//normal handling, more than one token
			result=" and end up with the tokens with the numbers ";
			for(z=0;z<workString.length-1;z++) {
				result=result + workString[z] + ", ";
			}
			//last one
			result=result + "and " + workString[z];
			result = result + " in the spots that they're currently in"
			break;
		
	}
	
	
	return result;
	
}

function returnStepLockedLocations(stepIndex) {
	var result;
	//this function returns a comma-delimited list of spaces that should not change from the beginning of this section to the end (although during they of course do.)
	stepIndex=parseInt(stepIndex);
	switch(stepIndex) {
		case 0:
			result=false;
			break;
		case 1:
			result="1";
			break;
		case 2:
			result="1,8";
			break;
		case 3:
			result="1,8,2";
			break;
		case 4:
			result="1,8,2,9";
			break;
		case 5:
			result="1,8,2,9,3";
			break;
		case 6:
			result="1,8,2,9,3,10";
			break;
		case 7:
			result="1,8,2,9,3,10,4";
			break;
		case 8:
			result="1,8,2,9,3,10,4,11";
			break;
		default: 
			alert("A solution step out of bounds was observed: " + stepIndex );
			result=false;
			break;
		
		
	}
	return result;
}

function returnStepLocation(stepIndex) {
	//this function returns the location index where the piece will rest at the end of that step.
	var stepLocation=-1;
	stepIndex=parseInt(stepIndex);
	switch (stepIndex) {
		case 0:
			
			stepLocation=1;
			break;
		case 1:
			
			stepLocation=8;
			break;
		case 2:
			
			stepLocation=2;
			break;
		case 3:
			
			stepLocation=9;
			break;
		case 4:
			
			stepLocation=3;
			break;
		case 5:
			
			stepLocation=10;
			break;
		case 6:
			
			stepLocation=4;
			break;
		case 7:
			
			stepLocation=11;
			break;
		case 8:
			
			stepLocation=5;
			break;
		default:
			alert("A solution step out of bounds was observed: " + stepIndex);
			stepLocation=-1;
			break;
	}
	return stepLocation;
}


function makeSolutionStep(stepIndex, puzzleArray) {
	//this function returns the solution step string--but does NOT transform the puzzle array.
	var stepPiece=-1;
	var stepLocation=-1;
	stepLocation=returnStepLocation(stepIndex);
	stepPiece=targetSolution[stepLocation];
	//in stepPiece we have which token, and in stepLocation, we have where it should be deposited in this step.
	//it might already be there; check.
	if (puzzleArray[stepLocation]==stepPiece) {
		//It's already there; skip this step
		return "#NOTHING#";
	}
	//figure out where that token currently is
	var i;
	var tempString;
	var resultString="#NOTHING#";
	var tokenLocation=-1;
	for (i=0;i<puzzleArray.length;i++) {
		if(puzzleArray[i]==stepPiece) {
			tokenLocation=i;
			break;
		}
	}
	//quick error check:
	if (tokenLocation==-1) {
		alert ("For some reason, the search token was not found in the puzzle array.");
		return "#NOTHING#";
	}
	//now we know where our token is and where it needs to be.  Check the solution array for the steps needed to get it to the right place
	for (i=0;i<moveSolutionArray[stepIndex].length;i++) {
		tempString=moveSolutionArray[stepIndex][i].split(":");
		
		if (tempString[0]==tokenLocation) {
			
			resultString=tempString[1];
			break;
		}
		
	}
	
	if (resultString=="#NOTHING#") {
		alert("There was an error; solution string not found.");
		return "#NOTHING#";
	}
	
	//Make sure that the move is valid:
	//make a copy of the puzzlearray (simply assigning a new variable to it would not work)
	var puzzleCopy=puzzleArray.join("|");
	puzzleCopy=puzzleCopy.split("|");
	puzzleCopy=performMoves(resultString,puzzleCopy.join("|"));
	if (puzzleCopy[stepLocation]==stepPiece) {
		//the target piece was put into the right spot; it's successful.
	}
	else {
		//Oops, the step didn't do what it was supposed to do; that step must be wrong in the database.
		alert( "A step in the database is invalid: #" + stepIndex + ": L:" + stepLocation + "; P:" + stepPiece + "; T:" + tokenLocation);
	}
	
	
	return resultString;
			
}


function performSolution(solutionString, puzzleArray) {
	//it is currently not used by anyone (checking to make sure a given move did what it was supposed to is handled by section, not overall.)
	//This function performs the solution silently, without displaying the progress. 
	//This can be used to verify that the solution generated is valid.
	var solutionSteps = new Array();
	var i;
	puzzleArray=puzzleArray.split("|");
	solutionSteps=solutionString.split("~");
	for (i=0;i<solutionSteps.length;i++) {
		if (solutionSteps[i]!="#") {
			// the character # is a place holder for a step (number 0-8) that was skipped because it was unneccesary. 
			// the place holder ensures that each solution string consists of 9 steps, no matter what.
		puzzleArray=performMoves(solutionSteps[i],puzzleArray.join("|"));
		}
		
	}
	return puzzleArray;
}

function performMoves(moveString, puzzleArray, doNotHome) {
	//these functions are not used by any of the playback routines.  they are used only by solution-string generation and error-checking routines.
	//Takes multiple moves in the format:
	// 0,2;1,0;5,4
	//Always returns them to home position:2,2
	puzzleArray=puzzleArray.split("|");
	var instruction = new Array();
	instruction=moveString.split(";");
	var count;
	for (count=0;count<instruction.length;count++) {
		
		puzzleArray=performFlip(instruction[count],puzzleArray.join("|"));
	}
	//Now, return the puzzle to home position
	if(!doNotHome) {
		puzzleArray=positionRow(1,2,puzzleArray.join("|"));
		puzzleArray=positionRow(2,2,puzzleArray.join("|"));
	}
	return puzzleArray;
}



function performFlip(moveString, puzzleArray) {
	//these functions are not used by any of the playback routines.  they are used only by solution-string generation and error-checking routines.
	//Takes a single move of the format:
	//0,1
	//Does not retrun it in home position
	puzzleArray=puzzleArray.split("|");
	var position = new Array();
	position=moveString.split(",");
	puzzleArray=positionRow(1,position[0],puzzleArray.join("|"));
	puzzleArray=positionRow(2,position[1],puzzleArray.join("|"));
	puzzleArray=flipPuzzle(puzzleArray.join("|"));
	return puzzleArray;
}

function positionRow(row, position, puzzleArray) {
	//a workhorse function; used by many others
	//Offsets the specified row to the specified position.
	//Position value is absolute, not relative, and is in the numbering scheme used in the solution
	puzzleArray=puzzleArray.split("|");
	var offset=0;
	var currentPosition=0;
	if (row==2) {
		//add seven to the base index
		offset=7;
	}
	//*******************
	//check current position
	if (puzzleArray[1+offset]==-1) {
		//the row is in either position 1 or 0
		if (puzzleArray[2+offset]==-1) {
			//The row is in position 0
			currentPosition=0;
		}
		else {
			//The row is in position 1
			currentPosition=1;
		}
	}
	else {
		//the row is in position 2
		currentPosition=2;
	}
	
	//*******************
	
	//now, translate the current position number to the number of the index (minus offset) that the farthest left piece is in
	currentPosition=3-currentPosition;
	position=3-position;
	
	
	//Now, copy from the currentposition five spaces into a buffer
	var tempArray = new Array();
	for (i=0;i<5;i++) {
		tempArray[i]=puzzleArray[currentPosition + i + offset];
	}
	//Now, erase the current row (by filling with ones)
	for (i=1;i<8;i++) {
		puzzleArray[i + offset]=-1;
	}
	//Now, copy from the buffer into the same row (filling only five positions; the unfilled positions are left as -1)
	for (i=0;i<5;i++) {
		puzzleArray[position+i+offset]=tempArray[i];
	}
	//Now the row has been shifted; return it.
	
	return puzzleArray;
}

function flipPuzzle(puzzleArray) {
	//Flips the numbers in the flip positions.  Position the array before sending it.
	puzzleArray=puzzleArray.split("|");
	var temp=0;
	temp=puzzleArray[3];
	puzzleArray[3]=puzzleArray[10];
	puzzleArray[10]=temp;
	temp=puzzleArray[4];
	puzzleArray[4]=puzzleArray[11];
	puzzleArray[11]=temp;
	temp=puzzleArray[5];
	puzzleArray[5]=puzzleArray[12];
	puzzleArray[12]=temp;
	
	return puzzleArray;
}
