关于生命游戏
之前在学校看到ThoughtWorks举办的线下结对编程的比赛一等奖是大疆无人机,冲着无人机就拉着实验室小伙伴马总一起报了个名。然后题目就是实现一个界面版的生命游戏,所以才了解了生命游戏。
关于生命游戏,可以参考维基百科。
下图是效果图:
规则
生命游戏中,对于任意细胞,规则如下:
每个细胞有两种状态-存活或死亡,每个细胞与以自身为中心的周围八格细胞产生互动。(如图,黑色为存活,白色为死亡)
当前细胞为存活状态时,当周围低于2个(不包含2个)存活细胞时, 该细胞变成死亡状态。(模拟生命数量稀少)
当前细胞为存活状态时,当周围有2个或3个存活细胞时, 该细胞保持原样。
当前细胞为存活状态时,当周围有3个以上的存活细胞时,该细胞变成死亡状态。(模拟生命数量过多)
当前细胞为死亡状态时,当周围有3个存活细胞时,该细胞变成存活状态。 (模拟繁殖)
可以把最初的细胞结构定义为种子,当所有在种子中的细胞同时被以上规则处理后, 可以得到第一代细胞图。按规则继续处理当前的细胞图,可以得到下一代的细胞图,周而复始。
根据上面的规则得出下面的代码:
/**
* 上一个状态到下一个状态的转移
* 根据规则可以总结得出两条规则:
* 1. 对于周围活着的细胞为3的情况,下一个状态该细胞总是为活
* 2. 对于周围活着的细胞为2的情况,下一个状态与上一状态相同
*/
public void transform(){
int[][] nextMatrix=new int[height][width];
for (int y = 0; y < matrix.length; y++) {
for (int x = 0; x < matrix[0].length; x++) {
nextMatrix[y][x]=0;
int nearNum= findLifedNum(y,x);
//等于3,则下一状态总是活
if(nearNum==3){
nextMatrix[y][x]=1;
}
//等于2,则与上一状态一样
else if(nearNum==2){
nextMatrix[y][x]=matrix[y][x];
}
}
}
matrix=nextMatrix;
}
/**
* 统计每个细胞周围活着的个数
* @param x 横坐标
* @param y 纵坐标
* @return
*/
public int findLifedNum(int y, int x){
int num=0;
//左边
if(x!=0){
num+=matrix[y][x-1];
}
//左上角
if(x!=0&&y!=0){
num+=matrix[y-1][x-1];
}
//上边
if(y!=0){
num+=matrix[y-1][x];
}
//右上
if(x!=width-1&&y!=0){
num+=matrix[y-1][x+1];
}
//右边
if(x!=width-1){
num+=matrix[y][x+1];
}
//右下
if(x!=width-1&&y!=height-1){
num+=matrix[y+1][x+1];
}
//下边
if(y!=height-1){
num+=matrix[y+1][x];
}
//左下
if(x!=0&&y!=height-1){
num+=matrix[y+1][x-1];
}
return num;
}
实现
首先看我们做的效果,如下图:
关于初始状态的输入
初始状态是通过文件加载的方式来实现的,为了加载很多的情况,又写了一个工具类负责随机产生case,代码如下:
/**
* 创建测试案例
*/
private static void createCaseFile() {
Random random = new Random();
int rows = 1 + random.nextInt(100);
int cols = 1 + random.nextInt(100);
int duration = 200;
int num = 300;
File file = new File(cols+"_"+rows+"_"+System.nanoTime() + ".txt");
PrintWriter writer = null;
try {
writer = new PrintWriter(new FileWriter(file));
StringBuilder sb = new StringBuilder(cols + " " + rows + " " + duration + " " + num);
writer.write(sb.append("\n").toString());
//开始逐行初始化
for (int y = 0; y < rows; y++) {
sb = new StringBuilder();
for (int x = 0; x < cols; x++) {
if (random.nextInt(3) % 3 == 0) {
sb.append("1 ");
} else {
sb.append("0 ");
}
}
sb.deleteCharAt(sb.length()-1).append("\n");
writer.write(sb.toString());
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (writer != null) {
writer.close();
}
}
}
关于界面
使用的Swing编程,java的界面做出来确实不咋的。不过我们加入了暂停、继续功能,这个是通过控制一个变量来做的,每两帧动画之间通过线程睡眠实现的。核心代码如下:
private class GameControlTask implements Runnable {
@Override
public void run() {
while (!stop) {
cellMatrix.transform();
showMatrix();
try {
TimeUnit.MILLISECONDS.sleep(duration);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
}
其中cellMatrix代表了一帧的状态,调用transform()将会进行下一次变换,showMatrix负责更新界面。
总结
最后,由于界面比较酷炫,可以支持暂停、继续功能,代码结构比较优美及注释比较完整,最后和我的小伙伴拿了个无人机,还是挺爽的。
关于代码,可以参考TxGameOfLife。