Funhelper2.psm1

<#
 .Synopsis
 Another simple implementation of Conway`s game of live (AIOCGOL)
 .Notes
 Author: Peter Monadjemi, last update: 13/08/18
#>


Set-StrictMode -Version Latest
Import-LocalizedData -BindingVariable MsgTable -FileName OsmiumMessages.psd1

<#
 .Synopsis
 Handy function to test an expression and runs a different scriptblock if the cond is either true or false
#>

function Iif
{
    [CmdletBinding()]
    param([Parameter(Mandatory=$true, ValueFromPipeline=$true)][Bool]$Cond, [Scriptblock]$TrueOpt, [Scriptblock]$FalseOpt)

    if ($Cond)
    { &$TrueOpt }
    else
    { &$FalseOpt }
}

<#
 .Synopsis
 Returns the number of neighbours of a cell
#>

function GetNeighbourCount
{
    [CmdletBinding()]
    param([Byte[,]]$Field, [Int]$Row, [Int]$Col)
    $NeighbourCount = 0
    # test 1: cell in the upper left
    if (($Row - 1 -gt 0) -and ($Col - 1 -gt 0))
    {
        $NeighbourCount += $Field[($Row-1), ($Col-1)]
    }
    # test 2: cell from above
    if ($Row - 1 -gt 0)
    {
        $NeighbourCount += $Field[($Row-1), $Col]
    }
    # test 3: cell in upper right
    if (($Row - 1 -gt 0) -and ($Col -lt $Field.GetLength(1)-1))
    {
        $NeighbourCount += $Field[($Row-1), ($Col+1)] 
    }
    # test 4: cell from right
    if ($Col + 1 -lt $Field.GetLength(1)-1)
    {
        $NeighbourCount += $Field[$Row, ($Col+1)]
    }
    # test 5: cell below on the right
    if (($Row + 1 -lt $Field.GetLength(0)) -and ($Col + 1 -lt $Field.GetLength(1)-1))
    {
        $NeighbourCount += $Field[($Row+1), ($Col+1)]     
    }
    # test 6: cell below
    if (($Row + 1 -lt $Field.GetLength(0)))
    {
        $NeighbourCount += $Field[($Row+1), $Col] 
    }
    # test 7: cell below on the left
    if ($Row + 1 -lt $Field.GetLength(0)-1 -and $Col -gt 0)
    {
        $NeighbourCount += $Field[($Row+1), ($Col-1)] 
    }
    # test 8: cell from left
    if ($Col - 1 -gt 0)
    {
        $NeighbourCount += $Field[$Row, ($Col-1)]
    }
    
    # Return the neighbour count
    return $NeighbourCount
}

<#
 .Synopsis
 Outputs the whole field as text
#>

function ShowGoLField
{
    [CmdletBinding()]
    param([Parameter(Mandatory=$true)][Byte[,]]$Field, [Int]$Generation)
    $Population = 0
    switch($Field.GetLength(1))
    {
      { $_ -ge 20} { $Banner = "Generation Nr. $Generation"; break}
      { $_ -ge 10} { $Banner = "Gen $Generation"; break }
      default { $Banner = $Generation }
    }  
    # Really annoying the need for another round of parantheses
    Write-Host ([String]::new("=", $Field.GetLength(1) + 4))
    $SpaceCount = ($Field.GetLength(1) - $Banner.Length) / 2
    Write-Host ("=$([String]::new(' ', $SpaceCount))") -NoNewline
    Write-Host $Banner -NoNewline
    Write-Host ("$([String]::new(' ', $SpaceCount+2))=") 
    Write-Host ([String]::new("=", $Field.GetLength(1) + 4))
    for($i = 0;$i -lt $Field.GetLength(0);$i++)
    {
        $RowOutput = ""
        for($j = 0;$j -lt $Field.GetLength(1);$j++)
        {
            # Won't work with positional parameters ??? (SO help!)
            $RowOutput += $Field[$i,$j] -eq 1 | Iif -TrueOpt {"X"} -FalseOpt {"."}
            $Population += $Field[$i,$j]
        }
        Write-Host $RowOutput
    }
    Write-Host "Population: $Population"
    Write-Host "`n"
    return $Population
}

<#
 .Synopsis
 Outputs the whole field in the console window
#>

function ShowGoLFieldConsole
{
    [CmdletBinding()]
    param([Parameter(Mandatory=$true)][Byte[,]]$Field, [Int]$Generation, [String]$Cellcolor)
    $Population = 0
    $XPos = 0
    $YPos = 2
    switch($Field.GetLength(1))
    {
      { $_ -ge 20} { $Banner = "Generation Nr. $Generation"; break}
      { $_ -ge 10} { $Banner = "Gen $Generation"; break }
      default { $Banner = $Generation }
    }  
    [Console]::Clear()
    [Console]::SetCursorPosition($XPos, $YPos)
    [Console]::ForegroundColor = "Green"
    [Console]::WriteLine([String]::new("=", $Field.GetLength(1) + 4))
    # Really annoying the need for another round of parantheses
    $SpaceCount = ($Field.GetLength(1) - $Banner.Length) / 2
    $GenSpaceOffset = $Generation -gt 9 | Iif -TrueOpt { 1 } -FalseOpt { 2 }
    [Console]::WriteLine("=$([String]::new(' ', $SpaceCount))$Banner$([String]::new(' ', $SpaceCount+$GenSpaceOffset))=")
    [Console]::WriteLine([String]::new("=", $Field.GetLength(1) + 4))
    [Console]::ForegroundColor = "White"
    for($i = 0;$i -lt $Field.GetLength(0);$i++)
    {
        $RowOutput = ""
        for($j = 0;$j -lt $Field.GetLength(1);$j++)
        {
            # Won't work with positional parameters ??? (SO help!)
            $RowOutput += $Field[$i,$j] -eq 1 | Iif -TrueOpt {"X"} -FalseOpt {"."}
            $Population += $Field[$i,$j]
        }
        # [Console]::WriteLine($RowOutput)
        $RowOutput.ToCharArray().ForEach{
            [Console]::ForegroundColor = $_ -eq "X" | Iif -TrueOpt { $Cellcolor } -FalseOpt { "White"}
            [Console]::Write($_)
        }
    [Console]::ForegroundColor = "White"
    [Console]::WriteLine()
    }
    [Console]::WriteLine("Population: $Population")
    # return command is not really necessary
    return $Population
}

