// 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 = 15;

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

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

// 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(! 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 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();

