使用java语言编写俄罗斯方块小游戏

本人是javaEE开发,日常工作就是对数据的增删改查。这次想挑战一下用java语言开发小游戏。历时三天时间终于开发出了一个简陋版的俄罗斯方块小游戏。特此把我的开发过程分享给大家。

首先给大家看看我的游戏界面效果图:

俄罗斯方块游戏效果图
首先分析一下我想实现的俄罗斯方块小游戏的需求:
1、游戏界面分为三个模块:主界面、下一个方块提示界面、得分界面。
2、主界面由20×15个正方形方格组成,共300个小方格;下一个方块提示界面展示下一个即将出现的方格;得分界面展示得分
3、共有7种方块类型,分别为:长条形、T形、L形及其镜面形状、Z形及其镜面形状、正方形。
4、游戏开始,方块不断向下移动,触底或碰到其他方块则固定方块。
5、若方块形成整行,则消除该行,上面的方块整体向下移动一格。

需求梳理完,正式开始开发:
1、开发窗体控件的代码:

public class TetrisFrame extends JFrame {
    //定义游戏窗口的标题
    public static final String TITLE = "俄罗斯方块";
    //定义游戏窗口的宽度和高度
    public static final int WIDTH = 600;
    public static final int HEIGHT = 800;
    //定义游戏面板对象
    private TetrisPanel tetrisPanel;
    //下一个方块面板对象
    private NextBlockPanel nextBlockPanel;
    //得分面板对象
    private ScorePanel scorePanel;

    public static void main(String[] args) {
        //创建一个游戏窗口对象
        TetrisFrame tetrisFrame = new TetrisFrame();
        tetrisFrame.setVisible(true);
    }

    //构造方法,初始化游戏窗口
    public TetrisFrame() {
        //设置游戏窗口的标题
        setTitle(TITLE);
        //设置游戏窗口的大小
        setSize(WIDTH, HEIGHT);
        //设置游戏窗口的位置居中
        setLocationRelativeTo(null);
        //设置游戏窗口的关闭方式为退出程序
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        //设置游戏窗口的布局为边界布局
        setLayout(new BorderLayout());
        //初始化下一个方块面板对象,并添加到游戏窗口的东边位置
        nextBlockPanel = new NextBlockPanel();
        add(nextBlockPanel, BorderLayout.EAST);
        //初始化得分面板对象,并添加到游戏窗口的南边位置
        scorePanel = new ScorePanel();
        add(scorePanel, BorderLayout.SOUTH);
        //初始化游戏面板对象,并添加到游戏窗口的中间位置
        tetrisPanel = new TetrisPanel(nextBlockPanel,scorePanel);
        add(tetrisPanel, BorderLayout.CENTER);
        //设置游戏窗口可见
        setVisible(true);
    }
}

2、定义方块类

/**
 * 定义方块类
 */
public class Block {
    //当前方块的形状,用二维数组表示
    private int[][] shape;
    //方块的颜色
    private Color color;

    //构造方法,根据给定的形状和颜色创建方块
    public Block(int[][] shape, Color color) {
        this.shape = shape;
        this.color = color;
        //随机旋转(0-3次)
        Random random = new Random();
        int rotateTime = random.nextInt(4);
        for (int i = 0; i < rotateTime; i++) {
            this.shape = rotate(this.shape);
        }
    }

    //获取方块的形状
    public int[][] getShape() {
        return shape;
    }

    //获取方块的形状
    public void setShape(int[][] shape) {
        this.shape = shape;
    }

    //获取方块的颜色
    public Color getColor() {
        return color;
    }

    //方块向左移动一格
    public void moveLeft() {
        if(shape.length > 0){
            for (int i = 0; i < shape.length; i++) {
                shape[i][0] = shape[i][0] - 1;
            }
        }
    }

    //方块向右移动一格
    public void moveRight() {
        if(shape.length > 0){
            for (int i = 0; i < shape.length; i++) {
                shape[i][0] = shape[i][0] + 1;
            }
        }
    }

    //方块向下移动一格
    public void moveDown() {
        if(shape.length > 0){
            for (int i = 0; i < shape.length; i++) {
                shape[i][1] = shape[i][1] + 1;
            }
        }
    }


