// $Id: display.js,v 1.6 2010/08/08 01:53:52 pwh Exp $
//
// Definition of the Sudoku puzzle Display object. -----------------------------
//
//   Private Methods -----------------------------------------------------------
//
//     this.setChkptTime()		Display checkpoint saved time.
//     this.enableCheckpoints()		Enable checkpoints.
//     this.disableCheckpoints()	Disable checkpoints.
//     this.clearShowDigits ( enable )	Clear the Show Digits flags and enable
//					or disable the checkboxes.
//     this.resetState ()		Reset to start state.
//     this.setInactiveState ()		Set state to inactive.
//     this.setSolvingState ()		Set state to solving.
//     this.setTile ()			Select a tile for a cell.
//     this.clear ()			Clear or reset the display.
//     this.getSolution ()		Get solution from server.
//     this.showSolution ()		Display the solution.
//     this.buildRequest ()		Build the request for solution.
//     this.checkSolution ()		Check validity of solution.
//     this.flashError ( row, column,	Flash wrong digit(s) when diplaying the
//        oldDigit, newDigit, repeat )	solution.
//
//   Sundry tests. -------------------------------------------------------------
//
//     this.isEmpty ( row, column )	Test if a cell is empty.
//     this.isClue ( row, column )	Test if a cell contains a clue.
//     this.isGuess ( row, column )	Test if a cell contains a guess.
//     this.isFull ()			Test if the grid is full.
//     this.isSolved ()			Test if the puzzle is solved.
//     this.isMarkup ( digit )		Test if the digit is marked up in the
//					current cell.
//     this.getDigit ( row, column )	Get the contents of a cell.
//
//   Control of error indicators. ----------------------------------------------
//
//     this.clearErrors ()		Turn off redundant clues warnings.
//     this.checkErrors ( digit )	Highlight redundant clues.

//   Key pad control. ----------------------------------------------------------
//
//     this.keyOn ( digit )		Highlight a button in a key pad.
//     this.keyOff ( digit )		Return a button in a key pad to normal.
//     this.hideKeyPad ()		Hide the current key pad.
//     this.showKeyPad ( row, column )	Display a key pad.
//
//   Grid control. -------------------------------------------------------------
//
//     this.clearDigit ()		Clear the contents of a cell.
//     this.setDigit ( digit )		Set the contents of a cell.
//
//   Markup control. -----------------------------------------------------------
//
//     this.setMarkup ( digit )		Set a markup digit.
//     this.clearMarkup ( digit )	Clear a markup digit.
//     this.autofill ()			Autofill the markup digits.
//     this.autoclear ()		Autoclear the markup digits.
//
//   Options and state control. ------------------------------------------------
//
//     this.saveCheckpoint ()		Save a checkpoint.
//     this.restoreCheckpoint ()	Restore a checkpoint.
//     this.toggleShowErrors ()		Show Errors checkbox changed state.
//     this.toggleDigits ( digit )	Show Digits checkbox changed state.
//     this.reset ()			Reset button clicked.
//     this.clearGuesses ()		Clear button clicked.
//     this.solveIt ()			Solve it button clicked.
//
//   The Display object (read only flags) --------------------------------------
//
//     this.solving			True when all the clues are entered.
//     this.inactive			Only Reset and Clear buttons active.
//     this.showDigits [1-9]		Markup digits to highlight.
//     this.showErrors			Warn if duplicate digit entry.
//
// -----------------------------------------------------------------------------


// Private Methods -------------------------------------------------------------

// (PRIVATE) display checkpoint saved time.
function dspSetChkptTime ()

{
   var hour = this.checkpoints.date.getHours ()
   var minute = this.checkpoints.date.getMinutes ()
   var second = this.checkpoints.date.getSeconds ()
   var am_pm = "AM"

   if ( hour > 11 ) {

	am_pm = "PM"
	hour -= 12

   }

   if ( hour == 0 ) hour = 12

   if ( minute < 10 ) minute = "0" + minute

   if ( second < 10 ) second =  "0" + second

   // List the checkpoint on the display.
   document.getElementById ( "chkptText" ).innerHTML = "Saved " + hour
				+ ":" + minute + ":" + second + " " + am_pm
}

// (PRIVATE) Enable checkpoints.
function dspEnableChkpts ()

{
   document.getElementById ( "setCheck" ).disabled = false
   if ( this.checkpoints ) document.getElementById ( "restoreCheck" ).disabled
									= false
   document.getElementById ( "chkptText" ).style.color = "black"
}


// (PRIVATE) Disable checkpoints.
function dspDisableChkpts ()

{
   document.getElementById ( "setCheck" ).disabled = true
   document.getElementById ( "restoreCheck" ).disabled = true
   document.getElementById ( "chkptText" ).style.color = "gray"
}


// (PRIVATE) Clear the Show Digits flags.
function dspClearShowDigits ( enable )

{
   document.getElementById ( "showDigitsLbl" ).style.color = ( enable  ?
							"#0055FF" : "gray" )

   for ( var i = 1; i < 10; i++ ) {

	var showDigitBox = document.getElementById ( "show" + i )

	showDigitBox.disabled = ! enable
	showDigitBox.checked = false
	this.toggleDigits ( i )

	document.getElementById ( "Lbl" + i ).style.color = ( enable ?
							"black" : "gray" )
   }
}


