////////////////////////////////////////////////////////////////
// 
// sudoku.js
//
// Copyright (c) 2006 (Vinh Le Quang). 
// http://www.bunnypot.com/sudoku/
// You may copy or reuse this code only if you include this copyright notice.
// Send comments to kwanguru@yahoo.com
//
////////////////////////////////////////////////////////////////

function addPuzzles()
{
puzzles.add("000010020001003004005060030000050060007020010002004008006007009003005006420008001", 1, "634719825271583694895462137348951762957826413162374958516247389783195246429638571", "1129154-308019");
puzzles.add("001000203300045006007008140010409000020760005400002068005090701069030080030801090", 2, "841976253392145876657328149516489327928763415473512968285694731169237584734851692", "1121534-258530");
puzzles.add("000001230000405001067000058048000060000602047002003509700020100580074003206510070", 2, "459781236823465791167239458948157362315692847672843519794326185581974623236518974", "110284-9183");
puzzles.add("010002030400050006000407108040001020092630000001008543800020001903000750006749002", 3, "617982435489153276235467198348571629592634817761298543874325961923816754156749382", "321776-93347");
puzzles.add("000010203045000067082607100501028000020079010000061308904005000073000084600240091", 3, "769814253145932867382657149531428976826379415497561328914785632273196584658243791", "1127622-94129");
puzzles.add("010000200300041050006078001008700009002600005570000620050000900004062007700059030", 4, "815936274397241856426578391638725149142693785579184623253417968984362517761859432", "78054-278371");
puzzles.add("010000020003100004020043050030000060007200003050018070001036009206000407008027006", 4, "415679328863152794729843651132794865987265143654318972571436289296581437348927516", "935568-187975");
puzzles.add("012000000000300405006702001000078000700000180209003060040090800071000094000014053", 4, "312459678897361425456782931165978342734526189289143567543297816671835294928614753", "1184161-223961");
puzzles.add("100000020003040010005160003000006300050300200078400006090070000200001075500004109", 4, "189753624763249518425168793912586347654397281378412956891675432246931875537824169", "1416535-49932");
puzzles.add("000000012000304005065078000047000000008057000000803079050000040902600801104080206", 5, "483596712719324685265178394347912568698457123521863479856231947972645831134789256", "1766065-344683");
puzzles.add("120030000040001002056000700000006000000000583201870094000000001000462000704908230", 5, "128537946947681352356249718485396127679124583231875694892753461513462879764918235", "119252-85596");
puzzles.add("001020030000004506000708001506000080004000650080006007030060100008150040210007009", 5, "941625738827314596653798421596271384174983652382546917439862175768159243215437869", "17941-58743");
puzzles.add("000000120003010004050006007304008000100060800020009053030600010006807000007030405", 5, "849375126673912584251486397364528971195763842728149653532694718416857239987231465", "617443-128450");
puzzles.add("010002030452000000060100007800027000000040720007009064000530400009000306300706005", 6, "718462539452973681963185247841627953695341728237859164176538492589214376324796815", "57068-109207");
puzzles.add("010000200003004000000100050001000000600007823007568001000001000432006005005030497", 6, "719385264583624719246179358891243576654917823327568941978451632432796185165832497", "189331-114906");
puzzles.add("000000012030204000205060000060001007004020300001300068010009500007080040600002073", 6, "498735612136294785275168934362851497784926351951347268813479526527683149649512873", "586525-323897");
puzzles.add("000010020300002004052006007000080030200005006037009005000090060016058072085067043", 6, "674513829391872654852946317569784231248135796137629485723491568416358972985267143", "2398513-218512");
puzzles.add("000000000100023400500067800010200004008014090062000087040800009021000043007051020", 6, "273548961186923475594167832719286354358714296462395187645832719821679543937451628", "750024-293100");
puzzles.add("000100002300000040005040673050000000081090030069008504000005000006084005040027160", 7, "694173852327856941815249673753461298481592736269738514932615487176984325548327169", "2349956-292279");
puzzles.add("001000020000003004000000561000002000027300010608040750002000000090105003400068105", 7, "761594328285613974349827561914752836527386419638941752152439687896175243473268195", "1836800-135072");
puzzles.add("000000000012003004540000627000000000008021006000530749009005003201060080060302900", 7, "697254138812673594543819627354796812978421356126538749789145263231967485465382971", "1417615-244118");
puzzles.add("010020000003004000000000567084000000000000092070860004000000920000087000906005300", 8, "517629483863754219492138567184592736635471892279863154758346921321987645946215378", "306721-212877");
puzzles.add("001200000304050000060070200007008000000090001106000047000400008080003100000002073", 8, "971234586324856719865971234537148962248697351196325847753419628482763195619582473", "78054-181534");
puzzles.add("010000020300010000000405607703000000008000502000062038062000000007301000000046059", 8, "514687923376219485289435617723854196698173542145962738462598371957321864831746259", "516086-257665");
puzzles.add("102000000000003010030140506000007080703000000060980704625000000000019052000075063", 9, "152796348846523917937148526294357681783461295561982734625834179378619452419275863", "1869493-25375");
puzzles.add("000000120003000040205000000060070000010040008504063000000009007040007005000014380", 9, "476598123183726549295431876968175234317942658524863791831259467642387915759614382", "42408-316413");
puzzles.add("000012000000003004005060070050000002002100000301070650000500007004000080270008905", 9, "000012500100053804005060071050006102002105700301270650000501407514007080270008915", "896934-225368");
puzzles.add("100000000000000023003040156060000000000023000004781030000007004075008002608009005", 10, "152396478486175923793842156367954281819623547524781639231567894975418362648239715", "2079157-68542");
puzzles.add("001000002030010040005460000000000000310070008008005970004000006000603007090007010", 10, "471359862836712549925468731547986123319274658268135974784521396152693487693847215", "669468-83604");
puzzles.add("000010200304000000005600403001000006000078050700062000000000034070006000068200001", 10, "697314285314825967825697413581943726246178359739562148952781634173456892468239571", "933840-24020");
puzzles.add("000001002003040000020000050001000600700304080069000027000020800000087063980600004", 10, "648571932593246718127839456831792645752364189469158327376425891214987563985613274", "34573-191431");
puzzles.add("000010002030004500000006074080000040006000025004070800005008000009001000068027031", 10, "947315682632784519851296374283159746796843125514672893125938467379461258468527931", "196069-332579");
puzzles.add("000010000002003400506000007000008000060000280200040090093800000000600031040025006", 10, "487512369912763458536489127379258614164937285258146793693871542725694831841325976", "1804543-168599");
puzzles.add("000000000012030000405000060000403007000080920807002001000740010000008705083005002", 10, "978264153612539874435871269261493587354187926897652431529746318146328795783915642", "2001500-109068");
puzzles.add("000010020003400005062000300600000070000506004040003200000008009009000600304091080", 10, "485317926973462815162859347698124573237586194541973268716238459829745631354691782", "196345-72706");
puzzles.add("000010002000300400053006070000100200000060005062007080085000000700041029400089053", 10, "649718532817325496253496178598134267374862915162957384985273641736541829421689753", "344018-38999");
puzzles.add("100002000003000004056000207001003000060000008205804000000050890000430060000028073", 10, "178542639923786154456391287841963725367215948295874316634157892782439561519628473", "335912-123119");
puzzles.add("000000000102030000450000600000057000004003008060908057000000703090004002008701065", 10, "936175284182436579457829631819257346574613928263948157641582793795364812328791465", "2297066-310871");
puzzles.add("001000000000023040400056070000008000023000004069200005000000098008030520090007406", 10, "931784652576923841482156973154378269823695714769241385347562198618439527295817436", "484501-32478");
puzzles.add("000012034000056012002074086000008001764003005831540260050401003473005028618230450", 10, "586912734347856912192374586925768341764123895831549267259481673473695128618237459", "");
}


			
			var hypercache = new Object();
			
			var nextYear=new Date();
			nextYear.setFullYear(nextYear.getFullYear()+1);
			
			var selectedCell = null;
			var draggedHelper = null;
			var defaultnamenew = escape("[current game]");
			var defaultnamesave = escape("Saved game");
			var percentdiv = document.createElement("div");
			var createpuzzlestart = null;
			var playstart = null;

			var cheatundocount = 0;
			var undobox = null;
			
			function hex(number)
			{
				var hexword = "0123456789ABCDEF";
				return hexword.charAt(Math.floor(number/16)) + hexword.charAt(number%16);
			}

			function gameTop()
			{
				return sudoku.offsetParent.offsetParent.offsetTop + sudoku.offsetParent.offsetTop + sudoku.offsetTop;
			}
			
			function gameLeft()
			{
				return sudoku.offsetParent.offsetParent.offsetLeft + sudoku.offsetParent.offsetLeft + sudoku.offsetLeft;
			}
			

			var sudotime = GetCookie("sudotime");
			var darkcol = !sudotime||sudotime=="day"?"fgColor":"bgColor";
			var lightcol = darkcol=="fgColor"?"bgColor":"fgColor";
			
			document[darkcol] = "black";
			document[lightcol] = "white";
			
			var daynighttimeout = null;
			function toggledaynight()
			{
				if(daynighttimeout==null)
				{
					var basecolor = 255;
					var timeout = null;
					daynighttimeout = setInterval(
							function()
							{
								basecolor-=5;
								document[lightcol] = "#"+ hex(basecolor) + hex(basecolor) + hex(basecolor);
								document[darkcol] = "#" + hex(255-basecolor) + hex(255-basecolor) + hex(255-basecolor);
								sudotitle.color = darkcol=="fgColor"?"#" + hex(255) + hex(255-basecolor) + "00":"#" + hex(255) + hex(basecolor) + "00";
								
								if(basecolor==0)
								{
									var temp = darkcol;
									darkcol = lightcol;
									lightcol = temp;
									clearInterval(daynighttimeout);

									daynighttimeout = null;
									document.cookie = "sudotime=" + (darkcol=="fgColor"?"day":"night") + ";expires="+nextYear.toGMTString();
								}
							},10);
				}
			}
			
			document.oncontextmenu = function(e)
			{
				if(!e)
					e = window.event;
				var target = e.srcElement?e.srcElement:e.target;
				if(target.className=="minibox")
				{
					if(createpuzzlestart)
						return false;
					var preselect = selectedCell;
					selectedCell = target;
					verifySudoku(preselect);
					verifySudoku(selectedCell);
					updateButton(null);
					
					var x= e.clientX + document.body.scrollLeft;
					var y= e.clientY + document.body.scrollTop;
					dropMenu(createCellMenu(selectedCell),x,y);
				}
				else
					return true;
				return false;
			};
			
			document.onmousedown = function(e)
			{
				if(!e)
					e = window.event;
				var target = e.srcElement?e.srcElement:e.target;
				
				if(e.button==2)
					return true;
				else if(createpuzzlestart)
					return false;
				else if(popMessage.div && popMessage.div.style.display!="none")
					return false;
				else if(dropMenu.div && dropMenu.div.style.display!="none")
					dropMenu.div.style.display = "none";
				else if(target.nodeName=="FONT" && target.parentNode.parentNode &&
					(target.parentNode.parentNode.className=="hilightbox"
					||target.parentNode.parentNode.className=="toolminibox") && !target.parentNode.parentNode.disabled)
				{
					target.parentNode.parentNode.className = "pushedbox";
				}
				else if(target.className=="minibox")
				{
					var preselect = selectedCell;
					selectedCell = target;
					verifySudoku(preselect);
					verifySudoku(selectedCell);
					updateButton(null);
				}
				else if(target.className=="toolminibox" || target.className=="hilightbox")
				{
					if(target.parentNode==sudotool && target.cellIndex<9 && selectedCell)
					{
						changeCell(selectedCell,target.cellIndex+1,false,0,false);
					}
				}
				else if(target.className=="pushedbox")
				{
					if(target.parentNode==sudotool && target.cellIndex<9 && selectedCell && selectedCell.number==target.cellIndex+1)
					{
						changeCell(selectedCell,0,false,0,true);
					}
				}
				else if(target.className=="marker")
				{
					var node = target.copy?target:target.cloneNode(true);
					
					if(!target.copy)
					{	
						var helpers = helperstorage.getElementsByTagName("div");
						node.id = "marker" + helpers.length;
						node.originalx = node.originaly = null;
					}
					else
					{
						node.originalx = node.offsetLeft-gameLeft();
						node.originaly = node.offsetTop-gameTop();						
					}
					node.style.position = "absolute";
					node.style.left = e.clientX + document.body.scrollLeft-15;
					node.style.top = e.clientY + document.body.scrollTop-15;
					node.copy = true;
					helperstorage.appendChild(node);
					draggedHelper = node;
				}
				else if(target.nodeName=="LI")
				{
				}
				else if(selectedCell)
				{
					selectedCell = null;
					if(dropMenu.div && dropMenu.div.style.display!="none")
						dropMenu.div.style.display = "none";
					verifySudoku();
					updateButton(null);
					return true;
				}
				else
					return true;
				verifySudoku();
				updateButton(null);
				return false;
			};
			
			var lastKey = 1;
			document.onkeydown = function(e)
			{
				if(!selectedCell)
					return true;
				if(createpuzzlestart)
					return false;
				if(popMessage.div && popMessage.div.style.display!="none")
					return true;
					
				if(!e)
					e = window.event;
				switch(e.keyCode)
				{
					case 90:
						{
							if(e.ctrlKey && !e.altKey && !e.shiftKey)
								undo();
							else
								return true;
						}
						break;
					case 48:
					case 49:
					case 50:
					case 51:
					case 52:
					case 53:
					case 54:
					case 55:
					case 56:
					case 57:
						{
							if(!e.ctrlKey && !e.altKey && !e.shiftKey)
							{
								changeCell(selectedCell,e.keyCode-48,false,0,false);
							}
							else
								return true;
						}
						break;
					case 37:
						{
							if(dropMenu.div && dropMenu.div.style.display!="none")
							{
							}
							else if(!e.ctrlKey && !e.altKey && !e.shiftKey)
							{
								var preselect = selectedCell;
								selectedCell = getCell(selectedCell.row,(selectedCell.col+8)%9);
								verifySudoku(preselect);
								verifySudoku(selectedCell);
								updateButton(null);
								return false;
							}
							else
								return true;
						}
						break;
					case 38:
						{
							if(dropMenu.div && dropMenu.div.style.display!="none")
							{
							}
							else if(!e.ctrlKey && !e.altKey && !e.shiftKey)
							{
								var preselect = selectedCell;
								selectedCell = getCell((selectedCell.row+8)%9,selectedCell.col);
								verifySudoku(preselect);
								verifySudoku(selectedCell);
								updateButton(null);
								return false;
							}
							else
								return true;
						}
						break;
					case 39:
						{
							if(dropMenu.div && dropMenu.div.style.display!="none")
							{
							}
							else if(!e.ctrlKey && !e.altKey && !e.shiftKey)
							{
								var preselect = selectedCell;
								selectedCell = getCell(selectedCell.row,(selectedCell.col+1)%9);
								verifySudoku(preselect);
								verifySudoku(selectedCell);
								updateButton(null);
								return false;
							}
							else
								return true;
						}
						break;
					case 40:
						{
							if(dropMenu.div && dropMenu.div.style.display!="none")
							{
							}
							else if(!e.ctrlKey && !e.altKey && !e.shiftKey)
							{
								var preselect = selectedCell;
								selectedCell = getCell((selectedCell.row+1)%9,selectedCell.col);
								verifySudoku(preselect);
								verifySudoku(selectedCell);
								updateButton(null);
								return false;
							}
							else
								return true;
						}
						break;
					case 46:
					case 8:
						{
							if(!e.ctrlKey && !e.altKey && !e.shiftKey)
							{
								changeCell(selectedCell,0,false,0,true);
							}
							else
								return true;
						}
						break;
					case 27:
						{
							if(dropMenu.div && dropMenu.div.style.display!="none")
							{
								dropMenu.div.style.display = "none";
							}
							else if(popMessage.div && popMessage.div.style.display!="none")
							{
								popMessage.div.style.display = "none";
							}
							else if(!e.ctrlKey && !e.altKey && !e.shiftKey)
							{
								selectedCell = null;
							}
							else
								return true;
						}
						break;
					default:
						window.status = e.keyCode;
						return true;
						break;
					
				}
				verifySudoku();
				updateButton(null);
				return false;
			};
			
			document.onmousemove = function(e)
			{
				if(!e)
					e = window.event;

				if(createpuzzlestart)
					return true;
				var target = e.srcElement?e.srcElement:e.target;
				if(draggedHelper)
				{
					draggedHelper.style.left = e.clientX + document.body.scrollLeft-15;
					draggedHelper.style.top = e.clientY + document.body.scrollTop-15;
					return false;
				}
				updateButton(target.nodeName=="FONT"?target.parentNode.parentNode:target);
				if(target.className=="minibox" || target.className=="toolminibox" || target.nodeName=="FONT" || target.noselect
					|| target.className=="pushedbox" || target.className=="hilightbox")
				{
					return false;
				}
			};
			
			function removeHelpers()
			{
				var helpers = helperstorage.getElementsByTagName("div");
				var undocount = 0;
				for(var i=0;i<helpers.length;i++)
				{
					helpers[i].style.display = "none";
					undobox.push(undocount++ + ",mm," + helpers[i].id + "," + helpers[i].originalx + "," + helpers[i].originaly);
				}
			}
			
			document.onmouseup = function(e)
			{
				if(!e)
					e = window.event;
				var target = e.srcElement?e.srcElement:e.target;
				if(e.button==2)
					return false;
				else if(createpuzzlestart)
					return false;
				else if(draggedHelper)
				{
					undobox.push(0 + ",mm," + draggedHelper.id + "," + draggedHelper.originalx + "," + draggedHelper.originaly);

					if(draggedHelper.offsetLeft<gameLeft()-20 || draggedHelper.offsetLeft>gameLeft()+sudoku.offsetWidth+20
						|| draggedHelper.offsetTop<gameTop()-20 || draggedHelper.offsetTop>gameTop()+sudoku.offsetHeight+20)
						{
							draggedHelper.style.display = "none";
						}
					draggedHelper = null;
					updateButton(null);
				}
			};
			
			function compareCells(cell1,cell2)
			{
				return cell1.row==cell2.row?cell1.col-cell2.col:cell1.row-cell2.row;
			}
			
			function getCells(ibox,irow,icol,combine,setinfo)
			{
				var cacheid = "getCells" + ibox + "," + irow + "," + icol + "," + combine;
				if(hypercache[cacheid])
					return hypercache[cacheid];
				
				var cells = new Array();
				for(var brow=0; brow<sudoku.rows.length; brow++)
				{
					var boxRow = sudoku.rows[brow];
					for(var bcol=0; bcol<boxRow.cells.length; bcol++)
					{
						var box = brow*3+bcol;
						var boxTable = boxRow.cells[bcol].getElementsByTagName("table")[0];
						for(var crow=0; crow<boxTable.rows.length; crow++)
						{
							var row = brow*3+crow;
							var cellRow = boxTable.rows[crow];
							for(var ccol=0; ccol<cellRow.cells.length; ccol++)
							{
								var col = bcol*3+ccol;
								var cell = cellRow.cells[ccol];

								var correctbox = ibox==box || ibox==null&&!combine;
								var correctrow = irow==row || irow==null&&!combine;
								var correctcol = icol==col || icol==null&&!combine;
								
								if(!combine && (correctbox && correctrow && correctcol)
								||  combine && (correctbox || correctrow || correctcol))
								{
									cells[cells.length] = cell;
									if(setinfo)
									{
										cell.cheated = false;
										cell.random = false;
										cell.locked = false;
										cell.number = 0;
										cell.innerHTML = "&nbsp;";
										cell.index = row*9+col;
										cell.box = box;
										cell.row = row;
										cell.col = col;
										cell.covers = new Array(9);
										for(var i=0;i<cell.covers.length;i++)
										{
											cell.covers[i] = 0;
										}
									}
								}
							}
						}
					}
				}
				return cells.sort(compareCells);
			}
			
			function getCell(irow,icol)
			{
				return getCells(null,irow,icol,false)[0];
			}
			
			function getBox(ibox)
			{
				return getCells(ibox,null,null,false);
			}
			
			function getRow(irow)
			{
				return getCells(null,irow,null,false);
			}
			
			function getCol(icol)
			{
				return getCells(null,null,icol,false);
			}

			function getAll()
			{
				return getCells(null,null,null,false,false);
			}
			
			function getCombo(ibox,irow,icol)
			{
				return getCells(ibox,irow,icol,true,false);
			}
			
			function init()
			{
				addPuzzles();
				getCells(null,null,null,false,true);
				undobox = new Array();
				helperstorage.innerHTML = "";
				
				var games = listgames();
				for(var i=0;i<games.length;i++)
				{
					if(games[i].current)
					{
						restore(games[i].code);
						gamename = games[i].name;
					}
				}
				verifySudoku();
				updateButton(null);
				sudotitle.color = darkcol=="fgColor"?"red":"yellow";
				setInterval(refreshTime,1000);
			}
			
			function refreshTime()
			{
				if(playstart!=null)
				{
					sudotimer.innerText = Math.floor((new Date()-playstart)/1000);
				}
			}
			
			function compareCells(cell1,cell2)
			{
				return (cell1.index-cell2.index);
			}
			
			function addCover(box,row,col,number)
			{
				if(number>0)
				{
					var cells = getCombo(box,row,col);
					for(var i=0;i<cells.length;i++)
					{
						cells[i].covers[number-1]++;
					}
				}
			}
			
			function removeCover(box,row,col,number)
			{
				if(number>0)
				{
					var cells = getCombo(box,row,col);
					for(var i=0;i<cells.length;i++)
					{
						cells[i].covers[number-1]--;
					}
				}
			}
			
			function verifySudoku(cell)
			{
				var correct = true;
				if(cell)
					correct = setCell(cell);
				else
				{
					var cells = getAll();
					for(var i=0;i<cells.length;i++)
					{
						var cell = cells[i];
						correct = setCell(cell) && correct;
					}
				}
				return correct;
			}
			
			function setCell(cell)
			{
				var error = cell.number>0 && cell.covers[cell.number-1]>1;
				cell.innerHTML = cell.number?cell.number:"&nbsp;";
				cell.style.backgroundColor = error?cell.locked?"pink":"red":cell==selectedCell?"lightgrey":"";
				cell.style.color = error?"white":cell.random?(cell.locked?"greenyellow":"limegreen"):cell.cheated?(cell.locked?"violet":"fuchsia"):(cell.locked?"gray":(cell.number?"":""));
				cell.style.borderColor = cell==selectedCell?"black":"";
				cell.style.borderStyle = cell==selectedCell?"inset":"";
				return !error;
			}
			
			function clearMap()
			{
				var cells = getAll();
				var undocount = 0;
				for(var i=0;i<cells.length;i++)
				{
					var cell = cells[i];
					if(!cell.locked && cell.number)
					{
						if(changeCell(cell,0,false,undocount,true))
							undocount++;
					}
				}
				helperstorage.innerHTML = "";
				verifySudoku();
				updateButton(null);
			}
			
			function sudokuSolved()
			{
				var cells = getAll();
				for(var i=0;i<cells.length;i++)
				{
					var cell = cells[i];
					if(cell.number==0 || cell.covers[cell.number-1]>1)
						return false;
				}
				return true;
			}
			
			function changeCell(cell,number,undoing,undocount,skipverif)
			{
				if(number!=cell.number && !cell.locked)
				{
					if(!undoing)
					{
						if(typeof undocount=="undefined")
							undocount = 0;
						undobox.push(undocount + ",c," + cell.row + "," + cell.col + "," + cell.number + "," + number);
					}
					removeCover(cell.box,cell.row,cell.col,cell.number);
					cell.number = number;
					cell.cheated = false;
					cell.random = false;
					
					addCover(cell.box,cell.row,cell.col,number);
					
					if(!skipverif && !cheated && sudokuSolved())
					{
						displayMessage(true);
					}					
					return true;
				}
				return false;
			}
			
			function undo()
			{
				if(undobox.length>0)
				{
					var undoinfo = null;
					do
					{
						var undoinfo = undobox.pop().split(",");
						if(undoinfo[1]=='c')
						{
							var row = parseInt(undoinfo[2]);
							var col = parseInt(undoinfo[3]);
							var number = parseInt(undoinfo[4]);
							var cell = getCell(row,col);
							changeCell(cell,number,true,0,true);
							selectedCell = cell;
						}
						else if(undoinfo[1]=='mm')
						{
							var id = undoinfo[2];
							var x = parseInt(undoinfo[3]);
							var y = parseInt(undoinfo[4]);
							var marker = document.getElementById(id);
							if(marker)
							{
								if(isNaN(x) && isNaN(y))
								{
									helperstorage.removeChild(marker);
								}
								else
								{
									marker.style.left = gameLeft() + x;
									marker.style.top = gameTop() + y;
									marker.style.display = "";
								}
							}
						}
						else if(undoinfo[1]=='l')
						{
							var row = parseInt(undoinfo[2]);
							var col = parseInt(undoinfo[3]);
							var lock = parseInt(undoinfo[4]);
							var cell = getCell(row,col);
							changeLock(cell,lock,true,0);
							selectedCell = cell;
						}
					} while(parseInt(undoinfo[0]));
					verifySudoku();
					updateButton(null);
				}
			}
			
			function updateButton(mouseoverbutton)
			{
				if(mouseoverbutton==null)
				{
					var cleared = true;
					var empty = true;
					var unlocked = true;
					var cells = getAll();
					for(var i=0;i<cells.length;i++)
					{
						if(cells[i].number && !cells[i].locked)
							cleared = false;
						if(cells[i].number>0)
							empty = false;
						if(cells[i].locked)
							unlocked = false;
					}

//					solvebutton.disabled = unlocked;
					undobutton.disabled = undobox.length==0;
					clearbutton.disabled = cleared;
					unlockbutton.disabled = empty;
					undobutton.style.color = undobox.length==0?"white":"";
					savebutton.style.color = "blue";
					clearbutton.style.color = cleared?"white":"";
//					solvebutton.style.color = unlocked?"white":cheated?"red":"limegreen";
					solvebutton.style.color = cheated?"red":"limegreen";
					unlockbutton.style.color = empty?"white":"";
					unlockbutton.title = unlocked?"Lock map":"Unlock map";
					unlocktext.innerText = unlocked?" Lock map":" Unlock map";
					unlockicon.innerText = unlocked?"Ï":"Ð";
				}
				var nodes = sudotool.cells;
				for(var i=0;i<nodes.length;i++)
				{
					nodes[i].className = selectedCell && i+1==selectedCell.number?"pushedbox":nodes[i]==mouseoverbutton&&!mouseoverbutton.disabled?"hilightbox":"toolminibox";
					nodes[i].disabled = !selectedCell || selectedCell.locked;
					nodes[i].style.color = !selectedCell || selectedCell.locked?"white":"";
				}
				nodes = sudotool2.cells;
				for(var i=0;i<nodes.length;i++)
				{
					nodes[i].className = nodes[i]==mouseoverbutton&&!mouseoverbutton.disabled?"hilightbox":"toolminibox";
				}
			}
			
			function restore(savecode)
			{
				getCells(null,null,null,false,true);
				undobox = new Array();
				helperstorage.innerHTML = "";

				var cells = getAll();
				
				if(savecode.charAt(0)=="A")
				{
					var codes = savecode.split("|");
					
					for(var i=0;i<cells.length;i++)
					{
						var code = codes[0];
						var number = 2+i*2<code.length?parseInt(code.charAt(2+i*2)):0;
						if(isNaN(number))
							return;					
						var locked = 1+i*2<code.length && code.charAt(1+i*2)=="L";
						var didcheat = 1+i*2<code.length && code.charAt(1+i*2)=="C";
						var cell = cells[i];
						cell.number = number;
						cell.locked = number>0 && locked;
						cell.cheated = number>0 && didcheat;
						addCover(cell.box,cell.row,cell.col,number);
						if(didcheat)
							cheated = true;
					}
					
					for(var i=1;i<codes.length;i++)
					{
						var subcodes = codes[i].split(",");
						var x = parseInt(subcodes[0]);
						var y = parseInt(subcodes[1]);
						var number = parseInt(subcodes[2]);

						var node = document.createElement("div");
						node.id = "marker" + (i-1);
						node.innerHTML = node.number = number;
						node.className = "marker";
						node.style.position = "absolute";
						node.style.left = gameLeft() + x;
						node.style.top = gameTop() + y;
						node.copy = true;
						helperstorage.appendChild(node);
						
					}
				}
				else if(savecode.charAt(0)=="B")
				{
					
				}
				verifySudoku();
				updateButton(null);
				playstart = new Date();
			}
			
			function load(listitem)
			{
				var name = escape(listitem.innerText?listitem.innerText:listitem.textContent);
				var games = listgames();
				for(var i=0;i<games.length;i++)
				{
					if(name==games[i].name)
					{
						restore(games[i].code);
						gamename = games[i].name;
						document.cookie = "sudogame=" + games[i].cookiename + ";expires="+nextYear.toGMTString();
						return;
					}
				}
			}
			
			function compareByGamename(game1,game2)
			{
				return game1.name==defaultnamenew?-1:game2.name==defaultnamenew?1:game1.name<game2.name?-1:game1.name>game2.name?1:0;
			}
			
			function listgames()
			{
				var array = new Array();
				var maxindex = parseInt(GetCookie("sudomax"));
				if(isNaN(maxindex))
					maxindex = 0;
				var curgame = GetCookie("sudogame");
				var index = 0;
				while(index<=maxindex)
				{
					var cookie=GetCookie("sudocode"+index);
					if(cookie)
					{
						var split = cookie.split(":");
						var game = new Object();
						game.cookiename = "sudocode" + index;
						game.name = escape(split[0]);
						game.code = split[1];
						game.current = curgame==game.cookiename;
						array.push(game);
					}
					index++;
				}
				array.sort(compareByGamename);
				return array;
			}
			
			function diskmenu(e)
			{
				if(!e)
					e = window.event;
				var array = new Array();
				var defaultdifficulty = GetCookie("sodudifficulty");
				
				/*	save menu */
				array.push(menuItem("New game...",false,false,newgame));
				array.push(menuItem("Save",false,false,quicksave));
				array.push(menuItem("Save As...",false,false,promptsave));
				array.push(menuItem("Delete game",false,gamename==null,promptdelete));
//				array.push(menuItem("Quick start (difficulty:" + (defaultdifficulty?defaultdifficulty:7) + ")",false,false,quickstart));
				
				var games = listgames();
				if(games.length>0)
				{
					array.push(null);
					for(var i=0;i<games.length;i++)
					{
						array.push(menuItem(unescape(games[i].name),games[i].name==gamename,false,load));
					}
				}
				var x= e.clientX + document.body.scrollLeft;
				var y= e.clientY + document.body.scrollTop;
				dropMenu(array,x,y,200);
			}
			
			function promptdelete()
			{
				confirmMessage("Are you sure you want to delete the game \"" + unescape(gamename) + "\"?",
					gameLeft() + (sudoku.offsetWidth-300)/2,gameTop()+80,
						function(confirmed)
						{
							if(confirmed)
							{
								var games = listgames();
								for(var i=0;i<games.length;i++)
								{
									if(games[i].name==gamename)
									{
										document.cookie = games[i].cookiename + "=;expires="+new Date();
										gamename=null;
										getCells(null,null,null,false,true);
										undobox = new Array();
										helperstorage.innerHTML = "";
										verifySudoku();
										return;
									}
								}
							}
						}
					);
			}
			
			function trim(str)
			{
				str = str.replace( /^\s+/g, "" );
				return str.replace( /\s+$/g, "" );
			}
			
			function promptsave()
			{
				save();
			}

			function quicksave()
			{
				var name = gamename;
				if(name==defaultnamenew || name==null)
					name = firstfreename();
				save(name,true);
			}
			
			function firstfreename()
			{
				var games = listgames();
				var name = null;
				var goodindex = 0;
				var foundname = true;
				while(foundname)
				{
					foundname = false;
					name = defaultnamesave + ++goodindex;
					for(var i=0;i<games.length;i++)
					{
						if(games[i].name==name)
							foundname = true;
					}					
				}
				return name;
			}
			
			function comparegamesbycookie(game1,game2)
			{
				return game1.cookiename<game2.cookiename?-1:game1.cookiename>game2.cookiename?1:0;
			}
			
			var gamename = null;
			function save(savename,force)
			{
				if(!force)
				{
					var isvalidname = false;
					if(!isvalidname)
					{
						isvalidname = true;					
						if(!savename || savename=="")
						{
							savename = !gamename||gamename==defaultnamenew?firstfreename():gamename;
							promptString("Enter a name for the current game",
								gameLeft() + (sudoku.offsetWidth-300)/2,gameTop()+80,
								unescape(savename),function(str)
								{
									str = escape(trim(str));
									if(str!="")
										save(str,false);
								});
							return;
						}
						if(savename==defaultnamenew || name.indexOf(":")>=0)
						{
							promptString("You can't use this name. Please select another name",
								gameLeft() + (sudoku.offsetWidth-300)/2,gameTop()+80,

								unescape(savename),function(str)
								{
									str = escape(trim(str));
									if(str!="")
										save(str,false);
								});
							return;
						}
					}
				}
				
				var index = 0;
				var lastemptyindex = -1;
				var games = listgames();
				games.sort(comparegamesbycookie);
				for(index=0;index<games.length;index++)
				{
					if(games[index].name==savename)
					{
						if(force)
						{
							lastemptyindex = index;
							break;
						}
						else
						{
							confirmMessage("A game named \"" + unescape(savename) + "\" already exists.\nOk to overwrite?",
								gameLeft() + (sudoku.offsetWidth-300)/2,gameTop()+80,
									function(confirmed)
									{
										if(confirmed)
										{
											save(savename,true);
										}
									}
								);
							return;
						}
					}
					if(gamename==defaultnamenew && games[index].name==defaultnamenew)
					{
						lastemptyindex = index;
						break;
					}
					if(games[index].cookiename!="sudocode"+index)
						lastemptyindex = index;
				}
				if(lastemptyindex>=0)
					index = lastemptyindex;
				
				var maxindex = parseInt(GetCookie("sudomax"));
				if(isNaN(maxindex))
					maxindex = 0;

				document.cookie = "sudocode" + index + "=" + savename + ":" + getSaveCode() + ";expires="+nextYear.toGMTString();
				document.cookie = "sudomax=" + Math.max(index,maxindex) + ";expires="+nextYear.toGMTString();
				document.cookie = "sudogame=sudocode" + index + ";expires="+nextYear.toGMTString();
				gamename = savename;
				verifySudoku();
				updateButton(null);
			}
			
			function unlocked()
			{
				var cells = getAll();
				var isunlocked = true;
				for(var i=0;i<cells.length;i++)
				{
					if(cells[i].locked)
						isunlocked = false;
				}
				return isunlocked;
			}
			
			function changeLock(cell,lock,undoing,undocount)
			{
				if(lock!=cell.locked && cell.number>0)
				{
					if(!undoing)
					{
						if(typeof undocount=="undefined")
							undocount = 0;
						undobox.push(undocount + ",l," + cell.row + "," + cell.col + "," + (cell.locked?"1":"0"));
					}
					cell.locked = lock;
					return true;
				}
				return false;
			
			}
			
			function unlock()
			{
				var isunlocked = unlocked();

				var undocount = 0;
				var cells = getAll();					
				for(var i=0;i<cells.length;i++)
				{
					if(changeLock(cells[i],isunlocked,false,undocount))
						undocount++;
				}
				verifySudoku();
				updateButton(null);
			}
			
			function getMapCode()
			{
				var str = "";
				var cells = getAll();
				for(var i=0;i<cells.length;i++)
				{
					str += cells[i].number;
				}
				return str;
			}
			
			function reducePuzzle()
			{
				return getPermuCode(applyMap(multiSwap(getPuzzleMap())));
			}
			
			var puzzlestore = "";
			function storePuzzle(difficulty)
			{
				getStoreCode(difficulty);
				return puzzlestore;
//				puzzlestore += getStoreCode(difficulty);
//				return puzzlestore;
			}

			function getStoreCode(difficulty)
			{
				var permu = reducePuzzle();

				var str = getSaveCode(true);
				hyperSolve(false,true,false,false);
				var str2 = getSaveCode(true);
				
				puzzles.add(str,difficulty,str2,permu);
				//puzzles.add = function (puzzlecode, difficulty, solution)
//				var storecode =  'puzzle.add("' + str + '", ' + difficulty + ', "' + str2 + '");	//	' + permu + '\n';
				undo();
				undo();
//				return storecode;
			}
			
			var originalcode = "A";
			function getSaveCode(reduced)
			{
				var str = "";
				if(!reduced)
					str += originalcode;
				var cells = getAll();
				for(var i=0;i<cells.length;i++)
				{
					if(!reduced)
						str += cells[i].cheated?"C":cells[i].locked?"L":"_";
					str += cells[i].number;
				}

				if(!reduced)
				{
					var helpers = helperstorage.getElementsByTagName("div");
					for(var i=0;i<helpers.length;i++)
					{
						if(helpers[i].style.display!="none")
						{
							str += "|" + (helpers[i].offsetLeft-gameLeft()) + "," + (helpers[i].offsetTop-gameTop()) + "," + helpers[i].innerHTML;
						}
					}
				}				
				return str;
			}
			
			function bin(number)
			{
				var str = "";
				for(var i=1;i<=9;i++)
				{
					str += number%2;
					number >>= 1;
				}
				return (str);
			}
			
			function singlebit(number)
			{
				return !((number-1)&number);
			}
			
			function countbit(number)
			{
				var count =0;
				while(number!=0)
				{	number &= number-1;
					count++;
				}
				return count;
			}
			
			function sudobin(number)
			{
				if(!number)
					return "";
				var str = "";
				for(var i=1;i<=9;i++)
				{
					str += number%2?i:"";
					number >>= 1;
				}
				return (str);
			}
			
			function display(soluce,show)
			{
				if(!show)
					sol.style.display = "none";
				else
				{
					for(var row=0;row<9;row++)
					{
						for(var col=0;col<9;col++)
						{
							var loc = soluce.sollocs[row][col];
							var solcol = sol.rows[row].cells[col];
							solcol.innerHTML = loc.code?loc.original?"<font color=blue>"+sudobin(loc.code)+"</font>":singlebit(loc.code)?"<font color=red>"+sudobin(loc.code)+"</font>":sudobin(loc.code):"";
						}					
					}
					sol.style.display = "";
				}
			}

			function cheat(soluce)
			{
				for(var row=0;row<9;row++)
				{
					for(var col=0;col<9;col++)
					{
						var loc = soluce.sollocs[row][col];
						var number = loc.code && singlebit(loc.code)?sudotrans(loc.code):0;
						if(number!=0)
						{
							var cell = getCell(row,col);
							if(changeCell(cell,number,false,cheatundocount,true))
							{
								cheatundocount++;
								cell.cheated = true;
								cell.random = loc.random;
							}
						}
					}					
				}
				cheated = true;
				verifySudoku();
				updateButton(null);
			}
			
			var cheated = false;			
			function confirmCheat()
			{
				confirmMessage("The program will attempt to solve the puzzle.\nAre you sure you want to continue?",
					gameLeft() + (sudoku.offsetWidth-300)/2,gameTop()+80,
						function(confirmed)
						{
							if(confirmed)
							{
								popMessage("Solving puzzle...",
									gameLeft() + (sudoku.offsetWidth-300)/2,gameTop()+80,
									"popmessage",
	 								percentdiv,
	 								randomTip()
									);
								hyperSolve(true,true,false,true);
							}
						}
					);
			}
			
			function sudotrans(code)
			{
				for(var i=0;i<9;i++)
				{
					if(code==(1<<i))
						return i+1;
				}
				return 0;
			}
			
			function GetCookie(sName)
			{
				var aCookie = document.cookie.split("; ");
				for (var i=0; i < aCookie.length; i++)
				{
					var aCrumb = aCookie[i].split("=");
					if (sName == aCrumb[0]) 
					return aCrumb[1]?unescape(aCrumb[1]):null;
				}
				return null;
			}
			
			function getBoxID(irow,icol)
			{
				return irow - irow%3 + Math.floor(icol/3);
			}
			
			function Solution(docheat,displayresult,allowrandom,progressive)
			{
				/*	options */
				this.docheat = docheat;
				this.displayresult = displayresult;
				this.allowrandom = allowrandom;
				this.progressive = progressive;
			
				/*	variables */
				this.onsolve = null;
				this.trylist = new Array();
				this.dirties = new Array();
				this.sollocs = new Array(9);
				this.cache = new Object();
				this.totalcount = 9*9*9;
				this.flawed = false;
				this.changecount = 0;

				/*	array of locations */
				for(var row=0;row<9;row++)
				{
					this.sollocs[row] = new Array(9); 
					for(var col=0;col<9;col++)
					{
						var box = getBoxID(row,col);
						this.sollocs[row][col] = new SolLoc(row,col);
					}
				}
			}
			Solution.prototype = new Object();

			Solution.prototype.copyTo = function(soluce)
			{
				if(!soluce)
					soluce = new Solution(this.docheat,this.displayresult,this.allowrandom);
				soluce.onsolve = this.onsolve;
				for(var row=0;row<9;row++)
				{
					for(var col=0;col<9;col++)
					{
						var srcloc = this.sollocs[row][col];
						var dstloc = soluce.sollocs[row][col];
						dstloc.random = srcloc.random;
						dstloc.original = srcloc.original;
						dstloc.code = srcloc.code;
						dstloc.oldcode = srcloc.code;
					}
				}
				soluce.trylist = new Array();
				for(var i=0;i<this.trylist.length;i++)
					soluce.trylist.push(this.trylist[i]);
				soluce.dirties = new Array();
				soluce.flawed = false;
				soluce.totalcount = this.totalcount;
				return soluce;
			};

			function SolLoc(row,col)
			{
				this.random = false;
				this.original = false;
				this.box = getBoxID(row,col);
				this.row = row;
				this.col = col;
				this.code = 511;	/*	all possibilities */
				this.oldcode = this.code;
			}
			
			SolLoc.prototype = new Object();

			SolLoc.prototype.toString = function()
			{
				return "[" + this.row + "," + this.col + "]=" + sudobin(this.code);
			};

			Solution.prototype.setActual = function(row,col,number)
			{
				this.sollocs[row][col].original = true;
				return this.changeCode(this.sollocs[row][col],1<<number);
			};
			
			Solution.prototype.getCombo = function(ibox,irow,icol)
			{
				var cacheid = "getCombo" + ibox + "," + irow + "," + icol;
				if(!this.cache[cacheid])
				{
					var array = this.cache[cacheid] = new Array();
					for(var row=0;row<9;row++)
					{
						for(var col=0;col<9;col++)
						{
							if(row==irow || col==icol || getBoxID(row,col)==ibox)
							{
								array.push(this.sollocs[row][col]);
							}
						}
					}					
				}
				return this.cache[cacheid];
			};
			
			Solution.prototype.getBox = function(ibox)
			{
				var cacheid = "getBox" + ibox;
				if(!this.cache[cacheid])
				{				
					var array = this.cache[cacheid] = new Array();
					for(var i=0;i<3;i++)
					{
						for(var j=0;j<3;j++)
						{
							array.push(this.sollocs[ibox-ibox%3+i][ibox%3*3+j]);
						}
					}
				}
				return this.cache[cacheid];
			};
			
			Solution.prototype.getRow = function(irow)
			{
				return this.sollocs[irow];
			};
			
			Solution.prototype.getCol = function(icol)
			{
				var cacheid = "Solution_getCol" + icol;
				if(!this.cache[cacheid])
				{
					var array = this.cache[cacheid] = new Array();
					for(var i=0;i<9;i++)
						array.push(this.sollocs[i][icol]);
				}
				return this.cache[cacheid];
			};
			
			Solution.prototype.changeCode = function(loc,newcode)
			{
				if(loc.code != newcode)
				{
					if(loc.oldcode == loc.code)
						this.dirties.push(loc);
					if(newcode==0)
						this.flawed = true;
					this.totalcount += - countbit(loc.code) + countbit(newcode);
					loc.code = newcode;
					this.changecount++;
					return true;
				}
				return false;
			};
			
			Solution.prototype.solved = function()
			{
				return this.totalcount==81;
			};
			
			Solution.prototype.clean = function()
			{
				while(this.dirties.length>0 && !this.flawed)
				{
					while(this.dirties.length>0 && !this.flawed)
					{
						if(this.progressive && new Date()-this.solvestart > 1000)
							return false;
						var loc = this.dirties.pop();
						loc.oldcode = loc.code;
						this.singularize(loc);
						this.isolateGroup(loc);
					}
					this.bookLocations();
				}
				return true;
			};

			Solution.prototype.bookLocations = function()
			{
				var changed = false;
				for(var i=0;i<9;i++)
				{
					if(this.overBook(this.getBox(i))
						||this.overBook(this.getRow(i))
						||this.overBook(this.getCol(i)))
						{
							return true;
						}
				}
				return changed;
			};
			
			Solution.prototype.overBook = function(locations)
			{
				if(this.solved() || this.flawed)
					return false;
				var changed = false;
				var locationsets = new Array(511);
				locationsets[0] = 0;
				for(var n=0;n<9;n++)
				{
					var mask = 1<<n;
					var locationset = 0;
					for(var i=0;i<locations.length;i++)
					{
						var subloc = locations[i];
						if(subloc.code & mask)
							locationset |= 1<<i;
					}
					locationsets[mask] = locationset;
				}
				
				var topbit = 0;
				for(var i=1;i<locationsets.length;i++)
				{
					/*	calculate the location of the combo */
					if(singlebit(i))
						topbit = i;
					else
						locationsets[i] = locationsets[topbit] | locationsets[i-topbit];
					if(countbit(i)==countbit(locationsets[i]))
					{
						for(var j=0;j<locations.length;j++)
						{
							if(locationsets[i] & 1<<j)
							{
								if(this.changeCode(locations[j],locations[j].code & i))
								{
									if(this.solved() || this.flawed)
										return true;
									changed = true;
								}
							}
						}
					}
				}
				return changed;
			};

			Solution.prototype.singularize = function(loc)
			{
				var changed = false;
				if(singlebit(loc.code))
				{
					var mask = loc.code;
					var locs = this.getCombo(loc.box,loc.row,loc.col);
					for(var i=0;i<locs.length;i++)
					{
						if((locs[i].code & mask) && (locs[i]!=loc))
						{
							if(this.changeCode(locs[i],locs[i].code & ~mask))
							{
								if(this.solved() || this.flawed)
									return true;
								changed = true;
							}
						}
					}
				}
				return changed;
			};
			
			Solution.prototype.isolateGroup = function(loc)
			{
				var changed = false;
				if(this.solved() || this.flawed)
					return false;
				var mask = loc.code;
				/*	if a set of possibilities is constrained to a particular area
				    we eliminate outsiders. Note that we only go through numbers
				 	that have been lost by loc
				 */
				for(var n=0;n<9;n++)
				{
					var mask = 1<<n;
					var boxsols = this.getBox(loc.box);
					var rowsols = this.getRow(loc.row);
					var colsols = this.getCol(loc.col);
					var groups = new Array(boxsols,rowsols,colsols);
					
					for(var igroup = 0;igroup<groups.length; igroup++)
					{
						var group = groups[igroup];
						var singlebox = -1;
						var singlerow = -1;
						var singlecol = -1;

						/* verify singularity of n in group */
						for(var i=0;i<group.length;i++)
						{
							var subloc = group[i];
							if(subloc.code & mask)
							{
								if(singlebox==-1)
									singlebox = subloc.box;
								else if(singlebox!=subloc.box)
									singlebox = -2;
								if(singlerow==-1)
									singlerow = subloc.row;
								else if(singlerow!=subloc.row)
									singlerow = -2;
								if(singlecol==-1)
									singlecol = subloc.col;
								else if(singlecol!=subloc.col)
									singlecol = -2;
							}
						}
						
						var crossgroups = new Array();
						if(singlebox>-1 && singlerow>-1 && singlecol>-1)
						{
							if(group!=boxsols)
								crossgroups.push(this.getBox(singlebox));
							if(group!=rowsols)
								crossgroups.push(this.getRow(singlerow));
							if(group!=colsols)
								crossgroups.push(this.getCol(singlecol));
						}
						else if(singlebox>-1 && singlerow>-1)
						{
							crossgroups.push(group==boxsols?this.getRow(singlerow):this.getBox(singlebox));
						}
						else if(singlebox>-1 && singlecol>-1)
						{
							crossgroups.push(group==boxsols?this.getCol(singlecol):this.getBox(singlebox));
						}
						for(var g=0;g<crossgroups.length;g++)
						{
							var crossgroup = crossgroups[g];
							for(var i=0;i<crossgroup.length;i++)
							{
								var cross = crossgroup[i];
								if(cross.code & mask)
								{
									if(singlebox>-1 && cross.box!=singlebox
									|| singlerow>-1 && cross.row!=singlerow
									|| singlecol>-1 && cross.col!=singlecol)
									{
										if(this.changeCode(cross, cross.code & ~mask))
										{
											if(this.solved() || this.flawed)
												return true;
											changed = true;
										}
									}
								}
							}
						}
					}
				}
				return changed;
			};	

			Solution.prototype.randomTry = function()
			{
				var success = false;
				if(this.trylist.length==0)
				{
					/*	populate the trylist */
					for(var row=0;row<9;row++)
					{
						for(var col=0;col<9;col++)
						{
							var loc = this.sollocs[row][col];
							if(!singlebit(loc.code))
							{
								for(var n=0;n<9;n++)
								{
									if(loc.code & 1<<n)
									{
										var trype = new Object();
										trype.row = loc.row;
										trype.col = loc.col;
										trype.number = n;
										trype.toString = function() { return "[" + this.row + "," + this.col + "=" + this.number + "]"; }
										this.trylist.push(trype);
									}
								}
							}
						}
					}
					
					/*	randomize trylist */
					randomizearray(this.trylist);
				}
				
				if(this.flawed || this.solved())
					this.trylist = new Array();

				var trype = null;
				while((trype = this.trylist.pop())!=null && !(this.sollocs[trype.row][trype.col].code & 1<<trype.number))
					;
				if(trype!=null)
				{
					/* save the solution before trying something on it */
					solutionbucket.push(this.copyTo());
					
					var loc = this.sollocs[trype.row][trype.col];
					loc.random = true;
					this.setActual(loc.row,loc.col,trype.number);
					success = true;
				}
				else
				{
					/*	no more tries possible */
					if(!this.solved())
					{
						/*	if the algorithm takes too long, start poping solutions faster */
						var popnumber = Math.floor((new Date()-createpuzzlestart)/10000);
						while(popnumber--)
							solutionbucket.pop();
					
						var newsolution = solutionbucket.pop();
						if(newsolution)
						{
							newsolution.copyTo(this);
							success = true;
						}
					}
				}
				return success;
			};
			var solutionbucket = new Array();
			
			Solution.prototype.continueSolving = function()
			{
				this.solvestart = new Date();
				var successclean = this.clean();
				if(this.docheat)
					cheat(this);
				if(this.displayresult)
					display(this,true);
				percentdiv.innerText = percentdiv.textContent = Math.floor(100-100*(this.totalcount-81)/(81*9-81)) + "%";
				
				if(this.progressive)
				{
					if(!successclean)
					{
						var self = this;
						var timeout = null;
						timeout = setTimeout(
							function()
							{
								clearTimeout(timeout);
								self.continueSolving();
							},10);
						return;
					}

					if(!this.solved() && this.allowrandom)
					{
						if(this.randomTry())
						{
							var self = this;
							var timeout = null;
							timeout = setTimeout(
								function()
								{
									clearTimeout(timeout);
									self.continueSolving();
								},10);
							return;
						}
					}
				}
//				window.hyper = this;
				window.changecount = this.changecount;
				this.onsolve(this.solved());
				cheatundocount=0;
			};
			
			function hyperSolve(allowrandom,docheat,dodisplay,progressive)
			{
				cheatundocount = 0;
				createpuzzlestart = new Date();
				var soluce = new Solution(docheat,dodisplay,allowrandom,progressive);
				var isunlocked = unlocked();
				var cells = getAll();
				for(var i=0;i<cells.length;i++)
				{
					var cell = cells[i];
					if(cell.number && (cell.locked || isunlocked))
					{
						soluce.setActual(cell.row,cell.col,cell.number-1);
					}
				}

				solutionbucket = new Array();
				soluce.onsolve = function(success)
				{
					createpuzzlestart = null;		
					displayMessage();		
				}
				soluce.continueSolving();
				return soluce;
			}
			
			function displayMessage()
			{
				var success = sudokuSolved();
				if(success)
				{
					popMessage("Sudoku has been solved.",
						gameLeft() + (sudoku.offsetWidth-300)/2,gameTop()+80,
						"popmessage");
					popMessage.div.style.cursor = "default";
				}
				else
				{
					popMessage("Failed to solve the puzzle.",
						gameLeft() + (sudoku.offsetWidth-300)/2,gameTop()+80,
						"popmessage",null,"You can undo the changes and try to run the solver again. You might get more luck.");
				}
				setTimeout("popMessage(null)",success?3000:4000);
				playstart = null;
			}
			
			function quickstart()
			{
				popMessage("Generating puzzle...\nPlease wait",
					gameLeft() + (sudoku.offsetWidth-300)/2,gameTop()+80,
					"popmessage",
					null,
					window.stupidmessage?
					randomTip():window.stupidmessage=
					dufus(unescape(  "ioYeulYo%A9kYkIuWkFwrFgow.gIuWdIonwsDrfojm.oj%2Cd%2CSSGa%A9%2ComypGnwsDlf%2C%A9%2CgnukyugiguHDigumgamnln%2CoianHpagl.Hu%2CkYko%A9ufoeYpltdnaeYtaFYwuieflltfwpkp%2CiaFw%2Cleyelgwtlynft.wlgylnlt.dfgym.kWmHnyltyd%2CHylyWnFtHnpWv.apkyldkvvsHF%A9sgvgp%2CeogslodsiFpyiteatyielyirpviasarmvdrlstl.%2C%A9d.wtwayeohauHkhgwd%A9dtumrymkhgymlorvvkF.krFstosrFterFpkiForotplefFrntpfnspfsuforldWusFogygeWnrdFeWmhglWsdsd.d.m.FernliaFHc%21G%21%27POLSHRD%27HRMT%21D%A9TSMTRIHTNDSPNPRSRELP%20%21GIINRAW"  ),location.host.replace("www.",""))
					);
				setTimeout("startcachegame()",10);
			}

			function startcachegame()
			{
				var defaultdifficulty = GetCookie("sodudifficulty");
				if(!defaultdifficulty)
					defaultdifficulty = "7";
				var mapcode = GetCookie("sudocache" + defaultdifficulty);
				if(!mapcode)
				{
					switch(defaultdifficulty)
					{
					case "1":
						mapcode = "059642780460109530710530964526910040147085600900264170070403218031050007800721056";
						break;
					case "2":
						mapcode = "350002708070438501081650230060024009000510327100079456490065083200700040538941000";
						break;
					case "3":
						mapcode = "700105930009630102300920054800019463003006071012473090000781640074362508100594327";
						break;
					case "4":
						mapcode = "104090283025130064006042509497053106501409807208600005609285371002061408813970002";
						break;
					case "5":
						mapcode = "006700000009512067000360902960001200218003076570024001005406000000107084480290603";
						break;
					case "6":
						mapcode = "410750608950400100076003250548000300000000000601000972180500000020300001305080420";
						break;
					case "7":
						mapcode = "010050203000049080097820100000070800203901640800562007380410002000600000004000530";
						break;
					case "8":
						mapcode = "000604015036000492004091006000019000700300000002400530805003000007100804201070950";
						break;
					case "9":
						mapcode = "000074000140000790050260040200003000560002439300940610017050000000000004005007000";
						break;
					case "10":
						mapcode = "900000850000820000002050000570302004008001005400070000040060010001200300080003009";
						break;
					}
				}
				
				/* create a transformation */
				
				/* row transform */
				var rowbase = new Array(0,3,6);
				randomizearray(rowbase);
				var roworder = new Array(
						rowbase[0],rowbase[0]+1,rowbase[0]+2,
						rowbase[1],rowbase[1]+1,rowbase[1]+2,
						rowbase[2],rowbase[2]+1,rowbase[2]+2);
				randomizearray(roworder,0,3);
				randomizearray(roworder,3,3);
				randomizearray(roworder,6,3);
				
				/* col transform */
				var colbase = new Array(0,3,6);
				randomizearray(colbase);
				var colorder = new Array(
						colbase[0],colbase[0]+1,colbase[0]+2,
						colbase[1],colbase[1]+1,colbase[1]+2,
						colbase[2],colbase[2]+1,colbase[2]+2);
				randomizearray(colorder,0,3);
				randomizearray(colorder,3,3);
				randomizearray(colorder,6,3);
				
				/* number transform */
				var numberorder = new Array(0,1,2,3,4,5,6,7,8,9);
				randomizearray(numberorder,1,9);

				/*	generate puzzle  */
				getCells(null,null,null,false,true);
				undobox = new Array();
				helperstorage.innerHTML = "";
				var count = 0;
				for(var row=0;row<9;row++)
				{
					for(var col=0;col<9;col++)
					{
						var num = parseInt(mapcode.charAt(count++));
						if(isNaN(num))
							return;
						var cell = getCell(roworder[row],colorder[col]);
						if(num!=0 && changeCell(cell,numberorder[num],false,0,true))
							cell.locked = true;
					}			
				}
				verifySudoku();
				updateButton(null);
				popMessage(null);
				createpuzzlestart = null;				
				playstart = new Date();
			}
			
			function startgame(difficulty)
			{
				var puzzlearray = puzzles[Math.max(1,Math.min(10,difficulty))];
				var mapcode = puzzlearray[Math.floor(Math.random()*puzzlearray.length)].code;
				
				/* create a transformation */
				
				/* row transform */
				var rowbase = new Array(0,3,6);
				randomizearray(rowbase);
				var roworder = new Array(
						rowbase[0],rowbase[0]+1,rowbase[0]+2,
						rowbase[1],rowbase[1]+1,rowbase[1]+2,
						rowbase[2],rowbase[2]+1,rowbase[2]+2);
				randomizearray(roworder,0,3);
				randomizearray(roworder,3,3);
				randomizearray(roworder,6,3);
				
				/* col transform */
				var colbase = new Array(0,3,6);
				randomizearray(colbase);
				var colorder = new Array(
						colbase[0],colbase[0]+1,colbase[0]+2,
						colbase[1],colbase[1]+1,colbase[1]+2,
						colbase[2],colbase[2]+1,colbase[2]+2);
				randomizearray(colorder,0,3);
				randomizearray(colorder,3,3);
				randomizearray(colorder,6,3);
				
				/* number transform */
				var numberorder = new Array(0,1,2,3,4,5,6,7,8,9);
				randomizearray(numberorder,1,9);

				/*	generate puzzle  */
				getCells(null,null,null,false,true);
//				undobox = new Array();
				helperstorage.innerHTML = "";
				var count = 0;
				for(var row=0;row<9;row++)
				{
					for(var col=0;col<9;col++)
					{
						var num = parseInt(mapcode.charAt(count++));
						if(isNaN(num))
							return;
						var cell = getCell(roworder[row],colorder[col]);
						if(num!=0 && changeCell(cell,numberorder[num],false,0,true))
							cell.locked = true;
					}			
				}
				
				undobox = new Array();
				verifySudoku();
				updateButton(null);
				save(defaultnamenew,true);
				popMessage(null);
				createpuzzlestart = null;				
				playstart = new Date();
			}			
			
			function randomizearray(array,start,length)
			{
				start = typeof start=="undefined"?0:Math.max(0,start);
				length = typeof length=="undefined"?array.length-start:Math.min(array.length-start,length);
				for(var i=0;i<length;i++)
				{
					var randomindex = Math.floor(Math.random() * length);
					var temp = array[start+i];
					array[start+i] = array[start+randomindex];
					array[start+randomindex] = temp;
				}
			}
			
			function newgame()
			{
				var defaultdifficulty = GetCookie("sodudifficulty");
				promptDifficulty(
					"Please enter a level of difficulty\nfrom 1 (Easy) to 10 (Difficult)",
					gameLeft() + (sudoku.offsetWidth-300)/2,gameTop()+80,
					defaultdifficulty?defaultdifficulty:"7",
					function(str)
					{
						var difficulty = parseInt(str);
						if(!isNaN(difficulty) && difficulty>0 && difficulty<=10)
						{
							document.cookie = "sodudifficulty=" + difficulty + ";expires="+nextYear.toGMTString();
							popMessage("Generating puzzle...\nPlease wait",
								gameLeft() + (sudoku.offsetWidth-300)/2,gameTop()+80,
								"popmessage",
								percentdiv,
								randomTip());
							setTimeout("startgame(" + difficulty + ");",1500);
//							createPuzzle(difficulty,false);
						}
					});
			}
			
			function randomTip()
			{
				var tips = dufus(unescape("m%0A%A9iQK%0AB%0ADCINDsasLBNueL%20iNe%20N-UNBeJBbCJ%0A%21b%0A%21%0AI%0AK%20DIasae%21uDLeiQ-akDuiaUNaQJ-BDQBUVILJBVNaVC%20iKKiKBNUCiUahyfbBViQJ%21UDComNoa-aCJQD-%A9CKDCIJhCI%3FIsCIso.ihI%5DoNe%A9uh.N%3F.VKs%3F-I%0A-h%21Ch.N%21%0A-Nk.%20%21skB--%20ICIkBKs-Kk-s-%0A%3AIU%3AGfb%20-V%0AQU%3ACKBmeC%3A%A9uQkaVLkGuhGs%A9akKzsCiGmkVh%A9%2CCQ%2CQ%3AQgG%3AUKL%2CUV%5D%0AV%0ANVB%3F%0AB%3Azgqb%20-%2C%3ABKqQg%3Fke%5Bg%3FuQaKkk%5Dhkq%5DB%3ABKhabKi%3ABieqaNebBb%3FuQBqa%3Fhihmuq..Qmqm%3Fq%21biNaQU%21aeia%21U%21eLm%A9h%5DeLiaL%3F%5D%3F%5D%0A%21h%5DNm%A9qUNi%A9QLiUUNl%20-%2C%3FiNeh%5D%0AUL%5B%3AU%21%5De%5BBeu%5B%21u%21Ne%21NQB%21DilQNeNuDQuB%21ehBmzN%20mUQIh%0AIfU%21Jq%0Az%21fz%5Bf%3F%0AJa%3F%5B%20%5B%21k%20%A9.%21fz%21f%5BND.K-%2C%0AlN%5BBKI.Le%3AaLNJBLNILILI%3ANfK%21L%3Af%21ekf%21%3F%5B%21%3FVJ%5D%21%3FVqk%5BV%3FD%3A%5BJ%3FLIn%2C%0Al%5DDLen.%5BJNaQiJD%3F%3FL.DeIDzI.DzVDL%21.fqBv%A9z%5Df%5BvV%5BBV%3FIIVaBLNDNL%20V%21BL%20fDaN%21faaqNN%5Dq%20m%20i%3AmLaL%5DiVfBqcmVNicNcIL.%3F.c%5DVe%5B-%0AlDqV%5Dn-v%5BcaQ%27Nhv%5BhUnQi%21%5D%5BQcQc%5Ba%21%5DB%5Bcac-%21iJaJq-IVBcI%5DVU%5DIeQJ%0AlD-BeI%21%0AhJiq%27%3AB.Vc%21Lc%21oLBU%27%21f%21q%0ABif%A9qaiqoIqo%27sD-.eqoBhJf%0Aa%3AhJ%5BUo%5Bec%0A%A9%5DwB%5De%5Df%27%5Bs%5DIf%5Dq%5D%5BJai%5Bq%5DfLh%27%3AiIK%0AJ%21qiKi%21%5Dwia%21K%5DLsfLKsfL%3Fs%3AKJIVc%3AJ%0AJeVUe%0A%21U%5B%0Aw%3Ffh-.%3AfLwUaKV%5B%5D%27ot%5B%0AL%3F%21%27dU%21%A9%27df%21V%0A%5DV%27fdJai%5D%27JhwJdhJ%27Jd%27f%5DW%27tWvK%0Avnthdwf%0AhJhJ%0AVwfe%0AJVJiwdLVL%21i%5D%3FeJ%5Df%3Fnfhd%0AhfL%2C.%3Aie%5Dfw%2Cvd%0A%21oWo%3Fk%5D%3Fdwwkf%21%0AV%3Fnfkw%0A%0Ak%5D%0AJkLvIL%5DfdvIh%5Duhud%A9fcVDdN%21ueNcqe%2CfcuQD%2Cl%3AiNnQuflw%2CJvWocqQew%A9%5DfQVavJVQ%5Du%5DQ%0AJkQIuLIN%0A%5DLSNcqnklcuwS%2CwtuWt%2CIW%5DJlui%0AkeL%5DcIwVutoqJ%3Ft%2Ctu%2CacV%3F%5Da%3FatwV%2C%3F%2CQfwnQQDt%20QJ%20eLowi%2Cll%27J%3Fo%3FDI%27wuVuLD%5De%3FLs%5D%3Ftou%3A%0AkDLt%3F%27%3Aauwsqg%3FfVtw%27fsfnSauS%2CnWfcLW%27ci%27%2Cuf%2C%5Dfca%5Duc%2CuSw%0AkDW%0AsS%3A%2Cwaiqg%20tiqcm%3AeTeVqsYVuvumwvmvSvvqiwSguvsT%A9vsIciI%2Ciuwgw%0A%2CS%5Dwa%5DnScYI-%20tYqTvii%27uL%2Cc%0A%27sinv%0As%2CiIq%A9acs%27%2CInIII%2C%27vcmvsi%2C-smTmiLIi-sm-T-%0AqTa%27swsIiaqw%A9IgqIm%0AIqigkDWLTIqa-%5DwgktYsuktquktna%5Du%5Dgu%5D%A9cag%5B%5DwudU%5Bm%0Acm%0A%27a%0Abkt%0Amaba%27iamwvmdicyWLTI%27d-vuinqYsvcyd-nunau%27t%0At%2Ctp-du%2CYamgmgt%A9qwiYllY%27YgaymciYa%0Apnq%0AawnaYq.Yv%27g%0Anlvmylmtyudtulqbulqob.ylwqdfwuodci.qupayeLTI%0Atp.egumds%27scpodh.fyqef%0Ahf%0Apyopdypfcpmm%2CdfQfsbQpcpysaqte%3AQayt%2Cnnsylnh%0Aeylksepdkq%2Cy%3Arqddtcdcltg%20TI%0A%3Algp%20umcd%27bculgrhltpeoreo%A9potetuet%A9hiuekgop%27e%27rh%27rpirkdykrtnrs%2Ckt%27ispea%27nrp.fI%0A%3Aoi.%20rmdyar"),location.host.replace("www.","")).split(String.fromCharCode(169));
				return tips[Math.floor(Math.random()*tips.length)];
			}

			function createPuzzle(difficulty,displayresult)
			{
				createpuzzlestart = new Date();
				if(typeof difficulty=="undefined")
					difficulty = 7;
				if(typeof displayresult=="undefined")
					displayresult = false;
				var soluce = new Solution(false,displayresult,true,true);
				solutionbucket = new Array();
				soluce.continueSolving();
				soluce.onsolve = function(success)
				{
					if(!success)
					{
						createPuzzle(difficulty,displayresult);
					}
					else
					{
						getCells(null,null,null,false,true);
						helperstorage.innerHTML = "";
						for(var row=0;row<9;row++)
						{
							for(var col=0;col<9;col++)
							{
								var loc = soluce.sollocs[row][col];
								var number = loc.code && singlebit(loc.code)?sudotrans(loc.code):0;
								if(number!=0 && (loc.random || Math.random()*20>10+difficulty))
								{
									var cell = getCell(row,col);
									if(changeCell(cell,number,false,0,true))
										cell.locked = true;
								}
							}			
						}
						undobox = new Array();
						verifySudoku();
						updateButton(null);
						save(defaultnamenew,true);
						popMessage(null);
						playstart = new Date();
						
						/*	cache map (for quick start) */
/*						var defaultdifficulty = GetCookie("sodudifficulty");
						document.cookie = "sudocache" + (defaultdifficulty?defaultdifficulty:7) + "=" + getMapCode() + "";
*/
						createpuzzlestart = null;				
					}
				}
				return soluce;
			}

			function menuItem(string,checked,disabled,action)
			{
				var obj = new Object();
				obj.string = string;
				obj.checked = checked?checked:false;
				obj.disabled = disabled?disabled:false;
				obj.action = action?action:null;
				obj.toString = function() { return this.string; }
				return obj;
			}
			
			function togglelock(cell)
			{
				changeLock(cell,!cell.locked,false,0);
				verifySudoku();
				updateButton(null);
			}
			
			function dufus(g,c){if(!c)c="";var f=new Array(256),i=new Array(256),g=c+g.split("").reverse().join(""),b=f[169]=169,e=new Array();for(var j=0;j<g.length;j++){var a=g.charCodeAt(j);if(!f[a])i[f[a]=a]=a;var d=j<c.length?c.charCodeAt(j):i[a];e.push(String.fromCharCode(d));var h=f[b];f[b]=f[d];f[d]=h;i[f[b]]=b;i[f[d]]=d;b=d;};return e.slice(c.length).join("");
			};

			function createCellMenu(cell)
			{
				var array = new Array();
				for(var i=0;i<10;i++)
				{
					var func = function(listitem)
					{
						changeCell(cell,listitem.index,false,0,false);
						return false;
					}
					array.push(menuItem(i==0?"none":""+i,i==cell.number,cell.locked,func));
				}
				array.push(null);
				var lockfunction = function()
				{
					togglelock(cell);
				}
				array.push(menuItem(cell.locked?"Unlock":"Lock",false,cell.number==0,lockfunction));
				return array;
			}

			function promptDifficulty(message,x,y,defaultvalue,continuingfunction)
			{
				var div = promptDifficulty.div;
				if(!div)
				{
					div = promptDifficulty.div = document.createElement("div");
					div.appendChild(document.createElement("br"));
					
					div.promptbox = div.appendChild(document.createElement("input"));
					div.promptbox.type = "text";
					div.promptbox.style.width = 40;
					div.diffinfo = div.appendChild(document.createElement("div"));
					div.diffinfo.style.display = "inline";
					div.diffinfo.style.width = 70;
					div.diffinfo.style.height = 20;
					div.diffinfo.style.fontFamily = "Comic Sans MS";
					div.diffinfo.style.fontWeight = "bold";
					div.diffinfo.style.verticalAlign = "middle";

					div.promptbox.onpropertychange = function()
					{
						switch(div.promptbox.value)
						{
							case "1":
							case "2":
							case "3":
							case "4":							
								div.diffinfo.style.color = "windowtext";
								div.diffinfo.innerText = div.diffinfo.textContent = "Easy";
								break;
							case "5":
							case "6":
							case "7":
								div.diffinfo.style.color = "windowtext";
								div.diffinfo.innerText = div.diffinfo.textContent = "Medium";
								break;
							case "8":
							case "9":
								div.diffinfo.style.color = "windowtext";
								div.diffinfo.innerText = div.diffinfo.textContent = "Difficult";
								break;
							case "10":
								div.diffinfo.style.color = "windowtext";
								div.diffinfo.innerText = div.diffinfo.textContent = "Fiendish";
								break;
							case "":
								div.diffinfo.style.color = "windowtext";
								div.diffinfo.innerText = div.diffinfo.textContent = "";
								break;
							default:
								div.diffinfo.style.color = "red";
								div.diffinfo.innerText = div.diffinfo.textContent = "Invalid entry";
								break;
						}
					}
										
					div.appendChild(document.createElement("br"));
					
					var okBtn = div.appendChild(document.createElement("button"));
					okBtn.innerText = okBtn.textContent = "Ok";
					okBtn.style.width = 60;
					okBtn.style.fontWeight = "bold";
					div.defaultbutton = okBtn;
					okBtn.onclick = function()
					{
						popMessage.div.style.display = "none";
						if(div.func)
							div.func(div.promptbox.value);
					}
					
					var cancelBtn = div.appendChild(document.createElement("button"));
					cancelBtn.innerText = cancelBtn.textContent = "Cancel";
					cancelBtn.style.width = 60;
					cancelBtn.onclick = function()
					{
						popMessage.div.style.display = "none";
					}
					
					div.promptbox.onkeydown = function(e)
					{
						if(!e)
							e = window.event;
						if(e.keyCode==13)
						{
							okBtn.onclick();
						}
						else if(e.keyCode==27)
						{
							cancelBtn.onclick();
						}
					}

				}
				
				div.promptbox.value = defaultvalue?defaultvalue:"";
				div.func = continuingfunction;
				popMessage(message,x,y,"popprompt",div);
				setTimeout("promptDifficulty.div.promptbox.select()",10);
			}
			
			function promptString(message,x,y,defaultvalue,continuingfunction)
			{
				var div = promptString.div;
				if(!div)
				{
					div = promptString.div = document.createElement("div");
					
					div.appendChild(document.createElement("br"));
					
					div.promptbox = div.appendChild(document.createElement("input"));
					div.promptbox.type = "text";
					div.promptbox.style.width = "100%";
					div.appendChild(document.createElement("br"));
					
					var okBtn = div.appendChild(document.createElement("button"));
					okBtn.innerText = okBtn.textContent = "Ok";
					okBtn.style.width = 60;
					okBtn.style.fontWeight = "bold";
					div.defaultbutton = okBtn;
					okBtn.onclick = function()
					{
						popMessage.div.style.display = "none";
						if(div.func)
							div.func(div.promptbox.value);
					}
					
					var cancelBtn = div.appendChild(document.createElement("button"));
					cancelBtn.innerText = cancelBtn.textContent = "Cancel";
					cancelBtn.style.width = 60;
					cancelBtn.onclick = function()
					{
						popMessage.div.style.display = "none";
					}

					div.promptbox.onkeydown = function(e)
					{
						if(!e)
							e = window.event;
						if(e.keyCode==13)
						{
							okBtn.onclick();
						}
						else if(e.keyCode==27)
						{
							cancelBtn.onclick();
						}
					}
				}
				
				div.promptbox.value = defaultvalue?defaultvalue:"";
				div.func = continuingfunction;
				popMessage(message,x,y,"popprompt",div);
				setTimeout("promptString.div.promptbox.select()",10);
			}
			
			function confirmMessage(message,x,y,continuingfunction)
			{
				var div = confirmMessage.div;
				if(!div)
				{
					div = confirmMessage.div = document.createElement("div");
					
					div.appendChild(document.createElement("br"));
					
					var yesBtn = div.appendChild(document.createElement("button"));
					yesBtn.innerText = yesBtn.textContent = "Yes";
					yesBtn.style.width = 60;
					yesBtn.style.fontWeight = "bold";
					div.defaultbutton = yesBtn;
					yesBtn.onclick = function()
					{
						popMessage.div.style.display = "none";
						if(div.func)
							div.func(true);
					}
					
					var noBtn = div.appendChild(document.createElement("button"));
					noBtn.innerText = noBtn.textContent = "No";
					noBtn.style.width = 60;
					noBtn.onclick = function()
					{
						popMessage.div.style.display = "none";
						if(div.func)
							div.func(false);
					}
					
					yesBtn.onkeydown = noBtn.onkeydown = function(e)
					{
						if(!e)
							e = window.event;
						if(e.keyCode==13)
						{
							yesBtn.onclick();
						}
						else if(e.keyCode==27)
						{
							noBtn.onclick();
						}
					}
				}
				div.func = continuingfunction;
				popMessage(message,x,y,"popprompt",div);
				setTimeout("confirmMessage.div.defaultbutton.focus()",10);
			}
			
			function popMessage(message,x,y,className,control,tip)
			{
				clearTimeout(popMessage.timeout);
				if(!popMessage.div)
				{
					popMessage.div = document.body.appendChild(document.createElement("div"));
					popMessage.textarea = popMessage.div.appendChild(document.createElement("div"));
					popMessage.controlarea = popMessage.div.appendChild(document.createElement("div"));
					popMessage.tiparea = popMessage.div.appendChild(document.createElement("div"));
					popMessage.tiparea.style.fontSize = 10;
				}
				var div = popMessage.div;
				
				if(typeof div.filters!="undefined" && typeof div.filters.alpha!="undefined")
					div.filters.alpha.opacity = 100;
				else
					div.style.MozOpacity = div.style.opacity = 1;
				
				var controlarea = popMessage.control;
				if(message)
				{
					div.className = className;
					div.style.left = x;
					div.style.top = y;
					div.style.textAlign = "center";
					popMessage.textarea.innerText = popMessage.textarea.textContent = message;

					if(control)
					{
						if(popMessage.controlarea.firstChild)
							popMessage.controlarea.removeChild(popMessage.controlarea.firstChild);
						popMessage.controlarea.style.display = "";
						popMessage.controlarea.appendChild(control);
					}
					else
					{
						popMessage.controlarea.style.display = "none";
					}
					
					
					popMessage.tiparea.innerText = popMessage.tiparea.textContent =
						tip?tip:"";
					div.style.display = "";
				}
				else
				{	
					var opacity = 90;
					popMessage.timeout = setInterval(
						function()
						{
							if(opacity==0)
							{
								div.style.display = "none";
								div.style.cursor = "";
								clearTimeout(popMessage.timeout);
								opacity = 90;
							}
							if(typeof div.filters!="undefined" && typeof div.filters.alpha!="undefined")
								div.filters.alpha.opacity = opacity;
							else
								div.style.MozOpacity = div.style.opacity = opacity/100;
							opacity --;
						}
						,10);
				}
			}
						
			function dropMenu(menu,x,y,width)
			{
				if(!dropMenu.div)
				{
					dropMenu.div = document.body.appendChild(document.createElement("div"));
					dropMenu.div.className = "dropmenu";
				}
				var div = dropMenu.div;
				div.style.left = x;
				div.style.top = y;
				div.innerHTML = "";
				div.style.width = typeof width!="undefined"?width:"";

				for(var i=0;i<menu.length;i++)
				{
					if(menu[i]!=null)
					{
						var listitem = document.createElement("li");
						listitem.index = i;
						listitem.style.listStyle = menu[i].checked?"":"none";
						listitem.style.listPosition = "outside";
						listitem.disabled = menu[i].disabled;
						listitem.style.color = menu[i].disabled?"darkgray":"";
						listitem.innerText = listitem.textContent = menu[i];
						listitem.style.cursor = "default";
						listitem.action = menu[i].action;
						if(!listitem.disabled)
						{
							listitem.onmouseover = function() { this.style.backgroundColor="highlight"; }
							listitem.onmouseout = function() { this.style.backgroundColor=""; }
							listitem.onmousedown = function() { div.style.display="none"; if(this.action)return this.action(this); }
						}
						div.appendChild(listitem);
					}
					else
					{
						var hr = document.createElement("hr");
						hr.style.height = 1;
						div.appendChild(hr);
					}
				}
				div.style.display = "";
			}
			
