/*******************************************
 * 
 *    Mastermind with JavaScript
 *
 * Motivation
 *    Primarily this project was a way to explore JavaScript and see 
 *    what it could do.  The language itself is interesting - more 
 *    Perl-ish than I expected, though awkward at times - 
 *    but trying to figure out which pieces of the DOM will run
 *    on which platforms is a complete mess.  I've been using
 *    "JavaScript: the Definitive Guide, 4th Edition" as a reference.
 *
 * Author
 *    Jim Mahoney (mahoney@marlboro.edu)
 *    April 2003, version 1.2
 *
 * Tested platforms
 *    - Mozilla v1.3 Mac OS X 10.2.4    works (Testing primarily done here.)
 *    - Explorer 6.0 Windows XP         works
 *    - Safari beta1 v60 on Mac OS X    works
 *    - Explorer 5.2 Mac                fails (Problems inserting table rows.)
 * 
 * To Do
 *   - Put color choices into a pop-up menu.
 *   - Add user option to change number of columns (nColumns) 
 *     or colors (colorImages.length)
 *   - Make sure various window widths match columns, colors. (Somewhat done.)
 *   - More DOM testing for browser compatibility.
 *     (Partly done; see testTables.html in this directory.)
 *   - Move timer display to bottom right of board.
 *
 * Files
 *   mastermind.js     JavaScript source. (You're looking at it.)
 *   mastermind.html   Corresponding HTML source which loads mastermind.js
 *   testTables.html   DOM testing
 *   whitepixel.gif    1x1
 *   blackpixel.gif    1x1
 *   greybox.gif       8x8 black outline around grey interior
 *   <color>ball.gif   32x32 circles on white bkground
 *                     color = red,blue,brown,green,yellow,pink
 *
 ***********************/

var nColumns = 4;
var gameNumber = 1;
var startTimeMillis;
var timerIsRunning;
var lastElapsedTime;
var hiddenAnswer = [];     // what the user is trying to guess
var guessTr = null;        // null => is no "Guess" row; game is not running.
var lastGuess = [];        // what the user thinks is the answer.
var oldGuesses = [];       // guessTr row history displayed on screen
var messageTd;             // <td> block that holds user messages.
var boardTable;            // table that holds guesses and buttons
var blackLineTr;           // table row after guessTr
colorImages  = ['redball.gif',    'blueball.gif',    'brownball.gif',
                'greenball.gif',  'yellowball.gif',  'pinkball.gif' ];

// --------------------------------------------------------
// called when user clicks "New Game".
function newGame(){
    resetGame();
    gameNumber++;
    showMessage("<small>Starting the clock for game "+gameNumber+".</small>");
    runTimer();
}

// --------------------------------------------------------
// return a string like "10:02:23 pm".
function currentTime(){
    var d = new Date();
    var h = d.getHours();
    var m = d.getMinutes();
    var s = d.getSeconds();
    var ampm = (h>=12)?"pm":"am";
    if (h>12) {h-=12;}
    if (h==0) {h=12;}
    if (m<10) {m="0"+m;}
    if (s<10) {s="0"+s;}
    return h+":"+m+":"+s+" "+ampm;
}

// --------------------------------------------------------
// return a string like "01:22" giving min, since game began.
function elapsedTimeAsString(){
    var d = new Date();
    var diff = d.getTime() - startTimeMillis;
    var h = Math.floor( diff/3600000 );
    var m = Math.floor( diff/60000 ) % 60;
    var s = Math.floor( diff/1000. ) % 60;
    if (h==0) {h="";} else {h=h+":";}
    if (m<10) {m="0"+m;}
    if (s<10) {s="0"+s;}
    return ""+h+m+":"+s;
}

// --------------------------------------------------------
// show the user the elapsed time and set lastElapsedTime
function showTimer(){
  lastElapsedTime = elapsedTimeAsString();
  defaultStatus = lastElapsedTime;
}

// --------------------------------------------------------
// This calls itself every second while the timer is going.
function runTimer(){
     if (timerIsRunning){
        showTimer();
        setTimeout("runTimer()", 1000); // repeat every second
     }
}

// --------------------------------------------------------
// display a message where the user can see it.
function showMessage(aString){
    messageTd.innerHTML = aString;
}

// --------------------------------------------------------
// Confirm that the user wants to quit the game, and if so
// show the hidden answer.
function showAnswer(){
    if ( guessTr == null ){
        alert("The game is stopped and the answer is visible.\n" + 
              "Click the 'New Game' link to start another.");
        return;
    }
    if (confirm("Would you like to quit this game\n and see the answer?")){
        for (var c=0; c<nColumns; c++){
            getColumnNImage(c).src = colorImages[hiddenAnswer[c]];
        }
        guessTr.childNodes[2*nColumns-1].innerHTML = "<b>Answer</b>";
        oldGuesses.push(guessTr);
        guessTr = null;
        timerIsRunning = false;
        showMessage("The game is over.");
    }
}

