/* Javascript for tic-tac-toe game on page tictactoe_js.html */

/* function Debug ----------------------------------------------------------
 Checks the value of the 'debug' form element on the web page, and returns
 'true' or 'false' accordingly.
---------------------------------------------------------------------------- */
function Debug() {
    var debugElementValue = document.getElementById( 'debug' ).value;
    if ( debugElementValue == 'true' ) { return true; }
    else { return false; }
}
/* ------------------------------------------------------ end function Debug */


/* function ProcessMove ----------------------------------------------------
 Manages cascade of actions based on which square the user clicks
---------------------------------------------------------------------------- */
function ProcessMove( positionID ) {

    var winMessage;

    MarkMove( positionID, 'user' );
    var game = CheckForWin();

    if ( game == 'X' ) {                // user's last move is a winner
        if ( Debug() ) { alert( "User wins!" ); }
        RegisterWin( 'user' );
    }
    else if ( game == 'cat' ) {         // check for cat game
        if ( Debug() ) { alert( "Cat game." ); }
        RegisterWin( 'cat' );
    }
    else {                          // let the computer move
        var compPosID  = DecideComputerMove();
        MarkMove( compPosID, 'comp' );
        game = CheckForWin();
        if ( game == 'O' ) {        // computer's move is a winner
            if ( Debug() ) { alert( "Computer wins!" ); }
            RegisterWin( 'comp' );
        }
        else {                      // no winner, game continues
            if ( Debug() ) { alert( "Keep going..." ); }
            document.getElementById( 'message' ).innerHTML
                = PickMessage( 'cont' );
        }
    }
}
/* ------------------------------------------------ end function ProcessMove */


/* function MarkMove -------------------------------------------------------
 Changes the appropriate square (as indicated by the position ID) in the
 game grid to show an 'X' or 'O' (depending on the player), adds an 'X' or 'O'
 to the 'state' value string at the appropriate place, and increments 'moves'.
---------------------------------------------------------------------------- */
function MarkMove( positionID, player ) {

    if ( player == 'user' ) {
        gif = 'ex.gif';
        mark = 'X';
    }
    else {
        gif = 'oh.gif';
        mark = 'O';
    }

    var gridCell = document.getElementById( positionID );

    // de-activate the onClick link and put an 'X' or 'O' gif there
    gridCell.innerHTML = "\n\
            <a href=\"\" onClick=\"return false;\">\n\
                <img border=\"0\" src=\"../images/tictactoe/" + gif + "\">\n\
            </a>";

    // record move in the 'state' hidden input element string
    var stateElement   = document.getElementById( 'state' );
    var stateValue     = stateElement.value;
    var stateIndex     = GetStateIndexFromPositionID( positionID );
    stateElement.value = stateValue.substring( 0, stateIndex )
                       + mark
                       + stateValue.substring( stateIndex+1 );

    document.getElementById( 'moves' ).value++;     // increment 'moves'
}
/* --------------------------------------------------- end function MarkMove */


