【Swing】——俄罗斯方块

一、前言


该俄罗斯方块版本是教主第二次写的,也算是一次重构,主要有三个目标:

  1. 一是“大道至简”,用最简单的逻辑实现俄罗斯方块的功能;
  2. 二是“逻辑与视图分离”,解开游戏运行的逻辑与Swing渲染的耦合;
  3. 三是记录Swing,记录一下Swing的简单使用方法。



二、设计


我们发现大部分俄罗斯方块都是由10x20的小格子组成,然后有7种类型的方块,每种类型方块的对应1~4种可以由旋转而得到的形状。

于是我们可以设置一个10x20的布尔矩阵作为全局矩阵,然后以4x4的布尔矩阵来实现方块。方块的移动即是方块矩阵在全局矩阵上的偏移;方块的旋转即是单位矩阵的旋转。方块的创建可以采用原型模型,创建7种方块的原型,新方块都克隆自这7种原型方块。


建模

综上,俄罗斯方块游戏需要的类有:

  • 布尔矩阵(BoolMatrix):将二维数组封装为矩阵,以方便其它类的访问。
  • 方块(Tetris
  • 抽象游戏逻辑接口(GameHandler):定义数据获取和事件处理的接口,目的是为了以接口的方式解开逻辑与渲染的耦合。
  • 具体游戏逻辑(Game):继承自GameHandler,实现具体的游戏逻辑。
  • 游戏主窗口(GameView):继承自JFrame,组织各种组件以及负责事件的监听。
  • 面板GamePanel:继承自JPanel,负责渲染界面。
  • 入口类App

预览

俄罗斯方块预览


疑问

1、为什么要写这么多类?

可以发现,俄罗斯方块本身的逻辑其实也就哪些,而这些逻辑好像又的确与视图的渲染没什么直接的关系。

也就是说,只要定义好俄罗斯方块游戏逻辑的访问接口(包括但不限于事件的处理、数据的读取),任何按照一定约定来访问该接口的渲染组件,都能够产生正确的交互并渲染出正确的界面。

2、为什么抽象游戏逻辑接口(GameHandler)中的全局矩阵的宽高为12x22?

大道至简。

如果设置为10x20个小格子,那么除了碰撞问题还要处理移动越界的问题。而如果加一层值为1的边框,那么就可以统一的用碰撞问题去处理。

3、统一用一种方法判断会不会降低效率?

会。

但即便这样设计会增加不必要的循环和判断,视图约定以0.5秒的间隔去刷新和访问,对于逻辑运算和界面渲染来说也已经绰绰有余。另外Swing中的监听是单独的线程,但是触发的事件会被放入队列中,不存在多线程访问的同步问题。

4、一直创建方块会不会增加内存开销?

会。

可以使用享元模式来管理方块。将创建的方块放入享元工厂中就不用一直创建方块了。




三、代码


布尔矩阵

  • 为了支持方块的原型模式,布尔矩阵需要实现Cloneable接口并重写Clone()方法,对内置的二维数组进行深克隆。
  • 并且方块都是4阶单位矩阵,通过对单位矩阵进行旋转操作来实现方块的形状变化。
  • 对于方块的碰撞和越界,将全局矩阵加一层值为1的格子就可以统一用碰撞问题来处理,具体到代码就是判断方块矩阵经偏移后是否与全局矩阵有重合点(其中偏移量对应方块的x、y坐标)。
  • 对于方块更替时的处理,可以先将方块矩阵经偏移后或运算到全局矩阵中,然后再将当前方块的引用指向下一个方块。

在这里插入图片描述

public class BoolMatrix implements Cloneable {
    // 内置二维数组
    private byte[][] array;
    // 矩阵的行数
    private int      rows;
    // 矩阵的列数
    private int      columns;

    public BoolMatrix(int rows, int columns) {
        this.rows = rows;
        this.columns = columns;
        this.array = new byte[rows][columns];
    }

    /**
     * 对于传入的二维数组将会进行复制操作以使得矩阵不依赖实参
     * @param array 二维数组
     */
    public BoolMatrix(byte[][] array) {
        this.rows = array.length;
        this.columns = array[0].length;
        this.array = new byte[this.rows][this.columns];
        for (int i = 0; i < this.rows; i++) {
            System.arraycopy(array[i], 0, this.array[i], 0, this.columns);
        }
    }

    /**
     * 判断源矩阵经偏移后是否与目标矩阵重合
     * @param source  源矩阵
     * @param target  目标矩阵
     * @param xOffset 源矩阵在目标矩阵上的水平偏移量
     * @param yOffset 源矩阵在目标矩阵上的垂直偏移量
     * @return 源矩阵经偏移后是否与目标矩阵重合
     */
    public static boolean overlapped(BoolMatrix source, BoolMatrix target, int xOffset, int yOffset) {
        for (int i = 0; i < source.rows; i++) {
            for (int j = 0; j < source.columns; j++) {
                if (source.get(i, j) == 1 && target.get(i + yOffset, j + xOffset) == 1) {
                    return true;
                }
            }
        }
        return false;
    }


    /**
     * 将源矩阵经偏移后或运算到目标矩阵上
     * 修改的是目标矩阵
     * @param source  源矩阵
     * @param target  目标矩阵
     * @param xOffset 源矩阵在目标矩阵上的水平偏移量
     * @param yOffset 源矩阵在目标矩阵上的垂直偏移量
     */
    public static void or(BoolMatrix source, BoolMatrix target, int xOffset, int yOffset) {
        for (int i = 0; i < source.rows; i++) {
            for (int j = 0; j < source.columns; j++) {
                if (source.get(i, j) == 1) {
                    target.set(i + yOffset, j + xOffset, (byte) 1);
                }
            }
        }
    }

    public byte get(int i, int j) {
        return this.array[i][j];
    }

    public void set(int i, int j, byte value) {
        this.array[i][j] = value;
    }

    /**
     * 矩阵右旋
     * @throws Exception 行列不相等的矩阵不支持旋转
     */
    public void rotateRight() throws Exception {
        if (this.rows != this.columns) {
            throw new Exception("Rotate not supported");
        } else {
            int len = this.rows;
            for (int i = 0; i < len; i++) {
                for (int j = 0; j < len - i; j++) {
                    byte temp = this.array[i][j];
                    this.array[i][j] = this.array[len - j - 1][len - i - 1];
                    this.array[len - j - 1][len - i - 1] = temp;
                }
            }
            for (int i = 0; i < len / 2; i++) {
                for (int j = 0; j < len; j++) {
                    byte temp = this.array[i][j];
                    this.array[i][j] = this.array[len - i - 1][j];
                    this.array[len - i - 1][j] = temp;
                }
            }
        }
    }

    /**
     * 矩阵左旋
     * @throws Exception 行列不相等的矩阵不支持旋转
     */
    public void rotateLeft() throws Exception {
        if (this.rows != this.columns) {
            throw new Exception("Rotate not supported");
        } else {
            int len = this.rows;
            for (int i = 0; i < len; i++) {
                for (int j = i; j < len; j++) {
                    byte temp = this.array[i][j];
                    this.array[i][j] = this.array[j][i];
                    this.array[j][i] = temp;
                }
            }
            for (int i = 0; i < len; i++) {
                for (int j = 0; j < len / 2; j++) {
                    byte temp = this.array[i][j];
                    this.array[i][j] = this.array[i][len - j - 1];
                    this.array[i][len - j - 1] = temp;
                }
            }
        }
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        BoolMatrix bsm = (BoolMatrix) super.clone();
        bsm.array = bsm.array.clone();
        for (int i = 0; i < bsm.array.length; i++) {
            bsm.array[i] = bsm.array[i].clone();
        }
        return bsm;
    }

    public byte[][] getArray() {
        return array;
    }

    public int getRows() {
        return rows;
    }

    public int getColumns() {
        return columns;
    }
}

方块

  • 将字段定义为package-private包访问权限可减少setter方法。
  • 为了支持原型模式,方块需要:
    1. 实现Cloneable接口并重写clone()方法,对方块进行深克隆。
    2. 创建7种方块原型
    3. 私有化构造方法,以静态工厂的形式提供创建方块的途径,随机创建克隆自7种原型方块的新放方块,并初始化其坐标值。

在这里插入图片描述

import java.awt.*;
import java.util.Random;

public class Tetris implements Cloneable {
    // 7种原型单例
    private static final Tetris RED_PROTOTYPE
            = new Tetris(Color.RED, new BoolMatrix(new byte[][]{{0, 1, 0, 0},
                                                                {0, 1, 0, 0},
                                                                {0, 1, 0, 0},
                                                                {0, 1, 0, 0}}));
    private static final Tetris ORANGE_PROTOTYPE
            = new Tetris(Color.ORANGE, new BoolMatrix(new byte[][]{{0, 0, 0, 0},
                                                                   {0, 1, 1, 0},
                                                                   {0, 1, 1, 0},
                                                                   {0, 0, 0, 0}}));
    private static final Tetris YELLOW_PROTOTYPE
            = new Tetris(Color.YELLOW, new BoolMatrix(new byte[][]{{0, 0, 0, 0},
                                                                   {0, 1, 0, 0},
                                                                   {1, 1, 1, 0},
                                                                   {0, 0, 0, 0}}));
    private static final Tetris GREEN_PROTOTYPE
            = new Tetris(Color.GREEN, new BoolMatrix(new byte[][]{{0, 0, 1, 0},
                                                                  {0, 0, 1, 0},
                                                                  {0, 1, 1, 0},
                                                                  {0, 0, 0, 0}}));
    private static final Tetris CYAN_PROTOTYPE
            = new Tetris(Color.CYAN, new BoolMatrix(new byte[][]{{0, 1, 0, 0},
                                                                 {0, 1, 0, 0},
                                                                 {0, 1, 1, 0},
                                                                 {0, 0, 0, 0}}));
    private static final Tetris BLUE_PROTOTYPE
            = new Tetris(Color.BLUE, new BoolMatrix(new byte[][]{{0, 0, 1, 0},
                                                                 {0, 1, 1, 0},
                                                                 {0, 1, 0, 0},
                                                                 {0, 0, 0, 0}}));
    private static final Tetris MAGENTA_PROTOTYPE
            = new Tetris(Color.MAGENTA, new BoolMatrix(new byte[][]{{0, 1, 0, 0},
                                                                    {0, 1, 1, 0},
                                                                    {0, 0, 1, 0},
                                                                    {0, 0, 0, 0}}));

    // 原型的颜色
    Color      prototype;
    // 矩阵
    BoolMatrix matrix;

    // 当前的坐标/在全局矩阵的偏移量
    int x;
    int y;

    /**
     * 私有化构造方法,通过简单原型工厂方法提供创建新方块的途径
     * @param prototype 原型的颜色
     * @param matrix    矩阵
     */
    private Tetris(Color prototype, BoolMatrix matrix) {
        this.prototype = prototype;
        this.matrix = matrix;
    }

    /**
     * 根据随机原型深克隆创建方块
     * @param x 新方块的x坐标/在全局矩阵的水平偏移量
     * @param y 新方块的y坐标/在全局矩阵的垂直偏移量
     * @return 随机新方块
     */
    public static Tetris createTetris(int x, int y) {
        Tetris t = null;
        try {
            int prototypeId = new Random().nextInt(7);
            switch (prototypeId) {
                case 0:
                    t = (Tetris) RED_PROTOTYPE.clone();
                    break;
                case 1:
                    t = (Tetris) ORANGE_PROTOTYPE.clone();
                    break;
                case 2:
                    t = (Tetris) YELLOW_PROTOTYPE.clone();
                    break;
                case 3:
                    t = (Tetris) GREEN_PROTOTYPE.clone();
                    break;
                case 4:
                    t = (Tetris) CYAN_PROTOTYPE.clone();
                    break;
                case 5:
                    t = (Tetris) BLUE_PROTOTYPE.clone();
                    break;
                case 6:
                    t = (Tetris) MAGENTA_PROTOTYPE.clone();
                    break;
                default:
                    t = null;
            }
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        // switch必定能克隆出方块
        assert t != null;
        t.x = x;
        t.y = y;
        return t;
    }

    public Color getPrototype() {
        return prototype;
    }

    public BoolMatrix getMatrix() {
        return matrix;
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    @Override
    public String toString() {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < this.matrix.getRows(); i++) {
            for (int j = 0; j < this.matrix.getColumns(); j++) {
                if (this.matrix.get(i, j) == 1) {
                    sb.append("◼");
                } else {
                    sb.append("◻");
                }
            }
            sb.append("\n");
        }
        return String.valueOf(sb);
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        Tetris t = (Tetris) super.clone();
        t.matrix = (BoolMatrix) t.matrix.clone();
        return t;
    }
}

抽象游戏逻辑接口

在这里插入图片描述

public interface GameHandler {
    /**
     * 全局矩阵的宽高
     */
    int GLOBAL_WIDTH  = 10 + 2;
    int GLOBAL_HEIGHT = 20 + 2;

    /**
     * 处理向左移动事件
     */
    void handleLeftAction();

    /**
     * 处理向右移动事件
     */
    void handleRightAction();

    /**
     * 处理旋转事件
     */
    void handleRotateAction();

    /**
     * 处理速降事件
     */
    void handleLandAction();

    /**
     * 请求调整(包括下降、清行、更新方块)
     * @return 游戏是否尚未输掉
     */
    boolean requestAdjust();

    /**
     * @return 全局矩阵
     */
    BoolMatrix requestGlobal();

    /**
     * @return 当前方块
     */
    Tetris requestCurrent();

    /**
     * @return 预览方块
     */
    Tetris requestNext();

    /**
     * @return 分数/消行数
     */
    int requestScore();
}

具体游戏逻辑

在这里插入图片描述

public class Game implements GameHandler {
    // 全局矩阵
    private BoolMatrix globalMatrix;
    // 当前方块
    private Tetris     current;
    // 预览方块
    private Tetris     next;
    // 分数/消行数
    private int        score;

    public Game() {
        this.globalMatrix = new BoolMatrix(GLOBAL_HEIGHT, GLOBAL_WIDTH);
        // 边框为1
        for (int i = 0; i < GLOBAL_HEIGHT; i++) {
            for (int j = 0; j < GLOBAL_WIDTH; j++) {
                if ((i == 0 || i == GLOBAL_HEIGHT - 1) 
                 || (j == 0 || j == GLOBAL_WIDTH - 1)) {
                    this.globalMatrix.set(i, j, (byte) 1);
                }
            }
        }
        // 初始化方块
        this.current = Tetris.createTetris(4, 1);
        this.next = Tetris.createTetris(4, 1);
    }

    @Override
    public void handleLeftAction() {
        int preX = this.current.x - 1;
        if (!BoolMatrix.overlapped(this.current.matrix,
                                   this.globalMatrix,
                                   preX,
                                   this.current.y)) {
            this.current.x = preX;
        }
    }


    @Override
    public void handleRightAction() {
        int preX = this.current.x + 1;
        if (!BoolMatrix.overlapped(this.current.matrix,
                                   this.globalMatrix,
                                   preX,
                                   this.current.y)) {
            this.current.x = preX;
        }
    }

    @Override
    public void handleRotateAction() {
        try {
            // 预先右转
            this.current.matrix.rotateRight();
            if (BoolMatrix.overlapped(this.current.matrix,
                                      this.globalMatrix,
                                      this.current.x,
                                      this.current.y)) {
                // 重合则撤销
                this.current.matrix.rotateLeft();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void handleLandAction() {
        // 最长的距离为"一"型的方块将要重合的距离
        int maxDistance = GLOBAL_HEIGHT - this.current.y - 1;
        for (int i = 0; i <= maxDistance; i++) {
            int preY = this.current.y + i;
            if (BoolMatrix.overlapped(this.current.matrix,
                                      this.globalMatrix,
                                      this.current.x,
                                      preY + 1)) {
                this.current.y = preY;
                break;
            }
        }
    }

    @Override
    public boolean requestAdjust() {
        // 判断输赢
        for (int i = 1; i < GLOBAL_WIDTH - 1; i++) {
            if (this.globalMatrix.get(3, i) == 1) {
                return false;
            }
        }
        int preY = this.current.y + 1;
        if (!BoolMatrix.overlapped(this.current.matrix,
                                   this.globalMatrix,
                                   this.current.x,
                                   preY)) {
            // 能继续下降
            this.current.y = preY;
        } else {
            // 不能继续下降
            // 1.或运算到全局矩阵
            BoolMatrix.or(this.current.matrix,
                          this.globalMatrix,
                          this.current.x,
                          this.current.y);
            // 2.更新方块
            this.current = this.next;
            this.next = Tetris.createTetris(4, 1);
            // 3.清空满行
            int zeroRowIndex = -1;
            for (int i = GLOBAL_HEIGHT - 2; i >= 1; i--) {
                int num = 0;
                for (int j = 1; j < GLOBAL_WIDTH - 1; j++) {
                    if (this.globalMatrix.get(i, j) == 1) {
                        num++;
                    }
                }
                if (num == GLOBAL_WIDTH - 2) {
                    for (int j = 1; j < GLOBAL_WIDTH - 1; j++) {
                        this.globalMatrix.set(i, j, (byte) 0);
                    }
                    // 每次只清空给一行
                    this.score++;
                    zeroRowIndex = i;
                    break;
                }
            }
            // 4.向下填充
            for (int i = zeroRowIndex + 1; i >= 2; i--) {
                for (int j = 1; j < GLOBAL_WIDTH - 1; j++) {
                    if (this.globalMatrix.get(i, j) == 0 && this.globalMatrix.get(i - 1, j) == 1) {
                        this.globalMatrix.set(i, j, (byte) 1);
                        this.globalMatrix.set(i - 1, j, (byte) 0);
                    }
                }
            }
        }
        return true;
    }

    @Override
    public BoolMatrix requestGlobal() {
        return this.globalMatrix;
    }

    @Override
    public Tetris requestCurrent() {
        return this.current;
    }

    @Override
    public Tetris requestNext() {
        return this.next;
    }

    @Override
    public int requestScore() {
        return this.score;
    }
}

主窗口

  • 为了让代码块的概念更加清晰,可以将窗体的创建拆分为三个部分,分别是buildBase()buildComponent()buildListener()
  • 提供一个start()方法来开始运行游戏。其中在while循环种间歇性的向抽象游戏罗杰接口发起调整请求,并让自己的面板组件重新绘制。当收到游戏输掉的消息后退出循环并弹出JDilalog对话框来结束游戏。

在这里插入图片描述

注意事项
  • 事件的监听需要获得焦点才能触发,可以在调用setVisible()方法前加上requestFocus(),或者在其后加setFocusable(true),以使得组件能够获得焦点。
  • 窗口运行在main线程中并且会阻塞主线程,而事件的监听时运行在独立的线程中,可以调用Thread.currentThread.getName()方法来获取当前线程的名称。
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

public class GameView extends JFrame {
    // 调整后的窗口最佳宽高
    private static final int FRAME_WIDTH  = 560;
    private static final int FRAME_HEIGHT = 720;

    // 游戏处理者
    private GameHandler handler;
    // 绘制面板
    private JPanel      gamePanel;
    // 提示栏
    private JTextArea   tipText;
    // 游戏是否暂停
    private boolean     paused;

    public GameView(GameHandler handler) {
        this.handler = handler;
        this.paused = false;

        this.buildBase();
        this.buildComponent();
        this.buildListener();

        // 设置窗口可见
        this.setVisible(true);
        // 设置窗口可获取焦点以触发事件监听
        this.setFocusable(true);
    }

    /**
     * 开始游戏
     * 在while循环中以0.5秒的间隔阻塞绘制和调整请求
     */
    public void start() {
        boolean going = true;
        while (going) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (!this.paused) {
                going = handler.requestAdjust();
                this.gamePanel.repaint();
            }
        }
        // 游戏输掉后以弹出对话框的形式退出游戏并释放资源
        this.setEnabled(false);
        JDialog result = new JDialog(this, "GAME OVER");
        result.setSize(this.getWidth() / 4, this.getHeight() / 8);
        result.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                super.windowClosing(e);
                GameView.this.dispose();
            }
        });
        result.setLocationRelativeTo(null);
        result.setResizable(false);
        result.setVisible(true);
    }

    /**
     * 添加窗口基本属性
     */
    private void buildBase() {
        this.setTitle("教主的俄罗斯方块");
        this.setSize(FRAME_WIDTH, FRAME_HEIGHT);
        this.setLocationRelativeTo(null);
        this.setDefaultCloseOperation(EXIT_ON_CLOSE);
        this.setResizable(false);
    }

    /**
     * 添加窗口组件
     */
    private void buildComponent() {
        this.gamePanel = new GamePanel(this.handler);
        this.getContentPane().add(this.gamePanel, BorderLayout.CENTER);

        this.tipText = new JTextArea();
        this.tipText.setFont(new Font("微软雅黑", Font.PLAIN, 14));
        this.tipText.setText("W : 旋转\t" + "A : 向左\t" + "S : 速降\t" + "D : 向右\t" + "Enter : 开始/暂停");
        this.tipText.setBackground(null);
        this.tipText.setEditable(false);
        this.getContentPane().add(this.tipText, BorderLayout.SOUTH);
    }

    /**
     * 添加事件监听
     */
    private void buildListener() {
        this.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                super.keyPressed(e);
                switch (e.getKeyCode()) {
                    case KeyEvent.VK_W:
                        if (!GameView.this.paused) {
                            GameView.this.handler.handleRotateAction();
                        }
                        break;
                    case KeyEvent.VK_A:
                        if (!GameView.this.paused) {
                            GameView.this.handler.handleLeftAction();
                        }
                        break;
                    case KeyEvent.VK_S:
                        if (!GameView.this.paused) {
                            GameView.this.handler.handleLandAction();
                        }
                        break;
                    case KeyEvent.VK_D:
                        if (!GameView.this.paused) {
                            GameView.this.handler.handleRightAction();
                        }
                        break;
                    case KeyEvent.VK_ENTER:
                        GameView.this.paused = !GameView.this.paused;
                        break;
                }
            }
        });
    }
}