// --------------------------------------------------------
// Tell the user what the rules are.
function showHelp(){
    showMessage( 
                "<small>" + 
                 "You're trying to guess the correct sequence of colors.<p>" +
                 "To make a guess, click one of the small circles at "+
                 "the bottom in each column, and then click 'Guess' " +
                 "to see if you're right." +
                 "<p>After each guess you get some hints.  For each correct " +
                 "color in the correct column, you'll see " + 
                 "<img src='blackpixel.gif' height=8 width=8> , while for " +
                 "each correct color in the wrong column, you'll see " +
                 "<img src='greybox.gif' height=8 width=8>.<p>Colors " +
                 "can't be counted in more than one hint, " +
                 "so the maximum number of black and grey boxes for " +
                 "a single guess is the same as the number of columns.<p>\n" + 
                 "Peaking inside the computer to find the answer "+
                 "is not allowed.  Otherwise, anything goes.  Good luck." + 
                 "</small>"); 
}

// --------------------------------------------------------
// Sets up the globals and start game. Called from <body onload=>
function init(){
    boardTable  = document.getElementById('boardTable');
    blackLineTr = document.getElementById('blackLineTr');
    messageTd   = document.getElementById('messageTd');
    document.getElementById('blackHorizontalTd').setAttribute('colspan',
                                                              2*nColumns);
    resetGame();
    showMessage("<small>Game started at " + currentTime() + ".<p>\n" + 
                "The clock's ticking;<br>" +
                "click some colors and start guessing.</small>");
    runTimer();

    // --- initial annwer display ---     
    // var notice = "Answer is ";
    // for (var c=0; c<nColumns; c++){
    //   notice += hiddenAnswer[c] + " ";
    // }
    // alert(notice);
}
    
// --------------------------------------------------------
// return a random integer r, where 0<= r <= (n-1)
function getRandom(n){
    return Math.floor( n * Math.random() );
}

// --------------------------------------------------------
// clear all old guesses, reset timer, start timer.
function resetGame(){
    while (oldGuesses.length>0){
        boardTable.deleteRow( oldGuesses.pop().rowIndex );
    }
    for (var column=0; column<nColumns; column++){
        lastGuess[column] = -1;
        hiddenAnswer[column] = getRandom( colorImages.length );
    }
    timerIsRunning = true;
    var d = new Date();
    startTimeMillis = d.getTime();
    createOrClearGuessTr();
}

// --------------------------------------------------------
// This is called within a document.write() command from 
// the .html page when it first loads.
// It returns the <td>...</td> HTML for an array of 
// button images that the user can click on to specify his guess.
// Clicking on a button will call click(column,color), where
//  column = (0,1,2,..,nColumns-1) ,  
//  color = (0,...,colorImages.length-1)
function getButtonArrayHTML(){
    var result = "";
    var blackLineHeight = colorImages.length * 19;
    var whiteLineWidth  = nColumns * 22;
    for (var column=0; column<nColumns; column++){
        result += "<td align='center'><table>\n";
        for (color in colorImages) {
            result += "<tr><td><a href='javascript:click(" + column + 
                "," + color + ")'><img src='" + colorImages[color] + 
                "' border=0 width=16></a></td></tr>";
        }
        result += "</table></td>\n";
        if (column<nColumns-1){
            result += "<td><img height=" + blackLineHeight +" width=2 " + 
                "src='blackpixel.gif' border=0></td>\n";
        }
    }
    result += "<td align='right' valign='bottom'>"+
              "<img src='whitepixel.gif' width="+whiteLineWidth+"></td>";
              // + "<br><small>00:00</small></td>"
    return result;
}

