// John Horton Conway's Game of Life 2D
// Implementation by Warren Willmey 2010.
// Un-optimized and no unit tests, done in a rush...
//
//-----------------------------------------------------------------------------
	/** Constructor, Life2d Class.
	 *
	 *  @author Warren Willmey 2010
	 */
function Life2d()
{
	this.iGridWidth = 71;
	this.iGridHeight= 30;
	
	this.aGridOne = new Array( this.iGridWidth * this.iGridHeight );
	this.aGridTwo = new Array( this.iGridWidth * this.iGridHeight );
	
	this.aCurrentGrid = this.aGridOne;
	this.bUseGridOne  = true;
};

//-----------------------------------------------------------------------------
	/** Clear a Cell Grid.
	 *
	 *  @Param boolean bGridOne = TRUE: Clear life grid one, FALSE: life grid two.
	 *   
	 *  @author Warren Willmey 2010
	 */
Life2d.prototype.clearCellGrid = function( bGridOne )
{
	var aGrid = this.aGridOne;

	if( !bGridOne )
		aGrid = this.aGridTwo;

	for( var iLength = aGrid.length; iLength--; aGrid[ iLength] = 0 );
};

//-----------------------------------------------------------------------------
	/** Switch over Cell grids, two buffers are required due to the destructive process of the life cycle.
	 *
	 *  @author Warren Willmey 2010
	 */
Life2d.prototype.switchCellGrids = function( )
{
	this.bUseGridOne = this.bUseGridOne ? false : true;
	this.aCurrentGrid = this.getReadGrid();
};

//-----------------------------------------------------------------------------
	/** Get the current grid to read from.
	 *
	 *  @Return Array = The current active read grid.
	 *   
	 *  @author Warren Willmey 2010
	 */
Life2d.prototype.getReadGrid = function( )
{
	if( this.bUseGridOne )
		return this.aGridOne;

	return this.aGridTwo;
};

//-----------------------------------------------------------------------------
	/** Get the current grid to write too.
	 *
	 *  @Return Array = The current active write grid.
	 *   
	 *  @author Warren Willmey 2010
	 */
Life2d.prototype.getWriteGrid = function( )
{
	if( this.bUseGridOne )
		return this.aGridTwo;
	
	return this.aGridOne;
};

//-----------------------------------------------------------------------------
	/** Check current cell to see if its alive or dead.
	 *
	 *  @Param int x = The X position in the grid of the current cell to look at.
	 *  @Param int y = The Y position in the grid of the current cell to look at.
	 *
	 *  @Return int = 0: dead, 1: ALIVE!!.
	 *  
	 *  @author Warren Willmey 2010
	 */
Life2d.prototype.checkCell = function( x, y )
{
	var iX = (x + this.iGridWidth) % this.iGridWidth;
	var iY = (y + this.iGridHeight) % this.iGridHeight;

	return this.aCurrentGrid[ iX + ( iY * this.iGridWidth ) ] > 0 ? 1 : 0;
};

//-----------------------------------------------------------------------------
	/** Count neighbours of living cells around current cell.
	 *
	 *  @Param int x = The X position in the grid of the current cell to look at.
	 *  @Param int y = The Y position in the grid of the current cell to look at.
	 *
	 *  @Return int = Number of living neighbours cells.
	 *  
	 *  @author Warren Willmey 2010
	 */
Life2d.prototype.countLivingNeighbours = function( x, y )
{
	var iLivingNeighboursCells = 0;

	// Check neighbours.
	//
	iLivingNeighboursCells += this.checkCell( x -1, y -1 );
	iLivingNeighboursCells += this.checkCell( x   , y -1 );
	iLivingNeighboursCells += this.checkCell( x +1, y -1 );

	iLivingNeighboursCells += this.checkCell( x -1, y );
	iLivingNeighboursCells += this.checkCell( x +1, y );

	iLivingNeighboursCells += this.checkCell( x -1, y +1 );
	iLivingNeighboursCells += this.checkCell( x   , y +1 );
	iLivingNeighboursCells += this.checkCell( x +1, y +1 );
	
	return 	iLivingNeighboursCells;
};

//-----------------------------------------------------------------------------
	/** Count neighbours of living cells around current cell, with no wrapping, 
	 * so CANNOT be used for the edges of the cell grid.
	 *
	 *  @Param int x = The X position in the grid of the current cell to look at.
	 *  @Param int y = The Y position in the grid of the current cell to look at.
	 *
	 *  @Return int = Number of living neighbours cells.
	 *  
	 *  @author Warren Willmey 2010
	 */