面板

在这里插入图片描述

可以引入560x720的背景图片于面板类的同级目录下,去掉注释掉的代码并修改图片名,运行时即可渲染背景图片。

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.io.IOException;

public class GamePanel extends JPanel {
    // 矩阵单元格的长度
    private static final int   CELL_LEN = 30;
    // 背景图
    private static       Image img;
/*
    static {
        try {
            img = ImageIO.read(GamePanel.class.getResource("cartoon1.jpg"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
*/

    private GameHandler handler;

    public GamePanel(GameHandler handler) {
        this.handler = handler;
    }


    @Override
    public void paint(Graphics g) {
        super.paint(g);
        this.paintImage(g);
        this.paintGlobalMatrix(g);
        this.paintCurrentMatrix(g);
        this.paintNextMatrix(g);
        this.paintScore(g);
    }

    private void paintImage(Graphics g) {
     //   g.drawImage(img, 0, 0, null);
    }

    private void paintGlobalMatrix(Graphics g) {
        BoolMatrix globalMatrix = this.handler.requestGlobal();
        for (int i = 0; i < GameHandler.GLOBAL_HEIGHT; i++) {
            for (int j = 0; j < GameHandler.GLOBAL_WIDTH; j++) {
                g.setColor(Color.BLACK);
                g.drawRect(j * CELL_LEN, i * CELL_LEN, CELL_LEN, CELL_LEN);
                if (globalMatrix.get(i, j) == 1) {
                    if (i == 0 || i == GameHandler.GLOBAL_HEIGHT - 1 
                     || j == 0 || j == GameHandler.GLOBAL_WIDTH - 1) {
                        // 边框值
                        g.setColor(Color.LIGHT_GRAY);
                    } else {
                        // 由其它方块复制过来的值
                        g.setColor(Color.GRAY);
                    }
                    // 不完全填充会好看一点儿
                    g.fillRect(j * CELL_LEN + 2,
                               i * CELL_LEN + 2,
                               CELL_LEN - 3,
                               CELL_LEN - 3);
                }
            }
        }
    }

