java实现2048小游戏

学习Java基础有一段时间了,一直想做个小的桌面程序练下手,最近自己有点时间用Java写了一个2048的桌面程序,和大家分享一下!!!

游戏效果展示:

java实现2048小游戏_第1张图片

1、设计思想

AppFrame.java 游戏的启动类,只调用了一个MainFrame的构造方法

MainFrame.java 游戏的界面类,在构造方法中做了所有的初始化操作

MyKeyListener 游戏的控制类,继承自Java中的KeyListener,实现上下左右移动的控制。

程序中的组件除了newGame按钮之外基本都是JLable组件,空块就是上面黄色没有数字的块,空快、2、4、8、16……都是通过修改JLabel的背景图片来实现的。游戏实现整体基本没有太大的难度,重要的逻辑在代码中也有相应的解释说明。

2、在eclipse中创建如下所示的文件和目录结构

java实现2048小游戏_第2张图片

由于时间有限,没有截到512以后的图片,但是游戏是编写完成的,有兴趣的可以自己完善下,把512以后的图片进行截图按照上面的命名方式存到numberimage文件夹下即可。已有的图片我也会同时上传上去,方便大家自己练习。

3、三个类的源码及代码的解释

主程序启动类AppFrame.java,只有一个MainFrame的构造方法

package cn.tzfe.app;

import cn.tzfe.view.MainFrame;

public class AppFrame
{
	public static void main(String[] args)
	{
		MainFrame mainFrame = new MainFrame();
		mainFrame.setVisible(true);
	}
}
程序类MainFrame.java,包括初始化UI、数据、监听等

package cn.tzfe.view;

import java.awt.Cursor;
import java.awt.Desktop;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.net.URI;
import java.util.Random;

import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;

import cn.tzfe.listener.MyKeyListener;

public class MainFrame extends JFrame
{
	//为了下面图片和组件的方便使用,将资源基本都定义在一起
	private static ImageIcon mainFrameIcon;//游戏窗口背景图片
	private static ImageIcon logoIcon;	   //白色背景的2048图片
	private static ImageIcon scoreIcon;	   //score图片
	private static ImageIcon bestIcon;	   //best图片
	private static ImageIcon newGameIcon;  //newGame按钮图片
	private static ImageIcon emptyIcon;	   //空块(零块)
	private static ImageIcon twoIcon;      //2块
	private static ImageIcon fourIcon;	   //4块
	
	private JLabel logo_Label;				//2048Lable
	private JLabel contact_Label;			//HOW TO PLAY Label
	private JLabel score_Label;				//scoreLabel
	private static JLabel scoreText_Label;	//scoreTextLabel
	private JLabel best_Label;				//bestLabel
	private static JLabel bestText_Label;	//bestTextLabel
	private JButton newgameBtn;				//newGame按钮
	private JLabel back_Label;				//主窗体背景组件
	private static int[][] block_Data = new int[4][4];	//一个二维int数组数据域
	private static JLabel[][] block_Lable = new JLabel[4][4];	//一个二维组件块域和上面数组对应,各个数字块的移动与合并就是操作者两个数组共同的结果
	
