// $Id: PanexGame.js,v 1.15 2010/08/06 18:02:20 pwh Exp $

var returnStrings = new Array()

function Graphics ()

{
   var i

   this.leftMargin = 26
   this.topMargin = 91
   this.blockWidth = 13
   this.blockHeight = 26
   this.innerMargin = 5
   this.overlap = 3
   this.halfHeight = Math.round ( this.blockHeight / 2 )

   this.leftXbar = new Image()
   this.leftXbar.src = "images/LeftXbar.gif"
   this.leftXbar.style.position = "absolute"
   this.leftXbar.style.zIndex = 2

   this.leftUpright = new Image()
   this.leftUpright.src = "images/LeftUpright.gif"
   this.leftUpright.style.position = "absolute"
   this.leftUpright.style.zIndex = 2

   this.LUShadow = new Image()
   this.LUShadow.src = "images/Shadow-Peg.gif"
   this.LUShadow.style.position = "absolute"
   this.LUShadow.style.zIndex = 1

   this.xBar = new Image()
   this.xBar.src = "images/Xbar.gif"
   this.xBar.style.position = "absolute"
   this.xBar.style.zIndex = 2

   this.XBShadow = new Image()
   this.XBShadow.src = "images/Shadow-Pbase.gif"
   this.XBShadow.style.position = "absolute"
   this.XBShadow.style.zIndex = 1

   this.rightXbar = new Image()
   this.rightXbar.src = "images/RightXbar.gif"
   this.rightXbar.style.position = "absolute"
   this.rightXbar.style.zIndex = 2

   this.rightUpright = new Image()
   this.rightUpright.src = "images/RightUpright.gif"
   this.rightUpright.style.position = "absolute"
   this.rightUpright.style.zIndex = 2

   this.RUShadow = new Image()
   this.RUShadow.src = "images/Shadow-Peg.gif"
   this.RUShadow.style.position = "absolute"
   this.RUShadow.style.zIndex = 1

   this.pegTops = new Array()
   this.pegTopShadows = new Array()
   this.pegs = new Array()
   this.pegShadows = new Array()

   for ( i = 0; i < 3; ++i ) {

	this.pegTops[i] = new Image()
	this.pegTops[i].src = "images/PegTop.gif"
	this.pegTops[i].style.position = "absolute"
	this.pegTops[i].style.zIndex = 2

	this.pegTopShadows[i] = new Image()
	this.pegTopShadows[i].src = "images/Shadow-PegTop.gif"
	this.pegTopShadows[i].style.position = "absolute"
	this.pegTopShadows[i].style.zIndex = 1

	this.pegs[i] = new Image()
        this.pegs[i].src = "images/Peg.gif"
	this.pegs[i].style.position ="absolute"
	this.pegs[i].style.zIndex = 2

	this.pegShadows[i] = new Image()
        this.pegShadows[i].src = "images/Shadow-Peg.gif"
	this.pegShadows[i].style.position ="absolute"
	this.pegShadows[i].style.zIndex = 1
   }

   this.leftBase = new Image()
   this.leftBase.src = "images/LeftPbase.gif"
   this.leftBase.style.position = "absolute"
   this.leftBase.style.zIndex = 2

   this.base = new Image()
   this.base.src = "images/Base.gif"
   this.base.style.position = "absolute"
   this.base.style.zIndex = 2

   this.rightBase = new Image()
   this.rightBase.src = "images/RightPbase.gif"
   this.rightBase.style.position = "absolute"
   this.rightBase.style.zIndex = 2

   this.baseShadow = new Image()
   this.baseShadow.src = "images/Shadow-Pbase.gif"
   this.baseShadow.style.position = "absolute"
   this.baseShadow.style.zIndex = 1

   this.ground = new Image()
   this.ground.src = "images/Ground.gif"
   this.ground.style.position = "absolute"
   this.ground.style.zIndex = 1

   this.disks = new Array()
   this.diskShadows = new Array()

   for ( i = 0; i < 12; ++i ) {

	var index = 2 * i

	this.disks[index] = new Image()
	this.disks[index].id = "disk" + index
	this.disks[index].src = "images/MagentaDisk" + i + ".gif"
	this.disks[index].style.position = "absolute"
	this.disks[index].style.zIndex = 3
	this.disks[index].onclick = "onclick(event)"
	this.disks[index].setAttribute ( "onclick",
						"puzzle.grab(" + index + ")" )

	this.diskShadows[index] = new Image()
	this.diskShadows[index].id = "shadow" + index
	this.diskShadows[index].src = "images/Shadow" + i + ".gif"
	this.diskShadows[index].style.position = "absolute"
	this.diskShadows[index].style.zIndex = 1

	++index

	this.disks[index] = new Image()
	this.disks[index].id = "disk" + index
	this.disks[index].src = "images/CyanDisk" + i + ".gif"
	this.disks[index].style.position = "absolute"
	this.disks[index].style.zIndex = 3
	this.disks[index].onclick = "onclick(event)"
	this.disks[index].setAttribute ( "onclick",
						"puzzle.grab(" + index + ")" )

	this.diskShadows[index] = new Image()
	this.diskShadows[index].id = "shadow" + index
	this.diskShadows[index].src = "images/Shadow" + i + ".gif"
	this.diskShadows[index].style.position = "absolute"
	this.diskShadows[index].style.zIndex = 1
   }
}


var graphics = new Graphics ()


function drawDisplay ( size )