// (PRIVATE) Reset state.
function dspResetState ()

{
   this.solving = false

   this.hideKeyPad ()

   var	difficulty = document.getElementById ( "difficulty" )

   difficulty.style.color = "black"
   difficulty.innerHTML = ""
   document.getElementById ( "technique" ).innerHTML = ""

   document.getElementById ( "clearBoard" ).disabled = true
   this.disableCheckpoints ()
   this.checkpoints = null
   document.getElementById ( "chkptText" ).innerHTML = "No checkpoint saved"
   document.getElementById ( "autofill" ).disabled = true
   document.getElementById ( "clearMarkup" ).disabled = true
   document.getElementById ( "showErrors" ).disabled = true
   document.getElementById ( "showErrorsLbl" ).style.color = "gray"

   this.clearShowDigits ( false )

   document.getElementById ( "instructions2" ).style.display = "none"
   document.getElementById ( "instructions1" ).style.display = "block"

   var solveIt = document.getElementById ( "SolveIt" )

   solveIt.value = "Begin"
   solveIt.disabled = false
   document.getElementById ( "clock" ).innerHTML = ""
   this.timer.reset ()
}


// (PRIVATE) Set state to inactive.
function dspSetInactiveState ()

{
   this.inactive = true
   this.disableCheckpoints ()
   document.getElementById ( "autofill" ).disabled = true
   document.getElementById ( "clearMarkup" ).disabled = true
   document.getElementById ( "showErrors" ).disabled = true
   document.getElementById ( "showErrorsLbl" ).style.color = "gray"

   this.clearShowDigits ( false )

   document.getElementById ( "instructions1" ).style.display = "none"
   document.getElementById ( "instructions2" ).style.display = "none"

   document.getElementById ( "SolveIt" ).disabled = true
}


// (PRIVATE) Set state to solving.
function dspSetSolvingState ()

{
   this.solving = true
   document.getElementById ( "clearBoard" ).disabled = false
   this.enableCheckpoints ()
   document.getElementById ( "autofill" ).disabled = false
   document.getElementById ( "clearMarkup" ).disabled = false
   document.getElementById ( "showErrors" ).disabled = false
   document.getElementById ( "showErrorsLbl" ).style.color = "black"

   this.clearShowDigits ( true )

   document.getElementById ( "instructions1" ).style.display = "none"
   document.getElementById ( "instructions2" ).style.display = "block"

   var solveIt = document.getElementById ( "SolveIt" )

   solveIt.value = "Solve It"
   solveIt.disabled = false
}


// (PRIVATE) select a tile.
function dspSetTile ( row, column )

{
   var digit = this.getDigit ( row, column )
   var clue = this.isClue ( row, column )
   var error = ( ( ! this.solving || this.showErrors )
						&& this.error [row][column] )

   document.getElementById ( "tile" + row + column ).src = "images/"
	+ ( digit ? ( ( clue ? ( error ? "clueError-" : "Clue-" )
		: ( error ? "digitError-" : "Digit-" ) ) + digit )
		: "Clear" ) + ".gif"
}


// (PRIVATE) Clear the display.
function dspClear ()

{
   for ( var row = 1; row < 10; row++ ) {

	this.kpRow = row

	for ( var column = 1; column < 10; column++ ) {

		this.kpColumn = column

		this.clearDigit ()

		for ( var digit = 1; digit < 10; digit++ )
						this.clearMarkup ( digit )

		if ( ! this.isClue ( row, column ) ) document.getElementById
			( "tile" + row + column ).src = "images/Clear.gif"
	}
   }

   this.kpRow = 0
   this.kpColumn = 0
}


// (PRIVATE) Flash the wrong digit when displaying the solution.
function dspFlashError ( row, column, oldDigit, newDigit, repeat )

{
   if ( repeat % 2 )
   document.getElementById ( "tile" + row + column ).src = "images/Digit-"
							+ newDigit + ".gif"
   else
   document.getElementById ( "tile" + row + column ).src = "images/digitError-"
							+ oldDigit + ".gif"
   if ( ++repeat < 10 )
   setTimeout ( this.name + ".flashError(" + row + "," + column + ","
		+ oldDigit + "," + newDigit + "," + repeat + ")", 500 )
}


// Sundry tests. ---------------------------------------------------------------

// Test if a cell is empty.
function dspIsEmpty ( row, column )

{
   return ( this.grid [row][column] == 0 )
}


// Test if a cell contains a clue.
function dspIsClue ( row, column )

{
   return ( this.grid [row][column] < 0 )
}


// Test if a cell contains a guess.
function dspIsGuess ( row, column )

{
   return ( this.grid [row][column] > 0 )
}


// Test if the grid is full.
function dspIsFull ()

{
   return ( ! this.empty )
}


// Test if the puzzle is solved.
function dspIsSolved ()

{
   return ( ! this.unsolved )
}


// Test if the digit is marked up in the current cell.
function dspIsMarkup ( digit )