/* function DecideComputerMove ---------------------------------------------
 Decides on the computer's move, based on a combination of randomness
 (as dictated by the 'dumbness' parameter) and logic.
---------------------------------------------------------------------------- */
function DecideComputerMove() {

    // retrieve the current game board state, via the 'state' string
    var stateElement = document.getElementById( 'state' );
    var stateString  = stateElement.value;

    var stateIndex;   // use this to keep track of chosen move

    /* occasionally make silly moves */
    var rand = Math.floor( 1+Math.random()*10 );
    var dumbness = document.getElementById( 'dumbness' ).value;
    if ( rand < dumbness ) {
        var stateChar;
        // pick a random empty square
        do {
            stateIndex = Math.floor( Math.random()*9 );
            stateChar  = stateString.substring( stateIndex, stateIndex+1 );
        } while ( stateChar != '-' );
        if ( Debug() ) { alert( "random computer move " + stateIndex + " made" ) };
    }

    /* if a random move isn't made, use logic to find a good move */
    else {

        var slicesArray = MakeSlices();

        /* look for any winning moves first */
        if ( Debug() ) { alert( "Looking for a winner for me..." ); }
        for ( sliceNum=0; sliceNum<8; sliceNum++ ) {

            thisSliceSumm = SummarizeSlice( slicesArray[sliceNum] );

            // if this slice has two O's and a space in it,
            if ( thisSliceSumm['O']==2 && thisSliceSumm['-']==1 ) {
                // this slice has a winning move in it for the computer
                // find it and return that move
                spaceLocation = FindSpace( slicesArray[sliceNum] );
                stateIndex    = GetStateIndexFromSliceInfo( spaceLocation, sliceNum );
                return GetPositionIDFromStateIndex( stateIndex );
            }
        }

        /* if there are no winners, look for opponent winners to block next */
        if ( Debug() ) { alert( "No winners for me...looking for a block..." ); }
        for ( sliceNum=0; sliceNum<8; sliceNum++ ) {

            thisSliceSumm = SummarizeSlice( slicesArray[sliceNum] );

            // if this slice has two X's and a space in it,
            if ( thisSliceSumm['X']==2 && thisSliceSumm['-']==1 ) {
                // this slice has a winning move in it for the opponent
                // find the space and return that move, to block
                spaceLocation = FindSpace( slicesArray[sliceNum] );
                stateIndex    = GetStateIndexFromSliceInfo( spaceLocation, sliceNum );
                return GetPositionIDFromStateIndex( stateIndex );
            }
        }

        // will want the 'state' marks in a convenient form now
        stateArray = stateString.split( '' );
        // randomize some arrays of corner and side indices
        var corners = new Array ( 0, 2, 6, 8 ); fisherYates( corners );
        var sides   = new Array ( 1, 3, 5, 7 ); fisherYates( sides );
        if ( Debug() ) { alert( "Arrays shuffled" ); }

        // I STILL have to check for the diagonal gambit!
        if ( Debug() ) { alert( "Nothing to block.  Checking for diagonal gambit..." ); }
        //           if there are exactly 3 marks,      and  we have the center,
        if ( document.getElementById('moves').value == 3 && stateArray[4] == 'O' ) {
            // look at the two diagonals;
            slice6Summ = SummarizeSlice( slicesArray[6] );
            slice7Summ = SummarizeSlice( slicesArray[7] );
            // if all 3 marks are in one diagonal (XOX), there is a danger
            if ( slice6Summ['-'] == 0 || slice7Summ['-'] == 0 ) {
                if ( Debug() )
                    alert( "marking a side square to avoid the diagonal gambit" );
                return GetPositionIDFromStateIndex( sides[0] );
            }
        }
        /*
        Next, look for 'forks' - moves which create two winning moves for next turn.
        These are found by looking for the intersection of two slices which each
        have one mark (of the same sort) and two spaces.  Move into either an X fork
        (to set up a win) or or an O fork (to block a move setting up a win for the
        opponent). 
        */
        if ( Debug() ) { alert( "Diagonal gambit doesn't apply.  Looking for forks..." ); }
        for ( slice1Num=0; slice1Num<8; slice1Num++ ) {

            firstSliceSumm = SummarizeSlice( slicesArray[slice1Num] );

            for ( slice2Num=slice1Num+1; slice2Num<8; slice2Num++ ) {

                secondSliceSumm = SummarizeSlice( slicesArray[slice2Num] );

                // check fork condition
                if ( (firstSliceSumm['-']==2 && secondSliceSumm['-']==2)
                      && (firstSliceSumm['X']==secondSliceSumm['X']) ) {
                    // might be a fork, let's see
                    if ( Debug() )
                        { alert( "Checking a possible fork where slices " + slice1Num
                               + " and " + slice2Num + " cross" ); }
                    posID = FindPositionIDofIntersectionOfSliceNumbers( slice1Num, slice2Num );
                    if ( posID ) {  // intersection exists
                        // make sure the intersection isn't already marked - not a fork, then
                        index = GetStateIndexFromPositionID( posID );
                        if ( stateArray[index] == '-' ) {
                            // find the intersection, return that move
                            if ( Debug() ) { alert( "Found a fork - slices " + slice1Num
                                                  + " and " + slice2Num ); }
                            return posID;
                        }
                        else if ( Debug() ) {
                            alert( "That intersection isn't open" );
                        }
                    }
                    else if ( Debug() ) {
                        alert( "That isn't a real intersection" );
                    }
                }   // end 'if each slice has 2 spaces (and the same mark)'
            }   // end inner loop on remaining slices
        }   // end loop on all slices
        if ( Debug() ) { alert( "No forks either!  Moving by rank." ); }

        /* if all else fails, mark a high-ranked spot */
        // the center, if it's open
        if ( stateArray[4] == '-' ) { stateIndex = 4; }
        // corners are next best
        else if ( stateArray[ corners[0] ] == '-' ) { stateIndex = corners[0]; }
        else if ( stateArray[ corners[1] ] == '-' ) { stateIndex = corners[1]; }
        else if ( stateArray[ corners[2] ] == '-' ) { stateIndex = corners[2]; }
        else if ( stateArray[ corners[3] ] == '-' ) { stateIndex = corners[3]; }
        // top, right, bottom, and left middles are the least desireable
        else if ( stateArray[ sides[0] ] == '-' ) { stateIndex = sides[0]; }
        else if ( stateArray[ sides[1] ] == '-' ) { stateIndex = sides[1]; }
        else if ( stateArray[ sides[2] ] == '-' ) { stateIndex = sides[2]; }
        else if ( stateArray[ sides[3] ] == '-' ) { stateIndex = sides[3]; }
    }
    return GetPositionIDFromStateIndex( stateIndex );
}
/* ----------------------------------------- end function DecideComputerMove */