	//定义一个静态块,将所需要初始化的图片一次性初始化完毕,只初始化0、2、4块的原因是最初只会出现这三种块
	static
	{
		mainFrameIcon = new ImageIcon("mainframebg.png");
		logoIcon = new ImageIcon("logo.png");
		scoreIcon = new ImageIcon("score.png");
		bestIcon = new ImageIcon("best.png");
		newGameIcon = new ImageIcon("newgame.png");
		emptyIcon = new ImageIcon("numberimage/0.png");
		twoIcon = new ImageIcon("numberimage/2.png");
		fourIcon = new ImageIcon("numberimage/4.png");
		
	}
	//MainFrame的构造方法,在Swing中有这么一个问题在窗体中放置图片时,先放置的图片在最上层,后放置的在下层。
	//所以要先初始化游戏块,最后初始化窗体的背景。
	public MainFrame()
	{
		//初始化窗体大小位置等(设置窗体布局方式为自由布局)
		initBasic();
		//初始化开始游戏安按钮,得分,最好成绩等
		initNewGame();
		//初始化空块
		initEmptyBlocks();
		//初始化两个初始值(2或4)到block_Data数组中,并设置block_Label中对应块的图像
		initData();
		//初始化事件监听
		initListener();
		//初始化窗体背景
		initFrameBackGround();
		setVisible(true);
	}
	private void initListener()
	{
		//HOW TO PLAY?组件增加监听,设置为模拟超链接的那种方式
		contact_Label.addMouseListener(new MouseAdapter()
		{
			//鼠标进入设置颜色为蓝色并添加下划线,和提示信息
			public void mouseEntered(MouseEvent e)
			{
				contact_Label.setText(""+contact_Label.getText()+"");
				contact_Label.setToolTipText("You will open the official website of 2048.");
			}
			//鼠标移出设置为原来的文本,并将提示信息设置为""
			public void mouseExited(MouseEvent e)
			{
				contact_Label.setText("HOW TO PLAY?");
				contact_Label.setToolTipText("");
			}
			//鼠标单击打开2048的官网
			public void mouseClicked(MouseEvent e)
			{
				try
				{
					Desktop.getDesktop().browse(new URI("http://2048game.com/"));
				} catch (Exception ex)
				{
					ex.printStackTrace();
				}
			}
		});
		//newGame按钮设置鼠标放上去时变成手型样式
		newgameBtn.setCursor(new Cursor(Cursor.HAND_CURSOR));
		//newGame按钮绑定触发事件,即开始依据新游戏
		newgameBtn.addActionListener(new ActionListener()
		{
			public void actionPerformed(ActionEvent e)
			{
				//清空block_Label,全部设置为空块,并设置block_Data数据都为0
				reSetBlocks();
				//初始化两个随机数(2或4),并刷新界面
				initData();
			}
		});
		//增加键盘事件,并将数据数组传入
		newgameBtn.addKeyListener(new MyKeyListener(block_Data));
	}
	
	//重置每个块元素
	public static void reSetBlocks()
	{
		for(int i=0; i<4; i++)
		{
			for(int j=0; j<4; j++)
			{
				block_Lable[i][j].setIcon(emptyIcon);
				block_Data[i][j] = 0;
			}
		}
	}

	//调用两次生成随机块的方法
	public static void initData()
	{
		for(int n=0; n<2; n++)
		{
			createOneRandomNumber();
		}
	}

	//由于每次移动后,都要生成一个随机块,所以单独定义一个方法来生成随机块,生成2或4的概率是4:1
	//将生成的数存放到block_Data中,并设置对应组件的图片
	public static void createOneRandomNumber()
	{
		int i,j;
		Random random = new Random();
		i = random.nextInt(4);
		j = random.nextInt(4);
		while(true)
		{
			if(block_Data[i][j] == 0)
			{
				break;
			}
			else
			{
				i = random.nextInt(4);
				j = random.nextInt(4);
			}
		}
		block_Data[i][j] = random.nextDouble() > 0.2 ? 2 : 4;
		if(block_Data[i][j] == 2)
		{
			block_Lable[i][j].setIcon(twoIcon);
		}else
		{
			block_Lable[i][j].setIcon(fourIcon);
		}
	}
	//初始化16个0块(空块),30和166都是自己调试出的左边距和上边距,块元素的宽和高都是90,块之间的
	//间隔是10,所以平均出来每一个块元素的位置都是加上95,这个自己在纸上画图很容易理解。
	//比较难理解的是,此时初始化块的顺序是按照列初始化的,所以如block_Data[0][3]就表示的是第0列,
	//第三行的那个元素,这个点必须要区分清楚,否则在判断移动问题的逻辑就比较混乱了。
	private void initEmptyBlocks()
	{
		//设置16个空块,设置每个块的位置,将块添加到主界面中
		for(int i=0; i<4; i++)
		{
			for(int j=0; j<4; j++)
			{
				block_Lable[i][j] = new JLabel(emptyIcon);
				block_Lable[i][j].setBounds(30+i*95, 166+j*95, 90, 90);
				this.add(block_Lable[i][j]);
			}
		}
	}
	