{
   var marked = false
   var row = this.kpRow
   var column = this.kpColumn

   if ( row && column ) marked = this.markup [row][column][digit]

   return ( marked )
}


// Get the contents of a cell.
function dspGetDigit ( row, column )

{
   var digit = 0

   if ( row && column ) {

	if ( this.grid [row][column] < 0 ) digit = -this.grid [row][column]
	else digit = this.grid [row][column]
   }

   return ( digit )
}


// Control of error indicators. ------------------------------------------------

// Turn off redundant clues warnings.
function dspClearErrors ()

{
   var row = this.errorRow
   var column = this.errorColumn
   var digit = this.errorDigit

   if ( digit && row && column ) {

	var block = this.block [row][column]

	// Check row for redundant clues.
	var i = this.nextColumn [row][0][digit]

	if ( i ) {

		this.error [row][i] = 0
		this.setTile ( row, i )
	}

	// Check column for redundant clues.
	i = this.nextRow [0][column][digit]

	if ( i ) {

		this.error [i][column] = 0
		this.setTile ( i, column )
	}

	// Check block for redundant clues.
	i = this.nextOffset [block][0][digit]

	if ( i ) {

		var r = this.row [block][i]
		var c = this.column [block][i]

		this.error [r][c] = 0
		this.setTile ( r, c )
	}

	this.errorDigit = 0
	this.errorRow = 0
	this.errorColumn = 0
   }
}


// Highlight redundant clues.
function dspCheckErrors ( digit )

{
   var errors = ( this.errorDigit != 0 )
   var row = this.kpRow
   var column = this.kpColumn

   if ( ! this.solving && ( this.errorDigit != digit ) && row && column ) {

	var block = this.block [row][column]

	this.clearErrors ()
	errors = false

	// Check row for redundant clues.
	var i = this.nextColumn [row][0][digit]

	if ( i ) {

		errors = true
		this.error [row][i] = 1
		this.setTile ( row, i )
	}

	// Check column for redundant clues.
	i = this.nextRow [0][column][digit]

	if ( i ) {

		errors = true
		this.error [i][column] = 1
		this.setTile ( i, column )
	}

	// Check block for redundant clues.
	i = this.nextOffset [block][0][digit]

	if ( i ) {

		var r = this.row [block][i]
		var c = this.column [block][i]

		errors = true
		this.error [r][c] = 1
		this.setTile ( r, c )
	}

	if ( errors ) {

		this.errorDigit = digit
		this.errorRow = row
		this.errorColumn = column
	}
   }

   return ( errors )
}


// Key pad control. ------------------------------------------------------------

// Highlight a button in a key pad.
function dspKeyOn ( digit )

{
   var row = this.kpRow
   var column = this.kpColumn

   if ( row && column ) {

	var keyPadImg = document.getElementById ( "keyPad" + row + column
								+ digit )
	keyPadImg.src = keyPadImg.src.replace ( /images\/sm/, "images/bg" )
   }
}


// Return a button in a key pad to normal.
function dspKeyOff ( digit )

{
   var row = this.kpRow
   var column = this.kpColumn

   if ( row && column ) {

	var keyPadImg = document.getElementById ( "keyPad" + row + column
								+ digit )
	keyPadImg.src = keyPadImg.src.replace ( /images\/bg/, "images/sm" )
   }
}


// Hide the current key pad.
function dspHideKeyPad ()

{
   var row = this.kpRow
   var column = this.kpColumn

   if ( this.offGrid ) {

	clearTimeout ( this.offGrid )
	this.offGrid = null
   }

   if ( row && column ) {

	this.clearErrors ()
	document.getElementById ( "tile" + row + column ).style.zIndex = 5
	document.getElementById ( "floor" + row + column ).style.zIndex = 3
	this.kpRow = 0
	this.kpColumn = 0
   }
}


// Display a key pad.
function dspShowKeyPad ( row, column )

{
   if ( ! this.inactive
		&& ( ! this.solving || ! this.isClue ( row, column ) ) ) {

	this.hideKeyPad ()

	document.getElementById ( "floor" + row + column ).style.zIndex = 0
	document.getElementById ( "tile" + row + column ).style.zIndex = -1
	this.kpRow = row
	this.kpColumn = column
   }
}


// Grid control. ---------------------------------------------------------------

// Clear the contents of a cell.
function dspClearDigit ()

