// JavaScript Life Simulation
// Copyright (c) 1999 Kelly Yancey <kbyanc@posi.net>
// All rights reserved.
//
// Redistribution and use in source form, with or without modification, is
// permitted provided that the following conditions are met:
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer,
//    without modification, immediately at the beginning of the file.
// 2. All advertising materials mentioning features or use of this software
//    must display the following acknowledgement:
//      This product includes software developed by Kelly Yancey     
//    and a reference to the URL http://www.posi.net/software/automata/
// 3. The name of the author may not be used to endorse or promote products
//    derived from this software without specific prior written permission.
//    
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  
// IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
// NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT  
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


// size of the pictures used to represent cells (all pictures must be the same size)
var        cellwidth = 20;
var        cellheight = 20;

// default size of the 'board'
var        width = 20;
var        height = 16;

// the delay between simulation cycles (in milliseconds)
var        cycledelay = 250;

// user-supplied expression to evaluate at the end of each simulation cycle
var        cyclehook = "";

var     ip = "";

// some internal global variables
var        numpictures = 0;
var        picture = new Array();
var        life = new Array(width);

// some stats gathered during the cycle
var        cyclecount = 0;
var        numlivingcells = 0;
var        numbirths = 0;
var        numdeaths = 0;

// flag set to terminate the cycle
var        stopcycle = false;


var     lastlookup = 0;
function lookup(name) {
// routine to get image number by name
// optimized for the case where we lookup successive images
    for(i = lastlookup; i < document.images.length; i++)
        if(document.images[i].name == name) { lastlookup = i; return(i); }
    for(i = 0; i < lastlookup; i++)
        if(document.images[i].name == name) { lastlookup = i; return(i); }
    return(-1);
}

function cell(imagename) {
// object definition for 'cell' type
  this.imagenum = lookup(imagename);
  this.age = 0;
  this.neighbors = 0;
}

function SetCellImage(x, y, src) {
// routine to set the image associated with the given cell
    if(document.images[life[x][y].imagenum].src != src)
        // we explicitely check to see if we are changing the image source to prevent
        // unnecissary redrawing of images which do not change
        document.images[life[x][y].imagenum].src = src;
}

function CountNeighbors(x, y) {
// routine to update the count per-cell of the cells adjacent to it
    var result = 0;
    var checkx;
    var checky;

    for(checkx = x - 1; checkx <= x + 1; checkx++)
        for(checky = y - 1; checky <= y + 1; checky++) {
            if((checkx == x) && (checky == y)) continue;        // skip our own cell

            if((checkx >= 0) && (checkx < width) &&
               (checky >= 0) && (checky < height) &&
               (life[checkx][checky].age > 0)) result++;
        }
    return(result);
}

function UpdateCell(x, y) {
// routine to update the status of the given cell

    if(life[x][y].age == 0) {
        // cell is currently empty
        if(life[x][y].neighbors == 3) {                                // takes 3 to tango (we have a child!)
            life[x][y].age = 1;
            numbirths++;
        }
    }
    else {
        if((life[x][y].neighbors > 3) || (life[x][y].neighbors < 2)) {
            numdeaths++;                // update the death count
            life[x][y].age = 0;                // reset the cell age (0 is empty)
        }
    }
}


function Cycle() {
// routine to perform the life cycle
    var x;
    var y;

    // reset our stat counters
    numbirths = 0;
    numdeaths = 0;
    numlivingcells = 0;

    // update the cycle count
    cyclecount++;

    // first, count all of the neighbors per cell
    for(x = 0; x < width; x++)
        for(y = 0; y < height; y++)
            life[x][y].neighbors = CountNeighbors(x, y);

    // now update the status of each cell
    for(x = 0; x < width; x++)
        for(y = 0; y < height; y++) {
            UpdateCell(x, y);
            SetCellImage(x, y, picture[life[x][y].age].src);
            if(life[x][y].age > 0) numlivingcells++;
        }

    if(cyclehook != "") eval(cyclehook);

    //if(numbirths == 0) stopcycle = true;

    if(! stopcycle) {
      // schedule to be invoked again shortly
      setTimeout("Cycle()", cycledelay);
    }
}

function InitializeBoard() {
// routine to initialize the state of the life simulation (and draw the 'board')
    // build the life cell array
    for(x = 0; x < width; x++)
        life[x] = new Array (height);

    for(y = 0; y < height; y++) {
        document.write('<NOBR>');
        for(x = 0; x < width; x++) {
            document.write('<A HREF="javascript:void(0);" onclick="PutCell(' + x + ', ' + y + ')"><IMG NAME="c[' + x + ',' + y + ']" SRC="' + picture[0].src + '" BORDER="0" HEIGHT="' + cellheight + '" WIDTH="' + cellwidth + '"></A>');
            life[x][y] = new cell('c[' + x + ',' + y + ']');
        }

        document.writeln('</NOBR><BR>');
    }
}

function ClearBoard() {
// routine to reset all of the cells on the board
    for(x = 0; x < width; x++)
        for(y = 0; y < height; y++) {
            life[x][y].age = 0;
            SetCellImage(x, y, picture[0].src);
        }
    cyclecount = 0;        // reset the number of simulation cycles
}

function RandomFillBoard() {
// routine to put some cell in random places on the board
    for(x = 0; x < width; x++)
        for(y = 0; y < height; y++)
            if((life[x][y].age == 0) && (Math.floor(Math.random() * 5) == 0)) {
                life[x][y].age = 1;
                SetCellImage(x ,y, picture[1].src);
            }
}

function PatternFillBoard() {
    var pattern = ip2pattern(ip, '_');
    //var pattern = ip2pattern("255.255.255.255", '_');
    //var pattern = "_1111_111111111111111111111111_1111_";
    var idx = 0;

    var delta  = Math.sqrt(pattern.length) / 2;
    var xStart = (width / 2 - 1) - delta;
    var xEnd   = (width / 2 - 1) + delta;
    var yStart = (height / 2 - 1) - delta;
    var yEnd   = (height / 2 - 1) + delta;

    for(y = 0; y < height; y++)
        for(x = 0; x < width; x++)
            if((life[x][y].age == 0) &&
               ((xStart < x) && (x <= xEnd) && (yStart < y) && (y <= yEnd))) {
                 if (pattern.charAt(idx % pattern.length) == '1') {
                     life[x][y].age = 1;
                     SetCellImage(x ,y, picture[1].src);
                 }
                 idx++;
            }
}

function LZ(s, len) {
    var rval = s;
    while (rval.length < len) {
        rval = '0' + rval;
    }
    return rval;
}

function ip2binary(ip) {
    var rval = "";
    var a = ip.split('.');
    for (x = 0; x < 4; x++) {
        var i = parseInt(a[x], 10);
        rval += LZ(i.toString(2), 8);
    }
    return rval;
}

function ip2pattern(ip, f) {
  var b = ip2binary(ip);

  return f + b.substring( 0,  4) + 
         f + b.substring( 4, 28) +
         f + b.substring(28, 32) + f;
}

function LoadCellImage(generation, pictureURL) {
// routine to associated the given picture with a cell of the given generation (age)
    picture[generation] = new Image();
    picture[generation].src = pictureURL;
    numgenerations = generation;
}

function PutCell(x, y) {
    life[x][y].age = 1;
    SetCellImage(x, y, picture[1].src);
}


// you must load 2 cell images: one for generation 0 (ie dead) and one for generation 1 (ie alive)

// LoadCellImage(0, "blank.gif");
// LoadCellImage(1, "g1.gif");

// initialize the state of the board
// InitializeBoard();

// Cycle();