Life2d.prototype.countLivingNeighboursNoWrap = function( x, y )
{
	var iLivingNeighboursCells = 0;
	var aGrid = this.aCurrentGrid;
	var iNeighbourOffset = x-1 + ( (y-1) * this.iGridWidth );
	// Check neighbours.
	//
	
	iLivingNeighboursCells += aGrid[ iNeighbourOffset++ ];
	iLivingNeighboursCells += aGrid[ iNeighbourOffset++ ];
	iLivingNeighboursCells += aGrid[ iNeighbourOffset++ ];
	iNeighbourOffset += this.iGridWidth -3;
	iLivingNeighboursCells += aGrid[ iNeighbourOffset++ ];
	iNeighbourOffset++;
	iLivingNeighboursCells += aGrid[ iNeighbourOffset++ ];
	iNeighbourOffset += this.iGridWidth -3;
	iLivingNeighboursCells += aGrid[ iNeighbourOffset++ ];
	iLivingNeighboursCells += aGrid[ iNeighbourOffset++ ];
	iLivingNeighboursCells += aGrid[ iNeighbourOffset++ ];
	
	return 	iLivingNeighboursCells;
};

//-----------------------------------------------------------------------------
	/** Process the cycle of life at the edges of Cell Grid.
	 *
	 *  @Param int iMostNeighbours = The current most neighbours count value (used for planting an acorn).
	 *  @Param int iXOffset = The X offset into the cell grid to check.
	 *  @Param int iWidth = The width of the area to check.
	 *  @Param int iYOffset = The Y offset into the cell grid to check.
	 *  @Param int iHeight = The height of the area to check.
	 *  
	 *  @Return int = iMostNeighbours, the max number of neighbour count of all the cell checked.
	 *  
	 *  @author Warren Willmey 2010
	 */
Life2d.prototype.growEdge = function( iMostNeighbours, iXOffset, iWidth, iYOffset, iHeight )
{
	var aWriteGrid = this.getWriteGrid();
	
	// Check edges of Grid first, as they have to deal with wrap round.
	//
	for( var y = iYOffset; y < iHeight; y++ )
	{
		var iLineOffset = iXOffset + (y * this.iGridWidth);
		
		for( var x = iXOffset; x < iWidth; x++ )
		{
			var iLiveOrDie = 0;
			var iNeighbourCount = this.countLivingNeighbours( x, y );
			iMostNeighbours = iNeighbourCount > iMostNeighbours ? iNeighbourCount : iMostNeighbours;
	
			if( 0 == this.checkCell(x, y) )
			{
				// Currently on a dead cell.
				//
				if( 3 == iNeighbourCount )
					iLiveOrDie = 1;
			}
			else
			{
				// Currently on a live cell.
				//
				if( iNeighbourCount == 2 || iNeighbourCount == 3 )
					iLiveOrDie = 1;
				
			}
	
			aWriteGrid[ iLineOffset++ ] = iLiveOrDie;
		}
	}	

	return iMostNeighbours
}

//-----------------------------------------------------------------------------
	/** Process the cycle of life.
	 *
	 *  @author Warren Willmey 2010
	 */
Life2d.prototype.grow = function( )
{
	var aWriteGrid = this.getWriteGrid();
	var iMostNeighbours = 0;
	
	// Check edges of Grid first, as they have to deal with wrap round.
	// Top edge.
	iMostNeighbours = this.growEdge( iMostNeighbours, 0, this.iGridWidth, 0, 1 );
	// Bottom edge.
	iMostNeighbours = this.growEdge( iMostNeighbours, 0, this.iGridWidth, this.iGridHeight -1, this.iGridHeight );	
	// Left edge.
	iMostNeighbours = this.growEdge( iMostNeighbours, 0, 1, 1, this.iGridHeight -1 );	
	// Right edge.
	iMostNeighbours = this.growEdge( iMostNeighbours, this.iGridWidth-1, this.iGridWidth, 1, this.iGridHeight -1 );	
	
	// Check center quicker because there is no wrapping on the grid.
	//
	var aReadGrid = this.getReadGrid();
	for( var y = 1, iHeight = this.iGridHeight-1; y < iHeight; y++ )
	{
		var iLineOffset = y * this.iGridWidth + 1;
		
		for( var x = 1, iWidth = this.iGridWidth-1; x < iWidth; x++ )
		{
			var iLiveOrDie = 0;
			var iNeighbourCount = this.countLivingNeighboursNoWrap( x, y );
			iMostNeighbours = iNeighbourCount > iMostNeighbours ? iNeighbourCount : iMostNeighbours;

			if( 0 == aReadGrid[ iLineOffset ] )
			{
				// Currently on a dead cell.
				//
				if( 3 == iNeighbourCount )
					iLiveOrDie = 1;
			}
			else
			{
				// Currently on a live cell.
				//
				if( iNeighbourCount == 2 || iNeighbourCount == 3 )
					iLiveOrDie = 1;
				
			}

			aWriteGrid[ iLineOffset++ ] = iLiveOrDie;
		}
	}	
	
	// Stimulate growth by randomly adding a cell every iteration.
	//
	{
		var iRandomCellAddition = Math.randomInt( 0, aWriteGrid.length -1 );
		if( this.countLivingNeighbours( iRandomCellAddition % this.iGridWidth, Math.round( iRandomCellAddition / this.iGridWidth ) ) > 1 )
		{
			// Only add a cell if there is a neighbour, this stops random noise appearing on the grid..
			//
			aWriteGrid[ iRandomCellAddition ] = 1;
		}
	}
	
	if(  iMostNeighbours <= 2 )
	{
		// Life Grid is dead or stagnated, inject some life into it.
		//
		this.plantAcorn();
	}
	
	this.switchCellGrids();
};


