Java & Python 康威生命游戏 - 命令行版(2020年7月23日)


制作背景

高二的时候看霍金的《大设计》最后几页的时候看到里面提到了康威生命游戏,介绍了它的规则,感觉很有意思,但是在草稿纸上一点一点画,推演,实在是太麻烦了,于是我便想是否可以通过编程的方式实现康威生命游戏?我当时高二,感觉自己很难做出来。到了大一下学期,自学了python和java,学校的课程里也学习了C语言,自己也经常写一些有意思的小程序,可以说是有一定的编程基础了,于是就又会想起了一年多前的康威生命游戏。

其实在 2020年2月24号,大一上学期结束的寒假里,我已经把一维版的康威宇宙做出了来了,但是那终究是一维的,没有二维的丰富,开发难度肯定没有二维的高。所以这次我打算真正的试一试二维的,原版的、正经的康威宇宙。

在 2020年4月11日的时候,生命游戏的作者 约翰·何顿·康威 已经因为新冠肺炎去世了。感觉突然挺遗憾的。之前了解这个人完全是因为生命游戏,但是其实他是一个数学家。他还有其他很多更大的贡献和发现,不只是生命游戏。

2020年7月23日,我把用python实现了,随后一天我又做了一份Java版本的。

相关介绍

百度介绍:

https://baike.baidu.com/item/%E5%BA%B7%E5%A8%81%E7%94%9F%E5%91%BD%E6%B8%B8%E6%88%8F

知乎介绍:

https://zhuanlan.zhihu.com/p/45026142

效果截图

康威生命游戏动态图

康威生命游戏静态图

源代码

Java

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
import java.util.Random;

/**
* 生命游戏
* 设计作者:康威
* 代码作者:littlefean
* 代码时间:2020年7月24日
*/
public class GameOfLife {
//场地的宽度
private static final int CELLS_WIDTH = 85;
//场地的高度
private static final int CELLS_HEIGHT = 45;
//活细胞显示
private static final String LIVE_CELL = "█";
//死细胞显示
private static final String DEATH_CELL = " ";
//检测半径(以自身为中心,R为半径的圆形的外接正方形)
private static final int R = 1;
//检测范围内,使活细胞存活的 邻胞数量的最小值
private static final int LIVE_NUM_MIN = 2;
//检测范围内,使活细胞存活的 邻胞数量的最大值
private static final int LIVE_NUM_MAX = 3;
//检测范围内,使死细胞复活的 邻胞数量的最小值
private static final int BIRTH_NUM_MIN = 3;
//检测范围内,使死细胞复活的 邻胞数量的最大值
private static final int BIRTH_NUM_MAX = 3;

public static void main(String[] args) {
//创建矩形全死细胞集
byte[][] cells = newCells(CELLS_WIDTH, CELLS_HEIGHT);
System.out.println(str(cells));

//随机复活其中的细胞
randPoint(cells);
System.out.println(str(cells));

//开始迭代演示
while (true) {
cells = iterative(cells);
System.out.println(str(cells));
}
}

/**
* 生成一个空的全是0的二维数组并返回
* @param width 宽度
* @param height 高度
* @return 二维数组
*/
private static byte[][] newCells(int width, int height){
byte[][] cellsArray = new byte[height][width];
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
cellsArray[i][j] = 0;
}
}
return cellsArray;
}

/**
* 将二维数组随机化,该函数直接改传入数组的状态
* @param cellsArray 二维数组
*/
private static void randPoint(byte[][] cellsArray){
for (int i = 0; i < cellsArray.length; i++) {
for (int j = 0; j < cellsArray[i].length; j++) {
int rd = Math.random()>0.5 ? 1 : 0;
if(rd == 1) {
cellsArray[i][j] = 1;
}
}
}
}

/**
* 传入二维数组并使其按照生命游戏规则迭代一次,迭代完成后返回一个新的迭代后的二维数组
* @param cellsArray 迭代前的二维数组
* @return 迭代后的二维数组
*/
private static byte[][] iterative(byte[][] cellsArray){
byte[][] afterCells = newCells(cellsArray[0].length, cellsArray.length);

for (int i = 0; i < cellsArray.length; i++) {
for (int j = 0; j < cellsArray[i].length; j++) {
int neighborNum = 0;
int top = Math.max(i - R, 0);
int button = Math.min(cellsArray.length-1, i + R);
int left = Math.max(j - R, 0);
int right = Math.min(j + R, cellsArray[i].length-1);
for( int y = top; y <= button; y++){
for (int x = left; x <= right; x++){
if(cellsArray[y][x] == 1){
if(j != x || i != y){
neighborNum++;
}
}
}
}
if(cellsArray[i][j] == 1){
//存活
if(LIVE_NUM_MIN <= neighborNum && neighborNum <= LIVE_NUM_MAX){
afterCells[i][j] = 1;
}
}else if(cellsArray[i][j] == 0){
//诞生
if(BIRTH_NUM_MIN <= neighborNum && neighborNum <= BIRTH_NUM_MAX){
afterCells[i][j] = 1;
}
}
}
}
return afterCells;
}

/**
* 传入二维数组并返回打印在屏幕上的字符串形式
* @param cellsArray 二维数组
* @return 打印在屏幕上的字符串形式
*/
private static String str(byte[][] cellsArray){
StringBuilder cellsString = new StringBuilder();
for (byte[] bytes : cellsArray) {
for (byte aByte : bytes) {
if (aByte == 0) {
cellsString.append(DEATH_CELL);
} else {
cellsString.append(LIVE_CELL);
}
}
cellsString.append("/n");
}
return cellsString.toString();
}