{
   var i
   var currentLine = graphics.topMargin - graphics.halfHeight
						- graphics.blockHeight - 2

   document.getElementById ("size").selectedIndex = size - 1

   // Take the disks out.
   if ( this.size > 0 ) {

	for ( i= 0; i < this.size; ++i ) {

		var index = 2 * i

		this.gameFrame.removeChild ( graphics.disks[index] )
		this.gameFrame.removeChild ( graphics.diskShadows[index] )

		++ index

		this.gameFrame.removeChild ( graphics.disks[index] )
		this.gameFrame.removeChild ( graphics.diskShadows[index] )
	}
   }

   if ( size != this.size ) {

	var frameWidth = ( 6 * size + 19 ) * graphics.blockWidth
	var frameHeight = graphics.topMargin
				+ ( 2 * size + 1 ) * graphics.blockHeight

	var pegSpace = graphics.blockWidth * ( 2 * size + 4 )
	var currentColumn = graphics.leftMargin - graphics.innerMargin

	document.getElementById ("title").style.width = frameWidth + "px"

	this.gameFrame.style.height = frameHeight + "px"
	this.gameFrame.style.width = frameWidth + "px"

	graphics.leftXbar.style.top = currentLine + "px"
	graphics.leftXbar.style.left = currentColumn + "px"

	graphics.leftUpright.style.top = ( currentLine + graphics.blockHeight )
									+ "px"
	graphics.leftUpright.style.left = currentColumn + "px"
	graphics.leftUpright.style.width = graphics.blockWidth + "px"
	graphics.leftUpright.style.height
			= ( ( size + 1 ) * graphics.blockHeight + 2 ) + "px"

	graphics.LUShadow.style.top = ( currentLine + graphics.halfHeight )
									+ "px"
	graphics.LUShadow.style.left
			= ( currentColumn + 3 * graphics.blockWidth ) + "px"
	graphics.LUShadow.style.width = graphics.blockWidth + "px"
	graphics.LUShadow.style.height = ( ( size + 1 ) * graphics.blockHeight
					+ graphics.halfHeight + 2 ) + "px"

	currentColumn += graphics.blockWidth

	graphics.xBar.style.top = currentLine + "px"
	graphics.xBar.style.left = currentColumn + "px"
	graphics.xBar.style.width = ( frameWidth - 8 * graphics.blockWidth
					+ 2 * graphics.innerMargin ) + "px"
	graphics.xBar.style.height = graphics.halfHeight + "px"

	currentColumn += ( 3 * graphics.blockWidth )

	graphics.XBShadow.style.top = ( currentLine + graphics.halfHeight )
									+ "px"
	graphics.XBShadow.style.left = currentColumn + "px"
	graphics.XBShadow.style.width = ( frameWidth - 8 * graphics.blockWidth
					+ 2 * graphics.innerMargin ) + "px"
	graphics.XBShadow.style.height = graphics.halfHeight + "px"

	currentColumn += ( frameWidth - 11 * graphics.blockWidth
						+ 2 * graphics.innerMargin )

	graphics.rightXbar.style.top = currentLine + "px"
	graphics.rightXbar.style.left = currentColumn + "px"

	graphics.rightUpright.style.top = ( currentLine + graphics.blockHeight )
									+ "px"
	graphics.rightUpright.style.left = currentColumn + "px"
	graphics.rightUpright.style.width = graphics.blockWidth + "px"
	graphics.rightUpright.style.height
			= ( ( size + 1 ) *  graphics.blockHeight + 2 ) + "px"

	graphics.RUShadow.style.top = ( currentLine + graphics.halfHeight )
									+ "px"
	graphics.RUShadow.style.left
			= ( currentColumn + 3 * graphics.blockWidth ) + "px"
	graphics.RUShadow.style.width = graphics.blockWidth + "px"
	graphics.RUShadow.style.height
			= ( ( size + 2 ) * graphics.blockHeight + 2 ) + "px"

	currentLine += ( graphics.blockHeight + graphics.halfHeight + 2 )
	currentColumn = graphics.leftMargin + graphics.blockWidth * ( size + 2 )

	for ( i = 0; i < 3; ++i ) {

		graphics.pegTops[i].style.top = currentLine + "px"
		graphics.pegTops[i].style.left = currentColumn + "px"

		graphics.pegTopShadows[i].style.top
				= ( currentLine + graphics.halfHeight ) + "px"
		graphics.pegTopShadows[i].style.left
			= ( currentColumn + 3 * graphics.blockWidth ) + "px"

		currentColumn += pegSpace
	}

	currentLine += ( graphics.blockHeight - graphics.halfHeight )
	currentColumn = graphics.leftMargin + graphics.blockWidth * ( size + 2 )

	for ( i = 0; i < 3; ++i ) {

		graphics.pegs[i].style.top = currentLine + "px"
		graphics.pegs[i].style.left = currentColumn + "px"
		graphics.pegs[i].style.height = ( size * graphics.blockHeight )
									+ "px"
		graphics.pegs[i].style.width = graphics.blockWidth + "px"

		graphics.pegShadows[i].style.top
				= ( currentLine + graphics.halfHeight ) + "px"
		graphics.pegShadows[i].style.left
			= ( currentColumn + 3 * graphics.blockWidth ) + "px"
		graphics.pegShadows[i].style.height
					= ( size * graphics.blockHeight ) + "px"
		graphics.pegShadows[i].style.width = graphics.blockWidth + "px"

		currentColumn += pegSpace
	}

	currentLine += ( size * graphics.blockHeight )
	currentColumn = graphics.leftMargin - graphics.innerMargin

	graphics.leftBase.style.top = currentLine + "px"
	graphics.leftBase.style.left = currentColumn + "px"

	currentColumn += ( graphics.blockWidth + graphics.innerMargin )

	graphics.base.style.top = currentLine + "px"
	graphics.base.style.left = currentColumn + "px"
	graphics.base.style.height = graphics.blockHeight + "px"
	graphics.base.style.width = ( 3 * pegSpace - graphics.blockWidth )
									+ "px"

	currentColumn += ( 3 * pegSpace - graphics.blockWidth )

	graphics.rightBase.style.top = currentLine + "px"
	graphics.rightBase.style.left = currentColumn + "px"

	currentLine += graphics.halfHeight

	graphics.baseShadow.style.top = currentLine + "px"
	graphics.baseShadow.style.left = currentColumn + "px"

	currentLine += graphics.blockHeight - graphics.halfHeight

	graphics.ground.style.top = currentLine + "px"
	graphics.ground.style.left = 0
	graphics.ground.style.height = ( graphics.blockHeight * size
						- graphics.blockWidth ) + "px"
	graphics.ground.style.width = "100%"

	this.size = size
   }

   currentLine = graphics.topMargin + graphics.blockHeight - graphics.halfHeight

   // Put the disks back in.
   for ( i = 0; i < size; ++i ) {

	var index = 2 * i

	graphics.disks[index].style.top = currentLine + "px"
	graphics.disks[index].style.left = ( graphics.leftMargin
				+ graphics.blockWidth * ( size - i ) ) + "px"
	this.gameFrame.appendChild ( graphics.disks[index] )

	++index

	graphics.disks[index].style.top = currentLine + "px"
	graphics.disks[index].style.left = ( graphics.leftMargin
			+ graphics.blockWidth * ( 5 * size - i + 8 ) ) + "px"
	this.gameFrame.appendChild ( graphics.disks[index] )

	--index
	currentLine += graphics.halfHeight

	graphics.diskShadows[index].style.top = currentLine + "px"
	graphics.diskShadows[index].style.left = ( graphics.leftMargin
			+ graphics.blockWidth * ( size + 3 - i ) ) + "px"
	this.gameFrame.appendChild ( graphics.diskShadows[index] )

	++index

	graphics.diskShadows[index].style.top = currentLine + "px"
	graphics.diskShadows[index].style.left = ( graphics.leftMargin
			+ graphics.blockWidth * ( 5 * size + 11 - i ) ) + "px"
	this.gameFrame.appendChild ( graphics.diskShadows[index] )

	currentLine += ( graphics.blockHeight - graphics.halfHeight )

   }
}


function Display ( size )

{
   var i

   this.gameFrame = document.getElementById ("gameFrame")
   this.size = 0

   this.draw = drawDisplay

   this.draw ( size )

   this.gameFrame.appendChild ( graphics.leftXbar )
   this.gameFrame.appendChild ( graphics.leftUpright )
   this.gameFrame.appendChild ( graphics.LUShadow )
   this.gameFrame.appendChild ( graphics.xBar )
   this.gameFrame.appendChild ( graphics.XBShadow )
   this.gameFrame.appendChild ( graphics.rightXbar )
   this.gameFrame.appendChild ( graphics.rightUpright )
   this.gameFrame.appendChild ( graphics.RUShadow )

   for ( i = 0; i < 3; ++i ) {

	this.gameFrame.appendChild ( graphics.pegTops[i] )
	this.gameFrame.appendChild ( graphics.pegTopShadows[i] )
	this.gameFrame.appendChild ( graphics.pegs[i] )
	this.gameFrame.appendChild ( graphics.pegShadows[i] )
   }

   this.gameFrame.appendChild ( graphics.leftBase )
   this.gameFrame.appendChild ( graphics.base )
   this.gameFrame.appendChild ( graphics.rightBase )
   this.gameFrame.appendChild ( graphics.baseShadow )
   this.gameFrame.appendChild ( graphics.ground )
}

function mouseCoords (e)