    /**
     * 实现俄罗斯方块顺时针旋转90度的功能
     * 可以使旋转后的方块坐标的横坐标的最小值和纵坐标的最小值与旋转之前的相同。
     * 接受一个二维数组作为参数,该数组表示待替换的方块坐标。
     * @param block
     * @return
     */
    public static int[][] rotate(int[][] block) {
        //如果传入的数组为空或者长度不为4,返回null
        if (block == null || block.length != 4) {
            return null;
        }
        //创建一个新的二维数组,用于存储旋转后的坐标
        int[][] result = new int[4][2];
        //计算传入的方块的中心点坐标
        int centerX = (block[0][0] + block[1][0] + block[2][0] + block[3][0]) / 4;
        int centerY = (block[0][1] + block[1][1] + block[2][1] + block[3][1]) / 4;
        //遍历传入的方块的每个点,计算旋转后的坐标,并存入新的数组
        for (int i = 0; i < 4; i++) {
            //计算当前点相对于中心点的偏移量
            int offsetX = block[i][0] - centerX;
            int offsetY = block[i][1] - centerY;
            //根据顺时针旋转90度的公式,计算旋转后的偏移量
            int newOffsetX = -offsetY;
            int newOffsetY = offsetX;
            //根据旋转后的偏移量,计算旋转后的坐标,并存入新的数组
            result[i][0] = centerX + newOffsetX;
            result[i][1] = centerY + newOffsetY;
        }
        //计算旋转后的方块的最小横纵坐标和
        int minX = Math.min(Math.min(result[0][0], result[1][0]), Math.min(result[2][0], result[3][0]));
        int minY = Math.min(Math.min(result[0][1], result[1][1]), Math.min(result[2][1], result[3][1]));
        //计算旋转前的方块的最小横纵坐标和
        int oldMinX = Math.min(Math.min(block[0][0], block[1][0]), Math.min(block[2][0], block[3][0]));
        int oldMinY = Math.min(Math.min(block[0][1], block[1][1]), Math.min(block[2][1], block[3][1]));
        //计算两者之间的差值
        int diffX = oldMinX - minX;
        int diffY = oldMinY - minY;
        //如果有差值,就将旋转后的方块平移相应的距离,使得最小横纵坐标和保持不变
        if (diffX != 0 || diffY != 0) {
            for (int i = 0; i < 4; i++) {
                result[i][0] += diffX;
                result[i][1] += diffY;
            }
        }
        //返回旋转后的方块的坐标
        return result;
    }

}

3、定义游戏主面板类

/**
 * 定义游戏面板类,继承自JPanel类,实现KeyListener接口
 */