/* function MakeSlices -----------------------------------------------------
 Creates array of 'slices' of the gameboard - starts from the 'state' element.
---------------------------------------------------------------------------- */
function MakeSlices() {

    var stateString  = document.getElementById( 'state' ).value;
    var state        = stateString.split( '' );

    var topRow       = new Array( state[0], state[1], state[2] );
    var middleRow    = new Array( state[3], state[4], state[5] );
    var bottomRow    = new Array( state[6], state[7], state[8] );
    var leftColumn   = new Array( state[0], state[3], state[6] );
    var middleColumn = new Array( state[1], state[4], state[7] );
    var rightColumn  = new Array( state[2], state[5], state[8] );
    var NWtoSEdiag   = new Array( state[0], state[4], state[8] );
    var NEtoSWdiag   = new Array( state[2], state[4], state[6] );

    var slicesArray  = new Array( topRow, middleRow, bottomRow,
                                  leftColumn, middleColumn, rightColumn,
                                  NWtoSEdiag, NEtoSWdiag );

    return slicesArray;
}
/* ------------------------------------------------- end function MakeSlices */


/* function SummarizeSlice -------------------------------------------------
 Analyzes an array 'slice' and returns a summary of its contents.
---------------------------------------------------------------------------- */
function SummarizeSlice( slice ){

    var sliceSumm = { "-": 0, "X": 0, "O": 0 };

    for ( i=0; i<3; i++ ) {
        sliceSumm[ slice[i] ]++;
    }

    return sliceSumm;
}
/* --------------------------------------------- end function SummarizeSlice */


/* function FindSpace ------------------------------------------------------
 Analyzes an array 'slice' and returns the position of the first space there.
---------------------------------------------------------------------------- */
function FindSpace( slice ){

    for ( i=0; i<3; i++) {
        if ( slice[i] == '-' )
        return i;
    }
    return -1;
}
/* -------------------------------------------------- end function FindSpace */


