JAVA改良版2048小游戏,优雅,简单

本次的2048小游戏容易理解。很适合上手练习
代码量不大,可以直接运行。


前言

运行效果截图
在这里插入图片描述


运行环境:

使用软件:eclipse2021-12版
JDK版本:JDK15.0.1

一、使用步骤

1.项目结构以及下载连接

在这里插入图片描述
链接:https://pan.baidu.com/s/1MWQKg7VeiKwRNOzqYtLbiA
提取码:erp6

2.代码

2.1 BaseData.java

注意:这是一个结构类:
代码如下:

import java.awt.Font;
public interface BaseData {
	Font topicFont = new Font("微软雅黑", Font.BOLD, 50);
    Font scoreFont = new Font("微软雅黑", Font.BOLD, 28);
    Font normalFont = new Font("宋体", Font.PLAIN, 20);
    Font font1 = new Font("宋体", Font.BOLD, 46);
    Font font2 = new Font("宋体", Font.BOLD, 40);
    Font font3 = new Font("宋体", Font.BOLD, 34);
    Font font4 = new Font("宋体", Font.BOLD, 28);
    Font font5 = new Font("宋体", Font.BOLD, 22);
    int normalFontData = 20;
    int topicFontData = 30;
    void init();
    void showView();
}

2.2 Check.java

代码如下:

import java.awt.Color;
import java.awt.Font;
public class Check {
	public int value;
    public Check() {
        clear();
    }
    public void clear() {
        value = 0;
    }
    public Color getForeground() {
        switch (value) {
            case 0:
                return new Color(0xcdc1b4);
            case 2:
            case 4:
                return Color.BLACK;
            default:
                return Color.WHITE;
        }
    }
    public Color getBackground() {
        switch (value) {
            case 0:
                return new Color(0xcdc1b4);
            case 2:
                return new Color(0xeee4da);
            case 4:
                return new Color(0xede0c8);
            case 8:
                return new Color(0xf2b179);
            case 16:
                return new Color(0xf59563);
            case 32:
                return new Color(0xf67c5f);
            case 64:
                return new Color(0xf65e3b);
            case 128:
                return new Color(0xedcf72);
            case 256:
                return new Color(0xedcc61);
            case 512:
                return new Color(0xedc850);
            case 1024:
                return new Color(0xedc53f);
            case 2048:
                return new Color(0xedc22e);
            case 4096:
                return new Color(0x65da92);
            case 8192:
                return new Color(0x5abc65);
            case 16384:
                return new Color(0x248c51);
            default:
                return new Color(0x248c51);
        }
    }
    public Font getCheckFont() {
        if (value < 10) {
            return BaseData.font1;
        }
        if (value < 100) {
            return BaseData.font2;
        }
        if (value < 1000) {
            return BaseData.font3;
        }
        if (value < 10000) {
            return BaseData.font4;
        }
        return BaseData.font5;
    }
}

2.3 GameView.java

代码如下:

