俄罗斯方块JAVA
上学的时候用 C 写过俄罗斯方块,用 JAVA 再实现一次。
外公走了两个多月了,年初刚刚疫情的时候,外公还常给我打电话,让我在外面注意安全,不要乱跑。现在想想就是黄粱一梦。
外公为人极好,真心的希望每个他遇到的人能过好。好到从一个村里的民办教师,被众人生生的托举到了副市级的位置。
外公也极不容易,家里无权无势,每天回家是六个等着吃饭的弟弟,后来又加上了三个子女,全靠他在外面爬冰卧雪、。
我大舅说,直到我和我弟弟出生,他们才知道外公还会笑。
外公不只是我的外公,也是我的挚友。不管我学习好学习差,不管我要去做什么,永远是他看的起我,我看的起他。
外公让自己的子女考体制,给家里创造稳定的条件。但到了我和我弟弟这儿就变成了不要进体制里来,做你自己想做的事,快乐就好。
上次外公过生日,回家的时候嘴里一直嘟嘟囔囔的,嫌一个大爷太抠,送的烟太差。我拉着他说我挣钱了我给你买好的,一路半推半就,夹杂着他嘴里的“不用吧不用吧”和诚实的双手,我们带了四条好烟回家。这可能是我唯一的孝顺了。
因为再后来我给他买的好东西,他都吃不下去了。临走那天我喂他吃了一颗葡萄,也没有咽下去。我欠外公的这一辈子,不差这一颗葡萄,但我还是难以释怀。
外公跟我说的最后一句话是,别在这儿耗着了,和你弟弟回家把院里的葡萄摘了。
我照做了。但是没能减轻我一丝一毫的剧痛。
我一个受过高等教育的人,对我们还能团聚深信不疑。
红楼开篇写的真好:满纸荒唐言,一把辛酸泪。都云作者痴,谁解其中味。
胡言乱语到此为止,进入正题。
俄罗斯方块实现思路很简单,在一个二维矩阵上渲染不断位移的图形,在图形无法下落时进行进行计分处理。
二维矩阵上的图形由一个个基本的坐标点组成,首先实现坐标点:
public class Cell { private int row; private int column; private BufferedImage bufferedImage; public int getRow() { return row; } public void setRow(int row) { this.row = row; } public int getColumn() { return column; } public void setColumn(int column) { this.column = column; } public BufferedImage getBufferedImage() { return bufferedImage; } public void setBufferedImage(BufferedImage bufferedImage) { this.bufferedImage = bufferedImage; } public Cell(int row, int column, BufferedImage bufferedImage) { this.row = row; this.column = column; this.bufferedImage = bufferedImage; } public void moveLeft() { this.column--; } public void moveRight() { this.column++; } public void moveDown() { this.row++; } }
基于坐标点封装图形的基类:
public abstract class BaseTetromino { protected Cell[] cells = new Cell[4]; Cell baseCell = new Cell(0, 0, null); //当前图形索引 protected int currentShape; //图形集合 protected Cell[][] shapes = new Cell[4][4]; //初始化图形集合 protected abstract void initShips(); //图形顺时针旋转 public void spin(Cell[][] cellWall) { Cell[] nextShape = copyCells(shapes[(currentShape + 1) % shapes.length]); long offSet = getOffset(); int rowOffset = (int) (offSet >> 32), columnOffset = (int) offSet; for (Cell cell : nextShape) { cell.setColumn(cell.getColumn() + columnOffset); cell.setRow(cell.getRow() + rowOffset); if (isOutOfWall(cellWall, cell) || isOccupied(cellWall, cell)) return; } cells = nextShape; currentShape++; } public Cell[] getCells() { return cells; } public void setCells(Cell[] cells) { this.cells = cells; } //左移 public void moveLeft() { for (Cell cell : cells) cell.moveLeft(); baseCell.moveLeft(); } //右移 public void moveRight() { for (Cell cell : cells) cell.moveRight(); baseCell.moveRight(); } //下移 public void moveDown() { for (Cell cell : cells) cell.moveDown(); baseCell.moveDown(); } //图像顺时针旋转,O(N方),弃用 protected final void rotate(int[][] matrix) { int n = matrix.length; // transpose matrix for (int i = 0; i < n; i++) { for (int j = i; j < n; j++) { int tmp = matrix[j][i]; matrix[j][i] = matrix[i][j]; matrix[i][j] = tmp; } } // reverse each row for (int i = 0; i < n; i++) { for (int j = 0; j < n / 2; j++) { int tmp = matrix[i][j]; matrix[i][j] = matrix[i][n - j - 1]; matrix[i][n - j - 1] = tmp; } } } //获取原点偏移量 protected final long getOffset() { return ((long) baseCell.getRow() << 32) | baseCell.getColumn(); } //获取最左上方偏移量 protected final long getRealOffset() { int rowOffset = Integer.MAX_VALUE, columnOffet = Integer.MAX_VALUE; for (Cell cell : cells) { rowOffset = Math.min(rowOffset, cell.getRow()); columnOffet = Math.min(columnOffet, cell.getColumn()); } return (((long) rowOffset) << 32) | columnOffet; } //深拷贝 Cell 集合 protected final Cell[] copyCells(Cell[] source) { Cell[] re = new Cell[source.length]; for (int i = 0; i < source.length; i++) { re[i] = new Cell(source[i].getRow(), source[i].getColumn(), source[i].getBufferedImage()); } return re; } // cell 是否被占位 protected final boolean isOccupied(Cell[][] wall, Cell cell) { if (wall[cell.getColumn()][cell.getRow()] == null) return false; return true; } // cell 是否在墙外 protected final boolean isOutOfWall(Cell[][] wall, Cell cell) { if (cell.getColumn() >= wall.length || cell.getRow() >= wall[0].length) return true; if (cell.getColumn() < 0 || cell.getRow() < 0) return true; return false; } // 随机获取图形 public static BaseTetromino randomTetromino() { int random = (int) (Math.random() * 7); switch (random) { case 0: return new Tetromino0(); case 1: return new Tetromino1(); case 2: return new Tetromino2(); case 3: return new Tetromino3(); case 4: return new Tetromino4(); case 5: return new Tetromino5(); case 6: return new Tetromino6(); } return null; } }
七种图形的实现:
public class Tetromino0 extends BaseTetromino { //正方形 public Tetromino0() { cells[0] = new Cell(0, 0, Tetris.bufferedImage0); cells[1] = new Cell(0, 1, Tetris.bufferedImage1); cells[2] = new Cell(1, 0, Tetris.bufferedImage2); cells[3] = new Cell(1, 1, Tetris.bufferedImage3); } @Override protected void initShips() { shapes = null; } @Override public void spin(Cell[][] wall) { return; } }
/** * @Author Niuxy * @Date 2020/11/25 8:34 下午 * @Description T 字型 */ public class Tetromino1 extends BaseTetromino { private int currentShape; //T 字型 public Tetromino1() { shapes = new Cell[4][]; initShips(); currentShape = (int) (Math.random() * 3); cells = copyCells(shapes[currentShape%4]); } @Override protected void initShips() { Cell[] shape0 = new Cell[4]; shape0[0] = new Cell(1, 0, Tetris.bufferedImage3); shape0[1] = new Cell(1, 1, Tetris.bufferedImage4); shape0[2] = new Cell(1, 2, Tetris.bufferedImage5); shape0[3] = new Cell(2, 1, Tetris.bufferedImage6); Cell[] shape1 = new Cell[4]; shape1[0] = new Cell(0, 1, Tetris.bufferedImage3); shape1[1] = new Cell(1, 1, Tetris.bufferedImage4); shape1[2] = new Cell(2, 1, Tetris.bufferedImage5); shape1[3] = new Cell(1, 0, Tetris.bufferedImage6); Cell[] shape2 = new Cell[4]; shape2[0] = new Cell(0, 1, Tetris.bufferedImage3); shape2[1] = new Cell(1, 0, Tetris.bufferedImage4); shape2[2] = new Cell(1, 1, Tetris.bufferedImage5); shape2[3] = new Cell(1, 2, Tetris.bufferedImage6); Cell[] shape3 = new Cell[4]; shape3[0] = new Cell(0, 1, Tetris.bufferedImage3); shape3[1] = new Cell(1, 1, Tetris.bufferedImage4); shape3[2] = new Cell(2, 1, Tetris.bufferedImage5); shape3[3] = new Cell(1, 2, Tetris.bufferedImage6); shapes[0] = shape0; shapes[1] = shape1; shapes[2] = shape2; shapes[3] = shape3; } }
/** * @Author Niuxy * @Date 2020/11/25 8:12 下午 * @Description L 型 */ public class Tetromino2 extends BaseTetromino { public Tetromino2() { shapes = new Cell[4][]; initShips(); currentShape = (int) (Math.random() * 3); cells = copyCells(shapes[currentShape]); } protected void initShips() { Cell[] shape0 = new Cell[4]; shape0[0] = new Cell(0, 0, Tetris.bufferedImage3); shape0[1] = new Cell(0, 1, Tetris.bufferedImage4); shape0[2] = new Cell(0, 2, Tetris.bufferedImage5); shape0[3] = new Cell(1, 0, Tetris.bufferedImage6); Cell[] shape1 = new Cell[4]; shape1[0] = new Cell(0, 0, Tetris.bufferedImage3); shape1[1] = new Cell(1, 0, Tetris.bufferedImage4); shape1[2] = new Cell(2, 0, Tetris.bufferedImage5); shape1[3] = new Cell(2, 1, Tetris.bufferedImage6); Cell[] shape2 = new Cell[4]; shape2[0] = new Cell(0, 2, Tetris.bufferedImage3); shape2[1] = new Cell(1, 0, Tetris.bufferedImage4); shape2[2] = new Cell(1, 1, Tetris.bufferedImage5); shape2[3] = new Cell(1, 2, Tetris.bufferedImage6); Cell[] shape3 = new Cell[4]; shape3[0] = new Cell(0, 0, Tetris.bufferedImage3); shape3[1] = new Cell(0, 1, Tetris.bufferedImage4); shape3[2] = new Cell(1, 1, Tetris.bufferedImage5); shape3[3] = new Cell(2, 1, Tetris.bufferedImage6); shapes[0] = shape0; shapes[1] = shape1; shapes[2] = shape2; shapes[3] = shape3; } }
public class Tetromino3 extends BaseTetromino { //长条形 public Tetromino3() { shapes = new Cell[2][]; initShips(); currentShape = (int) (Math.random() * 2); cells = copyCells(shapes[currentShape]); } @Override protected void initShips() { Cell[] shape0 = new Cell[4]; shape0[0] = new Cell(1, 0, Tetris.bufferedImage3); shape0[1] = new Cell(1, 1, Tetris.bufferedImage4); shape0[2] = new Cell(1, 2, Tetris.bufferedImage5); shape0[3] = new Cell(1, 3, Tetris.bufferedImage6); Cell[] shape1 = new Cell[4]; shape1[0] = new Cell(0, 1, Tetris.bufferedImage3); shape1[1] = new Cell(1, 1, Tetris.bufferedImage4); shape1[2] = new Cell(2, 1, Tetris.bufferedImage5); shape1[3] = new Cell(3, 1, Tetris.bufferedImage6); shapes[0] = shape0; shapes[1] = shape1; } }
public class Tetromino4 extends BaseTetromino { //Z 字形 public Tetromino4() { shapes = new Cell[2][]; initShips(); currentShape = (int) (Math.random() * 2); cells = copyCells(shapes[currentShape]); } @Override protected void initShips() { Cell[] shape0 = new Cell[4]; shape0[0] = new Cell(0, 0, Tetris.bufferedImage3); shape0[1] = new Cell(0, 1, Tetris.bufferedImage4); shape0[2] = new Cell(1, 1, Tetris.bufferedImage5); shape0[3] = new Cell(1, 2, Tetris.bufferedImage6); Cell[] shape1 = new Cell[4]; shape1[0] = new Cell(0, 1, Tetris.bufferedImage3); shape1[1] = new Cell(1, 0, Tetris.bufferedImage4); shape1[2] = new Cell(1, 1, Tetris.bufferedImage5); shape1[3] = new Cell(2, 0, Tetris.bufferedImage6); shapes[0] = shape0; shapes[1] = shape1; } }
public class Tetromino5 extends BaseTetromino { //倒 Z 字形 public Tetromino5() { shapes = new Cell[2][]; initShips(); currentShape = (int) (Math.random() * 2); cells = copyCells(shapes[currentShape]); } @Override protected void initShips() { Cell[] shape0 = new Cell[4]; shape0[0] = new Cell(0, 1, Tetris.bufferedImage3); shape0[1] = new Cell(0, 2, Tetris.bufferedImage4); shape0[2] = new Cell(1, 0, Tetris.bufferedImage5); shape0[3] = new Cell(1, 1, Tetris.bufferedImage6); Cell[] shape1 = new Cell[4]; shape1[0] = new Cell(0, 1, Tetris.bufferedImage3); shape1[1] = new Cell(1, 1, Tetris.bufferedImage4); shape1[2] = new Cell(1, 2, Tetris.bufferedImage5); shape1[3] = new Cell(2, 2, Tetris.bufferedImage6); shapes[0] = shape0; shapes[1] = shape1; } }
/** * @Author Niuxy * @Date 2020/11/25 8:12 下午 * @Description 倒 L 型 */ public class Tetromino6 extends BaseTetromino { public Tetromino6() { shapes = new Cell[4][]; initShips(); currentShape = (int) (Math.random() * 3); cells = copyCells(shapes[currentShape]); } protected void initShips() { Cell[] shape0 = new Cell[4]; shape0[0] = new Cell(0, 1, Tetris.bufferedImage3); shape0[1] = new Cell(1, 1, Tetris.bufferedImage4); shape0[2] = new Cell(2, 1, Tetris.bufferedImage5); shape0[3] = new Cell(2, 0, Tetris.bufferedImage6); Cell[] shape1 = new Cell[4]; shape1[0] = new Cell(0, 0, Tetris.bufferedImage3); shape1[1] = new Cell(1, 0, Tetris.bufferedImage4); shape1[2] = new Cell(1, 1, Tetris.bufferedImage5); shape1[3] = new Cell(1, 2, Tetris.bufferedImage6); Cell[] shape2 = new Cell[4]; shape2[0] = new Cell(0, 1, Tetris.bufferedImage3); shape2[1] = new Cell(0, 2, Tetris.bufferedImage4); shape2[2] = new Cell(1, 1, Tetris.bufferedImage5); shape2[3] = new Cell(2, 1, Tetris.bufferedImage6); Cell[] shape3 = new Cell[4]; shape3[0] = new Cell(1, 0, Tetris.bufferedImage3); shape3[1] = new Cell(1, 1, Tetris.bufferedImage4); shape3[2] = new Cell(1, 2, Tetris.bufferedImage5); shape3[3] = new Cell(2, 2, Tetris.bufferedImage6); shapes[0] = shape0; shapes[1] = shape1; shapes[2] = shape2; shapes[3] = shape3; } }
封装游戏规则:
public class Tetris extends JPanel { private BaseTetromino currentTeromino = BaseTetromino.randomTetromino(); private BaseTetromino nextTeromino = BaseTetromino.randomTetromino(); private static final int CELL_SIZE = 26; private static final int X_SIZE = 10; private static final int Y_SIZE = 20; //刷新时间 private static final int INTERVAL = 1000; private static int mark = 0; private Cell[][] wall = new Cell[X_SIZE][Y_SIZE]; public static BufferedImage bufferedImage0; public static BufferedImage bufferedImage1; public static BufferedImage bufferedImage2; public static BufferedImage bufferedImage3; public static BufferedImage bufferedImage4; public static BufferedImage bufferedImage5; public static BufferedImage bufferedImage6; public static BufferedImage background; public static BufferedImage gameOver; static { try { bufferedImage0 = ImageIO.read(Tetris.class.getResource("img/stone0.png")); bufferedImage1 = ImageIO.read(Tetris.class.getResource("img/stone1.png")); bufferedImage2 = ImageIO.read(Tetris.class.getResource("img/stone2.png")); bufferedImage3 = ImageIO.read(Tetris.class.getResource("img/stone3.png")); bufferedImage4 = ImageIO.read(Tetris.class.getResource("img/stone4.png")); bufferedImage5 = ImageIO.read(Tetris.class.getResource("img/stone5.png")); bufferedImage6 = ImageIO.read(Tetris.class.getResource("img/stone6.png")); background = ImageIO.read(Tetris.class.getResource("img/background1.png")); gameOver = ImageIO.read(Tetris.class.getResource("img/background1.png")); } catch (Exception e) { e.printStackTrace(); } } @Override public void paint(Graphics g) { g.drawImage(background, 0, 0, null); g.translate(15, 15); paintWall(g); paintCurrentOne(g); paintNextOne(g); } // 渲染下一个图形 private void paintNextOne(Graphics g) { Cell[] cells = nextTeromino.getCells(); for (Cell cell : cells) { int x = cell.getColumn(); int y = cell.getRow(); g.drawImage(cell.getBufferedImage(), x * CELL_SIZE + 260, y * CELL_SIZE + 26, null); } } //渲染当前图形 private void paintCurrentOne(Graphics g) { Cell[] cells = currentTeromino.getCells(); for (Cell cell : cells) { g.drawImage(cell.getBufferedImage(), cell.getColumn() * CELL_SIZE, cell.getRow() * CELL_SIZE, null); } } //渲染墙壁 private void paintWall(Graphics g) { for (int i = 0; i < X_SIZE; i++) { for (int j = 0; j < Y_SIZE; j++) { if (wall[i][j] == null) g.drawRect(i, j, CELL_SIZE, CELL_SIZE); else g.drawImage(wall[i][j].getBufferedImage(), i * CELL_SIZE, j * CELL_SIZE, null); } } } //渲染得分 private void paintMark(Graphics g) { g.drawString(String.valueOf(mark), 300, 30); } public void start() { KeyListener listener = new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { int code = e.getKeyCode(); switch (code) { case KeyEvent.VK_DOWN: moveDown(); break; case KeyEvent.VK_LEFT: moveLeft(); break; case KeyEvent.VK_RIGHT: moveRight(); break; case KeyEvent.VK_UP: spin(); break; default: break; } repaint(); } }; this.addKeyListener(listener); this.requestFocus(); while (true) { try { Thread.sleep(INTERVAL); } catch (InterruptedException interruptedException) { interruptedException.printStackTrace(); } if (canDrop()) currentTeromino.moveDown(); else { landToWall(); currentTeromino = nextTeromino; nextTeromino = BaseTetromino.randomTetromino(); } repaint(); } } /** * @Author Niuxy * @Date 2020/11/29 7:26 下午 * @Description 画布下沉并计分 * 递归的进行行下沉处理 * 每处理完一行,下一次递归由该行继续 * 不再重复遍历该行以下的行 */ private final void elimination() { int index = Y_SIZE - 1; while (index > -1) { if (!isFull(index)) { index--; continue; } //空行剪枝 if (isEmpty(index)) break; sink(index); //计分 this.mark++; } } //画布下沉 private final void sink(int rowIndex) { if (rowIndex == 0) { for (int i = 0; i < X_SIZE; i++) { wall[i][0] = null; } return; } for (int i = 0; i < X_SIZE; i++) { wall[i][rowIndex] = wall[i][rowIndex - 1]; } sink(rowIndex - 1); } //判断 index 行是否已满 private boolean isFull(int index) { if (index < 0 || index >= Y_SIZE) throw new RuntimeException("index is out of the wall!"); for (int i = 0; i < X_SIZE; i++) { if (wall[i][index] == null) return false; } return true; } //判断空行 private final boolean isEmpty(int index) { if (index < 0 || index >= Y_SIZE) throw new RuntimeException("index is out of the wall!"); for (int i = 0; i < X_SIZE; i++) { if (wall[i][index] != null) return false; } return true; } // 旋转图形 public void spin() { currentTeromino.spin(wall); } //键盘监听事件-图形右移 public void moveRight() { currentTeromino.moveRight(); if (outOfBounds() || coincide()) currentTeromino.moveLeft(); } //键盘监听事件-图形左移 public void moveLeft() { currentTeromino.moveLeft(); if (outOfBounds() || coincide()) currentTeromino.moveRight(); } //键盘监听事件-图形下移 public void moveDown() { if (canDrop()) currentTeromino.moveDown(); else { landToWall(); currentTeromino = nextTeromino; nextTeromino = BaseTetromino.randomTetromino(); } } //图形坐标是否重复 private boolean coincide() { Cell[] cells = currentTeromino.getCells(); for (Cell cell : cells) { if (wall[cell.getColumn()][cell.getRow()] != null) return true; } return false; } //图形是否超出画布 private boolean outOfBounds() { Cell[] cells = currentTeromino.getCells(); for (Cell cell : cells) { if (cell.getColumn() < 0 || cell.getColumn() >= X_SIZE || cell.getRow() >= Y_SIZE - 1) return true; } return false; } //图形是否可以下移 public boolean canDrop() { Cell[] cells = currentTeromino.getCells(); for (Cell c : cells) { if (c.getRow() == Y_SIZE - 1 || wall[c.getColumn()][c.getRow() + 1] != null) return false; } return true; } //图形位置确定,载入画布 private void landToWall() { Cell[] cells = currentTeromino.getCells(); for (Cell c : cells) wall[c.getColumn()][c.getRow()] = c; elimination(); } public static void main(String[] args) { JFrame jFrame = new JFrame("斯巴达!"); Tetris tetris = new Tetris(); jFrame.add(tetris); jFrame.setVisible(true); jFrame.setSize(535, 595); jFrame.setLocationRelativeTo(null); jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); tetris.start(); } }