// Sudoku Puzzle of the Day
// (c) 2005 Mark Huckvale - University College London
//
// global variables
var sd_ccell;						// current cell for cursor
var sd_board=new Array(81);		// board on display
var sd_digit=new Array('1','2','3','4','5','6','7','8','9','*','*','*','*','*','*',' ');
var sd_digit_mask=0x0000000F;		// displayed digit mask
var sd_solve_mask=0x000000F0;		// solution digit mask
var sd_allow_mask=0x0001FF00;		// digits allowed in this cell mask
var sd_locks_mask=0x00020000;		// cell locked mask
var sd_allow_bits=new Array(0x00100,0x00200,0x00400,0x00800,0x01000,0x02000,0x04000,0x08000,0x10000);
var sd_lock_flag=0x00020000;		// cell locked
var sd_box_index=new Array(9);		// look-up table for boxes
var sd_col_index=new Array(9);		// look-up table for columns
var sd_row_index=new Array(9);		// look-up table for rows
var sd_solved;						// flag if solution found
var sd_colorbg="#ffffff";
var sd_colorfg="#000000";
var sd_colorcs="#ffeedd";
var sd_colorhn="#ddeeff";

var sd_animthread=null;
var sd_animtrigger=0;
var sd_spiral=new Array(0,1,2,3,4,5,6,7,8,17,26,35,44,53,62,71,80,79,78,77,76,75,74,73,72,63,54,45,36,27,18,9,10,11,12,13,14,15,16,25,34,43,52,61,70,69,68,67,66,65,64,55,46,37,28,19,20,21,22,23,24,33,42,51,60,59,58,57,56,47,38,29,30,31,32,41,50,49,48,39,40);
var sd_target0=new Array(40,40);
var sd_target1=new Array(30,31,32,41,50,49,48,39);
var sd_target2=new Array(20,21,22,23,24,33,42,51,60,59,58,57,56,47,38,29);
var sd_target3=new Array(10,11,12,13,14,15,16,25,34,43,52,61,70,69,68,67,66,65,64,55,46,37,28,19);
var sd_target4=new Array(0,1,2,3,4,5,6,7,8,17,26,35,44,53,62,71,80,79,78,77,76,75,74,73,72,63,54,45,36,27,18,9);

// random number generator
var sd_today=new Date();
var sd_months=new Array('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec');
var sd_seed=0;
function sd_rnd() {
	sd_seed = (sd_seed*9301+49297) % 233280;
	return sd_seed/(233280.0);
};
function sd_rand(number) {
	return Math.floor(sd_rnd()*number);
};

// clear back to empty
function sd_doclear()
{
	var i;

	for (i=0;i<81;i++) {
		var pfield=document.getElementById("c"+i);
		pfield.className="sd_digit";
		pfield.style.backgroundColor=sd_colorbg;
		pfield.value=' ';
		sd_board[i]=sd_allow_mask | sd_solve_mask | sd_digit_mask;
	}
	sd_solved=0;
	sd_seed = 10000*sd_today.getFullYear()+100*sd_today.getMonth()+sd_today.getDate();
}

