/*********************************************************************
/ Jigsaw.js
/
/ JavaScript functions for implementing a jigsaw puzzle.  See also
/ Jigsaw.asp and Jigsaw.css.
/
/ The puzzle is made from an image that has been sliced into 58
/ irregularly shaped pieces.  The pieces can be dragged around the
/ window and dropped into position.  If a piece is dropped close
/ enough (as defined by g_tolerance) to its correct position, it
/ sticks there and can't be moved again.
/
/ When the HTML page is unloaded, the user is prompted to save the
/ state of the puzzle.  If the user chooses 'OK', the position of
/ each piece is written to a cookie (cookies must be enabled to save
/ the puzzle).  The next time the page is loaded, the positions are
/ read from the cookie.
/
/ 1999.6  SJL  Initial write.
/ 1999.10 SJL  Include support for Netscape 4+.
*********************************************************************/

var mintNumPieces;      // The number of pieces in the puzzle
var mintNumSolved = 0;  // The number of pieces in their correct positions
var mstrPieceID = null; // The ID of the currently-selected piece, if any (NS)
var mobjPiece = null;   // The currently-selected piece, if any (IE)
var mintTolerance = 8;  // How close a piece must be to be considered OK
var mintXdiff = null;   // The difference between the event's x, y position
var mintYdiff = null;   //    and the top left corner of the current piece
var mintMaxZ = 1;       // The maximum z-index of any piece.  Used to
                        //    bring pieces to the foreground

var mbooIE = (document.all ? true : false);     // Is this Internet Explorer?
var mbooNS = (document.layers ? true : false);  // Is this Netscape
var mbooMac = ((navigator.userAgent.toLowerCase()).indexOf ("mac") != -1);
                                                // Is this a Mac?


/*********************************************************************
/ Set some event handler calls.
/ The calls and syntax differ depending on the browser.
*********************************************************************/

if (! (mbooIE || mbooNS))
        alert ("This page requires one of the following browsers to work " +
                "correctly:\nInternet Explorer 4.0 or higher\nNetscape Navigator " +
                "4.0 or higher");
else
{
        window.onload = initPuzzle;
        window.onunload = unloadPuzzle;

        if (mbooNS)
        {
                document.captureEvents (Event.MOUSEDOWN | Event.MOUSEMOVE | Event.MOUSEUP);
                document.onmousedown = pickUpPiece;
        }

        if (mbooIE)
        {
                if (mbooMac)
                        document.onmousedown = pickUpPiece;
                else
                        document.ondragstart = pickUpPiece;
        }

        document.onmousemove = movePiece;
        document.onmouseup = releasePiece;
}


/*********************************************************************
/ initPuzzle sets up the initial display of the puzzle.  It reads a
/ cookie called "jigsaw" to see if the user saved a previous puzzle.
/ If the cookie exists and is well-formed (see decodePositions), the
/ saved positions are restored.  Otherwise, the pieces are placed
/ randomly around the screen.
*********************************************************************/

function initPuzzle ()
{
        var strCookie = getCookie ("jigsaw");

        defineSolution ();

        if (! decodePositions (strCookie))
                shuffle ();

        deleteCookie ("jigsaw");

        if (mbooIE)
        {
                document.all.shuffle.onclick = shuffle;
                if (mbooMac)
                {
                        document.all ("puzzleframe").style.width = 644;   // no idea why
                        document.all ("puzzleframe").style.height = 484;
                        alert ("The Mac Internet Explorer explorer version is still " +
                                "kind of quirky.  Sorry!");
                }
        }
}


/*********************************************************************
/ setPieceCount defines the number of pieces.  It is called from
/ Jigsaw.asp.
*********************************************************************/

function setPieceCount (pintNumPieces)
{
        mintNumPieces = pintNumPieces;
}


/*********************************************************************
/ shuffle places the pieces randomly around the screen.
*********************************************************************/

function shuffle ()
{
        var i;

        for (i = 1; i <= mintNumPieces; i++)
                positionPiece (i);

        mintNumSolved = 0;
}


/*********************************************************************
/ showHidden brings pieces that are buried behind other pieces to the
/ foreground.  It is only necessary in the Netscape version, where
/ pieces that have a higher z-index can sometimes be displayed behind
/ pieces with a lower z-index.  This function refreshes all z-index
/ values to force the moveable pieces to display in front of the
/ fixed (solved) pieces.
*********************************************************************/

function showHidden ()
{
        var i;

        if (mbooNS)
                for (i = 1; i <= mintNumPieces; i++)
                        with (document[getID (i)])
                                if ((left == realLeft) && (top == realTop))
                                        zIndex = -1;
                                else
                                        zIndex = 1;
}


/*********************************************************************
/ positionPiece places one piece.  If pintXpos and Ypos are given,
/ they are used.  Otherwise, they are assigned random values.
*********************************************************************/

