Java GUI编程和小游戏开发-2048小游戏JAVA课程设计(附带源码和注释)

基于Java Swing的2048游戏实现详解

本文将深入解析一个完整的2048游戏实现,使用Java Swing作为GUI框架,适合Java初学者和游戏开发爱好者学习。

一、游戏架构设计

1.1 核心类结构
public class Game2048 extends JPanel {
    // 游戏状态枚举
    enum State { start, won, running, over }
    
    // 方块类
    class Tile {
        private boolean merged;
        private int value;
        
        // 方块操作方法
        int mergeWith(Tile other) { ... }
    }
    
    // 游戏主逻辑
    private Tile[][] tiles; // 4x4游戏网格
    private State gamestate = State.start; // 初始状态
}
1.2 游戏状态机
  • start: 初始界面,等待玩家点击
  • running: 游戏进行中
  • won: 达成2048目标
  • over: 无可用移动,游戏结束

二、图形界面实现

2.1 界面初始化
public Game2048() {
    setPreferredSize(new Dimension(900, 700));
    setBackground(new Color(0xFAF8EF)); // 米色背景
    setFont(new Font("SansSerif", Font.BOLD, 48));
    
    // 添加事件监听器
    addMouseListener(...); // 鼠标点击开始游戏
    addKeyListener(...);   // 键盘方向键控制
}
2.2 网格绘制算法
void drawGrid(Graphics2D g) {
    g.setColor(gridColor);
    g.fillRoundRect(200, 100, 499, 499, 15, 15); // 主游戏区
    
    for (int r = 0; r < side; r++) {
        for (int c = 0; c < side; c++) {
            if (tiles[r][c] == null) {
                // 绘制空白格子
                g.fillRoundRect(215 + c*121, 115 + r*121, 106, 106, 7, 7);
            } else {
                // 绘制数字方块
                drawTile(g, r, c);
            }
        }
    }
}
2.3 方块颜色方案
final Color[] colorTable = {
    new Color(0x704A10), // 2
    new Color(0xFFEAC3), // 4
    new Color(0xFFF3D3), // 8
    // ... 其他颜色对应更高数值
};

三、游戏核心逻辑

3.1 游戏初始化
void startGame() {
    score = 0;
    highest = 0;
    gamestate = State.running;
    tiles = new Tile[side][side]; // 4x4网格
    addRandomTile(); // 生成两个初始方块
    addRandomTile();
}
3.2 随机方块生成
private void addRandomTile() {
    int pos = rand.nextInt(16); // 随机位置
    int row = pos / 4, col = pos % 4;
    
    // 90%概率生成2,10%概率生成4
    int val = rand.nextInt(10) == 0 ? 4 : 2; 
    tiles[row][col] = new Tile(val);
}
3.3 移动与合并算法
private boolean move(int countDownFrom, int yIncr, int xIncr) {
    boolean moved = false;
    for (int i = 0; i < 16; i++) {
        int j = Math.abs(countDownFrom - i);
        int r = j / 4, c = j % 4;
        
        if (tiles[r][c] == null) continue;
        
        // 计算移动方向
        int nextR = r + yIncr, nextC = c + xIncr;
        
        while (isValidPosition(nextR, nextC)) {
            if (tiles[nextR][nextC] == null) {
                // 移动到空位置
                tiles[nextR][nextC] = tiles[r][c];
                tiles[r][c] = null;
                moved = true;
            } 
            else if (tiles[nextR][nextC].canMergeWith(tiles[r][c])) {
                // 合并相同数字
                int newValue = tiles[nextR][nextC].mergeWith(tiles[r][c]);
                score += newValue;
                moved = true;
                break;
            }
        }
    }
    return moved;
}
3.4 移动方向实现
boolean moveUp() {
    return move(0, -1, 0); // 从顶部开始向下移动
}

boolean moveDown() {
    return move(15, 1, 0); // 从底部开始向上移动
}

// 左右移动同理

四、游戏状态检测

4.1 胜利条件检测
if (highest >= target) { // target=2048
    gamestate = State.won;
}
4.2 失败条件检测
private boolean movesAvailable() {
    // 临时启用检测模式
    checkingAvailableMoves = true; 
    
    // 尝试四个方向是否还有移动可能
    boolean hasMoves = moveUp() || moveDown() || moveLeft() || moveRight();
    
    checkingAvailableMoves = false;
    return hasMoves;
}

