第四章:ADT 实践:生命游戏
2. GUI 实现生命游戏
在本节中,我们将详细介绍如何使用 Fyne 框架在 GUI 中实现和运行生命游戏。这个实现将基于之前的网格和细胞状态定义,并通过 GUI 界面来展示网格的演变过程。
步骤1:创建 go.mod 文件
首先,我们需要创建一个 go.mod 文件来初始化 Go 模块并添加 Fyne 依赖项。
go mod init project
go get fyne.io/fyne/v2
步骤2:定义网格和细胞状态
与之前的实现类似,定义网格和细胞的状态。
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,
}
}
// 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
}
}
}
}
步骤3:细胞演化规则
实现根据生命游戏规则更新网格的功能。
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
}
步骤4:创建 GUI 界面
使用 Fyne 框架创建 GUI 界面,并在其中展示生命游戏的网格。
package main
import (
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/widget"
"project/life"
"time"
)
const (
gridSize = 20
cellSize = 20
)
func main() {
a := app.New()
w := a.NewWindow("Conway's Game of Life")
grid := life.NewGrid(gridSize, gridSize)
grid.Init()
cells := make([]*canvas.Rectangle, gridSize*gridSize)
for i := range cells {
cells[i] = canvas.NewRectangle(nil)
}
updateGrid := func() {
for i := 0; i < gridSize; i++ {
for j := 0; j < gridSize; j++ {
rect := cells[i*gridSize+j]
if grid.Cells[i][j] == life.Alive {
rect.FillColor = color.RGBA{0, 255, 0, 255}
} else {
rect.FillColor = color.RGBA{255, 255, 255, 255}
}
rect.Refresh()
}
}
}
go func() {
for {
time.Sleep(500 * time.Millisecond)
grid.NextState()
updateGrid()
}
}()
cellContainer := container.NewGridWithColumns(gridSize, cells...)
w.SetContent(container.NewVBox(
cellContainer,
widget.NewButton("Start", func() {
go func() {
for {
time.Sleep(500 * time.Millisecond)
grid.NextState()
updateGrid()
}
}()
}),
))
w.Resize(fyne.NewSize(gridSize*cellSize, (gridSize+1)*cellSize))
w.ShowAndRun()
}
解释
在这个实现中,我们使用 Fyne 框架来创建一个 GUI 界面,并在其中展示生命游戏的网格。我们首先创建并初始化了网格,然后通过创建一系列矩形来表示细胞状态。我们实现了一个 updateGrid 函数,用于根据细胞状态更新矩形的颜色。
在主程序中,我们设置了一个定时器,每隔 500 毫秒更新一次网格状态并调用 updateGrid 来刷新 GUI 界面。通过这种方式,我们可以在 GUI 界面中看到生命游戏的动态演化过程。
这种实现展示了如何使用 Go 语言和 Fyne 框架来创建一个动态的 GUI 应用程序,并展示复杂系统的演化过程。