数据结构和算法-Go泛型实现:第四章ADT 实践:生命游戏- 1. 生命游戏规则&控制台实现

130 阅读3分钟

第四章:ADT 实践:生命游戏

1. 生命游戏

网格细胞演化规则

"生命游戏"(Conway's Game of Life)是由数学家约翰·康威(John Conway)于1970年发明的细胞自动机。这个游戏展示了如何通过简单的规则来模拟复杂的系统行为。我们将使用 Go 来实现这个游戏,并展示如何利用抽象数据类型(ADT)来简化实现。

规则概述

生命游戏在一个二维网格上进行,网格中的每个细胞有两种状态:活的或死的。细胞的状态根据以下规则更新:

  1. 活细胞

    • 如果一个活细胞有两个或三个活的邻居,它继续存活。
    • 如果一个活细胞有少于两个活的邻居,它会因“孤独”而死亡。
    • 如果一个活细胞有多于三个活的邻居,它会因“拥挤”而死亡。
  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,
	}
}
初始化网格

我们添加一个方法来初始化网格的初始状态。

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 中使用面向对象编程的思想来实现复杂的系统行为,同时利用抽象数据类型来简化代码实现。