{
   var x = e.pageX
   var y = e.pageY

   // Internet Explorer.
   if ( ( ! e.pageX ) && ( ! e.pageY ) ) {

	x = e.clientX
	y = e.clientY

	// Change the relative coordinates to absolute.
	if ( typeof ( window.pageYOffset ) == "number" ) {

		x += window.pageXOffset
		y += window.pageYOffset

	} else if ( document.body && ( document.body.scrollLeft
						|| document.body.scrollTop ) ) {

		x += document.body.scrollLeft
		y += document.body.scrollTop

	} else if ( document.documentElement
		&& ( document.documentElement.scrollLeft
				|| document.documentElement.scrollTop ) ) {

		x += document.documentElement.scrollLeft
		y += document.documentElement.scrollTop
	}

   }

   var gameFrame = document.getElementById ("gameFrame")
   x -= gameFrame.offsetLeft
   y -= gameFrame.offsetTop

   return { x:x, y:y }
}


function Disk ( index, width, floor )

{
   this.index = index			// Self reference.
   this.floor = floor

   // Used to detect if the disk is over a peg or potentially, collisions.
   this.width = Math.round ( ( width - 1 ) / 2 )

   // The coordinates of the "center" of the disk.
   this.x = -1
   this.y = -1

   this.peg = 2 * ( index % 2 )		// Index of the peg this disk starts on.
   this.below = -1			// Index of the disk below this disk.
}


function clearPeg ( size, floor, x )

{
   this.size = size			// Number of disks peg can hold.
   this.floor = floor			// Location of the base or top disk.
   this.x = x				// Center of the peg.
   this.top = -1			// Index of top disk in the stack.
}


function Peg ( index, size, x, floor )

{
   this.clear = clearPeg

   this.index = index			// Self reference.

   this.clear ( size, floor, x )
}


function popDisk ( pegIndex )

{
   var peg = this.pegs[pegIndex]
   var disk = null

   if ( ! ( peg.top < 0 ) ) {

	disk = this.disks[peg.top]

	var oldFloor = peg.floor
	var i = pegIndex
	var index = disk.index

	this.leftDrag = this.leftEdge + 1
	while ( i > 0 ) {

		--i

		if ( this.pegs[i].size == 0 ) {

			var topDisk = this.disks[this.pegs[i].top]

			this.leftDrag = topDisk.x + topDisk.width
								- this.overlap
			break
		}
	}

	i = pegIndex
	this.rightDrag = this.rightEdge - 1
	while ( i < 2 ) {

		++i

		if ( this.pegs[i].size == 0 ) {

			var topDisk = this.disks[this.pegs[i].top]

			this.rightDrag = topDisk.x - topDisk.width
								+ this.overlap
			break
		}
	}

	if ( ( 2 * ( 1 - index % 2 ) ) == peg.index && disk.y == disk.floor )
								this.unsolved++

	peg.top = disk.below
	peg.floor = ( peg.top == -1 ? this.floor
			: ( this.disks[peg.top].y - graphics.blockHeight ) )

	while ( oldFloor < peg.floor ) {

		oldFloor += graphics.blockHeight
		++peg.size
	}

	disk.below = -1
   }

   return ( disk )
}


function grabDisk ( index )

{
   if ( ! this.solving && this.activeDisk == -1 ) {

	var disk = this.disks[index]
	var peg = this.pegs[disk.peg]

	if ( peg.top == index ) {

		this.ok2drop = false
		this.pop ( peg.index )
		this.activeDisk = index
	}
   }
}


function dragDisk (e)

{
   var index = this.activeDisk

   if ( ! this.solving && index > -1 ) {

	var mouse = mouseCoords (e)
	var disk = this.disks[index]
	var y = mouse.y
	var x = mouse.x
	var leftEdge = this.leftDrag + disk.width
	var rightEdge = this.rightDrag - disk.width
	var diskImg = graphics.disks[index]
	var shadowImg = graphics.diskShadows[index]

	if ( y < this.pegTops || disk.y < this.pegTops ) {

		y = this.pegTops - 1
		if ( x < leftEdge ) x = leftEdge
		else if ( x > rightEdge ) x = rightEdge

	} else {

		var peg = this.pegs[disk.peg]

		x = peg.x

		if ( y > peg.floor ) y = peg.floor
		if ( y > disk.floor ) y = disk.floor
	}

	disk.y = y
	disk.x = x

	x -= disk.width
	diskImg.style.top = y - graphics.halfHeight + "px"
	diskImg.style.left = x + "px"
	shadowImg.style.top = y + "px"
	shadowImg.style.left = ( x + 3 * graphics.blockWidth ) + "px"
   }
}


function pushDisk ( diskIndex, pegIndex )

{
   var peg = this.pegs[pegIndex]
   var disk = this.disks[diskIndex]

   disk.x = peg.x

   while ( peg.floor > disk.floor ) {

	peg.floor -= graphics.blockHeight
	peg.size--
   }

   disk.y = peg.floor
   disk.peg = pegIndex
   disk.below = peg.top
   peg.top = diskIndex

   peg.floor -= graphics.blockHeight
   peg.size--

   if ( disk.y == disk.floor && ( 2 * ( 1 - diskIndex % 2 ) ) == pegIndex )
								this.unsolved--
}


function animationSpeed ( index )

{
   var speed = ( 2 * index + 2 ) * index + 4

   if ( speed != this.speed ) {

	this.speed = speed
	setCookie ( "speed", index, 30 )
   }
}


function startAnimation ()

{
   // Setup the next segment of the animation.
   var s
   var n

   this.timeInc = this.originalTimeInc
   this.x = this.disk.x
   this.y = this.disk.y
   this.dx = 0
   this.dy = 0

   // Make sure that we aren't already there.
   while ( this.nextStop == null && this.stops.length > 0 ) {

	this.nextStop = this.stops.pop()
	this.dx = this.nextStop.x - this.x
	this.dy = this.nextStop.y - this.y

	if ( this.dx == 0 && this.dy == 0 ) {

		delete ( this.nextStop )
		this.nextStop = null
	}
   }

   // If there is another segment, setup the move bookkeeping.
   if ( this.nextStop != null ) {

	// If you don't understand this and the corresponding animateDisk()
	// code there isn't space to explain it here.  This is based on old
	// algorithms for drawing lines and curves that only used integer
	// addition and subtraction (after the setup) avoiding costly floating
	// -point multiplication, division and square root operations.  This
	// may be a false economy here since we don't display a frame at every
	// pixel on the path, but we do from 4 to 8 additions/subtractions for
	// each pixel on the path plus 2 more additions/subtractions when a
	// frame is displayed.  The one definite benefit is that this algorithm
	// guarantees that we end up exactly at the end-point of the path and
	// the frames between the end-points are as evenly spaced as possible
	// with discrete (integer) coordinates.  If the speed parameter is
	// set to less than 3 the algorithm as implemented here may not work
	// properly.

	// Calculate the length of the segment.
	this.s2 = this.dx * this.dx + this.dy * this.dy
	s = Math.sqrt ( this.s2 )

	// Calculate the number of frames to display on this segment.
	n = Math.round ( s / this.speed )
	if ( n == 0 ) n = 1

	this.n2 = n * n

	// Adjust the time increment to keep a constant speed.
	this.timeInc = Math.round ( ( this.timeInc * s ) / ( this.speed * n ) )

	// We increment/decrement the x and y coordinates by 1.
	this.xInc = ( ( this.dx > 0 ) ? 1 : ( ( this.dx < 0 ) ? -1 : 0 ) )
	this.yInc = ( ( this.dy > 0 ) ? 1 : ( ( this.dy < 0 ) ? -1 : 0 ) )

	// Bookkeeping for calculating each pixel of the path segment.
	this.dx = Math.abs ( this.dx )
	this.dy = Math.abs ( this.dy )
	this.incX = ( ( this.dx < this.dy ) ? 0 : 1 )
	this.z = ( ( this.dx < this.dy ) ? this.dy : this.dx )

	// First moment of "distance" travelled squared.
	this.dx2 = this.n2
	this.dy2 = this.n2

	// First moment of "distance" to next frame squared.
	this.dz2 = this.s2

	this.z2 = this.dz2		// "Distance" to next frame squared.

	// Second moments of the "distances".
	this.n2 += this.n2
	this.s2 += this.s2
   }
}