public class TetrisPanel extends JPanel implements KeyListener {
    //定义俄罗斯方块中的所有方块形状类型集合
    protected static java.util.List<int[][]> shapeList;
    protected static java.util.List<Color> colorList;
    protected static int colorIndex = 0;
    static{
        //默认长条形方块形状A
        int[][] BLOCK_SHAPE_STICK_A = {{0,0},{0,1},{0,2},{0,3}};
        //默认长条形方块形状B
        int[][] BLOCK_SHAPE_STICK_B = {{0,0},{1,0},{2,0},{3,0}};
        //默认L形方块形状A
        int[][] BLOCK_SHAPE_L_A = {{0,0},{0,1},{0,2},{1,2}};
        //默认L形方块形状B
        int[][] BLOCK_SHAPE_L_B = {{0,2},{1,0},{1,1},{1,2}};
        //默认T形方块形状A
        int[][] BLOCK_SHAPE_T_A = {{0,0},{1,0},{1,1},{2,0}};
        //默认T形方块形状B
        int[][] BLOCK_SHAPE_T_B = {{0,1},{1,0},{1,1},{2,1}};
        //默认Z形方块形状A
        int[][] BLOCK_SHAPE_Z_A = {{0,0},{1,0},{1,1},{2,1}};
        //默认Z形方块形状B
        int[][] BLOCK_SHAPE_Z_B = {{0,1},{1,0},{1,1},{2,0}};
        //默认正方形方块形状
        int[][] BLOCK_SHAPE_SQUARE_A = {{0,0},{0,1},{1,0},{1,1}};
        //默认正方形方块形状B,一样,但为了每种方块类型出现频率相同,正方形也搞两份
        int[][] BLOCK_SHAPE_SQUARE_B = {{0,0},{0,1},{1,0},{1,1}};
        //将所有方块种类放入集合中
        shapeList = Arrays.asList(BLOCK_SHAPE_STICK_A, BLOCK_SHAPE_STICK_B, BLOCK_SHAPE_L_A, BLOCK_SHAPE_L_B, BLOCK_SHAPE_T_A, BLOCK_SHAPE_T_B, BLOCK_SHAPE_Z_A, BLOCK_SHAPE_Z_B, BLOCK_SHAPE_SQUARE_A, BLOCK_SHAPE_SQUARE_B);
        //定义方块颜色
        colorList = new ArrayList<>();
        colorList.add(Color.RED);
        colorList.add(Color.YELLOW);
        colorList.add(Color.GREEN);
        colorList.add(Color.BLUE);
        colorList.add(Color.ORANGE);
        colorList.add(Color.magenta);
        colorList.add(Color.PINK);
    }
    //定义游戏面板的宽度和高度
    public static final int WIDTH = 450;
    public static final int HEIGHT = 600;
    //定义游戏面板的背景颜色
    public static final Color BACKGROUND = Color.BLACK;
    //定义游戏面板的网格颜色
    public static final Color GRID = Color.GRAY;
    //定义方块的大小
    public static final int BLOCK_SIZE = 30;
    //定义游戏面板的行数和列数
    public static final int ROWS = HEIGHT / BLOCK_SIZE;
    public static final int COLS = WIDTH / BLOCK_SIZE;
    //定义游戏面板上的方块数组,用二维数组表示
    private Block[][] blocks;
    //定义当前下落的方块
    private Block currentBlock;
    //定义下一个要出现的方块
    protected static Block nextBlock;
    //定义游戏是否开始的标志
    private boolean isStarted;
    //定义游戏是否暂停的标志
    private boolean isPaused;
    //定义游戏是否结束的标志
    private boolean isOver;
    //下一个方块面板
    private NextBlockPanel nextBlockPanel;
    //得分面板
    private ScorePanel scorePanel;

    //构造方法,初始化游戏面板
    public TetrisPanel(NextBlockPanel nextBlockPanel,ScorePanel scorePanel) {
        this.nextBlockPanel = nextBlockPanel;
        this.scorePanel = scorePanel;
        //设置游戏面板的大小
        setPreferredSize(new Dimension(WIDTH, HEIGHT));
        //设置游戏面板的背景颜色
        setBackground(BACKGROUND);
        //设置游戏面板可以获取键盘焦点
        setFocusable(true);
        //添加键盘监听器
        addKeyListener(this);
        //初始化方块数组
        blocks = new Block[COLS][ROWS];
        for (int i = 0; i < COLS; i++) {
            for (int j = 0; j < ROWS; j++) {
                blocks[i][j] = null;
            }
        }
        //初始化当前方块和下一个方块,随机生成形状和颜色
        currentBlock = generateRandomBlock();
        nextBlock = generateRandomBlock();
        //初始化游戏状态和得分
        isStarted = false;
        isPaused = false;
        isOver = false;
    }

    //随机生成一个方块,返回一个Block对象
    public Block generateRandomBlock() {
        //随机获取一种俄罗斯方块形状
        int[][] shape = shapeList.get(new Random().nextInt(shapeList.size()));
        //按顺序从颜色池中获取颜色
        if(colorIndex == colorList.size() - 1){
            //按顺序获取颜色,当取完列表中的颜色时,将获取颜色列表的索引重置。
            colorIndex = 0;
        }
        Color color = colorList.get(colorIndex);
        colorIndex++;
        //生成一个随机的方块对象,返回给调用者。
        Block block = new Block(shape,color);
        //为营造出掉落感,将生成方块的最下方方块展示在第一行,最后一行以上方块坐标改为负坐标。
        block.setShape(hideShape(block.getShape()));
        return block;
    }

