一点一点写出来的程序,想跟大家分享一下自己的心得,可能有错误,还请多多包涵~
Cell类:
public class Cell
定义了本游戏最基本的元素:小方块(cell)的基本参数:行,列,小方块图片:
private int row;//行数
private int col;//列数
private BufferedImage image;//图片
提供无参有参构造器:
public Cell() {}
public Cell(int row, int col, BufferedImage image) {super();this.row = row;this.col = col;this.image = image;}
提供get/set方法:
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;}
定义了本游戏最基本的行为:左移一格;右移一格;下落一格.
public void left() {col--;//向左移动,列数减1}public void right() {col++;//向右移动,列数加1}public void drop() {row++;//向下移动,行数+1}
Tetromino类,提供方块的各种行为方法:
public class Tetromino{
我们将一个四个方块视为一个数组:
protected Cell[] cells = new Cell[4];
将一个四个方块的四种状态也定义为数组:
protected State[] states;
添加一个作为旋转计数器的量:
private int count = 100000;//数值多少都可以
定义cell的各种行为:下落;左移;右移;旋转:
public void moveLeft() {//向左移动for(int i=0;i<cells.length;i++) {//for循环遍历整个"方块组"的四格方块Cell cell = cells[i];//四格方块都要移动cell.left();}}public void moveRight() {//向右移动for(Cell c:cells) {//此处使用增强for循环也可以c.right();}}public void softDrop() {//下落for(Cell c:cells) {c.drop();}}
以及旋转的方法
public void rotateRight() {//向右旋转//旋转有一次,计算器增长1count++;//100001State s = states[count%states.length];Cell c = cells[0];int row = c.getRow();int col = c.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() {//向左旋转方法有什么用呢?稍后就会就知道了.count--;//100001State s = states[count%states.length];Cell c = cells[0];int row = c.getRow();int col = c.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);}
以及随机生成七种方块中的一种的方法randomOne()
public static Tetromino randomOne() {
//随机生成方块,七种方块形状分别为O,T,I,J,L,S,ZTetromino t = null;int num = (int)(Math.random()*7);switch(num) {case 0:t = new O();break;case 1:t = new T();break;case 2:t = new I();break;case 3:t = new J();break;case 4:t = new L();break;case 5:t = new S();break;case 6:t = new Z();break;}return t;}
然后定义内部类state:
public class State{
此类用来描述方块旋转的四种状态,首先定义八个整型变量,用来描述四个方块的位置.0,1,2,3分别代表四个方块,我们旋转的时候以方块0为轴,其余三个方块向右旋转:
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) {super();this.row0 = row0;this.col0 = col0;this.row1 = row1;this.col1 = col1;this.row2 = row2;this.col2 = col2;this.row3 = row3;this.col3 = col3;}
get/set方法:
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;}
之后我们来定义七种方块(O,T,I,J,L,S,Z):
(注意七种方块均应该继承Tetromino类)
这里以T型为例详细讲解:
public class T extends Tetromino{/*** 提供构造器,进行初始化* T型的四格方块的位置* */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);//状态0states[1] = new State(0,0,-1,0,1,0,0,-1);//状态1states[2] = new State(0,0,0,1,0,-1,-1,0);//状态2states[3] = new State(0,0,1,0,-1,0,0,1);//状态三}
}
O型方块:
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[] { new State(0, 0, 0, 1, 1, 0, 1, 1)};}
}
I型方块:
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[] { new State(0, 0, 0, -1, 0, 1, 0, 2),new State(0, 0, -1, 0, 1, 0, 2, 0)};}
}
J型方块:
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[] { new State(0, 0, 0, 1, 0, -1, -1, -1),new State(0, 0, 1, 0, -1, 0, -1, 1),new State(0, 0, 0, -1, 0, 1, 1, 1),new State(0, 0, -1, 0, 1, 0, 1, -1)};}
}
L型方块
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[] { new State(0, 0, 0, 1, 0, -1, -1, 1),new State(0, 0, 1, 0, -1, 0, 1, 1),new State(0, 0, 0, -1, 0, 1, 1, -1),new State(0, 0, -1, 0, 1, 0, -1, -1)};}
}
S型方块:
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[] { new State(0, 0, 0, 1, 1, -1, 1, 0),new State(0, 0, -1, 0, 1, 1, 0, 1)};}
}
Z型方块:
public class Z extends Tetromino {/*** 提供构造器,进行初始化* Z型的四格方块的位置* */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[] { new State(0, 0, -1, -1, -1, 0, 0, 1),new State(0, 0, -1, 1, 0, 1, 1, 0)};}
}
主类Tetris类:游戏的核心,使用JPanel绘制游戏界面:
public class Tetris extends JPanel{
首先定义正在下落和即将下落的四格方块:
private Tetromino currentOne = Tetromino.randomOne();//正在下落private Tetromino nextOne = Tetromino.randomOne();//下一个下落
定义一个叫做墙的二维数组作为游戏界面:
private Cell[][] wall = new Cell[20][10];//20行10列
定义分数池,作为消除0,1,2,3,4列的得分:
int[] scores_pool = { 0, 1, 2, 5, 10 };
这里用来统计游戏分数和已经消除的行数:
private int totalScore = 0;//总分
private int totalLine = 0;//总行数
定义游戏的三种状态常量:正在游戏0,暂停1,游戏结束2:
public static final int PLAYING = 0;
public static final int PAUSE = 1;
public static final int GAMEOVER = 2;
游戏状态:
private int game_state;
这个数组用来显示游戏状态文字:
String[] showState = { "P[pause]", "C[continue]", "Enter[replay]" };
定义常量:方块的边长,已知是26:
private static final int CELL_SIZE = 26;//常量应使用private static final修饰.
游戏界面的各种图片,此时我们应该将图片放入此项目的包内:
public static BufferedImage T;//各种形状的方块public static BufferedImage I;public static BufferedImage O;public static BufferedImage J;public static BufferedImage L;public static BufferedImage S;public static BufferedImage Z;public static BufferedImage background;//游戏背景public static BufferedImage game_over;//游戏结束
如何读取资源呢?这里应该使用静态代码块进行读取,为了防止各种意外,我们将其放入try....catch中,这时会读取包内的同名图片:
static {try {//getResource(String url) url:加载图片的路径 相对位置是同包下T = ImageIO.read(Tetris.class.getResource("T.png"));O = ImageIO.read(Tetris.class.getResource("O.png"));I = ImageIO.read(Tetris.class.getResource("I.png"));J = ImageIO.read(Tetris.class.getResource("J.png"));L = ImageIO.read(Tetris.class.getResource("L.png"));S = ImageIO.read(Tetris.class.getResource("S.png"));Z = ImageIO.read(Tetris.class.getResource("Z.png"));background = ImageIO.read(Tetris.class.getResource("tetris.png"));game_over = ImageIO.read(Tetris.class.getResource("game-over.png"));} catch (Exception e) {e.printStackTrace();}}
接下来绘制游戏的各种图片,需要使用JPanel类中的paint()方法:
public void paint(Graphics g) {// 绘制背景,在区域1//g:画笔 g.drawImage(image,x,y,null) image:绘制的图片 x:开始绘制的横坐标 y:开始绘制的纵坐标g.drawImage(background, 0, 0, null);// 平移坐标轴g.translate(15, 15);// 绘制墙paintWall(g);// 绘制正在下落的四格方块,在区域5paintCurrentOne(g);// 绘制下一个将要下落的四格方块,在区域2paintNextOne(g);paintScore(g);//绘制游戏分数和列数,分数在区域3,列数在区域4paintState(g);//绘制游戏状态,在区域6}private void paintState(Graphics g) {//在右侧绘制游戏状态if (game_state == GAMEOVER) {//游戏结束g.drawImage(game_over, 0, 0, null);g.drawString(showState[GAMEOVER], 285, 265);}if (game_state == PLAYING) {//正在游戏g.drawString(showState[PLAYING], 285, 265);}if (game_state == PAUSE) {//暂停游戏g.drawString(showState[PAUSE], 285, 265);}}public void paintScore(Graphics g) {//在右侧位置绘制游戏分数g.setFont(new Font(Font.SANS_SERIF, Font.ITALIC, 26));g.drawString("SCORES:" + totalScore, 285, 165);g.drawString("LINES:" + totalLine, 285, 215);}/*** 绘制下一个将要下落的四格方块 绘制到面板的右上角的相应区域*/public void paintNextOne(Graphics g) {// 获取nextOne对象的四个元素Cell[] cells = nextOne.cells;for (Cell c : cells) {// 获取每一个元素的行号和列号int row = c.getRow();int col = c.getCol();// 横坐标和纵坐标int x = col * CELL_SIZE + 260;int y = row * CELL_SIZE + 26;g.drawImage(c.getImage(), x, y, null);}}/*** 绘制正在下落的四格方块 取出数组的元素 绘制元素的图片, 横坐标x: 纵坐标y:*/public void paintCurrentOne(Graphics g) {Cell[] cells = currentOne.cells;for (Cell c : cells) {int x = c.getCol() * CELL_SIZE;int y = c.getRow() * CELL_SIZE;g.drawImage(c.getImage(), x, y, null);}}/*** 墙是20行,10列的表格 是一个二维数组, 应该使用双层循环 绘制正方形。*/public void paintWall(Graphics a) {// 外层循环控制行数for (int i = 0; i < 20; i++) {// 内层循环控制列数for (int j = 0; j < 10; j++) {int x = j * CELL_SIZE;int y = i * CELL_SIZE;Cell cell = wall[i][j];if (cell == null) {a.drawRect(x, y, CELL_SIZE, CELL_SIZE);} else {a.drawImage(cell.getImage(), x, y, null);}}}}
之后开始设置游戏的各种状态:
布尔型方法,游戏是否结束:
public boolean isGameOver() {Cell[] cells = nextOne.cells;for (Cell c : cells) {int row = c.getRow();int col = c.getCol();if (wall[row][col] != null) {//若方块已经达到第20行,则游戏结束return true;}}return false;}
下落之后就要判断一行是否填满以便进行消除,所以我们定义布尔型方法,带参数row:
public boolean isFullLine(int row) {
把一行定义为一个数组进行遍历:
Cell[] line = wall[row];for (Cell c : line) {if (c == null) {//遍历到为空的方块即返回false,表明这一行没有满.return false;}}return true;}
关键的方法:消除
public void destroyLine(){
若其中一行满了则需进行消除,首先定义变量来统计消除的行数:
int lines = 0;
然后进入方法:
Cell[] cells = currentOne.cells;for (Cell c : cells) {int row = c.getRow();//无需判断列数,所以不需要colwhile (row < 20) {if (isFullLine(row)) {//判断是否消除lines++;//消除的行数+1wall[row] = new Cell[10];for (int i = row; i > 0; i--) {System.arraycopy(wall[i - 1], 0, wall[i], 0, 10);//复制数组方法}wall[0] = new Cell[10];//将被消除的行清空}row++;}}// 从分数池中取出分数,加入总分数totalScore += scores_pool[lines];totalLine += lines;
定义可以下落方法:
public boolean canDrop() {
该方法用来判断currentOne能否继续下落,只要这个方块的一个元素的下一行存在方块(不是null)或者已经到达底部则停止下落:
Cell[] cells = currentOne.cells;//当前方块数组for (Cell c : cells) {int row = c.getRow();int col = c.getCol();if (row == 19) {//落到底了return false;}if (wall[row + 1][col] != null) {//某一元素下面不为空return false;}}return true;}
不能下落之后就应该着陆了,然后应该把它镶嵌进wall中,即存储到wall[][]中:
public void landToWall() {Cell[] cells = currentOne.cells;for (Cell c : cells) {// 获取最终的行号和列号int row = c.getRow();int col = c.getCol();wall[row][col] = c;}}
为了防止游戏错误,我们应该设计两个方法来进行判定:
public boolean outOfBounds() {//越界异常Cell[] cells = currentOne.cells;for (Cell c : cells) {int col = c.getCol();int row = c.getRow();if (col < 0 || col > 9 || row > 19 || row < 0) {//不能越过wall[][]return true;}}return false;}
public boolean coincide() {//两个方块重合Cell[] cells = currentOne.cells;for (Cell c : cells) {int row = c.getRow();int col = c.getCol();if (wall[row][col] != null) {return true;}}return false;}
然后可以进行游戏的五种操作:左移,右移,缓慢下落,直接到底和旋转:
左移:
protected void moveLeftAction() {currentOne.moveLeft();if (outOfBounds() || coincide()) {//如果左移出了边界,执行右移的方法防止游戏错误currentOne.moveRight();}}
右移:
protected void moveRightAction() {currentOne.moveRight();if (outOfBounds() || coincide()) {//如果右移出了边界,执行左移的方法防止错误.currentOne.moveLeft();}}
缓慢下落:
public void softDropAction() {if (canDrop()) {currentOne.softDrop();} else {landToWall();destroyLine();currentOne = nextOne;//把这一个方块"变成"下一个方块nextOne = Tetromino.randomOne();//再随机生成一个"下一个方块"}}
直接到底:
public void handDropAction() {for (;;) {if (canDrop()) {currentOne.softDrop();} else {break;}}landToWall();destroyLine();if (!isGameOver()) {currentOne = nextOne;nextOne = Tetromino.randomOne();} else {game_state = GAMEOVER;}}
旋转:
public void rotateRightAction() {currentOne.rotateRight();if (outOfBounds() || coincide()) {//转过头了怎么办?这就是rotateLeft()方法的用处了currentOne.rotateLeft();}}
接下来把以上方法都编入start()
public void start() {//封装了游戏逻辑
将游戏状态置为PLAYING:
game_state = PLAYING;
游戏应该使用键盘操作,所以我们要开启键盘监听事件:
KeyListener l = new KeyAdapter() {
按键按下时即应该进行响应,注意此处keyPress()的k应该是小写,我就是因为这个导致很久没有运行成功:
public void keyPressed(KeyEvent e) {
定义code变量:
int code = e.getKeyCode();
按P(pause)键暂停游戏,前提是正在进行游戏:
if (code == KeyEvent.VK_P) {//VK_P即表示键盘P键if (game_state == PLAYING) {//状态为PLAYING才能暂停game_state = PAUSE;}
}
按C(continue)键继续游戏:
if (code == KeyEvent.VK_C) {if (game_state == PAUSE) {game_state = PLAYING;}
}
按回车键开始游戏:
if (code == KeyEvent.VK_ENTER) {game_state = PLAYING;wall = new Cell[20][10];//画一个新的"墙"currentOne = Tetromino.randomOne();nextOne = Tetromino.randomOne();totalScore = 0;//分数置为0totalLine = 0;//列数置为0
}
上下左右空格键来操作方块,这里利用switch循环:
switch (code) {case KeyEvent.VK_DOWN://按下缓慢下降softDropAction();break;case KeyEvent.VK_LEFT://按左左移moveLeftAction();break;case KeyEvent.VK_RIGHT://按右右移moveRightAction();break;case KeyEvent.VK_UP://按上变形rotateRightAction();break;case KeyEvent.VK_SPACE://按空格直接到底handDropAction();break;}repaint();//每操作一次都要重新绘制方块
}
};//内部类
把监听添加进面板,并把面板设置为焦点:
this.addKeyListener(l);
this.requestFocus();
由于CPU的速度极快,导致我们不能看清方块的下落,所以我们要设置延时,让CPU"睡眠"一段时间后再进行下一次下落:
while (true) {
/*** 当程序运行到此,会进入睡眠状态, 睡眠时间为800毫秒,单位为毫秒 800毫秒后,会自动执行后续代码*/
try {Thread.sleep(800);
} catch (InterruptedException e) {e.printStackTrace();
}
补充剩余部分:
if (game_state == PLAYING) {if (canDrop()) {currentOne.softDrop();} else {landToWall();destroyLine();// 将下一个下落的四格方块赋值给正在下落的变量if (!isGameOver()) {currentOne = nextOne;nextOne = Tetromino.randomOne();} else {game_state = GAMEOVER;}}repaint();/** 下落之后,要重新进行绘制,才会看到下落后的 位置 repaint方法 也是JPanel类中提供的 此方法中调用了paint方法*/}
}
}
一切准备就绪,可以编写主方法来运行游戏了:
public static void main(String[] args) {
// 1:创建一个窗口对象JFrame frame = new JFrame("俄罗斯方块");// 创建游戏界面,即面板Tetris panel = new Tetris();// 将面板嵌入窗口frame.add(panel);// 2:设置为可见frame.setVisible(true);// 3:设置窗口的尺寸frame.setSize(535, 580);// 4:设置窗口居中frame.setLocationRelativeTo(null);// 5:设置窗口关闭,即程序终止frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);// 游戏的主要逻辑封装在start方法中panel.start();}
}
至此一个简单的俄罗斯方块小游戏就编写完成了,点击运行,即可开始游戏!
总结:一个小程序打下来,感觉收获颇多,对面向对象编程更加熟悉了,打算多打几遍,争取只看简单的提示就可以完成这个程序.















