在研究元胞自动机理论过程中,Conway发明生命游戏(Game of Life、GoL),在1970s风靡一时。
这是0人游戏,即按照初始的设置,游戏自动演化。在类似围棋的棋盘中,每一个格子可以是空格或者存在一个生命/细胞/Cell;每一个格子有8个相邻的格子(正上方、正下方、右侧、左侧、左上方、右上方、左下方以及右下方),相邻的格子中存活的生命数量称为其邻居(neighbor)数。在世代交替时,所有的格子根据其邻居数,诞生新生命、Cell保持存活或者Cell死亡。生命演化规则:B3/S23
Conway's Game of Life
在介绍数组期间,介绍了《5.3.5 案例:生命游戏》。在学习了图形显示技术后,《编程导论(Java)·9.5 案例:GoL》再次给出更美观的实现。
参考《5.3.5 案例:生命游戏》。
1.游戏世界
游戏世界是一个char[][]二维数组,用字符*表示存在细胞,而空格表示该格子没有细胞。在控制台中输出world数据。
package dataStructures.array; public class simpleGOL{ private final int HEIGHT=15;//二维游戏世界的大小。HEIGHT*WIDTH 个字符 private final int WIDTH =60; private char[][] world = new char[HEIGHT][WIDTH]; private final char Y ='*'; //用字符*代表一个细胞 private final char No = ' '; /** * 构造器。使用预定义的数据初始化world. */ simpleGOL(){ world[3][4] = world[3][5] = world[3][6] =Y; //glider world[10][4] = world[10][5] = world[10][6] = world[11][6] = world[12][5] = Y; } /** * 在控制台中输出world数据 */ private void print(){ for (int height = 0; height < HEIGHT; height++) { for (int width = 0;width < WIDTH; width++) { char c = ( world[height][width] == Y) ? Y: No ; System.out.print(c); } System.out.println(""); } System.out.println(""); } }2.nextWorld()
生命游戏的核心是在当前world数据基础上,计算出下一代的world数据。为此,需扫描当代world的每一个格子,计算其邻居的数量,以条件语句表达生命演化规则,设定新一代world的数据。
助手方法getNeighbors(int row,int col),计算指定坐标位置(x,y)或(row,col)格子邻居的数量;
cellState(int y,int x),返回指定坐标(x,y)的格子中细胞状况值,即是否存在细胞。为了方便getNeighbors调用它,返回值不是boolean而是0与1。
/** * Helper Method。 * 获取指定坐标(x,y)或(col,row)格子中细胞状况值,即是否存在细胞。注意参数超出边界情况。 * @参数 y y坐标,或row 格子所在行 * @参数 x x坐标,或col 格子所在列, * @返回值 参数超出边界或者格子为空,返回0;否则返回1。 */ private int cellState(int y,int x) { boolean isEmpty = (y < 0 || y >= HEIGHT || x < 0 || x >= WIDTH || world[y][x] != Y ) ; return isEmpty ? 0 : 1 ; } /** * 获取指定坐标(x,y)或(col,row)格子的邻居(neighbor)数 * @参数 y y坐标,或row 格子所在行 * @参数 x x坐标,或col 格子所在列, * @返回值 邻居数目. */ private int getNeighbors(int y,int x) { int n = 0; //neighbor n += cellState( y - 1, x - 1); n += cellState( y - 1, x); n += cellState( y - 1, x + 1); n += cellState( y, x - 1); n += cellState( y, x + 1); n += cellState( y + 1, x - 1); n += cellState( y + 1, x); n += cellState( y + 1, x + 1); return n; } /** * 世代交替。 * 生命游戏的核心是在当前world数据基础上计算出下一代的world数据,以便打印新一代的二维世界。 * 为此,需扫描当代world的每一个格子,计算其邻居的数量,以条件语句表达生命演化规则,设定新一代world的数据。 * 因为在所有的格子计算完之前,world的数据不得变化, * 因此使用了应该临时的temp,暂存二维世界的数据。 * ★calculate() */ public void nextWorld() { char[][] temp = new char[HEIGHT][WIDTH]; int y=0;//坐标位置(x,y) for (char[] rowArr : world){ int x=0; for(char colData :rowArr ){ int neighbor = getNeighbors(y, x);//习惯(y, x) if (neighbor==3) { temp[y][x] = Y; }else if (neighbor == 2 && colData == Y){ temp[y][x] = Y; }else{ temp[y][x] = No; } x++; } y++; } /* for (int height = 0; height < HEIGHT; height++) { for (int width = 0; width < WIDTH; width++) { int neighbor = getNeighbors(height, width); if (neighbor==3) { temp[height][width] = Y; }else if (neighbor == 2 && world[height][width] == Y){ temp[height][width] = Y; }else{ temp[height][width] = No; } } }*/ world = temp; } /** * 在BlueJ中,你可以创建一个simpleGOL对象,交替调用其print()、nextWorld()方法。 */ public static void main(String[] a){ simpleGOL gol = new simpleGOL(); String end; int generation = 0; gol.print(); java.util.Scanner in = new java.util.Scanner(System.in); do{ System.out.printf("Generation %d\n", ++generation); gol.nextWorld(); gol.print(); System.out.printf("Press q to quit or other key to continue: "); end = in.next(); }while(!"q".equals(end)); } }这个代码说明了 GoL的算法。
演示程序中,二维游戏世界的大小共SIZE*SIZE个格子,每个格子的边长CELL_Size为指定值。
当前这一代的状况,由boolean[][] table描述,而取代了《5.3.5 案例:生命游戏》的char[][] world ;
int[][] neighbors,保存每个格子的邻居数目
update ()中为绘制代码,对每一个格子按照有无生命设置颜色,并填充格子,留一个坐标单位露出背景色。
* 用法: * 点击鼠标设置cell,ctrl+点击将cell置空。 * 键盘space切换线程运行/停止。
package API.graphics; import java.applet.Applet; import java.awt.Color; import java.awt.Graphics; import javax.swing.*; import java.awt.event.*; /** * The Game Of Life Applet.生命游戏演示。 * boolean[][] table保存各个格子中细胞状况(比较char[][] table ); * int[][] neighbors保存 按照当代grid所推导出的各个格子的邻居数目。 * 用法: * 点击鼠标设置cell,ctrl+点击将cell置空。 * 键盘space切换线程运行/停止。 * @作者: yqj2065 * @日期: 09-01-30 */ public class GoL extends Applet implements Runnable,MouseListener,MouseMotionListener,KeyListener{ private final int SIZE = 30;//二维游戏世界的大小,共SIZE*SIZE个格子 private final int CELL_Size =10;//每个格式的边长,Java坐标系单位。 private Color cell =new Color(32,98,40); private Color space =new Color(226,245,226); //当代的状况,格子中是否有生命 private boolean[][] table = new boolean[SIZE][SIZE]; //格子的邻居数目 private int[][] neighbors = new int[SIZE][SIZE]; private Thread animator; private int delay;//延迟 private boolean running;//flag。标识线程的运行状况,正在运行则running为true,被用户中断,running为false。 @Override public void run() { long tm = System.currentTimeMillis(); while (Thread.currentThread() == animator) { if (running == true) { getNeighbors(); nextWorld(); repaint(); } try { tm += delay; Thread.sleep(Math.max(0, tm - System.currentTimeMillis())); } catch (InterruptedException e) { break; } } } // run /** * applet 生命周期方法 */ @Override public void init() { animator = new Thread(this); delay = 100; running = false; //setBackground(Color.yellow); setBackground(new Color(199,237,204)); addMouseListener(this); addMouseMotionListener(this); addKeyListener(this); } @Override public void start() { animator.start(); } @Override public void stop() { animator = null; } @Override public void paint(Graphics g) { update(g); } @Override public void update (Graphics g) { for (int x = 0; x < SIZE; x++) for (int y = 0; y < SIZE; y++) { g.setColor(table[x][y]?cell:space); g.fillRect(x * CELL_Size, y * CELL_Size, CELL_Size - 1, CELL_Size - 1); } } /** * 从table数组中推导出neighbors数组。 */ public void getNeighbors() { for (int r = 0; r < SIZE; r++){//row for (int c = 0; c < SIZE; c++){//col if(r-1 >= 0 && c-1 >= 0 && table[r-1][c-1] )neighbors[r][c]++; if(r-1 >= 0 && table[r-1][c]) neighbors[r][c]++; if(r-1 >= 0 && c+1 < SIZE && table[r-1][c+1])neighbors[r][c]++; if(c-1 >= 0 && table[r][c-1]) neighbors[r][c]++; if(c+1 < SIZE && table[r][c+1]) neighbors[r][c]++; if(r+1 < SIZE && table[r+1][c]) neighbors[r][c]++; if(r+1 < SIZE && c+1 < SIZE && table[r+1][c+1]) neighbors[r][c]++; if(r+1 < SIZE && c-1 >=0 && table[r+1][c-1]) neighbors[r][c]++; } } } /** * nextWorld(),世代交替。 * 生命游戏的核心是计算出下一代的table,产生新一代的二维世界。 * 按照每一个neighbors元素 */ public void nextWorld() { for (int r = 0; r < SIZE; r++){//row for (int c = 0; c < SIZE; c++){//col if (neighbors[r][c] == 3){ table[r][c] = true; }//if (neighbors[r][c] == 2) 不改变table[r][c]。 if (neighbors[r][c] < 2) table[r][c] = false; if (neighbors[r][c] >= 4) table[r][c] = false; neighbors[r][c] = 0; } } } /** * event handler */ public void mouseClicked(MouseEvent e){ } public void mousePressed(MouseEvent e){ int cellX = e.getX()/CELL_Size; int cellY = e.getY()/CELL_Size; table[cellX][cellY] = !e.isControlDown(); repaint(); } public void mouseReleased(MouseEvent e){} public void mouseEntered(MouseEvent e){} public void mouseExited(MouseEvent e){} public void mouseDragged(MouseEvent e){ this.mousePressed(e); } public void mouseMoved(MouseEvent e){} public void keyTyped(KeyEvent e){} public void keyPressed(KeyEvent e){ if(e.getKeyChar()==' '){ running = !running; repaint(); } } public void keyReleased(KeyEvent e){} }
截图用win8自带的snippingTool。