{
   var success = false
   var row = this.kpRow
   var column = this.kpColumn
   var digit = this.getDigit ( row, column )


   if ( digit && ( ! this.solving || this.isGuess ( row, column ) ) ) {

	if ( ! this.solving ) this.clearErrors ()

	var keyPadImg = document.getElementById ( "keyPad" + row + column
								+ digit )
	var block = this.block [row][column]
	var offset = this.offset [row][column]

	// Remove digit from row list.
	var next = this.nextColumn [row][column]
	var last = this.lastColumn [row][column]

	if ( next ) {

		this.lastColumn [row][next] = last

		// Update error indicators.
		if ( ! last ) {

			if ( ! --this.error [row][next] )  {

				--this.unsolved

				if ( this.showErrors ) this.setTile ( row,
									next )
			}
		}

	} else this.lastColumn [row][0][digit] = last

	if ( last ) {

		this.nextColumn [row][last] = next

		// Update error indicators.
		if ( ! next ) {

			if ( ! --this.error [row][last] ) {

				--this.unsolved

				if ( this.showErrors ) this.setTile ( row,
									last )
			}
		}

	} else this.nextColumn [row][0][digit] = next

	// Insert empty cell in row available list.
	next = this.nextColumn [row][0][0]

	this.nextColumn [row][column] = next
	this.lastColumn [row][column] = 0

	this.nextColumn [row][0][0] = column

	if ( next ) this.lastColumn [row][next] = column
	else this.lastColumn [row][0][0] = column

	// Remove digit from column list.
	next = this.nextRow [row][column]
	last = this.lastRow [row][column]

	if ( next ) {

		this.lastRow [next][column] = last

		// Update error indicators.
		if ( ! last ) {

			if ( ! --this.error [next][column] ) {

				--this.unsolved
				if ( this.showErrors ) this.setTile ( next,
									column )
			}
		}

	} else this.lastRow [0][column][digit] = last

	if ( last ) {

		this.nextRow [last][column] = next

		// Update error indicators.
		if ( ! next ) {

			if ( ! --this.error [last][column] ) {

				--this.unsolved
				if ( this.showErrors ) this.setTile ( last,
									column )
			}
		}

	} else this.nextRow [0][column][digit] = next

	// Insert empty cell in column available list.
	next = this.nextRow [0][column][0]

	this.nextRow [row][column] = next
	this.lastRow [row][column] = 0

	this.nextRow [0][column][0] = row

	if ( next ) this.lastRow [next][column] = row
	else this.lastRow [0][column][0] = row

	// Remove digit from block list.
	next = this.nextOffset [row][column]
	last = this.lastOffset [row][column]

	if ( next ) {

		var r = this.row [block][next]
		var c = this.column [block][next]

		this.lastOffset [r][c] = last

		// Update error indicators.
		if ( ! last ) {

			if ( ! --this.error [r][c] ) {

				--this.unsolved
				if ( this.showErrors ) this.setTile ( r, c )
			}
		}

	} else this.lastOffset [block][0][digit] = last

	if ( last ) {

		var r = this.row [block][last]
		var c = this.column [block][last]

		this.nextOffset [r][c] = next

		// Update error indicators.
		if ( ! next ) {

			if ( ! --this.error [r][c] ) {

				--this.unsolved
				if ( this.showErrors ) this.setTile ( r, c )
			}
		}

	} else this.nextOffset [block][0][digit] = next

	// Insert empty cell in block available list.
	next = this.nextOffset [block][0][0]

	this.nextOffset [row][column] = next
	this.lastOffset [row][column] = 0

	this.nextOffset [block][0][0] = offset

	if ( next ) this.lastOffset [this.row [block][next]]
				[this.column [block][next]] = offset
	else this.lastOffset [block][0][0] = offset

	if ( this.error [row][column] ) this.error [row][column] = 0
	else ++this.unsolved

	++this.empty

	this.grid [row][column] = 0

	keyPadImg.src = keyPadImg.src.replace ( /Black/,
				( this.isMarkup ( digit ) ? "Red" : "Blue" ) )
	this.setTile ( row, column )

	success = true
   }

   return ( success )
}


// Set the contents of a cell.
function dspSetDigit ( digit )

{
   var row = this.kpRow
   var column = this.kpColumn
   var success = ( row && column && digit )
   var newDigit = digit

   if ( success && ! this.solving ) {

	newDigit = -digit

	// Check if clue is already in the row, column or block.
	if ( this.checkErrors ( digit ) ) success = false
   }

   // Insert digit in lists.
   if ( success ) {

	var keyPadImg = document.getElementById ( "keyPad" + row + column
								+ digit )
	var block = this.block [row][column]
	var offset = this.offset [row][column]

	this.clearDigit ()
	this.grid [row][column] = newDigit
	--this.unsolved
	--this.empty

	// Delete digit from available row list.
	var next = this.nextColumn [row][column]
	var last = this.lastColumn [row][column]

	if ( next ) this.lastColumn [row][next] = last
	else this.lastColumn [row][0][0] = last

	if ( last ) this.nextColumn [row][last] = next
	else this.nextColumn [row][0][0] = next

	// Insert digit in row list.
	next = this.nextColumn [row][0][digit]

	this.nextColumn [row][column] = next
	this.lastColumn [row][column] = 0
	
	this.nextColumn [row][0][digit] = column

	if ( next ) {

		this.lastColumn [row][next] = column
		if ( ! this.error [row][column]++ ) ++this.unsolved

		// Update error indicators.
		if ( ! this.error [row][next]++ ) {

			++this.unsolved
			if ( this.showErrors ) this.setTile ( row, next )
		}

	} else this.lastColumn [row][0][digit] = column

	// Delete digit from available column list.
	var next = this.nextRow [row][column]
	var last = this.lastRow [row][column]

	if ( next ) this.lastRow [next][column] = last
	else this.lastRow [0][column][0] = last

	if ( last ) this.nextRow [last][column] = next
	else this.nextRow [0][column][0] = next

	// Insert digit in column list.
	next = this.nextRow [0][column][digit]

	this.nextRow [row][column] = next
	this.lastRow [row][column] = 0

	this.nextRow [0][column][digit] = row

	if ( next ) {

		this.lastRow [next][column] = row
		if ( ! this.error [row][column]++ ) ++this.unsolved

		// Update error indicators.
		if ( ! this.error [next][column]++ ) {

			++this.unsolved
			if ( this.showErrors ) this.setTile ( next, column )
		}

	} else this.lastRow [0][column][digit] = row

	// Delete digit from available block list.
	var next = this.nextOffset [row][column]
	var last = this.lastOffset [row][column]

	if ( next ) this.lastOffset [this.row [block][next]]
					[this.column [block][next]] = last
	else this.lastOffset [block][0][0] = last

	if ( last ) this.nextOffset [this.row [block][last]]
					[this.column [block][last]] = next
	else this.nextOffset [block][0][0] = next

	// Insert in block list.
	next = this.nextOffset [block][0][digit]

	this.nextOffset [row][column] = next
	this.lastOffset [row][column] = 0

	this.nextOffset [block][0][digit] = offset

	if ( next ) {

		var r = this.row [block][next]
		var c = this.column [block][next]

		if ( ! this.error [row][column]++ ) ++this.unsolved
		this.lastOffset [r][c] = offset

		// Update error indicators.
		if ( ! this.error [r][c]++ ) {

			++this.unsolved
			if ( this.showErrors ) this.setTile ( r, c )
		}

	} else this.lastOffset [block][0][digit] = offset

	keyPadImg.src = keyPadImg.src.replace ( /(Red)|(Blue)/, "Black" )
	this.setTile ( row, column )
   }

   if ( ! this.unsolved ) {

	this.timer.stop ()
	this.setInactiveState ()
   }

   return ( success )
}