function animateDisk ( getReturnString )

{
   var	animation = this.animation

   if ( animation.abort ) {

	this.activeDisk = animation.disk.index
	delete ( animation )
	this.animation = null

   } else {

	if ( animation.x == animation.nextStop.x
				&& animation.y == animation.nextStop.y ) {

		delete ( animation.nextStop )
		animation.nextStop = null

		animation.start ()
	}

	if ( animation.nextStop != null ) {

		var diskImg = graphics.disks[animation.disk.index]
		var shadowImg = graphics.diskShadows[animation.disk.index]
		var x
		var y

		do {
			// Calculate each pixel on this path segment.
			if ( animation.incX ) {

				animation.x += animation.xInc
				animation.z2 -= animation.dx2
				animation.dx2 += animation.n2
				animation.z -= animation.dy

				if ( ! ( animation.z > 0 ) ) {

					animation.y += animation.yInc
					animation.z2 -= animation.dy2
					animation.dy2 += animation.n2
					animation.z += animation.dx
				}

			} else {

				animation.y += animation.yInc
				animation.z2 -= animation.dy2
				animation.dy2 += animation.n2
				animation.z -= animation.dx

				if ( ! ( animation.z > 0 ) ) {

					animation.x += animation.xInc
					animation.z2 -= animation.dx2
					animation.dx2 += animation.n2
					animation.z += animation.dy
				}
			}

		// Check if we have reached the next frame.
		} while ( animation.z2 > 0 )

		// Reset frame distance oddometer.
		animation.dz2 += animation.s2
		animation.z2 += animation.dz2

		x = animation.x
		y = animation.y

		animation.disk.x = x
		animation.disk.y = y

		x -= animation.disk.width

		// Display the next frame.
		diskImg.style.top = y - graphics.halfHeight + "px"
		diskImg.style.left = x + "px"
		shadowImg.style.top = y + "px"
		shadowImg.style.left = ( x + 3 * graphics.blockWidth ) + "px"

		setTimeout ( "puzzle.animate(" + getReturnString + ")",
							animation.timeInc )

	} else {

		if ( animation.disk.peg != animation.peg.index ) {

			this.moveCount++

			document.getElementById ( "moves" ).innerHTML
							= this.moveCount
		}

		this.push ( animation.disk.index, animation.peg.index )

		delete ( animation )
		this.animation = null

		if ( this.unsolved ) this.activeDisk = -1
		else if ( this.solving ) {

			alert ( "See how easy it was?" )
			this.activeDisk = -2

		} else alert ( "Congratulations!  You did it!" )

		if ( getReturnString ) setTimeout ( returnStrings.pop(), 1 )
	}
   }
}


function Animation ( disk, peg, pegTops, speed, timeInc )

{
   // Method to setup the next segment of the animation.
   this.start = startAnimation

   // Properties.
   this.speed = speed
   this.originalTimeInc = timeInc
   this.disk = disk			// The disk being moved.
   this.peg = peg			// Destination peg.
   this.abort = false			// Interrupt.
   this.nextStop = null			// End point of active segment.
   this.stops = new Array()		// The end-points of the segments.

   // Final destination.
   this.stops.push ( { x:peg.x,
		y:( peg.floor < disk.floor ? peg.floor : disk.floor ) } )

   if ( disk.x != peg.x ) {

	// Top of the destination peg.
	this.stops.push ( { x:peg.x, y:pegTops - 1 } )

	if ( ! ( disk.y < pegTops ) ) {

		// Top of the source peg.
		this.stops.push ( { x:disk.x, y:pegTops - 1 } )
        }
   }

   // Setup the first segment of the animation.
   this.start ()
}


function dropDisk (e)

{
   var index = this.activeDisk

   if ( this.ok2drop && ! this.solving && index > -1 ) {

	var mouse = mouseCoords (e)
	var pegIndex = -1
	var disk = this.disks[index]

	if ( Math.abs ( this.pegs[0].x - disk.x ) < disk.width ) pegIndex = 0
	else if ( Math.abs ( this.pegs[1].x - disk.x ) < disk.width )
								pegIndex = 1
	else if ( Math.abs ( this.pegs[2].x - disk.x ) < disk.width )
								pegIndex = 2

	if ( pegIndex > -1 ) {

		var peg = this.pegs [pegIndex]

		this.activeDisk = -2

		if ( pegIndex != disk.peg ) this.undoMoves.push (
					{ src:pegIndex, dest:disk.peg } )

		this.animation = new Animation ( disk, peg, this.pegTops, 16,
									12 )

		if ( this.animation.nextStop != null ) {

			setTimeout ( "puzzle.animate()",
					this.animation.timeInc )

		} else {

			this.push ( disk.index, peg.index )
			delete ( this.animation )
			this.animation = null
			this.activeDisk = -1
		}


	} else alert ( "Put the disk over a peg before dropping it." )
   }

   this.ok2drop = true
}


function moveDisk ( src, dest, getReturnString )

{
   var disk

   if ( this.activeDisk < 0 ) {

	var peg = this.pegs[src]

	disk = this.pop ( peg.index )

	this.activeDisk = disk.index

   } else {

	disk = this.disks[this.activeDisk]

	if ( disk.peg != src ) dest = disk.peg
   }

   if ( disk != null ) {

	this.animation = new Animation ( disk, this.pegs[dest], this.pegTops,
						this.speed, this.timeInc )

	if ( this.animation.nextStop != null ) {

		this.activeDisk = -2
		setTimeout ( "puzzle.animate(" + getReturnString + ")",
							this.animation.timeInc )

	} else {

		this.activeDisk = -1
		this.push ( this.animation.disk.index,
						this.animation.peg.index )
		delete ( this.animation )
		this.animation = null

		if ( this.solving && getReturnString )
					setTimeout ( returnStrings.pop(), 1 )
	}
   }
}


function swapDisks ( getReturnString, breakpoint, srcPeg )

{
   var	peg1
   var	peg2

   if ( srcPeg == undefined ) peg1 = 2 * ( Math.round ( Math.random () ) )
   else peg1 = srcPeg

   peg2 = 2 - peg1

   if ( ! breakpoint ) {

	returnStrings.push ( "puzzle.swap(" + getReturnString + ",1," + peg1
								+ ")" )
	this.move ( 1, peg1, true )

   } else switch ( breakpoint ) {

      case 1:

	returnStrings.push ( "puzzle.swap(" + getReturnString + ",2," + peg1
								+ ")" )
	this.move ( 1, peg2, true )
	break

      case 2:

	returnStrings.push ( "puzzle.swap(" + getReturnString + ",3," + peg1
								+ ")" )
	this.move ( peg1, 1, true )
	break

      case 3:
	this.move ( peg2, 1, getReturnString )
	break
   }
}


function swapSideDisks ( src, getReturnString, breakpoint, startPeg )

{
   var peg1 = startPeg

   if ( ! breakpoint ) {

	var peg2 = 1

	peg1 = 2 * Math.round ( Math.random () )

	if ( peg1 == 1 ) peg2 = 2 - src
	else peg1 = 2 - src

	returnStrings.push ( "puzzle.sideSwap(" + src + "," + getReturnString
						+ ",1," + peg1 + ")" )
	this.move ( src, peg1, true )

   } else {

	var peg2 = 3 - src - peg1

	switch ( breakpoint ) {

	   case 1:

		returnStrings.push ( "puzzle.sideSwap(" + src + ","
				+ getReturnString + ",2," + peg1 + ")" )
		this.move ( src, peg2, true )
		break

	   case 2:

		returnStrings.push ( "puzzle.sideSwap(" + src + ","
				+ getReturnString + ",3," + peg1 + ")" )
		this.move ( peg1, src, true )
		break

	   case 3:

		this.move ( peg2, src, getReturnString )
		break
	}
   }
}


