Java版连连看
Posted 迷失的小菜包
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java版连连看相关的知识,希望对你有一定的参考价值。
连连看大家应该都玩过,不多说直接上一个做好的界面截图吧,所有的功能都在上面的,要做的就只是如何去实现它们了。
差不多就是这个样子。先说一下大致的思路吧。首先编写基本的界面:把什么按钮啊,表格啊什么的都画上去。然后就是编写事件处理类,因为操作使用鼠标,所以加上鼠标监听。然后获取点击的坐标,根据坐标得出图片在数组中的位置。接着创建一个类,实现连连看消除的算法。这样就基本上可以开始游戏了。然后实现排行榜按钮和存档按钮的基本功能。最后加上一个线程类,用于处理倒计时。下面的介绍也基于这个顺序。
界面实现:这个其实没什么好说的,把JFrame的知识用上就好了。考虑到图片的闪烁问题,在界面类中重写paint方法,加上双缓冲(双缓冲不懂的,可以自行百度或者看看我写的Java版本2048)。所以就直接贴代码了。
package com.cbs.look; public interface LookConfig { int x=50;//初始x坐标,原来是10 int y=100;//初始y坐标,原来是50 int space=10;//图片间的间隔 int arc=50;//圆角矩形的弧度 int size=60;//图片的大小 int num=9;//图片类型 }
1 package com.cbs.look; 2 3 import java.awt.Color; 4 import java.awt.Font; 5 import java.awt.Graphics; 6 import java.awt.Graphics2D; 7 import java.awt.Image; 8 import java.awt.RenderingHints; 9 10 import javax.swing.ImageIcon; 11 import javax.swing.JButton; 12 import javax.swing.JFrame; 13 import javax.swing.JLabel; 14 import javax.swing.JOptionPane; 15 16 /** 17 * 连连看的主界面类 18 * 19 * @author CBS 20 * 21 */ 22 @SuppressWarnings("serial") 23 public class GameLook extends JFrame implements LookConfig { 24 25 private int[][] array = new int[8][8];// 数组用于保存界面的信息 26 27 JLabel timeJl;// 用于显示剩余时间 28 29 public static void main(String[] args) throws InterruptedException { 30 GameLook g = new GameLook(); 31 g.showUI(); 32 33 } 34 35 /** 36 * 初始化界面 37 * 38 * @throws InterruptedException 39 */ 40 public void showUI() throws InterruptedException { 41 setTitle("连连看"); 42 setSize(700, 800); 43 setDefaultCloseOperation(3); 44 setLocationRelativeTo(null); 45 setResizable(true); 46 setLayout(null); 47 48 // 添加新游戏按钮 49 // ImageIcon start = new ImageIcon("res/start.png"); 50 JButton startJB = new JButton("新游戏"); 51 startJB.setBounds(30, 700, 100, 40); 52 // startJB.setBorderPainted(false);// 设置边框为空 53 startJB.setFocusable(false); 54 // startJB.setContentAreaFilled(false);// 设置内容空 55 this.add(startJB); 56 // 添加排行榜按钮 57 JButton save = new JButton("排行榜"); 58 save.setFocusable(false); 59 save.setBounds(190, 700, 100, 40); 60 this.add(save); 61 62 // 添加存档按钮 63 JButton saveGame = new JButton("存档"); 64 saveGame.setFocusable(false); 65 saveGame.setBounds(320, 700, 100, 40); 66 this.add(saveGame); 67 68 69 // 添加剩余时间 70 JLabel jl = new JLabel("Time:"); 71 jl.setFont(new Font("", Font.BOLD, 20)); 72 jl.setBounds(440, 700, 80, 50); 73 this.add(jl); 74 75 // 显示剩余时间 76 timeJl = new JLabel("90"); 77 timeJl.setFont(new Font("", Font.BOLD, 20)); 78 timeJl.setBounds(520, 700, 80, 50); 79 this.add(timeJl); 80 81 setVisible(true); 82 83 GameListener gl = new GameListener(); 84 gl.setFrame(this); 85 gl.setTimeJl(timeJl); 86 gl.setArray(array); 87 saveGame.addActionListener(gl); 88 startJB.addActionListener(gl); 89 save.addActionListener(gl); 90 91 int i=JOptionPane.showConfirmDialog(this, "是否读取上次的存档", "读档", 92 JOptionPane.YES_NO_OPTION); 93 if(i==1){ 94 JOptionPane.showMessageDialog(this, "请按新游戏开始游戏吧!"); 95 }else{ 96 GameSave2 gs2=new GameSave2(); 97 CunD c=gs2.opean(); 98 if(c!=null){ 99 array=c.getArray(); 100 gl.setArray(array); 101 this.addMouseListener(gl); 102 this.repaint(); 103 TimeOut tt =new TimeOut(timeJl, this, gl); 104 gl.setTt(tt); 105 tt.setSeconds(c.getTime()); 106 tt.start(); 107 }else{ 108 JOptionPane.showMessageDialog(this, "读取失败!"); 109 } 110 } 111 112 113 } 114 115 @Override 116 public void paint(Graphics g) { 117 super.paint(g); 118 buffPaint(g); 119 } 120 121 /** 122 * 使用双缓冲技术解决闪屏问题 123 * 124 * @param g传入的画笔对象 125 */ 126 public void buffPaint(Graphics g) { 127 Image i = createImage(space + (size + space) * array[0].length, space 128 + (size + space) * array.length); 129 Graphics2D g2d = (Graphics2D) i.getGraphics(); 130 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 131 RenderingHints.VALUE_ANTIALIAS_ON); 132 // 绘制背景矩形 133 g2d.setColor(new Color(210, 180, 140)); 134 g2d.fillRoundRect(0, 0, space + (size + space) * array[0].length, space 135 + (size + space) * array.length, arc, arc); 136 // 绘制背景方格 137 g2d.setColor(new Color(245, 245, 220)); 138 for (int r = 0; r < array.length; r++) { 139 for (int c = 0; c < array[r].length; c++) { 140 g2d.fillRect(space + (size + space) * c, space + (size + space) 141 * r, size, size); 142 } 143 } 144 // 绘制图片 145 g2d.setColor(Color.BLUE); 146 g2d.setFont(new Font("宋体", Font.BOLD, 30)); 147 for (int r = 0; r < array.length; r++) { 148 for (int c = 0; c < array[r].length; c++) { 149 if (array[r][c] != 0) { 150 ImageIcon icon = new ImageIcon("res/" + array[r][c] 151 + ".jpg"); 152 Image image = icon.getImage(); 153 g2d.drawImage(image, space + (size + space) * c, space 154 + (size + space) * r, size, size, null); 155 } 156 } 157 } 158 g.drawImage(i, x, y, this); 159 } 160 }
事件处理类:鼠标的事件处理主要负责的是记录两次点击的坐标,然后判断是否能够把两个图片消除,如果可以消除图片把对应的数组位置的数置为0,然后重绘画板,如果不行同样重绘画板消除选框及连线。动作的事件处理主要负责实现不同的按钮的功能。
package com.cbs.look; import java.awt.BasicStroke; import java.awt.Color; import java.awt.FlowLayout; import java.awt.Font; import java.awt.Graphics2D; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.List; import java.util.Random; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.plaf.FontUIResource; /** * 事件处理类 * @author CBS */ public class GameListener extends MouseAdapter implements LookConfig, ActionListener { // 用于控制坐标的获取 private boolean flag = true; private int r1, c1, r2, c2;// 对应数组的下标位置 private int x1, y1, x2, y2;// 鼠标点击的坐标 private int array[][];// 保存数组 private JFrame frame;// 用于获取窗体对象,调用Repaint方法 private Graphics2D g;// 画笔对象 JLabel timeJl;// 用于显示剩余时间 TimeOut tt ;// 倒计时线程类 private int x;// 保存画框的顶点x坐标 private int y;// 保存画框的顶点y坐标 public TimeOut getTt() { return tt; } public void setTt(TimeOut tt) { this.tt = tt; } public void setTimeJl(JLabel timeJl) { this.timeJl = timeJl; } public void setFrame(JFrame frame) { this.frame = frame; g = (Graphics2D) frame.getGraphics(); } public void setArray(int[][] array) { this.array = array; } @Override public void mousePressed(MouseEvent e) { // 获取坐标 if (flag) { x1 = e.getX() - 40; y1 = e.getY() - 50; flag = false; if (y1 / (size + space) - 1 >= array.length) r1 = array.length - 1; else if (y1 / (size + space) - 1 < 0) r1 = 0; else r1 = y1 / (size + space) - 1; if (x1 / (size + space) >= array[0].length) c1 = array[0].length - 1; else c1 = x1 / (size + space); g.setColor(Color.RED); g.setStroke(new BasicStroke(5)); x = space + space + c1 * (size + space) + 40; y = size + r1 * (size + space) + 50; g.drawRect(x, y, size, size); } else { x2 = e.getX() - 40; y2 = e.getY() - 50; flag = true; if (y2 / (size + space) - 1 >= array.length) r2 = array.length - 1; else if (y1 / (size + space) - 1 < 0) r1 = 0; else r2 = y2 / (size + space) - 1; if (x2 / (size + space) >= array[0].length) c2 = array[0].length - 1; else c2 = x2 / (size + space); g.setColor(Color.RED); g.setStroke(new BasicStroke(4)); x = space + space + c2 * (size + space) + 40; y = size + r2 * (size + space) + 50; g.drawRect(x, y, size, size); } GameUtil gu = new GameUtil(this.frame); if (array[r1][c1] == array[r2][c2] && flag && !(r1 == r2 && c2 == c1) && (array[r1][c1] != 0 || array[r2][c2] != 0)) { if (gu.wuZhe(r1, c1, r2, c2, array)) { array[r1][c1] = 0; array[r2][c2] = 0; g.setColor(Color.PINK); g.drawLine(2 * space + size / 2 + c2 * (size + space) + 40, size + size / 2 + r2 * (size + space) + 50, 2 * space + size / 2 + c1 * (size + space) + 40, size + size / 2 + r1 * (size + space) + 50); } else if (gu.yiZhe(r1, c1, r2, c2, array)) { array[r1][c1] = 0; array[r2][c2] = 0; g.setColor(Color.PINK); g.drawLine(2 * space + size / 2 + gu.getPath().get(0).y * (size + space) + 40, size + size / 2 + gu.getPath().get(0).x * (size + space) + 50, 2 * space + size / 2 + c1 * (size + space) + 40, size + size / 2 + r1 * (size + space) + 50); g.drawLine(2 * space + size / 2 + gu.getPath().get(0).y * (size + space) + 40, size + size / 2 + gu.getPath().get(0).x * (size + space) + 50, 2 * space + size / 2 + c2 * (size + space) + 40, size + size / 2 + r2 * (size + space) + 50); } else if (gu.erZhe(r1, c1, r2, c2, array)) { array[r1][c1] = 0; array[r2][c2] = 0; g.setColor(Color.PINK); g.drawLine(2 * space + size / 2 + gu.getPath().get(1).y * (size + space) + 40, size + size / 2 + gu.getPath().get(1).x * (size + space) + 50, 2 * space + size / 2 + c1 * (size + space) + 40, size + size / 2 + r1 * (size + space) + 50); // path的下标为一的位置要减一,因为数组扩大了 g.drawLine(2 * space + size / 2 + (gu.getPath().get(0).y - 1) * (size + space) + 40, size + size / 2 + (gu.getPath().get(0).x - 1) * (size + space) + 50, 2 * space + size / 2 + gu.getPath().get(1).y * (size + space) + 40, size + size / 2 + gu.getPath().get(1).x * (size + space) + 50); g.drawLine(2 * space + size / 2 + (gu.getPath().get(0).y - 1) * (size + space) + 40, size + size / 2 + (gu.getPath().get(0).x - 1) * (size + space) + 50, 2 * space + size / 2 + c2 * (size + space) + 40, size + size / 2 + r2 * (size + space) + 50); } //实现消除控制重绘的刷新时间 Thread t=new Thread(); try { t.sleep(100); frame.repaint(); } catch (InterruptedException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } if (isWin(array)) { tt.setFlag(false); frame.removeMouseListener(this); JOptionPane.showMessageDialog(frame, "恭喜你," + "你赢了!!请点击新游戏开始新一局"); int i = JOptionPane.showConfirmDialog(frame, "是否记录将你的信息记入排行榜", "排行榜", JOptionPane.YES_NO_OPTION); if (i == 0) { String str = JOptionPane.showInputDialog(frame, "请输入你的名字", "排行榜", JOptionPane.YES_NO_OPTION); int time=90-tt.getSeconds(); User u = new User(str, time); GameSave gs = new GameSave(); gs.save(u); } } } //未实现消除,重绘去掉线条 if (flag) { Thread t=new Thread(); try { t.sleep(100); frame.repaint(); } catch (InterruptedException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } } } // 按钮动作监听 public void actionPerformed(ActionEvent e) { String str = e.getActionCommand(); if ("新游戏".equals(str)) { for (int r = 0; r < array.length; r++) for (int c = 0; c < array[r].length; c++) if (array[r][c] != 0) { array[r][c] = 0; } if(tt!=null){ if(tt.isFlag()){ frame.removeMouseListener(this); tt.setFlag(false); } } randomData(); frame.repaint(); frame.addMouseListener(this); // 启动线程 tt = new TimeOut(timeJl, frame, this); if(!tt.isFlag()) tt.setFlag(false); tt.start(); } if ("排行榜".equals(str)) { GameSave gs = new GameSave(); List<User> list = gs.opean(); for (int i = 0; i < list.size(); i++) { int flag = i; for (int j = i + 1; j < list.size(); j++) { if (list.get(i).getTime() > list.get(j).getTime()) flag = j; } if (flag != i) { User u1 = list.get(i); User u2 = list.get(flag); list.set(i, u2); list.set(flag, u1); } } JFrame jf = new JFrame(); jf.setTitle("排行榜"); jf.setDefaultCloseOperation(2); jf.setSize(300, 500); FlowLayout fl = new FlowLayout(FlowLayout.LEFT); jf.setLayout(fl); jf.setLocationRelativeTo(null); for (int i = 0; i < list.size(); i++) { JLabel jl = new JLabel(list.get(i).toString()); jl.setFont(new FontUIResource("楷体", Font.BOLD, 20)); jf.add(jl); } jf.setVisible(true); } if("存档".equals(str)){ System.out.println(23333); GameSave2 gs2=new GameSave2(); int time=tt.getSeconds(); CunD c=new CunD(array, time); boolean is=gs2.save(c); if(is) JOptionPane.showMessageDialog(frame, "存档成功!"); else JOptionPane.showMessageDialog(frame, "存档失败!"); } } /** * 生成随机数字 */ public void randomData() { Random random = new Random(); int r1, r2, c1, c2; for (int i = 0; i < array.length * array[0].length / 2; i++) { do { r1 = random.nextInt(array.length); c1 = random.nextInt(array[r1].length); } while (array[r1][c1] != 0); array[r1][c1] = random.nextInt(num) + 1; do { r2 = random.nextInt(array.length); c2 = random.nextInt(array[r2].length); } while (array[r2][c2] != 0); array[r2][c2] = array[r1][c1]; } } //遍历数组,判断输赢 public boolean isWin(int[][] array) { for (int r = 0; r < array.length; r++) for (int c = 0; c < array[r].length; c++) if (array[r][c] != 0) return false; return true; } }
这里的图片我使用的是直接绘制图片,而不是通过按钮,所以坐标的判断有些麻烦。数组的下标取得是通过鼠标坐标整除方格的边长加间隔,然后由数组下标取得屏幕坐标则相反。初始数据是由randomData方法生成,不同的数字对应不同的图片。
连连看的算法:这里我使用的算法是比较容易理解和实现的分类算法,据说还有一种比较厉害的算法叫广度优先搜索,那个我不会,所以就只能用这种了。先说说连连看的规则吧,就是用不超过两次拐弯的直线能够相连就能够消除。这样分类算法就很好理解了,按照消除要拐弯的次数分为无折相连,一折相连和二折相连。首先是无折相连这个很好理解,要么他们左右相邻或者是上下相邻,要么就是同一行,两个方快中间没有阻隔或者是同一列中间没有阻隔。
就像上面的图,左边是不相邻的,右边是相邻的,这两种都属于无折的相连。然后是一折的相连。一折的相连就是拐一个弯,先看看示意图把:
其实无论是哪种情况,能够实现一折相连的方块都是在矩形的两个对顶角,所以只要判断矩形的另外两个对顶角是否能够实现无折相连就行了。最后是二折相连,同样是先看示意图:
二折的情况看似复杂,其实只要在水平方向上和垂直方向上分别进行遍历,如果是空格就判断这个空格是否能够和另一个格子一折相连就行了。其实整个算法有点像是递归,一折调用无折,二折调用一折。算法的思路大概就是这样。然后就上代码吧:
package com.cbs.look; import java.awt.Point; import java.util.ArrayList; import java.util.List; import javax.swing.JFrame; /** * 核心算法 * 判断两个方块是否联通 * @author CBS * */ public class GameUtil implements LookConfig { //path主要是记录下相连的数组的位置,为了方便实现连线的功能 private List<Point> path=new ArrayList<Point>(); public List<Point> getPath() { return path; } public GameUtil(JFrame frame) { } /** * 无折算法,无折的情况,要么同行,判断列是否连通;要么同列判断行是否连通 * * @param r1第一个方块行下标 * @param c1第一个方块列下标 * @param r2第二个方块行下标 * @param c2第二个方块列下标 * @param array用于保存数组的信息 * @return 如果能够连通返回TRUE,or返回FALSE */ public boolean wuZhe(int r1, int c1, int r2, int c2, int[][] array) { if (r1 != r2 && c1 != c2) return false; // 如果两点的x坐标相等,则在水平方向上扫描 if (r1 == r2) { if (c1 == c2 - 1 || c2 == c1 - 1)// 列相邻 return [vscode]--HTML代码片段(基础版,reactvuejquery)