	//初始化界面上部分组件,并添加到合适的位置,setBounds(x,y,width,height)方法用于设置组件
	//在窗体中的绝对位置和组件的大小。位置基本没有什么说的,这个基本都是靠自己调试出来的,不熟悉的话
	//调试几个组件就差不多掌握了,大小就基本上都设置为JLabel中图片的大小即可。
	private void initNewGame()
	{
		logo_Label = new JLabel(logoIcon);
		logo_Label.setBounds(10, 25, 224, 84);
		this.add(logo_Label);
		contact_Label = new JLabel("HOW TO PLAY?");
		contact_Label.setBounds(10, 110, 224, 20);
		this.add(contact_Label);
		newgameBtn = new JButton(newGameIcon);
		newgameBtn.setBounds(280, 80, 142, 44);
		this.add(newgameBtn);
		score_Label = new JLabel(scoreIcon);
		best_Label = new JLabel(bestIcon);
		score_Label.setBounds(280, 25, 60, 24);
		best_Label.setBounds(360, 25, 60, 24);
		this.add(score_Label);
		this.add(best_Label);
		scoreText_Label = new JLabel("0");
		bestText_Label = new JLabel("0");
		scoreText_Label.setBounds(290, 55, 60, 24);
		bestText_Label.setBounds(370, 55, 60, 24);
		this.add(scoreText_Label);
		this.add(bestText_Label);
	}

	private void initFrameBackGround()
	{
		back_Label = new JLabel(mainFrameIcon);
		back_Label.setBounds(6, 6, 420, 556);
		this.add(back_Label);
	}

	private void initBasic()
	{
		this.setTitle("2048game");	//设置标题
		this.setSize(450, 614);		//设置窗体大小
		this.setLocation(700, 200);	//设置窗体显示位置
		this.setLayout(null);		//设置窗体布局方式为自由布局方式(自由布局就可以按照像素位置去放置组件)
		this.setDefaultCloseOperation(EXIT_ON_CLOSE);	//设置窗体x为默认关闭窗口
	}
	//根据数据数组来刷新游戏界面,使视觉效果和真实数据是一致的,reFreshScore是在每次移动后刷新当前得分
	public static void upDateUI(int[][] block_Data)
	{
		for(int i=0; i<4; i++)
		{
			for(int j=0; j<4; j++)
			{
				block_Lable[i][j].setIcon(new ImageIcon("numberimage/"+block_Data[i][j]+".png"));
			}
		}
	}
	public static void reFreshScore()
	{
		int max = block_Data[0][0];
		for(int i=0; i<4; i++)
		{
			for(int j=0; j<4; j++)
			{
				if(block_Data[i][j] > max)
				{
					max = block_Data[i][j];
				}
			}
		}
		scoreText_Label.setText(max+"");
	}

	
}

MyKeyListener实现游戏的逻辑控制

package cn.tzfe.listener;

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JOptionPane;
import cn.tzfe.view.MainFrame;