function moveStackOut ( height, dest, getReturnString, breakpoint )

{
   if ( ! breakpoint ) {

	var other_peg = 2 - dest

	switch ( height ) {

	   case 1:

		this.move ( other_peg, dest, getReturnString )
		break

	   case 2:

		returnStrings.push ( "puzzle.stackOut (" + ( height - 1 )
				+ "," + dest + "," + getReturnString + ")" )
		this.move ( 1, dest, true )
		break

	   default:

		returnStrings.push ( "puzzle.stackOut (" + height + "," + dest
					+ "," + getReturnString + ",1)" )
		this.stackOut ( height - 2, dest, true )
		break
	}

   } else switch ( breakpoint ) {

      // height > 2
      case 1:

	returnStrings.push ( "puzzle.stackOut (" + height + "," + dest + ","
						+ getReturnString + ",2)" )
	this.swap ( true )
	break

      case 2:

	returnStrings.push ( "puzzle.stackOut (" + height + "," + dest + ","
						+ getReturnString + ",3)" )
	this.float ( height - 1, dest, true )
	break

      case 3:

	this.stackOut ( height - 1, dest, getReturnString )
	break
   }
}


function moveStackIn ( height, src, getReturnString, breakpoint )

{
   var other_peg = 2 - src

   if ( ! breakpoint ) {

	switch ( height ) {

	   case 2:

		returnStrings.push ( "puzzle.stackIn(" + height + "," + src
					+ "," + getReturnString + ",1)" )
		getReturnString = true

	   case 1:

		this.move ( src, other_peg, getReturnString )
		break

	   default:

		returnStrings.push ( "puzzle.stackIn(" + height + "," + src
					+ "," + getReturnString + ",2)" )
		this.stackIn ( height - 1, src, true )
		break
	}

   } else switch ( breakpoint ) {

      case 1:

	this.move ( src, 1, getReturnString )
	break

      // height > 2
      case 2:

	returnStrings.push ( "puzzle.stackIn(" + height + "," + src
					+ "," + getReturnString + ",3)" )
	this.sink ( height - 1, src, true )
	break

      case 3:

	returnStrings.push ( "puzzle.stackIn(" + height + "," + src
					+ "," + getReturnString + ",4)" )
	this.swap ( true )
	break

      case 4:

	this.stackIn ( height - 2, src, getReturnString )
	break
   }
}


function floatDisk ( height, dest, getReturnString, breakpoint )

{
   var other_peg = 2 - dest

   if ( ! breakpoint ) {

	switch ( height ) {

	   case 1:

		this.move ( 1, dest, getReturnString );
		break

	   case 2:

		returnStrings.push ( "puzzle.float(" + height + "," + dest
					+ "," + getReturnString + ",1)" )
		this.move ( dest, other_peg, true )
		break

	   case 3:

		returnStrings.push ( "puzzle.float(" + height + "," + dest
					+ "," + getReturnString + ",2)" )
		this.move ( dest, 1, true )
		break

	   default:

		returnStrings.push ( "puzzle.float(" + height + "," + dest
					+ "," + getReturnString + ",9)" )
		this.stackIn ( height - 2, dest, true )

		break
	}

   } else switch ( breakpoint ) {

      // height == 2
      case 1:

	this.move ( 1, dest, getReturnString )
	break

      // height == 3
      case 2:

	returnStrings.push ( "puzzle.float(" + height + "," + dest
					+ "," + getReturnString + ",3)" )
	this.move ( dest, other_peg, true )
	break

      case 3:

	returnStrings.push ( "puzzle.float(" + height + "," + dest
					+ "," + getReturnString + ",4)" )
	this.move ( 1, dest, true )
	break

      case 4:

	returnStrings.push ( "puzzle.float(" + height + "," + dest
					+ "," + getReturnString + ",5)" )
	this.move ( 1, dest, true )
	break

      case 5:

	returnStrings.push ( "puzzle.float(" + height + "," + dest
					+ "," + getReturnString + ",6)" )
	this.move ( other_peg, 1, true )
	break

      case 6:

	returnStrings.push ( "puzzle.float(" + height + "," + dest
					+ "," + getReturnString + ",7)" )
	this.move ( dest, 1, true )
	break

      case 7:

	returnStrings.push ( "puzzle.float(" + height + "," + dest
					+ "," + getReturnString + ",8)" )
	this.move ( dest, other_peg, true )
	break

      case 8:

	this.move ( 1, dest, getReturnString )
	break

      // height > 3
      case 9:

	returnStrings.push ( "puzzle.float(" + height + "," + dest
					+ "," + getReturnString + ",10)" )
	this.sink ( height - 1, dest, true )
	break

      case 10:

	returnStrings.push ( "puzzle.float(" + height + "," + dest
					+ "," + getReturnString + ",11)" )
	this.swap ( true )
	break

      case 11:

	this.float ( height - 1, dest, getReturnString )
	break
   }
}


function sinkDisk ( height, src, getReturnString, breakpoint )

{
   var	other_peg = 2 - src

   if ( ! breakpoint ) {

	if ( height > 3 ) {

		returnStrings.push ( "puzzle.sink(" + height + "," + src + ","
					+ getReturnString + ",9)" )

		this.sink ( height - 1, src, true )

	} else {

		if ( height > 1 ) {

			returnStrings.push ( "puzzle.sink(" + height + ","
				+ src + "," + getReturnString + ","
							+ ( height - 1 ) + ")" )
			getReturnString = true
		}

		this.move ( src, 1, getReturnString )
	}

   } else switch ( breakpoint ) {

      // height == 2
      case 1:

	this.move ( other_peg, src, getReturnString )
	break

      // height == 3
      case 2:

	returnStrings.push ( "puzzle.sink(" + height + "," + src + ","
						+ getReturnString + ",3)" )
	this.move ( other_peg, src, true )
	break

      case 3:

	returnStrings.push ( "puzzle.sink(" + height + "," + src + ","
						+ getReturnString + ",4)" )
	this.move ( 1, src, true )
	break

      case 4:

	returnStrings.push ( "puzzle.sink(" + height + "," + src + ","
						+ getReturnString + ",5)" )
	this.move ( 1, other_peg, true )
	break

      case 5:

	returnStrings.push ( "puzzle.sink(" + height + "," + src + ","
						+ getReturnString + ",6)" )
	this.move ( src, 1, true )
	break

      case 6:

	returnStrings.push ( "puzzle.sink(" + height + "," + src + ","
						+ getReturnString + ",7)" )
	this.move ( src, 1, true )
	break

      case 7:

	returnStrings.push ( "puzzle.sink(" + height + "," + src + ","
						+ getReturnString + ",8)" )
	this.move ( other_peg, src, true )
	break

      case 8:

	this.move ( 1, src, getReturnString )
	break

      // height > 3
      case 9:

	returnStrings.push ( "puzzle.sink(" + height + "," + src + ","
						+ getReturnString + ",10)" )
	this.swap ( true )
	break

      case 10:

	returnStrings.push ( "puzzle.sink(" + height + "," + src + ","
						+ getReturnString + ",11)" )
	this.float ( height - 1, src, true )
	break

      case 11:

	this.stackOut ( height - 2, src, getReturnString )

	break
   }
}


