?
?连连看项目
一、前期准备:
1、若干种大小相同的图片,图片最好以数字命名,这样便于随机参数图片。
2、数据结构
由于我们需要每次根据鼠标的坐标定位每张图片位置,所以我们每张图片的大小需要固定
而且采用数组来存放图片,这样容易根据鼠标坐标来定位图片的下标。
3、对于一些固定的数据,最好写在一个接口中,或者写在XML文件中,写在xml文件中
便于程序发布之后对数据的更改,写在接口中,每个类只需要实现这个接口,就可以获得
这个接口中的所有配置数据,比如,行数、列数、图片大小、起始点的坐标等等数据。
4、设计要求:不大于两个弯道可以联通就可以消除,需要画出消除的路径,需要提示当前选中的图片。
二、实现步骤:
1、给图片数组赋值,每种图片都是偶数个的实现:
首先需要明白,练练看中的图片必须是成对出现,每一种图片的个数必须是偶数。
(1、)我们利用一个队列来存放我们需要画在游戏面板中的图片,队列的自动增减长度的特性我们接下来需要用到,所以这里需要用队列。
第一步:随机产生图片,每次产生一个图片,我们都将它往队列中插入两次,这样就保证了每种图片的个数是偶数。
ArrayList<ImageIcon> arrs = new ArrayList<ImageIcon>(); Random random= new Random(); for (int i = 0; i < ROWS * COLS / 2; i++) { int num = random.nextInt(5); // 构建图片路径 String str = "images/" + num + ".gif"; ImageIcon image = new ImageIcon(str); // 将图片放入队列,每次放两次 arrs.add(image); arrs.add(image); }
?第二步:我们在游戏面板中画出图片的时候,每次从队列中随机取出一张图片,这个随机数的大小就是队列的大小,取出后我们就从队列中将它移除,这样队列的长度就减小了,我们下次再从队列的大小来产生随机数,这样就实现了随机取出。
?2、在游戏面板中画出图片数组中的图片。
注意:这里需要根据图片的下标,来计算出图片的左上角的坐标。
?3、消除的实现
?? (1)同行的消除,鼠标每点击一次,就根据鼠标的坐标算出该图片的下标,当点击第二次的时候,将两个图片进行对比,先看是不是同一行,也就是r值否相同,再看两个图片是否是同一种类型的图片,再判断这两张图片中间的所有图片是不是为空(我们消除图片,就是将图片数组中对应的图片对象赋值为空)
注意:如果两次点击的是同一张图片,我们也不做处理
下面的代码,先判断鼠标是不是在有效范围内点击,而且两次点击的是不是同一图片,如果满足,我们再调用比较函数,比较这两个图片再同一行是不是能够消除。
//得到事件源,由于监听器是加载在游标面板上,所以事件源就是面板 final GamePanel gp = (GamePanel) e.getSource(); x1=e.getX(); y1=e.getY(); //每次选中一个图片都,在这个图片外围画一个框,提示该图片被选中 //得到该图片左上角的坐标 if((x1>=X0&&y1>=Y0)&&(x1<=(COLS*SIZE+X0)&&y1<=(ROWS*SIZE+Y0))){ System.out.println("点"+x1); System.out.println("边际"+(COLS*SIZE+X0)); int xp=((x1-X0))/SIZE*SIZE+X0; int yp=((y1-X0))/SIZE*SIZE+Y0; g.drawRect(xp, yp, SIZE, SIZE); //第一次 if(count==0){ icon1=getImage(x1,y1); //根据坐标获得数组下标,L,R C01=(x1-X0)/SIZE; R01=(y1-Y0)/SIZE; count=1; }else{ x2=e.getX(); y2=e.getY(); icon2=getImage(x2,y2); //通过x算出的是第几列 C02=(x2-X0)/SIZE; R02=(y2-Y0)/SIZE; //如果图标都不为空,且是相同图片,则让这两张图片为空。 if((C01!=C02||R01!=R02)&&icon1!=null&&icon2!=null&&icon1.toString().equals(icon2.toString())){ if(Suanfa.checkCol(C01, R01, C02, R02)||Suanfa.checkRow(C01, R01, C02, R02) ||Suanfa.checkZhuan01(C01, R01, C02, R02) ||Suanfa.checkZhuan02(C01, R01, C02, R02) ){ //启动一个线程来消除图片 //先将连接线画出来,再将图片消除 for(int i=0;i<GamePanelListener.list.size();i+=2){ Point p1=GamePanelListener.list.get(i); Point p2=GamePanelListener.list.get(i+1); g.drawLine(p1.x, p1.y, p2.x, p2.y); } //将队列清空 GamePanelListener.list.clear(); try { Thread.sleep(1000); } catch (Exception e1) { // TODO Auto-generated catch block e1.printStackTrace(); } images[R01][C01]=null; images[R02][C02]=null; icon1=null; icon2=null; gp.repaint(); } } gp.repaint(); count=0; } }
?
检查再同一行是不是能够消除:
//参数是数组的下标 public static boolean checkRow(int C1,int R1,int C2,int R2){ //同一行,说明是x的下标相同 if(R1==R2){ //找出y1和y2的最大值和最小值 int min = Math.min(C1, C2); int max = Math.max(C1, C2); //从min+1到max-1遍历 for(int i =min+1;i<max;i++){ //如果有不为空,直接返回false,如果,这里一直没有返回false,就说明都是空,最后返回true if(images[R1][i]!=null){ return false; } } //每次将可以消除的行加入队列 //这里将下标转为图片中心点的坐标 int x1=X0+C1*SIZE+SIZE/2; int y1=Y0+R1*SIZE+SIZE/2; int x2=X0+C2*SIZE+SIZE/2; int y2=Y0+R2*SIZE+SIZE/2; GamePanelListener.list.add(new Point(x1,y1)); GamePanelListener.list.add(new Point(x2,y2)); System.out.println("行可消除"); return true; } return false; }
?检查在同一列是不是可以消除:
public static boolean checkCol(int C1,int R1,int C2,int R2){ //同一行,说明是x的下标相同 if(C1==C2){ //找出y1和y2的最大值和最小值 int min = Math.min(R1, R2); int max = Math.max(R1, R2); //从min+1到max-1遍历 for(int i =min+1;i<max;i++){ //如果有不为空,直接返回false,如果,这里一直没有返回false,就说明都是空,最后返回true if(images[i][C1]!=null){ return false; } } int x1=X0+C1*SIZE+SIZE/2; int y1=Y0+R1*SIZE+SIZE/2; int x2=X0+C2*SIZE+SIZE/2; int y2=Y0+R2*SIZE+SIZE/2; GamePanelListener.list.add(new Point(x1,y1)); GamePanelListener.list.add(new Point(x2,y2)); System.out.println("列可消除"); return true; } return false; }
?
4、如果同行同列可以消除了,下面的也就简单了。
首先是转一个弯道的情况,其实也就是在比较三个点是不是两两可以同行或者同列可以消除,
关键是我们要找出那个转折点,转一个弯道,那个转折点,应该和需要消除的两个点,在同行或者同列,
也就是一个矩形的另外两个,只需要将这两个点分别拿来和需要消除的点比较是不是同列可消除并且同行可以消除,并且自己是为空的,那么就实现了转一个弯道。
// 有一个转折点的 public static boolean onePoint(int r1, int c1, int r2, int c2) { // 转折点为r1c2 if (ICONS[r1][c2] == null) { if (checkRow(r1, c1, r1, c2) && checkCol(r2, c2, r1, c2)) { return true; } } // 转折点为r2c1 if (ICONS[r2][c1] == null) { if (checkRow(r2, c2, r2, c1) && checkCol(r1, c1, r2, c1)) { return true; } } GamePanelListener.list.clear(); return false; }
?5、转两个弯道,转两个弯道分为水平的情况和垂直的情况,
水平的时候,两个转折点和需要消除的两个点在同一行,这两个转折点是在同一列的,这两个转折点从最左边一起平移到最右边,比较完所有情况。
// 水平移动 public static boolean twoPointH(int r1, int c1, int r2, int c2) { // 定义一个变量表示两个转折点的列下标 for (int i = 0; i < COLS; i++) { // 转折点为r1,i和r2,i if (ICONS[r1][i] == null && ICONS[r2][i] == null) { if (checkRow(r1, i, r1, c1) && checkRow(r2, i, r2, c2) && checkCol(r1, i, r2, i)) { return true; } } } GamePanelListener.list.clear(); return false; }
?垂直的也是一样的,转折的两个点在同一样,从上到下平移所有情况。
// 垂直移动 public static boolean twoPointV(int r1, int c1, int r2, int c2) { for (int i = 0; i < ROWS; i++) { if (ICONS[i][c1] == null && ICONS[i][c2] == null) { if (checkRow(i, c1, i, c2) && checkCol(r1, c1, i, c1) && checkCol(r2, c2, i, c2)) { return true; } } } GamePanelListener.list.clear(); return false; }
?
6、画出连接路径:
?每次在可以消除的时候,都是比较了两个点同行或者同列的时候,如果可以消除,每次比较的这个两个点是必须要画线的,那么我们每次将这两个点添加到队列中,画线的时候就画出这两个点之间的线
比如1,2和3,4? 但是注意2,3不能画线,因为,1,2是一个比较组,3,4是一个比较组,2,3之间是不需要画线的。
还要特别注意一点,我们比较行或者列可以消除之后就将对于的点加到了队列,但是比如需要转两个点,我们就需要判断三次,如果前面两次满足,后面一个不满足,那么我们就将不满足的也添加到了画线的点的队列中,这样就会出错,所以,我们在判断其转一个点和两个点的时候,如果最后的结果是不满足,那么我们就要将画线的点的队列清空,清楚掉刚才满足部分条件添加到队列中的点。
这样只需要在检查行和列中添加一段代码,
int x1=X0+C1*SIZE+SIZE/2; int y1=Y0+R1*SIZE+SIZE/2; int x2=X0+C2*SIZE+SIZE/2; int y2=Y0+R2*SIZE+SIZE/2; GamePanelListener.list.add(new Point(x1,y1)); GamePanelListener.list.add(new Point(x2,y2));
?在一个转折点中和两个转折点中添加一段代码。
GamePanelListener.list.clear();
?7、提示框,这个只需要根据鼠标坐标计算出该图片左上角的点,在画出一个矩形框就可以了
?
原文:http://376798041.iteye.com/blog/2154288