Java实现2048小游戏,我竟然一盘都没赢,你们说我到底行不行?_java2048小游戏

	//jmi1 jmi2添加到菜单项“游戏”中
	jMenu1.add(jmi1);
	jMenu1.add(jmi2);
	
	JMenuItem jmi3 = new JMenuItem("操作帮助");
	jmi3.setFont(tFont);
	JMenuItem jmi4 = new JMenuItem("胜利条件");
	jmi4.setFont(tFont);
	//jmi13 jmi4添加到菜单项“游戏”中
	jMenu2.add(jmi3);
	jMenu2.add(jmi4);
	
	jmb.add(jMenu1);
	jmb.add(jMenu2);
	
	mainFrame.setJMenuBar(jmb);
	
	//添加监听
	jmi1.addActionListener(this);
	jmi2.addActionListener(this);
	jmi3.addActionListener(this);
	jmi4.addActionListener(this);
	//设置指令
	jmi1.setActionCommand("restart");
	jmi2.setActionCommand("exit");
	jmi3.setActionCommand("help");
	jmi4.setActionCommand("win");
}
@Override
public void actionPerformed(ActionEvent e) {
	String command = e.getActionCommand();
	UIManager.put("OptionPane.buttonFont", new FontUIResource(new Font("思源宋体", Font.ITALIC, 18)));
	UIManager.put("OptionPane.messageFont", new FontUIResource(new Font("思源宋体", Font.ITALIC, 18)));
	if ("exit".equals(command)) {
		Object[] options = { "确定", "取消" };
		int response = JOptionPane.showOptionDialog(this, "您确认要退出吗", "",
				JOptionPane.YES_OPTION, JOptionPane.QUESTION_MESSAGE, null,
				options, options[0]);
		if (response == 0) {
			System.exit(0);
		} 
	}else if("restart".equals(command)){
	}else if("help".equals(command)){
		JOptionPane.showMessageDialog(null, "通过键盘的上下左右来移动,相同数字会合并!",
				"提示!", JOptionPane.INFORMATION_MESSAGE);
	}else if("win".equals(command)){
		JOptionPane.showMessageDialog(null, "得到数字2048获得胜利,当没有空卡片则失败!",
				"提示!", JOptionPane.INFORMATION_MESSAGE);
	}
}

}


![在这里插入图片描述](https://img-blog.csdnimg.cn/982e9195655243e9aec0b194da314d01.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA57yW56iL55WM5piO5LiW6ZqQ,size_10,color_FFFFFF,t_70,g_se,x_16)


### 创建Card


建立Card类



package main;

import java.awt.Graphics;

public class Card {
private int x = 0;// x坐标
private int y = 0;// y坐标
private int w = 80;// 宽
private int h = 80;// 高
private int i = 0;//下标i
private int j = 0;//下标j
private int start=10;//偏移量(固定值)
private int num=0;//显示数字
private boolean merge=false;//当前是否被合并过,如果合并了,则不能继续合并,针对当前轮

public Card(int i,int j){
	this.i=i;
	this.j=j;
}
//根据i j计算x y坐标
private void cal(){
	this.x = start + j\*w + (j+1)\*5;
	this.y = start + i\*h + (i+1)\*5;
}
//绘制方法
public void draw(Graphics g) {
	cal();
	g.fillRoundRect(x, y, w, h, 4, 4);
}


public int getNum() {
	return num;
}
public void setNum(int num) {
	this.num = num;
}
public boolean isMerge() {
	return merge;
}
public void setMerge(boolean merge) {
	this.merge = merge;
}

}


在GamePanel中加入相关参数



private final int COLS=4;//列
private final int ROWS=4;//行
private Card cards[][] = new Card[ROWS][COLS];
private String gameFlag = “start”;//游戏状态


实例化Card对象



//初始化
private void init() {
Card card;
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
card = new Card(i,j);
cards[i][j]=card;
}
}
}


在构造方法中调用



//构造里面初始化相关参数
public GamePanel(JFrame frame){
this.setLayout(null);
this.setOpaque(false);
this.mainFrame=frame;
this.panel =this;

//创建菜单
createMenu();

//初始化
init();

}