function Y_base ( src, getReturnString, breakpoint )

{
   var	other_peg = 2 - src

   if ( ! breakpoint ) {

	returnStrings.push ( "puzzle.Y(" + src + "," + getReturnString + ",1)" )
	this.move ( src, 1, true )

   } else switch ( breakpoint ) {

      case 1:

	returnStrings.push ( "puzzle.Y(" + src + "," + getReturnString + ",2)" )
	this.move ( other_peg, src, true )
	break

      case 2:

	returnStrings.push ( "puzzle.Y(" + src + "," + getReturnString + ",3)" )
	this.move ( 1, src, true )
	break

      case 3:

	returnStrings.push ( "puzzle.Y(" + src + "," + getReturnString + ",4)" )
	this.move ( 1, other_peg, true )
	break

      case 4:

	returnStrings.push ( "puzzle.Y(" + src + "," + getReturnString + ",5)" )
	this.move ( src, 1, true )
	break

      case 5:

	returnStrings.push ( "puzzle.Y(" + src + "," + getReturnString + ",6)" )
	this.move ( src, 1, true )
	break

      case 6:

	returnStrings.push ( "puzzle.Y(" + src + "," + getReturnString + ",7)" )
	this.move ( other_peg, src, true )
	break

      case 7:

	returnStrings.push ( "puzzle.Y(" + src + "," + getReturnString + ",8)" )
	this.move ( other_peg, src, true )
	break

      case 8:

	returnStrings.push ( "puzzle.Y(" + src + "," + getReturnString + ",9)" )
	this.move ( other_peg, src, true )
	break

      case 9:

	returnStrings.push ( "puzzle.Y(" + src + "," + getReturnString
								+ ",10)" )
	this.move ( 1, other_peg, true )
	break

      case 10:

	returnStrings.push ( "puzzle.Y(" + src + "," + getReturnString
								+ ",11)" )
	this.move ( 1, other_peg, true )
	break

      case 11:

	this.move ( src, 1, getReturnString )
	break
   }
}


function YZ ( src, getReturnString, breakpoint )

{
   var other_peg = 2 - src

   if ( ! breakpoint ) {

	returnStrings.push ( "puzzle.YZ(" + src + "," + getReturnString
								+ ",1)" )
	this.Y ( src, true )

   } else switch ( breakpoint ) {

      case 1:

	returnStrings.push ( "puzzle.YZ(" + src + "," + getReturnString
								+ ",2)" )
	this.sideSwap ( other_peg, true )
	break

      case 2:

	returnStrings.push ( "puzzle.YZ(" + src + "," + getReturnString
								+ ",3)" )
	this.move ( src, other_peg, true )
	break

      case 3:

	returnStrings.push ( "puzzle.YZ(" + src + "," + getReturnString
								+ ",4)" )
	this.move ( src, 1, true )
	break

      case 4:

	returnStrings.push ( "puzzle.YZ(" + src + "," + getReturnString
								+ ",5)" )
	this.move ( other_peg, src, true )
	break

      case 5:

	returnStrings.push ( "puzzle.YZ(" + src + "," + getReturnString
								+ ",6)" )
	this.move ( other_peg, src, true )
	break

      case 6:

	returnStrings.push ( "puzzle.YZ(" + src + "," + getReturnString
								+ ",7)" )
	this.move ( 1, other_peg, true )
	break

      case 7:

	returnStrings.push ( "puzzle.YZ(" + src + "," + getReturnString
								+ ",8)" )
	this.move ( src, other_peg, true )
	break

      case 8:

	returnStrings.push ( "puzzle.YZ(" + src + "," + getReturnString
								+ ",9)" )
	this.swap ( true )
	break

      case 9:

	returnStrings.push ( "puzzle.YZ(" + src + "," + getReturnString
								+ ",10)" )
	this.move ( src, other_peg, true )
	break

      case 10:

	returnStrings.push ( "puzzle.YZ(" + src + "," + getReturnString
								+ ",11)" )
	this.move ( 1, src, true )
	break

      case 11:

	returnStrings.push ( "puzzle.YZ(" + src + "," + getReturnString
								+ ",12)" )
	this.move ( 1, src, true )
	break

      case 12:

	this.move ( other_peg, src, getReturnString )
	break
   }
}


function Y_not ( src, getReturnString, breakpoint )

{
   var	other_peg = 2 - src

   if ( ! breakpoint ) {

	returnStrings.push ( "puzzle.Y_not(" + src + "," + getReturnString
								+ ",1)" )
	this.Y ( src, true )

   } else switch ( breakpoint ) {

      case 1:

	returnStrings.push ( "puzzle.Y_not(" + src + "," + getReturnString
								+ ",2)" )
	this.move ( other_peg, 1, true )
	break

      case 2:

	returnStrings.push ( "puzzle.Y_not(" + src + "," + getReturnString
								+ ",3)" )
	this.move ( other_peg, src, true )
	break

      case 3:

	this.move ( 1, other_peg, getReturnString )
	break
   }
}


function W ( dest, getReturnString, breakpoint )

{
   var	other_peg = 2 - dest

   if ( ! breakpoint ) {

	returnStrings.push ( "puzzle.W(" + dest + "," + getReturnString
								+ ",1)" )
	this.move ( other_peg, dest, true )

   } else switch ( breakpoint ) {

      case 1:

	returnStrings.push ( "puzzle.W(" + dest + "," + getReturnString
								+ ",2)" )
	this.move ( 1, dest, true )
	break

      case 2:

	returnStrings.push ( "puzzle.W(" + dest + "," + getReturnString
								+ ",3)" )
	this.move ( 1, other_peg, true )
	break

      case 3:

	returnStrings.push ( "puzzle.W(" + dest + "," + getReturnString
								+ ",4)" )
	this.move ( dest, 1, true )
	break

      case 4:

	returnStrings.push ( "puzzle.W(" + dest + "," + getReturnString
								+ ",5)" )
	this.move ( dest, 1, true )
	break

      case 5:

	returnStrings.push ( "puzzle.W(" + dest + "," + getReturnString
								+ ",6)" )
	this.move ( other_peg, dest, true )
	break

      case 6:

	returnStrings.push ( "puzzle.W(" + dest + "," + getReturnString
								+ ",7)" )
	this.move ( other_peg, dest, true )
	break

      case 7:

	returnStrings.push ( "puzzle.W(" + dest + "," + getReturnString
								+ ",8)" )
	this.move ( other_peg, dest, true )
	break

      case 8:

	returnStrings.push ( "puzzle.W(" + dest + "," + getReturnString
								+ ",9)" )
	this.move ( 1, other_peg, true )
	break

      case 9:

	returnStrings.push ( "puzzle.W(" + dest + "," + getReturnString
								+ ",10)" )
	this.move ( dest, other_peg, true )
	break

      case 10:

	returnStrings.push ( "puzzle.W(" + dest + "," + getReturnString
								+ ",11)" )
	this.move ( dest, 1, true )
	break

      case 11:

	returnStrings.push ( "puzzle.W(" + dest + "," + getReturnString
								+ ",12)" )
	this.move ( other_peg, dest, true )
	break

      case 12:

	this.move ( other_peg, dest, getReturnString )
	break
   }
}


function V ( dest, getReturnString, breakpoint, i )

