生命游戏(Java)

在研究元胞自动机理论过程中,Conway发明生命游戏(Game of Life、GoL),在1970s风靡一时。

这是0人游戏,即按照初始的设置,游戏自动演化。在类似围棋的棋盘中,每一个格子可以是空格或者存在一个生命/细胞/Cell;每一个格子有8个相邻的格子(正上方、正下方、右侧、左侧、左上方、右上方、左下方以及右下方),相邻的格子中存活的生命数量称为其邻居(neighbor)数。在世代交替时,所有的格子根据其邻居数,诞生新生命、Cell保持存活或者Cell死亡。生命演化规则:B3/S23

  • 一个生命如果恰好有2个或3个邻居,它会存活到下一个世代;否则,会因为孤独或拥挤而死亡。
  • 一个空格,如果恰好有3个邻居,则诞生一个新生命。

Conway's Game of Life

在介绍数组期间,介绍了《5.3.5 案例:生命游戏》。在学习了图形显示技术后,《编程导论(Java)·9.5 案例:GoL》再次给出更美观的实现。

最简单的game of life演示程序

参考《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的算法。

·9.5 案例: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){}
}


生命游戏(Java)_第1张图片

空白键切换



截图用win8自带的snippingTool。



你可能感兴趣的:(生命游戏(Java))