数据结构课程设计写的2048小游戏,答辩完了就开源了,因为这次的技术文档任性地写成了傻瓜式教程了,就干脆也放出来了,供参考,源代码打包在最后面会附上。

  • 一 实现方案
  • 二 具体代码及程序框图分析
  • 三 参考资料

一、 实现方案

本游戏采用Java语言编写,使用Eclipse编译器, jdk1.7.0_51编译环境。
游戏的UI主要运用Java图形界面编程(AWT),实现窗口化可视化的界面。
游戏的后台通过监听键盘方向键来移动数字方块,运用随机数的思想随机产生一个2或4的随机数,显示在随机方块中,运用二维数组存储、遍历查找等思想,在每次移动前循环查找二维数组相邻的移动方向的行或列可以合并与否,如果没有可以合并的数字方块同时又没有空余的空间产生新的数字则游戏宣告结束,同时,当检测到合并的结果中出现2048,也宣告游戏结束。
游戏设计了非常简单的交互逻辑,流程如下:

为了增加游戏的用户体验,后期加入了操作音效(音效文件提取自百度移动应用商店——2048),在移动和合并方块时播放不同声音。

二、 具体代码及程序框图分析

整个游戏有三个类,分别为游戏的主类Game.class、事件处理类MyListener.class、声音处理类PlaySound.class,下面对Game.class和MyListener.class进行说明。
Game.class的简单程序框图如下:

游戏的主类Game.class是窗体程序JFrame的扩展类,主要负责界面的搭建,完成界面绘图的工作。该类作为主类,主方法public static void main(String[] args)中先新建一个该类的对象,接着调用用与创建界面控件的方法IntUI(),代码如下:

public static void main(String[] args) {Game UI = new Game();UI.IntUI();}

IntUI()方法用于JFrame控件及界面框架的搭建,代码解析如下:
首先创建一个窗体,标题为“2048小游戏”,把坐标固定在屏幕的x=450,y=100的位置,把窗体大小设置为宽400像素高500像素,然后把JPlane的布局管理器设置为空,具体代码如下:

this.setTitle("2048小游戏");
this.setLocation(450, 100);
this.setSize(400, 500);
this.setLayout(null);

接下来分别是【新游戏】、【帮助】、和【退一步】的按钮,以【新游戏】按钮为例,创建一个新游戏的图片按钮,图片相对路径为res/start.png,为了达到更美观的显示效果,把聚焦,边线等特征设置为false,把相对窗体的坐标设置为(5, 10),大小设置为宽120像素高30像素,具体代码如下:

ImageIcon imgicon = new ImageIcon("res/start.png");
JButton bt = new JButton(imgicon);
bt.setFocusable(false);
bt.setBorderPainted(false);
bt.setFocusPainted(false);
bt.setContentAreaFilled(false);
bt.setBounds(-15, 10, 120, 30);
this.add(bt);

而分数显示控件与按钮控件类似,不再赘述。
布置好控件后,为了彻底结束进程,不再占用内存,使用 System exit 方法退出应用程序,同时由于按钮都是相对窗体固定坐标的,所以不允许用户随意改变窗体大小,最后把界面设置为可见,具体代码如下:

this.setDefaultCloseOperation(3);
this.setResizable(false);
this.setVisible(true);

对于按钮的监听,是在MyListener.class中处理的,在IntUI中只是新建一个对象来引用该类,该类具体后面会有说明,这里引用的代码如下:

MyListener cl = new MyListener(this, Numbers, lb, bt, about,back);
bt.addActionListener(cl);
about.addActionListener(cl);
back.addActionListener(cl);
this.addKeyListener(cl);

IntUI方法至此结束。
接下来便是游戏中大方框和数字小方框的绘制,这里用到了paint方法来绘制容器。
paint方法不需要调用,只需要重写。
在继承父类的方法后,先绘制出大矩形框,为了美观,这里绘制圆角矩形框,底色的16进制值为:0xBBADA0,边长为370像素,在窗口的相对位置为x=15像素,y=110像素,上下弧度为15,代码如下:

super.paint(g);
g.setColor(new Color(0xBBADA0));
g.fillRoundRect(15, 110, 370, 370, 15, 15);