{
   var other_peg = 2 - dest
   var height = this.size - 2

   if ( ! breakpoint ) {

	if ( height > 2 ) {

		i = height
		breakpoint = 1

	} else breakpoint = 4
   }

   switch ( breakpoint ) {

      // while ( i > 2 )
      case 1:

	returnStrings.push ( "puzzle.V(" + dest + "," + getReturnString + ",2,"
								+ i + ")" )
	this.stackOut ( i - 1, dest, true )
	break

      case 2:

	returnStrings.push ( "puzzle.V(" + dest + "," + getReturnString + ",3,"
								+ i + ")" )
	this.swap ( true )
	break

      case 3:

	if ( i > 3 )
		returnStrings.push ( "puzzle.V(" + dest + "," + getReturnString
						+ ",1," + ( i - 1 ) + ")" )
	else
		returnStrings.push ( "puzzle.V(" + dest + "," + getReturnString
								+ ",4)" )
	this.float ( i, dest, true )
	break

      // end while ( i > 2 )

      case 4:

	returnStrings.push ( "puzzle.V(" + dest + "," + getReturnString
								+ ",5)" )
	this.W ( dest, true )
	break

      case 5:

	returnStrings.push ( "puzzle.V(" + dest + "," + getReturnString
								+ ",6)" )
	this.move ( other_peg, dest, true )
	break

      case 6:

	returnStrings.push ( "puzzle.V(" + dest + "," + getReturnString
								+ ",7)" )
	this.move ( 1, other_peg, true )
	break

      case 7:

	returnStrings.push ( "puzzle.V(" + dest + "," + getReturnString
								+ ",8)" )
	this.move ( 1, other_peg, true )
	break

      case 8:

	returnStrings.push ( "puzzle.V(" + dest + "," + getReturnString
								+ ",9)" )
	this.move ( dest, 1, true )
	break

      case 9:

	returnStrings.push ( "puzzle.V(" + dest + "," + getReturnString
								+ ",10)" )
	this.move ( other_peg, 1, true )
	break

      case 10:

	if ( height > 3 )
		returnStrings.push ( "puzzle.V(" + dest + "," + getReturnString
								+ ",11,3)" )
	else
		returnStrings.push ( "puzzle.V(" + dest + "," + getReturnString
								+ ",14)" )
	this.move ( other_peg, dest, true )
	break

      // while ( i < height )
      case 11:

	returnStrings.push ( "puzzle.V(" + dest + "," + getReturnString + ",12,"
								+ i + ")" )
	this.sink ( i, other_peg, true )
	break

      case 12:

	returnStrings.push ( "puzzle.V(" + dest + "," + getReturnString + ",13,"
								+ i + ")" )
	this.swap ( true )
	break

      case 13:

	++i;

	if ( i < height ) 
		returnStrings.push ( "puzzle.V(" + dest + "," + getReturnString
							+ ",11," + i + ")" )
	else
		returnStrings.push ( "puzzle.V(" + dest + "," + getReturnString
								+ ",14)" )
	this.stackIn ( i - 2, other_peg, true )
	break

      // end while ()

      case 14:

	returnStrings.push ( "puzzle.V(" + dest + "," + getReturnString
								+ ",15)" )
	if ( height > 2 ) this.sink ( height + 1, other_peg, true )
	else this.stackOut ( height, other_peg, true )
	break

      case 15:

	returnStrings.push ( "puzzle.V(" + dest + "," + getReturnString
								+ ",16)" )
	this.swap ( true )
	break

      case 16:

	this.float ( height + 1, other_peg, getReturnString )
	break
   }
}


function resetPuzzle ( size )

{
   this.solving = false

   if ( this.animation ) {

	this.animation.abort = true
	setTimeout ( "puzzle.reset(" + size + ")", this.timeInc )

   } else {

	var index = 0
	var pegX = graphics.leftMargin
	+ Math.round ( ( graphics.blockWidth * ( 2 * size + 6 ) - 1 ) / 2 )
	var width = graphics.blockWidth * ( size + size + 4 )

	this.activeDisk = -2

	this.floor = graphics.topMargin + graphics.blockHeight * size
	this.rightEdge = graphics.leftMargin
	+ graphics.blockWidth * ( 6 * ( size + 2 ) + 1 ) + graphics.innerMargin
	this.rightDrag = this.rightEdge - 1

	this.display.draw ( size )

	if ( size != this.size ) setCookie ( "size", size, 30 )

	this.size = size
	this.unsolved = 2 * size
	this.moveCount = 0
	document.getElementById ( "moves" ).innerHTML = this.moveCount

	// Clear the pegs.
	while ( index < 3 ) {

		this.pegs[index].clear ( this.size + 1, this.floor, pegX )

		pegX += width
		++index
	}

        index = this.unsolved

	while ( index > 0 ) {		// Stack disks.

		index--
		this.push ( index, 2 )

		index--
		this.push ( index, 0 )
	}

	// Empty the move history.
	while ( this.undoMoves.length > 0 ) {

		this.undoMoves.pop()
	}

	// Clear the return stack.
	while ( returnStrings.length > 0 ) returnStrings.pop()

	this.activeDisk = -1
   }
}


function solvePuzzle ( srcPeg, breakpoint, i )