<#
<#
 .Synopsis
 Internal function
#>

function TestGoLRule
{
    param([Int]$FieldValue, [Int]$NeighbourCount)
    if ($FieldValue)
    {
        $NeighbourCount -eq 2 -or $NeighbourCount -eq 3
    }
    else
    {
        $NeighbourCount -eq 3
    }
}

<#
 .Synopsis
 Starts a GoL run
#>

function Start-GoL
{
    [CmdletBinding()]
    param([Parameter(ValueFromPipeline=$true)][Byte[,]]$GoLField,
          [Int]$MaxGenerationen=0,
          [Switch]$ShowGoLField=$true,
          [Int]$CycleSpeed=500,
          [String]$CellColor = "Yellow"
    )
    $Generation = 1
    $FieldRowSize = $GoLField.GetLength(0)
    $FieldColSize = $GoLField.GetLength(1)
    while($true)
    {
        if ($ShowGoLField)
        {
            $Population = ShowGoLFieldConsole -Field $GoLField -Generation $Generation -CellColor $CellColor
            # Wait a few cycles
            Start-Sleep -Milliseconds $CycleSpeed
        }
        else
        {
            $Population = @($GoLField | Where-Object { $_ -eq 1 }).Count
        }
        if ($Population -eq 0)
        {
            Write-Warning ($MsgTable.GoLPopulationTerminated -f $Generation)
            if ($ShowGoLField)
            {
                return
            }
            else
            {
                return 0
            }
            break
        }
        $NeighbourField = [Byte[,]]::new($FieldRowSize, $FieldColSize)
        for($i = 0;$i -lt $GoLField.GetLength(0);$i++)
        {
            for($j = 0;$j -lt $GoLField.GetLength(1);$j++)
            {
                $NeighbourField[$i, $j] = GetNeighbourCount -Field $GoLField -Row $i -Col $j
            }
        }
        if ($Generation -eq $MaxGenerationen)
        {
            if ($ShowGoLField)
            {
                return
            }
            else
            {
                return $Population
            }
        }
        $Generation++
        for($i = 0;$i -lt $GoLField.GetLength(0);$i++)
        {
            for($j = 0;$j -lt $GoLField.GetLength(1);$j++)
            {
                $GoLField[$i, $j] = TestGolRule -FieldValue $GoLField[$i, $j] -NeighbourCount $NeighbourField[$i, $j] | iif -TrueOpt { 1 } -FalseOpt { 0 }
            }
        }
    }
}

<#
 .Synopsis
 Creates a new GoL field with a predefined pattern
 .Notes
 DefaultParametersetName is really helpful - thanks to James Brundage and his 'Fun with parametersets' blog post many years ago
#>

function New-GolField
{
    [CmdletBinding(DefaultParametersetname="")]
    param([Parameter(Parametersetname="FilePath")][String]$GolPattern,
          [Parameter(Mandatory=$true)][String]$PatternFilePath,
          [Parameter(Parametersetname="FilePath")][Int]$FieldRowSize=20,
          [Parameter(Parametersetname="FilePath")][Int]$FieldColSize=20,
          [Parameter(Parametersetname="Patterns")][Switch]$ShowPatterns)
    $GolField = [Byte[,]]::new($FieldRowSize,$FieldColSize)
    $GolPatterns = Import-PowerShellDataFile -Path $PatternFilePath
    if ($PSBoundParameters.ContainsKey("ShowPatterns"))
    {
        $GolPatterns.Keys.ForEach{
         $_
        }
        return
    }
    if (!$GolPatterns.ContainsKey($GolPattern))
    {
        throw ($MsgTable.GotPatternNotExist -f $GolPattern)      
    }
    $GolLines = $GolPatterns.$GolPattern -split "`n"
    $RowCount = $GolLines.Length
    $MaxCol = $GolLines | ForEach-Object -Begin { $Max = 0 } -Process { $Max = [Math]::Max($Max, $_.Length)} -End { $Max }

    [int]$StartCol = ($FieldColSize - $MaxCol) / 2
    [int]$StartRow = ($FieldRowSize - $RowCount) / 2

    for($r = 0;$r -lt $RowCount;$r++)
    {
        $c = 0
        $GolLines[$r].ToCharArray().ForEach{
            $GolField[($StartRow + $r), ($StartCol + $c)] = $GolLines[$r].ToCharArray()[$c] -eq "X" | Iif -TrueOpt { 1 } -FalseOpt { 0}
            $c++
        }
    }
    # this can be tricky - the return value has to be an 2D array not just a sequence of values
    ,$GolField
}