// --------------------------------------------------------
// If the row where the user submits his guess isn't available, create it.
// If it is there, initialize it.
//   This function is the one that seems to have the biggest problems
//   on various different browsers, particularly the issue of how to add
//   rows to a table.  Here's how this one works, following (mostly) 
//   the DOM spec (except for innerHTML, which I only call on tableData).
//      * foo document.createElement('foo')        # to make TD's, TR's, etc.
//      * tableData.innerHTML = ...                # to add images, links
//      * tableRow.appendChild(tableData)          # put data in row
//      * tbody = boardTable.getElementsByTagName('tbody')[0];   # get tbody
//      * tbody.insertAfter(tableRow,otherRow)     # put row in table
//   (whew!)
function createOrClearGuessTr(){
    if (guessTr != null){
        for (var c=0; c<nColumns; c++){
            // images are at indeces 0,2,4,...
            guessTr.childNodes[2*c].childNodes[0].src = 'whitepixel.gif';
        }
        return;
    }
    guessTr = document.createElement('tr');
    var td;
    for (var column=0; column<nColumns; column++){
        td = document.createElement('td');
        td.innerHTML="<img src='whitepixel.gif' width=32 height=32 border=0>";
        guessTr.appendChild(td);
        if (column<nColumns-1){ 
            td = document.createElement('td');
            td.innerHTML="<img src='whitepixel.gif' border=0>";
            guessTr.appendChild(td);
        }
    }
    td = document.createElement('td');
    td.innerHTML="<a href='javascript:checkGuess()'>Guess&nbsp;</a>";
    td.align = "right";
    guessTr.appendChild(td);
    var tbody = boardTable.getElementsByTagName('tbody')[0];
    tbody.insertBefore(guessTr, blackLineTr);
}

// --------------------------------------------------------
// One of the image buttons was clicked; change the corresponding picture.
function click(column,color){
    if ( guessTr == null ){
        alert("The game is stopped; you can't make guesses now.\n" + 
              "Click 'New Game' to start another.");
        return;
    }
    getColumnNImage(column).src = colorImages[color];
    lastGuess[column] = color;
}

// --------------------------------------------------------
// Return the image corresponding to the N'th column of the puzzle from guessTr
function getColumnNImage(column){
    return guessTr.childNodes[2*column].childNodes[0];
}

// --------------------------------------------------------
// Show the user the hints by replacing the "Guess" link with
// the given number of black and grey images.
function drawAndPopGuessResult(black, grey){
    guessTd = guessTr.childNodes[2*nColumns-1];
    var htmlResult = "";
    for (var i=0; i<black; i++){
        htmlResult += "<img src='blackpixel.gif' height=8 width=8>" + 
            "<img src='whitepixel.gif' width=4>";
    }
    for (var i=0; i<grey; i++){
        htmlResult += "<img src='greybox.gif' height=8 width=8>" +
            "<img src='whitepixel.gif' width=4>";
    }
    htmlResult += "<img src='whitepixel.gif' width=10>"+(oldGuesses.length+1);
    guessTd.innerHTML = htmlResult;
    oldGuesses.push(guessTr);
    guessTr = null;
}

// --------------------------------------------------------
// return true if one of the columns where the user gueses colors is empty
function isAColumnEmpty(){
    for (var c=0; c<nColumns; c++){
        if ( lastGuess[c]<0 ){
            alert(" Please pick a color for each column\n" + 
                  " by clicking on the small circles\n" + 
                  " before submitting your guess.");
            return true;
        }
    }
    return false;
}

// --------------------------------------------------------
// The user clicked 'Guess'; time to count how many black and grey
// hints should be displayed.
// Each correct color in correct column gives a black marker.
// Each correct color in wrong column gives a grey marker.
// No guess or answer can match more than one marker.
function checkGuess(){
    if (isAColumnEmpty()) {return;}
    var guessCopy  = [];
    var answerCopy = [] ;
    var nBlack = 0;
    var nGrey  = 0;
    // Make copies of the guesses and answers;
    // it's easiest to do this counting by erasing ones we've counted.
    for (var c=0; c<nColumns; c++){
        guessCopy[c]  = lastGuess[c];
        answerCopy[c] = hiddenAnswer[c];
    }
    // count number of black markers.
    for (var c=0; c<nColumns; c++){
        if ( answerCopy[c] == guessCopy[c] ){
            nBlack++;
            answerCopy[c] = -10;  // don't match this again
            guessCopy[c] = -11;   // or this again.
        }
    }
    // count number of grey markers
    for (var ca=0; ca<nColumns; ca++){
        for (var cg=0; cg<nColumns; cg++){
            if ( answerCopy[ca] == guessCopy[cg] ){
                nGrey++;
                answerCopy[ca] = -10;  // don't match this again
                guessCopy[cg] = -11;   // or this again.
            }
        }
    }
    drawAndPopGuessResult(nBlack, nGrey);
    for (var c=0; c<nColumns; c++){
        lastGuess[c] = -1;
    }
    if (nBlack == nColumns){         // was this guess correct?
        timerIsRunning = false;
        showTimer();                // display time and set lastElapsedTime.
        showMessage("<b>Correct.</b><p>" + 
                    "Number of guesses = "+ oldGuesses.length + "<br>" + 
                    "Elapased time = " + lastElapsedTime );
    }
    else {
        createOrClearGuessTr();
    }       
}