作者名:编程界明世隐
简介:CSDN博客专家,从事软件开发多年,精通Java、JavaScript,博主也是从零开始一步步把学习成长、深知学习和积累的重要性,喜欢跟广大ADC一起打野升级,欢迎您关注,期待与您一起学习、成长、起飞!
1. Java俄罗斯方块
2. Java五子棋小游戏
3. 老Java程序员花一天时间写了个飞机大战
4. Java植物大战僵尸
5. 老Java程序员花2天写了个连连看
6. Java消消乐(天天爱消除)
7. Java贪吃蛇小游戏
8. Java扫雷小游戏
9. Java坦克大战
10. Java迷宫小游戏
Java 2048小游戏
前几天偶尔看到了这个数字游戏,感觉还蛮有意思,就玩了一下,竟然赢不了,怎么玩都是输,真是邪门了,这不作为程序员员,我玩不赢我就自己写一个,行不行?我自己写的,我想赢就赢,条件我自己设定,就是玩!!
首先创建一个游戏窗体类GameFrame,继承至JFrame,用来显示在屏幕上(window的对象),每个游戏都有一个窗口,设置好窗口标题、尺寸、布局等就可以。
package main;
import java.awt.Color;
import javax.swing.JFrame;
/** *窗体类 */
public class GameFrame extends JFrame {
//构造方法
public GameFrame(){
setTitle("2048");//设置标题
setSize(370, 420);//设置窗体大小
getContentPane().setBackground(new Color(66,136,83));//加上背景
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//关闭后进程退出
setLocationRelativeTo(null);//居中
setResizable(false);//不允许变大
}
}
创建面板容器GamePanel继承至JPanel
package main;
import javax.swing.JFrame;
import javax.swing.JPanel;
/* * 画布类 */
public class GamePanel extends JPanel{
private JFrame mainFrame=null;
private GamePanel panel = null;
//构造里面初始化相关参数
public GamePanel(JFrame frame){
this.setLayout(null);
this.setOpaque(false);
this.mainFrame=frame;
this.panel =this;
}
}
再创建一个Main类,来启动这个窗口,用来启动。
package main;
//Main类
public class Main {
public static void main(String[] args) {
GameFrame frame = new GameFrame();
GamePanel panel = new GamePanel(frame);
frame.add(panel);
frame.setVisible(true);
}
}
右键执行这个Main类,窗口建出来了
private Font createFont(){
return new Font("思源宋体",Font.BOLD,18);
}
//创建菜单
private void createMenu() {
//创建JMenuBar
JMenuBar jmb = new JMenuBar();
//取得字体
Font tFont = createFont();
//创建游戏选项
JMenu jMenu1 = new JMenu("游戏");
jMenu1.setFont(tFont);
//创建帮助选项
JMenu jMenu2 = new JMenu("帮助");
jMenu2.setFont(tFont);
JMenuItem jmi1 = new JMenuItem("新游戏");
jmi1.setFont(tFont);
JMenuItem jmi2 = new JMenuItem("退出");
jmi2.setFont(tFont);
//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");
}
此时直接把这个代码加入到GamePanel中,发现是会报错的,需要实现ActionListener,并重写actionPerformed 方法。
此时GamePanel的代码如下:
package main;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.plaf.FontUIResource;
/* * 画布类 */
public class GamePanel extends JPanel implements ActionListener{
private JFrame mainFrame=null;
private GamePanel panel = null;
//构造里面初始化相关参数
public GamePanel(JFrame frame){
this.setLayout(null);
this.setOpaque(false);
this.mainFrame=frame;
this.panel =this;
//创建菜单
createMenu();
}
private Font createFont(){
return new Font("思源宋体",Font.BOLD,18);
}
//创建菜单
private void createMenu() {
//创建JMenuBar
JMenuBar jmb = new JMenuBar();
//取得字体
Font tFont = createFont();
//创建游戏选项
JMenu jMenu1 = new JMenu("游戏");
jMenu1.setFont(tFont);
//创建帮助选项
JMenu jMenu2 = new JMenu("帮助");
jMenu2.setFont(tFont);
JMenuItem jmi1 = new JMenuItem("新游戏");
jmi1.setFont(tFont);
JMenuItem jmi2 = new JMenuItem("退出");
jmi2.setFont(tFont);
//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);
}
}
}
建立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);
}
}
}
运行
这个黑色不是我们想要的,要根据不同的数字来设置不同的颜色,于是我们在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;
}
修改一下Card默认的数字,试试效果
- 先把Card类中 num 默认改成0
- 因为2跟4出现的比例是1:4,所以采用随机出1-5的数字,当是1的时候就表示,当得到2、3、4、5的时候就表示要出现数字2.
- 随机获取i,j 就可以得到卡片的位置,割接i,j取到card实例,如果卡片没有数字,就表示可以,否则就递归继续取,取到为止。
- 把刚才取到的数字,设置到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;
}
构造中调用,表示打开游戏默认一个数字
记得在构造中调用这个方法哦。
//添加键盘监听
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类中加入向上移动的处理逻辑
- 从第2行开始移动,因为第一行不需要移动。
- 只要卡片的数字不是0,就表示要移动。
- 根据 i-1 可以获取到上一个卡片,如果上一个卡片是空,则把当前卡片交换上去,并且递归,因为可能要继续往上移动。
- 如果当前卡片与上一个卡片是相同数字的,则要合并。
- 以上两种都不是,则不做操作。
//卡片向上移动
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;
}
}
看看效果
加入其他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();
}
做到这里就基本完成了,加入其他一下辅助的东西就行了,比如重新开始、游戏胜利,游戏结束等,也就不多说了。
看到这里的大佬,动动发财的小手 点赞 + 回复 + 收藏,能【 关注 】一波就更好了。
订阅我的专栏《Java游戏大全》后,可以查看专栏内所有的文章,并且联系博主免费获取其中1-3份你心仪的源代码,专栏的文章都是上过csdn热榜的,值得信赖!专栏内目前有[14]篇实例,未来2个月内专栏会更新到15篇以上,一般2周一更,了解一下我的专栏。
1. Java俄罗斯方块
2. Java五子棋小游戏
3. 老Java程序员花一天时间写了个飞机大战
4. Java植物大战僵尸
5. 老Java程序员花2天写了个连连看
6. Java消消乐(天天爱消除)
7. Java贪吃蛇小游戏
8. Java扫雷小游戏
9. Java坦克大战
10. Java迷宫小游戏
1. JavaWeb图书管理系统
2. JavaWeb学生宿舍管理系统
3. JavaWeb在线考试系统
为了帮助更多小白从零进阶 Java 工程师,从CSDN官方那边搞来了一套 《Java 工程师学习成长知识图谱》,尺寸 870mm x 560mm,展开后有一张办公桌大小,也可以折叠成一本书的尺寸,原件129元现价 29 元,先到先得,有兴趣的小伙伴可以了解一下!