康威生命游戏 | Powershell

5

我一直在使用Powershell创建一个基本的康威生命游戏模拟器,以便更好地熟悉这种语言。但是我的当前代码错误地计算了邻居单元格的数量,因此游戏无法正常工作。从我所看到的来判断,我认为我的环绕方式是正确的,但是我的邻居计数似乎有问题。

以下是代码:

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 ""
}

在上面的演示中,使用以上板子设置,应该得到一个闪烁器:

第0代

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

第一代

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

对于那些不熟悉的人,规则可以在这里找到: 维基百科 - 生命游戏


你非常模糊地描述了问题。邻居计数有什么问题?调试告诉了你什么? - Carcigenicate
邻居计数返回的值比矩阵表示的要高。对于模糊性感到抱歉,我已经摆弄了几个小时,所以我不想发布所有的调试输出。 - Christopher
以特定值或其他未知方式持续更高?我已经扫描了您的逻辑,针对PowerShell的典型错误/不正确的假设,并没有看到任何明显的问题。 - Mark Wragg
@MarkWragg getNeighbors函数返回的值如下(0000123313531121),这不仅是错误的(在gen1中没有5个邻居细胞),而且还很短...我期望返回25个值。也许迭代有误。 - Christopher
这里有一个不必要的冒号,但实际上并没有引起任何问题:$z -= $:size - Mark Wragg
@MarkWragg 对,我认为错误在于我的逻辑,而不是代码本身。 - Christopher
3个回答

2

我添加了一些调试代码(使用write-verbose),并找到了错误出现的位置。

首先,你在检查邻居时使用了$tmpm数组(该数组正在更新),而不是当前的代数。 其次,在下一个生成函数的结尾处,你设置了$global:OrigM,但实际上应该是$script:M,因为该变量存在于脚本作用域中,而不是全局作用域中。

此外,getNeighbors错误地将目标位置本身也视为邻居,而不仅仅是周围的8个位置。

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. 你在next-gen中的循环边界检查有误,我已将其更正,从一个干净的板子开始而不是重用上一代(因为您明确设置了每个位置,所以这并不重要)。


我在你发布之前大约2分钟发现了第二个错误,但它仍然没有起作用,我准备用另一种语言重新制作它!谢谢,我从未使用过write-verbose,我将不得不研究一下。 - Christopher
1
要查看这些内容,请将$verbosepreference设置为“Continue”。 - Mike Shepard

1

Mike Shepard's helpful answer已经提供了一个可行的解决方案,并解释了问题代码的问题。

让我补充一下,这是一个重构版本,它:

  • 通过[ref](按引用)变量原地更新板变量,以便后续调用按预期工作。

  • 具有灵活的显示功能,允许指定要显示的代数数量、是否原地更新显示以及每代之间暂停的时间长度。

    • 如果按原样运行代码,则显示5个代数,原地更新,每代之间暂停1秒。
  • 使用更符合PowerShell命名约定的函数名称。

  • 使代码更加健壮,例如:

    • 完全避免使用脚本级(或全局)变量。
      • 顺便说一下:全局变量是会话全局的,在脚本退出后仍然保持在作用域内;如果需要“脚本全局”变量,请使用$script:作用域;该脚本作用域(而不是全局作用域)是在脚本顶层创建未明确指定作用域的变量时的默认作用域。
    • 为接收板的参数使用强类型的[int[,]]参数。
    • 防止对未初始化变量的引用。

注意:完整游戏的可交互功能实现可以在this Gist中找到;适用于Windows PowerShell v3+和PowerShell Core。


$ErrorActionPreference = 'Stop' # Abort on all unhandled errors.
Set-StrictMode -version 1 # Prevent use of uninitialized variables.

# Given a board as $m_ref, calculates the next generation and assigns it
# back to $m_ref.
function update-generation {
    param(
      [ref] [int[,]]$m_ref  # the by-reference board variable (matrix)
    )

    # Create a new, all-zero clone of the current board (matrix) to 
    # receive the new generation.
    $m_new = New-Object 'int[,]' ($m_ref.Value.GetLength(0), $m_ref.Value.GetLength(1))

    For($x=0; $x -le $m_new.GetUpperBound(0); $x++ ){
        For($y=0; $y -le $m_new.GetUpperBound(1); $y++){
            # Get the count of live neighbors.
            # Note that the *original* matrix must be used to:
            #  - determine the live neighbors
            #  - inspect the current state
            # because the game rules must be applied *simultaneously*.
            $neighborCount = get-LiveNeighborCount $m_ref.Value $x $y
            if ($m_ref.Value[$x,$y]) { # currently LIVE cell
              # A live cell with 2 or 3 neighbors lives, all others die.
              $m_new[$x,$y] = [int] ($neighborCount -eq 2 -or $neighborCount -eq 3)
            } else { # curently DEAD cell
              # A currently dead cell is resurrected if it has 3 live neighbors.
              $m_new[$x,$y] = [int] ($neighborCount -eq 3)
            }
            $null = $m_new[$x,$y]
        } 
    }

    # Assign the new generation to the by-reference board variable.
    $m_ref.Value = $m_new
}