//-----------------------------------------------------------------------------
	/** Plant an Acorn.. The Acorn is a Life2D Cell pattern that lives for 5,206 generations.
	 * Requires a minimum grid size of 7 * 4 cells to be plotted.
	 *
	 *  @author Warren Willmey 2010
	 */
Life2d.prototype.plantAcorn = function( )
{
	var aGrid = this.getWriteGrid();

	aGrid[ 2 + ( 2 * this.iGridWidth ) ] = 1;
	aGrid[ 4 + ( 3 * this.iGridWidth ) ] = 1;
	aGrid[ 1 + ( 4 * this.iGridWidth ) ] = 1;
	aGrid[ 2 + ( 4 * this.iGridWidth ) ] = 1;
	aGrid[ 5 + ( 4 * this.iGridWidth ) ] = 1;
	aGrid[ 6 + ( 4 * this.iGridWidth ) ] = 1;
	aGrid[ 7 + ( 4 * this.iGridWidth ) ] = 1;
};

//-----------------------------------------------------------------------------
	/** Display BYE message in Cell Grid.
	 * 
	 *  @Param int iXPos = The X offset into the cell grid to display message.
	 *  @Param int iYPos = The Y offset into the cell grid to display message.
	 *
	 *  @author Warren Willmey 2010
	 */
Life2d.prototype.goByeBye = function( iXPos, iYPos )
{
	var aGrid = this.getReadGrid();
	var aBigLetters = [   '1111110001100110001111'
	                    , '0110011001100110011000'
	                    , '0111110000111110011110'
	                    , '0110011000000110011000'
	                    , '0110011000000110011000'
	                    , '1111110001111100001111'
	                    
	                    ];

	var iOffsetPosition = iXPos + ( iYPos * this.iGridWidth );
	
	for( var y = 0, iLines = aBigLetters.length; y < iLines; y++ )
	{
		var sLine = aBigLetters[ y ];
		var iLength= sLine.length;
		
		for( var x = 0; x < iLength; x++ )
		{
			var iCellSet = sLine.charAt( x ) == '0' ? 0 : 1;

			var iCellOffset = (x * 2) + ( (y * 2 * this.iGridWidth )) + iOffsetPosition;

			aGrid[ iCellOffset ] = iCellSet;
			aGrid[ iCellOffset + 1  ] = iCellSet;
			aGrid[ iCellOffset + this.iGridWidth ] = iCellSet;
			aGrid[ iCellOffset + this.iGridWidth + 1 ] = iCellSet;
		}
	}
};

//-----------------------------------------------------------------------------
	/** Constructor, CellManager Class.
	 *
	 *  @author Warren Willmey 2010
	 */
function CellManager()
{
	this.aDisplayGrid = new Array(); 
}

//-----------------------------------------------------------------------------
	/** Add a Cell Sprite to the Display Grid.
	 *
	 *  @Param float fTransparency  = The transparency level of the new Sputnik.
	 *  @Param int x  = The X Position of the sprite (in pixels).
	 *  @Param int y  = The Y Position of the sprite (in pixels).
	 *
	 *  @author Warren Willmey 2008
	 */