//继承Java中的KeyListener接口,实现里面的抽象方法,主要是键被释放时处理游戏逻辑
public class MyKeyListener implements KeyListener
{
	//定义一个二维数组用于接收从MainFrame传入的数组
	private int[][] block_Data;
	public MyKeyListener(int[][] block_Data)
	{
		this.block_Data = block_Data;
	}
	public void keyTyped(KeyEvent e)
	{
	}
	public void keyPressed(KeyEvent e)
	{
	}
	//有键被按下时的调用逻辑
	public void keyReleased(KeyEvent e)
	{
		//获取到键盘按键的code值(左上右下分别对应37、38、39、40)
		int keyCode = e.getKeyCode();
		if(keyCode >=37 && keyCode<=40)
		{
			//分别从四个方向处理4个按键时的逻辑,以下以向左移动为例来解释
			//若按下的是左键,并且当前界面能够向左移动,执行向左移动的方法
			if(keyCode == 37 && canMoveLeft())
			{
				//真正向左移动的方法
				MoveLeft();
				MainFrame.createOneRandomNumber();
			}
			else if(keyCode == 39 && canMoveRight())
			{
				MoveRight();
				MainFrame.createOneRandomNumber();
			}
			else if(keyCode == 38 && canMoveUp())
			{
				MoveUp();
				MainFrame.createOneRandomNumber();
			}
			else if(keyCode == 40 && canMoveDown())
			{
				MoveDown();
				MainFrame.createOneRandomNumber();
			}
			//刷新界面和当前得分和判断游戏是否结束
			MainFrame.upDateUI(block_Data);
			MainFrame.reFreshScore();
			isGameOver();
		}
	}
	private void isGameOver()
	{
		//胜利
		for(int i=0; i<4; i++)
		{
			for(int j=0; j<4; j++)
			{
				if(block_Data[i][j] == 256)
				{
					int result = JOptionPane.showConfirmDialog(null, "恭喜您,游戏胜利,再来一局?", "游戏结果",JOptionPane.YES_NO_OPTION);
					if(result == 0)
					{
						MainFrame.reSetBlocks();
						MainFrame.initData();
					}
					else
					{
						System.exit(0);
					}
					return;
				}
			}
		}
		//失败
		if(block_DataIsFull() && !canMove())
		{
			int result = JOptionPane.showConfirmDialog(null, "很遗憾您输了,再来一局?", "游戏结果", JOptionPane.YES_NO_OPTION);
			if(result == 0)
			{
				MainFrame.reSetBlocks();
				MainFrame.initData();
			}
			else
			{
				System.exit(0);
			}
			return;
		}
	}
	private boolean block_DataIsFull()
	{
		for(int i=0; i<4; i++)
		{
			for(int j=0; j<4; j++)
			{
				if(block_Data[i][j] == 0)
				{
					return false;
				}
			}
		}
		return true;
	}
	private boolean canMove()
	{
		return canMoveLeft() || canMoveUp() || canMoveUp() || canMoveDown();
	}
	private void MoveDown()
	{
		for(int i=0; i<4; i++)
		{
			for(int j=2; j>=0; j--)
			{
				if(isNeedMoveDown(i, j))
				{
					int k;
					for(k=j+1; k<4;)
					{
						if(block_Data[i][k] == 0)
						{
							k++;
							continue;
						}
						if(block_Data[i][k] == block_Data[i][j])
						{
							block_Data[i][k] = 2 * block_Data[i][j];
							block_Data[i][j] = 0;
							break;
						}
						else
						{
							block_Data[i][k-1] = block_Data[i][j];
							block_Data[i][j] = 0;
							break;
						}
					}
					if(k==4)
					{
						block_Data[i][k-1] = block_Data[i][j];
						block_Data[i][j] = 0;
					}
				}
			}
		}
	}
	private void MoveUp()
	{
		for(int i=0; i<4; i++)
		{
			for(int j=1; j<4; j++)
			{
				if(isNeedMoveUp(i,j))
				{
					int k;
					for(k=j-1; k>=0;)
					{
						if(block_Data[i][k] == 0)
						{
							k--;
							continue;
						}
						if(block_Data[i][k] == block_Data[i][j])
						{
							block_Data[i][k] = 2 * block_Data[i][j];
							block_Data[i][j] = 0;
							break;
						}
						else
						{
							block_Data[i][k+1] = block_Data[i][j];
							block_Data[i][j] = 0;
							break;
						}
					}
					if(k<0)
					{
						block_Data[i][k+1] = block_Data[i][j];
						block_Data[i][j] = 0;
					}
				}
			}
		}
	}
	private void MoveRight()
	{
		for(int j=0; j<4; j++)
		{
			for(int i=2; i>=0; i--)
			{
				if(isNeedMoveRight(i,j))
				{
					int k;
					for(k=i+1; k<4;)
					{
						if(block_Data[k][j] == 0)
						{
							k++;
							continue;
						}
						if(block_Data[k][j] == block_Data[i][j])
						{
							block_Data[k][j] = 2 * block_Data[i][j];
							block_Data[i][j] = 0;
							break;
						}
						else
						{
							block_Data[k-1][j] = block_Data[i][j];
							block_Data[i][j] = 0;
							break;
						}
					}
					if(k == 4)
					{
						block_Data[k-1][j] = block_Data[i][j];
						block_Data[i][j] = 0;
					}
				}
			}
		}
		
	}
	//真正向左移动的方法。处理思想是遍历除了最左边一列的所有块,判断该块是否需要移动,若需要则移动
	//若不需要,则继续遍历下一个元素块
	private void MoveLeft()
	{
		for(int j=0; j<4; j++)
		{
			for(int i=1; i<4; i++)
			{
				//先判断当前block_Data[i][j]是否需要移动,需要拿另一个变量k用来查找当前块需要
				//移动到的最终位置
				if(isNeedMoveLeft(i,j))
				{
					int k;
					for(k=i-1; k>=0;)
					{
						//向左查找,若为0则继续查找
						if(block_Data[k][j] == 0)
						{
							k--;
							continue;
						}
						//在左边找到相同的合并
						if(block_Data[k][j] == block_Data[i][j])
						{
							block_Data[k][j] = 2 * block_Data[i][j];
							block_Data[i][j] = 0;
							break;
						}
						//没有找到相同的移动到相对较左的位置
						else
						{
							block_Data[k+1][j] = block_Data[i][j];
							block_Data[i][j] = 0;
							break;
						}
					}
					//在左侧方向没有找到相同的,并且已经找到最左边,则移动当前块到最左边的位置
					if(k<0)
					{
						block_Data[k+1][j] = block_Data[i][j];
						block_Data[i][j] = 0;
					}
				}
			}
		}
	}
	