在GamePanel中重写paint方法,并在此方法中绘制这些卡片。



@Override
public void paint(Graphics g) {
super.paint(g);
//绘制卡片
drawCard(g);
}
//绘制卡片
private void drawCard(Graphics g) {
Card card;
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
card = cards[i][j];
card.draw(g);
}
}
}


运行  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/492439925650415582c44253199e6b6b.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA57yW56iL55WM5piO5LiW6ZqQ,size_10,color_FFFFFF,t_70,g_se,x_16)  
 这个黑色不是我们想要的,要根据不同的数字来设置不同的颜色,于是我们在Card修改一下。



//获取color
private Color getColor(){
Color color=null;
//根据num设定颜色
switch (num) {
case 2:
color = new Color(238,244,234);
break;
case 4:
color = new Color(222,236,200);
break;
case 8:
color = new Color(174,213,130);
break;
case 16:
color = new Color(142,201,75);
break;
case 32:
color = new Color(111,148,48);
break;
case 64:
color = new Color(76,174,124);
break;
case 128:
color = new Color(60,180,144);
break;
case 256:
color = new Color(45,130,120);
break;
case 512:
color = new Color(9,97,26);
break;
case 1024:
color = new Color(242,177,121);
break;
case 2048:
color = new Color(223,185,0);
break;

default://默认颜色
	color = new Color(92,151,117);
	break;
}

return color;

}


加入数字的显示和颜色的修改代码,修改draw方法。



//绘制方法
public void draw(Graphics g) {
cal();
//获取旧的颜色
Color oColor = g.getColor();
//获取要用的颜色
Color color = getColor();
//设置画笔颜色
g.setColor(color);
g.fillRoundRect(x, y, w, h, 4, 4);

if(num!=0){
	//设置字的颜色
	g.setColor(new Color(125,78,51));
	Font font = new Font("思源宋体", Font.BOLD, 35);
	g.setFont(font);
	//转换成String
	String text = num+"";
	//计算该字体文本的长度
    int wordWidth = getWordWidth(font, text);
    //计算出字体居中位置的X坐标
    int sx = x+(w-wordWidth)/2;
    //绘制
	g.drawString(text, sx , y+50);
}

//恢复画笔颜色
g.setColor(oColor);

}

//得到该字体字符串的长度
public static int getWordWidth(Font font, String content) {
FontDesignMetrics metrics = FontDesignMetrics.getMetrics(font);
int width = 0;
for (int i = 0; i < content.length(); i++) {
width += metrics.charWidth(content.charAt(i));
}
return width;
}


![在这里插入图片描述](https://img-blog.csdnimg.cn/2ce65595f2134ff9ad839e73359ef44e.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA57yW56iL55WM5piO5LiW6ZqQ,size_10,color_FFFFFF,t_70,g_se,x_16)  
 修改一下Card默认的数字,试试效果  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/f85aed3947164afb9e672e13e4fa00ba.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA57yW56iL55WM5piO5LiW6ZqQ,size_17,color_FFFFFF,t_70,g_se,x_16)  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/5fad69d27bf64f57aead2ac840913b31.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA57yW56iL55WM5piO5LiW6ZqQ,size_10,color_FFFFFF,t_70,g_se,x_16)


### 随机创建一个数字,2或者4



> 
> 1. 先把Card类中 num 默认改成0
> 2. 因为2跟4出现的比例是1:4,所以采用随机出1-5的数字,当是1的时候就表示,当得到2、3、4、5的时候就表示要出现数字2.
> 3. 随机获取i,j 就可以得到卡片的位置,割接i,j取到card实例,如果卡片没有数字,就表示可以,否则就递归继续取,取到为止。
> 4. 把刚才取到的数字,设置到card实例对象中就好了。
> 
> 
> 


代码如下:



//在随机的空卡片创建数字2或者4
private void createRandomNumber() {
int num = 0;
Random random = new Random();
int index = random.nextInt(5)+1;//这样取出来的就是1-5 之间的随机数
//因为2和4出现的概率是1比4,所以如果index是1,则创建数字4,否则创建数字2(1被随机出来的概率就是1/5,而其他就是4/5 就是1:4的关系)

if(index==1){
	num = 4;
}else {
	num = 2;
}
//判断如果格子已经满了,则不再获取,退出
if(cardFull()){
	return ;
}
//获取随机卡片,不为空的
Card card = getRandomCard(random);
//给card对象设置数字
if(card!=null){
	card.setNum(num);
}

}
//获取随机卡片,不为空的
private Card getRandomCard(Random random) {
int i = random.nextInt(ROWS);
int j = random.nextInt(COLS);
Card card = cards[i][j];
if(card.getNum()==0){//如果是空白的卡片,则找到了,直接返回
return card;
}
//没找到空白的,就递归,继续寻找
return getRandomCard(random);
}
//判断格子满了
private boolean cardFull() {
Card card;
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
card = cards[i][j];
if(card.getNum()==0){//有一个为空,则没满
return false;
}
}
}
return true;
}


构造中调用,表示打开游戏默认一个数字  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/113e8dc183874f38819e2a6a7c0f86c5.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA57yW56iL55WM5piO5LiW6ZqQ,size_12,color_FFFFFF,t_70,g_se,x_16)  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/4fbf1df1b4944b93a9f2b53939f53112.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA57yW56iL55WM5piO5LiW6ZqQ,size_10,color_FFFFFF,t_70,g_se,x_16)


### 加入键盘事件


记得在构造中调用这个方法哦。



//添加键盘监听
private void createKeyListener() {
KeyAdapter l = new KeyAdapter() {
//按下
@Override
public void keyPressed(KeyEvent e) {
if(!“start”.equals(gameFlag)) return ;
int key = e.getKeyCode();
switch (key) {
//向上
case KeyEvent.VK_UP:
case KeyEvent.VK_W:
moveCard(1);//向上
break;

			//向右 
			case KeyEvent.VK_RIGHT:
			case KeyEvent.VK_D:
				moveCard(2);//向右
				break;
				
			//向下
			case KeyEvent.VK_DOWN:
			case KeyEvent.VK_S:
				moveCard(3);//向上下
				break;
				
			//向左
			case KeyEvent.VK_LEFT:
			case KeyEvent.VK_A:
				moveCard(4);//向左
				break;
		}
	
	}
	//松开
	@Override
	public void keyReleased(KeyEvent e) {
	}
	
};
//给主frame添加键盘监听
mainFrame.addKeyListener(l);

}


加入鼠标移动逻辑处理代码



//卡片移动的方法
protected void moveCard(int dir) {
//将卡片清理一遍,因为每轮移动会设定合并标记,需重置
clearCard();

if(dir==1){//向上移动
	moveCardTop(true);
}
//移动后要创建新的卡片
createRandomNumber();
//重绘
repaint();

}

//将卡片清理一遍,因为每轮移动会设定合并标记,需重置
private void clearCard() {
Card card;
for (int i = 0; i < ROWS; i++) {//i从1开始,因为i=0不需要移动
for (int j = 0; j < COLS; j++) {
card = cards[i][j];
card.setMerge(false);
}
}
}

//向上移动
private boolean moveCardTop(boolean bool) {
boolean res = false;
Card card;
for (int i = 1; i < ROWS; i++) {//i从1开始,因为i=0不需要移动
for (int j = 0; j < COLS; j++) {
card = cards[i][j];
if(card.getNum()!=0){//只要卡片不为空,要移动
if(card.moveTop(cards,bool)){//向上移动
res = true;//有一个为移动或者合并了,则res为true
}
}
}
}
return res;
}


在Card类中加入向上移动的处理逻辑



> 
> 1. 从第2行开始移动,因为第一行不需要移动。
> 2. 只要卡片的数字不是0,就表示要移动。
> 3. 根据 i-1 可以获取到上一个卡片,如果上一个卡片是空,则把当前卡片交换上去,并且递归,因为可能要继续往上移动。
> 4. 如果当前卡片与上一个卡片是相同数字的,则要合并。
> 5. 以上两种都不是,则不做操作。
> 
> 
> 



