function HardSolution ()

{
   this.moves = new Array ()
   this.solution = new Array ()

   this.moves [0] = new Array (	1, 0, 0, 0,
				0, 1, 1, 0,
				0, 1, 0, 1,
				0, 0, 1, 0 )
   this.moves [1] = new Array (	1, 0, 1, 1,
				1, 0, 0, 0,
				1, 0, 0, 0,
				0, 1, 1, 1 )
   this.moves [2] = new Array (	1, 1, 0, 1,
				0, 0, 0, 1,
				0, 0, 0, 1,
				1, 1, 1, 0 )
   this.moves [3] = new Array (	0, 0, 0, 1,
				0, 1, 1, 0,
				1, 0, 1, 0,
				0, 1, 0, 0 )
   this.moves [4] = new Array ( 1, 1, 1, 0,
				0, 0, 0, 1,
				1, 0, 0, 1,
				1, 0, 0, 1 )
   this.moves [5] = new Array ( 0, 0, 0, 1,
				0, 1, 1, 1,
				0, 1, 0, 0,
				1, 1, 0, 0 )
   this.moves [6] = new Array ( 1, 0, 0, 0,
				1, 1, 1, 0,
				0, 0, 1, 0,
				0, 0, 1, 1 )
   this.moves [7] = new Array ( 0, 1, 1, 1,
				1, 0, 0, 0,
				1, 0, 0, 1,
				1, 0, 0, 1 )
   this.moves [8] = new Array ( 1, 0, 0, 1,
				1, 0, 0, 1,
				0, 0, 0, 1,
				1, 1, 1, 0 )
   this.moves [9] = new Array ( 1, 1, 0, 0,
				0, 1, 0, 0,
				0, 1, 1, 1,
				0, 0, 0, 1 )
   this.moves [10] = new Array(	0, 0, 1, 1,
				0, 0, 1, 0,
				1, 1, 1, 0,
				1, 0, 0, 0 )
   this.moves [11] = new Array(	1, 0, 0, 1,
				1, 0, 0, 1,
				1, 0, 0, 0,
				0, 1, 1, 1 )
   this.moves [12] = new Array(	0, 0, 1, 0,
				0, 1, 0, 1,
				0, 1, 1, 0,
				1, 0, 0, 0 )
   this.moves [13] = new Array(	0, 1, 1, 1,
				1, 0, 0, 0,
				1, 0, 0, 0,
				1, 0, 1, 1 )
   this.moves [14] = new Array(	1, 1, 1, 0,
				0, 0, 0, 1,
				0, 0, 0, 1,
				1, 1, 0, 1 )
   this.moves [15] = new Array(	0, 1, 0, 0,
				1, 0, 1, 0,
				0, 1, 1, 0,
				0, 0, 0, 1 )
}


function EasySolution ()

