生命游戏(Conway's Game of Life)是英国数学家约翰・何顿・康威在 1970 年发明的细胞自动机。它是一个零玩家游戏,包含一个二维网格,每个网格代表一个细胞,细胞有存活和死亡两种状态,且每个细胞的状态会根据其周围细胞的状态按照特定规则进行演变。生命游戏不仅在数学领域有着重要的理论意义,在计算机科学、生物学等领域也有广泛的应用,例如模拟生物群体的生长和演变。
本文将深入探讨使用 Java 语言实现生命游戏的具体过程,并详细剖析其核心算法。
整体架构
在 Java 实现中,我们将整个系统划分为三个主要类:GameUI
、GameOfLife
和 DieDaiGuiZe
。GameUI
类负责创建图形用户界面(GUI),展示生命游戏的演变过程;GameOfLife
类负责管理游戏的网格状态,包括初始化和状态更新;DieDaiGuiZe
类则包含了生命游戏的核心算法,用于计算下一代细胞的状态。
代码实现细节
GameUI 类
GameUI
类继承自 JFrame
,用于创建一个窗口来显示生命游戏的演变过程。在构造函数中,我们设置了窗口的标题、大小、关闭操作,并将窗口居中显示。同时,我们创建了一个 GameOfLife
对象,并调用其 randomInit
方法对网格进行随机初始化。
在 paint
方法中,我们进行了 10000 代的迭代。在每一代中,我们更新窗口标题,获取当前网格状态,将网格状态绘制到一个 BufferedImage
上,然后将该图像绘制到窗口上。接着,我们调用 GameOfLife
对象的 nextGeneration
方法来计算下一代的网格状态,并通过 Thread.sleep(1)
方法进行 1 毫秒的延时,以便观察每一代的演变过程。
java
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
public class GameUI extends JFrame {
GameOfLife game = new GameOfLife(450, 450);
public GameUI() {
setTitle("生命游戏");
setSize(1000, 1000);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setVisible(true);
game.randomInit(); // 随机初始化
}
// 传大不传小
public void paint(Graphics g) {
// super.paint(g);
int generations = 10000;
for (int i = 0; i < generations; i++) {
setTitle("星云" + (i + 1));
int[][] grid = game.grid;
// 绘制
BufferedImage bfImg = new BufferedImage(900, 900, 2);
Graphics bg = bfImg.getGraphics();
for (int j = 0; j < grid.length; j++) {
for (int k = 0; k < grid[j].length; k++) {
int l = grid[j][k];
bg.setColor(l == 0 ? Color.BLACK : Color.WHITE);
bg.fillRect(j * 2, k * 2, 2, 2);
}
}
g.drawImage(bfImg, 50, 50, null);
//演变
game.nextGeneration();
// 延时
try {
Thread.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public static void main(String[] args) {
new GameUI();
}
}
GameOfLife 类
GameOfLife
类包含一个公开的二维数组 grid
,用于存储细胞的状态。在构造函数中,我们根据传入的行数和列数初始化网格。randomInit
方法用于随机初始化网格,每个细胞有 50% 的概率为活细胞。nextGeneration
方法调用 DieDaiGuiZe
类的 nextGeneration
方法来计算下一代的网格状态。
java
public class GameOfLife {
// 公开网格变量,供UI访问
public int[][] grid;
private int rows;
private int cols;
// 构造函数,初始化网格大小
public GameOfLife(int rows, int cols) {
this.rows = rows;
this.cols = cols;
this.grid = new int[rows][cols];
}
// 随机初始化网格
public void randomInit() {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
// 30%的概率为活细胞
grid[i][j] = Math.random() < 0.5 ? 1 : 0;
}
}
}
// 调用规则类计算下一代
public void nextGeneration() {
grid = DieDaiGuiZe.nextGeneration(grid);
}
}
DieDaiGuiZe 类
DieDaiGuiZe
类包含了生命游戏的核心算法。nextGeneration
方法用于计算下一代细胞的状态。首先,我们检查传入的当前网格是否为空或长度为 0,如果是,则返回一个空的二维数组。然后,我们创建一个新的二维数组 nextGrid
来存储下一代的细胞状态。
接下来,我们遍历当前网格中的每个细胞,调用 countLiveNeighbors
方法计算该细胞周围的活细胞数量。根据生命游戏的规则,如果当前细胞是活的,且周围活细胞数为 2 或 3,则该细胞在下一代仍然存活;否则,该细胞死亡。如果当前细胞是死的,且周围活细胞数正好为 3,则该细胞在下一代复活;否则,该细胞仍然死亡。
countLiveNeighbors
方法用于计算指定位置细胞周围的活细胞数量。我们通过遍历该细胞周围的 8 个方向,检查每个邻居细胞的坐标是否在网格范围内,并且是否为活细胞,如果是,则计数器加 1。
java
public class DieDaiGuiZe {
/**
* 计算下一代细胞状态
* @param currentGrid 当前代细胞状态网格
* @return 下一代细胞状态网格
*/
public static int[][] nextGeneration(int[][] currentGrid) {
if (currentGrid == null || currentGrid.length == 0) {
return new int[0][0];
}
int rows = currentGrid.length;
int cols = currentGrid[0].length;
int[][] nextGrid = new int[rows][cols];
// 遍历每个细胞计算下一代状态
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
// 计算周围活细胞数量
int liveNeighbors = countLiveNeighbors(currentGrid, i, j);
// 应用生命游戏规则
if (currentGrid[i][j] == 1) { // 当前细胞是活的
// 活细胞周围活细胞数少于2个或多于3个,则死亡
nextGrid[i][j] = (liveNeighbors == 2 || liveNeighbors == 3) ? 1 : 0;
} else { // 当前细胞是死的
// 死细胞周围正好有3个活细胞,则复活
nextGrid[i][j] = (liveNeighbors == 3) ? 1 : 0;
}
}
}
return nextGrid;
}
/**
* 计算指定位置细胞周围的活细胞数量
* @param grid 细胞状态网格
* @param row 细胞所在行
* @param col 细胞所在列
* @return 周围活细胞数量
*/
private static int countLiveNeighbors(int[][] grid, int row, int col) {
int count = 0;
int rows = grid.length;
int cols = grid[0].length;
// 检查周围8个方向的细胞
for (int i = -1; i <= 1; i++) {
for (int j = -1; j <= 1; j++) {
// 跳过当前细胞自身
if (i == 0 && j == 0) {
continue;
}
// 计算邻居细胞的坐标
int neighborRow = row + i;
int neighborCol = col + j;
// 检查邻居坐标是否在网格范围内
if (neighborRow >= 0 && neighborRow < rows &&
neighborCol >= 0 && neighborCol < cols) {
// 如果邻居是活细胞,计数加1
if (grid[neighborRow][neighborCol] == 1) {
count++;
}
}
}
}
return count;
}
}
核心算法总结
该算法所模拟的细胞群体分布于 N×M 维度的二维矩阵中,每个单元格通过二元离散状态(1 表征存活,0 表征死亡)反映细胞的生命活性。其核心演化机制建立在细胞邻域关系的拓扑分析之上,具体表现为通过迭代计算实现群体状态的时序跃迁。在每一次状态更新过程中,系统需对网格内的所有细胞执行邻域存活计数操作 ------ 此处的邻域定义为以目标细胞为中心的 3×3 范围内的 8 个相邻单元格(排除细胞自身位置)。对于边界区域的细胞,其邻域范围将因坐标系的约束而自然缩减,需通过坐标合法性校验避免越界访问。
状态迁移规则的执行建立在当前存活状态与邻域存活数量的耦合分析之上:当目标细胞当前处于存活状态(值为 1)时,其下一时刻的存活概率取决于邻域存活细胞数量是否落在 [2,3] 的闭区间内,若存活邻居数小于 2 或大于 3,则该细胞将因资源竞争或种群稀疏而进入死亡状态;当目标细胞当前处于死亡状态(值为 0)时,仅当邻域内恰好存在 3 个存活细胞时,该位置才会触发复活机制,表现为状态值从 0 跃迁为 1。这种基于局部邻域交互的规则体系,本质上是对生物种群在资源限制下的生存竞争模型的抽象化表达。
算法在实现层面采用双矩阵存储策略,即通过独立的当前状态矩阵与下一状态矩阵实现迭代过程的解耦 ------ 这一设计确保了在计算某一细胞的下一状态时,所参考的邻域信息均来自未发生变更的当前状态,避免了因实时更新导致的状态污染。整个演化过程通过嵌套循环结构完成:外层循环遍历矩阵的行索引,内层循环遍历列索引,对每个坐标位置依次执行邻域计数与规则匹配,最终生成完整的下一状态矩阵并替代当前矩阵,完成一次演化周期。这种基于局部规则的全局演化模式,展现了复杂系统自组织现象的涌现性特征,其宏观呈现的动态 patterns 本质上是微观个体交互规则的集体体现。