生命游戏概述:
生命游戏是英国数学家约翰·何顿·康威在 1970 年发明的细胞自动机,它包括一个二维矩形世界,这个世界中的每个方格居住着一个活着的或死亡的细胞。一个细胞在下一个时刻生死取决于相邻八个方格中活着的或死了的细胞的数量。如果相邻方格活着的细胞数量过多,这个细胞会因为资源匮乏而在下一个时刻死去;相反,如果周围活细胞过少,这个细胞会因太孤单而死去。游戏在一个类似于围棋棋盘一样的,可以无限延伸的二维方格网中进行。例如,设想每个方格中都可放置一个生命细胞,生命细胞只有两种状态:“生”或“死”。图中,用黑色的方格表示该细胞为“死”, 其它颜色表示该细胞为“生” 。游戏开始时, 每个细胞可以随机地(或给定地)被设定为“生”或“死”之一的某个状态, 然后,再根据如下生存定律计算下一代每个细胞的状态:
1.每个细胞的状态由该细胞及周围 8 个细胞上一次的状态所决定;
2.如果一个细胞周围有 3 个细胞为生,则该细胞为生,即该细胞若原先为死则转为生,若原先为生则保持不变;
3.如果一个细胞周围有 2 个细胞为生,则该细胞的生死状态保持不变;
4.在其它情况下,该细胞为死,即该细胞若原先为生则转为死,若原先为死则保持不变
设计思路:
首先通过生命游戏概述不难分析出游戏中的两个类(即细胞Cell和二维世界World),然后该游戏游戏规则也较为简单,游戏主要逻辑实现不难(主要也就是细胞繁衍换代,代码实现是比较简单的)。对于我这个Java小白来说,最困难的地方就在于GUI界面的设计,Java的GUI设计,首先想到的肯定是javax.swing.*包。然后像“开始游戏”、“结束游戏”等按钮的设计也不用多说,游戏的常规套路。重点在于用什么控件来表示一个个细胞,我首先想到的是用Jtable(表格),我想用一个一个单元格来表示细胞,然后通过设置每一个单元格的填充颜色来表示细胞生死的状态,但是尝试写了后才发现,Jtable要实现对一个单元格格式进行设置是不太容易的,而且要让整个表格不能编辑也有点复杂(我是Java小白,大佬勿喷),然后就放弃了这个想法。(大佬们可以去尝试一下这个方法,然后教教我)。
灵感来源与最终设计:
该作者是用JButton来表示一个个细胞,其他和上述差不多,对于像我这样的小白来说,这篇文章算是很友好了,作者给出了完整的源代码,感兴趣的小伙伴也可以去看看,参考学习一下。
然后我仿造该作者的代码,最终还是把这个实验写出来了。为了符合我们的实验要求,我设计了如下四个类:(连我都觉得很牵强)
- 细胞类(Cell)
属性:位置(x,y)、状态(isLive)
方法:构造方法,x、y、isLive的访问及设置方法
- 二维矩形世界(World)
属性:世界大小(lx,ly)、当前繁衍代数(nowGeneration)、细胞(cell)
方法:构造方法,lx、ly、nowGeneration、cell[x][y].isLive的访问方法,随机初始化细胞(randonInitCell())方法,细胞清零(deleteAllCell())方法,繁衍换代(updateOfCell())方法
- GUI界面类(GameGUI)
属性:世界界面大小(lx,ly)、二维矩形世界(world)、世界界面(TWorld)、当前繁衍代数显示(NowGeneration)、随机生成第一代与重新生成、开始游戏与结束游戏、暂停游戏与继续游戏、下一代、退出按钮(randomInit,BeginAndOver,StopAndContinue,Next,Exit)、游戏进程控制(isRunning)、游戏线程(thread)
方法:构造方法,初始化界面方法(initGameGUI()),事件处理方法(actionPerformed()),生成下一代界面方法(Change()),显示世界界面方法(showWorld())
- Main类
属性:游戏GUI界面(Game)(表现层),矩形二维世界(world)(业务逻辑层)
方法:main方法
完整源代码:
1.Cell.java
public class Cell {
private int x,y; //细胞位置
private boolean isLive; //细胞状态(true:存活 false:死亡)
public Cell(int x,int y){
this.x=x;
this.y=y;
isLive=false;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
public boolean getIsLive() {
return isLive;
}
public void setIsLive(boolean live) {
isLive = live;
}
}
2.World.java
public class World {
private int lx,ly;
private int nowGeneration;
private Cell[][] cell=new Cell[80][80];
public World(int lx,int ly){
this.lx=lx;
this.ly=ly;
nowGeneration=0;
for(int x=0;x<lx;x++)
for(int y=0;y<ly;y++){
cell[x][y]=new Cell(x,y);
cell[x][y].setIsLive(false);
}
}
public int getLx() {
return lx;
}
public int getLy() {
return ly;
}
public boolean getCellXY(int x, int y){
return cell[x][y].getIsLive();
}
public int getNowGeneration() {
return nowGeneration;
}
//随机初始化细胞
public void randonInitCell(){
for(int x=0;x<lx;x++)
for(int y=0;y<ly;y++)
cell[x][y].setIsLive(Math.random()>0.5?true:false);
}
//细胞清零
public void deleteAllCell(){
for(int x=0;x<lx;x++)
for(int y=0;y<ly;y++)
cell[x][y].setIsLive(false);
nowGeneration=0;
}
//繁衍换代
public void updateOfCell(){
Cell[][] ce=new Cell[lx][ly];
//Cell[][] ce=cell;
for(int x=0;x<lx;x++) {
for (int y = 0; y < ly; y++) {
ce[x][y]=new Cell(x,y);
}
}
for(int x=0;x<lx;x++) {
for (int y = 0; y < ly; y++){
int c=0;
for(int i=x-1;i<=x+1;i++){
for(int j=y-1;j<=y+1;j++){
if(i>=0&&i<lx&&j>=0&&j<ly&&cell[i][j].getIsLive())c++;
}
}
if(cell[x][y].getIsLive())c--;
if(c==3)ce[x][y].setIsLive(true);
else if(c==2)ce[x][y].setIsLive(cell[x][y].getIsLive());
else ce[x][y].setIsLive(false);
}
}
for(int x=0;x<lx;x++) {
for (int y = 0; y < ly; y++) {
cell[x][y]=ce[x][y];
}
}
nowGeneration++;
}
}
3.GameGUI.java
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class GameGUI extends JFrame implements ActionListener {
int lx,ly;
private World world;
private JButton[][] TWorld;
private JLabel NowGeneration;
private JButton randomInit,BeginAndOver,StopAndContinue,Next,Exit;
private boolean isRunning;
private Thread thread;
public GameGUI(String name,World world){
super(name);
this.lx=world.getLx();
this.ly=world.getLy();
this.world=world;
initGameGUI();
}
public void initGameGUI(){
JPanel backPanel,bottomPanel,centerPanel;
backPanel= new JPanel(new BorderLayout());
bottomPanel=new JPanel();
centerPanel=new JPanel(new GridLayout(lx,ly));
this.setContentPane(backPanel);
this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
backPanel.add(centerPanel,"Center");
backPanel.add(bottomPanel,"South");
TWorld=new JButton[lx][ly];
NowGeneration=new JLabel("当前代数:0");
randomInit=new JButton("随机生成细胞");
BeginAndOver=new JButton("开始游戏");
StopAndContinue=new JButton("暂停游戏");
Next=new JButton("下一代");
Exit=new JButton("退出");
for(int x=0;x<lx;x++){
for(int y=0;y<ly;y++){
TWorld[x][y]=new JButton("");
TWorld[x][y].setBackground(Color.white);
centerPanel.add(TWorld[x][y]);
}
}
bottomPanel.add(randomInit);
bottomPanel.add(BeginAndOver);
bottomPanel.add(StopAndContinue);
bottomPanel.add(Next);
bottomPanel.add(NowGeneration);
bottomPanel.add(Exit);
//设置窗口
int sizelx,sizely;
sizelx=Math.min((lx+1)*40,800);
sizely=Math.min(ly*40,1500);
sizely=Math.max(ly*40,500);
this.setSize(sizely,sizelx);
this.setResizable(true);
this.setLocationRelativeTo(null);//让窗口居中显示
this.setVisible(true);
//注册监听器
this.addWindowListener(new WindowAdapter() {
public void windowClosed(WindowEvent e){
System.exit(0);
}
});
randomInit.addActionListener(this);
BeginAndOver.addActionListener(this);
StopAndContinue.addActionListener(this);
Next.addActionListener(this);
Exit.addActionListener(this);
}
public void actionPerformed(ActionEvent e) {
if (e.getSource() == randomInit&&BeginAndOver.getText()=="开始游戏") {//随机生成第一代
world.randonInitCell();
showWorld();
isRunning = false;
thread = null;
randomInit.setText("重新生成");
} else if (e.getSource() == BeginAndOver && BeginAndOver.getText() == "开始游戏"&&randomInit.getText()=="重新生成") {//开始游戏
isRunning = true;
thread = new Thread(new Runnable() {
@Override
public void run() {
while (isRunning) {
Change();
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
}
});
thread.start();
BeginAndOver.setText("结束游戏");
} else if (e.getSource() == BeginAndOver && BeginAndOver.getText() == "结束游戏") {//结束游戏
isRunning = false;
thread = null;
world.deleteAllCell();
showWorld();
BeginAndOver.setText("开始游戏");
StopAndContinue.setText("暂停游戏");
randomInit.setText("随机生成细胞");
NowGeneration.setText("当前代数:0");
} else if (e.getSource() == StopAndContinue && StopAndContinue.getText() == "暂停游戏") {//暂停
isRunning = false;
thread = null;
StopAndContinue.setText("继续游戏");
} else if (e.getSource() == StopAndContinue && StopAndContinue.getText() == "继续游戏") {//继续
isRunning = true;
thread = new Thread(new Runnable() {
@Override
public void run() {
while (isRunning) {
Change();
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
}
});
thread.start();
StopAndContinue.setText("暂停游戏");
} else if (e.getSource() == Next && StopAndContinue.getText() == "继续游戏") {//下一代
Change();
isRunning = false;
thread = null;
}else if(e.getSource()==Exit){//退出游戏
isRunning = false;
thread = null;
this.dispose();
System.exit(0);
}
}
public void Change(){
world.updateOfCell();
showWorld();
NowGeneration.setText("当前代数:"+world.getNowGeneration());
}
public void showWorld(){
for(int x=0;x<lx;x++){
for(int y=0;y<ly;y++){
if(world.getCellXY(x,y)){
TWorld[x][y].setBackground(Color.green);
}
else{
TWorld[x][y].setBackground(Color.white);
}
}
}
}
}
4.Main.java
public class Main {
private static GameGUI Game;
private static World world;
public static void main(String arg[]){
world=new World(20,30);
Game=new GameGUI("Game of Life",world);
}
}
游戏运行效果:
写在最后:
这是我的第一篇博文,我还是一个软件工程专业的小白,正在努力学习相关知识,希望对像我一样的同学们能有所帮助,也希望大佬们多多批评指导。在后续学习中,如果有什么好的文章、学习资源等,我也会分享给大家。