    //绘制游戏面板上的内容,重写JPanel类的paintComponent方法
    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        //设置绘制颜色为背景颜色
        g.setColor(BACKGROUND);
        //绘制一个填充的矩形,作为游戏面板的背景
        g.fillRect(0, 0, WIDTH, HEIGHT);
        //设置绘制颜色为网格颜色
        g.setColor(GRID);
        //使用一个双重循环来遍历行和列,绘制每个小格的边框
        for (int i = 0; i < ROWS; i++) {
            for (int j = 0; j < COLS; j++) {
                //计算每个小格的左上角坐标
                int x = j * BLOCK_SIZE;
                int y = i * BLOCK_SIZE;
                //绘制一个空心的矩形,作为每个小格的边框
                g.drawRect(x, y, BLOCK_SIZE, BLOCK_SIZE);
            }
        }
        //绘制当前存在的所有块
        for (int i = 0; i < blocks.length; i++) {
            for (int j = 0; j < blocks[i].length; j++) {
                if(blocks[i][j] != null){
                    //循环当前存在的块,绘制出图案
                    if(blocks[i][j].getShape() != null){
                        Color color = blocks[i][j].getColor();
                        //获取存在的小格的横纵坐标
                        int x = i * BLOCK_SIZE;
                        int y = j * BLOCK_SIZE;
                        //设置颜色
                        g.setColor(color);
                        g.fillRect(x,y,BLOCK_SIZE,BLOCK_SIZE);
                    }
                }
            }
        }
        //绘制当前掉落的色块
        int[][] currentBlockShape = currentBlock.getShape();
        Color currentBlockColor = currentBlock.getColor();
        for (int i = 0; i < currentBlockShape.length; i++) {
            //计算每个小格的左上角坐标
            int x = currentBlockShape[i][0] * BLOCK_SIZE;
            int y = currentBlockShape[i][1] * BLOCK_SIZE;
            //设置颜色
            g.setColor(currentBlockColor);
            g.fillRect(x,y,BLOCK_SIZE,BLOCK_SIZE);
        }
    }

    //开始游戏,设置游戏状态为开始,并创建一个定时器来控制方块下落和刷新界面
    public void startGame() {
        isStarted = true;
        Timer timer = new Timer(250, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (isStarted && !isPaused && !isOver) {
                    if(canMoveDown(currentBlock)){
                        //控制方块下落和刷新界面。
                        currentBlock.moveDown();
                    }
                    repaint();
                }
            }
        });
        timer.start();
    }

    //暂停游戏,设置游戏状态为暂停,并显示提示信息
    public void pauseGame() {
        isPaused = true;
        repaint();
    }

    //继续游戏,设置游戏状态为继续,并显示提示信息
    public void resumeGame() {
        isPaused = false;
        repaint();
    }

    //结束游戏,设置游戏状态为结束,并显示提示信息
    public void endGame() {
        isOver = true;
        scorePanel.setOver(true);
        scorePanel.repaint();
    }

    //检测方块是否可以向左移动,返回一个布尔值
    public boolean canMoveLeft(Block block) {
        //检测方块是否可以向左移动,返回一个布尔值
        //检测方块如果继续左移是否会导致方块碰撞
        for (int i = 0; i < blocks.length; i++) {
            for (int j = 0; j < blocks[i].length; j++) {
                if(blocks[i][j] != null){
                    for (int k = 0; k < block.getShape().length; k++) {
                        if(block.getShape()[k][0] - 1  == i && block.getShape()[k][1] == j){
                            //不可向左移动
                            return false;
                        }
                    }
                }
            }
        }
        //判断方块最左边是否到边了
        for (int i = 0; i < block.getShape().length; i++) {
            if(block.getShape()[i][0] == 0){
                //方块到了最左边,不可向左移动
                return false;
            }
        }
        return true;
    }

    //检测方块是否可以向右移动,返回一个布尔值
    public boolean canMoveRight(Block block) {
        //检测方块是否可以向右移动,返回一个布尔值
        //检测方块如果继续下移是否会导致方块碰撞
        for (int i = 0; i < blocks.length; i++) {
            for (int j = 0; j < blocks[i].length; j++) {
                if(blocks[i][j] != null){
                    for (int k = 0; k < block.getShape().length; k++) {
                        if(block.getShape()[k][0] + 1 == i && block.getShape()[k][1] == j){
                            //不可向右移动
                            return false;
                        }
                    }
                }
            }
        }
        //判断方块最右边是否到边了
        for (int i = 0; i < block.getShape().length; i++) {
            if(block.getShape()[i][0] == COLS - 1){
                //方块到了最右边,不可向右移动
                return false;
            }
        }
        return true;
    }

    //检测方块是否可以向下移动,返回一个布尔值
    public boolean canMoveDown(Block block) {
        //检测方块如果继续下移是否会导致方块冲突碰撞
        for (int i = 0; i < blocks.length; i++) {
            for (int j = 0; j < blocks[i].length; j++) {
                if(blocks[i][j] != null){
                    for (int k = 0; k < block.getShape().length; k++) {
                        if(block.getShape()[k][0] == i && block.getShape()[k][1] + 1 == j){
                            //不可向下移动,该方块成型,固定该方块
                            fixBlock(block);
                            return false;
                        }
                    }
                }
            }
        }
        //判断方块最下层是否触底
        for (int i = 0; i < block.getShape().length; i++) {
            if(block.getShape()[i][1] == ROWS - 1){
                //触底,不可向下移动,该方块成型,固定该方块
                fixBlock(block);
                return false;
            }
        }
        return true;
    }

    //检测方块是否可以旋转,返回一个布尔值
    public boolean canRotate(Block block) {
        //检测方块是否可以旋转,返回一个布尔值
        //将方块旋转,检测是否有方块冲突碰撞
        int[][] rotatedBlock = Block.rotate(block.getShape());
        //判断旋转后是否越界
        for (int k = 0; k < rotatedBlock.length; k++) {
            if(rotatedBlock[k][0] < 0 || rotatedBlock[k][0] > (WIDTH/BLOCK_SIZE) - 1){
                return false;
            }
            if(rotatedBlock[k][1] > (HEIGHT/BLOCK_SIZE) - 1){
                return false;
            }
        }
        for (int i = 0; i < blocks.length; i++) {
            for (int j = 0; j < blocks[i].length; j++) {
                if(blocks[i][j] != null){
                    for (int k = 0; k < rotatedBlock.length; k++) {
                        if(rotatedBlock[k][0] == i && rotatedBlock[k][1] == j){
                            //旋转后有方块冲突,不可旋转
                            return false;
                        }
                    }
                }
            }
        }
        return true;
    }

    //将当前方块固定到游戏面板上,更新方块数组
    public void fixBlock(Block block) {
        //将当前方块固定到游戏面板上,更新方块数组
        for (int i = 0; i < block.getShape().length; i++) {
            //固定当前方块的横坐标
            int x = block.getShape()[i][0];
            //固定当前方块的纵坐标
            int y = block.getShape()[i][1];
            //若更新后的方块数组中存在负数坐标,说明超出游戏界面,游戏失败
            if(x < 0 || y < 0){
                //游戏失败,结束游戏
                endGame();
                return;
            }
            blocks[x][y] = block;
        }
        //固定方块后,消除整行的方块
        eliminateLines();
        //新方块登场,并生成候选方块
        currentBlock = nextBlock;
        nextBlock = generateRandomBlock();
        nextBlockPanel.repaint();
    }

    //消除满行,更新游戏面板和得分
    public void eliminateLines() {
        //记录已满行的行数,最多同时满20行
        java.util.List<Integer> fullLineNumberList = new ArrayList<>();
        //消除满行,更新游戏面板和得分
        for (int j = ROWS - 1; j >= 0; j--) {
            for (int i = 0; i < COLS; i++) {
                if(blocks[i][j] == null || blocks[i][j].getShape() == null){
                    //从第一列最下面那行开始循环所有方块,一单有一个方块为空则终止循环
                    break;
                }
                if(i == COLS - 1){
                    //若循环到最后一列最后一行,方块都有值,则该行为满行
                    fullLineNumberList.add(j);
                }
            }
        }
        if(fullLineNumberList.size() > 0){
            for (Integer fullLineNumber : fullLineNumberList) {
                //循环已满行数组,消除满行
                for (int i = 0; i < COLS; i++) {
                    blocks[i][fullLineNumber] = null;
                }
                //从被消行的上一行开始,将每行的元素向下移动一格
                for (Integer j = fullLineNumber; j > 0; j--) {
                    for (int i = 0; i < COLS - 1; i++) {
                        blocks[i][j] = blocks[i][j-1];
                    }
                }
            }
            //获取单次消除的行数
            int eliminateAmount = fullLineNumberList.size();
            //消除了整行,新增分数:5的消除行数次方。
            //单次消除1、2、3、4行,新增的得分分别为:5分、25分、125分、625分
            scorePanel.addScore((int)Math.pow(5,eliminateAmount));
            scorePanel.repaint();
        }
    }

    /**
     * 获得了一个方块对象坐标之后,我想让这个方块的最下方的方块展现在我的可视化网格中的第一行,
     * 也就是说凡是最后一行以上的方块区域,坐标都为负数
     * 比如一个长条形的方块坐标{{0,0},{0,1},{0,2},{0,3}},它的最下方的方块为{0,3}
     * 我为了创造出掉落感,最初想要只在屏幕区域展示改长条形方块的最下方的那个方块
     * 也就是说要将该方块的坐标改为{{0,-3},{0,-2},{0,-1},{0,0}}
     * 随着多次刷新界面,方块才正式进入网格区域。
     * @param block
     * @return
     */
    public static int[][] hideShape(int[][] block) {
        int[][] result = new int[block.length][2];
        int maxY = Integer.MIN_VALUE;
        for (int i = 0; i < block.length; i++) {
            result[i][0] = block[i][0];
            result[i][1] = block[i][1];
            maxY = Math.max(maxY, result[i][1]);
        }
        for (int i = 0; i < block.length; i++) {
            result[i][1] -= maxY;
        }
        return result;
    }

    @Override
    public void keyTyped(KeyEvent e) {

    }

    //处理键盘按下事件,重写KeyListener接口的keyPressed方法
    @Override
    public void keyPressed(KeyEvent e) {
        //获取按下的键盘码
        int keyCode = e.getKeyCode();
        //根据不同的键盘码执行不同的操作
        switch (keyCode) {
            case KeyEvent.VK_SPACE: //空格键
                if (!isStarted) { //如果游戏未开始,则开始游戏
                    startGame();
                } else if (isPaused) { //如果游戏已暂停,则继续游戏
                    resumeGame();
                } else { //如果游戏进行中,则暂停游戏
                    pauseGame();
                }
                break;
            case KeyEvent.VK_LEFT: //左箭头键
                if (isStarted && !isPaused && !isOver && canMoveLeft(currentBlock)) { //如果游戏进行中且方块可以向左移动,则移动方块
                    currentBlock.moveLeft();
                    repaint();
                }
                break;
            case KeyEvent.VK_RIGHT: //右箭头键
                if (isStarted && !isPaused && !isOver && canMoveRight(currentBlock)) { //如果游戏进行中且方块可以向右移动,则移动方块
                    currentBlock.moveRight();
                    repaint();
                }
                break;
            case KeyEvent.VK_DOWN: //下箭头键
                if (isStarted && !isPaused && !isOver && canMoveDown(currentBlock)) { //如果游戏进行中且方块可以向下移动,则移动方块
                    currentBlock.moveDown();
                    repaint();
                }
                break;
            case KeyEvent.VK_UP: //上箭头键
                if (isStarted && !isPaused && !isOver && canRotate(currentBlock)) {
                    //如果游戏进行中且方块可以旋转,则旋转方块
                    currentBlock.setShape(Block.rotate(currentBlock.getShape()));
                    repaint();
                }
                break;
            default:
                break;
        }
    }

    @Override
    public void keyReleased(KeyEvent e) {

    }
}