接下来,通过双重循环,绘制4*4的小方框,关于小方框的绘制的相对位置这里有个关键的算法,因为每一个小方框都要距离边框及相邻边框的大小相等才能达到相对美观的效果,以水平方向为例,分析如下:
因为总的宽度为370像素,假如让每个小方框的间距为10像素,那么在370像素里面除去5个小方框距离的像素值10*5=50像素外,还剩下320像素,一行4个方框,正好80像素一个小方框。在写这双重循环前,可以先绘制了一个简单的位置图如下:


以第一行的横坐标为例,每绘制完一个小方框,绘制下一个小方框时就要加上前一个方框的宽度,再加上边框之前的距离,所以绘制4*4颜色为0xCDC1B4的小边框的双重循环的代码如下:

g.setColor(new Color(0xCDC1B4));
for (int i = 0; i < 4; i++) {for (int j = 0; j < 4; j++) {g.fillRoundRect(25 + i * 90, 120 + j * 90, 80, 80, 15, 15); }}

由于2048游戏里面可能显示的数值有2、4、8、16等等不同的数值,同样是为了美观考虑,我们给显示不同数值的方框绘制不同的颜色,同样,由于一位数字2与两位数字16甚至多位数字128或1024等来说,如果显示的位置与大小相同,那么2等一位数字的显示是完美的,但是2048这些数字的显示就会超出小方框,影响观感所以还要对数字的相对位置和大小做一定的调整,这里的具体代码如下:

for (int i = 0; i < 4; i++) {for (int j = 0; j < 4; j++) {if (Numbers[j][i] != 0) {int FontSize = 30;int MoveX = 0, MoveY = 0;switch (Numbers[j][i]) {case 2:g.setColor(new Color(0xeee4da));FontSize = 30;MoveX = 0;MoveY = 0;break;case 4:g.setColor(new Color(0xede0c8));FontSize = 30;MoveX = 0;MoveY = 0;break;case 8:g.setColor(new Color(0xf2b179));FontSize = 30;MoveX = 0;MoveY = 0;break;case 16:g.setColor(new Color(0xf59563));FontSize = 29;MoveX = -5;MoveY = 0;break;case 32:g.setColor(new Color(0xf67c5f));FontSize = 29;MoveX = -5;MoveY = 0;break;case 64:g.setColor(new Color(0xf65e3b));FontSize = 29;MoveX = -5;MoveY = 0;break;case 128:g.setColor(new Color(0xedcf72));FontSize = 28;MoveX = -10;MoveY = 0;break;case 256:g.setColor(new Color(0xedcc61));FontSize = 28;MoveX = -10;MoveY = 0;break;case 512:g.setColor(new Color(0xedc850));FontSize = 28;MoveX = -10;MoveY = 0;break;case 1024:g.setColor(new Color(0xedc53f));FontSize = 27;MoveX = -15;MoveY = 0;break;case 2048:g.setColor(new Color(0xedc22e));FontSize = 27;MoveX = -15;MoveY = 0;break;default:g.setColor(new Color(0x000000));break;}g.fillRoundRect(25 + i * 90, 120 + j * 90, 80, 80, 10, 10);g.setColor(new Color(0x000000));g.setFont(new Font("Arial", Font.PLAIN, FontSize));g.drawString(Numbers[j][i] + "", 25 + i * 90 + 30 + MoveX,
120 + j * 90 + 50 + MoveY);}}
}

至此,Game.class的分析结束,下面是对按钮监听及时间处理的MyListener.class的分析。
MyListener.class是一个键盘监听事件的扩展类,该类的简单程序框图如下:

在该类的开始,先声明一些变量,如用于接收传递进来的数组Numbers[][],随机数rand,备份数组用的BackUp[][],BackUp2[][]等等,代码如下:

private Game UI;
private int Numbers[][];
private Random rand = new Random();
private int BackUp[][]= new int[4][4];
private int BackUp2[][]= new int[4][4];
public JLabel lb;
int score = 0;
int tempscore,tempscore2;
public JButton bt,about,back;
private boolean isWin=false,relive=false,hasBack=false,isSound=true;

然后是MyListener的构造方法,把来自Game类中传进来的参数接收,代码如下:

public MyListener(Game UI, int Numbers[][], JLabel lb,JButton bt,JButton about,JButton back,JCheckBox isSoundBox) {this.UI = UI;this.Numbers = Numbers;this.lb = lb;this.bt=bt;this.about=about;this.back=back;this.isSoundBox=isSoundBox;}

