PowerShellでライフゲーム

#はじめに
そういえば幼い以上若い未満の頃に眺めてたなぁと思って。
ルール確認したらそこまで難しくはなさそうな気がしたので力試しに書いてみた。
クラスの使い方の勉強を兼ねて、せっかくなので色をつけてみた。

#参考資料
[ライフゲーム - Wikipedia](https://ja.wikipedia.org/wiki/%E3%83%A9%E3%82%A4%E3%83%95%E3%82%B2%E3%83%BC%E3%83%A0)
ルールの確認から。あってんのかこの実装？

[超簡単な PowerShell Class の使い方(その1)](http://www.vwnet.jp/windows/PowerShell/2017082001/PSv5Class01.htm)
内部に値を保持できる関数？内部に関数を持てる構造体？ぐらいの認識。
今回は二次元配列の各要素にセルのクラスを設定した。
値は生/死とRGBの4値を設定し、関数は初期設定、殺し、生存確認の関数を設定。

[ImageMagick](https://www.imagemagick.org/script/index.php)
頼ってしまった。GIFの出力が楽そうでつい...

[連番画像から簡単にgifアニメを作る](https://qiita.com/ongaeshi/items/21fceae420b7330b0c1b)
ありがとうございました。すごく簡単です。

#環境
- Windows10
- Powershell
- ImageMagick-7.0.8-11-Q16-x64

```Powershell
PS C:\Users\(USER)> $PSVersionTable

Name                           Value                                                                                           
----                           -----                                                                                           
PSVersion                      5.1.15063.1155                                                                                  
PSEdition                      Desktop                                                                                         
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}                                                                         
BuildVersion                   10.0.15063.1155                                                                                 
CLRVersion                     4.0.30319.42000                                                                                 
WSManStackVersion              3.0                                                                                             
PSRemotingProtocolVersion      2.3                                                                                             
SerializationVersion           1.1.0.1    

```
#ソースコード
```powershell:LifeGame.ps1

#LifeGame on PowerShell (勉強版)

#セルのクラスを作ってみた
class cell{
    #内部データ
    [int]$alive #セルの生/死
    [int]$red #セルの色
    [int]$green
    [int]$blue
    #クラス設定時の初期設定
    cell(){
        $this.alive = [math]::Round((get-random -max 1.0 -min 0),0) #ランダムで生死決定
        if($this.alive -eq 1){ #生きてたら色を持たせる、0だと死にうるので最低は1
            $this.red = get-random -max 255 -min 1
            $this.green = get-random -max 255 -min 1
            $this.blue = get-random -max 255 -min 1
        } else {
            $this.red = 0
            $this.green = 0
            $this.blue = 0
        }
    }
    #セルを殺すメソッド
    [cell]kill(){ 
        $this.alive = 0
        $this.red = 0
        $this.green = 0
        $this.blue = 0
        return $this
    }
    #セルの生存確認メソッド
    [cell]alivecheck([int]$inalivesum,[int]$inredsum,[int]$ingreensum,[int]$inbluesum){
        switch($inalivesum){ #隣接セル数で生存判断
            2{ #2は現状維持
                $this.alive = $this.alive
            }
            3{ #3はスポーンor現状維持
                if($this.alive -eq 0){ #スポーンする際は新たに色を手に入れる（その後混ざる）
                    $this.red = get-random -max 255 -min 1
                    $this.green = get-random -max 255 -min 1
                    $this.blue = get-random -max 255 -min 1
                }
                $this.alive = 1;
            }
            default{ #1以下および4以上は死
                $this.alive = 0
            }
        }
        #生きてた/生まれたら周囲の生きてる色たちから色を分けてもらう
        if($this.alive -eq 1){
            $this.red = ($this.red+$inredsum)/($inalivesum+1)
            $this.green = ($this.green+$ingreensum)/($inalivesum+1)
            $this.blue = ($this.blue+$inbluesum)/($inalivesum+1)
        } else {
            $this.red = 0
            $this.green = 0
            $this.blue = 0
        }
        #Write-Host Alive=($this.alive), Color=($this.red,$this.green,$this.blue)
        return $this
    }
}

#メイン
#画像を扱えるようにする
Add-Type -AssemblyName System.Drawing 

#変数定義
$length = 64 #メインの最大幅
#計算フィールド
$field_main = New-Object "object[,]" $length,$length
$field_calc = New-Object "object[,]" ($length+2),($length+2) #計算用にちょっと大きく
$field_tmp = New-Object "object[,]" $length,$length #計算結果の仮置き用。結果をここに入力しきってからメインにコピーしにいく。
$field_arround =@() #周囲計算用の配列
#実行数
$count=0
$countmax = 200
#最終出力先の準備
$path = split-path ($MyInvocation.MyCommand.path) -Parent
mkdir Slide -force #ファイルの保存先を強制的に作り出す（上書き確認省略のため）
$savepath = $path+"\Slide"
$pict_single= New-Object System.Drawing.Bitmap($length,$length) #スライス画像の出力先

#初期設定
#計算用フィールドに仮のセルを叩き込む
(0..($length+1))|%{
    $y=$_
    (0..($length+1))|%{
        $x=$_
        $field_calc[$x,$y] = New-Object cell
    }
}
#計算用セルの一番外側を殺しておく
(0..($length+1))|%{
    $field_calc[$_,0].kill()
    $field_calc[$_,($length+1)].kill()
    $field_calc[0,$_].kill()
    $field_calc[($length+1),$_].kill()
}

#メインのフィールドに（フチを除いて）コピーする
(0..($length-1))|%{
    $x=$_
    (0..($length-1))|%{
        $y=$_
        $field_main[$x,$y] = $field_calc[($x+1),($y+1)]
    }
}

#ここから生存計算
while($count -lt $countmax){
    Write-Host COUNT = ($count+1)
    (1..($length))|%{
        $x = $_
        (1..($length))|%{
            $y=$_
            $arroundlist=@() #周囲計算セルの初期化
            #対象セルの周囲セルを取得し配列にカッこむ
            $arroundlist+=$field_calc[($x-1),($y-1)]
            $arroundlist+=$field_calc[$x,($y-1)]
            $arroundlist+=$field_calc[($x+1),($y-1)]
            $arroundlist+=$field_calc[($x-1),$y]
            $arroundlist+=$field_calc[($x+1),$y]
            $arroundlist+=$field_calc[($x-1),($y+1)]
            $arroundlist+=$field_calc[$x,($y+1)]
            $arroundlist+=$field_calc[($x+1),($y+1)]
            #必要な要素の計算
            $alivesum=0 #周囲8セルの生存数、の初期化
            $redsum=0 #周囲8セルのうち生存セルの色の和、の初期化
            $bluesum=0
            $greensum=0
            #周囲セルの条件計算
            foreach($i in $arroundlist){
                $alivesum+=$i.alive #セルの生存数の和
                if($i.alive -eq 1){ #参照したセルが生きてたら色を分けてもらう
                    $redsum+=$i.red
                    $greensum+=$i.green
                    $bluesum+=$i.blue
                }
            }
            #生存計算（クラス内関数）
            $field_tmp[($x-1),($y-1)]= $field_calc[$x,$y].alivecheck($alivesum,$redsum,$greensum,$bluesum)
        }
    }

    #計算結果を落とし込む
    (0..($length-1))|%{
        $x=$_
        (0..($length-1))|%{
            $y=$_
            $field_main[$x,$y] = $field_tmp[$x,$y]
            $field_calc[($x+1),($y+1)] = $field_tmp[$x,$y]
            #計算結果セルを画像出力
            $color=[System.Drawing.Color]::FromArgb(($field_main[$x,$y]).red,($field_main[$x,$y]).green,($field_main[$x,$y]).blue)
            $pict_single.SetPixel($x,$y,$color)
        }
    }
    #結果の単体保存（のちに結合）
    $pict_single.Save($savepath+"\Slide_"+$count.tostring("000")+".png",[System.Drawing.Imaging.ImageFormat]::Png)
    $count++
}

#連番をGIFで出力(Image Magickを使用)
magick convert -layers optimize -loop 1 -delay 5 $savepath\Slide_*.png $savepath\Slide_gif.gif
#画像の解放
$pict_single.Dispose()
```
#出力
![Slice.png](https://qiita-image-store.s3.amazonaws.com/0/276779/a74c8d9b-85c6-a919-680e-c80be354a839.png)
わー

![Slide_gif.gif](https://qiita-image-store.s3.amazonaws.com/0/276779/dd081115-3979-3adb-64bd-b1b7b0f31847.gif)
わー米津玄師のLoserっぽい。
たーのしー。

#所感
-フチの部分って周囲8セルの計算どうするのかわからなかったので、もう1周り大きい領域を作って、はみ出た部分を死で埋めつくした。
このやり方だと基本的にフチはほぼ確実に死に見舞われそう。正しいやり方を知りたい。
-周囲8セルの情報取得部分をもうちょっとスマートにしたい。
-作ったPNGぐらい最後に処分しようや自分