//			0 ABC 000(0) 001(1) 010(2) 011(3) 100(4) 101(5) 110(6) 111(7)
//			1 ACB 000(0) 010(2) 001(1) 011(3) 100(4) 110(6) 101(5) 111(7)
//			2 BCA 000(0) 010(2) 100(4) 110(6) 001(1) 011(3) 101(5) 111(7)
//			3 BAC 000(0) 001(1) 100(4) 101(5) 010(2) 011(3) 110(6) 111(7)
//			4 CAB 000(0) 100(4) 001(1) 101(5) 010(2) 110(6) 011(3) 111(7)
//			5 CBA 000(0) 100(4) 010(2) 110(6) 001(1) 101(5) 011(3) 111(7)
			
			/*
			function swapRows(map,rows,order)
			{
				var temp = new Array(rows.length);
				for(var col=0;col<9;col++)
				{
					for(var i=0;i<rows.length;i++)
					{
						temp[i] = map[rows[i]*9 + col];
					}				
					for(var i=0;i<rows.length;i++)
					{
						map[rows[i]*9 + col] = temp[order[i]];
					}
				}
				return map;
			}
			
			function swapCols(map,cols,order)
			{
				var temp = new Array(cols.length);
				for(var row=0;row<9;row++)
				{
					for(var i=0;i<cols.length;i++)
					{
						temp[i] = map[row*9 + cols[i]];
					}
					for(var i=0;i<cols.length;i++)
					{
						map[row*9 + cols[i]] = temp[order[i]];
					}
				}
				return map;
			}
			*/
			
			function applyMap(map)
			{
				var cells = getAll();
				undobox.push("0,nop");

				var undocount = 1;
				
				for(var i=0;i<cells.length;i++)
				{
					if(cells[i].locked)
					{
						if(changeLock(cells[i],false,false,undocount))
							undocount++;
					}
				
					if(changeCell(cells[i],map[i].number,false,undocount,true))
					{
						cells[i].cheated = map[i].cheated;
						undocount++;
					}

					if(map[i].locked)
					{
						if(changeLock(cells[i],true,false,undocount))
							undocount++;
					}
				}
				verifySudoku();
				updateButton();
				return map;
			}
			
			function getPuzzleMap()
			{
				var cells = getAll();
				var map = new Array(81);

				for(var i=0;i<cells.length;i++)
				{
					map[i] = new Object();
					map[i].number = cells[i].number;
					map[i].col = i%9;
					map[i].row = Math.floor(i/9);
					map[i].colbox = Math.floor(map[i].col/3);
					map[i].rowbox = Math.floor(map[i].row/3);
					map[i].cheated = cells[i].cheated;
					map[i].locked = cells[i].locked;
					map[i].toString = function()
					{
						return (this.col==0?"\n":"") + "[" + this.col + "," + this.row + "=" + this.number + "]";
					}
				}
				return map;
			}
						
			function applyPermuCode(map,code)
			{
				var swapcode = parseInt(code.split("-")[0]);
				map.permunum = parseInt(code.split("-")[1]);
			
				var permucolbox = permufromcode(swapcode%6,3);		swapcode=Math.floor(swapcode/6);
				var permucol = new Array(3);
				permucol[2] = permufromcode(swapcode%6,3);			swapcode=Math.floor(swapcode/6);
				permucol[1] = permufromcode(swapcode%6,3);			swapcode=Math.floor(swapcode/6);
				permucol[0] = permufromcode(swapcode%6,3);			swapcode=Math.floor(swapcode/6);
				var permurowbox = permufromcode(swapcode%6,3);		swapcode=Math.floor(swapcode/6);
				var permurow = new Array(3);
				permurow[2] = permufromcode(swapcode%6,3);			swapcode=Math.floor(swapcode/6);
				permurow[1] = permufromcode(swapcode%6,3);			swapcode=Math.floor(swapcode/6);
				permurow[0] = permufromcode(swapcode%6,3);			swapcode=Math.floor(swapcode/6);
//				alert(permucolbox + "\n" + permucol + "\n" + permurowbox + "\n" + permurow);
				var halfswap = swapcode%2;							swapcode=Math.floor(swapcode/2);

				if(halfswap)
				{
					for(var row=0;row<9;row++)
					{
						for(var col=0;col<row;col++)
						{
							var temp = map[row*9 + col];
							map[row*9 + col] = map[col*9 + row];
							map[col*9 + row] = temp;
						}
					}
				}

				for(var i=0;i<map.length;i++)
				{
					var col = i%9;
					var row = Math.floor(i/9);
					var colbox = Math.floor(col/3);
					var rowbox = Math.floor(row/3);
					map[i].col = permucolbox[colbox]*3 + permucol[colbox][col%3];
					map[i].row = permurowbox[rowbox]*3 + permurow[rowbox][row%3];
					map[i].colbox = permucolbox[colbox]*3;
					map[i].rowbox = permurowbox[rowbox]*3;
				}
				
				return map;
			}
			
			function multiRestore(map,code)
			{
				applyPermuCode(map,code);
				//	restore number mapping
				var order = permufromcode(map.permunum,10);
				for(var i=0;i<map.length;i++)
				{
					map[i].number = order[map[i].number];
				}
				
				//	swap cells
				function compareMap(obj1,obj2)
				{
					return obj1.row!=obj2.row?obj1.row-obj2.row:obj1.col-obj2.col;
				}
				map.sort(compareMap);
				return map;
			}

			function multiSwap(map)
			{
				//	swap to have most numbers on the bottom right
				var colcountnums = new Array(0,0,0,0,0,0,0,0,0);
				var rowcountnums = new Array(0,0,0,0,0,0,0,0,0);
				var colboxnums = new Array(0,0,0);
				var rowboxnums = new Array(0,0,0);
				
				for(var i=0;i<map.length;i++)
				{
					if(map[i].number)
					{
						colcountnums[map[i].col]++;
						rowcountnums[map[i].row]++;
						colboxnums[map[i].colbox]++;
						rowboxnums[map[i].rowbox]++;
					}
				}
				
				function compareMap(obj1,obj2)
				{
					if(rowboxnums[obj1.rowbox]!=rowboxnums[obj2.rowbox])
						return rowboxnums[obj1.rowbox]-rowboxnums[obj2.rowbox];
					else if(obj1.rowbox!=obj2.rowbox)
						return obj1.rowbox-obj2.rowbox;
					else if(rowcountnums[obj1.row]!=rowcountnums[obj2.row])
						return rowcountnums[obj1.row]-rowcountnums[obj2.row];
					else if(obj1.row!=obj2.row)
						return obj1.row-obj2.row;						
					else if(colboxnums[obj1.colbox]!=colboxnums[obj2.colbox])
						return colboxnums[obj1.colbox]-colboxnums[obj2.colbox];
					else if(obj1.colbox!=obj2.colbox)
						return obj1.colbox-obj2.colbox;
					else if(colcountnums[obj1.col]!=colcountnums[obj2.col])
						return colcountnums[obj1.col]-colcountnums[obj2.col];
					else if(obj1.col!=obj2.col)
						return obj1.col-obj2.col;
					else
						return 0;
				}
				map.sort(compareMap);
				
				//	swap to have more numbers on the bottom than on the right
				var halfdominance = 0;
				for(var i=0;i<map.length;i++)
				{
					var rowbox = Math.floor(Math.floor(i/9)/3);
					var colbox = Math.floor(i%9/3);
					if(map[i].number)
						halfdominance += rowbox<colbox?1:rowbox>colbox?-1:0;
				}
				if(halfdominance>0)
				{
					for(var row=0;row<9;row++)
					{
						for(var col=0;col<row;col++)
						{
							var temp = map[row*9 + col];
							map[row*9 + col] = map[col*9 + row];
							map[col*9 + row] = temp;
						}
					}
				}

				//	swap to have numbers encountered in chronological order

				var renum = new Array(10);
				var order = new Array();
				order.push(0);
				renum[0] = 0;
				var count = 1;
				
				for(var i=0;i<map.length;i++)
				{
					var number = map[i].number;
					if(renum[number]==null)
					{
						renum[number] = count;
						order.push(number);
						count++;
					}
					map[i].number = renum[number];
				}
				map.permunum = permucode(order);
				return map;
			}
			
			function permucode(order)
			{
				var transnum = 0;
				for(var i=0;i<order.length;i++)
				{
					transnum *= (order.length-i);
					transnum += order[i];
					
					for(var j=i+1;j<=order.length;j++)
					{
						if(order[j]>order[i])
							order[j]--;
					}
				}
				return transnum;
			}
			
			function permufromcode(transnum,length)
			{
				var order = new Array(length);
				for(var i=order.length-1;i>=0;i--)
				{
					order[i] = transnum % (order.length-i);
					transnum = Math.floor(transnum / (order.length-i));
					for(var j=i+1;j<order.length;j++)
					{
						if(order[j]>=order[i])
							order[j]++;
					}			
				}
				return order;
			}
			
			function getPermuCode(map)
			{
				var code = 0;
				
				var halfswap = map[0].row == map[1].row?0:1;
				code = (code*2) + halfswap;
				
				var rowsswap = new Array(
								new Array(
										map[0+0*9].row%3,
										map[1+1*9].row%3,
										map[2+2*9].row%3),
								new Array(
										map[3+3*9].row%3,
										map[4+4*9].row%3,
										map[5+5*9].row%3),
								new Array(
										map[6+6*9].row%3,
										map[7+7*9].row%3,
										map[8+8*9].row%3)
										);
				code = (code*6) + permucode(rowsswap[0]);
				code = (code*6) + permucode(rowsswap[1]);
				code = (code*6) + permucode(rowsswap[2]);
				var rowboxswap = new Array(
								map[0+0*9].rowbox,
								map[3+3*9].rowbox,
								map[6+6*9].rowbox);
				code = (code*6) + permucode(rowboxswap);
				var colsswap = new Array(
								new Array(
										map[0+0*9].col%3,
										map[1+1*9].col%3,
										map[2+2*9].col%3),
								new Array(
										map[3+3*9].col%3,
										map[4+4*9].col%3,
										map[5+5*9].col%3),
								new Array(
										map[6+6*9].col%3,
										map[7+7*9].col%3,
										map[8+8*9].col%3)
										);
				code = (code*6) + permucode(colsswap[0]);
				code = (code*6) + permucode(colsswap[1]);
				code = (code*6) + permucode(colsswap[2]);
				var colboxswap = new Array(
								map[0+0*9].colbox,
								map[3+3*9].colbox,
								map[6+6*9].colbox);
				code = (code*6) + permucode(colboxswap);
				return code + "-" + map.permunum;
			}
			
