C++实战:实现生命游戏

发布时间:2024年01月20日


?

一、实战概述

  • 在C++中实现康威生命游戏是一项结合了算法设计、数据结构和用户交互的实战编程任务。首先,定义一个名为Life的类来存储游戏状态,其中包含一个二维数组用于表示细胞网格,并实现初始化(initialize)、打印当前状态(print)以及根据规则更新状态到下一代(update)的方法。neighbor_count函数用于计算每个细胞周围活细胞的数量。

  • 此外,还创建了两个辅助函数:instructions用于输出游戏使用说明;user_says_yes则处理用户的交互输入,判断是否继续执行游戏迭代。

  • 主程序main.cpp中,实例化Life对象,按照流程初始化游戏状态、打印初始布局并持续询问用户是否查看下一个世代。通过调用update方法和print方法循环展示生命游戏的演化过程。

  • 此C++实现不仅展示了如何将数学概念转化为实际代码,也体现了计算机科学中的模拟与自动化思想,通过简单的规则模拟出复杂的生命现象,为学习者提供了一个理解和实践元胞自动机理论的良好平台。

二、实战步骤

  • 创建一个名为"GameOfLife"的项目,并编写头文件life.h和源文件life.cpp。通过这些文件,可以定义游戏的基本结构、初始化游戏、打印当前状态以及更新游戏状态。

(一)编写生命头文件

  • 这个头文件定义了生命游戏类(Life)的基本结构,包括成员函数和私有变量。其中的常量 maxrow 和 maxcol 分别表示网格的最大行数和最大列数。包含的成员函数有 initialize()、print() 和 update()。
    在这里插入图片描述
// 定义游戏区域最大尺寸,这里是行数和列数
const int maxrow = 20, maxcol = 60; // 游戏格子的维度

// 定义生命游戏(Conway's Game of Life)类
class Life {
public:
    // 初始化方法,用于设置初始细胞状态
    void initialize();

    // 打印当前游戏状态的方法,显示整个网格
    void print();

    // 更新方法,根据游戏规则计算下一代细胞的状态
    void update();    

    // 使用二维数组存储游戏网格,并额外增加两行两列以简化边界条件处理
    int grid[maxrow + 2][maxcol + 2]; 

    // 计算给定位置 (row, col) 的邻居细胞存活数量的方法
    int neighbor_count(int row, int col);
};

// 结束某些预处理器条件或标志着某个阶段完成的宏定义
#define DONE

// 包含实现上述接口的具体代码
#include "life.cpp"
  • 该C++代码定义康威生命游戏类Life,含初始化、打印状态、更新规则方法及计算邻居存活数量函数。利用二维数组存储格子状态,其中边界额外扩充以简化处理,并通过包含"life.cpp"实现具体逻辑。

(二)创建生命实现文件

  • 这个源文件实现了生命游戏类中的各个成员函数。initialize()函数用于初始化游戏状态,用户可以指定活细胞的位置;print()函数负责打印当前的游戏状态;update()函数根据邻居计数规则更新游戏状态到下一代。
    在这里插入图片描述
#include <iostream>
#include <istream>
using namespace std;

#ifdef DONE

// 初始化方法:清零整个游戏区域,并让用户输入活细胞坐标,构建初始生命配置
void Life::initialize()
/*Pre: 无预条件
  Post: Life对象包含用户指定的生命配置*/
{
	int row, col;
	for (row = 0; row <= maxrow + 1; row++)
		for (col = 0; col <= maxcol + 1; col++)
			grid[row][col] = 0; // 将所有格子初始化为死状态

	cout << "List the coordinates for living cells." << endl;
	cout << "Terminate the list with the special pair -1 -1" << endl;
	cin >> row >> col;
	while (row != -1 || col != -1)
	{
		if (row >= 1 && row <= maxrow)
			if (col >= 1 && col <= maxcol)
				grid[row][col] = 1; // 根据有效坐标设置活细胞
			else
				cout << "Column " << col << " is out of range." << endl;
		else
			cout << "Row " << row << " is out of range." << endl;
		cin >> row >> col; // 继续读取下一个坐标对
	}
}

// 打印当前生命游戏配置的方法
void Life::print()
/*Pre: Life对象已包含一个生命配置
  Post: 用户可以看到该配置信息*/
{
	int row, col;
	cout << "\nThe current Life configuration is:" << endl;
	for (row = 1; row <= maxrow; row++)
	{
		for (col = 1; col <= maxcol; col++)
			if (grid[row][col] == 1)
				cout << '*'; // 输出活细胞用星号表示
			else
				cout << ' '; // 死细胞用空格表示
		cout << endl;		 // 换行准备输出下一行
	}
	cout << endl; // 输出结束后的额外换行
}