// once only initialisation
function sd_initialise()
{
	// initialise quick indexes
	sd_box_index[0]=new Array(0,1,2,9,10,11,18,19,20);
	sd_box_index[1]=new Array(3,4,5,12,13,14,21,22,23);
	sd_box_index[2]=new Array(6,7,8,15,16,17,24,25,26);
	sd_box_index[3]=new Array(27,28,29,36,37,38,45,46,47);
	sd_box_index[4]=new Array(30,31,32,39,40,41,48,49,50);
	sd_box_index[5]=new Array(33,34,35,42,43,44,51,52,53);
	sd_box_index[6]=new Array(54,55,56,63,64,65,72,73,74);
	sd_box_index[7]=new Array(57,58,59,66,67,68,75,76,77);
	sd_box_index[8]=new Array(60,61,62,69,70,71,78,79,80);
	sd_row_index[0]=new Array(0,1,2,3,4,5,6,7,8);
	sd_row_index[1]=new Array(9,10,11,12,13,14,15,16,17);
	sd_row_index[2]=new Array(18,19,20,21,22,23,24,25,26);
	sd_row_index[3]=new Array(27,28,29,30,31,32,33,34,35);
	sd_row_index[4]=new Array(36,37,38,39,40,41,42,43,44);
	sd_row_index[5]=new Array(45,46,47,48,49,50,51,52,53);
	sd_row_index[6]=new Array(54,55,56,57,58,59,60,61,62);
	sd_row_index[7]=new Array(63,64,65,66,67,68,69,70,71);
	sd_row_index[8]=new Array(72,73,74,75,76,77,78,79,80);
	sd_col_index[0]=new Array(0,9,18,27,36,45,54,63,72);
	sd_col_index[1]=new Array(1,10,19,28,37,46,55,64,73);
	sd_col_index[2]=new Array(2,11,20,29,38,47,56,65,74);
	sd_col_index[3]=new Array(3,12,21,30,39,48,57,66,75);
	sd_col_index[4]=new Array(4,13,22,31,40,49,58,67,76);
	sd_col_index[5]=new Array(5,14,23,32,41,50,59,68,77);
	sd_col_index[6]=new Array(6,15,24,33,42,51,60,69,78);
	sd_col_index[7]=new Array(7,16,25,34,43,52,61,70,79);
	sd_col_index[8]=new Array(8,17,26,35,44,53,62,71,80);

	// clear board
	sd_docreate();

	var pfield=document.getElementById("sd_title");
	pfield.value="Puzzle of "+sd_today.getDate()+" "+sd_months[sd_today.getMonth()]+" "+sd_today.getFullYear();

	// restore from cookie
	sd_restoreboard();

	// display cursor
	sd_ccell=0;
	var pfield=document.getElementById("c"+sd_ccell);
	if (sd_board[sd_ccell]&sd_lock_flag)
		pfield.className="sd_digitlock";
	else
		pfield.className="sd_digit";
	pfield.style.backgroundColor=sd_colorcs;
	pfield.focus();

	// trap key presses
	document.onkeydown=sd_kbdhandler;

}

// get row index from cell number
function sd_getrow(cell)
{
	return(Math.floor(cell/9));
}

// get column from cell number
function sd_getcol(cell)
{
	return(Math.floor(cell%9));
}

// get box from cell number
function sd_getbox(cell)
{
	var row=sd_getrow(cell);
	var col=sd_getcol(cell);

	return(Math.floor(3*Math.floor(row/3)+Math.floor(col/3)));
}

// move cursor to cell
function sd_cell(cc)
{
	var pfield=document.getElementById("c"+sd_ccell);
	if (sd_board[sd_ccell]&sd_lock_flag)
		pfield.className="sd_digitlock";
	else
		pfield.className="sd_digit";
	pfield.style.backgroundColor=sd_colorbg;
	sd_ccell=cc;
	pfield=document.getElementById("c"+sd_ccell);
	if (sd_board[sd_ccell]&sd_lock_flag)
		pfield.className="sd_digitlock";
	else
		pfield.className="sd_digit";
	pfield.style.backgroundColor=sd_colorcs;
	pfield.focus();
}

// move hint highlight to cell
function sd_cellblink(cc)
{
	var pfield=document.getElementById("c"+sd_ccell);
	if (sd_board[sd_ccell]&sd_lock_flag)
		pfield.className="sd_digitlock";
	else
		pfield.className="sd_digit";
	pfield.style.backgroundColor=sd_colorbg;
	sd_ccell=cc;
	var pfield=document.getElementById("c"+sd_ccell);
	if (sd_board[sd_ccell]&sd_lock_flag)
		pfield.className="sd_digitlock";
	else
		pfield.className="sd_digit";
	pfield.style.backgroundColor=sd_colorhn;
	pfield.focus();
}

// for testing only - comment out if not used
function sd_doanim()
{
	sd_animthread=setInterval("sd_animate()",60);
	setTimeout("sd_noanimate()",20000);
	sd_animtrigger=0;
}