接下来是按钮的监听,通过e.getSource()取得按钮相应的值,进行相应的处理,下面分别解析下【新游戏】按钮和【退一步】按钮的代码:
【新游戏】按钮作为游戏的开始,将会在按下该按钮是初始化很多数据,例如游戏胜利标志,分数值,4*4的二维数组等,该按钮的控制代码的工作逻辑是这样的:先把胜利标志置为false,然后通过双重循环,把所有的小方格的数据初始化为0,接着把分数值置为0,接下来分别定义四个4以内的随机数整型变量,两两为行数和列数,当它们组合相等时再重新产生两个新的,直到不相等为止,然后生成2个2或者4的数字赋值到对应的位置,完成赋值后,重写一次paint函数,把数组的非零元素绘制在对应位置的小方框内,代码如下:

if(e.getSource() ==bt ){isWin=false;
for (int i = 0; i < 4; i++)for (int j = 0; j < 4; j++)Numbers[i][j] = 0;
score = 0;
lb.setText("分数:" + score);
int r1 = rand.nextInt(4);
int r2 = rand.nextInt(4);
int c1 = rand.nextInt(4);
int c2 = rand.nextInt(4);
while (r1 == r2 && c1 == c2) {r2 = rand.nextInt(4);c2 = rand.nextInt(4);}int value1 = rand.nextInt(2) * 2 + 2;
int value2 = rand.nextInt(2) * 2 + 2;
Numbers[r1][c1] = value1;
Numbers[r2][c2] = value2;
UI.paint(UI.getGraphics());
}

【退一步】按钮分了两种情况考虑,为了出现退一步继续按【退一步】按钮出现异常情况,加了一个是否已经进行过一次回退操作了的标志hasBack,进入回退按钮的操作后,先判断是否是起死回生类型的回退,如果不是起死回生类型的回退则把非起死回生分数备份的变量tempscore赋值回记录分数的变量score,然后循环调用java.util.Arrays.copyOf()方法复制数组,把备份的数组复制回去Numbers数组;如果是起死回生类型的回退,重新复制等操作和前面是一样的,不同点在于起死回生类型的回退在操作后,把起死回生回退的标志relive重新置为false。在完成这些操作后,重新绘图,代码如下:

else if(e.getSource()==back&&hasBack==false){hasBack=true;if(relive==false){score=tempscore;lb.setText("分数:" + score);for(int i=0;i<BackUp.length;i++){Numbers[i]=Arrays.copyOf(BackUp[i], BackUp[i].length);}}else{score=tempscore2;lb.setText("分数:" + score);for(int i=0;i<BackUp2.length;i++){Numbers[i]=Arrays.copyOf(BackUp2[i], BackUp2[i].length);}relive=false;}UI.paint(UI.getGraphics());
}

下面是按键监听的解析,按键监听通过相应的键值识别按键,然后运用switch开关语句控制不同按键的事件。在处理所有时间之前,先定义三个整型变量,用于计数,然后判断BackUp数组是否为空,因为第一次运行时用于普通数组备份的BackUp数组是没有任何东西的,直接拿来备份会报异常。在备份好数组后,就是对应的键值的监听,以按下方向键左为例(其它三个方向键处理方式基本相同,只是行列及方向的区别),向左移动时,采用三个双重循环,三个循环均为外循环的变量h控制行数,内循环变量l控制列数。在第一个双重循环中,内循环先遍历每一列,判断该列的前一列是否为0,如果前一列为0,则把该列赋值给一个临时变量,把临时变量的值赋值给前一列,然后把该列置0;外循环是遍历行数,每完成一行的操作转至下一行继续重复操作。该操作的代码如下:

for (int h = 0; h < 4; h++)for (int l = 0; l < 4; l++)if (Numbers[h][l] != 0) {int temp = Numbers[h][l];int pre = l - 1;while (pre >=0 && Numbers[h][pre] == 0) {
            Numbers[h][pre] = temp;
            Numbers[h][pre + 1] = 0;
            pre--;
            Counter++;
}
}

在完成一次靠左移动后,接下来的双重循环使用来合并小方格中数字相同的元素的,把他们相加并放到靠左的方格中,同样外循环是用来控制行数,内循环控制列数,在内循环中加了一个判断条件,当列数加1小于4时(因为只需循环到第三列即可完成该操作),同时该列与该列+1的那一列的元素值相等且它们都不等于0时,即可进行合并操作,合并后,把该列的值置为两元素相加的结果,并把该列+1列置为0,然后把统计是否移动了的计数器进行一个自增操作。完成该操作的代码如下:

for(int h=0;h<4;h++)for(int l=0;l<4;l++)if(l+1<4&&(Numbers[h][l]==Numbers[h][l+1])&&(Numbers[h][l]!=0||Numbers[h][l+1]!=0)){
new PlaySound("merge.wav").start();
Numbers[h][l]=Numbers[h][l]+Numbers[h][l+1];
Numbers[h][l+1]=0;
Counter++;
score+=Numbers[h][l];
if(Numbers[h][l]==2048){
isWin=true;}
}

在完成该操作后,为了避免合并后,出现方块不在最左边的情况,还需要一次整体遍历左移,这次整体遍历左移和第一次的双重循环一样,所以这里不再分析。
在完成移动及合并操作后,要判断相邻的方块是否还能合并,因为有可能出现移动后,相邻的方块又能进行新的合并,如果不加这个判断的话,当所有的方块全部填满时会无判断游戏结束。
这里判断相邻的新的方块是否能进一步合并也是用到双重循环,外循环控制行数,内循环控制列数,逐个判断下和右是否能进一步合并,如果能则相邻方块的计数器做自增操作,代码如下:

for (int i = 0; i < 3; i++) {for (int j = 0; j < 3; j++) {if (Numbers[i][j] == Numbers[i][j + 1]&& Numbers[i][j] != 0) {NumNearCounter++;}
if (Numbers[i][j] == Numbers[i + 1][j]&& Numbers[i][j] != 0) {NumNearCounter++;}
if (Numbers[3][j] == Numbers[3][j + 1]&& Numbers[3][j] != 0) {NumNearCounter++;}
if (Numbers[i][3] == Numbers[i + 1][3]&& Numbers[i][3] != 0) {NumNearCounter++;}}
}

在完成上述操作后,在判断非0元素的个数,应为当非零元素个数为0时,即代表所有的小方块中已经有元素存在了,16个方块全部都满了,这里的代码如下:

for (int i = 0; i < 4; i++) {for (int j = 0; j < 4; j++) {if (Numbers[i][j] != 0) {NumCounter++;}}}

在完成统计空格个数后,如果按下按键后发生了移动,就完成分数的更新,因为要移动一次后在随机的空的格子(这里指元素值为0的格子)里面产生新的基于2或者4的随机数,代码如下:

if (Counter > 0) {
lb.setText("分数:" + score);int r1 = rand.nextInt(4);int c1 = rand.nextInt(4);while (Numbers[r1][c1] != 0) {r1 = rand.nextInt(4);c1 = rand.nextInt(4);}int value1 = rand.nextInt(2) * 2 + 2;Numbers[r1][c1] = value1;
}

接下来判断这次移动后,有没有出现isWin == true的情况,如果出现了,就弹出游戏胜利的标志,代码如下:

if (isWin == true){UI.paint(UI.getGraphics());JOptionPane.showMessageDialog(UI, "恭喜你赢了!\n您的最终得分为:" + score);}

然后,判断是否是16个格子全满了同时相邻的方格不能进一步合并了,如果是则把可以进行起死回生操作的标志relive置为true,同时弹出游戏结束的提示语,代码如下:

if (NumCounter == 16 && NumNearCounter == 0) {relive = true;JOptionPane.showMessageDialog(UI, "没地方可以合并咯!!"+ "\n很遗憾,您输了~>_<~" + "\n悄悄告诉你,游戏有起死回生功能哦,不信你“退一步”试试?"
+ "\n说不定能扭转乾坤捏 (^_~)");}

最后,重新绘制图形。
MyListener类的分析到此结束。

三、 参考资料

Java API1.6.0中文版
Java从入门到精通(第三版)

源代码下载:http://download.csdn.net/detail/kevinwu93/8714079