4、定义下一个方块面板类

public class NextBlockPanel  extends JPanel {
    //定义下一个方块面板的宽度和高度
    public static final int WIDTH = 100;
    public static final int HEIGHT = 100;
    //定义方块的大小
    public static final int BLOCK_SIZE = 20;
    //定义下一个方块面板的背景颜色
    public static final Color BACKGROUND = Color.BLACK;
    //定义下一个方块对象
    private Block nextBlock;

    //构造方法,初始化下一个方块面板
    public NextBlockPanel() {
        //设置下一个方块面板的大小
        setPreferredSize(new Dimension(WIDTH, HEIGHT));
        //设置下一个方块面板的背景颜色
        setBackground(BACKGROUND);
        //初始化下一个方块对象,随机生成形状和颜色
        nextBlock = generateRandomBlock();
    }

    //随机生成一个方块,返回一个Block对象
    public Block generateRandomBlock() {
        //随机获取一种俄罗斯方块形状
        int[][] shape = TetrisPanel.shapeList.get(new Random().nextInt(TetrisPanel.shapeList.size()));
        //按顺序从颜色池中获取颜色
        if(TetrisPanel.colorIndex == TetrisPanel.colorList.size() - 1){
            //按顺序获取颜色,当取完列表中的颜色时,将获取颜色列表的索引重置。
            TetrisPanel.colorIndex = 0;
        }
        Color color = TetrisPanel.colorList.get(TetrisPanel.colorIndex);
        TetrisPanel.colorIndex++;
        //生成一个随机的方块对象,返回给调用者。
        Block block = new Block(shape,color);
        //为营造出掉落感,将生成方块的最下方方块展示在第一行,最后一行以上方块坐标改为负坐标。
        block.setShape(TetrisPanel.hideShape(block.getShape()));
        return block;
    }