// enter a digit into current cell
function sd_dodigit(num)
{
	var pfield=document.getElementById("c"+sd_ccell);
	if (sd_checklegal(sd_board,sd_ccell,num)&&!(sd_board[sd_ccell]&sd_lock_flag)) {
		pfield.value=sd_digit[num];
		sd_board[sd_ccell] = (sd_board[sd_ccell] & ~sd_digit_mask) | num;
		sd_solved=0;
		if (sd_animtrigger && sd_checksolution()) {
			sd_animthread=setInterval("sd_animate()",60);
			setTimeout("sd_noanimate()",20000);
			sd_animtrigger=0;
		}
	}
	else if (!(sd_board[sd_ccell]&sd_lock_flag)) {
		pfield.value=' ';
		sd_board[sd_ccell] |= sd_digit_mask;
		sd_solved=0;
		sd_dobeep();
	}
	sd_cell(sd_ccell);
}

// enter a digit from keyboard
function sd_kbdhandler(e)
{
	var pfield=document.getElementById("c"+sd_ccell);

	if (!e) e=window.event;
	var chr = String.fromCharCode(e.keyCode);

	if (('1'<=chr)&&(chr <='9')) {
		var d=chr.charCodeAt(0)-49;
		sd_dodigit(d);
	}
	else if (('a'<=chr)&&(chr <='i')) {
		var d=chr.charCodeAt(0)-97;
		sd_dodigit(d);
	}
	else if ((chr==' ')||(e.keyCode==46)||(e.keyCode==8)) {
		if ((sd_board[sd_ccell]&sd_lock_flag)==0) {
			pfield.value=' ';
			sd_board[sd_ccell] |= sd_digit_mask;
			sd_cell(sd_ccell);
			sd_solved=0;
		}
	}
	else if (chr=='\x25') {
		// left arrow
		var cc=sd_ccell-1;
		if (cc<0) {
			cc += 81;
		}
		sd_cell(cc);
	}
	else if (chr=='\x26') {
		// up arrow
		var cc=sd_ccell-9;
		if (cc<0) {
			if (cc==-9)
				cc = 80;
			else
				cc += 80;
		}
		sd_cell(cc);
	}
	else if (chr=='\x27') {
		// right arrow
		var cc=sd_ccell+1;
		if (cc>80) {
			cc -= 81;
		}
		sd_cell(cc);
	}
	else if (chr=='\x28') {
		// down arrow
		var cc=sd_ccell+9;
		if (cc > 80) {
			if (cc==89)
				cc=0;
			else
				cc -= 80;
		}
		sd_cell(cc);
	}
//	else if (chr==0)
//		alert("Got key code="+e.keyCode);
//	else
//		alert("Got key code="+e.keyCode+" char="+chr);
	pfield.focus();
}

// check user input is allowed
// note that we allow users to enter incorrect digits
// that happen to still be legal according to the rules
function sd_checklegal(board,cc,d)
{
	var hold=board[cc];
	var i,j;
	board[cc] |= sd_digit_mask;

	// check row
	var row=sd_getrow(cc);
	for (j=0;j<9;j++)
		if ((board[sd_row_index[row][j]]&sd_digit_mask)==d) {
			board[cc]=hold;
			return(0);
		}

	// check column
	var col=sd_getcol(cc);
	for (j=0;j<9;j++)
		if ((board[sd_col_index[col][j]]&sd_digit_mask)==d) {
			board[cc]=hold;
			return(0);
		}

	// check box
	var box=sd_getbox(cc);
	for (j=0;j<9;j++)
		if ((board[sd_box_index[box][j]]&sd_digit_mask)==d) {
			board[cc]=hold;
			return(0);
		}

	// OK
	board[cc]=hold;
	return(1);
}

// play a warning beep
function sd_dobeep() {
	//var thissound=document.getElementById("sd_beep");
	//thissound.Play();
}

// check solution
function sd_checksolution()
{
	var	i,sum;

	// check rows
	for (i=0;i<9;i++) {
		sum=0;
		for (j=0;j<9;j++) sum += sd_board[sd_row_index[i][j]] & sd_digit_mask;
		if (sum!=36) return(0);
	}
	// check cols
	for (i=0;i<9;i++) {
		sum=0;
		for (j=0;j<9;j++) sum += sd_board[sd_col_index[i][j]] & sd_digit_mask;
		if (sum!=36) return(0);
	}
	// check boxes
	for (i=0;i<9;i++) {
		sum=0;
		for (j=0;j<9;j++) sum += sd_board[sd_box_index[i][j]] & sd_digit_mask;
		if (sum!=36) return(0);
	}
	return(1);
}