function positionPiece (pintPieceNum, pintXpos, pintYpos)
{
        if (pintXpos == null)   pintXpos = f_random (700);
        if (pintYpos == null)   pintYpos = f_random (400);

        if (mbooNS)
        {
                document[getID (pintPieceNum)].left = pintXpos;
                document[getID (pintPieceNum)].top = pintYpos;
        }
        else   // mbooIE
        {
                document.all(getID (pintPieceNum)).style.pixelLeft = pintXpos;
                document.all(getID (pintPieceNum)).style.pixelTop = pintYpos;
        }
}


/*********************************************************************
/ defineSolution sets up the style properties that tell where a piece
/ is supposed to go.  This information should be defined in
/ Jigsaw.css, but it doesn't seem to work from there.
*********************************************************************/

function defineSolution ()
{
        var i;

        var aXpos = new Array
                (0, 67, 198, 278, 359, 409, 486, 574, 0, 271,
                480, 560, 142, 42, 204, 341, 419, 502, 585, 0,
                418, 270, 343, 200, 139, 353, 261, 401, 485, 568,
                73, 183, 571, 0, 239, 352, 467, 0, 72, 203,
                423, 167, 0, 79, 189, 264, 366, 407, 498, 558,
                0, 42, 140, 217, 312, 398, 491, 577);

        var aYpos = new Array
                (0, 0, 0, 0, 0, 0, 0, 0, 53, 45,
                35, 27, 0, 95, 68, 71, 72, 125, 118, 127,
                146, 125, 159, 115, 85, 194, 193, 213, 193, 213,
                191, 207, 273, 189, 287, 258, 265, 253, 249, 245,
                278, 272, 338, 353, 340, 360, 334, 350, 338, 357,
                401, 403, 400, 396, 419, 410, 425, 404);

        for (i = 1; i <= mintNumPieces; i++)
        {
                if (mbooNS)
                {
                        document[getID (i)].realLeft = aXpos [i-1] + 12;   // hard-code 12?
                        document[getID (i)].realTop  = aYpos [i-1] + 12;
                }
                else   // mbooIE
                {
                        document.all (getID (i)).style.realLeft = aXpos [i-1];
                        document.all (getID (i)).style.realTop  = aYpos [i-1];
                }
        }
}


/*********************************************************************
/ getIDfromSrc returns a document ID based on the src attribute of
/ an img tag.  It works because all the images for this page are
/ named ".../pieceXX.gif" where XX is a number between 01 and 58.
/ The ID associated with each image is "pieceXX", so we can extract
/ an ID from an image src.  This is only needed for Netscape.
*********************************************************************/

function getIDfromSrc (pstrSrc)
{
        var strTemp = null;

        if (pstrSrc)
                strTemp = pstrSrc.substr (pstrSrc.indexOf ("piece"), 7);

        return strTemp;
}


/*********************************************************************
/ getID takes a piece number (an integer between 1 and 58) and returns
/ an id of the format "pieceXX" where XX is between 01 and 58.
*********************************************************************/

function getID (pintPieceNum)
{
        return ("piece" + (pintPieceNum < 10 ? "0" : "") + pintPieceNum);
}


/*********************************************************************
/ pickUpPiece controls what happens when the user starts dragging
/ a piece.  The "e" parameter is Netscape's event object, and must
/ be passed in.  IE does not need the "e".
/ If neither mbooIE nor mbooNS is true, the function does nothing.
*********************************************************************/

function pickUpPiece (e)
{
        if (mbooNS)
        {
                mstrPieceID = getIDfromSrc ((e.target).src);
                if (mstrPieceID)
                {
                        with (document[mstrPieceID])
                        {
                                if ((left == realLeft) && (top == realTop))
                                        mstrPieceID = null;
                                else
                                {
                                        mintXdiff = e.pageX - left;
                                        mintYdiff = e.pageY - top;
                                        zIndex = ++mintMaxZ;
                                }
                        }

                        return false;
                }
                else
                        return true;
        }

        if (mbooIE)
        {
                mobjPiece = window.event.srcElement.parentElement;

                if (mobjPiece.id != "")
                {
                        if ((mobjPiece.style.pixelTop == mobjPiece.style.realTop) &&
                                (mobjPiece.style.pixelLeft == mobjPiece.style.realLeft) &&
                                (mobjPiece.style.zIndex < 0))
                        {
                                mobjPiece = null;
                        }
                        else
                        {
                                if (mintXdiff == null)
                                {
                                        mobjPiece.style.zIndex = ++mintMaxZ;
                                        mintXdiff = window.event.clientX - mobjPiece.style.pixelLeft;
                                        mintYdiff = window.event.clientY - mobjPiece.style.pixelTop;
                                }
                                event.returnValue = false;
                                event.cancelBubble = true;
                        }
                }
        }
}


/*********************************************************************
/ movePiece controls what happens when the user moves the mouse
/ while dragging a piece.  The "e" parameter is Netscape's event
/ object, and must be passed in.  IE does not need the "e".
/ If neither mbooIE nor mbooNS is true, the function does nothing.
*********************************************************************/