{
   this.moves = new Array ()
   this.solution = new Array ()

   this.moves [0] = new Array (	1, 0, 1, 1,
				0, 0, 1, 0,
				1, 1, 0, 0,
				1, 0, 0, 0 )
   this.moves [1] = new Array (	0, 0, 0, 1,
				0, 1, 1, 1,
				1, 0, 1, 0,
				0, 1, 0, 0 )
   this.moves [2] = new Array ( 1, 0, 0, 0,
				1, 1, 1, 0,
				0, 1, 0, 1,
				0, 0, 1, 0 )
   this.moves [3] = new Array (	1, 1, 0, 1,
				0, 1, 0, 0,
				0, 0, 1, 1,
				0, 0, 0, 1 )
   this.moves [4] = new Array ( 0, 0, 1, 0,
				0, 1, 0, 1,
				0, 1, 1, 0,
				1, 1, 0, 0 )
   this.moves [5] = new Array (	0, 1, 1, 1,
				1, 1, 0, 0,
				1, 0, 0, 1,
				1, 0, 1, 0 )
   this.moves [6] = new Array (	1, 1, 1, 0,
				0, 0, 1, 1,
				1, 0, 0, 1,
				0, 1, 0, 1 )
   this.moves [7] = new Array (	0, 1, 0, 0,
				1, 0, 1, 0,
				0, 1, 1, 0,
				0, 0, 1, 1 )
   this.moves [8] = new Array (	1, 1, 0, 0,
				0, 1, 1, 0,
				0, 1, 0, 1,
				0, 0, 1, 0 )
   this.moves [9] = new Array (	1, 0, 1, 0,
				1, 0, 0, 1,
				1, 1, 0, 0,
				0, 1, 1, 1 )
   this.moves [10] = new Array(	0, 1, 0, 1,
				1, 0, 0, 1,
				0, 0, 1, 1,
				1, 1, 1, 0 )
   this.moves [11] = new Array(	0, 0, 1, 1,
				0, 1, 1, 0,
				1, 0, 1, 0,
				0, 1, 0, 0 )
   this.moves [12] = new Array(	1, 0, 0, 0,
				1, 1, 0, 0,
				0, 0, 1, 0,
				1, 0, 1, 1 )
   this.moves [13] = new Array(	0, 1, 0, 0,
				1, 0, 1, 0,
				0, 1, 1, 1,
				0, 0, 0, 1 )
   this.moves [14] = new Array(	0, 0, 1, 0,
				0, 1, 0, 1,
				1, 1, 1, 0,
				1, 0, 0, 0 )
   this.moves [15] = new Array(	0, 0, 0, 1,
				0, 0, 1, 1,
				0, 1, 0, 0,
				1, 1, 0, 1 )
}


function hardMoves ()

{
   this.definitions [0][2] = 1
   this.definitions [0][5] = 1
   this.definitions [0][8] = 1
   this.definitions [3][1] = 1
   this.definitions [3][6] = 1
   this.definitions [3][11] = 1
   this.definitions [12][4] = 1
   this.definitions [12][9] = 1
   this.definitions [12][14] = 1
   this.definitions [15][7] = 1
   this.definitions [15][10] = 1
   this.definitions [15][13] = 1
}


function easyMoves ()

{
   this.definitions [0][2] = 0
   this.definitions [0][5] = 0
   this.definitions [0][8] = 0
   this.definitions [3][1] = 0
   this.definitions [3][6] = 0
   this.definitions [3][11] = 0
   this.definitions [12][4] = 0
   this.definitions [12][9] = 0
   this.definitions [12][14] = 0
   this.definitions [15][7] = 0
   this.definitions [15][10] = 0
   this.definitions [15][13] = 0
}


function Moves ( hard )

{
   var i = 0

   this.definitions = new Array ()

   this.hard = hardMoves
   this.easy = easyMoves

   while ( i < 16 ) {

	var j = 0

	this.definitions [i] = new Array ()

	while ( j < 16 ) {

		this.definitions [i][j] = 0
		++j
	}

	++i
   }

   this.definitions [0][0] = 1
   this.definitions [0][1] = 1
   this.definitions [0][4] = 1

   this.definitions [1][0] = 1
   this.definitions [1][2] = 1
   this.definitions [1][5] = 1

   this.definitions [2][1] = 1
   this.definitions [2][3] = 1
   this.definitions [2][6] = 1

   this.definitions [3][2] = 1
   this.definitions [3][3] = 1
   this.definitions [3][7] = 1

   this.definitions [4][0] = 1
   this.definitions [4][5] = 1
   this.definitions [4][8] = 1

   this.definitions [5][1] = 1
   this.definitions [5][4] = 1
   this.definitions [5][5] = 1
   this.definitions [5][6] = 1
   this.definitions [5][9] = 1

   this.definitions [6][2] = 1
   this.definitions [6][5] = 1
   this.definitions [6][6] = 1
   this.definitions [6][7] = 1
   this.definitions [6][10] = 1

   this.definitions [7][3] = 1
   this.definitions [7][6] = 1
   this.definitions [7][11] = 1

   this.definitions [8][4] = 1
   this.definitions [8][9] = 1
   this.definitions [8][12] = 1

   this.definitions [9][5] = 1
   this.definitions [9][8] = 1
   this.definitions [9][9] = 1
   this.definitions [9][10] = 1
   this.definitions [9][13] = 1

   this.definitions [10][6] = 1
   this.definitions [10][9] = 1
   this.definitions [10][10] = 1
   this.definitions [10][11] = 1
   this.definitions [10][14] = 1

   this.definitions [11][7] = 1
   this.definitions [11][10] = 1
   this.definitions [11][15] = 1

   this.definitions [12][8] = 1
   this.definitions [12][12] = 1
   this.definitions [12][13] = 1

   this.definitions [13][9] = 1
   this.definitions [13][12] = 1
   this.definitions [13][14] = 1

   this.definitions [14][10] = 1
   this.definitions [14][13] = 1
   this.definitions [14][15] = 1

   this.definitions [15][11] = 1
   this.definitions [15][14] = 1
   this.definitions [15][15] = 1

   if ( hard ) this.hard ()
}