// Markup control. -------------------------------------------------------------

// Set a markup digit.
function dspSetMarkup ( digit )

{
   var row = this.kpRow
   var column = this.kpColumn

   if ( this.solving && row && column && ! this.markup [row][column][digit] ) {

	var keyPadImg = document.getElementById ( "keyPad" + row + column
								+ digit )
	this.markup [row][column][digit] = true
	++this.markup [row][column][0]
	keyPadImg.src = keyPadImg.src.replace ( /Blue/, "Red" )
	keyPadImg.style.zIndex = 4

	if ( this.showDigits [digit]
				&& ( ++this.highlights [row][column] == 1 ) ) {

		document.getElementById ( "floor" + row + column ).src
							= "images/highlight.gif"
	}
   }
}


// Clear a markup digit.
function dspClearMarkup ( digit )

{
   var row = this.kpRow
   var column = this.kpColumn

   if ( row && column && this.markup [row][column][digit] ) {

	var keyPadImg = document.getElementById ( "keyPad" + row + column
								+ digit )
	this.markup [row][column][digit] = false
	--this.markup [row][column][0]
	keyPadImg.src = keyPadImg.src.replace ( /Red/, "Blue" )
	keyPadImg.style.zIndex = 2

	if ( this.showDigits [digit]
				&& ( --this.highlights [row][column] == 0 ) ) {

		document.getElementById ( "floor" + row + column ).src
							= "images/Blank.gif"
	}
   }
}


// Autofill the markup digits.
function dspAutofill ()

{
   if ( this.solving ) {

	var oldKProw = this.kpRow
	var oldKPcolumn = this.kpColumn

	this.inactive = true			// Temporarily disable input.

	for ( var row = 1; row < 10; row++ ) {

		this.kpRow = row

		for ( var column = this.nextColumn [row][0][0]; column;
				column = this.nextColumn [row][column] ) {

			var block = this.block [row][column]

			this.kpColumn = column

			for ( var digit = 1; digit < 10; digit++ ) {

				if ( this.nextColumn[row][0][digit]
					|| this.nextRow[0][column][digit]
					|| this.nextOffset[block][0][digit] )
						this.clearMarkup ( digit )
				else this.setMarkup ( digit )
			}
		}
	}

	this.kpRow = oldKProw
	this.kpColumn = oldKPcolumn
	this.inactive = false			// Reenable input.
   }
}


// Autoclear the markup digits.
function dspAutoclear ()

{
   if ( this.solving ) {

	var oldKProw = this.kpRow
	var oldKPcolumn = this.kpColumn

	this.inactive = true			// Temporarily disable input.

	for ( var row = 1; row < 10; row++ ) {

		this.kpRow = row

		for ( var column = this.nextColumn [row][0][0]; column;
				column = this.nextColumn [row][column] ) {

			this.kpColumn = column

			for ( var digit = 1; digit < 10; digit++ )
						this.clearMarkup ( digit )
		}
	}

	this.kpRow = oldKProw
	this.kpColumn = oldKPcolumn
	this.inactive = false			// Reenable input.
   }
}


// Options and state control. --------------------------------------------------

// Save a checkpoint.
function dspSaveCheckpoint ()

{

   if ( ! this.checkpoints )
		document.getElementById ( "restoreCheck" ).disabled = false

   this.checkpoints = new Checkpoint ( this )

   this.setChkptTime ()
}


