首页 > 作文

【Swing】——俄罗斯方块

更新时间:2023-04-04 02:27:25 阅读: 评论:0

一、前言

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

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

二、设计

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

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

建模

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

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

预览

疑问

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

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

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

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

大道至简。

如果设置为10×20个小格子,那么除了碰撞问题还要处理移动越界的问题。而如果加一层值为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 xOfft 源矩阵在目标矩阵上的水平偏移量 * @param yOfft 源矩阵在目标矩阵上的垂直偏移量 * @return 源矩阵经偏移后是否与目标矩阵重合 */public static boolean overlapped(BoolMatrix source, BoolMatrix target, int xOfft, int yOfft) {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 + yOfft, j + xOfft) == 1) {return true;}}}return fal;}/** * 将源矩阵经偏移后或运算到目标矩阵上 * 修改的是目标矩阵 * @param source 源矩阵 * @param target 目标矩阵 * @param xOfft 源矩阵在目标矩阵上的水平偏移量 * @param yOfft 源矩阵在目标矩阵上的垂直偏移量 */public static void or(BoolMatrix source, BoolMatrix target, int xOfft, int yOfft) {for (int i = 0; i < source.rows; i++) {for (int j = 0; j < source.columns; j++) {if (source.get(i, j) == 1) {target.t(i + yOfft, j + xOfft, (byte) 1);}}}}public byte get(int i, int j) {return this.array[i][j];}public void t(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");} el {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");} el {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;}}}}@Overridepublic 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包访问权限可减少tter方法。为了支持原型模式,方块需要:实现Cloneable接口并重写clone()方法,对方块进行深克隆。创建7种方块原型私有化构造方法,以静态工厂的形式提供创建方块的途径,随机创建克隆自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) {ca 0:t = (Tetris) RED_PROTOTYPE.clone();break;ca 1:t = (Tetris) ORANGE_PROTOTYPE.clone();break;ca 2:t = (Tetris) YELLOW_PROTOTYPE.clone();break;ca 3:t = (Tetris) GREEN_PROTOTYPE.clone();break;ca 4:t = (Tetris) CYAN_PROTOTYPE.clone();break;ca 5:t = (Tetris) BLUE_PROTOTYPE.clone();break;ca 6:t = (Tetris) MAGENTA_PROTOTYPE.clone();break;default:t = null;}} catch (CloneNotSupportedException e) {e.printStackTrace();}// switch必定能克隆出方块asrt 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;}@Overridepublic 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("◼");} el {sb.append("◻");}}sb.append("\n");}return St江西职业技术学院ring.valueOf(sb);}@Overridepublic 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);// 边框为1for (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.t(i, j, (byte) 1);}}}// 初始化方块this.current = Tetris.createTetris(4, 1);this.next = Tetris.createTetris(4, 1);}@Overridepublic void handleLeftAction() {int preX = this.current.x - 1;if (!BoolMatrix.overlapped(this.current.matrix,this.globalMatrix,preX,this.current.y)) {this.current.x = preX;}}@Overridepublic void handleRightAction() {int preX = this.current.x + 1;if (!BoolMatrix.overlapped(this.current.matrix,this.globalMatrix,preX,this.current.y)) {this.current.x = preX;}}@Overridepublic 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();}}@Overridepublic 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;}}}@Overridepublic boolean requestAdjust() {// 判断输赢for (int i = 1; i < GLOBAL_WIDTH - 1; i++) {if (this.globalMatrix.get(3, i) == 1) {return fal;}}int preY = this.current.y + 1;if (!BoolMatrix.overlapped(this.current.matrix,this.globalMatrix,this.current.x,preY)) {// 能继续下降this.current.y = preY;} el {// 不能继续下降// 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.t(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.t(i, j, (byte) 1);this.globalMatrix.t(i - 1, j, (byte) 0);}}}}return true;}@Overridepublic BoolMatrix requestGlobal() {return this.globalMatrix;}@Overridepublic Tetris requestCurrent() {return this.current;}@Overridepublic Tetris requestNext() {return this.next;}@Overridepublic int requestScore() {return this.score;}}

主窗口

