康威生命游戏是一种很有意思的游戏,显示的是细胞的生死状态取决于周边细胞(相邻8个细胞)的存活状态,是一种模拟生命的演化过程。
反过来看,还可模拟病毒的传染,患者传染给接触者,也可以设定概率,另外是否戴口罩的概率也不一样,这样我们可以可视化病毒在某个地区的传播情况。
那么对于模拟这个世界的生命游戏,需遵循如下的游戏规则:
周边存活细胞=2,这个细胞保持原状态不变;
周边存活细胞<2,这个细胞就孤独而死;周边存活细胞>3,这个细胞就拥挤而死;
实际若死亡细胞的周边有存活细胞=3,这个死亡细胞会因为繁衍而复活。
- package main
-
- import (
- "bytes"
- "fmt"
- "math/rand"
- "time"
- )
-
- // 定义一个名为Filed的二维矩阵结构体(存放细胞的生死状态与宽高)
- // 高宽就是行列,每个细胞看做是一个单元格
- type Field struct {
- s [][]bool
- w, h int
- }
-
- // 创建一个h行w列的二维矩阵(每个细胞默认false,死亡状态)
- // 换句话说就是创建一个多大面积的世界
- func NewField(w, h int) *Field {
- s := make([][]bool, h)
- for i := range s {
- s[i] = make([]bool, w)
- }
- return &Field{s: s, w: w, h: h}
- }
-
-
- // 设置指定位置细胞的生死状态
- func (f *Field) Set(x, y int, b bool) {
- f.s[y][x] = b
- }
-
- // 这个游戏里的世界是循环世界,没有边界,就是说左右边缘相邻,上下边缘相邻,如果说超过边界就相当于回到另一边
- // 返回指定细胞(坐标点)的生死状态
- func (f *Field) Alive(x, y int) bool {
- x += f.w
- x %= f.w
- y += f.h
- y %= f.h
- return f.s[y][x]
- }
-
- // 返回下个时间步的细胞的状态
- // 3个存活细胞就复活,2个存活细胞就保持原状
- func (f *Field) Next(x, y int) bool {
- // 计算相邻的存活细胞数量
- alive := 0
- for i := -1; i <= 1; i++ {
- for j := -1; j <= 1; j++ {
- //不包括自己(0,0)的周边8个细胞的状态
- if (j != 0 || i != 0) && f.Alive(x+i, y+j) {
- alive++
- }
- }
- }
- return alive == 3 || alive == 2 && f.Alive(x, y)
- }
-
- // 存储每轮生命游戏的状态
- type Life struct {
- a, b *Field
- w, h int
- }
-
- // 返回一个随机的新生命(true,活细胞)的游戏状态
- func NewLife(w, h int) *Life {
- a := NewField(w, h)
- for i := 0; i < (w * h / 4); i++ {
- a.Set(rand.Intn(w), rand.Intn(h), true)
- }
- return &Life{
- a: a, b: NewField(w, h),
- w: w, h: h,
- }
- }
-
- // 下一代将重新计算并更新所有细胞的状态
- func (l *Life) Step() {
- // 根据当代世界a的状态去更新下一代世界b的状态
- for y := 0; y < l.h; y++ {
- for x := 0; x < l.w; x++ {
- l.b.Set(x, y, l.a.Next(x, y))
- }
- }
- // 交换世界a与b的状态(世界b的状态成为当代世界,原本a世界保留结构做下一轮计算的缓存)
- l.a, l.b = l.b, l.a
- }
-
- // 将生命游戏的界面作为字符串返回
- func (l *Life) String() string {
- var buf bytes.Buffer
- for y := 0; y < l.h; y++ {
- for x := 0; x < l.w; x++ {
- b := byte(' ')
- if l.a.Alive(x, y) {
- b = '*'
- }
- buf.WriteByte(b)
- }
- buf.WriteByte('\n')
- }
- return buf.String()
- }
-
- func main() {
- l := NewLife(100, 20)
- for i := 0; i < 300; i++ {
- l.Step()
- fmt.Print("\x0c", l) // 清屏和打印游戏状态
- time.Sleep(time.Millisecond*50)
- }
- }
当然实际上是动态变化的,这个是循环结束的一张截图,大家运行下可以看看这个生命(细胞)在整个区域的繁衍与死亡的过程。

其中Go语言函数的定义遇到和其他语言都不一样的地方,就是函数名称前面有括号的情况:
- func (f *Field) Set(x, y int, b bool) {
- f.s[y][x] = b
- }
我们看到在函数名称前面出现一个括号(f *Field),这个是Go定义这些,函数将在其上运行的对象的方式。本质上Set函数是一个类型处理程序的方法,可以使用类型处理程序的任何对象来调用,比如f
我们单独来看一个示例,再熟悉下这个用法以及指针的区别,还记得吗?
- import "fmt"
-
- type person struct {
- name string
- hobby string
- }
-
- func (p person) GetInfo1() {
- p.name = "Tony"
- p.hobby = "Reading"
- }
-
- func (p *person) GetInfo2() {
- p.name = "Tony"
- p.hobby = "Reading"
- }
-
- func main() {
- p := &person{"小丑", "Murder"}
- p.GetInfo1()
- fmt.Println(p)
- //&{小丑 Murder}
-
- p.GetInfo2()
- fmt.Println(p, p.name, p.hobby)
- //&{Tony Reading} Tony Reading
- }
可以看出这种调用函数的方式比较特别,需要前面的自定义类型的前缀来调用函数,简单的可以看做自定义类型可以当做class,然后函数当做是class下面的实例对象。