生命游戏
1. 生命游戏简介:
生命游戏(Life Game)是英国数学家约翰·何顿·康威在1970年发明的细胞自动机。它是由3条规则构成的二维元胞自动机(2D Cellular Automata)。
2. 原理
在有N*N个格子的平面上,把每一个格子都可以看成是一个生命体,每个生命都有生和死两种状态,如果该格子生就显示蓝色,死则显示白色。每一个格子旁边都有邻居格子存在,如果我们把3*3的9个格子构成的正方形看成一个基本单位的话,那么这个正方形中心的格子的邻居就是它旁边的8个格子。
每个格子的生死遵循下面的原则:
1. 如果一个细胞周围有3个细胞为生(一个细胞周围共有8个细胞),则该细胞为生(即该细胞若原先为死,则转为生,若原先为生,则保持不变) 。
2. 如果一个细胞周围有2个细胞为生,则该细胞的生死状态保持不变;
3. 在其它情况下,该细胞为死(即该细胞若原先为生,则转为死,若原先为死,则保持不变)
3. 代码实现
功能:
1.全屏显示滑翔机发射器的演化过程如下图:
2. 按“Esc”键程序退出
类说明:
Canvas.java 画布类,封装了细胞演化的算法
Cell.java 细胞单元的类
Constant.java 常量类
MainFrame.java 主控程序
算法思路:
用三个链表,cellList(活着的细胞),bornCellList(刚出生的细胞),deadCellList(死去得细胞),遍历cellList,对每个细胞按规则进行繁衍,繁衍一代后,处理cellList,从中去掉deadCellList,加入bornCellList。在如此可完成一轮的繁衍,其中还要用到一个二维数组flagArr来保存细胞状态。
二维数组表示细胞的状态
//取值为1 和 2 1:当代存活的细胞 2:刚产生的下一代细胞
int [][] flagArr;
活着的细胞链表
List
新生代的细胞链表
List
死亡的细胞链表
List
- 清空bornCellList,deadCellList
- 遍历cellList,对每个细胞按规则进行演化
- 2步骤后deadCellList里面会有一些死去的细胞,遍历deadCellList,将其中细胞的状态标记到flagArr中。
for(Cell cel:deadCellList)
flagArr[cel.row][cel.col] = 0;
从活着的细胞列表中去掉在下一代将要死掉的
cellList.removeAll(deadCellList);
将下一代新生成的细胞标记为当代存活的细胞
for(Cell cel:bornCellList)
flagArr[cel.row][cel.col]=1;
将新生成的细胞加入到活着的细胞列表中
cellList.addAll(bornCellList);
重复执行1到3的步骤,每重复一次,重绘整个细胞面板
Canvas代码:
package com.difeng.lifegame;
import static com.difeng.lifegame.Constant.CELL_HEIGHT;
import static com.difeng.lifegame.Constant.*;
import java.awt.Color;
import java.awt.Graphics;
import java.util.LinkedList;
import java.util.List;
import javax.swing.JPanel;
/**
*
* @author difeng
*
*/
public class Canvas extends JPanel implements Runnable{
/**
*
*/
private static final long serialVersionUID = 1L;
//取值为1 和 2 1:当代存活的细胞 2:刚产生的下一代细胞
int [][] flagArr;
//活着的细胞
List<Cell> cellList = new LinkedList<Cell>();
//新生的细胞
List<Cell> bornCellList = new LinkedList<Cell>();
//死亡的细胞
List<Cell> deadCellList = new LinkedList<Cell>();
//方向数组
final int [][] dir = {
{-1,-1},{-1,0},
{-1,1},{0,-1},
{0,1},{1,-1},
{1,0},{1,1},
};
//滑翔机发射器的图案矩阵
final int[][] plant = {
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1},
{0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1},
{1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{1,1,0,0,0,0,0,0,0,0,1,0,0,0,1,0,1,1,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
};
public Canvas(){
setBackground(Color.black);
init();
}
/**
*
* 初始化
*
*/
public void init(){
CELL_WIDTH = 20;
CELL_HEIGHT = 20;
COL_NUM = SCREEN_WIDTH/CELL_WIDTH;
ROW_NUM= SCREEN_HEIGHT/CELL_HEIGHT;
flagArr = new int[ROW_NUM][COL_NUM];
//滑翔机发射器的起始坐标
int x = 5,y=5;
//初始化滑翔机发射器
for(int i = 0;i < plant.length;i++){
for(int j = 0;j < plant[0].length;j++){
if(plant[i][j] == 1){
int r = x + i;
int col = y + j;
if(flagArr[r][col]==0){
Cell cel = new Cell(r, col,true);
cellList.add(cel);
flagArr[r][col] = 1;
}
}
}
}
}
public void evolution(){
//清空中间计算的列表
bornCellList.clear();
deadCellList.clear();
//遍历当代细胞列表,让细胞开始繁衍生长,计算下一代的状态
for(Cell cel:cellList){
//细胞生长
cellEvolution(cel);
}
//将下一代将要死掉的细胞标记为死亡
for(Cell cel:deadCellList){
flagArr[cel.row][cel.col] = 0;
}
//从活着的细胞列表中去掉在下一代将要死掉的
cellList.removeAll(deadCellList);
//将下一代新生成的细胞标记为当代存活的细胞
for(Cell cel:bornCellList){
flagArr[cel.row][cel.col]=1;
}
//将新生成的细胞加入到活着的细胞列表中
cellList.addAll(bornCellList);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
//绘制细胞棋盘
g.setColor(new Color(33,33,33));
for(int i=0;i<=ROW_NUM;i++){
g.drawLine(0,i*CELL_HEIGHT,SCREEN_WIDTH,i*CELL_HEIGHT);
}
for(int i=0;i<=COL_NUM;i++){
g.drawLine(i*CELL_WIDTH,0,i*CELL_WIDTH,SCREEN_HEIGHT);
}
//开始画细胞
g.setColor(Color.GREEN);
for(int i=0;i<cellList.size();i++){
Cell cel = cellList.get(i);
if(cel!=null){
g.fill3DRect(cel.col*CELL_WIDTH,cel.row*CELL_HEIGHT,CELL_WIDTH,CELL_HEIGHT,true);
}
}
}
/**
* 细胞繁衍
* @param cel
*/
public void cellEvolution(Cell cel){
int row = 0;
int col = 0;
int cellNum = 0;
for(int i=0;i<dir.length;i++){
row = cel.row + dir[i][0];
col = cel.col + dir[i][1];
if(row>-1 && row<ROW_NUM && col>-1 && col<COL_NUM){
if(flagArr[row][col]==1){
cellNum++;
}else{
//此位置周围有三个存活的细胞且此位置暂无生成的新细胞,则此位置应产生一个细胞
if(computeRound(row,col) == 3 && flagArr[row][col] != 2){
Cell newCell = new Cell(row, col,true);
//标志此位置新生成一个细胞
flagArr[row][col] = 2;
bornCellList.add(newCell);
}
}
}
}
//细胞死亡判断条件
if(cellNum < 2 || cellNum > 3){
deadCellList.add(cel);
}
}
/**
* 计算一个细胞周围有多少个存活的细胞
* @param row
* @param col
* @return
*
*/
public int computeRound(int row,int col){
int cellNum = 0;
int r=0;
int cl=0;
for(int i = 0;i<dir.length;i++){
r = row + dir[i][0];
cl = col + dir[i][1];
if(r > -1 && r < ROW_NUM && cl > -1 && cl < COL_NUM){
if(flagArr[r][cl]==1){
cellNum++;
}
}
}
return cellNum;
}
public void run() {
while(true){
try {
Thread.sleep(REFRESH_INTERVALS);
//重新绘制
repaint();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
evolution();
}
}
}
Cell代码:
package com.difeng.lifegame;
/**
*
* @author difeng
*
*/
public class Cell {
int row;
int col;
boolean islive;
public Cell(int row, int col, boolean islive) {
super();
this.row = row;
this.col = col;
this.islive = islive;
}
}
Constant的代码:
package com.difeng.lifegame;
import java.awt.Dimension;
import java.awt.Toolkit;
/**
*
* @author difeng
*
*/
public class Constant {
static Dimension dimension = Toolkit.getDefaultToolkit().getScreenSize();
public static int SCREEN_WIDTH = (int) dimension.getWidth();
public static int SCREEN_HEIGHT = (int)dimension.getHeight();
public static int ROW_NUM;
public static int COL_NUM;
public static final int CELL_WIDTH = 20;
public static final int CELL_HEIGHT = 20;
public static final int REFRESH_INTERVALS = 100;
}
主控程序:
package com.difeng.lifegame;
import java.awt.BorderLayout;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import javax.swing.JFrame;
/**
*
* @author difeng
*
*/
public class MainFrame {
public static void main(String[] args) {
JFrame life = new JFrame();
Canvas canvas = new Canvas();
GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
life.getContentPane().add(canvas,BorderLayout.CENTER);
life.setUndecorated(true);
//设置全屏显示
gd.setFullScreenWindow(life);
//添加程序退出的事件监听
life.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
super.keyPressed(e);
if(e.getKeyCode()==KeyEvent.VK_ESCAPE){
System.exit(0);
}
}
});
new Thread(canvas).start();
}
}
4. 总结
1.搞清楚问题的本质,也就是原理部分。
2.然后思考寻找合理的数据结构。再考虑类的设计和协作。
3.coding
生命游戏还有其它游戏的算法来高效的实现,上述只是自己刚接触时的想法。不过这个游戏确实挺有意思的,其实它也属于分形学。有兴趣的朋友还可以阅读《混沌与分形》这本书。也可以去看看极客们制作的漂亮的分形图形