function movePiece (e)
{
        if (mbooNS)
        {
                if (mstrPieceID)
                {
                        document[mstrPieceID].moveTo (e.pageX - mintXdiff, e.pageY - mintYdiff);
                        return (false);
                }
                else
                        return (true);
        }

        if (mbooIE)
        {
                if (mobjPiece != null)
                {
                        mobjPiece.style.pixelLeft = window.event.clientX - mintXdiff;
                        mobjPiece.style.pixelTop  = window.event.clientY - mintYdiff;
                        event.cancelBubble = true;
                }
        }
}


/*********************************************************************
/ releasePiece controls what happens when the user releases the mouse
/ button while dragging a piece.  The "e" parameter is Netscape's
/ event object, and must be passed in.  IE does not need the "e".
/ If neither mbooIE nor mbooNS is true, the function does nothing.
*********************************************************************/

function releasePiece (e)
{
        var i;
        var xdiff;
        var ydiff;

        if (mbooNS)
        {
                if (mstrPieceID)
                {
                        with (document[mstrPieceID])
                        {
                                moveTo (e.pageX - mintXdiff, e.pageY - mintYdiff);

                                xdiff = Math.abs (left - realLeft);
                                ydiff = Math.abs (top - realTop);

                                if (xdiff <= mintTolerance && ydiff <= mintTolerance)
                                {
                                        moveTo (realLeft, realTop);
                                        zIndex = -1;
                                        if (++mintNumSolved == mintNumPieces)
                                                alert ("Congratulations!");
                                }
                        }

                        mstrPieceID = null;
                        return (false);
                }
                else
                        return (true);
        }

        if (mbooIE)
        {
                if (mobjPiece != null)
                {
                        xdiff = Math.abs (mobjPiece.style.pixelLeft - mobjPiece.style.realLeft);
                        ydiff = Math.abs (mobjPiece.style.pixelTop - mobjPiece.style.realTop);

                        if (xdiff <= mintTolerance && ydiff <= mintTolerance)
                        {
                                mobjPiece.style.pixelLeft = mobjPiece.style.realLeft;
                                mobjPiece.style.pixelTop = mobjPiece.style.realTop;
                                mobjPiece.style.zIndex = -1;
                                if (++mintNumSolved == mintNumPieces)
                                        alert ("Congratulations!");
                        }
                        mobjPiece = null;
                        mintXdiff = null;
                        mintYdiff = null;
                        event.returnValue = false;
                        event.cancelBubble = true;
                }
        }
}


/*********************************************************************
/ unloadPuzzle tries to save the current status of the puzzle by
/ setting a cookie named "jigsaw".  The user's browser must be set
/ to accept cookies for this to work.  If it fails, the puzzle is not
/ saved, and the next time the user comes to this page, the pieces
/ are scrambled randomly again.
*********************************************************************/

function unloadPuzzle ()
{
        setCookie ("jigsaw", encodePositions ());
}


/*********************************************************************
/ encodePositions returns a coded string that describes the positions
/ of all the pieces.  This string is what is saved in the "jigsaw"
/ cookie.  The format of the string is:
/
/ xpos1+ypos1+xpos2+ypos2+...+ypos58
/
/ For example:
/
/ 56+153+99+385+642+115+...+405+222
*********************************************************************/

function encodePositions ()
{
        var i;
        var s = "";   // format: <xpos1>+<ypos1>+<xpos2>... 15+145+97+103+...

        for (i = 1; i <= mintNumPieces; i++)
        {
                if (mbooNS)
                        s += "+" + document[getID (i)].left + "+" + document[getID (i)].top;
                else    // mbooIE
                        s += "+" + document.all(getID (i)).style.pixelLeft + "+" +
                                document.all(getID (i)).style.pixelTop;
        }

        return s.substr (1);
}


/*********************************************************************
/ decodePositions takes a string of the format defined in
/ encodePositions and moves the pieces to the specified positions.
/ If everything works, the function returns true.  A return value of
/ false means an error occurred.
*********************************************************************/

function decodePositions (strCookie)
{
        var a;
        var xpos;
        var ypos;
        var i;
        var objPiece;
        var allOK = (strCookie != null);

        if (strCookie != null)
        {
                a = strCookie.split ("+");
                if (a.length == 2 * mintNumPieces)
                {
                        for (i = 1; i <= mintNumPieces; i++)
                        {
                                xpos = parseInt (a[2 * i - 2] );
                                ypos = parseInt (a[2 * i - 1] );

                                if (isNaN (xpos) || isNaN (ypos))
                                        allOK = false;
                                else
                                {
                                        positionPiece (i, xpos, ypos);

                                        if (mbooNS)
                                        {
                                                with (document[getID (i)])
                                                {
                                                        if ((left == realLeft) && (top == realTop))
                                                        {
                                                                zIndex = -1;
                                                                mintNumSolved++;
                                                        }
                                                }
                                        }
                                        else   // mbooIE
                                        {
                                                objPiece = document.all (getID (i));
                                                if ((objPiece.style.pixelLeft == objPiece.style.realLeft) &&
                                                         (objPiece.style.pixelTop == objPiece.style.realTop))
                                                {
                                                        objPiece.style.zIndex = -1;
                                                        mintNumSolved++;
                                                }
                                        }
                                }
                        }
                }
        }

        return allOK;
}