// set a digit into a board cell
function sd_setdigit(cc,num)
{
	var pfield=document.getElementById("c"+cc);
	if (sd_board[cc] & sd_lock_flag)
		pfield.className="sd_digitlock";
	else
		pfield.className="sd_digit";
	pfield.value=sd_digit[num];
	sd_board[cc] = (sd_board[cc] & ~sd_digit_mask) | num;
}

// check the contents of all boxes
function sd_boxcheck(board)
{
	var i,j,d,sum,last,last2,last3;

	for (i=0;i<9;i++) {
		for (d=0;d<9;d++) {
			sum=0;
			last=-1;
			last2=-1;
			last3=-1;
			for (j=0;j<9;j++) {
				if (board[sd_box_index[i][j]]&sd_allow_bits[d]) {
					sum++;
					last3=last2;
					last2=last;
					last=sd_box_index[i][j];
				}
				else
					sum += ((board[sd_box_index[i][j]] & sd_solve_mask)==(d << 4)) ? 1: 0;
			}
			// if #possible is 0 then fail
			if (sum==0) return(0);
			// if #possible is 1 then fix it in solution
			if ((sum==1)&&(last>=0)) {
				if (!sd_setallowed(board,last,d)) return(0);
			}
			// if #possible is 2 then if both on same row, can't be elsewhere on row
			if ((sum==2)&&(last>=0)&&(last2>=0)&&(sd_getrow(last)==sd_getrow(last2))) {
				for (j=0;j<9;j++) {
					var c=sd_row_index[sd_getrow(last)][j];
					if ((c!=last)&&(c!=last2)) {
						if (board[c]&sd_allow_bits[d]) {
							board[c] &= ~sd_allow_bits[d];
							if ((board[c]&sd_allow_mask)==0) return(0);
						}
					}
				}
			}
			// if #possible is 2 then if both on same col, can't be elsewhere on col
			if ((sum==2)&&(last>=0)&&(last2>=0)&&(sd_getcol(last)==sd_getcol(last2))) {
				for (j=0;j<9;j++) {
					var c=sd_col_index[sd_getcol(last)][j];
					if ((c!=last)&&(c!=last2)) {
						if (board[c]&sd_allow_bits[d]) {
							board[c] &= ~sd_allow_bits[d];
							if ((board[c]&sd_allow_mask)==0) return(0);
						}
					}
				}
			}
			// if #possible is 3 then if all on same row, can't be elsewhere on row
			if ((sum==3)&&(last>=0)&&(last2>=0)&&(last3>=0)&&(sd_getrow(last)==sd_getrow(last2))&&(sd_getrow(last)==sd_getrow(last3))) {
				for (j=0;j<9;j++) {
					c=sd_row_index[sd_getrow(last)][j];
					if ((c!=last)&&(c!=last2)&&(c!=last3)) {
						if (board[c]&sd_allow_bits[d]) {
							board[c] &= ~sd_allow_bits[d];
							if ((board[c]&sd_allow_mask)==0) return(0);
						}
					}
				}
			}
			// if #possible is 3 then if all on same col, can't be elsewhere on col
			if ((sum==3)&&(last>=0)&&(last2>=0)&&(last3>=0)&&(sd_getcol(last)==sd_getcol(last2))&&(sd_getcol(last)==sd_getcol(last3))) {
				for (j=0;j<9;j++) {
					c=sd_col_index[sd_getcol(last)][j];
					if ((c!=last)&&(c!=last2)&&(c!=last3)) {
						if (board[c]&sd_allow_bits[d]) {
							board[c] &= ~sd_allow_bits[d];
							if ((board[c]&sd_allow_mask)==0) return(0);
						}
					}
				}
			}
		}
	}
	return(1);
}

// check the contents of all rows
function sd_rowcheck(board)
{
	var i,j,d,sum,last;

	for (i=0;i<9;i++) {
		for (d=0;d<9;d++) {
			sum=0;
			last=-1;
			for (j=0;j<9;j++) {
				if (board[sd_row_index[i][j]]&sd_allow_bits[d]) {
					sum++;
					last=j;
				}
				else
					sum += ((board[sd_row_index[i][j]] & sd_solve_mask)==(d << 4)) ? 1: 0;
			}
			if (sum==0) return(0);
			if ((sum==1)&&(last>=0)) {
				if (!sd_setallowed(board,sd_row_index[i][last],d)) return(0);
			}
		}
	}
	return(1);
}