CellManager.prototype.addCellSprite = function( fTransparency, x, y )
{
	var iIdNumber = this.aDisplayGrid.length;
	var oAppendCell = document.getElementById( 'LifeCells' );
	var oNewDivTag = document.createElement('div');
	oNewDivTag.id  = 'C' + iIdNumber;
	oNewDivTag.style.opacity = fTransparency;
			
	var sCSSClassName	 = 'cssGreenCell';
	oNewDivTag.className = sCSSClassName;
	oNewDivTag.style.left= x + 'px';
	oNewDivTag.style.top = y + 'px';
	
	if( 0 == fTransparency )
	{
		oNewDivTag.style.display = 'none';
	}
	
	oAppendCell.appendChild( oNewDivTag );
	
	this.aDisplayGrid.push( oNewDivTag.style );
}

//-----------------------------------------------------------------------------
	/** Create Life Cell Display Grid (which are all DIV layers ie Sprites).
	 *
	 *  @Param int iNumberOfCellsWide  = The number of Cell wide.
	 *  @Param int iNumberOfCellsHigh  = The number of Cell high.
	 *
	 *  @author Warren Willmey 2010
	 */
CellManager.prototype.createLifeCellDisplayGrid = function( iNumberOfCellsWide, iNumberOfCellsHigh )
{
	var iGapX = 10;
	var iGapY = 10;
	for( var y = 0, iCellsY = iNumberOfCellsHigh; iCellsY--; y += iGapY )
	{
		for( var x = 0, iCellsX = iNumberOfCellsWide; iCellsX--; x += iGapX )
		{
			this.addCellSprite( 0.0, x, y );
		}
	}
	
	// Resize container DIV so that dodgy gradient fill is correct, html sucks..
	//
	var oCellGrid = document.getElementById( 'LifeCells' );
	oCellGrid.style.width = iNumberOfCellsWide * iGapX + 6 + 'px';
	oCellGrid.style.height= iNumberOfCellsHigh * iGapY + 6 + 'px';
}

//-----------------------------------------------------------------------------
//There is no Random Integer function, safer to use a generic one than role your own taken from:
//http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Math:random
//As (Quote):
//Using Math.round() will give you a non-uniform distribution!
//
if( 'undefined' == typeof( Math.randomInt ) ) {
	Math.randomInt = function( iMinValue, iMaxValue )
	{
		return Math.floor( Math.random() * ( iMaxValue - iMinValue + 1 ) ) + iMinValue;
	}
}

//-----------------------------------------------------------------------------

var oLifeDontTalkToMeAboutLife = new Life2d();
var oCellManager = new CellManager();

//-----------------------------------------------------------------------------
	/** Display changes in the Life Cell Grid in the Display Grid.
	 *
	 *  @author Warren Willmey 2010
	 */
function displayCells()
{
	var aSprites= oCellManager.aDisplayGrid;
	var aGrid	= oLifeDontTalkToMeAboutLife.getReadGrid();
	
	for( var iOffset = 0, iLength = aGrid.length; iOffset < iLength; iOffset++ )
	{
		if( 0 == aGrid[ iOffset ] )
		{
			var fOpacity = aSprites[ iOffset ].opacity;
			if( fOpacity > 0 )
			{
				fOpacity *= 0.7;
				fOpacity  = fOpacity < 0.1 ? 0.0 : fOpacity;
				aSprites[ iOffset ].opacity = fOpacity;
				
				if( 0 == fOpacity )
				{
					aSprites[ iOffset ].display = 'none';
				}
			}
		}
		else
		{
			aSprites[ iOffset ].opacity = 1.0;
			aSprites[ iOffset ].display = 'inline';
		}
	}
}


//-----------------------------------------------------------------------------
	/** Main loop, grow life and display, say no more!.
	 *
	 *  @author Warren Willmey 2010
	 */
function mainLoop()
{
	oLifeDontTalkToMeAboutLife.grow();
	displayCells();
}

//-----------------------------------------------------------------------------
function initialise()
{
	oLifeDontTalkToMeAboutLife.clearCellGrid( true );
	oLifeDontTalkToMeAboutLife.clearCellGrid( false );
	
	oLifeDontTalkToMeAboutLife.goByeBye( (oLifeDontTalkToMeAboutLife.iGridWidth >> 1) - 22, (oLifeDontTalkToMeAboutLife.iGridHeight >> 1) - 6 );

	oCellManager.createLifeCellDisplayGrid( oLifeDontTalkToMeAboutLife.iGridWidth, oLifeDontTalkToMeAboutLife.iGridHeight );
	
	displayCells();
	setTimeout( "iIntervalID = setInterval('mainLoop()', 40 );", 500 );
//console.log( "hello world!" );
}


//
//---------------------------------------------------------------------------
//
window.onload = initialise;
//
//---------------------------------------------------------------------------
//