作者简介
作者名:编程界明世隐 简介: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迷宫小游戏
引言:
前几天偶尔看到了这个数字游戏,感觉还蛮有意思,就玩了一下,竟然赢不了,怎么玩都是输,真是邪门了,这不作为程序员员,我玩不赢我就自己写一个,行不行?我自己写的,我想赢就赢,条件我自己设定,就是玩!!
效果图
实现思路
- 绘制窗口。
- 创建菜单。
- 创建所有空白卡片。
- 随机创建一个卡片(2或者4)。
- 键盘事件监听(上、下、左、右键监听)。
- 根据键盘的方向,处理数字的移动合并。
- 加入成功、失败判定。
- 处理其他收尾工作。
代码实现
创建窗口
首先创建一个游戏窗体类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;
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 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);
jMenu1.add(jmi1);
jMenu1.add(jmi2);
JMenuItem jmi3 = new JMenuItem("操作帮助");
jmi3.setFont(tFont);
JMenuItem jmi4 = new JMenuItem("胜利条件");
jmi4.setFont(tFont);
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 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);
jMenu1.add(jmi1);
jMenu1.add(jmi2);
JMenuItem jmi3 = new JMenuItem("操作帮助");
jmi3.setFont(tFont);
JMenuItem jmi4 = new JMenuItem("胜利条件");
jmi4.setFont(tFont);
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
建立Card类
package main;
import java.awt.Graphics;
public class Card {
private int x = 0;
private int y = 0;
private int w = 80;
private int h = 80;
private int i = 0;
private int j = 0;
private int start=10;
private int num=0;
private boolean merge=false;
public Card(int i,int j){
this.i=i;
this.j=j;
}
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修改一下。
private Color getColor(){
Color color=null;
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 text = num+"";
int wordWidth = getWordWidth(font, text);
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默认的数字,试试效果
随机创建一个数字,2或者4
- 先把Card类中 num 默认改成0
- 因为2跟4出现的比例是1:4,所以采用随机出1-5的数字,当是1的时候就表示,当得到2、3、4、5的时候就表示要出现数字2.
- 随机获取i,j 就可以得到卡片的位置,割接i,j取到card实例,如果卡片没有数字,就表示可以,否则就递归继续取,取到为止。
- 把刚才取到的数字,设置到card实例对象中就好了。
代码如下:
private void createRandomNumber() {
int num = 0;
Random random = new Random();
int index = random.nextInt(5)+1;
if(index==1){
num = 4;
}else {
num = 2;
}
if(cardFull()){
return ;
}
Card card = getRandomCard(random);
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) {
}
};
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++) {
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++) {
for (int j = 0; j < COLS; j++) {
card = cards[i][j];
if(card.getNum()!=0){
if(card.moveTop(cards,bool)){
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){
prev.num=this.num;
this.num=0;
prev.moveTop(cards,bool);
}
return true;
}else if(prev.getNum()==num && !prev.merge){
if(bool){
prev.merge=true;
prev.num=this.num*2;
this.num=0;
}
return true;
}else {
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--) {
card = cards[i][j];
if(card.getNum()!=0){
if(card.moveRight(cards,bool)){
res = true;
}
}
}
}
return res;
}
private boolean moveCardBottom(boolean bool) {
boolean res = false;
Card card;
for (int i = ROWS-1; i >=0; i--) {
for (int j = 0; j < COLS; j++) {
card = cards[i][j];
if(card.getNum()!=0){
if(card.moveBottom(cards,bool)){
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++) {
card = cards[i][j];
if(card.getNum()!=0){
if(card.moveLeft(cards,bool)){
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){
prev.num=this.num;
this.num=0;
prev.moveBottom(cards,bool);
}
return true;
}else if(prev.getNum()==num && !prev.merge){
if(bool){
prev.merge=true;
prev.num=this.num*2;
this.num=0;
}
return true;
}else {
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){
prev.num=this.num;
this.num=0;
prev.moveRight(cards,bool);
}
return true;
}else if(prev.getNum()==num && !prev.merge){
if(bool){
prev.merge=true;
prev.num=this.num*2;
this.num=0;
}
return true;
}else {
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){
prev.num=this.num;
this.num=0;
prev.moveLeft(cards,bool);
}
return true;
}else if(prev.getNum()==num && !prev.merge){
if(bool){
prev.merge=true;
prev.num=this.num*2;
this.num=0;
}
return true;
}else {
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();
}
做到这里就基本完成了,加入其他一下辅助的东西就行了,比如重新开始、游戏胜利,游戏结束等,也就不多说了。
看到这里的大佬,动动发财的小手 点赞 + 回复 + 收藏,能【 关注 】一波就更好了。
代码获取方式:
帮忙文章【点赞】 +【 收藏】+【关注】+【评论】 后,加我主页左边的微信 或者 私聊我,我发给你!
更多精彩
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 元,先到先得,有兴趣的小伙伴可以了解一下!
|