import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class GameView implements BaseData{
	 private static final int jFrameWidth = 400;
	    private static final int jFrameHeight = 530;
	    private static int score = 0;
	    private JFrame jFrameMain;
	    private JLabel jLblTitle;
	    private JLabel jLblScoreName;
	    private JLabel jLblScore;
	    private GameBoard gameBoard;
	    private JLabel jlblTip;
	    public GameView() {
	        init();
	    }

	    @Override
	    public void init() {
	        jFrameMain = new JFrame("2048小游戏");
	        jFrameMain.setSize(jFrameWidth, jFrameHeight);
	        jFrameMain.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	        jFrameMain.setLocationRelativeTo(null);
	        jFrameMain.setResizable(false);
	        jFrameMain.setLayout(null);
	        jLblTitle = new JLabel("2048", JLabel.CENTER);
	        jLblTitle.setFont(topicFont);
	        jLblTitle.setForeground(Color.BLACK);
	        jLblTitle.setBounds(50, 0, 150, 60);
	        jFrameMain.add(jLblTitle);
	        // 分数区
	        jLblScoreName = new JLabel("得 分", JLabel.CENTER);
	        jLblScoreName.setFont(scoreFont);
	        jLblScoreName.setForeground(Color.WHITE);
	        jLblScoreName.setOpaque(true);
	        jLblScoreName.setBackground(Color.GRAY);
	        jLblScoreName.setBounds(250, 0, 120, 30);
	        jFrameMain.add(jLblScoreName);
	        jLblScore = new JLabel("0", JLabel.CENTER);
	        jLblScore.setFont(scoreFont);
	        jLblScore.setForeground(Color.WHITE);
	        jLblScore.setOpaque(true);
	        jLblScore.setBackground(Color.GRAY);
	        jLblScore.setBounds(250, 30, 120, 30);
	        jFrameMain.add(jLblScore);
	        // 说明:
	        jlblTip = new JLabel("操作: ↑ ↓ ← →, 按esc键重新开始  ",
	                JLabel.CENTER);
	        jlblTip.setFont(normalFont);
	        jlblTip.setForeground(Color.DARK_GRAY);
	        jlblTip.setBounds(0, 60, 400, 40);
	        jFrameMain.add(jlblTip);

	        gameBoard = new GameBoard();
	        gameBoard.setBounds(0, 100, 400, 400);
	        gameBoard.setBackground(Color.GRAY);
	        gameBoard.setFocusable(true);
	        gameBoard.setLayout(new FlowLayout());
	        jFrameMain.add(gameBoard);
	    }
	    // 游戏面板需要对键值实现侦听,
	    // 这里采用内部类来继承 JPanel 类,
	    // 并实现接口 KeyListener 中的 keyPressed 方法,
	    // 方格是通过
	    @SuppressWarnings("serial")
	    class GameBoard extends JPanel implements KeyListener {
	        private static final int CHECK_GAP = 10;
	        private static final int CHECK_ARC = 20;
	        private static final int CHECK_SIZE = 86;
	        private Check[][] checks = new Check[4][4];
	        private boolean isAdd = true;
	        public GameBoard() {
	            initGame();
	            addKeyListener(this);
	        }

	        @Override
	        public void keyPressed(KeyEvent e) {
	            switch (e.getKeyCode()) {
	                case KeyEvent.VK_ESCAPE:
	                    initGame();
	                    break;
	                case KeyEvent.VK_LEFT:
	                    moveLeft();
	                    createCheck();
	                    judgeGameOver();
	                    break;
	                case KeyEvent.VK_RIGHT:
	                    moveRight();
	                    createCheck();
	                    judgeGameOver();
	                    break;
	                case KeyEvent.VK_UP:
	                    moveUp();
	                    createCheck();
	                    judgeGameOver();
	                    break;
	                case KeyEvent.VK_DOWN:
	                    moveDown();
	                    createCheck();
	                    judgeGameOver();
	                    break;
	                default:
	                    break;
	            }
	            repaint();
	        }

	        private void initGame() {
	            score = 0;
	            for (int indexRow = 0; indexRow < 4; indexRow++) {
	                for (int indexCol = 0; indexCol < 4; indexCol++) {
	                    checks[indexRow][indexCol] = new Check();
	                }
	            }
	            // 生成两个数
	            isAdd = true;
	            createCheck();
	            isAdd = true;
	            createCheck();
	        }

	        private void createCheck() {
	            List<Check> list = getEmptyChecks();

	            if (!list.isEmpty() && isAdd) {
	                Random random = new Random();
	                int index = random.nextInt(list.size());
	                Check check = list.get(index);
	                // 2, 4出现概率3:1
	                check.value = (random.nextInt(4) % 3 == 0) ? 2 : 4;
	                isAdd = false;
	            }
	        }
	        // 获取空白方格
	        private List<Check> getEmptyChecks() {
	            List<Check> checkList = new ArrayList<>();
	            for (int i = 0; i < 4; i++) {
	                for (int j = 0; j < 4; j++) {
	                    if (checks[i][j].value == 0) {
	                        checkList.add(checks[i][j]);
	                    }
	                }
	            }

	            return checkList;
	        }

	        private boolean judgeGameOver() {
	            jLblScore.setText(score + "");

	            if (!getEmptyChecks().isEmpty()) {
	                return false;
	            }

	            for (int i = 0; i < 3; i++) {
	                for (int j = 0; j < 3; j++) {
	                    //判断是否存在可合并的方格
	                    if (checks[i][j].value == checks[i][j + 1].value
	                            || checks[i][j].value == checks[i + 1][j].value) {
	                        return false;
	                    }
	                }
	            }
	            return true;
	        }

	        private boolean moveLeft() {
	            for (int i = 0; i < 4; i++) {
	                for (int j = 1, index = 0; j < 4; j++) {
	                    if (checks[i][j].value > 0) {
	                        if (checks[i][j].value == checks[i][index].value) {
	                            score += checks[i][index++].value <<= 1;
	                            checks[i][j].value = 0;
	                            isAdd = true;
	                        } else if (checks[i][index].value == 0) {
	                            checks[i][index].value = checks[i][j].value;
	                            checks[i][j].value = 0;
	                            isAdd = true;
	                        } else if (checks[i][++index].value == 0) {
	                            checks[i][index].value = checks[i][j].value;
	                            checks[i][j].value = 0;
	                            isAdd = true;
	                        }
	                    }
	                }
	            }
	            return isAdd;
	        }
	        private boolean moveRight() {
	            for (int i = 0; i < 4; i++) {
	                for (int j = 2, index = 3; j >= 0; j--) {
	                    if (checks[i][j].value > 0) {
	                        if (checks[i][j].value == checks[i][index].value) {
	                            score += checks[i][index--].value <<= 1;
	                            checks[i][j].value = 0;
	                            isAdd = true;
	                        } else if (checks[i][index].value == 0) {
	                            checks[i][index].value = checks[i][j].value;
	                            checks[i][j].value = 0;
	                            isAdd = true;
	                        } else if (checks[i][--index].value == 0) {
	                            checks[i][index].value = checks[i][j].value;
	                            checks[i][j].value = 0;
	                            isAdd = true;
	                        }
	                    }
	                }
	            }
	            return isAdd;
	        }
	        private boolean moveUp() {
	            for (int i = 0; i < 4; i++) {
	                for (int j = 1, index = 0; j < 4; j++) {
	                    if (checks[j][i].value > 0) {
	                        if (checks[j][i].value == checks[index][i].value) {
	                            score += checks[index++][i].value <<= 1;
	                            checks[j][i].value = 0;
	                            isAdd = true;
	                        } else if (checks[index][i].value == 0) {
	                            checks[index][i].value = checks[j][i].value;
	                            checks[j][i].value = 0;
	                            isAdd = true;
	                        } else if (checks[++index][i].value == 0){
	                            checks[index][i].value = checks[j][i].value;
	                            checks[j][i].value = 0;
	                            isAdd = true;
	                        }
	                    }
	                }
	            }
	            return isAdd;
	        }
	        private boolean moveDown() {
	            for (int i = 0; i < 4; i++) {
	                for (int j = 2, index = 3; j >= 0; j--) {
	                    if (checks[j][i].value > 0) {
	                        if (checks[j][i].value == checks[index][i].value) {
	                            score += checks[index--][i].value <<= 1;
	                            checks[j][i].value = 0;
	                            isAdd = true;
	                        } else if (checks[index][i].value == 0) {
	                            checks[index][i].value = checks[j][i].value;
	                            checks[j][i].value = 0;
	                            isAdd = true;
	                        } else if (checks[--index][i].value == 0) {
	                            checks[index][i].value = checks[j][i].value;
	                            checks[j][i].value = 0;
	                            isAdd = true;
	                        }
	                    }
	                }
	            }
	            return isAdd;
	        }

	        @Override
	        public void paint(Graphics g) {
	            super.paint(g);
	            for (int i = 0; i < 4; i++) {
	                for (int j = 0; j < 4; j++) {
	                    drawCheck(g, i, j);
	                }
	            }

	            // GameOver
	            if (judgeGameOver()) {
	                g.setColor(new Color(64, 64, 64, 150));
	                g.fillRect(0, 0, getWidth(), getHeight());
	                g.setColor(Color.WHITE);
	                g.setFont(topicFont);
	                FontMetrics fms = getFontMetrics(topicFont);
	                String value = "Game Over!";
	                g.drawString(value,
	                        (getWidth()-fms.stringWidth(value)) / 2,
	                        getHeight() / 2);
	            }
	        }

	        // 绘制方格
	        // Graphics2D 类扩展了 Graphics 类,
	        // 提供了对几何形状、坐标转换、颜色管理和文本布局更为复杂的控制
	        private void drawCheck(Graphics g, int i, int j) {
	            Graphics2D gg = (Graphics2D) g;
	            gg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
	                    RenderingHints.VALUE_ANTIALIAS_ON);
	            gg.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
	                    RenderingHints.VALUE_STROKE_NORMALIZE);
	            Check check = checks[i][j];
	            gg.setColor(check.getBackground());
	            // 绘制圆角
	            // x - 要填充矩形的 x 坐标。
	            // y - 要填充矩形的 y 坐标。
	            // width - 要填充矩形的宽度。
	            // height - 要填充矩形的高度。
	            // arcwidth - 4 个角弧度的水平直径。
	            // archeight - 4 个角弧度的垂直直径。
	            gg.fillRoundRect(CHECK_GAP + (CHECK_GAP + CHECK_SIZE) * j,
	                    CHECK_GAP + (CHECK_GAP + CHECK_SIZE) * i,
	                    CHECK_SIZE, CHECK_SIZE, CHECK_ARC, CHECK_ARC);
	            gg.setColor(check.getForeground());
	            gg.setFont(check.getCheckFont());

	            // 对文字的长宽高测量。
	            FontMetrics fms = getFontMetrics(check.getCheckFont());
	            String value = String.valueOf(check.value);
	            //使用此图形上下文的当前颜色绘制由指定迭代器给定的文本。
	            //getAscent()是FontMetrics中的一个方法,
	            //它返回某字体的基线(baseline)到该字体中大多数字符的升部(ascender)之间的距离
	            //getDescent 为降部
	            gg.drawString(value,
	                    CHECK_GAP + (CHECK_GAP + CHECK_SIZE) * j +
	                            (CHECK_SIZE - fms.stringWidth(value)) / 2,
	                    CHECK_GAP + (CHECK_GAP + CHECK_SIZE) * i +
	                            (CHECK_SIZE - fms.getAscent() - fms.getDescent()) / 2
	                            + fms.getAscent());
	        }

	        @Override
	        public void keyReleased(KeyEvent e) {
	        }

	        @Override
	        public void keyTyped(KeyEvent e) {
	        }

	    }

	    @Override
	    public void showView() {
	        jFrameMain.setVisible(true);
	    }

}

2.4 MainProgramEntry.java

主程序在这里,整个程序在这里开始运行:
代码如下:

public class MainProgramEntry {
	public static void main(String[] args) {
        new GameView().showView();
    }
}


总结

程序设计的很巧妙,尤其是方块的移动与是否可合并判断。但也有一些BUG,就是最后游戏结束的判断,读懂源码以后可修改一下。
下载连接
链接:https://pan.baidu.com/s/1MWQKg7VeiKwRNOzqYtLbiA
提取码:erp6

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值