业务需求->业务对象模型(对象关系)->数据建模->类的设计->概要编码->详细功能设计
基本规则:
分别为:S、Z、L、J、I、O、T这几种
数据结构设计:
Cell 格子
|-- int row 行
|-- int col 列
|-- Image image 贴图
Tetromino 四格方块,有7个子类
|-- Cell[] cells 包含4个格子
Tetris 俄罗斯方块
|-- int score 分数
|-- int lines 行数
|-- Cell[][] wall = new Cell[20][10] 20*10的墙
|-- Tetromino tetromino 正在下落的方块
|-- Trtromino nextOne 下一个下落方块
其次是功能:
就在初始数据的数值状态设计
四格方块的下落计算: 就是将每个格子的row+1
四格子方块的左右移动计算:就是将每个格子的col+1或col-1
下落流程控制:控制方块下落与墙之间的控制关系
1) 如果"能够下落", 就下落一步
2) 否则就"着陆到墙里面"
3) 着落以后, "销毁充满的行", 并且记分
4) 检查游戏是否结束
5) 如果没有结束,还能玩,就生成下一个方块
分数计算
界面的绘制
键盘事件控制
旋转流程控制
加速下降流程控制
记分
开始流程控制(Timer)
主刷新(Timer)频率: 1/100 秒
0.5秒执行一次下落(是主刷新的50倍)
利用定时器定时的调用 下落流程
如果暂停时候, 就不执行下落流程来
实现过程:
1) 在Tetris类中添加属性 Timer, 用于启动主刷新频率
2) 在Tetris类中添加属性 level 是下落的间隔次数
如: 50 表示50次刷新以后,执行一次 下落, 如果减少,
加快下落
3) 在Tetris类中添加属性 stepIndex 是下落计数
每当 stepIndex % level == 0 时候 执行一次 下落
4) 在Tetris类中添加属性 pause 暂停执行状态控制
5) 在action() 方法添加主刷新Timer的启动
在主刷新中 添加代码 控制下落
绘制背景图片
1) 在Tetris类中声明静态变量 background
2) 使用静态代码块 加载磁盘文件到内存对象
将使用到 图片读取API: ImageIO
3) 在paint 方法中绘制背景图片
绘制 正在下落的方块
1) 先在action方法中生产 正在下落的方块和下一个方块
2) 在paint方法中调用 paintTetromino()方法
3) 在Tetris类中 增加paintTetromino()方法
将正在下落的4格方块的每个格子逐一绘制出来
使方块移动
1) 处理键盘事件(API), 获得用户何时按下 -> <-
2) 当按下 -> 按键时候, 执行当前4格方块 向右移动 方法
3) 向右移动 方法 会改变当前4格方块的每个格子的列坐标
4) 调用继承于JPanel类的 repaint(), 这个方法会尽快的
调用 paint()
5) paint() 会根据当前数据(已经被右移动改变的数据)
绘制全部面板效果
以下是源码:
Cell.java(格子类)
1 package com.timmy.tetris; 2 import java.awt.Image; 3 public class Cell { 4 /** 5 *一个格子 6 */ 7 private int row; 8 private int col; 9 private Image image; 10 public Cell(int row,int col,Image image){ 11 this.row = row; 12 this.col = col; 13 this.image = image; 14 } 15 16 //toString打印行列,方便调试 17 public String toString(){ 18 return "["+row+","+col+"]"; 19 } 20 21 public void drop(){ 22 row++; 23 } 24 25 public void moveLeft(){ 26 col--; 27 } 28 29 public void moveRight(){ 30 col++; 31 } 32 33 34 public void setRow(int row){ 35 this.row = row; 36 } 37 38 public int getRow(){ 39 return row; 40 } 41 public int getCol() { 42 return col; 43 } 44 public void setCol(int col) { 45 this.col = col; 46 } 47 public Image getImage() { 48 return image; 49 } 50 public void setImage(Image image) { 51 this.image = image; 52 } 53 }
Tetromino.java(四格方块类)
1 package com.timmy.tetris; 2 import java.util.Random; 3 //抽象类 4 public abstract class Tetromino { 5 /** 6 * 四格方块类 7 */ 8 //是留给子类的,所以用保护 9 protected Cell[] cells = new Cell[4]; 10 protected State[] states;//旋转状态 11 12 /**私有构造器,不会有子类*/ 13 private Tetromino(){ 14 } 15 16 /**内部类、旋转*/ 17 protected class State{ 18 int row0,col0,row1,col1,row2,col2,row3,col3; 19 public State(int row0, int col0, int row1, int col1, int row2, 20 int col2, int row3, int col3) { 21 super(); 22 this.row0 = row0; 23 this.col0 = col0; 24 this.row1 = row1; 25 this.col1 = col1; 26 this.row2 = row2; 27 this.col2 = col2; 28 this.row3 = row3; 29 this.col3 = col3; 30 } 31 } 32 33 /**旋转下标*/ 34 private int index = 10000; 35 36 /**向右转*/ 37 public void rotateRight(){ 38 index++; //10001 39 State s = states[index%states.length];//取余是[1] 40 //s = s1 41 Cell o = cells[0];//找到0号格子作为旋转轴 42 int row = o.getRow(); 43 int col = o.getCol(); 44 cells[1].setRow(row+s.row1); 45 cells[1].setCol(col+s.col1); 46 cells[2].setRow(row+s.row2); 47 cells[2].setCol(col+s.col2); 48 cells[3].setRow(row+s.row3); 49 cells[3].setCol(col+s.col3); 50 } 51 52 /**向左转*/ 53 public void rotateLeft(){ 54 index--; 55 State s = states[index%states.length]; 56 Cell o = cells[0]; 57 int row = o.getRow(); 58 int col = o.getCol(); 59 cells[1].setRow(row+s.row1); 60 cells[1].setCol(col+s.col1); 61 cells[2].setRow(row+s.row2); 62 cells[2].setCol(col+s.col2); 63 cells[3].setRow(row+s.row3); 64 cells[3].setCol(col+s.col3); 65 } 66 67 /**下落一步*/ 68 public void softDrop(){ 69 for(int i=0;i<cells.length;i++){ 70 Cell cell = cells[i]; 71 cell.drop(); 72 } 73 } 74 public void moveLeft(){ 75 for(int i=0;i<cells.length;i++){ 76 Cell cell = cells[i]; 77 cell.moveLeft(); 78 } 79 } 80 public void moveRight(){ 81 for(int i=0;i<cells.length;i++){ 82 Cell cell = cells[i]; 83 cell.moveRight(); 84 } 85 86 } 87 88 //简单工厂方法模式: 89 public static Tetromino randomOne(){ 90 Random r = new Random(); 91 int type = r.nextInt(7); 92 switch(type){ 93 case 0: return new T(); 94 case 1: return new I(); 95 case 2: return new S(); 96 case 3: return new Z(); 97 case 4: return new L(); 98 case 5: return new J(); 99 case 6: return new O(); 100 } 101 return null; 102 } 103 104 //利用私有内部类,封装了子类实现的细节,永远只有7种子类 105 //静态会造成用类名直接访问,外部不知道,别人不能new 106 private static class T extends Tetromino{ 107 public T(){ 108 cells[0] = new Cell(0,4,Tetris.T); 109 cells[1] = new Cell(0,3,Tetris.T); 110 cells[2] = new Cell(0,5,Tetris.T); 111 cells[3] = new Cell(1,4,Tetris.T); 112 states = new State[4]; 113 states[0] = new State(0,0,0,-1,0,1,1,0); 114 states[1] = new State(0,0,-1,0,1,0,0,-1); 115 states[2] = new State(0,0,0,1,0,-1,-1,0); 116 states[3] = new State(0,0,1,0,-1,0,0,1); 117 } 118 } 119 private static class I extends Tetromino{ 120 public I(){ 121 cells[0] = new Cell(0,4,Tetris.I); 122 cells[1] = new Cell(0,3,Tetris.I); 123 cells[2] = new Cell(0,5,Tetris.I); 124 cells[3] = new Cell(0,6,Tetris.I); 125 states = new State[2]; 126 states[0] = new State(0,0,0,-1,0,1,0,2); 127 states[1] = new State(0,0,-1,0,1,0,2,0); 128 } 129 130 } 131 private static class S extends Tetromino{ 132 public S(){ 133 cells[0] = new Cell(0,4,Tetris.S); 134 cells[1] = new Cell(0,5,Tetris.S); 135 cells[2] = new Cell(1,3,Tetris.S); 136 cells[3] = new Cell(1,4,Tetris.S); 137 states = new State[2]; 138 states[0] = new State(0,0,1,0,-1,-1,0,-1); 139 states[1] = new State(0,0,0,1,1,-1,1,0); 140 141 } 142 143 } 144 private static class Z extends Tetromino{ 145 public Z(){ 146 cells[0] = new Cell(1,4,Tetris.Z); 147 cells[1] = new Cell(0,3,Tetris.Z); 148 cells[2] = new Cell(0,4,Tetris.Z); 149 cells[3] = new Cell(1,5,Tetris.Z); 150 states = new State[2]; 151 states[0] = new State(0,0,-1,1,0,1,1,0); 152 states[1] = new State(0,0,-1,-1,-1,0,0,1); 153 } 154 155 } 156 private static class L extends Tetromino{ 157 public L(){ 158 cells[0] = new Cell(0,4,Tetris.L); 159 cells[1] = new Cell(0,3,Tetris.L); 160 cells[2] = new Cell(0,5,Tetris.L); 161 cells[3] = new Cell(1,3,Tetris.L); 162 states = new State[4]; 163 states[0] = new State(0,0,-1,0,1,0,-1,-1); 164 states[1] = new State(0,0,0,1,0,-1,-1,1); 165 states[2] = new State(0,0,1,0,-1,0,1,1); 166 states[3] = new State(0,0,0,-1,0,1,1,-1); 167 } 168 169 } 170 private static class J extends Tetromino{ 171 public J(){ 172 cells[0] = new Cell(0,4,Tetris.J); 173 cells[1] = new Cell(0,3,Tetris.J); 174 cells[2] = new Cell(0,5,Tetris.J); 175 cells[3] = new Cell(1,5,Tetris.J); 176 states = new State[4]; 177 states[0] = new State(0,0,-1,0,1,0,1,-1); 178 states[1] = new State(0,0,0,1,0,-1,-1,-1); 179 states[2] = new State(0,0,1,0,-1,0,-1,1); 180 states[3] = new State(0,0,0,-1,0,1,1,1); 181 } 182 183 } 184 private static class O extends Tetromino{ 185 public O(){ 186 cells[0] = new Cell(0,4,Tetris.O); 187 cells[1] = new Cell(0,5,Tetris.O); 188 cells[2] = new Cell(1,4,Tetris.O); 189 cells[3] = new Cell(1,5,Tetris.O); 190 states = new State[1]; 191 states[0] = new State(0,0,0,1,1,0,1,1); 192 } 193 194 } 195 }
Tetris.java(俄罗斯方块类【主类】)
1 package com.timmy.tetris; 2 import java.awt.Color; 3 import java.awt.Font; 4 import java.awt.Graphics; 5 import java.awt.Image; 6 import java.awt.event.KeyAdapter; 7 import java.awt.event.KeyEvent; 8 import java.awt.event.KeyListener; 9 import java.util.Arrays; 10 import java.util.Timer; 11 import java.util.TimerTask; 12 import javax.imageio.ImageIO; 13 import javax.swing.JPanel; 14 import javax.swing.JFrame; 15 //JPanel 图形界面上能够显示的空白面板(空白矩形区域) 16 //扩展了面板为 俄罗斯方块,扩展出 分数 和 正在下落的 17 //方块,以及下一个下落的方块 18 public class Tetris extends JPanel{ 19 /** 20 * 俄罗斯方块类,继承于JPanel 21 */ 22 public static final int ROWS = 20; 23 public static final int COLS = 10; 24 //格子的绘制大小 25 public static final int CELL_SIZE = 26; 26 //行数 27 private int lines; 28 //分数 29 private int score; 30 //字体颜色 31 public static final int FONT_COLOR =0x667799; 32 public static final int FONT_SIZE = 30; 33 34 private boolean pause; 35 private boolean gameOver; 36 private Timer timer; 37 //时间间隔 38 private int inteval = 500; 39 //墙 40 private Cell[][] wall = new Cell[20][10]; 41 //正在下落的方块 42 private Tetromino tetromino; 43 //下一个方块 44 private Tetromino nextOne; 45 //分数表,用于针对一次性消除不同行数,给不同分数 46 private int[] scoreTable= {0,1,5,10,20}; 47 //利用静态代码块静态加载图片资源 48 //将磁盘上的图片文件,加载到内存中的图片对象 49 public static Image background; 50 public static Image gameOverImg; 51 public static Image I; 52 public static Image T; 53 public static Image S; 54 public static Image Z; 55 public static Image L; 56 public static Image J; 57 public static Image O; 58 59 static {//静态代码块,只执行一次 60 //Class类提供了方法getResource()可以定位 61 //package中的文件位置 62 //图片文件到内存中的对象 63 //tetris.png 文件与Tetris.class 在同一个包中 64 try{ 65 Class cls = Tetris.class; 66 background = ImageIO.read(cls.getResource("tetris.png")); 67 gameOverImg = ImageIO.read(cls.getResource("game-over.png")); 68 I = ImageIO.read(cls.getResource("I.png")); 69 L = ImageIO.read(cls.getResource("L.png")); 70 J = ImageIO.read(cls.getResource("J.png")); 71 O = ImageIO.read(cls.getResource("O.png")); 72 S = ImageIO.read(cls.getResource("S.png")); 73 T = ImageIO.read(cls.getResource("T.png")); 74 Z = ImageIO.read(cls.getResource("Z.png")); 75 }catch(Exception e){ 76 e.printStackTrace(); 77 } 78 } 79 80 /**画界面*/ 81 //重写父类JPanel 类的 paint方法(绘图方法) 82 //重写之后修改了父类的paint方法,目的是实现自定义绘制 83 //Graphics 理解为一个绑定到当前面板的画笔 84 public void paint(Graphics g){ 85 g.drawImage(background, 0, 0, null); 86 //坐标系平移 87 g.translate(15,15); 88 //画墙 89 paintWall(g); 90 //画下落方块 91 paintTetromino(g); 92 //画下一个下落的方块 93 paintnextOne(g); 94 //画分数 95 paintScore(g); 96 if(gameOver){ 97 g.translate(-5, -5); 98 g.drawImage(gameOverImg,0,0,null); 99 } 100 } 101 102 /**绘制分数*/ 103 private void paintScore(Graphics g) { 104 105 int x = 289; 106 int y = 165; 107 g.setColor(new Color(FONT_COLOR)); 108 Font font = getFont();//获得系统字体 109 font = new Font(font.getFontName(),font.BOLD,FONT_SIZE); 110 g.setFont(font); 111 String str = "SCORE:" + score; 112 g.drawString(str, x, y); 113 y+= 54; 114 str = "LINES:" + lines; 115 g.drawString(str, x, y); 116 y+= 54; 117 str = "[P]Pause"; 118 if(pause){ 119 str = "[C]CONTINUE"; 120 } 121 if(gameOver){ 122 str = "[S]RESTART"; 123 } 124 g.drawString(str, x, y); 125 } 126 127 /**绘制墙,就是将Wall数组的内容绘制到界面*/ 128 private void paintWall(Graphics g){ 129 for(int row=0;row<wall.length;row++){ 130 //row是0~19 131 Cell[] line = wall[row]; 132 for(int col=0;col<line.length;col++){ 133 //col 是 0~9 134 Cell cell = line[col]; 135 int x = col*CELL_SIZE; 136 int y = row*CELL_SIZE; 137 if(cell==null){ 138 g.setColor(new Color(0));//黑色 139 //画方块 140 g.drawRect(x, y,CELL_SIZE, CELL_SIZE); 141 }else{ 142 g.drawImage(cell.getImage(), x-1, y-1, null); 143 } 144 } 145 } 146 } 147 148 /**启动软件*/ 149 public void action(){ 150 startAction(); 151 repaint();//JPanel 中的重绘方法,会尽快调用paint 152 //键盘按键监听器,KeyListener是KeyAdapter的父类 153 //利用匿名内部类 154 KeyListener l = new KeyAdapter() { 155 //如果有按键按下,就会执行 156 public void keyPressed(KeyEvent e){ 157 int key = e.getKeyCode(); 158 if(key == KeyEvent.VK_Q){ 159 System.exit(0);//结束程序 160 } 161 if(gameOver){ 162 if(key == KeyEvent.VK_S){ 163 startAction(); 164 repaint(); 165 } 166 return; 167 } 168 if(pause){ 169 if(key == KeyEvent.VK_C){ 170 continueAction(); 171 } 172 return; //提前结束方法,不再处理后续事件 173 } 174 switch(key){ 175 case KeyEvent.VK_DOWN:softDropAction(); 176 break; 177 case KeyEvent.VK_RIGHT:moveRightAction(); 178 break; 179 case KeyEvent.VK_LEFT:moveLeftAction(); 180 break; 181 case KeyEvent.VK_UP:rotateRightAction(); 182 break; 183 case KeyEvent.VK_SPACE:hardDropAction(); 184 break; 185 case KeyEvent.VK_P:pauseAction(); 186 break; 187 } 188 repaint();//重绘 189 } 190 }; 191 //将键盘监听器对象,添加到面板上 192 this.addKeyListener(l);//this 代表当前俄罗斯方块面板 193 this.requestFocus();//获得焦点 194 } 195 196 /**方块旋转*/ 197 private void rotateRightAction() { 198 tetromino.rotateRight(); 199 if(outOfBounds() || coincide()){ 200 tetromino.rotateLeft(); 201 } 202 } 203 204 /**画正在下落块*/ 205 public void paintTetromino(Graphics g){ 206 //如果没有正在下落的方块,就不绘制 207 if(tetromino==null){ 208 return; 209 } 210 Cell[] cells = tetromino.cells; 211 for(int i=0;i<cells.length;i++){ 212 Cell cell = cells[i]; 213 int x = cell.getCol()*CELL_SIZE; 214 int y = cell.getRow()*CELL_SIZE; 215 g.drawImage(cell.getImage(),x-1,y-1,null); 216 } 217 } 218 219 /**画下一个方块*/ 220 public void paintnextOne(Graphics g){ 221 //如果没有正在下落的方块,就不绘制 222 if(tetromino==null){ 223 return; 224 } 225 Cell[] cells = nextOne.cells; 226 for(int i=0;i<cells.length;i++){ 227 Cell cell = cells[i]; 228 int x = (cell.getCol()+10)*CELL_SIZE; 229 int y = (cell.getRow()+1)*CELL_SIZE; 230 g.drawImage(cell.getImage(),x-1,y-1,null); 231 } 232 } 233 234 /**下落方法*/ 235 private void softDropAction(){ 236 if(canDrop()){ 237 tetromino.softDrop(); 238 }else{ 239 landToWall(); 240 destroyLines(); 241 checkGameOver(); 242 tetromino = nextOne; 243 nextOne = Tetromino.randomOne(); 244 } 245 } 246 247 /**检查当前方块是否能下落*/ 248 private boolean canDrop(){ 249 Cell[] cells = tetromino.cells; 250 //检查当前方块是否到达底部 251 for(Cell cell: cells){ 252 int row = cell.getRow(); 253 if(row==ROWS-1){ 254 return false; 255 } 256 } 257 //检查是否到墙 258 for(Cell cell:cells){ 259 int row = cell.getRow(); 260 int col = cell.getCol(); 261 if(wall[row+1][col]!=null){ 262 return false; 263 } 264 } 265 return true; 266 } 267 /**着陆到墙*/ 268 private void landToWall(){ 269 Cell[] cells = tetromino.cells; 270 for(Cell cell:cells){ 271 int row = cell.getRow(); 272 int col = cell.getCol(); 273 wall[row][col] = cell; 274 } 275 } 276 277 /**硬下落*/ 278 public void hardDropAction(){ 279 while(canDrop()){ 280 tetromino.softDrop(); 281 } 282 landToWall(); 283 destroyLines(); 284 checkGameOver(); 285 tetromino = nextOne; 286 nextOne = Tetromino.randomOne(); 287 } 288 289 /**消除行*/ 290 private void destroyLines(){ 291 int lines = 0; 292 for(int row=0;row<ROWS;row++){ 293 if(fullCells(row)){ 294 deleteLine(row); 295 lines++; 296 } 297 } 298 this.lines += lines; 299 this.score += scoreTable[lines]; 300 } 301 302 /**检查是否这行是否满*/ 303 private boolean fullCells(int row){ 304 Cell[] line = wall[row]; 305 for(Cell cell:line){ 306 if(cell==null){ 307 return false; 308 } 309 } 310 return true; 311 } 312 313 /**删除行*/ 314 private void deleteLine(int row){ 315 for(int i=row;i>=1;i--){ 316 //wall[i-1]->wall[i] 317 System.arraycopy(wall[i-1], 0, wall[i], 0, COLS); 318 } 319 Arrays.fill(wall[0], null);//第零行归零 320 } 321 322 /**检查是否游戏结束*/ 323 private void checkGameOver(){ 324 if(wall[0][4] != null){ 325 gameOver = true; 326 timer.cancel(); 327 repaint(); 328 } 329 } 330 331 /**向左移动*/ 332 private void moveLeftAction(){ 333 tetromino.moveLeft(); 334 if(outOfBounds() || coincide()){ 335 tetromino.moveRight(); 336 } 337 repaint(); 338 } 339 340 /**向右移动*/ 341 private void moveRightAction(){ 342 tetromino.moveRight(); 343 //检查顺序一定是先检查是否出界,再检查是否重合 344 if(outOfBounds() || coincide()){ 345 tetromino.moveLeft(); 346 } 347 repaint(); 348 } 349 350 /**检查出界*/ 351 private boolean outOfBounds(){ 352 Cell[] cells = tetromino.cells; 353 for(Cell cell : cells){ 354 int col = cell.getCol(); 355 if(col<0 || col>=COLS){ 356 return true; 357 } 358 } 359 return false; 360 } 361 362 /**检查格子是否会重合*/ 363 private boolean coincide(){ 364 Cell[] cells = tetromino.cells; 365 for(Cell cell : cells){ 366 int row = cell.getRow(); 367 int col = cell.getCol(); 368 if(row>=0 && row<ROWS && col>=0 && col<COLS && wall[row][col]!=null){ 369 return true; 370 } 371 } 372 return false; 373 } 374 375 /**开始*/ 376 public void startAction(){ 377 pause = false; 378 gameOver = false; 379 score = 0; 380 lines = 0; 381 for(Cell[] line : wall){ 382 Arrays.fill(line, null); 383 } 384 tetromino = Tetromino.randomOne(); 385 nextOne = Tetromino.randomOne(); 386 TimerTask task = new TimerTask() { 387 public void run(){ 388 softDropAction(); 389 repaint(); 390 } 391 }; 392 timer = new Timer(); 393 timer.schedule(task, inteval, inteval); 394 } 395 396 /**暂停*/ 397 public void pauseAction(){ 398 timer.cancel(); 399 pause = true; 400 } 401 402 /**继续*/ 403 public void continueAction(){ 404 pause = false; 405 timer = new Timer(); 406 timer.schedule(new TimerTask(){ 407 public void run(){ 408 softDropAction(); 409 repaint(); 410 } 411 },inteval,inteval); 412 pause = false; 413 } 414 415 /**main 方法*/ 416 417 public static void main(String[] args) { 418 JFrame frame = new JFrame("俄罗斯方块");//窗口框 419 Tetris tetris = new Tetris();//Tetris 继承了JPanel 420 //Tetris 也是面板,面板可以放到frame中显示 421 422 frame.add(tetris); 423 //去处窗口装饰 424 frame.setUndecorated(true); 425 frame.setSize(530,580); 426 //设置窗口的默认关闭操作是退出程序 427 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 428 //设置窗口可见,frame在显示的时候会尽快的调用 paint方法 429 frame.setVisible(true); 430 //设置窗口居中 431 frame.setLocationRelativeTo(null); 432 //开始动作 433 tetris.action(); 434 } 435 }