游戏2048——那些年我们追过的脸萌
这是一款意外蹿红的小游戏,简单、益智,符合当代年轻人的气息,无须学习复杂的规则便可以得心应手,并且愈玩愈烈。简单介绍后,接下来就分析一下该游戏的界面实现和功能实现。
在界面的实现方面,我们需要考虑会用到哪些 API 类,分别如下:
- 布局:FlowLayout,BorderLayout;
- 组件:JFrame,JPanel,JButton,JTextField;
- 绘图:Graphics,Color,Image;
- 事件接口:KeyListener,KeyEvent,ActionListener,ActionEvent;
- 组件大小:Dimension;
- 菜单:JMenuBar,JMenu,JMenuItem。
首先,我们需要创建一个窗体,并且设置大小、题目、位置、可见等。然后整个界面应采用边框布局,分别是北边、南边和中间,而北边面板应采用流式布局来安排得分和最高分。北边面板需添加两个文本框以显示分数,并设置成不可更改的形式。
南边面板添加一个开始按钮使得游戏失败后还可以继续玩。中间面板需要一个四乘四的格子,我采用画矩形的方法,先画一个背景深粉色矩形,再在上面 通过画笔类用双层循环画出 16 个小矩形,然后需要在格子上绘制图片,我们采用 Image 抽象类来添加并显示图片。接着我们可以添加一个简单的菜单以及子菜单。最后我们需要在事件源文本框、按钮、中间面板分别添加监听器,使得可以监听动作的发 生和键盘的按下或释放。这样子一个 2048 游戏界面就大概实现了。顺便为自己提些注意事项:
- 第一,界面 Game2048 类继承了 JPanel 类,所以在写北边和南边面板方法的时候应该将 JFrame 作为参数。ps:其实我还是没懂为什么要继承 JPanel 类,有没有大神回复一下;
- 第二,JTextField 应该定义为全局变量,以便在设置分数的时候可以传递参数到功能实现的类里边;
- 第三,创建画笔对象应该在窗体可见之后;
- 第四,由于游戏主界面是表格形式,所以图片的存放应该设置为二维数组;
- 第五,绘制画图板的时候,应该调用父类的 paint ()方法;
- 第六,为了让键盘监听中间面板得以更好的实现,应该将文本框及按钮的焦点设置为 false。
import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.GridLayout; import java.awt.Image; import java.awt.LayoutManager; import java.awt.Rectangle; import java.awt.Shape; import java.awt.image.ImageObserver; import java.text.AttributedCharacterIterator; import java.util.Random; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JSeparator; import javax.swing.JTextField; public class Game2048 extends JPanel { int array[][]=new int[4][4]; private Random rand=new Random (); private int score; private JTextField jte=new JTextField ("得分:"); private JTextField jte1=new JTextField ("最高分:"); private JButton jbu=new JButton ("START"); private boolean flag=true; public static void main (String[] args) { Game2048 game =new Game2048(); game.initUI (); } public void initUI (){ JFrame jf=new JFrame (); jf.setTitle ("那些年我们追过的脸萌"); jf.setSize (435, 560); jf.setDefaultCloseOperation (3); jf.setLocationRelativeTo (null); jf.setLayout (new BorderLayout ()); initNorth (jf); initSouth (jf); initMenu (jf); this.setBackground (Color.WHITE); jf.add (this,BorderLayout.CENTER); jf.setVisible (true); this.setFocusable (true); Graphics g=this.getGraphics (); GameListener lis=new GameListener (this,g,array,jte,jte1); this.addKeyListener (lis); jbu.addActionListener (lis); } private void drawBackground (Graphics g){ g.setColor (new Color (255,155,255)); g.fillRoundRect (5, 5,410,410, 15, 15); g.setColor (new Color (255,181,255)); for(int i=0;i<4;i++) for(int j=0;j<4;j++) g.fillRoundRect (15+j*100,15+i*100,90,90, 15, 15); } private void drawRC (){ int name1=(rand.nextInt (2) +1)*2; int name2=(rand.nextInt (2) +1)*2; int r1=rand.nextInt (4); int c1=rand.nextInt (4); int r2=rand.nextInt (4); int c2=rand.nextInt (4); while(r1==r2&&c1==c2){ r2=rand.nextInt (4); c2=rand.nextInt (4); } array[r1][c1]=name1; array[r2][c2]=name2; System.out.println (r1 + " " + c1); System.out.println (r2 + " " + c2); } private void drawImage (Graphics g){ for(int r=0;r<4;r++){ for(int c=0;c<4;c++){ if(array[r][c]!=0){ Image image=new ImageIcon (this.getClass () .getResource (array[r][c]+".jpg")) .getImage (); g.drawImage (image,15+c*100, 15+r*100,90,90, null); } } } } public void paint (Graphics g){ super.paint (g); drawBackground (g); if(flag){ drawRC (); flag=false; } drawImage (g); } public void setFlag (boolean flag){ this.flag=flag; } public void initNorth (JFrame jf){ JPanel jpanel=new JPanel (); jpanel.setBackground (new Color (255,155,255)); jte.setPreferredSize (new Dimension (100, 30)); jte.setEditable (false); jpanel.add (jte); jte.setFocusable (false); jte1.setPreferredSize (new Dimension (100, 30)); jte1.setEditable (false); jpanel.add (jte1); jte1.setFocusable (false); jf.add (jpanel,BorderLayout.NORTH); } public void initSouth (JFrame jf){ JPanel jpanel=new JPanel (); jpanel.setBackground (new Color (255,155,255)); jpanel.add (jbu); jbu.setFocusable (false); jf.add (jpanel,BorderLayout.SOUTH); } public void initMenu (JFrame jf){ JMenuBar jme=new JMenuBar (); String [] array={"文件","编辑","查看","帮助"}; String [][] arrayItem={{"新建","打开","保存"},{"撤销","重复","剪切"},{"工具箱","颜料盒"},{"帮助主题","关于 2048"}}; for (int i=0;i<array.length;i++){ JMenu jm=new JMenu (array[i]); for(int j=0;j<arrayItem[i].length;j++){ if(!arrayItem[i][j].equals ("")){ JMenuItem jmenu=new JMenuItem (arrayItem[i][j]); jm.add (jmenu);} else{ JSeparator separ = new JSeparator (); jme.add (separ); } } jme.add (jm); } jf.setJMenuBar (jme); } }
在功能的实现方面,我们需要考虑事件的监听及游戏实现的算法。事件里的事件源是你的动作发生所在的组件上,事件监听器的添加方法是 add**Listener (**Listener l),事件处理类是接口的子类,因为接口不能创建对象,并且要实现事件处理类的所有方法,其中需要重写动作监听方法,从而获取开始按钮信息并设置开始,以 及设置分数、显示分数情况。根据简单的游戏规则,也就是1、开始时随机出现两个分数为 2 或 4 的图片2、按下键盘的上下左右键所有的图片随之移动,并且碰到相邻且相同的图片可以相加形成相应分数的图片,判断如果有相加或者移动就再随机生成一张分数 为 2 或 4 的图片3、相加后要设置分数的改变4、判断如果出现分数为 2048 的图片则跳出文本框提示玩家已经胜利了5、判断如果所有格子都已经满了,且相邻的所有格子都无法相加,则跳出文本框提示玩家已经输了,可以重新开始。
移动和相加部分是该游戏的精髓,所以进简单分析一下:如果是先移动再相加就会出现一个问题,就是相加完之后还需要再移动一次,才能保证上下左右键正常工作,所以我们采取先相加再移动的策略。
先判断当前格子是否不为0,如果不为 0 则判断是否相同再相加,不过这会出现一个问题,就是相邻的格子为0,但是再下一个格子与之相同,所以应该先判断相邻是否为空,如果为空则循环继续,如果不 为空则判断是否相同,如果相同便可以相加,如果不同则跳出循环;然后是移动问题,移动主要根据上下左右键,通过循环判断相应的下一个格子是否为空,如果是 空的格子则可以移动,也就是交换格子的内容。最后再提几个注意点:
- 第一,应该写一个构造函数,从而可以传递界面的一些参数;
- 第二,可以设置标志位 boolean flag = false 来判断是否有移动或者相加情况,从而选择是否继续执行一些方法;
- 第三,判断是否失败的方法中,需要判断两种情况,也就是格子是否都满了和是否不能再移动了,第一种情况中可以通过循环判断每个格子是否都不为0,然后再用一个计数器来累积判断次数,如果有 16 次也就是符合情况了,第二种情况中可以判断左上角的 9 个格子中与右边及下面相邻的格子是否有相同的,再判断第四列与下面相邻的格子中是否有相同的和第四行中与下面相邻的格子中是否有相同的,同样给一个计数器,如果最总计数结果为 0 次,则已经不能再移动,如果两种情况同时成立,则输了(这是顾大神教我的)。
import java.awt.Graphics; import java.awt.Image; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.util.Random; import javax.swing.ImageIcon; import javax.swing.JOptionPane; import javax.swing.JTextField; public class GameListener implements KeyListener, ActionListener { private Graphics g; private int[][] array; private Game2048 a; private boolean flag = false; private JTextField jte,jte1; public int count=0; private int max_score; private Random rand = new Random (); public GameListener (Game2048 a, Graphics g, int[][] array, JTextField jte,JTextField jte1) { this.a = a; this.g = g; this.array = array; this.jte = jte; this.jte1 = jte1; } public void actionPerformed (ActionEvent e) { if (e.getActionCommand () .equals ("START")) { // e.getSource ()返回的是一个对象(事件源) start (); jte.setText ("得分:"+count); } } public void start () { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { array[i][j] = 0; } } a.setFlag (true); a.paint (g); } public void keyTyped (KeyEvent e) { } public void keyPressed (KeyEvent e) { } private void randomAC () { Random rand = new Random (); int name = (rand.nextInt (2) + 1) * 2; int r = rand.nextInt (4); int c = rand.nextInt (4); while (array[r][c] != 0) { r = rand.nextInt (4); c = rand.nextInt (4); } array[r][c] = name; } public void stop (){ int t=0; int p=0; for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if(array[i][j]!=0){ t++; } } } for(int r=0;r<3;r++){ for(int c=0;c<3;c++){ if(array[r][c]==array[r][c+1]&&array[r][c]!=0){ p++;} if(array[r][c]==array[r+1][c]&&array[r][c]!=0){ p++ ;} } } for(int r=0;r<3;r++){ if(array[r][3]==array[r+1][3]&&array[r][3]!=0) { p++;} } for(int c=0;c<3;c++){ if(array[3][c]==array[3][c+1]&&array[3][c]!=0){ p++;} } if(t==16&&p==0){ JOptionPane.showMessageDialog (a, "您输了"); if(count>max_score){ max_score=count; count=0; jte1.setText ("最高分"+max_score); }else if(count<max_score){ count=0; } for (int r = 0; r < 4; r++) { for (int c = 0; c < 4; c++) { array[r][c] = 0; } } } } public void keyReleased (KeyEvent e) { stop (); int keyCode = e.getKeyCode (); switch (keyCode) { case 37:// 左键 relLeft (); int t=0; if (flag) { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if(array[i][j]!=0){ t++; } } } if(t!=16){ randomAC (); a.repaint (); } jte.setText ("得分:"+count); } break; case 38:// 上键 relUp (); int b=0; if (flag) { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if(array[i][j]!=0){ b++; } } } if(b!=16){ randomAC (); a.repaint (); } jte.setText ("得分:"+count); } break; case 39:// 右键 relRight (); int p=0; if (flag) { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if(array[i][j]!=0){ p++; } } } if(p!=16){ randomAC (); a.repaint (); } jte.setText ("得分:"+count); } break; case 40:// 下键 relDown (); int q=0; if (flag) { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if(array[i][j]!=0){ q++; } } } if(q!=16){ randomAC (); a.repaint (); } jte.setText ("得分:"+count); } break; } if(count==2048){ JOptionPane.showMessageDialog (a, "您的分数达到 2048,您赢了"); } } public void relLeft () { for (int r = 0; r < 4; r++) { for (int c = 0; c < 4; c++) { if (array[r][c] != 0) { for (int c1 = c + 1; c1 < 4; c1++) { if (array[r][c1] == 0) { continue; } else if (array[r][c] == array[r][c1]) { array[r][c] += array[r][c1]; array[r][c1] = 0; count+=array[r][c]; flag = true; } else if (array[r][c] != array[r][c1]) { break; } } } } } for (int r = 0; r < 4; r++) { for (int c = 0; c < 4; c++) { if (array[r][c] == 0) { for (int c1 = c + 1; c1 < 4; c1++) { if (array[r][c1] != 0) { array[r][c] = array[r][c1]; array[r][c1] = 0; c++; flag = true; } } } } } } public void relRight () { for (int r = 0; r < 4; r++) { for (int c = 3; c >=0; c--) { if (array[r][c] != 0) { for (int c1 = c - 1; c1 >=0; c1--) { if (array[r][c1] == 0) { continue; } else if (array[r][c] == array[r][c1]) { array[r][c] += array[r][c1]; array[r][c1] = 0; count+=array[r][c]; flag = true; } else if (array[r][c] != array[r][c1]) { break; } } } } } for (int r = 3; r >= 0; r--) { for (int c = 3; c >= 0; c--) { if (array[r][c] == 0) { for (int c1 = c - 1; c1 >= 0; c1--) { if (array[r][c1] != 0) { array[r][c] = array[r][c1]; array[r][c1] = 0; c--; flag = true; } } } } } } public void relDown () { for (int c = 0; c <4; c++) { for (int r = 3; r >=0; r--) { if (array[r][c] != 0) { for (int r1 = r - 1; r1 >=0; r1--) { if (array[r1][c] == 0) { continue; } else if (array[r][c] == array[r1][c]) { array[r][c] += array[r1][c]; array[r1][c] = 0; count+=array[r][c]; flag = true; } else if (array[r][c] != array[r1][c]) { break; } } } } } for (int c = 3; c >= 0; c--) { for (int r = 3; r >= 0; r--) { if (array[r][c] == 0) { for (int r1 = r - 1; r1 >= 0; r1--) { if (array[r1][c] != 0) { array[r][c] = array[r1][c]; array[r1][c] = 0; r--; flag = true; } } } } } } public void relUp () { for (int c = 0; c <4; c++) { for (int r = 0; r <4; r++) { if (array[r][c] != 0) { for (int r1 = r + 1; r1 <4; r1++) { if (array[r1][c] == 0) { continue; } else if (array[r][c] == array[r1][c]) { array[r][c] += array[r1][c]; array[r1][c] = 0; count+=array[r][c]; flag = true; } else if (array[r][c] != array[r1][c]) { break; } } } } } for (int c = 0; c < 4; c++) { for (int r = 0; r < 4; r++) { if (array[r][c] == 0) { for (int r1 = r + 1; r1 < 4; r1++) { if (array[r1][c] != 0) { array[r][c] = array[r1][c]; array[r1][c] = 0; r++; flag = true; } } } } } } }