banner
biuaxia

biuaxia

"万物皆有裂痕,那是光进来的地方。"
github
bilibili
tg_channel

Golangによるコンウェイのライフゲームの実装

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 型を定義し、ブール型の値 truefalse を使用して細胞の生存と死亡をそれぞれ表します:

type Universe [][]bool

スライスを使用して世界を表現することで、関数やメソッドが世界をより簡単に共有および変更できるようになります。

その後、make を使用して heightwidth 列の 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) になります。これは heighty を加算することで得られます。もし y がグリッドの height を超えた場合、以前に計算したうるう年の際に紹介した剰余演算子(%)を使用し、yheight で割った余りを得ることができます。この方法は xwidth にも適用できます。

隣接細胞のカウント#

与えられた細胞の隣接する生存細胞の数をカウントし、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

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。