title: Golangによるコンウェイのライフゲームの実装
date: 2022-05-23 17:15:00
toc: false
index_img: http://api.btstu.cn/sjbz/?lx=m_dongman&cid=2
category:
- Go
tags:
- 読み取り
- 作成
- Golang
- 繰り返し
- 計算
- 解決
- シミュレーション
- 出力
- 統計
- 印刷
- ゲーム
---
# 実験:スライスの人生
本実験では、「コンウェイのライフゲーム」(Conway's Game of Life)というシミュレーターを構築し、それを使用して人間の繁殖過程をシミュレートします。シミュレーションは細胞で満たされた二次元グリッド上で行われるため、今回の実験はスライスに焦点を当てます。
グリッド内の各細胞は、水平、垂直、対角線方向に合計8つの隣接細胞を持っています。各世代において、単一の細胞の生死は隣接細胞の生存数に依存します。
## 開天辟地
ライフゲームを初めて実装する際、世界を固定サイズに制限する必要があります。具体的には、グリッドのサイズを決定し、対応する定数を定義する必要があります:
```go
const (
width = 80
height = 15
)
次に、二次元細胞グリッドを保持するための Universe
型を定義し、ブール型の値 true
と false
を使用して細胞の生存と死亡をそれぞれ表します:
type Universe [][]bool
スライスを使用して世界を表現することで、関数やメソッドが世界をより簡単に共有および変更できるようになります。
その後、make
を使用して height
行 width
列の Universe
を割り当てて返す NewUniverse
関数を作成します:
func NewUniverse() Universe
新しく割り当てられたスライスの各要素はデフォルトのゼロ値 false
に設定されるため、世界の最初には生存細胞は存在しません。
世界を観察する#
Universe
に対して、現在の世界の状態を fmt
パッケージの関数を使用して画面に印刷するメソッドを作成してください。生存している細胞はアスタリスクで表示され、死亡している細胞は空白で表示されます。さらに、各行の細胞を印刷した後にカーソルを新しい出力行に移動させる必要があります:
func (u Universe) Show()
NewUniverse
関数を呼び出して新しい世界を作成し、Show
関数を呼び出してその世界を印刷する main
関数を作成してください。実験を続ける前に、プログラムが正常に動作することを確認してください。たとえ世界全体に生存細胞がまだ存在しなくてもです。
細胞を活性化する#
世界の約 25% の細胞をランダムに活性化する Seed
メソッドを作成してください(対応するスライス要素の値を true
に設定します):
func (u Universe) Seed()
このメソッドを実装する際には、math/rand
パッケージをインポートして Intn
関数を使用することを忘れないでください。その後、main
関数を修正し、Seed
メソッドを使用して世界を活性化し、Show
関数を使用して活性化された世界を印刷してください。
適者生存#
以下はコンウェイのライフゲームの具体的なルールです:
- 生存している細胞の隣接する生存細胞が 2 つ未満の場合、その細胞は死亡します。
- 生存している細胞の隣接する生存細胞が 2 つまたは 3 つの場合、その細胞は次の世代に引き継がれます。
- 生存している細胞の隣接する生存細胞が 3 つを超える場合、その細胞は死亡します。
- 死亡している細胞の隣接する生存細胞がちょうど 3 つの場合、その細胞は生存します。
これらのルールを実現するために、以下の 3 つのステップに分解し、それぞれのステップを対応するメソッドとして実装する必要があります:
- 細胞が生存しているかどうかを判断するメソッド
- 隣接する生存細胞の数をカウントする能力
- 次の世代で細胞が生存するか死亡するかのロジック
生存か死亡か#
細胞が生存しているかどうかを判断するには、Universe
スライス内の対応する要素のブール値をチェックします。その値が true
であれば、細胞は生存しています。
Universe
型に対して、以下のシグネチャを持つ Alive
メソッドを作成してください:
func (u Universe) Alive(x, y int) bool
Alive
メソッドを実装する際に最も難しいのは、境界外の状況を処理することです。たとえば、(-1, -1)
に位置する細胞が生存しているか死亡しているかをどう判断しますか?または、80x15
のグリッド上で (80, 15)
に位置する細胞が生存しているか死亡しているかをどう判断しますか?
この問題を解決するために、世界にラッピングを実装する必要があります。こうすることで、(0, 0)
に隣接する上方は (0, -1)
ではなく (0, 14)
になります。これは height
と y
を加算することで得られます。もし y
がグリッドの height
を超えた場合、以前に計算したうるう年の際に紹介した剰余演算子(%
)を使用し、y
を height
で割った余りを得ることができます。この方法は x
と width
にも適用できます。
隣接細胞のカウント#
与えられた細胞の隣接する生存細胞の数をカウントし、0~8
を返すメソッドを作成してください:
func (u Universe) Neighbors(x, y int) int
世界をラッピングするために、直接世界データにアクセスするのではなく Alive
メソッドを使用してください。
また、隣接細胞をカウントする際に、与えられた細胞をカウントに含めないように注意してください。
ゲームロジック#
隣接する生存細胞の数をカウントするメソッドを実装した後、Next
メソッド内で前述のゲームルールを正式に実装できます:
func (u Universe) Next(x, y int) bool
このメソッドは世界を直接変更するのではなく、ブール値を返し、与えられた細胞が次の世代で生存するか死亡するかを示します。
平行世界#
シミュレーション操作を完了するために、プログラムは世界の各細胞を繰り返し処理し、Next
を使用して次の世代の状態を判断する必要があります。
ここで注意すべき問題は、隣接細胞をカウントする際には世界の以前の状態に基づく必要があることです。プログラムが統計を実行している間に世界を直接変更すると、その変更が隣接細胞の統計結果に影響を与えることになります。
この問題を解決する簡単な方法は、同じサイズの 2 つの世界を作成し、世界 A
を読み取る際に世界 B
を設定することです。Step
関数を作成してこの操作を実行してください:
func Step(a, b Universe)
世界 B
が次の世代に更新された後、プログラムはこれら 2 つの世界を交換し、次の更新を続けることができます:
a, b = b, a
新しい世代の細胞を表示する前に、プログラムは特殊な ANSI
エスケープシーケンス "\x0c"
を使用して画面をクリアする必要があります。その後、プログラムは全世界を印刷し、time
パッケージの Sleep
関数を使用して世代の変化の速度を遅くします。
注意:
Go Playground
以外の場所では、画面をクリアするために他のメカニズムを使用する必要があります。たとえば、macOS
では"\x0c"
の代わりに"\033[H"
を印刷する必要があります。
これで、コンウェイのライフゲームを作成し、Go Playground
で実行するために必要なすべてのコンポーネントが揃いました。
実装#
開天辟地までのコード#
package main
import "fmt"
const (
width = 25
height = 15
)
type Universe [][]bool
func NewUniverse() Universe {
u := make(Universe, height)
for i := range u {
u[i] = make([]bool, width)
}
return u
}
func main() {
u := NewUniverse()
for _, i := range u {
fmt.Println(i)
}
}
実行結果:
[false false false false false false false false false false false false false false false false false false false false false false false false false]
[false false false false false false false false false false false false false false false false false false false false false false false false false]
[false false false false false false false false false false false false false false false false false false false false false false false false false]
[false false false false false false false false false false false false false false false false false false false false false false false false false]
[false false false false false false false false false false false false false false false false false false false false false false false false false]
[false false false false false false false false false false false false false false false false false false false false false false false false false]
[false false false false false false false false false false false false false false false false false false false false false false false false false]
[false false false false false false false false false false false false false false false false false false false false false false false false false]
[false false false false false false false false false false false false false false false false false false false false false false false false false]
[false false false false false false false false false false false false false false false false false false false false false false false false false]
[false false false false false false false false false false false false false false false false false false false false false false false false false]
[false false false false false false false false false false false false false false false false false false false false false false false false false]
[false false false false false false false false false false false false false false false false false false false false false false false false false]
[false false false false false false false false false false false false false false false false false false false false false false false false false]
[false false false false false false false false false false false false false false false false false false false false false false false false false]
Program exited.
完全なコード#
package main
import (
"fmt"
"math/rand"
"time"
)
const (
width = 80
height = 15
)
type Universe [][]bool
func NewUniverse() Universe {
u := make(Universe, height)
for i := range u {
u[i] = make([]bool, width)
}
return u
}
func (u Universe) Set(x, y int, b bool) {
u[y][x] = b
}
func (u Universe) Seed() {
for i := 0; i < (width * height / 4); i++ {
u.Set(rand.Intn(width), rand.Intn(height), true)
}
}
func (u Universe) Alive(x, y int) bool {
x = (x + width) % width
y = (y + height) % height
return u[y][x]
}
func (u Universe) Neighbors(x, y int) int {
n := 0
for v := -1; v <= 1; v++ {
for h := -1; h <= 1; h++ {
if !(v == 0 && h == 0) && u.Alive(x+h, y+v) {
n++
}
}
}
return n
}
func (u Universe) Next(x, y int) bool {
n := u.Neighbors(x, y)
return n == 3 || n == 2 && u.Alive(x, y)
}
func (u Universe) String() string {
var b byte
buf := make([]byte, 0, (width+1)*height)
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
b = ' '
if u[y][x] {
b = '*'
}
buf = append(buf, b)
}
buf = append(buf, '\n')
}
return string(buf)
}
func (u Universe) Show() {
fmt.Print("\x0c", u.String())
}
func Step(a, b Universe) {
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
b.Set(x, y, a.Next(x, y))
}
}
}
func main() {
a, b := NewUniverse(), NewUniverse()
a.Seed()
for i := 0; i < 300; i++ {
Step(a, b)
a.Show()
time.Sleep(time.Second / 30)
a, b = b, a
}
}
Go Playground で確認してください:Go Playground - The Go Programming Language