function flipCell ( row, column )

{
   var index = 4 * row + column

   if ( this.cellState [ index ] == 1 ) {

	this.cellState [ index ] = 0
	document.getElementById ( "cell" + row + column ).style.background
								= "silver"
	--this.cellsFilled

   } else {

	this.cellState [ index ] = 1
	document.getElementById ( "cell" + row + column ).style.background
								= "#3060FF"
	++this.cellsFilled
   }
}


function showMove ( row, column )

{
   var index = 4 * row + column
   var j = 0

   this.row = row
   this.column = column

   row = 0
   column = 0

   while ( j < 16 ) {

	if ( this.moves.definitions [index][j] == 1 ) {

		if ( this.cellState [j] == 1 ) {

			document.getElementById ( "cell"
				+ row + column ).style.background
								= "#3060FF"

		} else {

			document.getElementById ( "cell"
				+ row + column ).style.background
								= "silver"

		}
	}

	++column

	if ( column == 4 ) {

		column = 0
		++row
	}

	++j
   }
}


function hideMove ( row, column )

{
   var index = 4 * row + column
   var j = 0

   this.row = -1
   this.column = -1

   row = 0
   column = 0

   while ( j < 16 ) {

	if ( this.moves.definitions [index][j] == 1 ) {

		if ( this.cellState [j] == 1 ) {

			document.getElementById ( "cell"
				+ row + column ).style.background
								= "blue"

		} else {

			document.getElementById ( "cell"
				+ row + column ).style.background
								= "white"

		}
	}

	++column

	if ( column == 4 ) {

		column = 0
		++row
	}

	++j
   }
}


function solvePuzzle ()

{
   var i = 0;

   while ( i < 16 ) {

	this.solution.solution [i] = 0
	++i
   }

   i = 0

   while ( i < 16 ) {

	if ( this.cellState [i] == 1 ) {

		var j = 0

		while ( j < 16 ) {

			if ( this.solution.moves [i][j] == 1 ) {

				this.solution.solution [j]
					= ( this.solution.solution [j] == 1 ?
									0 : 1 )
			}

			++j
		}
	}

	++i
   }
}


function resetBoard ( restart )

{
   var i = 16
   var row = 3
   var column = 4
   var newBoard = 0

   if ( restart && this.original != 0 )  {

	newBoard = this.original

   } else {

   	newBoard = Math.round ( 65535 * Math.random () + 0.5 )
	if ( newBoard < 1 ) newBoard = 1
	else if ( newBoard > 65535 ) newBoard = 65535

	this.original = newBoard
	document.getElementById ( "restart" ).disabled = false
   }

   document.getElementById ( "startup" ).style.color = "silver"
   document.getElementById ( "hint" ).disabled = false

   this.cellsFilled = 0
   this.cheater = 0
   this.moveCount = 0
   this.wrongMoves = 0
   this.hints = 0

   while ( i > 0 ) {

	--i
	--column

	if ( ( newBoard % 2 ) == 1 ) {

		document.getElementById ( "cell" + row
					+ column ).style.background = "blue"
		this.cellState [i] = 1
		this.cellsFilled += 1
		newBoard -= 1

	} else {

		document.getElementById ( "cell" + row
					+ column ).style.background = "white"
		this.cellState [i] = 0
	}

	newBoard /= 2

	if ( column == 0 ) {

		column = 4
		--row
	}
   }

   this.solve ()

   if ( this.row != -1 ) this.show ( this.row, this.column )

   this.started = true
}