/* function GetStateIndexFromPositionID -------------------------------------
 Converts a position ID from the game board table (i.e., 21 for 2nd row, 
 left-hand square) to an index, between 0 and 8, for the 'state' string.
---------------------------------------------------------------------------- */
function GetStateIndexFromPositionID( positionID ){

    var coordinates = positionID.split( '' );
    var stateIndex  = 3*(coordinates[0]-1) + 1*coordinates[1] - 1;

    if ( Debug() ) { alert( "That's state string position " + stateIndex ) };
    return stateIndex;
}
/* --------------------------------- end function GetStateIndexFromPositionID */


/* function GetPositionIDFromStateIndex -------------------------------------
 Converts a character index in the 'state' string  to a position ID on the
 game board table (i.e., 21 for 2nd row, left-hand square).
---------------------------------------------------------------------------- */
function GetPositionIDFromStateIndex( stateIndex ){

    var row = Math.floor( stateIndex/3 ) + 1;
    var col = stateIndex%3 + 1;
    var positionID = row + '' + col;

    if ( Debug() ) { alert( "State string position " + stateIndex
                          + " corresponds to position ID " + positionID ) };
    return positionID;
}
/* --------------------------------- end function GetPositionIDFromStateIndex */


/* function GetStateIndexFromSliceInfo --------------------------------------
 Takes a slice number and an index i in it, and returns the corresponding
 index in the 'state' string.  ASSUMES how slices are made.
---------------------------------------------------------------------------- */
function GetStateIndexFromSliceInfo( i, sliceNum ){

    if ( sliceNum<3 ) {
        rowIndex = sliceNum;
        colIndex = i;
    }
    else if ( sliceNum<6 ) {
        rowIndex = i;
        colIndex = sliceNum - 3;
    }
    else if ( sliceNum==6 ) {
        rowIndex = i;
        colIndex = i;
    }
    else {
        rowIndex = i;
        colIndex = 2 - i;
    }

    return 3*rowIndex + colIndex;
}
/* ---------------------------------- end function GetStateIndexFromSliceInfo */


/* function FindPositionIDofIntersectionOfSliceNumbers ---------------------
 Takes a pair of slice numbers, and returns the position ID (i.e. RC on the
 gameboard) corresponding to their intersection.
 If there is no intersection, returns NULL.  ASSUMES how slices are made.
---------------------------------------------------------------------------- */
function FindPositionIDofIntersectionOfSliceNumbers( slice1Num, slice2Num ) {

    // if the two slices are the same, no intersection is possible
    if ( slice1Num == slice2Num ) { return null; }

    // if both are horizontal, or both vertical, no intersection is possible
    if ( slice1Num < 3 && slice2Num < 3 ) { return null; }
    if ( (slice1Num>2 && slice1Num<6) && (slice2Num>2 && slice2Num<6) )
        { return null; }

    // case the rest out
    if ( slice1Num < 3 ) {              // slice2Num can't be < 3 (see above)
        rowIndex = slice1Num + 1;
        if ( slice2Num < 6 ) {
            colIndex = slice2Num - 2;
        }
        else if ( slice2Num == 6 ) {
            colIndex = rowIndex;
        }
        else {
            colIndex = 4 - rowIndex;
        }
    }
    else if ( slice1Num < 6 ) {         // slice2Num must be 6 or 7 now
        colIndex = slice1Num - 2;
        if ( slice2Num == 6 ) {
            rowIndex = colIndex;
        }
        else {
            rowIndex = 4 - colIndex;
        }
    }
    else {                              // slices are 6 and 7 (the diagonals)
        rowIndex = 2;
        colIndex = 2;
    }
    if ( Debug() )
        alert( "The intersection of slices " + slice1Num + " and " + slice2Num
               + " is at " +  rowIndex + '' + colIndex );
    return rowIndex + '' + colIndex;
}
/* ----------------- end function FindPositionIDofIntersectionOfSliceNumbers */