为了让代码块的概念更加清晰,可以将窗体的创建拆分为三个部分,分别是buildBa()buildComponent()buildListener()。提供一个start()方法来开始运行游戏。其中在while循环种间歇性的向抽象游戏罗杰接口发起调整请求,并让自己的面板组件重新绘制。当收到游戏输掉的消息后退出循环并弹出JDilalog对话框来结束游戏。
注意事项
事件的监听需要获得焦点才能触发,可以在调用tVisible()方法前加上requestFocus(),或者在其后加tFocusable(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     paud;public GameView(GameHandler handler) {this.handler = handler;this.paud = fal;this.buildBa();this.buildComponent();this.buildListener();// 设置窗口可见this.tVisible(true);// 设置窗口可获取焦点以触发事件监听this.tFocusable(true);}/** * 开始游戏 * 在while循环中以0.5秒的间隔阻塞绘制和调整请求 */public 化学仪器void start() {boolean going = true;while (going) {try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}if (!this.paud) {going = handler.requestAdjust();this.gamePanel.repaint();}}// 游戏输掉后以弹出对话框的形式退出游戏并释放资源this.tEnabled(fal);JDialog result = new JDialog(this, "GAME OVER");result.tSize(this.getWidth() / 4, this.getHeight() / 8);result.addWindowListener(new WindowAdapter() {@Overridepublic void windowClosing(WindowEvent e) {super.windowClosing(e);GameView.this.dispo();}});result.tLocationRelativeTo(null);result.tResizable(fal);result.tVisible(true);}/** * 添加窗口基本属性 */private void buildBa() {this.tTitle("教主的俄罗斯方块");this.tSize(FRAME_WIDTH, FRAME_HEIGHT);this.tLocationRelativeTo(null);this.tDefaultCloOperation(EXIT_ON_CLOSE);this.tResizable(fal);}/** * 添加窗口组件 */private void buildComponent() {this.gamePanel = new GamePanel(this.handler);this.getContentPane().add(this.gamePanel, BorderLayout.CENTER);this.tipText = new JTextArea();this.tipText.tFont(new Font("微软雅黑", Font.PLAIN, 14));this.tipText.tText("W : 旋转\t" + "A : 向左\t" + "S : 速降\t" + "D : 向右\t" + "Enter : 开始/暂停");this.tipText.tBackground(null);this.tipText.tEditable(fal);this.getContentPane().add(this.tipText, BorderLayout.SOUTH);}/** * 添加事件监听 */private void buildListener() {this.addKeyListener(new KeyAdapter() {@Overridepublic void keyPresd(KeyEvent e) {super.keyPresd(e);switch (e.getKeyCode()) {ca KeyEvent.VK_W:if (!GameView.this.paud) {GameView.this.handler.handleRotateAction();}break;ca KeyEvent.VK_A:if (!GameView.this.paud) {GameView.this.handler.handleLeftAction();}break;ca KeyEvent.VK_S:if (!GameView.this.paud) {GameView.this.handler.handleLandAction();}break;ca KeyEvent.VK_D:if (!GameView.this.paud) {GameView.this.handler.handleRightAction();}break;ca KeyEvent.VK_ENTER:GameView.this.paud = !GameView.this.paud;break;}}});}}

面板

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

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;}@Overridepublic 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.tColor(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.tColor(Color.LIGHT_GRAY);} el {// 由其它方块复制过来的值g.tColor(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.tColor(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.tColor(Color.BLACK);g.drawRect(positionX + j * CELL_LEN,positionY + i * CELL_LEN,CELL_LEN,CELL_LEN);if (nextMatrix.get(i, j) == 1) {g.tColor(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.tColor(Color.RED);g.tFont(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)都是作为顶层容器而存在的,通常不能直接向里面添加组件。顶层容器的显示需要调用tVisible()方法并传参true。要想向顶层容器中添加组件通常需要借助中间容器,比如面板(JPanel)。还可以通过调用getContentPane()方法来获得默认的中间容器。窗体(JFrame)默认是边界布局(BorderLaylout);面板(JPanel)默认是流式布局(FlowLayout)。

五、参考资料

二维数组翻转Jframe框架基本结构和继承关系

本文地址:https://blog.csdn.net/XY1790026787/article/details/85943218

本文发布于:2023-04-04 02:27:21,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/zuowen/3ca703275f8a20f415d35c0f66843464.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

本文word下载地址:【Swing】——俄罗斯方块.doc

本文 PDF 下载地址:【Swing】——俄罗斯方块.pdf

标签:矩阵   方块   全局   原型
相关文章
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图