/**
* 传入二维数组和两个位置参数,在二维数组的特定位置上做修改
* 注意:该函数直接改传入数组的状态
* 此函数是在二维数组的xy下标位置上放了一个向右下飞行的 滑翔机
* 右下方向滑翔机示意图:
* □□■
* ■□■
* □■■
* @param cellsArray 修改前(还未放置滑翔机)的数组
* @param x 滑翔机的左上角顶点在数组里的x位置
* @param y 滑翔机的左上角顶点在数组里的y位置
*/
private static void putGlider(byte[][] cellsArray, int x, int y){
cellsArray[y][x+2] = 1;
cellsArray[y+1][x] = 1;
cellsArray[y+1][x+2] = 1;
cellsArray[y+2][x+1] = 1;
cellsArray[y+2][x+2] = 1;
}
}

python

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
"""
生命游戏-命令行版
设计作者:康威
代码作者:littlefean
代码时间:2020年7月23日
"""
from random import randint
import numpy as np


def newCells(width, height):
"""
生成一个空的全是0的二维数组并返回
:param width: 宽度
:param height: 高度
:return: 二元数组
"""
cellsArray = np.zeros([height, width], dtype=int)
return cellsArray


def randPoint(cellsArray):
"""
将二维数组随机化,该函数直接改参数的状态
:param cellsArray:二维数组
:return:
"""
for i in range(len(cellsArray)):
for j in range(len(cellsArray[i])):
cellsArray[i][j] = randint(0, 1)


def iterative(cellsArray):
"""
传入二维数组并使其按照生命游戏规则迭代一次,迭代完成后返回一个新的迭代后的二维数组
:param cellsArray:迭代前的二维数组
:return:迭代后的二维数组
"""
afterCells = newCells(len(cellsArray[0]), len(cellsArray))
for y in range(len(cellsArray)):
for x in range(len(cellsArray[y])):
neighborNum = 0
for y_border in range(max(y - R, 0), min(y + R, len(cellsArray) - 1) + 1):
for x_border in range(max(x - R, 0), min(x + R, len(cellsArray[y]) - 1) + 1):
if cellsArray[y_border][x_border] == 1:
# 判断不是自己:
if x != x_border or y != y_border: # 最后一个bug,应该是或的关系,而不是且的关系
neighborNum += 1
# 存活:周围数量 == 3 or == 2
if cellsArray[y][x] == 1:
if neighborNum in range(LIVE_NUM_MIN, LIVE_NUM_MAX + 1):
afterCells[y][x] = 1
# 诞生:周围数量 == 3
elif cellsArray[y][x] == 0:
if neighborNum in range(BIRTH_NUM_MIN, BIRTH_NUM_MAX + 1):
afterCells[y][x] = 1
# 死于拥挤:周围数量>=4
# 死于孤独:周围数量<=1
return afterCells


def string(cellsArray):
"""
返回cellsArray的字符串表示形式
:param cellsArray: 二维数组
:return: cellsArray的字符串表示形式
"""
cellsString = ""
for y in range(len(cellsArray)):
for x in range(len(cellsArray[y])):
if cellsArray[y][x] == 0:
cellsString += DEATH_CELL
else:
cellsString += LIVE_CELL
cellsString += "/n"
return cellsString


def putGlider(cellsArray, x, y):
"""
传入二维数组和两个位置参数,在二维数组的特定位置上做修改,并返回修改后的二维数组
此函数是在二维数组的xy下标位置上放了一个向右下飞行的 滑翔机
xy对应滑翔机的左上角顶点
□□■
■□■
□■■
:param cellsArray:修改前(还未放置滑翔机)的数组
:param x:滑翔机的左上角顶点在数组里的x位置
:param y:滑翔机的左上角顶点在数组里的y位置
:return:修改后(放置了一个滑翔机)的二维数组
"""
cellsArray[y][x + 2] = 1
cellsArray[y + 1][x] = 1
cellsArray[y + 1][x + 2] = 1
cellsArray[y + 2][x + 1] = 1
cellsArray[y + 2][x + 2] = 1
return cellsArray


if __name__ == '__main__':
"""

"""
CELLS_WIDTH = 85
CELLS_HEIGHT = 45
LIVE_CELL = "█"
DEATH_CELL = " "
R = 1
LIVE_NUM_MIN = 2
LIVE_NUM_MAX = 3
BIRTH_NUM_MIN = 3
BIRTH_NUM_MAX = 3

cells = newCells(85, 45)
print(string(cells))
randPoint(cells)
putGlider(cells, 0, 0)
print(string(cells))
step = 0
while True:
if step % 24 == 0:
putGlider(cells, randint(0, 5), randint(0, 5))
cells = iterative(cells)
print(string(cells))
step += 1

回顾反思

  1. 直接给调用者暴露了数组,但是调用者并不知道是用[y][x]的方式还是[x][y]的方式,你这个没有说明,会导致一些不方便
  2. 如果能把cellsArray封装成一个类那就好了,因为它已经很像类了。

不知道怎么结尾了,康威生命游戏的评价里有一句话说的不错,就以它结尾吧:“ 孤独和拥挤都不适合生命的繁衍和发展 ”。