function clickCell ( row, column )

{
   if ( this.started ) {

	var index = 4 * row + column
	var j = 0

	this.moveCount += 1
	this.flashCount = 1

	this.solution.solution [index]
			= ( this.solution.solution [index] == 1 ? 0 : 1 )

	if ( this.solution.solution [index] == 1 ) ++this.wrongMoves

	row = 0
	column = 0

	while ( j < 16 ) {

		if ( this.moves.definitions [index][j] == 1 )
						this.flip ( row, column )

		++column
		if ( column == 4 ) {

			++row
			column = 0
		}

		++j
	}

	if ( this.cellsFilled == 0 ) {

		this.started = false
		this.armed = false

		document.getElementById ( "hint" ).disabled = true
		document.getElementById ( "restart" ).disabled = false

		if ( this.row != -1 ) this.hide ( this.row, this.column )

		if ( this.cheater > 0 ) alert ( "You Cheated!" )
		else if ( this.hints > 0 ) alert ( "Now wasn't that easy?" )
		else {

			if ( this.wrongMoves == 0 )
				alert ( "Congratulations!  Perfect solution!" )
			else if ( this.wrongMoves == 1 )
				alert ( "Excellent!" )
			else if ( this.wrongMoves == 2 )
				alert ( "Very good!" )
			else if ( this.wrongMoves == 3 )
				alert ( "Nicely done." )
			else if ( this.wrongMoves == 4 )
				alert ( "Not Bad." )
			else if ( this.wrongMoves == 5 )
				alert ( "Could be better." )
			else if ( this.wrongMoves == 6 )
				alert ( "You need practice." )
			else if ( this.wrongMoves == 7 )
				alert ( "Finally!" )
			else if ( this.wrongMoves > 7 )
				alert ( "Maybe you should take up knitting." )
		}

		document.getElementById ( "startup" ).style.color = "purple"
	}

   } else this.reset ( false )

   return ( false )
}


function easyPuzzle ()

{
   if ( this.isHard && ( ! this.started || this.moveCount == 0  || confirm (
	"Changing the rules in the middle of the game is cheating!" ) ) ) {

	this.moves.easy ()
	this.solution = this.easySolution
	document.getElementById ( "easyButton").style.color = "green"
	document.getElementById ( "hardButton").style.color = "gray"
	this.isHard = false

        if ( this.started ) {

		this.solve ()

		if ( this.moveCount > 0 ) this.cheater += 1 
	}
   }
}


function hardPuzzle ()

{
   if ( ! this.isHard && ( ! this.started || this.moveCount == 0 || confirm (
	"Changing the rules in the middle of the game is cheating!" ) ) ) {

	this.moves.hard ()
	this.solution = this.hardSolution
	document.getElementById ( "easyButton").style.color = "gray"
	document.getElementById ( "hardButton").style.color = "green"
	this.isHard = true

        if ( this.started ) {

		this.solve ()

		if ( this.moveCount > 0 ) this.cheater += 1 
	}
   }
}


function flashHint ( row, column )

{
   var index = 4 * row + column
   var count = --this.flashCount

   if ( ( count % 2 ) == 1 ) {

	if ( this.cellState [index] == 1 ) {

		document.getElementById ( "cell"
			+ row + column ).style.background
							= "#3060FF"

	} else {

		document.getElementById ( "cell"
			+ row + column ).style.background
							= "silver"

	}
	
   } else {

	if ( this.cellState [index] == 1 ) {

		document.getElementById ( "cell"
			+ row + column ).style.background
							= "blue"

	} else {

		document.getElementById ( "cell"
			+ row + column ).style.background
							= "white"

	}

   }

   if ( count > 0 ) setTimeout ( this.name + ".flash(" + row + "," + column
								+ ")", 250 )
   else if ( this.row != -1 ) this.show ( this.row, this.column )
}


