読者です 読者をやめる 読者になる 読者になる

エンジニアの頭の中

フリーランスエンジニアが書く技術系ブログです。

Goでライフゲーム(Conway's Game of Life)を書いてみた

先日、プログラミング言語を学ぶ際に、よくライフゲームを書いてみるという記事を書きましたが、最近Goに興味を持ち出して、Goでライフゲームを書いてみたくなりました。

以下は、以前の記事です。この時は、JavaScriptとHTMLで作ったライフゲームを記事に載せました。

mi2.hatenablog.com

Goで書いてみた

で、早速Goでライフゲームを書いてみました。こちらが、今回Goで作ってみたライフゲームの画面です。

f:id:mitsu3204:20170325165916p:plain

ターミナル上で稼働します。以下の通り、キー操作が可能です。

  • スペース:セルの進化の停止、再開を切り替えます。
  • Control + R:セルをランダムに再生成します。
  • Control + U:セルの進化の速度を上げます。
  • Control + D:セルの進化の速度を下げます。
  • Escape:ライフゲームを終了します。

ターミナルへのセルの出力方法

termbox-goというGoで作られたターミナル出力を補助するためのライブラリを使用してみました。

github.com

termbox-goは初めて使用しましたが、termbox-goを使用して書かれているgotetrisというテトリスゲームのソースコードを参考にしたら、簡単に使用することができました。

github.com

ちなみに、gotetrisはgoで書いたテトリス(ゲームの)で、ターミナル上でテトリスをプレイすることができます。

termbox-goの使い方は簡単なので、以下の二つのソースを見れば大体わかると思います。

ソースコード

以下がソースコードです。1ファイルのみで書いています。

package main

import (
	"math/rand"
	"time"

	"github.com/nsf/termbox-go"
)

const (
	backgroundColor = termbox.ColorBlack
	cellColor       = termbox.ColorGreen
	textColor       = termbox.ColorWhite
	cellWidth       = 2
	size            = 32
	marginTop       = 2
	marginLeft      = 4
)

var (
	cells        = [size][size]bool{}
	pause        = true
	drawInterval = 100 * time.Millisecond
)

func reset() {
	for row := 0; row < len(cells); row++ {
		for col := 0; col < len(cells[row]); col++ {
			cells[row][col] = rand.Intn(10) < 4
		}
	}
}

func next() {
	newCells := [size][size]bool{}
	for row := 0; row < size; row++ {
		for col := 0; col < size; col++ {
			c := countIfAlive(row, col)
			if c == 3 {
				newCells[row][col] = true
			} else if c == 2 {
				newCells[row][col] = cells[row][col]
			} else {
				newCells[row][col] = false
			}
		}
	}
	cells = newCells
}

func countIfAlive(row, col int) (count int) {
	if row > 0 {
		if col > 0 && cells[row-1][col-1] {
			count++
		}
		if cells[row-1][col] {
			count++
		}
		if col < size-1 && cells[row-1][col+1] {
			count++
		}
	}
	if col > 0 && cells[row][col-1] {
		count++
	}
	if col < size-1 && cells[row][col+1] {
		count++
	}
	if row < size-1 {
		if col > 0 && cells[row+1][col-1] {
			count++
		}
		if cells[row+1][col] {
			count++
		}
		if col < size-1 && cells[row+1][col+1] {
			count++
		}
	}
	return
}

func draw() {
	for row := 0; row < len(cells); row++ {
		for col := 0; col < len(cells[row]); col++ {
			color := backgroundColor
			if cells[row][col] {
				color = cellColor
			}
			for i := 0; i < cellWidth; i++ {
				termbox.SetCell(marginLeft+col*cellWidth+i, marginTop+row, ' ', color, color)
			}
		}
	}
}

func drawText(x, y int, text string) {
	for _, c := range text {
		termbox.SetCell(size+4+x, y, c, textColor, backgroundColor)
		x++
	}
}

func drawKeyOperationsText() {
	y := marginTop
	drawText(size+8, y, "+----------+----------------+")
	y++
	drawText(size+8, y, "| Key      | Function       |")
	y++
	drawText(size+8, y, "+----------+----------------+")
	y++
	drawText(size+8, y, "| Space    | Run or Pause   |")
	y++
	drawText(size+8, y, "| Ctrl + R | Reset cells    |")
	y++
	drawText(size+8, y, "| Ctrl + U | Speed up       |")
	y++
	drawText(size+8, y, "| Ctrl + D | Speed down     |")
	y++
	drawText(size+8, y, "| Escape   | Exit           |")
	y++
	drawText(size+8, y, "+----------+----------------+")
	y++
}

func main() {

	err := termbox.Init()
	if err != nil {
		panic(err)
	}
	defer termbox.Close()

	eventQueue := make(chan termbox.Event)
	go func() {
		for {
			eventQueue <- termbox="" pollevent="" clear="" backgroundcolor="" reset="" draw="" drawkeyoperationstext="" flush="" loop:="" for="" select="" case="" event="" :="<-eventQueue:" if="" type="=" eventkey="" key="=" keyesc="" break="" loop="loop" else="" keyspace="" pause="true" keyctrlr="" keyctrld="" drawinterval="" 50="" time="" millisecond="" keyctrlu=""> 50*time.Millisecond {
						drawInterval -= 50 * time.Millisecond
					}
				}
			}
		default:
			if !pause {
				termbox.Clear(backgroundColor, backgroundColor)
				next()
				draw()
				drawKeyOperationsText()
			}
		}
		termbox.Flush()
		time.Sleep(drawInterval)
	}
}

グライダーガンとか、エデンの園などの固有のパターンも表示できるように改良していきたいです。