一、游戏背景
俄罗斯方块是俄罗斯人发明的。这人叫阿列克谢·帕基特诺夫(Алексей Пажитнов 英文:Alexey Pazhitnov)。俄罗斯方块原名是俄语Тетрис(英语是Tetris),这个名字来源于希腊语tetra,意思是“四”,而游戏的作者最喜欢网球(tennis)。于是,他把两个词tetra和tennis合而为一,命名为Tetris,这也就是俄罗斯方块名字的由来。
规则说明:
由小方块组成的不同形状的板块陆续从屏幕上方落下来,玩家通过调整板块的位置和方向,使它们在屏幕底部拼出完整的一条或几条。这些完整的横条会随即消失,给新落下来的板块腾出空间,与此同时,玩家得到分数奖励。没有被消除掉的方块不断堆积起来,一旦堆到屏幕顶端,玩家便告输,游戏结束。
二、功能实现
开发工具:idea、jdk8
技术汇总:Java基础知识、数组、面向对象、多线程、IO流、Swing。
整体代码分为三个模块:方格模块,七种图形模块,俄罗斯方块主模块。
小方块类:Cell
public class Cell { // 行 private int row; // 列 private int col; private BufferedImage image; public Cell() { } public Cell(int row, int col, BufferedImage image) { this.row = row; this.col = col; this.image = image; } public int getRow() { return row; } public void setRow(int row) { this.row = row; } public int getCol() { return col; } public void setCol(int col) { this.col = col; } public BufferedImage getImage() { return image; } public void setImage(BufferedImage image) { this.image = image; } @Override public String toString() { return "Cell{" + "row=" + row + ", col=" + col + ", image=" + image + '}'; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof Cell)) { return false; } Cell cell = (Cell) o; return getRow() == cell.getRow() && getCol() == cell.getCol() && Objects.equals(getImage(), cell.getImage()); } @Override public int hashCode() { return Objects.hash(getRow(), getCol(), getImage()); } //左移动一格 public void left(){ col--; } //右移动一格 public void right(){ col++; } //下移动一格 public void down(){ row++; } }
四方格图形的父类:Tetromino
public class Tetromino { public Cell[] cells = new Cell[4]; //旋转的状态 protected State[] states; //声明旋转次数 protected int count = 10000; //左移方法 public void moveLeft() { for (Cell cell : cells) { cell.left(); } } //右移方法 public void moveRight() { for (Cell cell : cells) { cell.right(); } } //单元格下落 public void moveDrop() { for (Cell cell : cells) { cell.down(); } } //编写随机生成四方格 public static Tetromino randomOne() { int num = (int) (Math.random() * 7); Tetromino tetromino = null; switch (num) { case 0: tetromino = new I(); break; case 1: tetromino = new J(); break; case 2: tetromino = new L(); break; case 3: tetromino = new O(); break; case 4: tetromino = new S(); break; case 5: tetromino = new T(); break; case 6: tetromino = new Z(); break; } return tetromino; } //顺时针旋转的方法 public void rotateRight() { if (states.length == 0) { return; } //旋转次数+1 count++; State s = states[count % states.length]; Cell cell = cells[0]; int row = cell.getRow(); int col = cell.getCol(); cells[1].setRow(row + s.row1); cells[1].setCol(col + s.col1); cells[2].setRow(row + s.row2); cells[2].setCol(col + s.col2); cells[3].setRow(row + s.row3); cells[3].setCol(col + s.col3); } //逆时针旋转的方法 public void rotateLeft() { if (states.length == 0) { return; } //旋转次数+1 count--; State s = states[count % states.length]; Cell cell = cells[0]; int row = cell.getRow(); int col = cell.getCol(); cells[1].setRow(row + s.row1); cells[1].setCol(col + s.col1); cells[2].setRow(row + s.row2); cells[2].setCol(col + s.col2); cells[3].setRow(row + s.row3); cells[3].setCol(col + s.col3); } //四方格旋转状态的内部类 protected class State { //存储四方格各元素的位置 int row0, col0, row1, col1, row2, col2, row3, col3; public State() { } public State(int row0, int col0, int row1, int col1, int row2, int col2, int row3, int col3) { this.row0 = row0; this.col0 = col0; this.row1 = row1; this.col1 = col1; this.row2 = row2; this.col2 = col2; this.row3 = row3; this.col3 = col3; } public int getRow0() { return row0; } public void setRow0(int row0) { this.row0 = row0; } public int getCol0() { return col0; } public void setCol0(int col0) { this.col0 = col0; } public int getRow1() { return row1; } public void setRow1(int row1) { this.row1 = row1; } public int getCol1() { return col1; } public void setCol1(int col1) { this.col1 = col1; } public int getRow2() { return row2; } public void setRow2(int row2) { this.row2 = row2; } public int getCol2() { return col2; } public void setCol2(int col2) { this.col2 = col2; } public int getRow3() { return row3; } public void setRow3(int row3) { this.row3 = row3; } public int getCol3() { return col3; } public void setCol3(int col3) { this.col3 = col3; } @Override public String toString() { return "State{" + "row0=" + row0 + ", col0=" + col0 + ", row1=" + row1 + ", col1=" + col1 + ", row2=" + row2 + ", col2=" + col2 + ", row3=" + row3 + ", col3=" + col3 + '}'; } } }
七种图形类:I、J、L、O、S、T、Z
public class I extends Tetromino { public I() { cells[0] = new Cell(0,4, Tetris.I); cells[1] = new Cell(0,3, Tetris.I); cells[2] = new Cell(0,5, Tetris.I); cells[3] = new Cell(0,6, Tetris.I); //共有两种旋转状态 states =new State[2]; //初始化两种状态的相对坐标 states[0]=new State(0,0,0,-1,0,1,0,2); states[1]=new State(0,0,-1,0,1,0,2,0); } }
public class J extends Tetromino { public J() { cells[0] = new Cell(0,4, Tetris.J); cells[1] = new Cell(0,3, Tetris.J); cells[2] = new Cell(0,5, Tetris.J); cells[3] = new Cell(1,5, Tetris.J); states=new State[4]; states[0]=new State(0,0,0,-1,0,1,1,1); states[1]=new State(0,0,-1,0,1,0,1,-1); states[2]=new State(0,0,0,1,0,-1,-1,-1); states[3]=new State(0,0,1,0,-1,0,-1,1); } }
public class L extends Tetromino { public L() { cells[0] = new Cell(0,4, Tetris.L); cells[1] = new Cell(0,3, Tetris.L); cells[2] = new Cell(0,5, Tetris.L); cells[3] = new Cell(1,3, Tetris.L); states=new State[4]; states[0]=new State(0,0,0,-1,0,1,1,-1); states[1]=new State(0,0,-1,0,1,0,-1,-1); states[2]=new State(0,0,0,1,0,-1,-1,1); states[3]=new State(0,0,1,0,-1,0,1,1); } }
public class O extends Tetromino { public O() { cells[0] = new Cell(0, 4, Tetris.O); cells[1] = new Cell(0, 5, Tetris.O); cells[2] = new Cell(1, 4, Tetris.O); cells[3] = new Cell(1, 5, Tetris.O); //无旋转状态 states = new State[0]; } }
public class S extends Tetromino { public S() { cells[0] = new Cell(0,4, Tetris.S); cells[1] = new Cell(0,5, Tetris.S); cells[2] = new Cell(1,3, Tetris.S); cells[3] = new Cell(1,4, Tetris.S); //共有两种旋转状态 states =new State[2]; //初始化两种状态的相对坐标 states[0]=new State(0,0,0,1,1,-1,1,0); states[1]=new State(0,0,1,0,-1,-1,0,-1); } }
public class T extends Tetromino { public T() { cells[0] = new Cell(0,4, Tetris.T); cells[1] = new Cell(0,3, Tetris.T); cells[2] = new Cell(0,5, Tetris.T); cells[3] = new Cell(1,4, Tetris.T); states=new State[4]; states[0]=new State(0,0,0,-1,0,1,1,0); states[1]=new State(0,0,-1,0,1,0,0,-1); states[2]=new State(0,0,0,1,0,-1,-1,0); states[3]=new State(0,0,1,0,-1,0,0,1); } }
public class Z extends Tetromino { public Z() { cells[0] = new Cell(1,4, Tetris.Z); cells[1] = new Cell(0,3, Tetris.Z); cells[2] = new Cell(0,4, Tetris.Z); cells[3] = new Cell(1,5, Tetris.Z); //共有两种旋转状态 states =new State[2]; //初始化两种状态的相对坐标 states[0]=new State(0,0,-1,-1,-1,0,0,1); states[1]=new State(0,0,-1,1,0,1,1,0); } }
俄罗斯方块游戏主类:Tetris
public class Tetris extends JPanel { //正在下落的方块 private Tetromino currentOne = Tetromino.randomOne(); //将要下落的方块 private Tetromino nextOne = Tetromino.randomOne(); //游戏主区域 private Cell[][] wall = new Cell[18][9]; //声明单元格的值 private static final int CELL_SIZE = 48; //游戏分数池 int[] scores_pool = {0, 1, 2, 5, 10}; //当前游戏的分数 private int totalScore = 0; //当前消除的行数 private int totalLine = 0; //游戏三种状态 游戏中、暂停、结束 public static final int PLING = 0; public static final int STOP = 1; public static final int OVER = 2; //当前游戏状态值 private int game_state; //显示游戏状态 String[] show_state = {"P[pause]", "C[continue]", "S[replay]"}; //载入方块图片 public static BufferedImage I; public static BufferedImage J; public static BufferedImage L; public static BufferedImage O; public static BufferedImage S; public static BufferedImage T; public static BufferedImage Z; public static BufferedImage background; static { try { I = ImageIO.read(new File("images/I.png")); J = ImageIO.read(new File("images/J.png")); L = ImageIO.read(new File("images/L.png")); O = ImageIO.read(new File("images/O.png")); S = ImageIO.read(new File("images/S.png")); T = ImageIO.read(new File("images/T.png")); Z = ImageIO.read(new File("images/Z.png")); background = ImageIO.read(new File("images/background.png")); } catch (IOException e) { e.printStackTrace(); } } @Override public void paint(Graphics g) { g.drawImage(background, 0, 0, null); //平移坐标轴 g.translate(22, 15); //绘制游戏主区域 paintWall(g); //绘制正在下落的四方格 paintCurrentOne(g); //绘制下一个将要下落的四方格 paintNextOne(g); //绘制游戏得分 paintSource(g); //绘制当前游戏状态 paintState(g); } public void start() { game_state = PLING; KeyListener l = new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { int code = e.getKeyCode(); switch (code) { case KeyEvent.VK_DOWN: sortDropActive(); break; case KeyEvent.VK_LEFT: moveleftActive(); break; case KeyEvent.VK_RIGHT: moveRightActive(); break; case KeyEvent.VK_UP: rotateRightActive(); break; case KeyEvent.VK_SPACE: hadnDropActive(); break; case KeyEvent.VK_P: //判断当前游戏状态 if (game_state == PLING) { game_state = STOP; } break; case KeyEvent.VK_C: if (game_state == STOP) { game_state = PLING; } break; case KeyEvent.VK_S: //重新开始 game_state = PLING; wall = new Cell[18][9]; currentOne = Tetromino.randomOne(); nextOne = Tetromino.randomOne(); totalScore = 0; totalLine = 0; break; } } }; //将窗口设置为焦点 this.addKeyListener(l); this.requestFocus(); while (true) { if (game_state == PLING) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } if (camDrop()) { currentOne.moveDrop(); } else { landToWall(); destroyLine(); if (isGameOver()) { game_state = OVER; } else { //游戏没有结束 currentOne = nextOne; nextOne = Tetromino.randomOne(); } } } repaint(); } } //创建顺时针旋转 public void rotateRightActive() { currentOne.rotateRight(); if (outOFBounds() || coincide()) { currentOne.rotateLeft(); } } //瞬间下落 public void hadnDropActive() { while (true) { //判断能否下落 if (camDrop()) { currentOne.moveDrop(); } else { break; } } //嵌入到墙中 landToWall(); destroyLine(); if (isGameOver()) { game_state = OVER; } else { //游戏没有结束 currentOne = nextOne; nextOne = Tetromino.randomOne(); } } //按键一次,下落一格 public void sortDropActive() { if (camDrop()) { //当前四方格下落一格 currentOne.moveDrop(); } else { landToWall(); destroyLine(); if (isGameOver()) { game_state = OVER; } else { //游戏没有结束 currentOne = nextOne; nextOne = Tetromino.randomOne(); } } } //单元格嵌入墙中 private void landToWall() { Cell[] cells = currentOne.cells; for (Cell cell : cells) { int row = cell.getRow(); int col = cell.getCol(); wall[row][col] = cell; } } //判断四方格能否下落 public boolean camDrop() { Cell[] cells = currentOne.cells; for (Cell cell : cells) { int row = cell.getRow(); int col = cell.getCol(); //判断是否到达底部 if (row == wall.length - 1) { return false; } else if (wall[row + 1][col] != null) { return false; } } return true; } //消除行 public void destroyLine() { int line = 0; Cell[] cells = currentOne.cells; for (Cell cell : cells) { int row = cell.getRow(); if (isFullLine(row)) { line++; for (int i = row; i > 0; i--) { System.arraycopy(wall[i - 1], 0, wall[i], 0, wall[0].length); } wall[0] = new Cell[9]; } } //分数池获取分数,累加到总分 totalScore += scores_pool[line]; //总行数 totalLine += line; } //判断当前行是否已经满了 public boolean isFullLine(int row) { Cell[] cells = wall[row]; for (Cell cell : cells) { if (cell == null) { return false; } } return true; } //判断游戏是否结束 public boolean isGameOver() { Cell[] cells = nextOne.cells; for (Cell cell : cells) { int row = cell.getRow(); int col = cell.getCol(); if (wall[row][col] != null) { return true; } } return false; } private void paintState(Graphics g) { if (game_state == PLING) { g.drawString(show_state[PLING], 500, 660); } else if (game_state == STOP) { g.drawString(show_state[STOP], 500, 660); } else { g.drawString(show_state[OVER], 500, 660); g.setColor(Color.RED); g.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 60)); g.drawString("GAME OVER!", 30, 400); } } private void paintSource(Graphics g) { g.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 30)); g.drawString("分数: " + totalScore, 500, 250); g.drawString("行数: " + totalLine, 500, 430); } private void paintNextOne(Graphics g) { Cell[] cells = nextOne.cells; for (Cell cell : cells) { int x = cell.getCol() * CELL_SIZE + 370; int y = cell.getRow() * CELL_SIZE + 25; g.drawImage(cell.getImage(), x, y, null); } } private void paintCurrentOne(Graphics g) { Cell[] cells = currentOne.cells; for (Cell cell : cells) { int x = cell.getCol() * CELL_SIZE; int y = cell.getRow() * CELL_SIZE; g.drawImage(cell.getImage(), x, y, null); } } private void paintWall(Graphics g) { for (int i = 0; i < wall.length; i++) { for (int j = 0; j < wall[i].length; j++) { int x = j * CELL_SIZE; int y = i * CELL_SIZE; Cell cell = wall[i][j]; //判断是否有小方块 if (cell == null) { g.drawRect(x, y, CELL_SIZE, CELL_SIZE); } else { g.drawImage(cell.getImage(), x, y, null); } } } } //判断是否出界 public boolean outOFBounds() { Cell[] cells = currentOne.cells; for (Cell cell : cells) { int col = cell.getCol(); int row = cell.getRow(); if (row < 0 || row > wall.length - 1 || col < 0 || col > wall[0].length-1) { return true; } } return false; } //按键一次,左移一次 public void moveleftActive() { currentOne.moveLeft(); //判断是否越界或重合 if (outOFBounds() || coincide()) { currentOne.moveRight(); } } //按键一次,右移一次 public void moveRightActive() { currentOne.moveRight(); //判断是否越界或重合 if (outOFBounds() || coincide()) { currentOne.moveLeft(); } } //判断是否重合 public boolean coincide() { Cell[] cells = currentOne.cells; for (Cell cell : cells) { int row = cell.getRow(); int col = cell.getCol(); if (wall[row][col] != null) { return true; } } return false; } public static void main(String[] args) { JFrame jFrame = new JFrame("俄罗斯方块"); //创建游戏界面 Tetris panel = new Tetris(); jFrame.add(panel); //设置可见 jFrame.setVisible(true); //设置窗口大小 jFrame.setSize(810, 940); //设置剧中 jFrame.setLocationRelativeTo(null); //设置窗口关闭时停止 jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //游戏主要开始逻辑 panel.start(); } }
三、效果展示
游戏开始,方快下落,右边区域展示即将下落的方块图、分数、消除的行数以及游戏切换的状态。
按下空格键,方块瞬间下落, 按下P键游戏暂停,消除一行分数为1(此处由分数池进行控制)
按下C键游戏继续。
按下S键,游戏重新开始。
方块占满,游戏结束,此时可以按下S键重新开始游戏。
本次游戏中所使用的素材文件以及所有的源代码文件都已经同步到Github,小伙伴们点击下方链接直接获取。
Github链接地址:点击获取完整源代码,下载到本地即可运行