// Restore a checkpoint.
function dspRestoreCheckpoint ()

{
   this.checkpoints.pop( this )

   if ( ! this.checkpoints ) {

	document.getElementById ( "restoreCheck" ).disabled = true
	document.getElementById ( "chkptText" ).innerHTML
							= "No checkpoint saved"

   } else this.setChkptTime ()
}


// Toggle the showErrors flag.
function dspToggleShowErrors ()

{
   this.showErrors = document.getElementById ( "showErrors" ).checked

   this.inactive = true				// Temporarily disable input.

   setCookie ( "showErrors", this.showErrors, 30 )

   for ( var row = 1; row < 10; row++ ) {

	for ( var column = 1; column < 10; column++ ) {

		if ( this.error [row][column] ) this.setTile ( row, column )
	}
   }

   this.inactive = false			// Reenable input.
}


// Toggle a Show Digits digit.
function dspToggleDigits ( digit )

{
   var oldShow = this.showDigits [digit]
   var newShow = document.getElementById ( "show" + digit ).checked


   if ( oldShow != newShow ) {

	this.inactive = true			// Temporarily disable input.

	this.showDigits [digit] = newShow

	for ( var row = 1; row < 10; row++ ) {

		for ( var column = 1; column < 10; column++ ) {

			if ( this.markup [row][column][digit] ) {

				if ( newShow ) {

					if ( ++this.highlights [row][column]
									== 1 )
						document.getElementById
							( "floor" + row
							+ column ).src
							= "images/highlight.gif"
				} else {

					if ( --this.highlights [row][column]
									== 0 )
						document.getElementById
							( "floor" + row
							+ column ).src
							= "images/Blank.gif"
				}
			}
		}
	}

	this.inactive = false			// Reenable input.
   }
}


// Reset the board.
function dspReset ()

{
   this.inactive = true				// Temporarily disable input.

   this.solutions = 0
   this.difficulty = 0
   this.technique = null

   this.resetState ()

   this.clear ()

   this.inactive = false			// Reenable input.
}


// Clear the guesses.
function dspClearGuesses ()

{
   this.inactive = true				// Temporarily disable input.
   this.hideKeyPad ()
   this.setSolvingState ()

   this.clear ()

   this.inactive = false			// Reenable input.
}


function dspCheckSolution ()

{
   var status = 1
   var digits = new Array ()
   var i = 1

   digits [0] = 1

   // Check the rows.
   while ( status && i < 10 ) {

	var j = 1

	// Clear the digits array.
	while ( j < 10 ) {

		digits [j] = 0
		++j
	}

	j = 1

	while ( j < 10 ) {

		var digit = this.solution [i][j]
		var oldDigit = this.getDigit ( i, j )

		if ( digits [digit] || ( oldDigit && digit != oldDigit ) ) {

			status = 0
			break

		} else digits [digit] = 1

		++j
	}

	++i
   }

   // Check the columns.
   i = 1
   while ( status && i < 10 ) {

	var j = 1

	// Clear the digits array.
	digits [0] = 1
	while ( j < 10 ) {

		digits [j] = 0
		++j
	}

	j = 1

	while ( j < 10 ) {

		var digit = this.solution [j][i]

		if ( digits [digit] ) {

			status = 0
			break

		} else digits [digit] = 1

		++j
	}

	++i
   }

   // Check the blocks.
   i = 1
   while ( status && i < 10 ) {

	var j = 1

	// Clear the digits array.
	digits [0] = 1
	while ( j < 10 ) {

		digits [j] = 0
		++j
	}

	j = 1

	while ( j < 10 ) {

		var digit = this.solution [this.row [i][j]][this.column [i][j]]

		if ( digits [digit] ) {

			status = 0
			break

		} else digits [digit] = 1

		++j
	}

	++i
   }

   return ( status )
}


function dspBuildRequest ()

{
   var request = "clues="

   for ( var row = 1; row < 10; row++ ) {

	for ( var column = 1; column < 10; column++ ) {

		request += this.getDigit ( row, column )
	}
   }

   return ( request )
}


function dspGetSolution ()

{
   var difficulty = document.getElementById ( "difficulty" )
   difficulty.style.color = "red"
   var connection = GetXmlHttpObject ( "difficulty" )
   var solutions = -1

   if ( connection ) {

	var request = this.buildRequest ()

	connection.open ( "POST", "/cgi-bin/sudoku.cgi", false )
	connection.setRequestHeader ( "Content-Type",
					"application/x-www-form-urlencoded" )
	connection.send ( request )

	if ( connection.status != 200 ) {

		difficulty.innerHTML = connection.statusText

	} else {

		var lines = connection.responseText.split ( "\n" )
		var lastLine = lines.length

		if ( lastLine > 0 ) {

			solutions = parseInt ( lines [0] , 10 )

			if ( isNaN ( solutions ) ) {

				solutions = -1

			} else if ( solutions > 0 ) {

				var row = 1
				var i = 3

				if ( lastLine > 1 ) this.difficulty
						= parseInt ( lines[1], 10 )

				if ( lastLine > 2 ) this.technique = lines [2]

				while ( row < 10 ) {

					var j = 0
					var column = 1
					var lineLength = ( i < lastLine
							? lines [i].length
							: 0 )

					while ( column < 10 ) {

						if ( j < lineLength ) {

							var digit = parseInt
							( lines [i].substring
								( j, j + 1 ) )

							if ( ! isNaN
								( digit ) ) {

								this.solution
								[row][column]
									= digit
								++column
							}

						} else this.solution
							[row][column] = 0

						++j
					}

					++row
					++i
				}

				if ( ! this.checkSolution () ) solutions = -1
			}
		}

		if ( solutions < 0 ) difficulty.innerHTML
		= "Invalid response from the server, no solution available."
		else difficulty.style.color = "black"
	}
   }

   return ( solutions )
}