// check the contents of all columns
function sd_colcheck(board)
{
	var i,j,d,sum,last;

	for (i=0;i<9;i++) {
		for (d=0;d<9;d++) {
			sum=0;
			last=-1;
			for (j=0;j<9;j++) {
				if (board[sd_col_index[i][j]]&sd_allow_bits[d]) {
					sum++;
					last=j;
				}
				else
					sum += ((board[sd_col_index[i][j]] & sd_solve_mask)==(d << 4)) ? 1: 0;
			}
			if (sum==0) return(0);
			if ((sum==1)&&(last>=0)) {
				if (!sd_setallowed(board,sd_col_index[i][last],d)) return(0);
			}
		}
	}
	return(1);
}

// set a digit into a board, clearing allowed flags elsewhere
function sd_setallowed(board,cc,num)
{
	var i,j,k,l,d;
	var s;

	// clear alternatives in this cell
	board[cc] &= ~sd_allow_mask;
	// and set value as solution
	board[cc] = (board[cc] & ~sd_solve_mask) | (num << 4);

	// clear this digit on same row
	var row=sd_getrow(cc);
	for (j=0;j<9;j++) {
		if (board[sd_row_index[row][j]]&sd_allow_bits[num]) {
			board[sd_row_index[row][j]] &= ~sd_allow_bits[num];
			if ((board[sd_row_index[row][j]]&sd_allow_mask)==0) return(0);
		}
	}

	// clear this digit on same column
	var col=sd_getcol(cc);
	for (j=0;j<9;j++) {
		if (board[sd_col_index[col][j]]&sd_allow_bits[num]) {
			board[sd_col_index[col][j]] &= ~sd_allow_bits[num];
			if ((board[sd_col_index[col][j]]&sd_allow_mask)==0) return(0);
		}
	}

	// clear this digit in same box
	var box=sd_getbox(cc);
	for (j=0;j<9;j++) {
		if (board[sd_box_index[box][j]]&sd_allow_bits[num]) {
			board[sd_box_index[box][j]] &= ~sd_allow_bits[num];
			if ((board[sd_box_index[box][j]]&sd_allow_mask)==0) return(0);
		}
	}

	// process all singletons created by setting
	for (i=0;i<81;i++)
		for (d=0;d<9;d++)
			if ((board[i]&sd_allow_mask)==sd_allow_bits[d])
				if (!sd_setallowed(board,i,d)) return(0);

	// check each digit still available in each box, row and column
	if (!sd_boxcheck(board)||!sd_rowcheck(board)||!sd_colcheck(board))
		return(0);

	// process all singletons created by setting (double check)
	for (i=0;i<81;i++)
		for (d=0;d<9;d++)
			if ((board[i]&sd_allow_mask)==sd_allow_bits[d])
				if (!sd_setallowed(board,i,d)) return(0);

	// process all singletons created by setting (double check)
	for (i=0;i<81;i++)
		for (d=0;d<9;d++)
			if ((board[i]&sd_allow_mask)==sd_allow_bits[d])
				if (!sd_setallowed(board,i,d)) return(0);

	return(1);
}

// check if board is a solution
function sd_chksolved(board)
{
	var i;
	var nchoices=0;

	// count # choices left
	for (i=0;i<81;i++)
		if ((board[i]&sd_allow_mask)!=0)
				nchoices++;

	// solved?
	if (nchoices==0) sd_solved=1;

	// OK
	return(sd_solved);
}

// debug variable to test depth of search
var sd_maxlevel;