function giveHint ()

{
   if ( this.started ) {

	var i = 0
	var hintRow = -1
	var hintColumn = -1

	++this.hints

	// Look for symetry on the corners.
	while ( i < 2 ) {

		if ( this.solution.solution [this.hintSequence [3 - i]]
			!= this.solution.solution [this.hintSequence [i]] ) {

			var j = this.hintSequence [i]

			if ( this.solution.solution [j] == 0 )
				j = this.hintSequence [3 - i]

			hintColumn = j % 4
			hintRow = ( j - hintColumn ) / 4

			break
		}

		++i
	}

	if ( hintRow == -1 ) {

		i = 4

		// Look for symetry in the middle.
		while ( i < 6 ) {

			if ( this.solution.solution
						[this.hintSequence [11 - i]]
				!= this.solution.solution
						[this.hintSequence [i]] ) {

				var j = this.hintSequence [i]

				if ( this.solution.solution [j] == 0 )
					j = this.hintSequence [11 - i]

				hintColumn = j % 4
				hintRow = ( j - hintColumn ) / 4

				break
			}

			++i
		}

		if ( hintRow == -1 ) {

			i = 8

			// Look for symetry on the edges.
			while ( i < 12 ) {

				if ( this.solution.solution
						[this.hintSequence [23 - i]]
					!= this.solution.solution
						[this.hintSequence [i]] ) {

					var j = this.hintSequence [i]

					if ( this.solution.solution [j] == 0 )
						j = this.hintSequence [23 - i]

					hintColumn = j % 4
					hintRow = ( j - hintColumn ) / 4

					break
				}

				++i
			}

			// Just give the first hint from the sequence.
			if ( hintRow == -1 ) {

				i = 0

				while ( i < 16 ) {

					var j = this.hintSequence [i]

					if ( this.solution.solution [j] == 1 ) {

						hintColumn = j % 4
						hintRow = ( j - hintColumn )
									/ 4
						break
					}

					++i
				}
			}
		}
	}

	this.flashCount = 10
	this.flash ( hintRow, hintColumn )
   }
}


function Board ( name, hard )

{
   this.name = name
   this.started = false
   this.armed = true
   this.moveCount = 0
   this.wrongMoves = 0
   this.cheater = 0
   this.cellsFilled = 0
   this.flashCount = 0
   this.hints = 0
   this.original = 0

   this.hintSequence = new Array (	0,  3, 12, 15,
					5,  6,  9, 10,
					1,  2,  4,  7,
					8, 11, 13, 14 )
   this.cellState = new Array ()
   this.moves = new Moves ( hard )
   this.hardSolution = new HardSolution ()
   this.easySolution = new EasySolution ()
   this.row = -1
   this.column = -1

   this.reset = resetBoard
   this.flip = flipCell
   this.move = clickCell
   this.easy = easyPuzzle
   this.hard = hardPuzzle
   this.show = showMove
   this.hide = hideMove
   this.hint = giveHint
   this.flash = flashHint
   this.solve = solvePuzzle
   
   if ( hard ) {

	this.isHard = true
	this.solution = this.hardSolution

   } else {

	this.isHard = false
	this.solution = this.easySolution
   }
}


var board = new Board ( "board", true )


function move ( row, column )

{
   board.move ( row, column )
}


function start ()

{
   if ( ! board.started ) {

	if ( board.armed ) board.reset ( false )
	else board.armed = true
   }
}


function restart ()

{
   if ( board.original != 0 && board.moveCount > 0 ) board.reset ( true )
}


function hard ()

{
   board.hard ()
}


function easy ()

{
   board.easy ()
}


function show ( row, column )

{
   board.show ( row, column )
}


function hide ( row, column )

{
   board.hide ( row, column )
}


function hint ()

{
   board.hint ()
}

