一、整体思路
该程序实现了最简单的一个行人疏散模型,程序模拟行人从一个带有出口的封闭房间疏散,墙壁上有疏散方向标志,行人先移动到墙底,然后按照疏散方向往出口移动。
创建一个矩阵,空位设置为0,行人设置为1,墙壁设置为100,出口设置为1000,墙壁和出口的位置手工设置,本程序墙壁为于场地四周,出口位于上方墙壁的中点。行人的位置由随机产生或者手工设定。
具体规则如下:
1、 房间长m,宽n,行人密度p。
2、 行人在墙壁之外的无法看到出口,只能看到墙。
3、 行人会向离自己最近的墙面移动。
4、 墙面上有疏散方向。
5、 如果2人的下一点位置重合,各有50%的几率占据位置,另一个人则原地等待一步。
6、 行人到达出口时从系统移除。
二、具体程序
(一)、绘图类img.java
在绘图类的构造方法,完成界面的初始化,窗口的大小,关闭按钮的作用,将JPanel作为与矩阵相对应的场地。
public Img (){ //构造方法 jf = new JFrame("CA"); jp = new JPanel[m] [n]; jf.setLayout(new GridLayout(m,n,5,5));//mn 5 5 长m宽n 行间距5 列间距5 for (int i=0;i<m;i++){ for (intj=0;j<n;j++){ jp[i][j]=new JPanel(); jp[i][j].setBackground(Color.black);//布置背景色 jf.add(jp[i][j]);//将jp添加到jf } } jf.setSize(500,500); //窗口大小 jf.setVisible(true);//显示窗口 jf.addWindowListener(newWindowAdapter() {//关闭按钮的作用 public voidwindowClosing(WindowEvent e) { System.exit(0); } }); }
然后是创建行人位置的方法,也就是在矩阵中产生行人、墙壁、出口的方法。产生行人可以用随机方法creatRomNum,也可以用指定方法creatRegNum。
public int [][] creatRegNum(inta[][]){ //手工 创建矩阵 for(int i=0;i<m;i++){ for (intj=0;j<n;j++){ a[0][j]=100;//设置边缘 墙的值为100 a[i][0]=100; a[i][m-1]=100; a[n-1][j]=100; } } for(int i=1;i<m-1;i++){ for(int j=1;j<n-1;j++){ a[i][i]=0; } } a[3][2]=1; //a[3][2]=1; //a[5][5]=1; a[data.CX][data.CY]=1000;//出口 returna; } public int [][] creatRomNum(inta[][]){//随机创造矩阵 for (inti=0;i<m;i++){ for (intj=0;j<n;j++){ a[0][j]=100;//设置边缘 墙的值为100 a[i][0]=100; a[i][m-1]=100; a[n-1][j]=100; } } for (inti=1;i<m-1;i++){ for (intj=1;j<n-1;j++){ if(Math.random()>p){ a [i][j]=1;//行人代表1 } else{ a [i][j]=0;//空位代表0 } } } a[data.CX][data.CY]=1000;//设置出口 值为1000 return a; }
最后时绘图方法onColor,根据矩阵不同的数值设为不同的颜色。
public void onColor(inta[][]){ //绘图方法 for(int i=0;i<data.m;i++){ for(int j=0;j<data.n;j++){ switch(a[i][j]){//对应矩阵的值添加不同的颜色 case 0: //空地为0 白色 jp[i][j].setBackground(Color.white); break; case 1: //人为1 红色 jp[i][j].setBackground(Color.red); break; case 100: //墙为100 蓝色 jp[i][j].setBackground(Color.blue); break; case 1000: //出口为1000 黄色 jp[i][j].setBackground(Color.yellow); break; default: //其他为黑色 一般用不到 jp[i][j].setBackground(Color.black); } } } }
(二)、行人类Peo.java
行人类包含移动方法peoMove,行人具体移动方法peoMoveDirection,判断下一点是否有人方法peoMoveDirectionDecect和行人到达出口方法peoMoveExit。
行人类中最为重要的就是移动方法peoMove。在此方法中,用到了3个数组:a[][],c[][]和d[][]。其中a是由绘图类产生并传进来的,用作判断。C是a的复制,采用循环赋值,在做出移动判断后将更改c的值。在移动之前,首先要判断:获取下一点移动的方向,下一点是否为墙或者人,下一点如果墙的话如何处理。最后交给具体移动方法peoMoveDirection来实现。
public int [][] peoMove(inta [][]){ int c[][]=new int [data.m][data.n]; int fr;//判断下一点为墙 int f;//移动方向 boolean moveOk; for (int i=0;i<data.m;i++){ //循环赋值 for (int j=0;j<data.n;j++){ c[i][j]=a[i][j]; } } for (int i=1;i<data.m-1;i++){ for(int j=1;j<data.n-1;j++){ //在进行移动之前先判断下一点是否能走 //用d的值来判断 f=rule.calRange(i, j, data.m, data.n,data.r);//判断方向 if(a[i][j]==1){ fr=rule.stopChange(a, i, j); if(fr==2){//判断墙 f=rule.calRangeDouble(i,j); moveOk=peoMoveDirectionDecect(f,i, j, c); if(moveOk){ c=peoMoveDirection(f,i, j, c); } else{ c=peoMoveDirection(5,i, j, c); } } else{ c=peoMoveDirection(f,i, j, c); } } } } return c; }
行人具体移动方法peoMoveDirection和判断下一点是否有人方法peoMoveDirectionDecect大概差不多。peoMoveDirection方法是将下一点设置为1,将原来的点设置为0;peoMoveDirectionDecect是判断下一点是否为1,如果是,则返回false。
public int[][] peoMoveDirection(intf,int i,int j,int c [][]){ //行人移动 传入方向f 改变下一点的值 switch(f){ case 1: c[i-1][j-1]=1; c[i][j]=0; break; case 2: c[i-1][j]=1; c[i][j]=0; break; case 3: c[i-1][j+1]=1; c[i][j]=0; break; case 4: c[i][j-1]=1; c[i][j]=0; break; case 5: c[i][j]=1; break; case 6: c[i][j+1]=1; c[i][j]=0; break; case 7: c[i+1][j-1]=1; c[i][j]=0; break; case 8: c[i+1][j]=1; c[i][j]=0; break; case 9: c[i+1][j+1]=1; c[i][j]=0; break; } return c; } public booleanpeoMoveDirectionDecect(int f,int i,int j,int c[][]){ //判断行人下一点移动 传入方向f 如果下一点数值为1flag为false boolean flag=true; switch(f){ case 1: if(c[i-1][j-1]==1){ flag=false; } break; case 2: if(c[i-1][j]==1){ flag=false; } break; case 3: if(c[i-1][j+1]==1){ flag=false; } break; case 4: if(c[i][j-1]==1){ flag=false; } break; case 5: c[i][j]=1; break; case 6: if(c[i][j+1]==1){ flag=false; } break; case 7: if(c[i+1][j-1]==1){ flag=false; } break; case 8: if(c[i+1][j]==1){ flag=false; } break; case 9: if(c[i+1][j+1]==1){ flag=false; } break; } return flag; }
行人到达出口方法peoMoveExit,如果检测到行人在出口,则将行人移除系统。(这里为什么不整合到移动方法中呢?在主方法中,所有步骤运行后会有个赋值语句,将此次的结果覆盖原来的矩阵。如果写到移动方法中,会使出口方法失效)
public int [][] peoMoveExit(inta[][]){ //行人到达出口方法如果出去 设为0 boolean exitOk=false; int c[][]=new int [data.m][data.n]; for (int i=0;i<data.m;i++){ //循环赋值 for (int j=0;j<data.n;j++){ c[i][j]=a[i][j]; } } for(int i=0;i<data.m;i++){ for(int j=0;j<data.n;j++){ if(a[i][j]==1){ exitOk=rule.exitDose(a, i, j); if(exitOk){ c[i][j]=0; System.out.println("跑出去一个"); } } } } return c; }
(三)、规则类Rule.Java
规则类包含方向判断方法calRange,遇到墙壁的方向判断calRangeDouble,判断墙壁方法stopChange和判断出口方法exitDose。
判断方法calRange,将行人距离四周墙壁的长度存入array中,然后用sort方法排序,取出第一个值,于4个方向的值相比较完成赋值。
public int calRange(int i,intj,int m,int n,int R){ //判断方向的方法 //将计算[i][j]与四周墙的距离 存入数组 使用sort方法进行排序 //首先判断距离出口长度 //其此将数组的最小值分别与4个值比较 得到具体方向 int f=5; int north=i; int east=m-j; int south=n-i; int west=j; int ran[]=newint[4]; ran[0]=north; ran[1]=east; ran[2]=south; ran[3]=west; Arrays.sort(ran); if(ran[0]==north){ f=2; } else if(ran[0]==east){ f=6; } else if(ran[0]==south){ f=8; } else if(ran[0]==west){ f=4; } return f; }
遇到墙如何走方法calRangeDouble,根据位置不同,直接指定方法。
public int calRangeDouble(inti,int j){ //沿着墙走时 判断遇到拐角的方法 intf=0;//输出方向 if(i==1){//如果行人在最上面 if(j==1){//如果在在左边 f=6;//方向向右 } elseif(j==data.n-2){//如果在最右边 f=4; } else{ if(j>=data.n/2){//如果在右半部分 f=4; } else{//左半边 f=6; } } } if(i==data.m-2){//在最下面 if(j==1){//最左边 f=2; } elseif(j==data.n-2){//最右边 f=2; } else{ if(j>=data.m/2){//右半部分 f=6; } else{//左边部分 f=4; } } } if(j==1){//在左边 if(i==1){//左上角 f=6; } else{ f=2; } } if(j==data.n-2){//在右边 if(i==1){//有上交 f=4; } else{ f=2; } } returnf; }
判断墙的方法stopChange,遇到墙壁返回2。
public int stopChange(inta[][],int i,int j){ //判断墙 intfr=0; //fr 为判断下一步是否为墙 if(i==1||j==1||i==data.n-2|| j==data.m-2 ){ fr=2;//fr=2 表示下一点为墙 } returnfr; }
判断出口方法exitDose,遇到出口返回TRUE。
public boolean exitDose(inta[][],int i,int j){ //判断是否到达出口 如果到达出口 flag改为TRUE booleanflag=false; intexitId=data.n/2; if(i==1){ if(j==exitId){ flag=true; } if(j==exitId+1){ flag=true; } if(j==exitId-1){ flag=true; } } returnflag; }
(四)、主方法
public class RunMain { public static voidmain(String[] args) { Data data=new Data(); Img img=new Img(); Peo peo=new Peo(); int a[][]=new int[data.m][data.n]; int c[][]=new int[data.m][data.n]; int d[][]=new int[data.m][data.n]; a=img.creatRomNum(data.a); img.onColor(a); try { Thread.sleep(15000); } catch (Exception e) { e.printStackTrace(); } for (int h=0;h<data.h;h++){ c=peo.peoMove(a); d=peo.peoMoveExit(c); img.onColor(d); for (int i=0;i<data.m;i++){ for (int j=0;j<data.n;j++){ a[i][j]=d[i][j]; } } try { Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } System.out.println("第"+h+"次"); } } }
三、总结
经过这次编程,我又重新拾起快遗忘的Java。相对于matlab来说,Java最大的优势就是面向对象,各种方法有机结合。但是对新手不友好,需要先学一大堆Java知识,而matlab入手简单,简单学习就可以使用。
本程序离预定目标还差不少,最关键的是对于行人碰撞的解决不太可行。在房间中间的视野盲区,行人应该按随机方向行走,于是就有可能有2人的下一点在同一位置上,此时2人占据这点的概率应该是50%,但是程序是通过遍历数组来完成绘图的。这就导致位于前面的人就可以优先选择。虽然也可以通过将行人的一下步存到数组f中,然后在对其进行标识,从而产生50%的概率,但是这样做有点麻烦,因此在我想不使用矩阵,直接通过坐标将图绘出来,采用多线程,希望可以解决这个问题。