// attempt to solve from this situation
function sd_attempt(pboard,level)
{
	var board=new Array(81);
	var i,j,k;
	var s,e;

	// check for runaway
	if (level > sd_maxlevel) {
		sd_maxlevel=level;
		// alert("Level="+level);
	}
	if (level > 25) return;		// probably gone wrong

	// pick starting square at random
	i=s=sd_rand(81);
	do {
	  if ((pboard[i]&sd_allow_mask)!=0) {
		// pick starting digit at random
		j=e=sd_rand(9);
		do {
		  if (pboard[i]&sd_allow_bits[j]) {
			// push current solution
			for (k=0;k<81;k++) board[k]=pboard[k];
			// try out digit
			if (sd_setallowed(board,i,j)) {
				board[i] = (board[i] & ~sd_digit_mask) | j;
				if (sd_chksolved(board)) {
					for (k=0;k<81;k++) pboard[k]=board[k];
					return;
				}
				// recurse
				sd_attempt(board,level+1);
				if (sd_chksolved(board)) {
					for (k=0;k<81;k++) pboard[k]=board[k];
					return;
				}
				// no good
				board[i] |= sd_digit_mask;
				if (level > 2) return;	// don't both searching anymore
			}
		  }
		  j=(j+1)%9;
		} while (j!=e);
	  }
	  i=(i+1)%81;
	} while (i!=s);
}

// create a new initial configuration
function sd_docreate()
{
	var i,d;

	do {
		// clear the board
		sd_doclear();
		sd_solved=0;
		sd_maxlevel=0;

		// assign one of each digit at random
		for (d=0;d<9;d++) {
			i=sd_rand(81);
			if (sd_board[i]&sd_allow_bits[d]) {
				sd_setallowed(sd_board,i,d);
				sd_board[i] = (sd_board[i] & ~sd_digit_mask) | d;
			}
		}

		// attempt to solve from here
		sd_attempt(sd_board,0);

		// display result
		for (i=0;i<81;i++) {
			if ((0<=(sd_board[i]&sd_digit_mask))&&((sd_board[i]&sd_digit_mask) < 9)) sd_board[i] |= sd_lock_flag;
			sd_setdigit(i,sd_board[i]&sd_digit_mask);
		}

		if (!sd_solved)
			alert("Failed to make puzzle\nTrying again.");

	} while (!sd_solved);

	// display cursor
	sd_ccell=0;
	var pfield=document.getElementById("c"+sd_ccell);
	if (sd_board[sd_ccell]&sd_lock_flag)
		pfield.className="sd_digitlock";
	else
		pfield.className="sd_digit";
	pfield.style.backgroundColor=sd_colorcs;
	pfield.focus();

	// set animation trigger
	sd_animtrigger=1;
}

// find a possible move in a board
function sd_findmove(board)
{
	var i,d;
	var s;

	// pick starting square at random
	i=s=sd_rand(81);
	do {
		if (!(board[i]&sd_allow_mask)) {
			d=(board[i]&sd_solve_mask)>>4;
			if ((board[i]&sd_digit_mask)!=d) return(i);
		}
		i=(i+1)%81;
	} while (i!=s);
	// failed
	return(-1);
}

// give a hint
function sd_dohint()
{
	var tboard=new Array(81);
	var i,j,k,l,d;
	var s,t;

	// reset work arrays
	for (i=0;i<81;i++)
		tboard[i]=sd_allow_mask | sd_solve_mask | sd_digit_mask;

	// add in current cells
	s=-1;
	i=t=sd_rand(81);
	do {
		c=sd_board[i] & sd_digit_mask;
		if ((0<=c)&&(c<9)) {
			tboard[i] = (tboard[i] & ~sd_digit_mask) | c;
			if (!sd_setallowed(tboard,i,c)) {
				alert("Current position in error\nno hint possible");
				return;
			}
			if ((s=sd_findmove(tboard))>=0) {
				if ((sd_board[s]&sd_digit_mask)==sd_digit_mask) break;
			}
		}
		i=(i+1)%81;
	} while (i!=t);

	if (s>=0) {
		sd_cellblink(s);
	}
	else {
		alert("No hint available.");
		sd_cell(sd_ccell);
	}
}