/* function CheckForWin ----------------------------------------------------
 Analyzes 'slices' array, looking for '3-in-a-rows'.
 If one is found, the letter ('X' or 'O') that has the win is returned.
 If no '3-in-a-rows' are found, returns 'cat' if all 9 moves have been made, 
 otherwise returns 'continues'.
---------------------------------------------------------------------------- */
function CheckForWin() {

    var slicesArray = MakeSlices();

    for ( i=0; i<8; i++ ) {
        if (    slicesArray[i][0] != '-'
             && slicesArray[i][0] == slicesArray[i][1]
             && slicesArray[i][0] == slicesArray[i][2] ) {
            if ( Debug() ) { alert( "CheckForWin sez " + slicesArray[i][0] ) };
            return slicesArray[i][0];
        }
    }
    if ( document.getElementById( 'moves' ).value > 8 ) {
        if ( Debug() ) { alert( "CheckForWin sez 'cat'" ) };
        return 'cat';
    }
    if ( Debug() ) { alert( "CheckForWin sez 'continues'" ) };
    return 'continues';
}
/* ------------------------------------------------ end function CheckForWin */


/* function PickMessage ----------------------------------------------------
 Randomly picks a 'game over' or continuing message -
 takes a game state as a parameter.
---------------------------------------------------------------------------- */
function PickMessage( gameState ) {
    var userwins = new Array( "Congratulations!",
                              "You think you&rsquo;re so smart...",
                              "You must have cheated! (Just kidding)",
                              "Alright!",
                              "Good game!",
                              "Nice work!",
                              "Brilliant!",
                              "You&rsquo;re a tic-tac-toe wizard!",
                              "You win!" );
    var compwins = new Array( "Computer wins!",
                              "Applause for computer!",
                              "Who&rsquo;s your daddy...",
                              "Caught you by surprise, didn&rsquo;t I?",
                              "Better luck next game.",
                              "Who says my intelligence is &lsquo;artificial&rsquo;?",
                              "No need to grovel...",
                              "Would you like some lessons?" );
    var catgame  = new Array( "The cat takes another one",
                              "Mee-ow!" );
    var nowinner = new Array( "That&rsquo;s a pretty good move...",
                              "Are you sure about that move?",
                              "Keep trying, you&rsquo;ll win eventually...",
                              "I&rsquo;m going to beat you!",
                              "C&rsquo;mon, move already!",
                              "Go ahead, make my day.",
                              "You&rsquo;re doing great!" );
    var message = '';
    var pick;

    if ( gameState == 'user' ) {
        pick = Math.floor( Math.random()*userwins.length );
        message = userwins[pick];
    }
    else if ( gameState == 'comp' ) {
        pick = Math.floor( Math.random()*compwins.length );
        message = compwins[pick];
    }
    else if ( gameState == 'cat' ) {
        pick = Math.floor( Math.random()*catgame.length );
        message = catgame[pick];
    }
    else if ( gameState == 'cont' ) {
        pick = Math.floor( Math.random()*nowinner.length );
        message = nowinner[pick];
    }
    else { message = 'What happened? Did anyone win?'; }

    return message;
}
/* ------------------------------------------------ end function PickMessage */


/* function PickBackground -------------------------------------------------
 Takes a game winner string as a parameter, and randomly picks an
 appropriate background picture for the gameboard.
---------------------------------------------------------------------------- */
function PickBackground( winner ) {
    var userwins = new Array( "celebrate.gif",
                              "colorful_balloons.jpg",
                              "fireworks_materials.jpg",
                              "fireworks.jpg",
                              "fireworks2.jpg",
                              "HotAirBalloon.jpg",
                              "party_abstract.jpg",
                              "BALLOONS3.jpg" );
    var compwins = new Array( "jollyroger.gif",
                              "jollyroger.gif",
                              "jollyroger.gif",
                              "jollyroger.gif" );
    var catgame  = new Array( "White_Lion.jpg",
                              "Black_cat.jpg",
                              "blackcat.jpg",
                              "Big-White-Cat-Small-Black-Cat.jpg",
                              "worlds-ugliest-cat.jpg" );
    var background = '';
    var pick;

    if ( winner == 'user' ) {
        pick = Math.floor( Math.random()*userwins.length );
        background = userwins[pick];
    }
    else if ( winner == 'comp' ) {
        pick = Math.floor( Math.random()*compwins.length );
        background = compwins[pick];
    }
    else if ( winner == 'cat' ) {
        pick = Math.floor( Math.random()*catgame.length );
        background = catgame[pick];
    }
    else { background = 'What happened? Did anyone win?'; }

    return background;
}
/* --------------------------------------------- end function PickBackground */