function dspShowSolution ()

{
   // Clear the guesses.
   for ( var row = 1; row < 10; row++ ) {

	this.kpRow = row

	for ( var column = 1; column < 10; column++ ) {

		this.kpColumn = column

		if ( ! this.isClue ( row, column ) ) {

			var oldDigit = this.getDigit ( row, column )
			var newDigit = this.solution [row][column]

			this.clearDigit ()

			if ( oldDigit && oldDigit != newDigit ) {

				this.flashError ( row, column, oldDigit,
								newDigit, 0 )

			} else document.getElementById ( "tile" + row
					+ column ).src = "images/Digit-"
					+ newDigit + ".gif"
		}
	}
   }
  
   this.kpRow = 0
   this.kpColumn = 0
}


// Solve it button clicked.
function dspSolveIt ()

{
   if ( ! this.inactive ) {

	if ( ! this.solving ) {

		this.inactive = true		// Temporarily disable input.
		this.solutions = 0

		if ( this.unsolved < 81 ) this.solutions = this.getSolution ()

		if ( this.solutions < 0 ) {

			if ( confirm ( "Unable to get a solution.  Click OK to try to solve it on your own." ) ) this.setSolvingState ()

		} else if ( this.solutions == 0 ) {

			if ( this.unsolved < 81 )
				alert ( "This puzzle has no solution." )
			else alert ( "Please enter some clues!" )

		} else if ( this.solutions == 1
			|| confirm ( "There is more than 1 solution to this puzzle.  Click Cancel to continue editing the clues." ) ) {

			if ( this.solutions == 1 ) document.getElementById
						( "difficulty" ).innerHTML
				= "Difficulty score: " + this.difficulty
			document.getElementById ( "technique" ).innerHTML
				= "Technique level: " + this.technique
			this.setSolvingState ()
			this.timer.start ()
		}

		this.inactive = false		// Reenable input.

	} else {

		if ( this.solutions > 0 ) {

			this.setInactiveState ()
			this.showSolution ()

		} else alert ( "No solution available." )
	}
   }
}


// The Display object ----------------------------------------------------------

function Display ( var_name )