// 更新方法:根据康威生命游戏规则计算并生成下一世代的生命配置
void Life::update()
/*Pre: Life对象已包含一个生命配置
  Post: Life对象现在包含基于原有配置计算出的新一代生命配置*/
{
	int row, col;
	int new_grid[maxrow + 2][maxcol + 2]; // 创建一个新的临时网格用于存放下一代状态

	// 遍历每一个细胞,根据邻居数量决定新状态
	for (row = 1; row <= maxrow; row++)
		for (col = 1; col <= maxcol; col++)
			switch (neighbor_count(row, col))
			{
			case 2:
				new_grid[row][col] = grid[row][col]; // 当前状态保持不变
				break;
			case 3:
				new_grid[row][col] = 1; // 符合“生死准则”的细胞变为活细胞
				break;
			default:
				new_grid[row][col] = 0; // 其他情况,细胞变为死细胞
			}

	// 将临时网格中的新状态复制回原网格中,完成更新过程
	for (row = 1; row <= maxrow; row++)
		for (col = 1; col <= maxcol; col++)
			grid[row][col] = new_grid[row][col];
}

// 生命游戏类 Life 的成员函数,计算给定位置周围活细胞数量的方法
int Life::neighbor_count(int row, int col)
{
	// 检查输入坐标是否在有效范围内(假设网格从1开始计数)
	if (row < 1 || row > maxrow || col < 1 || col > maxcol)
		return 0; // 如果坐标无效,则返回0表示没有活细胞邻居

	int count = 0;							 // 初始化活细胞计数器为0
	for (int i = row - 1; i <= row + 1; ++i) // 遍历以给定行为中心的3行
	{
		for (int j = col - 1; j <= col + 1; ++j) // 遍历以给定列为中心的3列
		{
			// 跳过当前格子自身(不计入邻居)
			if (i == row && j == col)
				continue;

			// 检查所考虑的单元格是否在有效范围内并存活
			if (i >= 1 && i <= maxrow && j >= 1 && j <= maxcol && grid[i][j] == 1)
				++count; // 若存活,则增加计数器
		}
	}

	return count; // 返回活细胞邻居的数量
}

#endif
  • 这段代码为康威生命游戏实现了一个简单的类Life,包含了初始化、打印当前配置以及根据游戏规则更新配置的方法。同时,还有一个辅助函数用来计算每个细胞周围活细胞的数量。注释详细解释了各个函数的前置条件、后置条件以及功能。

(三)编写工具头文件

  • 这个头文件包含了两个辅助函数声明:instructions()用于打印使用生命游戏的说明信息;user_says_yes()用于获取用户的输入,并判断是否为肯定回答。
    在这里插入图片描述
void instructions();
bool user_says_yes();

#define DONE
#include "utility.cpp"

(四)编写工具实现文件

  • 这个源文件实现了utility.h中声明的两个辅助函数。instructions()函数打印关于如何使用生命游戏的详细说明;user_says_yes()函数接收用户输入并返回一个布尔值,表示用户是否给出了肯定回答。
    在这里插入图片描述
#include <iostream>
#include <istream>
using namespace std;

#ifdef DONE

// 函数:打印游戏使用说明
void instructions()
/*Pre: 无预条件.
  Post: 已经打印了使用生命游戏的说明。*/
{
	cout << "Welcome to Conway's game of Life." << endl;
	cout << "This game uses a grid of size " << maxrow << " by " << maxcol << " in which " << endl;
	cout << "each cell can either be occupied by an organism or not." << endl;
	cout << "The occupied cells change from generation to generation" << endl;
	cout << "according to the number of neighboring cells which are alive." << endl;
}

// 函数:获取用户确认(yes/no)的回答,并返回布尔值表示结果
bool user_says_yes()
{
	int c;
	bool initial_response = true; // 标记是否是首次提示

	do
	{ // 循环直到获得有效输入为止
		if (initial_response)
			cout << " (y,n)? " << flush; // 首次提示信息
		else
			cout << "Respond with either y or n: " << flush; // 非首次提示信息
		do
		{ // 忽略空白字符并等待用户输入
			c = cin.get();
		} while (c == '\n' || c == ' ' || c == '\t'); // 检查换行符、空格和制表符
		initial_response = false;
	} while (c != 'y' && c != 'Y' && c != 'n' && c != 'N'); // 循环直至用户输入 y, Y, n 或 N
	return (c == 'y' || c == 'Y');							// 如果用户输入的是 y 或 Y,则返回 true,否则返回 false
}
#endif
  • instructions()函数用于输出康威生命游戏的基本介绍和运行原理,而user_says_yes()函数负责从用户那里获取“是/否”形式的回答,并在接收到有效的 y/n 输入后返回布尔值,以确定用户是否同意或希望继续进行某个操作。这个函数通过循环确保用户只能输入预期的有效字符。