//卡片向上移动
public boolean moveTop(Card[][] cards,boolean bool) {
//设定退出条件
if(i==0){//已经是最上面了
return false;
}
//上面一个卡片
Card prev = cards[i-1][j];
if(prev.getNum()==0){//上一个卡片是空
//移动,本质就是设置数字
if(bool){//bool为true才执行,因为flase只是用来判断能否移动
prev.num=this.num;
this.num=0;
//递归操作(注意这里是要 prev 来 move了)
prev.moveTop(cards,bool);
}
return true;
}else if(prev.getNum()==num && !prev.merge){//合并操作(如果已经合并了,则不运行再次合并,针对当然轮)
if(bool){bool为true才执行
prev.merge=true;
prev.num=this.num*2;
this.num=0;
}
return true;
}else {//上一个的num与当前num不同,无法移动,并退出
return false;
}
}


看看效果  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/eef701ffa3404b329529e17848ebbb77.gif#pic_center)


加入其他3个方向的代码,首先是在GamePanel中加入几个代码。



//向右移动
private boolean moveCardRight(boolean bool) {
boolean res = false;
Card card;
for (int i = 0; i < ROWS; i++) {
for (int j = COLS-1; j >=0 ; j–) {//j从COLS-1开始,从最右边开始移动递减
card = cards[i][j];
if(card.getNum()!=0){//只要卡片不为空,要移动
if(card.moveRight(cards,bool)){//向右移动
res = true;//有一个为移动或者合并了,则res为true
}
}
}
}
return res;
}

//向下移动
private boolean moveCardBottom(boolean bool) {
boolean res = false;
Card card;
for (int i = ROWS-1; i >=0; i–) {//i从ROWS-1开始,往下递减移动
for (int j = 0; j < COLS; j++) {
card = cards[i][j];
if(card.getNum()!=0){//只要卡片不为空,要移动
if(card.moveBottom(cards,bool)){//下移动
res = true;//有一个为移动或者合并了,则res为true
}
}
}
}
return res;
}

//向左移动
private boolean moveCardLeft(boolean bool) {
boolean res = false;
Card card;
for (int i = 0; i < ROWS; i++) {
for (int j = 1; j < COLS ; j++) {//j从1开始,从最左边开始移动
card = cards[i][j];
if(card.getNum()!=0){//只要卡片不为空,要移动
if(card.moveLeft(cards,bool)){//向左移动
res = true;//有一个为移动或者合并了,则res为true
}
}
}
}
return res;
}


在Card加入其他几个方向的方法



//向下移动
public boolean moveBottom(Card[][] cards,boolean bool) {
//设定退出条件
if(i==3){//已经是最下面了
return false;
}
//上面一个卡片
Card prev = cards[i+1][j];
if(prev.getNum()==0){//上一个卡片是空
//移动,本质就是设置数字
if(bool){//bool为true才执行,因为flase只是用来判断能否移动
prev.num=this.num;
this.num=0;
//递归操作(注意这里是要 prev 来 move了)
prev.moveBottom(cards,bool);
}
return true;
}else if(prev.getNum()==num && !prev.merge){//合并操作(如果已经合并了,则不运行再次合并,针对当然轮)
if(bool){bool为true才执行
prev.merge=true;
prev.num=this.num*2;
this.num=0;
}
return true;
}else {//上一个的num与当前num不同,无法移动,并退出
return false;
}

}
//向右移动
public boolean moveRight(Card[][] cards,boolean bool) {
//设定退出条件
if(j==3){//已经是最右边了
return false;
}
//上面一个卡片
Card prev = cards[i][j+1];
if(prev.getNum()==0){//上一个卡片是空
//移动,本质就是设置数字
if(bool){//bool为true才执行,因为flase只是用来判断能否移动
prev.num=this.num;
this.num=0;
//递归操作(注意这里是要 prev 来 move了)
prev.moveRight(cards,bool);
}
return true;
}else if(prev.getNum()num && !prev.merge){//合并操作(如果已经合并了,则不运行再次合并,针对当然轮)
if(bool){bool为true才执行
prev.merge=true;
prev.num=this.num*2;
this.num=0;
}
return true;
}else {//上一个的num与当前num不同,无法移动,并退出
return false;
}
}
//向左移动
public boolean moveLeft(Card[][] cards,boolean bool) {
//设定退出条件
if(j
0){//已经是最左边了
return false;
}
//上面一个卡片
Card prev = cards[i][j-1];
if(prev.getNum()==0){//上一个卡片是空
//移动,本质就是设置数字
if(bool){//bool为true才执行,因为flase只是用来判断能否移动
prev.num=this.num;
this.num=0;
//递归操作(注意这里是要 prev 来 move了)
prev.moveLeft(cards,bool);
}
return true;
}else if(prev.getNum()==num && !prev.merge){//合并操作(如果已经合并了,则不运行再次合并,针对当然轮)
if(bool){bool为true才执行
prev.merge=true;
prev.num=this.num*2;
this.num=0;
}
return true;
}else {//上一个的num与当前num不同,无法移动,并退出
return false;
}
}