# Get the count of live neighbors for board position $x, $y.
function get-LiveNeighborCount{
  param(
      [int[,]]$m, # the board (matrix)
      [Int]$x,
      [Int]$y
  )

  $xLength = $m.GetLength(0)
  $yLength = $m.GetLength(1)

  $count = 0
  for($xOffset = -1; $xOffset -le 1; $xOffset++) {
    for($yOffset = -1; $yOffset -le 1; $yOffset++) {
      if (-not ($xOffset -eq 0 -and $yOffset -eq 0)) { # skip the position at hand itself
        if($m[(get-wrappedIndex $xLength ($x + $xOffset)),(get-wrappedIndex $yLength ($y + $yOffset))]) {
          $count++
        }
      }
    }
  }
  # Output the count.
  $count
}

# Given a potentially out-of-bounds index along a dimension of a given length, 
# return the wrapped-around-the-edges value.
function get-wrappedIndex{
  param(
      [Int]$length,
      [Int]$index
  )

  If($index -lt 0){
    $index += $length
  }
  ElseIf($index -ge $length){
    $index -= $length
  }
  # Output the potentially wrapped index.
  $index
}

# Print a single generation's board.
function show-board {
  param(
    [int[,]] $m # the board (matrix)
  )
  0..$m.GetUpperBound(0) |
    ForEach-Object { 
      $dim1=$_
      (0..$m.GetUpperBound(1) | ForEach-Object { $m[$dim1, $_] }) -join ' ' 
    }
}

# Show successive generations.
function show-generations {

  param(
    [int[,]] $Board,
    [uint32] $Count = [uint32]::MaxValue,
    [switch] $InPlace,
    [int] $MilliSecsToPause
  )

  # Print the initial board (the 1st generation).
  Clear-Host
  show-board $Board

  # Print the specified number of generations or 
  # indefinitely, until Ctrl+C is pressed.
  [uint32] $i = 1
  while (++$i -le $Count -or $Count -eq [uint32]::MaxValue) {

    # Calculate the next generation.
    update-generation ([ref] $Board)

    if ($MilliSecsToPause) {
      Start-Sleep -Milliseconds $MilliSecsToPause
    }
    if ($InPlace) {
      Clear-Host
    } else {
      '' # Output empty line before new board is printed.
    }

    # Print this generation.
    show-board $Board

  } 

}

# Board is always a square, $size represents both x and y.
$size = 5
$board = New-Object 'int[,]' ($size, $size)

# Seed the board.
$board[2,1] = 1
$board[2,2] = 1
$board[2,3] = 1

# Determine how many generations to show and how to show them.
$htDisplayParams = @{
  Count = 5         # How many generations to show (1 means: just the initial state);
                    # omit this entry to keep going indefinitely.
  InPlace = $True   # Whether to print subsequent generations in-place.
  MilliSecsToPause = 1000 # To slow down updates.
}

# Start showing the generations.
show-generations -Board $board @htDisplayParams

1
谢谢。当我看到这段代码时,我的主要关注点是让它能够工作。 - Mike Shepard
谢谢您!这是一个很棒的小工具,可以帮助我更好地理解 :) 接下来 - 获得图形输出!太棒了! :) - Christopher

0

警告:您即将看到的内容极其hackish和...具有图形性质。 实际上,这段代码即使对于新手来说也很松散,所以我道歉。

这是一个在控制台中以黑白输出的版本。如果您想要,它还会随机生成一些种子。它不太好看,而且我现在正在工作,没有时间花在它上面:) 希望在本周末发布一个整洁的版本到github。

$ErrorActionPreference = 'Stop' # Abort on all unhandled errors.
Set-StrictMode -version 1 # Prevent use of uninitialized variables.