五、关键技术点解析

  1. 双缓冲技术

    • 通过paintComponent()实现平滑渲染
    • 使用repaint()触发界面更新
  2. 事件处理模型

    addKeyListener(new KeyAdapter() {
        @Override
        public void keyPressed(KeyEvent e) {
            switch (e.getKeyCode()) {
                case KeyEvent.VK_UP: moveUp(); break;
                // 其他方向...
            }
            repaint();
        }
    });
    
  3. 颜色动态计算

    // 根据方块数值计算颜色索引
    int colorIndex = (int)(Math.log(value) / Math.log(2));
    g.setColor(colorTable[colorIndex]);
    
  4. 字体渲染优化

    FontMetrics fm = g.getFontMetrics();
    int x = 215 + c*121 + (106 - fm.stringWidth(s)) / 2; // 水平居中
    int y = 115 + r*121 + (asc + (106 - (asc + dec)) / 2; // 垂直居中
    

六、运行效果展示

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
游戏界面包含:

  • 主游戏区域(4×4网格)
  • 当前分数显示
  • 历史最高分
  • 状态提示信息

七、扩展建议

  1. 添加分数显示

    // 在drawGrid()中添加
    g.drawString("分数: " + score, 300, 50);
    g.drawString("最高分: " + highest, 500, 50);
    
  2. 实现撤销功能

    private Tile[][] prevState;
    
    void saveState() {
        prevState = Arrays.copyOf(tiles, tiles.length);
    }
    
    void undo() {
        if (prevState != null) tiles = prevState;
    }
    
  3. 添加动画效果

    // 使用Swing Timer实现移动动画
    Timer timer = new Timer(10, e -> {
        // 更新方块位置
        repaint();
    });
    

本文实现的2048游戏完整展示了:

  • Java Swing GUI开发流程
  • 游戏状态机管理
  • 网格布局与渲染技术
  • 核心游戏算法实现
  • 事件处理机制

通过这个项目,可以学习到游戏开发的基本原理和Java GUI编程的核心技术。完整代码如下

package XIAXIA.game2048;

import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Random;

/**
 * 基于Java Swing的2048游戏实现
 * 核心功能:
 * 1. 4×4游戏网格与方块移动逻辑
 * 2. 方向键控制与鼠标交互
 * 3. 方块合并与分数计算
 * 4. 游戏状态管理(开始/进行中/胜利/结束)
 */
public class Game2048 extends JPanel {
    // 游戏状态枚举
    enum State {
        start, won, running, over
    }

    // 方块颜色表,不同数值对应不同颜色
    final Color[] colorTable = {
            new Color(0x704A10),   // 2的颜色
            new Color(0xFFEAC3),   // 4的颜色
            new Color(0xFFF3D3),   // 8的颜色
            new Color(0xffdac3),   // 16的颜色
            new Color(0xE7AD8E),   // 32的颜色
            new Color(0xE7DB8E),   // 64的颜色
            new Color(0xffc4c3),   // 128的颜色
            new Color(0xE7948e),   // 256的颜色
            new Color(0xbe7e56),   // 512的颜色
            new Color(0xbe5e56),   // 1024的颜色
            new Color(0x9c3931),   // 2048的颜色
            new Color(0x701710)    // >2048的颜色
    };
    
    // 游戏常量
    final static int target = 2048;  // 目标分数
    static int highest;              // 历史最高分
    static int score;                // 当前分数
    
    // 界面颜色
    private Color gridColor = new Color(0xBBADA0);   // 网格背景色
    private Color emptyColor = new Color(0xCDC1B4);  // 空方块颜色
    private Color startColor = new Color(0xFFEBCD);  // 开始界面颜色
    
    private Random rand = new Random();  // 随机数生成器
    private Tile[][] tiles;              // 4×4游戏方块数组
    private int side = 4;                // 网格尺寸
    private State gamestate = State.start; // 初始游戏状态
    private boolean checkingAvailableMoves; // 移动检查标志

    public Game2048() {
        // 界面初始化
        setPreferredSize(new Dimension(900, 700));
        setBackground(new Color(0xFAF8EF)); // 背景色
        setFont(new Font("SansSerif", Font.BOLD, 48)); // 字体设置
        setFocusable(true); // 允许接收键盘事件
        
        // 鼠标事件监听 - 点击开始游戏
        addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                startGame();
                repaint();
            }
        });
        
        // 键盘事件监听 - 方向键控制
        addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                switch (e.getKeyCode()) {
                    case KeyEvent.VK_UP:
                        moveUp();
                        break;
                    case KeyEvent.VK_DOWN:
                        moveDown();
                        break;
                    case KeyEvent.VK_LEFT:
                        moveLeft();
                        break;
                    case KeyEvent.VK_RIGHT:
                        moveRight();
                        break;
                }
                repaint();
            }
        });
    }

    @Override
    public void paintComponent(Graphics gg) {
        super.paintComponent(gg);
        Graphics2D g = (Graphics2D) gg;
        
        // 开启抗锯齿,使图形更平滑
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
        
        // 绘制游戏网格
        drawGrid(g);
    }

    /**
     * 初始化游戏
     */
    void startGame() {
        if (gamestate != State.running) {
            score = 0;
            highest = 0;
            gamestate = State.running;
            tiles = new Tile[side][side]; // 创建4×4网格
            addRandomTile(); // 添加两个初始方块
            addRandomTile();
        }
    }

    /**
     * 绘制游戏网格和方块
     */
    void drawGrid(Graphics2D g) {
        // 绘制游戏区域背景
        g.setColor(gridColor);
        g.fillRoundRect(200, 100, 499, 499, 15, 15);
        
        // 根据游戏状态绘制不同内容
        if (gamestate == State.running) {
            // 游戏进行中:绘制所有方块
            for (int r = 0; r < side; r++) {
                for (int c = 0; c < side; c++) {
                    if (tiles[r][c] == null) {
                        // 绘制空白方块
                        g.setColor(emptyColor);
                        g.fillRoundRect(215 + c * 121, 115 + r * 121, 106, 106, 7, 7);
                    } else {
                        // 绘制有数字的方块
                        drawTile(g, r, c);
                    }
                }
            }
        } else {
            // 非游戏状态:绘制开始/胜利/结束界面
            g.setColor(startColor);
            g.fillRoundRect(215, 115, 469, 469, 7, 7);
            g.setColor(gridColor.darker());
            g.setFont(new Font("SansSerif", Font.BOLD, 128));
            g.drawString("2048", 310, 270);
            
            g.setFont(new Font("SansSerif", Font.BOLD, 20));
            if (gamestate == State.won) {
                g.drawString("你是真的6", 390, 350);
            } else if (gamestate == State.over) {
                g.drawString("游戏结束", 400, 350);
            }
            
            g.setColor(gridColor);
            g.drawString("任意点击开始游戏", 370, 420);
            g.drawString("请使用上下左右控制方块", 340, 470);
        }
    }

    /**
     * 绘制单个方块
     */
    void drawTile(Graphics2D g, int r, int c) {
        int value = tiles[r][c].getValue();
        
        // 根据方块值计算颜色索引
        int colorIndex = (int) (Math.log(value) / Math.log(2)) - 1;
        if (colorIndex < 0) colorIndex = 0;
        if (colorIndex >= colorTable.length) colorIndex = colorTable.length - 1;
        
        g.setColor(colorTable[colorIndex]);
        g.fillRoundRect(215 + c * 121, 115 + r * 121, 106, 106, 7, 7);
        
        // 绘制方块上的数字
        String s = String.valueOf(value);
        g.setColor(value < 128 ? colorTable[0] : Color.WHITE); // 根据数值选择文字颜色
        
        // 文字居中处理
        FontMetrics fm = g.getFontMetrics();
        int asc = fm.getAscent();
        int dec = fm.getDescent();
        int x = 215 + c * 121 + (106 - fm.stringWidth(s)) / 2;
        int y = 115 + r * 121 + (asc + (106 - (asc + dec)) / 2);
        
        g.drawString(s, x, y);
    }

    /**
     * 在随机位置添加新方块
     */
    private void addRandomTile() {
        // 随机选择一个位置
        int pos = rand.nextInt(side * side);
        int row, col;
        
        // 找到空位置
        do {
            pos = (pos + 1) % (side * side);
            row = pos / side;
            col = pos % side;
        } while (tiles[row][col] != null);
        
        // 90%概率生成2,10%概率生成4
        int val = rand.nextInt(10) == 0 ? 4 : 2;
        tiles[row][col] = new Tile(val);
    }

    /**
     * 核心移动逻辑
     * @param countDownFrom 遍历起始位置
     * @param yIncr Y方向移动增量
     * @param xIncr X方向移动增量
     * @return 是否发生了移动
     */
    private boolean move(int countDownFrom, int yIncr, int xIncr) {
        boolean moved = false;
        
        // 遍历所有方块
        for (int i = 0; i < side * side; i++) {
            int j = Math.abs(countDownFrom - i);
            int r = j / side;
            int c = j % side;
            
            if (tiles[r][c] == null) continue;
            
            int nextR = r + yIncr;
            int nextC = c + xIncr;
            
            // 在有效范围内移动方块
            while (nextR >= 0 && nextR < side && nextC >= 0 && nextC < side) {
                Tile next = tiles[nextR][nextC];
                Tile curr = tiles[r][c];
                
                if (next == null) {
                    // 移动到空位置
                    if (checkingAvailableMoves) return true;
                    
                    tiles[nextR][nextC] = curr;
                    tiles[r][c] = null;
                    r = nextR;
                    c = nextC;
                    nextR += yIncr;
                    nextC += xIncr;
                    moved = true;
                } else if (next.canMergeWith(curr)) {
                    // 合并相同数字的方块
                    if (checkingAvailableMoves) return true;
                    
                    int value = next.mergeWith(curr);
                    if (value > highest) highest = value;
                    score += value;
                    tiles[r][c] = null;
                    moved = true;
                    break;
                } else {
                    break;
                }
            }
        }
        
        // 移动后处理
        if (moved) {
            if (highest < target) {
                clearMerged();
                addRandomTile();
                
                // 检查游戏是否结束
                if (!movesAvailable()) {
                    gamestate = State.over;
                }
            } else if (highest == target) {
                gamestate = State.won;
            }
        }
        
        return moved;
    }

    // 四个方向的移动方法
    boolean moveUp() {
        return move(0, -1, 0); // 从顶部开始向下移动
    }

    boolean moveDown() {
        return move(side * side - 1, 1, 0); // 从底部开始向上移动
    }

    boolean moveLeft() {
        return move(0, 0, -1); // 从左侧开始向右移动
    }

    boolean moveRight() {
        return move(side * side - 1, 0, 1); // 从右侧开始向左移动
    }

    /**
     * 清除方块的合并状态
     */
    void clearMerged() {
        for (Tile[] row : tiles) {
            for (Tile tile : row) {
                if (tile != null) {
                    tile.setMerged(false);
                }
            }
        }
    }

    /**
     * 检查是否还有可用的移动
     * @return 是否还有移动可能
     */
    boolean movesAvailable() {
        checkingAvailableMoves = true;
        boolean hasMoves = moveUp() || moveDown() || moveLeft() || moveRight();
        checkingAvailableMoves = false;
        return hasMoves;
    }

    /**
     * 方块内部类
     */
    class Tile {
        private boolean merged; // 是否已合并
        private int value;     // 方块数值

        Tile(int val) {
            value = val;
        }

        int getValue() {
            return value;
        }

        void setMerged(boolean m) {
            merged = m;
        }

        /**
         * 检查是否可以与另一个方块合并
         */
        boolean canMergeWith(Tile other) {
            return !merged && other != null && !other.merged && value == other.getValue();
        }

        /**
         * 与另一个方块合并
         * @return 合并后的新值
         */
        int mergeWith(Tile other) {
            if (canMergeWith(other)) {
                value *= 2;
                merged = true;
                return value;
            }
            return -1;
        }
    }

    /**
     * 程序入口
     */
    public static void main(String[] args) {
        // 在事件调度线程中创建GUI
        SwingUtilities.invokeLater(() -> {
            JFrame f = new JFrame();
            f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            f.setTitle("天上星的2048");
            f.setResizable(false);
            f.add(new Game2048(), BorderLayout.CENTER);
            f.pack();
            f.setLocationRelativeTo(null); // 居中显示
            f.setVisible(true);
        });
    }
}

游戏扩展建议

  1. 添加分数显示
// 在drawGrid方法中添加
g.drawString("分数: " + score, 300, 50);
g.drawString("最高分: " + highest, 500, 50);
  1. 增加动画效果
// 使用Swing Timer实现移动动画
Timer animationTimer = new Timer(10, e -> {
    // 更新方块位置
    repaint();
});
  1. 添加音效反馈
// 方块合并时播放音效
Clip mergeSound = AudioSystem.getClip();
mergeSound.open(AudioSystem.getAudioInputStream("merge.wav"));
mergeSound.start();
  1. 实现游戏存档
// 使用Properties保存游戏状态
Properties props = new Properties();
props.setProperty("score", String.valueOf(score));
props.store(new FileWriter("save.dat"), "Game State");
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值