0%

The Game Of Life - 数据结构与算法的敲门砖

The Game Of Life(生命游戏,又称为细胞自动机)几乎是所有数据结构与算法导论教程前言的一个很经典的程序了。这是一个零玩家游戏,发生在一个平面网格里。每个格子的细胞都有死亡和存活两种状态,在代与代之间有两种状态,如果每一个细胞周围少于或等于1个细胞或多于4个细胞时,他会在下一代死亡;如果一个格子周围恰好有3个细胞,他将会重新活过来。

例如,当一种特别的状态被初始化后,会形成下列状态。

周期为4的轻量级飞船

也会有循环或者稳定的状态:

周期为3的脉冲星

周期为3的Cross

更有甚者发现的「滑行者枪」,可以源源不断地向外发射滑行者。

滑行者枪

接下来是一个用C++和控制台实现生命游戏的尝试。本可以很短小的,但上周闲下来的时候东增西补地加了许多东西,也算是当作初学面向对象的一个习题了吧,实现了一个可以自动运行,随机初始化和自定义规则的黑框框生命游戏。

运行效果

Source Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
#include "pch.h"
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <windows.h>
constexpr int FIRST_INTRODUCTION = 0;
constexpr int MANIPUNATE_INSTRUCTION = 1;
constexpr int WITH\_GENERATION\_INFO = 1;
constexpr int NO\_GENERATION\_INFO = 0;
//Define rules;
int BOTTOM_LIMIT = 1;
int UPPER_LIMIT = 4;
int REBIRTH_NUM = 3;
//define characters to print
const char LIVEC = '#';
const char DIEC = '.';
const int MAXL = 101;
using namespace std;
class LifeMap {
public:
int width;
int height;
int matrix\[MAXL\]\[MAXL\] = { 0 };
int surround\[MAXL\]\[MAXL\] = { 0 };
int generation = 0;
void instructor(int type) {
switch (type) {
case FIRST_INTRODUCTION:
cout << "Welcome to the Game of Life!" << endl << endl <<
"生命游戏是一个零玩家游戏,它包括一个二维矩形世界,这个世界中的每个方格居住着一个活着的或死了的细胞。一个细胞在下一个时刻生死取决于相邻八个方格中活着的或死了的细胞的数量。如果相邻方格活着的细胞数量过多,这个细胞会因为资源匮乏而在下一个时刻死去;相反,如果周围活细胞过少,这个细胞会因太孤单而死去。"
<< endl << "实际中,你可以设定周围活细胞的数目怎样时才适宜该细胞的生存。如果这个数目设定过低,世界中的大部分细胞会因为找不到太多的活的邻居而死去,直到整个世界都没有生命;如果这个数目设定过高,世界中又会被生命充满而没有什么变化。所以我们把这个数目选取为2或者3;这样整个生命世界才不至于太过荒凉或拥挤,而是一种动态的平衡。"
<< endl << "这样的话,游戏的规则就是:当一个方格周围有2或3个活细胞时,方格中的活细胞在下一个时刻继续存活;即使这个时刻方格中没有活细胞,在下一个时刻也会“诞生”活细胞。在这个游戏中,还可以设定一些更加复杂的规则,例如当前方格的状况不仅由父一代决定,而且还考虑祖父一代的情况。"
<< endl << "你作为这个世界的上帝,随意设定某个方格细胞的死活,以观察对世界的影响。在游戏的进行中,杂乱无序的细胞会逐渐演化出各种精致、有形的结构;这些结构往往有很好的对称性,而且每一代都在变化形状。一些形状已经锁定,不会逐代变化。有时,一些已经成形的结构会因为一些无序细胞的“入侵”而被破坏。但是形状和秩序经常能从杂乱中产生出来。"
<< endl << endl << " Please insert the width AND the height of the grid:" << endl << "(Max Length is " << MAXL - 2 << ")" << endl;
break;
case MANIPUNATE_INSTRUCTION:
cout << endl << "Now you can insert \\"L X Y\\" (eg. L 2 3 or l 2 3 to set the cell located in the 2nd row, 3rd column) to set a cell to live,"
<< endl << "insert \\"D X Y\\" to set a cell to die. insert \\"P\\" to print the current status."
<< endl << "\\"N\\" for simply jump tp the next generation.\\nN (optional)GENERATIONS (optional)INTERVAL_TIME for evolve multiple generation at a time."
<< endl << "Insert \\"R\\" to reset your gird and \\"Q\\" to quit." << endl
<< "Inset X to define rules."
<< "Most interstingly, you can radomlize every cell by insert \\"!\\"" << endl << "You can set multiple cells' status in one line." << endl;
break;
}

}
void initialize(int W, int H) {
int i, j;
for (i = 0; i < MAXL; i++) {
for (j = 0; j < MAXL; j++) {
matrix\[i\]\[j\] = 0;
surround\[i\]\[j\] = 0;
}
}
width = W;
height = H;
generation = 0;
}
void printer(int gen) {
//gen = 1 for printing with generation info.
if (gen == 1) {
cout << "\\n Generation No." << generation << ":" << endl;
}
cout << endl;
int i, j;
cout << " ";
for (i = 0; i < width; i++) {
printf("%2d ", i + 1);
}
cout << endl;
for (i = 0; i < height; i++) {
printf("%2d ", i + 1);
for (j = 0; j < width; j++) {
if (matrix\[i + 1\]\[j + 1\] == 0) printf("%c ", DIEC);
else if (matrix\[i + 1\]\[j + 1\] == 1) printf("%c ", LIVEC);
}
cout << endl;
}
cout << endl;
//print ratio
if (gen == 1) {
int liveCells = 0;
for (i = 0; i < height; i++) {
for (j = 0; j < width; j++) {
if (matrix\[i + 1\]\[j + 1\] == 1) liveCells++;
}
}
cout << " Live cell ratio: " << (double)liveCells / (double)(width\*height) \* 100.0
<< "% (" << liveCells << "/" << width * height << ")" << endl << endl;
}
}
void SetLiving(int W, int H) {
if (W <= 0 || W > width || H <= 0 || H > height)
cout << "Unvalid input, please try again.\\n";
else {
matrix\[W\]\[H\] = 1;
cout << "Cell (" << W << " ," << H << ") came alive." << endl;
}

}
void SetDying(int W, int H) {
if (W <= 0 || W > width || H <= 0 || H > height)
cout << "Unvalid input, please try again.\\n";
else {
matrix\[W\]\[H\] = 0;
cout << "Cell (" << W << " ," << H << ") has been dead." << endl;
}

}
void Radomlize() {
int i, j;
cout << "Randomlizing cell status..." << endl;
for (i = 0; i < height; i++) {
for (j = 0; j < width; j++) {
//srand(time(NULL));
if ((rand() % 2) == 1) matrix\[i + 1\]\[j + 1\] = 0;
else {
matrix\[i + 1\]\[j + 1\] = 1;
cout << "Cell (" << i + 1 << " ," << j + 1 << ") came alive." << endl;
}
}
}
}
void Next() {
int i, j;
//Initialize the surround matrix.
for (i = 0; i < MAXL; i++) {
for (j = 0; j < MAXL; j++) {
surround\[i\]\[j\] = 0;
}
}
//Calculate the surrounded live cells
for (i = 0; i < height; i++) {
for (j = 0; j < width; j++) {
if (matrix\[i\]\[j\] == 1) surround\[i + 1\]\[j + 1\]++;
if (matrix\[i\]\[j + 1\] == 1) surround\[i + 1\]\[j + 1\]++;
if (matrix\[i\]\[j + 2\] == 1) surround\[i + 1\]\[j + 1\]++;
if (matrix\[i + 1\]\[j\] == 1) surround\[i + 1\]\[j + 1\]++;
if (matrix\[i + 1\]\[j + 2\] == 1) surround\[i + 1\]\[j + 1\]++;
if (matrix\[i + 2\]\[j\] == 1) surround\[i + 1\]\[j + 1\]++;
if (matrix\[i + 2\]\[j + 1\] == 1) surround\[i + 1\]\[j + 1\]++;
if (matrix\[i + 2\]\[j + 2\] == 1) surround\[i + 1\]\[j + 1\]++;
}
}
//Rules
for (i = 0; i < height; i++) {
for (j = 0; j < width; j++) {
if (surround\[i + 1\]\[j + 1\] >= UPPER_LIMIT) matrix\[i + 1\]\[j + 1\] = 0;
if (surround\[i + 1\]\[j + 1\] <= BOTTOM_LIMIT) matrix\[i + 1\]\[j + 1\] = 0;
if (surround\[i + 1\]\[j + 1\] == REBIRTH_NUM) matrix\[i + 1\]\[j + 1\] = 1;
}
}
generation++;
}
void Definer() {
int i, j, k;
cout << "Please insert three value to define the rules: (Defaul Configuration:1 4 3)" << endl
<< "BOTTOM\_LIMIT UPPER\_LIMIT REBIRTH_NUM" << endl;
inputrule: cout << ">> "; cin >> i >> j >> k;
if (cin.fail() || i >= 8 || j >= 8 || k >= 8 || i < 0 || j < 0 || k < 0) {
cout << "Invalid input, please input three positive integers smaller than 8." << endl;
cin.clear();
while (cin.get() != '\\n');
goto inputrule;
}
else {
BOTTOM_LIMIT = i;
UPPER_LIMIT = j;
REBIRTH_NUM = k;
printf("Rules have been defined to %d %d %d.\\n", i, j, k);
}
}
};
int main()
{
LifeMap Map;
//These variables are used for manipunating.
int X, Y;
//these two labels are used for jumping for input that's too large and the reset command.
start: Map.instructor(FIRST_INTRODUCTION);
input: cin >> X >> Y;
while (cin.fail()) {
cout << "Unvalid input, please try again. Pleas type two integers." << endl;
cin.clear();
while (cin.get() != '\\n') continue;
cin >> X >> Y;
}
while (X >= 100 || Y >= 100) {
cout << "Too long." << endl;
cin.clear();
goto input;
}
Map.instructor(MANIPUNATE_INSTRUCTION);
cout << "This is all the cells you have." << endl;
Map.initialize(X, Y);
Map.printer(NO\_GENERATION\_INFO);
cin.clear();
while (cin.get() != '\\n') continue; // Clear datas in the cache.
char command;
while ((command = toupper(cin.get())) != 'Q') {
switch (command) {
case 'L':
cin >> X >> Y;
if (cin.fail()) {
cout << "Unvalid input, please try again." << endl;
cin.clear();
while (cin.get() != '\\n') continue;
}
else
Map.SetLiving(X, Y);
break;
case 'D':
cin >> X >> Y;
if (cin.fail()) {
cout << "Unvalid input, please try again." << endl;
cin.clear();
while (cin.get() != '\\n') continue;
}
else
Map.SetDying(X, Y);
break;
case 'P':
Map.printer(WITH\_GENERATION\_INFO);
break;
case 'N':
//gens mens the number of generations to evolve
//interval means the interval times between two evolutions
int i, gens, interval;
if (cin.get() != '\\n') {
cin >> gens;
if (cin.get() == '\\n') interval = 0;
else cin >> interval;
if (gens <= 0 || interval < 0) {
cout << "Unvalid input, please enter positive integers." << endl;
cin.clear();
while (cin.get() != '\\n') continue;
}
else {
for (i = 0; i < gens; i++) {
system("cls");
Map.instructor(MANIPUNATE_INSTRUCTION);
Map.Next();
Map.printer(WITH\_GENERATION\_INFO);
Sleep(interval);
}
}
}
else {
system("cls");
Map.instructor(MANIPUNATE_INSTRUCTION);
Map.Next();
Map.printer(WITH\_GENERATION\_INFO);
}
break;
case 'R':
system("cls");
goto start;
break;
case '!':
Map.Radomlize();
cout << "OK, now it's just chaos." << endl;
Map.printer(WITH\_GENERATION\_INFO);
break;
case 'X':
Map.Definer();
case '\\n': break;
case ' ': break;
default:
cout << "Unknown Command:(" << endl;
//while (cin.get() != '\\n') continue;
}
}
cout << "See you:)\\n\\n";
system("pause");
return 0;
}

GitHub:https://github.com/BillChen2K/LearnigRepo/blob/master/Course/数据结构与算法/LifeGame/

图源:https://www.guokr.com/article/439770/

另外这里还有一个LifeWiki有详细的整理和介绍:http://www.conwaylife.com/wiki/

Welcome to my other publishing channels