# encoding: ascii
# api: powershell
# title: TheGameOfLife
# description: This is my first attempt at simulating Conway’s ‘the game of life’ in powershell.
# version: 10.5
# type: script
# author: sqlchow
# license: CC0
# function: Initialize-Host
# x-poshcode-id: 3987
# x-archived: 2013-02-28T00:14:42
# x-published: 2013-02-23T21:05:00
#
# Save the code in a file ‘TheGameOfLife.ps1’ and start it by dot sourcing the file name ‘.\TheGameOfLife.ps1’.
# The code saves the current state of the host along with buffer contents.
# The initial state is pre-defined, if you want to change the initial state, you can do that in the main function.
# It takes about, 30secs to draw the next generation. 
# To exit, just press enter.
# The code pops the original state of the host along with buffer contents on exit.
# I know, it is very slow. But, it would be nice if improvements can be suggested. I will work on them.
# Added function to put complex pattern on board. To make the function run a little longer.
# Draw-Pixel: drawn from: http://www.codeproject.com/Articles/241411/PowerShell-Falling-Blocks-Ascii-art-on-the-move written by ‘Lasse W’
#
#TheGameOfLife.ps1

[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") | Out-Null
$SCRIPT:hostProperties = @{};
$SCRIPT:hostState = $null;
$SCRIPT:BoardWidth = 50;
$SCRIPT:BoardHeight = 50;

Function Initialize-Host
{
	Param(
		[Parameter(Mandatory=$false)]
		$wndTitle = "Game of Life...",
		[Parameter(Mandatory=$false)]
		[Int]$wndWidth=50,
		[Parameter(Mandatory=$false)]
		[Int]$wndHeight=50
	)
	$wndSize = $Host.UI.RawUI.WindowSize;
	$wndSize.Width = $wndWidth;
	$wndSize.Height = $wndHeight;
	
	$wndBuffSize = $wndSize;
	
	#Set Console
	$Host.UI.RawUI.WindowTitle = $wndTitle;
	$Host.UI.RawUI.WindowSize  = $wndSize;
	$Host.UI.RawUI.BufferSize  = $wndBuffSize;
	$Host.UI.RawUI.CursorSize  = 0;
	$Host.UI.RawUI.ForegroundColor = "Green";
	$Host.UI.RawUI.BackgroundColor = "Black";

	#get a clear the screen.
	Clear-Host;
}

Function Push-Host
{
	#Get the full buffer
	$hostRect       = "System.Management.Automation.Host.Rectangle"
	$bufferObject   = New-Object $hostRect 0, 0, $(($Host.UI.RawUI.BufferSize).Width), $(($Host.UI.RawUI.BufferSize).Height)
	
	$SCRIPT:hostProperties= @{
    	"Title"           = $Host.UI.RawUI.WindowTitle
		"WindowSize"      = $Host.UI.RawUI.WindowSize
    	"WindowPosition"  = $Host.UI.RawUI.WindowPosition 
    	"BufferSize"      = $Host.UI.RawUI.BufferSize    
    	"Buffer"          = $Host.UI.RawUI.GetBufferContents($bufferObject)    
    	"Background"      = $Host.UI.RawUI.BackgroundColor    
    	"Foreground"      = $Host.UI.RawUI.ForegroundColor
    	"CursorSize"      = $Host.UI.RawUI.CursorSize
    	"CursorPosition"  = $Host.UI.RawUI.CursorPosition
	}
	
	$SCRIPT:hostState = New-Object -TypeName PSCustomObject -Property $SCRIPT:hostProperties
}

Function Pop-Host
{
	#Restore buffer contents
	$Host.UI.RawUI.BufferSize     = $SCRIPT:hostState.BufferSize;
	$initPosition = $Host.UI.RawUI.WindowPosition;
	$initPosition.x = 0;
	$initPosition.y = 0;
	$Host.UI.RawUI.SetBufferContents($initPosition, $SCRIPT:hostState.Buffer)
	
	#Start with the window
	$Host.UI.RawUI.WindowTitle    = $SCRIPT:hostState.Title;
	$Host.UI.RawUI.WindowPosition = $SCRIPT:hostState.WindowPosition;
	$Host.UI.RawUI.WindowSize     = $SCRIPT:hostState.WindowSize;
		
	#Set cursor
	$Host.UI.RawUI.CursorSize     = $SCRIPT:hostState.CursorSize;
	$Host.UI.RawUI.CursorPosition = $SCRIPT:hostState.CursorPosition;
	
	#set colors
	$Host.UI.RawUI.ForegroundColor = $SCRIPT:hostState.Foreground;
	$Host.UI.RawUI.BackgroundColor = $SCRIPT:hostState.Background;
}

Function Get-CursorPosition
{
	$dY = ([System.Windows.Forms.Cursor]::Position.Y )  #read the Y coordinates
  	$dX = ([System.Windows.Forms.Cursor]::Position.X )  #read the X coordinates
	return @($dX, $dY)
}

Function Draw-Pixel
{
	param(
		[Parameter(Mandatory=$true)]
		[Int]$X,
		[Parameter(Mandatory=$true)]
		[Int]$Y,
		[Parameter(Mandatory=$false)]
		[String]$ForeColor = 'White',
		[Parameter(Mandatory=$false)]
		[String]$BackColor = 'Black',
		[Parameter(Mandatory=$false)]
		[String]$pixel = [Char]9608
	)
	$pos = $Host.UI.RawUI.WindowPosition
	$pos.x = $x
	$pos.y = $y
	$row = $Host.UI.RawUI.NewBufferCellArray($pixel, $ForeColor, $BackColor) 
	$Host.UI.RawUI.SetBufferContents($pos,$row) 
}

#Initialize a full board of dead cells.
Function Initialize-GameMatrix
{
    param(
    [Int32]$M,
    [Int32]$N
    )
    $gameMatrix = New-Object "Int32[,]" $M, $N
    for($i=0; $i -lt $M; $i++)
    {
        for($j=0; $j -lt $N; $j++)
        {
            $gameMatrix[$i, $j] = 0;
        }
    }
    return ,$gameMatrix
}

#show the game board in 1's and 0's
Function Show-Matrix
{
	param(
		[Int[,]]$matrix
	)
	[Int]$m = $matrix.GetLength(0);
	[Int]$n = $matrix.GetLength(1);
	
    for($i=0; $i -lt $m; $i++)
	{
		for($j=0; $j -lt $n; $j++)
	    {
			Write-Host("{0}" -f $matrix[$i,$j]) -NoNewLine;
	    }
		Write-Host ""
	}
}

#Currently Taking 10.5 Secs to generate next generation.
#consumes around 20-25% cpu.
#need to find a better way to do this.
Function Get-NextGeneration
{
	param(
	 [Int[,]]$GameMatrix
	)
	BEGIN
	{
		$tmpGameMatrix = $GameMatrix;
		#The game board for game of life is infinite. So, we simulate this by wrapping the
		#width and height.
		Function Get-WrappedWidth
		{
			param(
			    [Int]$x,
			    [Int]$xEdge
			)
			$x += $xEdge;
			if($x -lt 0){
				$x += $SCRIPT:BoardWidth;
			}elseif($x -ge $SCRIPT:BoardWidth){
				$x -= $SCRIPT:BoardWidth;
			}
			return $x;
		}

		Function Get-WrappedHeight
		{
			param(
				[Int]$y,
				[Int]$yEdge
			)
			$y += $yEdge;
			if($y -lt 0){
				$y += $SCRIPT:BoardHeight;
			}elseif($y -ge $SCRIPT:BoardHeight){
				$y -= $SCRIPT:BoardHeight
			}
			return $y;
		}

		Function Get-Neighbours
		{
			param(
				[Int[,]]$ArrayMatrix,
				[Int]$coordX,
				[Int]$coordY
			)
			[Int]$nx = 0;
			[Int]$ny = 0;
			[Int]$count = 0;
			for($nx = -1; $nx -le 1; $nx++)
			{
				for($ny = -1; $ny -le 1; $ny++)
				{
					if($nx -or $ny)
					{
						if($ArrayMatrix[$(Get-WrappedWidth $coordX $nx), $(Get-WrappedHeight $coordY $ny)])
						{
							$count += 1;
						}
					}
				}
			}
			return $count;
		}
		
	}
	PROCESS
	{
		
		for($x = 0; $x -lt $SCRIPT:BoardWidth; $x++)
		{
			for($y = 0; $y -lt $SCRIPT:BoardHeight; $y++)
			{
				$neighbors = Get-Neighbours $tmpGameMatrix $x $y
				switch($neighbors)
				{
					{($neighbors -lt 2) -or ($neighbors -gt 3)}{$tmpGameMatrix[$x, $y] = 0;}
					{($neighbors -eq 3)}{$tmpGameMatrix[$x, $y] = 1;}
				}
			}
		}
		
	}
	END
	{
		$GameMatrix = $tmpGameMatrix;
		#should we even do this? : return ,$GameMatrix
		return ,$GameMatrix;
	}
}

Function Draw-Board
{
	param(
		[Int[,]]$Board
	)
	for($bx = 0; $bx -lt $SCRIPT:BoardWidth; $bx++)
	{
		for($by = 0; $by -lt $SCRIPT:BoardHeight; $by++)
		{
			if($Board[$bx, $by])
			{
				Draw-Pixel -X $bx -Y $by -ForeColor "Green" -BackColor "Yellow"
			}else{
				Draw-Pixel -X $bx -Y $by -ForeColor "Black" -BackColor "Black"
			}
		}
	}
}

#Setting a little bit of complex pattern on the board.
Function Set-SampleOnBoard
{
	param(
		[Int[,]]$ArrayMatrix
	)
	$ArrayMatrix[6,1] = 1
	$ArrayMatrix[7,1] = 1
	$ArrayMatrix[6,2] = 1
	$ArrayMatrix[7,2] = 1
	$ArrayMatrix[6,11] = 1
	$ArrayMatrix[7,11] = 1
	$ArrayMatrix[8,11] = 1
	$ArrayMatrix[9,12] = 1
	$ArrayMatrix[10,13] = 1
	$ArrayMatrix[10,14] = 1
	$ArrayMatrix[9,16] = 1
	$ArrayMatrix[8,17] = 1
	$ArrayMatrix[7,17] = 1
	$ArrayMatrix[6,17] = 1
	$ArrayMatrix[5,16] = 1
	$ArrayMatrix[4,14] = 1
	$ArrayMatrix[4,13] = 1
	$ArrayMatrix[5,12] = 1
	$ArrayMatrix[7,15] = 1
	$ArrayMatrix[7,18] = 1
	$ArrayMatrix[4,21] = 1
	$ArrayMatrix[5,21] = 1
	$ArrayMatrix[6,21] = 1
	$ArrayMatrix[4,22] = 1
	$ArrayMatrix[5,22] = 1
	$ArrayMatrix[6,22] = 1
	$ArrayMatrix[7,23] = 1
	$ArrayMatrix[3,23] = 1
	$ArrayMatrix[3,25] = 1
	$ArrayMatrix[2,25] = 1
	$ArrayMatrix[7,25] = 1
	$ArrayMatrix[8,25] = 1
	$ArrayMatrix[3,35] = 1
	$ArrayMatrix[3,36] = 1
	$ArrayMatrix[4,35] = 1
	$ArrayMatrix[4,36] = 1
	return ,$ArrayMatrix;
}
Function Main
{
	Push-Host;
	Initialize-Host;
	$gameBoard = Initialize-GameMatrix 50 50;
	#Sample filler
	$gameBoard = Set-SampleOnBoard $gameBoard
	Draw-Board $gameBoard
	do{
		$newBoard = Get-NextGeneration $gameBoard;
		#Clear-Host;
		Draw-Board $newBoard;
	}until($Host.UI.RawUI.KeyAvailable)
	Pop-Host;
}
. Main
