1. 问题背景:Game Of Life
// 英国数学家发明的生命游戏 (Game of Life)
// 使用一个2维数组来实现其规则
// 在这个数组中,每个存储位子都能容纳一个LIFE元胞。世代(gennerations)用于标记时间
// 的流逝。每个世代都会LIFE社会带来生与死。
// 生死规则如下:
// * 定义的元胞都有8个邻居。上下左右,左上左下,右上右下八个方位。
// * 如果一个元胞有一个或零个邻居,会因为孤独而死亡。3个以上的邻居会因为拥挤而死亡。
// * 如果空元胞弟正好有3个邻居,会在空元胞的位子生成一个元胞。
// * 生生死死世代交换。
// 使用一个2维数组来实现其规则
// 在这个数组中,每个存储位子都能容纳一个LIFE元胞。世代(gennerations)用于标记时间
// 的流逝。每个世代都会LIFE社会带来生与死。
// 生死规则如下:
// * 定义的元胞都有8个邻居。上下左右,左上左下,右上右下八个方位。
// * 如果一个元胞有一个或零个邻居,会因为孤独而死亡。3个以上的邻居会因为拥挤而死亡。
// * 如果空元胞弟正好有3个邻居,会在空元胞的位子生成一个元胞。
// * 生生死死世代交换。
2. 问题解决策略:
本博客使用Java 搭载Java Swing实现本算法。
3. 软件完成效果如下:
4. 程序代码部分:
核心代码Game.java
package com.game;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.JFrame;
import com.game.enmu.State;
public class Game {
private int row;
private int column;
private int numberRand;
private boolean running;
private Random rand;
private ArrayList<ArrayList<State>> data;
private ArrayList<CellStateOfSpecificPlace> back_data;
private JFrame parent;
private Thread generate;
public Game(int row, int column, int numberRand){
this.row = row;
this.column = column;
this.numberRand = numberRand;
this.running = true;
this.data = new ArrayList<ArrayList<State>>();
rand = new Random();
back_data = new ArrayList<CellStateOfSpecificPlace>();
initinalGame();
//runGame();
}
public Game(int row, int column, int numberRand, GameGUI gameGUI) {
this(row, column, numberRand);
parent = gameGUI;
}
/*
* initial the game with the specific policy.
* */
public void initinalGame(){
data.clear();
for (int rowIndex = 0; rowIndex < row; rowIndex++){
ArrayList<State> temp = new ArrayList<State>();
for (int columnIndex = 0; columnIndex < column; columnIndex++){
temp.add(State.DIE);
}
data.add(temp);
}
initialPolicy();
}
/*
* Below code is one kind of initial policy of Game Of Life, which you can
* modified by yourself.
* */
public void initialPolicy(){
int margin = (int)Math.sqrt(numberRand);
int rowPlace = rand.nextInt(row - margin - 1) + 1;
int columnPlace = rand.nextInt(column - margin - 1) + 1;
for (int rowIndex = rowPlace; rowIndex < rowPlace + margin; rowIndex++){
for (int columnIndex = columnPlace; columnIndex < columnPlace + margin; columnIndex++){
if (rand.nextBoolean()){
data.get(rowIndex).set(columnIndex, State.ALIVE);
}
}
}
}
/*
* Below method run the code in a thread named generate.
* */
public void runGame(){
if (false == running){
if (generate.isAlive()){
generate.interrupt();
generate = null;
}
} else {
if (null == generate){
generate = new Thread(new Runnable() {
@Override
public void run() {
while(!Thread.interrupted()){
//printGame();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().stop();
}
generated();
if (null != parent){
parent.repaint();
}
}
}
});
generate.start();
}
}
}
/*
* generated method to generate the next generation based on current generation.
* */
private void generated() {
int neighbours = 0;
State currentCellState = State.DIE;
State nextCellState = State.DIE;
back_data.clear();
for (int rowIndex = 1; rowIndex < row - 1; rowIndex++){
for (int columnIndex = 1; columnIndex < column - 1; columnIndex++){
neighbours = getNeighborNumbers(rowIndex, columnIndex);
currentCellState = data.get(rowIndex).get(columnIndex);
nextCellState = nextGenerateState(currentCellState, neighbours);
if (nextCellState != currentCellState){
back_data.add(new CellStateOfSpecificPlace(rowIndex, columnIndex, nextCellState));
}
}
}
for (CellStateOfSpecificPlace item:back_data){
data.get(item.getRowIndex()).set(item.getColumnIndex(), item.getState());
}
}
/*
* Below method get the cell's neighbor number.
* */
private int getNeighborNumbers(int rowIndex, int columnIndex){
int counter = 0;
for (int i = rowIndex - 1; i <= rowIndex + 1; i++){
for (int j = columnIndex - 1; j <= columnIndex + 1; j++){
if (State.ALIVE == data.get(i).get(j)){
counter++;
}
}
}
counter += State.ALIVE == data.get(rowIndex).get(columnIndex) ? -1:0;
return counter;
}
/*
* below code to debug the state of the game.
* */
private void printGame(){
System.out.println("-------------------");
for (ArrayList<State> rowData : data){
for (State ColumnData : rowData){
System.out.print(ColumnData + " ");
}
System.out.println();
}
System.out.println("-------------------");
}
/*
* judge whether the next generation of cell should be alive.
* */
public State nextGenerateState(State alive, int neighbourNumber) {
if (State.ALIVE == alive && (2 == neighbourNumber || 3 == neighbourNumber)){
return State.ALIVE;
}
if (State.DIE == alive && 3 == neighbourNumber){
return State.ALIVE;
}
return State.DIE;
}
public int getRow() {
return row;
}
public void setRow(int row) {
this.row = row;
}
public int getColumn() {
return column;
}
public ArrayList<ArrayList<State>> getData() {
return data;
}
public boolean isRunning() {
return running;
}
public void setRunning(boolean running) {
this.running = running;
}
public static void main(String[] args){
Game game = new Game(15, 15, 25);
}
}
辅助类 CellStateOfSpecificPlace.java,用于记录上一代发生状态改变的位置:
package com.game;
import com.game.enmu.State;
public class CellStateOfSpecificPlace {
private int rowIndex;
private int columnIndex;
private State state;
public CellStateOfSpecificPlace() {
this(0, 0, State.DIE);
}
public CellStateOfSpecificPlace(int rowIndex, int columnIndex, State state) {
super();
this.rowIndex = rowIndex;
this.columnIndex = columnIndex;
this.state = state;
}
public int getRowIndex() {
return rowIndex;
}
public void setRowIndex(int rowIndex) {
this.rowIndex = rowIndex;
}
public int getColumnIndex() {
return columnIndex;
}
public void setColumnIndex(int columnIndex) {
this.columnIndex = columnIndex;
}
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
}
}
辅助类State.java,定义Cell的状态的枚举类型,可以用于非界面的控制台打印输出
package com.game.enmu;
public enum State {
DIE,
ALIVE;
public String toString(){
return name().substring(0, 1);
}
}
GameBoard.java定义用于在Jswing中显示的包涵game的画图面板
package com.game;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JPanel;
import com.game.enmu.State;
public class GameBoard extends JPanel{
/**
*
*/
private static final long serialVersionUID = 1L;
private Game game;
private int squre;
public GameBoard() {
//this.setBounds(x, y, width, height);
}
public GameBoard(Game game){
this.game = game;
}
public void paint(Graphics g){
Graphics2D gg = (Graphics2D)g;
squre = (this.getWidth() - 50) / Math.max(game.getRow(), game.getColumn());
int x = 25;
int y = 10;
gg.setStroke(new BasicStroke(0.5f, BasicStroke.CAP_ROUND,
BasicStroke.JOIN_ROUND, 1.0f,new float[]{5f, 5f},0f));
gg.setColor(Color.blue);
for (int i = 0; i < game.getRow(); i++){
for (int j = 0; j < game.getColumn(); j++){
gg.drawRect(x + j * squre, y + i * squre, squre, squre);
}
}
for (int i = 0; i < game.getRow(); i++){
for (int j = 0; j < game.getColumn(); j++){
if (State.ALIVE == game.getData().get(i).get(j)){
gg.setColor(Color.blue);
gg.fillOval(x + j * squre, y + i * squre, squre, squre);
} else {
gg.setColor(Color.WHITE);
gg.fillOval(x + j * squre, y + i * squre, squre, squre);
}
}
}
gg.setStroke(new BasicStroke(5f));
gg.setColor(Color.blue);
gg.drawRect(x - 1, y - 1, squre * game.getColumn() + 2, squre * game.getRow() + 2);
gg.dispose();
}
}
GameGUI.java 定义了软件的整体外观,软件的入口:
package com.game;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class GameGUI extends JFrame{
/**
*
*/
private static final long serialVersionUID = 1L;
private JPanel mainframe;
private GameBoard board;
private JPanel control;
private BorderLayout border;
private JButton start;
private JButton stop;
private JButton exit;
private JButton restart;
private int row;
private int column;
private Game game;
private ActionListener actionlistener;
public GameGUI(String title, int row, int column){
super(title);
this.row = row;
this.column = column;
initial();
setSize(900, 900);
this.setResizable(false);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationByPlatform(true);
this.setVisible(true);
this.requestFocus();
}
private void initial() {
createComponent();
layOut();
listener();
}
private void createComponent() {
mainframe = new JPanel();
control = new JPanel();
border = new BorderLayout();
start = new JButton("Start");
game = new Game(this.row, this.column, 81, this);
board = new GameBoard(game);
stop = new JButton("Stop");
exit = new JButton("Exit");
restart = new JButton("Re-Start");
}
private void layOut() {
this.getContentPane().add(mainframe);
mainframe.setLayout(border);
mainframe.add(board, BorderLayout.CENTER);
mainframe.add(control, BorderLayout.EAST);
Box ve = Box.createVerticalBox();
ve.add(start);
ve.add(Box.createVerticalStrut(50));
stop.setSize(start.getWidth(), start.getHeight());
ve.add(stop);
ve.add(Box.createVerticalStrut(50));
ve.add(exit);
ve.add(Box.createVerticalStrut(50));
ve.add(restart);
control.add(ve);
}
private void listener() {
actionlistener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (((JButton)(e.getSource())).getText().equals("Start")){
game.setRunning(true);
game.runGame();
} else if (((JButton)(e.getSource())).getText().equals("Exit")){
System.exit(0);
} else if (((JButton)(e.getSource())).getText().equals("Stop")){
game.setRunning(false);
game.runGame();
} else if (((JButton)(e.getSource())).getText().equals("Re-Start")){
game.setRunning(true);
game.initinalGame();
repaint();
game.runGame();
}
}
};
start.addActionListener(actionlistener);
stop.addActionListener(actionlistener);
restart.addActionListener(actionlistener);
exit.addActionListener(actionlistener);
}
public static void main(String[] args) {
GameGUI game = new GameGUI("Game Of Life", 25, 25);
}
}
最后一点测试代码,算是TDD(测试驱动开发):
package com.game.test;
import static org.junit.Assert.*;
//import org.easymock.EasyMock;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.game.Game;
import com.game.enmu.State;
public class TestGame {
private Game mockGame;
private Game game;
@Before
public void setUp() throws Exception {
//mockGame = EasyMock.createMock(Game.class);
//mockGame.initinalGame();
game = new Game(5, 5, 9);
}
@After
public void tearDown() throws Exception {
}
@Test
public void AliveCellMoreThanThreeNeibourShouldBeDie() {
State result = game.nextGenerateState(State.ALIVE, 3 + 1);
assertEquals(State.DIE, result);
}
@Test
public void AliveCellLessThanOneNeibourShouldBeDie() {
State result = game.nextGenerateState(State.ALIVE, 1);
assertEquals(State.DIE, result);
}
@Test
public void AliveCellWithTwoOrThreeNeibourShouldBeAlive() {
State result = game.nextGenerateState(State.ALIVE, 2);
assertEquals(State.ALIVE, result);
result = game.nextGenerateState(State.ALIVE, 3);
assertEquals(State.ALIVE, result);
}
@Test
public void DieCellWithThreeNeibourShouldBeAlive() {
State result = game.nextGenerateState(State.DIE, 3);
assertEquals(State.ALIVE, result);
}
}
有好久没有写博客了,感谢你的再次惠顾。