    private void paintCurrentMatrix(Graphics g) {
        Tetris current = this.handler.requestCurrent();
        BoolMatrix currentMatrix = current.getMatrix();
        g.setColor(current.getPrototype());
        for (int i = 0; i < currentMatrix.getRows(); i++) {
            for (int j = 0; j < currentMatrix.getColumns(); j++) {
                if (currentMatrix.get(i, j) == 1) {
                    g.fillRect((current.getX() + j) * CELL_LEN + 2,
                               (current.getY() + i) * CELL_LEN + 2,
                               CELL_LEN - 3,
                               CELL_LEN - 3);
                }
            }
        }
    }

    private void paintNextMatrix(Graphics g) {
        Tetris next = handler.requestNext();
        BoolMatrix nextMatrix = next.getMatrix();
        int positionX = (GameHandler.GLOBAL_WIDTH + 1) * CELL_LEN;
        int positionY = CELL_LEN;
        for (int i = 0; i < nextMatrix.getRows(); i++) {
            for (int j = 0; j < nextMatrix.getColumns(); j++) {
                g.setColor(Color.BLACK);
                g.drawRect(positionX + j * CELL_LEN,
                           positionY + i * CELL_LEN,
                           CELL_LEN,
                           CELL_LEN);
                if (nextMatrix.get(i, j) == 1) {
                    g.setColor(next.getPrototype());
                    g.fillRect(positionX + j * CELL_LEN + 2,
                               positionY + i * CELL_LEN + 2,
                               CELL_LEN - 3,
                               CELL_LEN - 3);
                }
            }
        }
    }

    private void paintScore(Graphics g) {
        int score = this.handler.requestScore();
        int positionX = (GameHandler.GLOBAL_WIDTH + 1) * CELL_LEN;
        int positionY = 10 * CELL_LEN;
        g.setColor(Color.RED);
        g.setFont(new Font("微软雅黑", Font.BOLD, CELL_LEN));
        g.drawString("score = " + score, positionX, positionY);
    }
}

入口

public class App {
    public static void main(String[] args) {
        GameHandler handler = new Game();
        GameView view = new GameView(handler);
        view.start();
    }
}



四、Swing结构


  • 其中窗体(JFrame)和对话框(JDialog)都是作为顶层容器而存在的,通常不能直接向里面添加组件。顶层容器的显示需要调用setVisible()方法并传参true
  • 要想向顶层容器中添加组件通常需要借助中间容器,比如面板(JPanel)。还可以通过调用getContentPane()方法来获得默认的中间容器。
  • 窗体(JFrame)默认是边界布局(BorderLaylout);面板(JPanel)默认是流式布局(FlowLayout)。

在这里插入图片描述
在这里插入图片描述




五、参考资料


原创文章 41 获赞 34 访问量 3万+

猜你喜欢

转载自blog.csdn.net/XY1790026787/article/details/85943218