第四章:ADT 实践:生命游戏
1. 生命游戏
网格细胞演化规则
"生命游戏"(Conway's Game of Life)是由数学家约翰·康威(John Conway)于1970年发明的细胞自动机。这个游戏展示了如何通过简单的规则来模拟复杂的系统行为。我们将使用 Go 来实现这个游戏,并展示如何利用抽象数据类型(ADT)来简化实现。
规则概述
生命游戏在一个二维网格上进行,网格中的每个细胞有两种状态:活的或死的。细胞的状态根据以下规则更新:
-
活细胞:
- 如果一个活细胞有两个或三个活的邻居,它继续存活。
- 如果一个活细胞有少于两个活的邻居,它会因“孤独”而死亡。
- 如果一个活细胞有多于三个活的邻居,它会因“拥挤”而死亡。
-
死细胞:
- 如果一个死细胞有恰好三个活的邻居,它会复活。
实现网格结构
首先,我们定义网格结构和细胞状态。
package life
// CellState 表示细胞的状态
type CellState int
const (
Dead CellState = iota
Alive
)
// Grid 表示生命游戏的网格
type Grid struct {
Cells [][]CellState
Rows int
Cols int
}
// NewGrid 创建一个新的网格
func NewGrid(rows, cols int) *Grid {
cells := make([][]CellState, rows)
for i := range cells {
cells[i] = make([]CellState, cols)
}
return &Grid{
Cells: cells,
Rows: rows,
Cols: cols,
}
}
初始化网格
我们添加一个方法来初始化网格的初始状态。
package life
import "math/rand"
// Init 随机初始化网格的细胞状态
func (g *Grid) Init() {
for i := 0; i < g.Rows; i++ {
for j := 0; j < g.Cols; j++ {
if rand.Float64() < 0.5 {
g.Cells[i][j] = Alive
} else {
g.Cells[i][j] = Dead
}
}
}
}
细胞演化规则
我们实现一个方法来根据生命游戏的规则更新网格。
package life
// NextState 计算下一代的网格状态
func (g *Grid) NextState() {
newCells := make([][]CellState, g.Rows)
for i := range newCells {
newCells[i] = make([]CellState, g.Cols)
for j := range newCells[i] {
newCells[i][j] = g.nextCellState(i, j)
}
}
g.Cells = newCells
}
// nextCellState 计算给定细胞的下一状态
func (g *Grid) nextCellState(row, col int) CellState {
liveNeighbors := g.countLiveNeighbors(row, col)
if g.Cells[row][col] == Alive {
if liveNeighbors < 2 || liveNeighbors > 3 {
return Dead
}
return Alive
}
if liveNeighbors == 3 {
return Alive
}
return Dead
}
// countLiveNeighbors 计算给定细胞周围的活细胞数量
func (g *Grid) countLiveNeighbors(row, col int) int {
count := 0
for i := row - 1; i <= row + 1; i++ {
for j := col - 1; j <= col + 1; j++ {
if (i != row || j != col) && g.isAlive(i, j) {
count++
}
}
}
return count
}
// isAlive 检查给定位置的细胞是否存活
func (g *Grid) isAlive(row, col int) bool {
if row < 0 || row >= g.Rows || col < 0 || col >= g.Cols {
return false
}
return g.Cells[row][col] == Alive
}
显示网格
我们添加一个方法来显示当前的网格状态。
package life
import "fmt"
// Display 显示当前网格状态
func (g *Grid) Display() {
for i := 0; i < g.Rows; i++ {
for j := 0; j < g.Cols; j++ {
if g.Cells[i][j] == Alive {
fmt.Print("O ")
} else {
fmt.Print(". ")
}
}
fmt.Println()
}
fmt.Println()
}
主程序
最后,我们在主程序中初始化网格、运行游戏并显示每一代的状态。
package main
import (
"fmt"
"time"
"project/life"
)
func main() {
rows, cols := 10, 10
grid := life.NewGrid(rows, cols)
grid.Init()
for i := 0; i < 10; i++ {
fmt.Printf("Generation %d:\n", i)
grid.Display()
grid.NextState()
time.Sleep(1 * time.Second)
}
}
解释
在这个例子中,我们使用了结构体和方法来实现生命游戏。我们首先定义了网格结构,并添加了初始化、计算下一代状态和显示当前状态的方法。通过将细胞演化规则封装在网格结构内,我们实现了抽象数据类型的思想,使得代码结构更加清晰、易于理解和维护。
这个例子展示了如何在 Go 中使用面向对象编程的思想来实现复杂的系统行为,同时利用抽象数据类型来简化代码实现。