    //获取下一个方块对象,返回一个Block对象
    public Block getNextBlock() {
        return nextBlock;
    }

    //设置下一个方块对象,根据给定的Block对象更新下一个方块
    public void setNextBlock(Block block) {
        nextBlock = block;
    }

    //绘制下一个方块面板上的内容,重写JPanel类的paintComponent方法
    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        nextBlock = TetrisPanel.nextBlock;
        //绘制提示文字
        g.setColor(Color.WHITE);
        g.setFont(new Font("微软雅黑", Font.BOLD, 18));
        //绘制标题和提示信息
        g.drawString("下一个方块:", 0, 30);
        //绘制下一个方块
        int[][] nextBlockShape = nextBlock.getShape();
        Color nextBlockShapeColor = nextBlock.getColor();
        for (int i = 0; i < nextBlockShape.length; i++) {
            //计算每个小格的左上角坐标
            int x = nextBlockShape[i][0] * BLOCK_SIZE;
            int y = nextBlockShape[i][1] * BLOCK_SIZE + 100;
            //设置颜色
            g.setColor(nextBlockShapeColor);
            g.fillRect(x,y,BLOCK_SIZE,BLOCK_SIZE);
        }

    }
}```
5、定义得分面板类

```java
public class ScorePanel  extends JPanel {
    //定义得分面板的宽度和高度
    public static final int WIDTH = 300;
    public static final int HEIGHT = 100;
    //定义得分面板的背景颜色
    public static final Color BACKGROUND = Color.BLACK;
    //定义得分面板的文字颜色
    public static final Color TEXT = Color.WHITE;
    //定义得分面板的文字字体
    public static final Font FONT = new Font("Arial", Font.BOLD, 20);
    //定义游戏的得分
    private int score;
    //游戏是否结束
    private Boolean isOver = false;