// find and display complete solution
function sd_dosolution(show)
{
	var pboard=new Array(81);
	var	i,c;

	if (show) {

		if (!sd_solved) {
			// reset solution
			for (i=0;i<81;i++) pboard[i] = sd_allow_mask | sd_solve_mask | sd_digit_mask;
			// add in current cells
			for (i=0;i<81;i++) {
				c=sd_board[i] & sd_digit_mask;
				if ((0<=c)&&(c<9)) {
					if (!sd_setallowed(pboard,i,c)) {
						alert("Current position in error\nno solution possible");
						return;
					}
				}
			}
			// attempt solution
			sd_attempt(pboard,0);

			// copy solution into main board
			for (i=0;i<81;i++)
				sd_board[i] = (sd_board[i] & ~sd_solve_mask) | (pboard[i] & sd_solve_mask);
		}

		// display solution
		for (i=0;i<81;i++) {
			var pfield=document.getElementById("c"+i);
			pfield.value=sd_digit[(sd_board[i]&sd_solve_mask)>>4];
		}
	}
	else {
		// restore board contents
		for (i=0;i<81;i++) {
			var pfield=document.getElementById("c"+i);
			pfield.value=sd_digit[sd_board[i]&sd_digit_mask];
		}
	}
}

// save board to cookie on user's machine
function sd_saveboard()
{
	var s=sd_board.join("|");
	var exp = new Date();
	exp.setHours(23);
	exp.setMinutes(59);
	exp.setSeconds(59);
	document.cookie = "sudoku="+s+"; expires=" + exp.toGMTString();
}

// get a variable from a cookie string
function sd_getCookieData(labelName)
{
	var labelLen = labelName.length;
	var cookieData = document.cookie;
	var cLen = cookieData.length;
	var i = 0;
	var cEnd;
	while (i < cLen) {
		var j = i + labelLen;
		if (cookieData.substring(i,j) == labelName) {
			cEnd = cookieData.indexOf(";",j);
			if (cEnd == -1) {
				cEnd = cookieData.length;
			}
			return unescape(cookieData.substring(j+1, cEnd));
		}
		i++;
	}
	return "";
}

// restore board from cookie on user's machine
function sd_restoreboard()
{
	var s=sd_getCookieData("sudoku");
	var a=s.split("|");
	if (a.length==81) {
		var i;
		for (i=0;i<81;i++) {
			sd_board[i]=a[i];
			sd_setdigit(i,sd_board[i]&sd_digit_mask);
		}
	}
}

// animation state
var sd_state=0;

// animate a pattern of colours
function sd_animate()
{
	var	i,j;
	var	colours=new Array("#ffffcc", "#ffccff","#ccffff",sd_colorbg);

	if (sd_state < 81) {
		i=sd_spiral[(sd_state+80)%81];
		j=sd_spiral[sd_state];
		var pfield=document.getElementById("c"+i);
		pfield.style.backgroundColor=sd_colorbg;
		pfield=document.getElementById("c"+j);
		pfield.style.backgroundColor="#eecccc";
		pfield=document.getElementById("c"+(80-i));
		pfield.style.backgroundColor=sd_colorbg;
		pfield=document.getElementById("c"+(80-j));
		pfield.style.backgroundColor="#cceecc";
	}
	else {
		clearInterval(sd_animthread);
		sd_animthread=setInterval("sd_animate()",100);
		for (i=0;i<sd_target0.length;i++) {
			j=sd_target0[i];
			var pfield=document.getElementById("c"+j);
			pfield.style.backgroundColor=colours[sd_state%4];
		}
		for (i=0;i<sd_target1.length;i++) {
			j=sd_target1[i];
			var pfield=document.getElementById("c"+j);
			pfield.style.backgroundColor=colours[(sd_state+1)%4];
		}
		for (i=0;i<sd_target2.length;i++) {
			j=sd_target2[i];
			var pfield=document.getElementById("c"+j);
			pfield.style.backgroundColor=colours[(sd_state+2)%4];
		}
		for (i=0;i<sd_target3.length;i++) {
			j=sd_target3[i];
			var pfield=document.getElementById("c"+j);
			pfield.style.backgroundColor=colours[(sd_state+3)%4];
		}
		for (i=0;i<sd_target4.length;i++) {
			j=sd_target4[i];
			var pfield=document.getElementById("c"+j);
			pfield.style.backgroundColor=colours[(sd_state+4)%4];
		}
	}

	sd_state = sd_state+1;
}

// cancel animation
function sd_noanimate()
{
	if (sd_animthread) {
		clearInterval(sd_animthread);
		sd_animthread=null;
	}
	for (i=0;i<81;i++) {
		var pfield=document.getElementById("c"+i);
		pfield.style.backgroundColor=sd_colorbg;
		pfield.className="sd_digit";
	}
	sd_state=0;
}