2048小游戏(Java)源码解析及源代码打包相关推荐

  1. java 猫 游戏,crazycat 围住神经猫-小游戏-Java源码 联合开发网 - pudn.com

    crazycat 所属分类:Java编程 开发工具:Java 文件大小:1373KB 下载次数:1 上传日期:2019-01-19 21:03:14 上 传 者:lynnhl 说明:  围住神经猫-小 ...

  2. 飞机小游戏 java源码下载

    两种子弹,一种白点,飞行速度较慢,发射频率高,从四周随机位置发射,发射方向总是指向飞机的中点直线方向, 另一种导弹飞行速度较快,发射频率低,同样从四周随机位置发射,发射方向总是水平或垂直跟飞机位置无关 ...

  3. java实现2048小游戏(源码+注释)

    实现文件 APP.java import javax.swing.*;public class APP {public static void main(String[] args) {new MyF ...

  4. JavaScript | 益智类数字棋牌小游戏,无游戏框架,浏览器直接运行JavaScript(js)小游戏【源码+解析】

    游戏界面 曾几何时想有一款自己编写的游戏,通过不断学习,终于掌握了一点JavaScript基础,捣鼓了一个益智类的数字棋牌游戏,没有使用任何游戏框架,就简单使用HTML做布局,CSS做动画,JavaS ...

  5. 星益小游戏平台源码 内置80多个在线小游戏

    简介: 星益小游戏平台源码是一款星益在线小游戏可的网站源码,本程序由小星合集整理制作,共计80个小游戏. 内置了80个在线小游戏,直接就能玩耍,上传到空间用! 本程序大部分都是自适应,但是使用电脑端体 ...

  6. python小游戏-16行代码实现3D撞球小游戏!-源码下载

    python小游戏-16行代码实现3D撞球小游戏!-源码下载 所属网站分类: 资源下载 > python小游戏 作者:搞笑 链接: http://www.pythonheidong.com/bl ...

  7. 微信网页小游戏网站源码带后台+可后台添加游戏+推荐到微信

    微信网页小游戏网站源码带后台,淘宝上卖的很火的源码,免费公开!

  8. 遥控汽车网页小游戏html源码

    这是一个网页小游戏源代码 你可以开着小汽车在整个地图中旅行 可撞墙.可鸣笛.自己发现吧 无聊时可以用它打消下无聊的时间 首页注释挺详细的,可自行修改 运行不成功注意文件引用的路径 路径 路径 快来下载 ...

  9. flash高科技php网站源码下载,Flash小游戏PHP源码

    项目包里有两种版本, 第一种是:UTF-8 PHP版本 第二种是:UTF-8 伪静态版本(如果空间支持,强烈建议使用此版本,对搜索引擎友好) 开源项目:flash游戏源码,小游戏PHP源码,休闲fla ...

  10. 2023 俄罗斯方块网页小游戏HTML源码

    手机和电脑自适应都可以玩,试玩了一下源码没问题,也没有加密,想试玩的可以下载源码搭建耍耍 2023 俄罗斯方块网页小游戏HTML源码

最新文章

  1. 从零开始PyTorch项目:YOLO v3目标检测实现
  2. ClassLoader 初步
  3. AI理论知识基础(21)-对变化建模-用差分方程-动力系统及常数解
  4. win7 下的open live writer代码插件
  5. 微软Cloud+AI本地化社区贡献指南
  6. CMD 命令行查看端口被哪个程序占用,并根据PID值,找到相应的程序,关闭掉对应服务或进程!...
  7. ElasticSearch配置详解
  8. N 层应用程序中的数据检索和 CUD 操作 (LINQ to SQL)
  9. vue 动态的修改样式
  10. 从 JavaScript 到 TypeScript 6 - Vue 引入 TypeScript
  11. python生成linux执行文件_比较Python中两个PyInstaller生成的Linux可执行文件
  12. CSS布局大全-案例
  13. 基于springboot点餐系统java web订餐管理平台源码
  14. Linux设置自动关机
  15. AEG POWER可控硅工业充电机RCS
  16. 35幅非常漂亮的夜景摄影作品欣赏
  17. JST日本压着端子SHD系列线对板连接器的PCB封装库
  18. OOM系列之一:java.lang.OutOfMemoryError: Java堆空间问题详解
  19. 李开复给中国大学生的第三封信---成功、自信、快乐
  20. MBA-day6数学-应用题-工程问题-习题

热门文章

  1. CHM Editor V1.3.3.7(chm编辑、修改chm文件、chm编辑器)
  2. 基于三菱FX系列与扫码枪232的通讯
  3. 向量与直线,梯度与法向量,切向量
  4. 如何解决没有指定在Windows运行或者它包含错误
  5. 企业服务的定义、分类与特点
  6. 隐马尔可夫链模型的训练与预测
  7. 【滤波器】4. 反相输入有源低通滤波器
  8. dwg格式的计算机图,电脑上怎么打开dwg文件?
  9. Delta并联机构在ADAMS仿真中的运动副设置(二)
  10. 手动实现伽马校正(python)