{
   var cookie = getCookie ( "showErrors" )
   var block = 1
   var offset = 1
   var rowOffset = 0

   this.name = var_name
   this.timer = new Timer ( this.name + ".timer" )
   this.offGrid = null
   this.solutions = 0			// Number of solutions for the puzzle.
   this.difficulty = 0			// Difficulty score.
   this.technique = null		// Technical difficulty level.
   this.solving = false			// Are we solving, or entering clues?
   this.inactive = false		// Is the puzzle inactive?
   this.unsolved = 81			// Number of unsolved cells.
   this.empty = 81			// Number of empty cells.
   this.showDigits = new Array()	// Highlight cells with these digits
					// marked up.
   if ( cookie && cookie == "true" )	// Highlight duplicate digits in same
	this.showErrors = true		// row, column and/or block.
   else this.showErrors = false

   // Location of last displayed key pad, so we can hide it when the cursor
   // moves off of it.
   this.kpRow = 0
   this.kpColumn = 0

   this.errorDigit = 0			// Redundant clues.
   this.errorRow = 0
   this.errorColumn = 0

   this.grid = new Array()		// The grid.
   this.solution = new Array()		// The solution.

   this.row = new Array()		// Convert ( block, offset ) to
   this.column = new Array()		// ( row, column )

   this.block = new Array()		// Convert ( row, column ) to
   this.offset = new Array()		// ( block, offset )

   this.markup = new Array()		// The markup gids.
   this.highlights = new Array()	// This cell is highlighted.
   this.error = new Array()		// Highligted errors.

   // Linked lists, keeping track of duplicate digits.
   this.nextRow = new Array()
   this.nextRow [0] = new Array()
   this.lastRow = new Array()
   this.lastRow [0] = new Array()
   this.nextColumn = new Array()
   this.lastColumn = new Array()
   this.nextOffset = new Array()
   this.lastOffset = new Array()

   this.checkpoints = null

   // Initialize the arrays and lists.
   for ( var i = 1; i < 10; i++ ) {

	var columnOffset = 0

	this.showDigits [i] = false

	this.grid [i] = new Array()
	this.solution [i] = new Array()

	this.row [i] = new Array()
	this.column [i] = new Array()

	this.block [i] = new Array()
	this.offset [i] = new Array()

	this.markup [i] = new Array()
	this.highlights [i] = new Array()
	this.error [i] = new Array()

	this.nextRow [i] = new Array()
	this.nextRow [0][i] = new Array()
	this.nextRow [0][i][0] = 1
	this.lastRow [i] = new Array()
	this.lastRow [0][i] = new Array()
	this.lastRow [0][i][0] = 9
	this.nextColumn [i] = new Array()
	this.nextColumn [i][0] = new Array()
	this.nextColumn [i][0][0] = 1
	this.lastColumn [i] = new Array()
	this.lastColumn [i][0] = new Array()
	this.lastColumn [i][0][0] = 9
	this.nextOffset [i] = new Array()
	this.nextOffset [i][0] = new Array()
	this.nextOffset [i][0][0] = 1
	this.lastOffset [i] = new Array()
	this.lastOffset [i][0] = new Array()
	this.lastOffset [i][0][0] = 9

	for ( var j = 1; j < 10; j++ ) {

		this.grid [i][j] = 0
		this.solution [i][j] = 0

		this.block [i][j] = block
		this.offset [i][j] = offset

		this.markup [i][j] = new Array()
		this.markup [i][j][0] = 0
		this.highlights [i][j] = 0
		this.error [i][j] = 0

		this.nextRow [i][j] = i + 1
		this.nextRow [0][i][j] = 0
		this.lastRow [i][j] = i - 1
		this.lastRow [0][i][j] = 0
		this.nextColumn [i][j] = j + 1
		this.nextColumn [i][0][j] = 0
		this.lastColumn [i][j] = j - 1
		this.lastColumn [i][0][j] = 0
		this.nextOffset [i][j] = offset + 1
		this.nextOffset [i][0][j] = 0
		this.lastOffset [i][j] = offset - 1
		this.lastOffset [i][0][j] = 0

		for ( var k = 1; k < 10; k++ ) {

			this.markup [i][j][k] = false
		}

		if ( ++columnOffset == 3 ) {

			columnOffset = 0
			++block
			offset -= 2

		} else ++offset
	}

	if ( ++rowOffset == 3 ) {

		rowOffset = 0
		offset = 1

	} else {

		block -= 3
		offset += 3
	}
   }

   // Initialize the row and column translation arrays.
   for ( var row = 1; row < 10; row++ ) {

	for ( var column = 1; column < 10; column++ ) {

		block = this.block [row][column]
		offset = this.offset [row][column]

		this.row [block][offset] = row
		this.column [block][offset] = column
	}
   }

   // Correct the last members of the available lists.
   for ( var i = 1; i < 10; i++ ) {

	var r = this.row [i][9]
	var c = this.column [i][9]

	this.nextRow [9][i] = 0
	this.nextColumn [i][9] = 0
	this.nextOffset [r][c] = 0
   }

   // Private Methods.
   this.setChkptTime = dspSetChkptTime
   this.enableCheckpoints = dspEnableChkpts
   this.disableCheckpoints = dspDisableChkpts
   this.clearShowDigits = dspClearShowDigits
   this.resetState = dspResetState
   this.setInactiveState = dspSetInactiveState
   this.setSolvingState = dspSetSolvingState
   this.setTile = dspSetTile
   this.clear = dspClear
   this.getSolution = dspGetSolution
   this.showSolution = dspShowSolution
   this.buildRequest = dspBuildRequest
   this.checkSolution = dspCheckSolution
   this.flashError = dspFlashError

   // Public Methods.
   this.isEmpty = dspIsEmpty			// Is the cell empty?
   this.isClue = dspIsClue			// Does the cell contain a clue?
   this.isGuess = dspIsGuess			// Does the cell contain a
						// guess?
   this.isFull = dspIsFull			// Are all the cells filled?
   this.isSolved = dspIsSolved			// Are we done?
   this.isMarkup = dspIsMarkup			// Test for markup digits.
   this.getDigit = dspGetDigit			// Get the contents of a cell.
   this.clearErrors = dspClearErrors		// Clear error indicators.
   this.checkErrors = dspCheckErrors		// Check for errors and set
						// error indicators.
   this.clearDigit = dspClearDigit		// Clear digit from a cell.
   this.setDigit = dspSetDigit			// Set digit in a cell.
   this.setMarkup = dspSetMarkup		// Set a markup digit.
   this.clearMarkup = dspClearMarkup		// Clear a markup digit.
   this.autofill = dspAutofill			// Auto-fill the markup digits.
   this.autoclear = dspAutoclear		// Clear all markup digits.
   this.showKeyPad = dspShowKeyPad		// Show a key pad over a cell.
   this.hideKeyPad = dspHideKeyPad		// Hide the key pad.
   this.keyOn = dspKeyOn			// Highlight a key.
   this.keyOff = dspKeyOff			// Turn of highlighted key.
   this.reset = dspReset
   this.clearGuesses = dspClearGuesses
   this.saveCheckpoint = dspSaveCheckpoint
   this.restoreCheckpoint = dspRestoreCheckpoint
   this.toggleShowErrors = dspToggleShowErrors
   this.toggleDigits = dspToggleDigits
   this.solveIt = dspSolveIt

   this.resetState ()

   // Make sure the document mirrors the display object setting.
   document.getElementById ( "showErrors" ).checked = this.showErrors
}