# Given a board as $m_ref, calculates the next generation and assigns it
# back to $m_ref.
function update-generation {
    param(
      [ref] [int[,]]$m_ref  # the by-reference board variable (matrix)
    )

    # Create a new, all-zero clone of the current board (matrix) to 
    # receive the new generation.
    $m_new = New-Object 'int[,]' ($m_ref.Value.GetLength(0), $m_ref.Value.GetLength(1))

    For($x=0; $x -le $m_new.GetUpperBound(0); $x++ ){
        For($y=0; $y -le $m_new.GetUpperBound(1); $y++){
            # Get the count of live neighbors.
            # Note that the *original* matrix must be used to:
            #  - determine the live neighbors
            #  - inspect the current state
            # because the game rules must be applied *simultaneously*.
            $neighborCount = get-LiveNeighborCount $m_ref.Value $x $y
            if ($m_ref.Value[$x,$y]) { # currently LIVE cell
              # A live cell with 2 or 3 neighbors lives, all others die.
              $m_new[$x,$y] = [int] ($neighborCount -eq 2 -or $neighborCount -eq 3)
            } else { # curently DEAD cell
              # A currently dead cell is resurrected if it has 3 live neighbors.
              $m_new[$x,$y] = [int] ($neighborCount -eq 3)
            }
            $null = $m_new[$x,$y]
        } 
    }

    # Assign the new generation to the by-reference board variable.
    $m_ref.Value = $m_new
}

# Get the count of live neighbors for board position $x, $y.
function get-LiveNeighborCount{
  param(
      [int[,]]$m, # the board (matrix)
      [Int]$x,
      [Int]$y
  )

  $xLength = $m.GetLength(0)
  $yLength = $m.GetLength(1)

  $count = 0
  for($xOffset = -1; $xOffset -le 1; $xOffset++) {
    for($yOffset = -1; $yOffset -le 1; $yOffset++) {
      if (-not ($xOffset -eq 0 -and $yOffset -eq 0)) { # skip the position at hand itself
        if($m[(get-wrappedIndex $xLength ($x + $xOffset)),(get-wrappedIndex $yLength ($y + $yOffset))]) {
          $count++
        }
      }
    }
  }
  # Output the count.
  $count
}

# Given a potentially out-of-bounds index along a dimension of a given length, 
# return the wrapped-around-the-edges value.
function get-wrappedIndex{
  param(
      [Int]$length,
      [Int]$index
  )

  If($index -lt 0){
    $index += $length
  }
  ElseIf($index -ge $length){
    $index -= $length
  }
  # Output the potentially wrapped index.
  $index
}

# Print a single generation's board.
function show-board {
  param(
    [int[,]] $m # the board (matrix)
  )
  0..$m.GetUpperBound(0) |
    ForEach-Object { 
      $dim1=$_
      (0..$m.GetUpperBound(1) | ForEach-Object { $m[$dim1, $_] }) -join ' ' 
    }
}

# Show successive generations.
function show-generations {

  param(
    [int[,]] $Board,
    [uint32] $Count = [uint32]::MaxValue,
    [switch] $InPlace,
    [int] $MilliSecsToPause
  )

  # Print the initial board (the 1st generation).


  Clear-Host
  #show-board $Board
  drawIt $Board
  # Print the specified number of generations or 
  # indefinitely, until Ctrl+C is pressed.
  [uint32] $i = 1
  while (++$i -le $Count -or $Count -eq [uint32]::MaxValue) {

    # Calculate the next generation.
    update-generation ([ref] $Board)

    if ($MilliSecsToPause) {
      Start-Sleep -Milliseconds $MilliSecsToPause
    }
    if ($InPlace) {
      Clear-Host
    } else {
      '' # Output empty line before new board is printed.
    }

    # Print this generation.
    #show-board $Board
    drawIt $Board
  } 

}

function drawIt{
    param([int[,]]$m_ref)


     For($x=0; $x -le $m_ref.GetUpperBound(0); $x++ ){
        For($y=0; $y -le $m_ref.GetUpperBound(1); $y++){
            $val = $m_ref[$x,$y]
           If($val -eq 1){
               $cellColor = 'White'
           }
           Else{
               $cellColor = 'Black'
           }
           #write-host $cellColor.GetType()
           Write-Host " " -NoNewline -BackgroundColor $CellColor
           Write-Host " " -NoNewline
        }
        Write-Host '' #start a new line
    }

}

# Board is always a square, $size represents both x and y.
$size = 10 #change this to change size of the board
$board = New-Object 'int[,]' ($size, $size)
$seedCount = 17 #change this to change # of alive cells to start with
# Seed the board.
for($seed = 0; $seed -lt $seedCount ; $seed ++){
    $board[$(Get-random -Maximum ($size -1)),$(Get-random -Maximum ($size -1))] = 1
}

# Determine how many generations to show and how to show them.
$htDisplayParams = @{
  Count = 25         # how many generations to show (1 means: just the initial state)
                    # omit this entry to keep going indefinitely
  InPlace = $True   # whether to print subsequent generations in-place
  MilliSecsToPause = 1000 # To slow down updates
}

# Start showing the generations.
show-generations -Board $board @htDisplayParams

非常感谢mklement0和Mike Shepard抽出时间帮助并提供指导和建议。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接