	private boolean isNeedMoveRight(int i, int j)
	{
		if(block_Data[i][j]!=0&&(block_Data[i+1][j]==0||block_Data[i][j]==block_Data[i+1][j]))
		{
			return true;
		}
		return false;
	}
	private boolean isNeedMoveLeft(int i, int j)
	{
		if(block_Data[i][j]!=0&&(block_Data[i-1][j]==0||block_Data[i][j]==block_Data[i-1][j]))
		{
			return true;
		}
		return false;
	}
	private boolean isNeedMoveUp(int i, int j)
	{
		if(block_Data[i][j]!=0&&(block_Data[i][j-1]==0||block_Data[i][j]==block_Data[i][j-1]))
		{
			return true;
		}
		return false;
	}
	private boolean isNeedMoveDown(int i, int j)
	{
		if(block_Data[i][j]!=0&&(block_Data[i][j+1]==0||block_Data[i][j]==block_Data[i][j+1]))
		{
			return true;
		}
		return false;
	}
	//判断整个界面是否能够向左移动
	private boolean canMoveLeft()
	{
		//判断逻辑:遍历当前界面的块元素,如果当前块不是0 &&(当前块的左边块是0或当前块的左边块和当前块相同,则返回true)
		for(int i=1; i<4; i++)
		{
			for(int j=0; j<4; j++)
			{
				if(block_Data[i][j]!=0&&(block_Data[i-1][j]==0||block_Data[i][j]==block_Data[i-1][j]))
				{
					return true;
				}
			}
		}
		return false;
	}
	//判断整个界面是否能够向右移动
	private boolean canMoveRight()
	{
		for(int i=2; i>=0; i--)
		{
			for(int j=0; j<4; j++)
			{
				if(block_Data[i][j]!=0&&(block_Data[i+1][j]==0||block_Data[i][j]==block_Data[i+1][j]))
				{
					return true;
				}
			}
		}
		return false;
	}
	//判断整个界面能否向上移动
	public boolean canMoveUp()
	{
		for(int j=1; j<4; j++)
		{
			for(int i=0; i<4; i++)
			{
				if(block_Data[i][j]!=0&&(block_Data[i][j-1]==0||block_Data[i][j]==block_Data[i][j-1]))
				{
					return true;
				}
			}
		}
		return false;
	}
	//判断整个界面能否向下移动
		public boolean canMoveDown()
		{
			for(int j=2; j>=0; j--)
			{
				for(int i=0; i<4; i++)
				{
					if(block_Data[i][j]!=0&&(block_Data[i][j+1]==0||block_Data[i][j]==block_Data[i][j+1]))
					{
						return true;
					}
				}
			}
			return false;
		}
}

博客图片资源不好上传,若需要项目图片资源,请直接加博主QQ2819160952,博主会第一时间给大家分享的。


你可能感兴趣的:(java,面向对象)