Search code examples
powershellconways-game-of-life

Conway's Life | Powershell


I have been working on creating a basic Conway's Game of Life simulator in Powershell to get more familiar with the language. But my current code is incorrectly counting # of neighboring cells, so the game is not working. I believe my wrapping is correct, from what I can tell, but something seems to be 'up' with my neighbor count.

Here is the code:

function next-gen{
    param([System.Array]$origM)

    $tmpM = $origM

    For($x=0; $x -lt $tmpM.GetUpperBound(0); $x++ ){
        For($y=0; $y -lt $tmpM.GetUpperBound(1); $y++){
            $neighborCount = getNeighbors $tmpM $x $y
            if($neighborCount -lt 2 -OR $neighborCount -gt 3){
                $tmpM[$x,$y] = 0
            }
            elseif($neighborCount -eq 3){
                $tmpM[$x, $y] = 1
            }
        } 
    }
    $Global:origM = $tmpM
}

function getNeighbors{
    param(
        [System.Array]$g,
        [Int]$x,
        [Int]$y
    )
    $newX=0
    $newY=0
    $count=0

    for($newX = -1; $newX -le 1; $newX++){
        for($newY = -1; $newY -le 1; $newY++){
            if($g[$(wrap $x $newX),$(wrap $y $newY)]){
                $count++
            }
        }
    }
    return $count
}

function wrap{
    param(
        [Int]$z,
        [Int]$zEdge
    )

    $z+=$zEdge
    If($z -lt 0){
        $z += $size
    }
    ElseIf($z -ge $size){
        $z -= $:size
    }
    return $z
}

function printBoard{
    0..$m.GetUpperBound(0) | 
    % { $dim1=$_; (0..$m.GetUpperBound(1) | % { $m[$dim1, $_] }) -join ' ' }
    write-host ""
}
#board is always a square, size represents both x and y
$size = 5

$m = New-Object 'int[,]' ($size, $size)
$m[2,1] = 1
$m[2,2] = 1
$m[2,3] = 1

clear
printBoard

For($x=0; $x -lt 1; $x++){
    next-gen $m
    printBoard
    write-host ""
}

With the board setup in the above demo, the result of should be a blinker:

Gen0

0 0 0 0 0
0 0 0 0 0
0 1 1 1 0
0 0 0 0 0
0 0 0 0 0

Gen1

0 0 0 0 0
0 0 1 0 0
0 0 1 0 0
0 0 1 0 0
0 0 0 0 0

For those unfamiliar, the rules can be found here: Wikipedia - Conway's Game of Life


Solution

  • I added some debugging code (with write-verbose), and found where the error(s) were.

    First, you were checking neighbors in the $tmpm array (which was being updated) rather than the current generation). Second, you were setting $global:OrigM when you meant $Global:M at the end of then next-gen function, but it actually has to be $script:M, because the variable exists in the script scope, not the global one.

    Also, getNeighbors mistakenly also considered the target position itself as a neighbor instead of just the 8 surrounding positions.

    function next-gen{
        param([System.Array]$origM)
        $size=1+$origM.GetUpperBound(0) 
        $tmpM = New-Object 'int[,]' ($size, $size)
    
        For($x=0; $x -lt $size; $x++ ){
            For($y=0; $y -lt $size; $y++){
                $neighborCount = getNeighbors $origm $x $y
                if($neighborCount -lt 2 -OR $neighborCount -gt 3){
                    $tmpM[$x,$y] = 0
                    write-verbose "Clearing $x,$y"
                }
                elseif($neighborCount -eq 3 -or $OrigM[$x,$y] -eq 1){
                    $tmpM[$x, $y] = 1
                    write-verbose "Setting $x,$y"
                }
            } 
        }
         $script:M = $tmpM
    }
    
    function getNeighbors{
        param(
            [System.Array]$g,
            [Int]$x,
            [Int]$y
        )
        $newX=0
        $newY=0
        $count=0
    
        for($newX = -1; $newX -le 1; $newX++){
            for($newY = -1; $newY -le 1; $newY++){
                if($newX -ne 0 -or $newY -ne 0){
                    $neighborx=wrap $x $newx
                    $neighborY=wrap $y $newY
                    write-verbose "x=$x y=$y Nx=$neighborx Ny=$neighborY"
                    if($g[$neighborx,$neighborY] -eq 1){
                        write-verbose "Neighbor at $neighborx, $neighborY is Set!"
                        $count++
                    }
                }
            }
        }
        write-verbose "x=$x y=$y Neighbor count = $count"
        return $count
    }
    
    function wrap{
        param(
            [Int]$z,
            [Int]$zEdge
        )
    
        $z+=$zEdge
        If($z -lt 0){
            $z += $size
        }
        ElseIf($z -ge $size){
            $z -= $size
        }
        return $z
    }
    
    function printBoard{
        0..$m.GetUpperBound(0) | 
        % { $dim1=$_; (0..$m.GetUpperBound(1) | % { $m[$dim1, $_] }) -join ' ' }
        write-host ""
    }
    #board is always a square, size represents both x and y
    $size = 5
    
    $m = New-Object 'int[,]' ($size, $size)
    $m[2,1] = 1
    $m[2,2] = 1
    $m[2,3] = 1
    
    clear
    printBoard
    
    For($x=0; $x -lt 1; $x++){
        next-gen $m
        printBoard
        write-host ""
    }
    

    P.S. Your bounds checking in the loops in next-gen were off by 1, and I made it start with a clean board rather than re-using the last gen (since you explicitly set each position, it doesn't really matter).