修改一下moveCard方法



//卡片移动的方法
protected void moveCard(int dir) {
//将卡片清理一遍,因为每轮移动会设定合并标记,需重置
clearCard();

if(dir==1){//向上移动
	moveCardTop(true);
}else if(dir==2){//向右移动
	moveCardRight(true);
}else if(dir==3){//向下移动
	moveCardBottom(true);
}else if(dir==4){//向左移动
	moveCardLeft(true);
}
//移动后要创建新的卡片
createRandomNumber();
//重绘
repaint();

}


![在这里插入图片描述](https://img-blog.csdnimg.cn/112f60e18e5742099dc3ec408e76daa4.gif#pic_center)


做到这里就基本完成了,加入其他一下辅助的东西就行了,比如重新开始、游戏胜利,游戏结束等,也就不多说了。


**看到这里的大佬,动动发财的小手 点赞 + 回复 + 收藏,能【 关注 】一波就更好了。**




## 最后的话

**无论是哪家公司,都很重视Spring框架技术,重视基础,所以千万别小看任何知识。面试是一个双向选择的过程,不要抱着畏惧的心态去面试,不利于自己的发挥。
同时看中的应该不止薪资,还要看你是不是真的喜欢这家公司,好了希望这篇文章对大家有帮助!**

部分截图:
![在这里插入图片描述](https://img-blog.csdnimg.cn/img_convert/61c5cefe9994c9c5c265628d9daf39d7.webp?x-oss-process=image/format,png)


了解详情https://docs.qq.com/doc/DSmxTbFJ1cmN1R2dB
rCard();
	
	if(dir==1){//向上移动
		moveCardTop(true);
	}else if(dir==2){//向右移动
		moveCardRight(true);
	}else if(dir==3){//向下移动
		moveCardBottom(true);
	}else if(dir==4){//向左移动
		moveCardLeft(true);
	}
	//移动后要创建新的卡片
	createRandomNumber();
	//重绘
	repaint();
}

在这里插入图片描述

做到这里就基本完成了,加入其他一下辅助的东西就行了,比如重新开始、游戏胜利,游戏结束等,也就不多说了。

看到这里的大佬,动动发财的小手 点赞 + 回复 + 收藏,能【 关注 】一波就更好了。

最后的话

无论是哪家公司,都很重视Spring框架技术,重视基础,所以千万别小看任何知识。面试是一个双向选择的过程,不要抱着畏惧的心态去面试,不利于自己的发挥。
同时看中的应该不止薪资,还要看你是不是真的喜欢这家公司,好了希望这篇文章对大家有帮助!

部分截图:
[外链图片转存中…(img-mKHs23FI-1724090591037)]

了解详情https://docs.qq.com/doc/DSmxTbFJ1cmN1R2dB

  • 30
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值