/* function RegisterWin ----------------------------------------------------
 Freezes the gameboard so no new moves can be made without resetting.
 Depending on winner, function also
  - changes the background of the game board table,
  - updates the scores table, and
  - displays a message.
---------------------------------------------------------------------------- */
function RegisterWin( winner ) {
    if ( Debug() ) { alert( "registering win for " + winner ); }

    // freeze the board (switch off remaining active onClick links)
    var gridCell;
    for ( x=1; x<=3; x++ ) {
        for ( y=1; y<=3; y++ ) {
            positionID = x + '' + y;
            gridCell   = document.getElementById( positionID );
            state      = document.getElementById( 'state' ).value;
            stateIndex = GetStateIndexFromPositionID( positionID );
            mark       = state.substring( stateIndex, stateIndex+1 );
            if ( mark == '-' ) {
                gridCell.innerHTML = "\n\
            <a href=\"\" onClick=\"return false;\">\n\
                <img border=\"0\" src=\"../images/tictactoe/blank.gif\">\n\
            </a>";
            }
        }
    }

    // change board background
    var boardTable = document.getElementById( 'gameboard' );
    var bgdImage   = PickBackground( winner );
    boardTable.style.backgroundImage    = 'url(../images/tictactoe/' + bgdImage + ')';

    // hide the raz message, and display a winner message
    document.getElementById(   'message'  ).innerHTML = '&nbsp;';
    document.getElementById( 'winMessage' ).innerHTML = PickMessage( winner );

    // increment score
    var winnerTableCell = document.getElementById( winner + 'Games' );
    var score = winnerTableCell.innerHTML;
    score++;
    winnerTableCell.innerHTML = score;

}
/* ------------------------------------------------ end function RegisterWin */


/* function SetUpNewGame ---------------------------------------------------
 Resets most, but not all of the web page conditions at page load.
 Leaves the score fields unchanged.
---------------------------------------------------------------------------- */
function SetUpNewGame() {

    // clear the game board
    var gridCell;
    for ( x=1; x<=3; x++ ) {
        for ( y=1; y<=3; y++ ) {
            gridCell = document.getElementById( x + '' + y );
            gridCell.innerHTML = "\n\
            <a href=\"\" onClick=\"ProcessMove( '" + x + '' + y + "' ); return false;\">\n\
                <img border=\"0\" src=\"../images/tictactoe/blank.gif\">\n\
            </a>";
        }
    }
    document.getElementById( 'gameboard' ).background = '';
    
    // clear the background image
    document.getElementById( 'gameboard' ).style.backgroundImage = '';

    // clear game state storage
    document.getElementById( 'state'  ).value = '---------';
    document.getElementById( 'moves' ).value = '0';

    // clear winner message and set move message to 'New Game!'
    document.getElementById( 'winMessage' ).innerHTML = '';
    document.getElementById( 'message'    ).innerHTML = 'New Game!';

}
/* --------------------------------------------------- function SetUpNewGame */


/* function fisherYates ----------------------------------------------------
 Performs a Fisher-Yates shuffle on myArray
---------------------------------------------------------------------------- */
function fisherYates ( myArray ) {
  var i = myArray.length;
  if ( i == 0 ) return false;
  while ( --i ) {
     var j = Math.floor( Math.random() * ( i + 1 ) );
     var tempi = myArray[i];
     var tempj = myArray[j];
     myArray[i] = tempj;
     myArray[j] = tempi;
   }
}/* ----------------------------------------------- end function fisherYates */