    //构造方法,初始化得分面板
    public ScorePanel() {
        //设置得分面板的大小
        setPreferredSize(new Dimension(WIDTH, HEIGHT));
        //设置得分面板的背景颜色
        setBackground(BACKGROUND);
        //初始化游戏得分为0
        score = 0;
    }

    //获取游戏得分,返回一个整数值
    public int getScore() {
        return score;
    }

    //设置游戏得分,根据给定的整数值更新游戏得分
    public void setScore(int score) {
        this.score = score;
    }

    //增加游戏得分,根据给定的整数值增加游戏得分
    public void addScore(int score) {
        this.score += score;
    }

    public Boolean getOver() {
        return isOver;
    }

    public void setOver(Boolean over) {
        isOver = over;
    }

    //绘制得分面板上的内容,重写JPanel类的paintComponent方法
    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        if(!isOver){
            //绘制得分面板上的内容,包括背景、得分和提示信息。
            g.setColor(Color.WHITE);
            g.setFont(new Font("微软雅黑", Font.BOLD, 18));
            //绘制标题和提示信息
            g.drawString("当前得分:", 30, 30);
            g.drawString(String.valueOf(score),130,30);
        }else{
            //绘制得分面板上的内容,包括背景、得分和提示信息。
            g.setColor(Color.WHITE);
            g.setFont(new Font("微软雅黑", Font.BOLD, 18));
            //绘制标题和提示信息
            g.drawString("游戏结束,您的最终得分为:", 30, 30);
            g.drawString(String.valueOf(score),270,30);
        }
    }
}

游戏运行的原理:运行TetrisFrame类的main方法,程序会将定义好的游戏面板展示出来,单击空格键,程序开启定时器,每秒向下移动4格,触底或触碰其他方块时固定。
每次固定方块后,程序判断是否已满行,若满行则消除该行,加分并将上方的方块下移。

本来想从头到尾详细分析一下方块掉落、旋转、移动和得分之类的机制,但奈何时间精力以及能力有限,先暂时把代码贴上来,后面有时间再补吧。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值