目录
引言
在Java面向对象程序设计的广阔领域中,通过实际项目来加深理论知识与实践技能的结合是一个不可或缺的过程。本项目——2048小游戏的设计与实现,不仅是一个对Java基础知识的全面应用,更是对面向对象编程思想的一次深刻实践。通过此项目,我们将学习到如何使用JFrame
来构建图形用户界面(GUI),如何利用JPanel
作为游戏的主要画布,以及如何通过监听键盘事件来实现方块的移动与合并。此外,本项目还涉及到了数据的持久化存储,即将用户的游戏分数与最高分记录到文件中,以便下次游戏时能够显示历史最高成绩。
2048小游戏以其简洁的玩法和无限的挑战性,自问世以来便受到了广泛的欢迎。玩家需要在一个4x4的网格中,通过滑动方块来使相同数字的方块合并,直至出现数字2048为止。在这个过程中,每一步的决策都至关重要,因为一旦网格被填满且没有可合并的方块,游戏即宣告结束。这种既考验策略又充满随机性的游戏机制,使得2048成为了一个既适合休闲娱乐又能够锻炼逻辑思维能力的佳作。
一、项目功能
(1)创建一个JFrame窗口;
(2)往该窗口添加游戏面板,游戏失败界面,以及“重新开始”和“退出游戏”2个按钮;
(3)在游戏面板中有分数记录显示,最高分记录显示,以及方块移动界面中能
能进行合并移动方块,使游戏正常进行;
(4)游戏结束,会弹出游戏失败界面,判断此局得分与最高分比较并存储文件中。
二、需求分析(流程图)
1)创建一个JFrame窗口,并将小组件一一添加到其中;
2)往该窗口添加游戏面板,游戏失败界面,以及“重新开始”和“退出游戏”2个按钮;
3)有分数记录显示,最高分记录显示,游戏用户进行“↑↓←→”方块移动界面中能进行合并移动方块后生成随机方块,系统计算得分在分数显示,设计虚拟移动判断移动后是否有变化,若无则无法合并,当“↑↓←→”则游戏结束。
4)游戏结束,会弹出游戏失败界面,判断此局得分与最高分比较并存储文件中。
系统流程图如下:
三、系统整体设计
2048数字方块小游戏包含游戏面板模块,方块移动模块,游戏失败模块,重新开始和退出游戏两个按钮。
四、系统功能实现
(1)游戏面板模块:
图1.4.1
“SCORE”,“BEST”是分别添加了两个JPanel面板以及添加了一个“2048”标题JLabel标签;“BEST”面板记录的分数是通过文本文件记录。
下面的方块面板,是在4*4的二维数组的辅助下实现的,每个方块都是一JLabel,存放一个图片。通过对二维数组的元素的值的设置和图片名称的设置。代码如下:
int [][] arr = new int[4][4] ; //定义一个二维数组存放数组方块
//将图片添加到p3中
for(int i=0;i<4;i++) {
for(int j=0;j<4;j++) {
JLabel img=new JLabel(new ImageIcon("D:\\code\\keshe\\IMG\\icon\\icon-"+arr[i][j]+".png"));
p3.add(img);
img.setBounds(5+j*105, 5+i*105, 100, 100);
}
(2)方块移动模板:
图1.2.1 图1.2.2
如上二图,图2.2.2是图2.2.1向左移动了一步,首先,添加键盘监听给窗口frame,当键盘按下”↑↓←→”,便可进行移动操作。
游戏面板需要对键盘进行监听,因此需要实现接口 KeyListener 中的 keyPressed方法,代码如下:
class MoveAction implements KeyListener{//键盘"上下左右"进行监听
@Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub//TODO自动生成方根
}
/** 按下某个键时调用此方法 */
@Override
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_UP: //上
check();
MoveUP(1);
addcude();
break;
case KeyEvent.VK_DOWN: //下
check();
MoveDown(1);
addcude();
break;
case KeyEvent.VK_LEFT: //左
check();
MoveLeft(1);
addcude();
break;
case KeyEvent.VK_RIGHT: //右
check();
MoveRight(1);
addcude();
break;
default:
break;
}
}
@Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
}
}
比如,某一行在左移动时,需要先判断该行是否有0元素,如果有就需要将其移动到该行的最后,其他元素向前移动。然后判断该元素与后一个元素是否相同,如果相同就进行合并。合并的数字大小的值计入得分SCROCE。
在移动一次后,便重新绘制面板。
以左移动为例,代码如下:
public void MoveLeft(int flag) {//左移动
//1.将每一行的中的0放到最右边
for(int i=0;i<arr.length;i++) {//从第一列到第四列逐一进行
int[] newarr =new int[4]; //创建一个新的一维数组,
int index=0; //在该数组的前面存放第i行的非0元素,0元素放到后面(系统默认值为0,所以不用管)
for(int x=0;x<arr[i].length;x++) {
if(arr[i][x]!=0) {//不等于0的元素就先放进去
newarr[index]=arr[i][x];
index++;
}
}
arr[i]=newarr;//重新给arr[i]行是已经把0放最右边
//2.合并元素
for(int x=0;x<3;x++) {//注意:当某一个元素合并后,不会再和后面的元素作合并操作
if(arr[i][x]==arr[i][x+1]) {//若该元素与后面的元素相同则合并
arr[i][x]*=2; //靠左边你的数组合并后变两倍
if(flag==1) {
score+=arr[i][x];//计算得分
}
//右面的元素向左移动
for(int j=x+1;j<3;j++) {
arr[i][j]=arr[i][j+1];
}
arr[i][3]=0;//最后再补0
}
}
}
diamonds();
p3.repaint();//刷新界面
}
合并完成后会随机在0元素空位上生成一个2或4方块,根据0元素空位数量,如无0元素空位则不添加。代码如下:
public void addcude() {//添加新的方块
int [] x=new int [16]; //记录横坐标i
int [] y=new int [16];//记录纵坐标j
int n=0; //为0的方块有多少个
for(int i=0;i<4;i++) {
for(int j=0;j<4;j++) {
if(arr[i][j]==0) {
x[n]=i;
y[n]=j;
n++;
}
}
}
//根据剩余空位的个数进行操作
int m;
if(n==0)
return;
if(n==1)//产生2或4
arr[x[0]][y[0]]=2*(int)((Math.random()*2)+1);
if(n>1) {
m=(int)(Math.random()*n);//产生一个随机数在[0,n-1]
// 1/5的几率产生4,4/5的几率产生2
if((int)(Math.random()*10+1)<=2) {//1 2 3 4
arr[x[m]][y[m]]=4;
}else {
arr[x[m]][y[m]]=2;
}
}
(3)游戏失败模块:
每次进行移动前,进行当前游戏界面模拟移动,依旧以左移动为例,代码如下:
public boolean checkLeft() {//判断是否可以向左移动
boolean flag=true;
copy(arr,beifen);
//左移动
MoveLeft(0);
//判断是否相同
flag=notEquals(arr,beifen);//若不相同则返回true
//System.out.println(flag);
copy(beifen,arr);//恢复原来的数组
return flag;
}
注:因为要调用到左移动函数,若不加参数控制,在进行模拟的时候,得分会被记录进去。所有当参数为1时,才记录得分,参数为0时,不记录得分。
图1.3.1
若上下左右都不能移动,则游戏结束,弹出失败面板。如图1.3.1所示,代码如下:
public void check() {
if(checkLeft()==false && checkRight()==false && checkUp()==false && checkDown()==false ) {
loseflat=1;
diamonds();
p3.repaint();
}
}
(4)重新开始模块
图1.4.1
点击“重新开始”按钮,进入endAction监听器处理,对部分数值进行重新设置,对面板重新绘制。代码如下:
class endAction implements ActionListener{
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
String s=e.getActionCommand();//获取命令
switch (s) {
case "重新开始":{
loseflat=0;//更新失败标志
score=0;
view();
diamonds();
p3.remove(losePanel);
//losePanel.removeAll();
p3.repaint();
frame.this.requestFocus();
}
break;
default:
savePoint();
System.exit(0);
break;
}
}
}
(5)退出游戏模板
点击“退出游戏”按钮,进入endAction监听器处理事件,关闭窗口,将最高分写入文本文件。
void savePoint() {
//将最高分储存在BEST.txt
try{
FileWriter fileWritter = new FileWriter(file.getName());
fileWritter.write(bestscore+"");
fileWritter.close();
}catch(IOException e){
e.printStackTrace();
}
}
五、运行结果
1. 游戏面板
图2.1
如图2.1所示,显示得分,最高分,当你的分数SCORE大于最高分BEST将记录到最高分BEST中。
2. 方块移动
图2.2.1 图2.2.2
如上二图,图2.2.2是图2.2.1向左移动了一步,即按“←”键。当键盘按下“↑↓←→”,便可进行移动操作,合并后的数值将记录到得分里。并在随机空方块位置随机生成2或4方块。
3. 游戏失败
图2.3
如图2.3所示当游戏界面没有可合并的方块了,并且没有空的方块了,则游戏失败。
4. 重新开始
图2.4
如图2.4是游戏结束图2.3后随机按“↑↓←→”弹出的面板,点击“重新开始”按钮即可开启新一轮的游戏。
5. 退出游戏
如图2.4,点击“退出游戏”按钮即可退出游戏。
六、结尾
通过本次2048小游戏的开发,我们不仅巩固了Java语言的基础知识,如类的设计、对象的创建、事件的监听与处理等,还深入理解了面向对象编程中的封装、继承与多态等核心概念。更重要的是,我们学会了如何将理论知识与实际项目相结合,通过不断的调试与优化,最终完成了一个功能完善、界面友好的小游戏。如果本篇文章对你有所帮助,动动发财的小手点点赞加入收藏夹★★★