{
   var peg1
   var peg2

   if ( srcPeg == undefined ) peg1 = 2 * Math.round ( Math.random () )
   else peg1 = srcPeg

   peg2 = 2 - peg1

   if ( ! breakpoint ) {

	if ( ! this.solving ) {

		if ( this.animation ) {

			this.animation.abort = true
			setTimeout ( "puzzle.solve(" + peg1 + ")",
								this.timeInc )

		} else {

			this.reset ( this.size )

			this.solving = true
			setTimeout ( "puzzle.solve(" + peg1 + ",1)", 500 )
		}
	}

   } else switch ( breakpoint ) {

      case 1:

	switch ( this.size ) {

	   case 1:

		returnStrings.push ( "puzzle.solve(" + peg1 + ",2)" )
		this.move ( peg1, 1, true )
		break

	   case 2:

		returnStrings.push ( "puzzle.solve(" + peg1 + ",4)" )
		this.move ( peg1, peg2, true )

		break

	   default:

		returnStrings.push ( "puzzle.solve(" + peg1 + ",12)" )
		this.stackIn ( this.size - 1, peg1, true )

		break
	}

	break

      // One disk stack
      case 2:

	returnStrings.push ( "puzzle.solve(" + peg1 + ",3)")
	this.move ( peg2, peg1, true )
	break

      case 3:

	this.move ( 1, peg2 )
	break

      // Two disk stack
      case 4:

	returnStrings.push ( "puzzle.solve(" + peg1 + ",5)" )
	this.sink ( 2, peg1, true )
	break

      case 5:

	returnStrings.push ( "puzzle.solve(" + peg1 + ",6)" )
	this.move ( peg2, peg1, true )

	break

      case 6:

	returnStrings.push ( "puzzle.solve(" + peg1 + ",7)" )
	this.sink ( 2, peg2, true )

	break

      case 7:

	returnStrings.push ( "puzzle.solve(" + peg1 + ",8)" )
	this.float ( 2, peg1, true )

	break

      case 8:

	returnStrings.push ( "puzzle.solve(" + peg1 + ",9)" )
	this.move ( peg2, 1, true )

	break

      case 9:

	returnStrings.push ( "puzzle.solve(" + peg1 + ",10)" )
	this.move ( peg2, peg1, true )

	break

      case 10:

	returnStrings.push ( "puzzle.solve(" + peg1 + ",11)" )
	this.move ( 1, peg1, true )

	break

      case 11:

	this.stackOut ( 2, peg2 )
	break

      // Stacks bigger than 2 disks.
      case 12:

	var height = this.size

	++breakpoint

	if ( height > 3 ) {

		--height
		breakpoint = 15
	}

	returnStrings.push ( "puzzle.solve(" + peg1 + "," + breakpoint + ")" )
	this.sink ( height, peg1, true )
	break

      // 3 disk stack.
      case 13:

	returnStrings.push ( "puzzle.solve(" + peg1 + ",14)" )
	this.stackIn ( this.size - 1, peg2, true )
	break

      case 14:

	this.YZ ( peg2 )
	break

      // Stacks bigger than 3 disks.
      case 15:

	returnStrings.push ( "puzzle.solve(" + peg1 + ",16)" )
	this.swap ( true )
	break

      case 16:

	returnStrings.push ( "puzzle.solve(" + peg1 + ","
					+ ( this.size < 5 ? 25 : 17 ) + ")" )
	this.stackIn ( this.size - 2, peg2, true )
	break

      // if ( height > 4 )
      case 17:

	returnStrings.push ( "puzzle.solve(" + peg1 + ",18)" )
	this.sink ( this.size - 1, peg2, true )
	break

      case 18:

	if ( this.size > 5 ) returnStrings.push ( "puzzle.solve(" + peg1
					+ ",19," + ( this.size - 2 ) + ")" )
	else returnStrings.push ( "puzzle.solve(" + peg1 + ",23)" )

	this.float ( this.size - 1, peg1, true )
	break

      // while ( i > 3 )
      case 19:

	returnStrings.push ( "puzzle.solve(" + peg1 + ",20," + i + ")" )
	this.stackOut ( i - 1, peg1, true )
	break

      case 20:

	returnStrings.push ( "puzzle.solve(" + peg1 + ",21," + i + ")" )
	this.stackIn ( i - 1, peg2, true )
	break

      case 21:

	returnStrings.push ( "puzzle.solve(" + peg1 + ",22," + i + ")" )
	this.sink ( i, peg2, true )
	break

      case 22:

	if ( i > 4 ) returnStrings.push ( "puzzle.solve(" + peg1
						+ ",19," + ( i - 1 ) + ")" )
	else returnStrings.push ( "puzzle.solve(" + peg1 + ",23)" )

	this.float ( i, peg1, true )
	break

      // end while ( i > 3 )

      case 23:

	returnStrings.push ( "puzzle.solve(" + peg1 + ",24)" )
	this.stackOut ( 2, peg1, true )
	break

      case 24:

	returnStrings.push ( "puzzle.solve(" + peg1 + ",25)" )
	this.stackIn ( 2, peg2, true )
	break

      // end if ( height > 4 )

      case 25:

	returnStrings.push ( "puzzle.solve(" + peg1 + ",26)" )
	this.Y_not ( peg2, true )
	break

      case 26:

	returnStrings.push ( "puzzle.solve(" + peg1 + ",27)" )
	this.stackOut ( 2, peg1, true )
	break

      case 27:

	returnStrings.push ( "puzzle.solve(" + peg1 + ",28)" )
	this.stackIn ( 2, peg2, true )
	break

      case 28:

	returnStrings.push ( "puzzle.solve(" + peg1 + ",29)" )
	this.sink ( this.size, peg2, true )
	break

      case 29:

	returnStrings.push ( "puzzle.solve(" + peg1 + ",30)" )
	this.float ( this.size, peg1, true )
	break

      case 30:

	returnStrings.push ( "puzzle.solve(" + peg1 + ",31)" )
	this.V ( peg1, true )
	break

      case 31:

	this.stackOut ( this.size - 1, peg2 )
	break
   }
}


function undoMove ()

{
   if ( ! this.solving && this.activeDisk > -2 ) {

	if ( this.activeDisk > -1 ) {

		if ( this.animation ) {

			this.animation.abort = true
			setTimeout ( "puzzle.undo()", this.timeInc )

		} else {

			this.move ( -1, -1 )
		}

	} else if ( this.undoMoves.length > 0 ) {

		var lastMove = this.undoMoves.pop()

		this.moveCount -= 2
		this.move ( lastMove.src, lastMove.dest )
	}
   }
}


function Puzzle ( size )

{
   var cookie = getCookie ( "size" )
   var speedIndex = 2
   var ok2drop = true

   if ( cookie ) size = parseInt ( cookie )

   cookie = getCookie ( "speed" )

   if ( cookie ) {

	speedIndex = parseInt ( cookie )
	document.getElementById ("speed").selectedIndex = speedIndex
   }

   var pegX = graphics.leftMargin
	+ Math.round ( ( graphics.blockWidth * ( 2 * size + 6 ) - 1 ) / 2 )
   var width = graphics.blockWidth * ( 2 * size + 3 )
   var index = 24
   var diskFloor = graphics.topMargin + 12 * graphics.blockHeight

   // Methods.
   this.reset = resetPuzzle
   this.grab = grabDisk			// Player grabs a disk.
   this.drag = dragDisk			// Player drags a disk.
   this.drop = dropDisk			// Player drops a disk.
   this.push = pushDisk			// Stack a disk on a peg.
   this.pop = popDisk			// Remove a disk from a peg.
   this.animate = animateDisk		// Display a disk move or drop.
   // Puzzle solution logic.
   this.move = moveDisk			// Move a disk.
   this.swap = swapDisks
   this.sideSwap = swapSideDisks
   this.sink = sinkDisk
   this.float = floatDisk
   this.stackOut = moveStackOut
   this.stackIn = moveStackIn
   this.Y = Y_base
   this.Y_not = Y_not
   this.YZ = YZ
   this.W = W
   this.V = V
   this.solve = solvePuzzle		// Display the puzzle solution.
   this.undo = undoMove			// Undo the previous move.
   this.setSpeed = animationSpeed

   this.display = new Display ( size )

   // Properties.
   this.size = size			// Number of disks in the puzzle.
   this.leftEdge = graphics.leftMargin + graphics.blockWidth
							- graphics.innerMargin
   this.leftDrag = this.leftEdge + 1
   this.rightEdge = graphics.leftMargin
	+ graphics.blockWidth * ( 6 * ( size + 2 ) + 1 ) + graphics.innerMargin
   this.rightDrag = this.rightEdge - 1
   this.floor = graphics.topMargin + graphics.blockHeight * size
   this.pegTops = graphics.topMargin + graphics.halfHeight
							- graphics.blockHeight
   this.overlap = graphics.overlap + graphics.blockWidth + 2
   this.unsolved = 2 * size

   this.activeDisk = -1			// Disk in transit.
   this.pegs = new Array()		// Peg states.
   this.disks = new Array()		// Disk states.
   this.moveCount = 0
   this.undoMoves = new Array()		// Previous moves.

   // Left peg.
   this.pegs[0] = new Peg ( 0, this.size + 1, pegX, this.floor )

   width = graphics.blockWidth * ( size + size + 4 )
   pegX += width

   // Center peg.
   this.pegs[1] = new Peg ( 1, this.size + 1, pegX, this.floor )

   pegX += width

   // Right peg.
   this.pegs[2] = new Peg ( 2, this.size + 1, pegX, this.floor )

   width = 28 * graphics.blockWidth

   while ( index > 0 ) {		// Create disks and stack them on the
					// left and right pegs.
	index--
	this.disks[index] = new Disk ( index, width, diskFloor )

	if ( index < this.unsolved ) this.push ( index, 2 )

	index--
	this.disks[index] = new Disk ( index, width, diskFloor )

	if ( index < this.unsolved ) this.push ( index, 0 )

	diskFloor -= graphics.blockHeight
	width -= ( graphics.blockWidth + graphics.blockWidth ) 
   }

   this.animation = null
   this.speed = 16			// Move 16 pixels per frame.
   this.timeInc = 12			// Wait 12 milliseconds between frames.
   this.solving = false			// Computer or player is in control.

   this.setSpeed ( speedIndex )

   loaded = true
}