(五)编写主程序文件

  • 编写主程序文件main.cpp,以运行我们的生命游戏并查看结果。在这个过程中,我们可以尝试不同的初始布局,观察它们如何按照生命游戏的规则演变。
    在这里插入图片描述
#include "life.h"
#include "utility.h"

int main() // 主程序:运行康威生命游戏
/*Pre: 用户提供一个初始的生命细胞配置。
  Post: 程序按照生命游戏规则打印出一系列显示生命细胞配置变化的图像。
  使用:Life 类及其方法 initialize(), 2 print(), 和 update()。
		函数 instructio1ns(); user_says_yes()。*/
{
	Life configuration;			// 创建一个 Life 类的对象,用于存储和更新生命游戏状态
	instructions();				// 输出游戏使用说明
	configuration.initialize();		// 初始化生命游戏的起始状态
	cout << "count = " << configuration.neighbor_count(2, 3) << endl; // 示例性调用 neighbor_count 方法,并打印某个位置周围活细胞的数量
	configuration.print();		// 打印当前生命游戏状态
	cout << "Continue viewing new generations? " << endl;
	while (user_says_yes()) // 当用户希望继续查看时,循环执行以下操作
	{
		configuration.update(); // 根据生命游戏规则计算并更新到下一个世代
		configuration.print();	// 打印更新后的新一代生命游戏状态
		cout << "Continue viewing new generations? " << endl;
	}

	return 0; // 主程序正常结束,返回值为0
}
  • 这段代码定义了主函数main(),它创建了一个Life对象来处理生命游戏的所有状态变化。首先输出游戏说明,然后初始化生命游戏状态,接着通过neighbor_count()方法演示如何计算某个格子周围的活细胞数量,并打印当前状态。

  • 接下来,程序进入一个循环,询问用户是否继续观察新的生命游戏世代。如果用户选择继续(由user_says_yes()函数获取),则通过调用configuration.update()更新到下一世代,并再次打印新的状态,循环反复直到用户不再希望继续观看。最后,主函数返回0,表示程序成功执行完毕。

(六)运行程序,查看结果

  • 运行生命游戏程序后,我们可以看到一个由星号(*)表示的生命单元组成的初始布局。随着每个世代的更新,生命的数量和分布会发生变化,根据生命游戏的规则(B3/S23),只有在有2个或3个邻居时生命才能存活。通过观察这些动态变化,我们可以感受到生命游戏中的简单规则如何产生复杂且意想不到的行为模式。
  • 尝试初始布局
    在这里插入图片描述
  • 运行程序,输入
2 2
2 3
2 4
2 5
-1 -1

在这里插入图片描述

  • 输入y,继续游戏
    在这里插入图片描述

  • 输入y,继续游戏
    在这里插入图片描述

  • 输入n,结束游戏
    在这里插入图片描述

(七)尝试生命游戏其它初始布局

  • 在生命游戏中,尝试不同的初始布局可以产生千变万化的结果。例如,你可以从简单的点状布局开始,观察它们如何相互作用和演变;也可以设计复杂的图案,如滑翔机、飞船等,看它们如何在网格上移动和繁殖。通过探索各种初始布局,我们可以更深入地理解生命游戏的规则和复杂性。
    在这里插入图片描述

三、实战总结

  • 在C++中实现康威生命游戏的实战项目通过设计Life类及其成员函数,将数学理论转化为实际代码。该项目涵盖了从初始化、打印到更新游戏状态的关键环节,并通过计算邻居数量的方法遵循生命游戏规则进行迭代。此外,项目还包括了辅助工具函数以增强用户体验,如输出游戏说明和处理用户交互。主程序实现了整个生命游戏的流程控制,允许用户观察不同初始布局下的演化过程。此实现不仅展示了如何构造元胞自动机模型,也通过实践帮助学习者深入理解简单规则如何产生复杂行为这一深刻概念。
文章来源:https://blog.csdn.net/howard2005/article/details/135713629
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。