在“java俄罗斯方块小游戏编写心得01”中已经将游戏界面完成,那么接下来就是要让方块动起来。
我们之前在写Cell类时,曾经给每个最基本小方块定义了三个方法,即左移、右移、下落,而七种方块皆是由四个最基本小方块组成,所以我们可以在Tetromino类中定义一个七种方块自动下落方法,然后在方法中对七种方块的四个最基本小方块分别调最基本小方块的下落方法,这样就可以使七种小方块动起来。左移、右移皆是如此。
代码如下:
package com.practict;
public class Tetromino {
protected Cell[] cells=new Cell[4];
public void moveLeft() {
for(Cell c:cells) {
c.Left();
}
}
public void moveRight() {
for(Cell c:cells) {
c.right();
}
}
public void softMoveDrop() {
for(Cell c:cells) {
c.drop();
}
}
}
仅仅这样还不足以使小方块动起来,我们需要在程序运行时自动调用该方法,现在我们想,当点开俄罗斯方块这个游戏时,这个游戏便已经开始,由此,我们可以定义一个start()方法,来封装所有游戏开始后可以进行的操作,包括游戏自动进行的和认为操控的如变形、右移等,然后在主方法中创建完窗体后对该方法进行自动调用,现在,这个小方块才算动起来了。
代码如下:
package com.practict;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Tetris extends JPanel{
public void start(){
currentOne.softMoveDrop();
}
public static void main(String[] args){
JFrame jf=new JFrame("火拼俄罗斯");
Tetris tetris=new Tetris();
jf.add(tetris);
jf.setVisible(true);
jf.setSize(535,595);
jf.setLocationRelativeTo(null);
jf.setDefaultCloseOperation(jf.EXIT_ON_CLOSE);
tetris.start();
}
}
当再次运行,我们发现他仍然没有下落,当程序运行后,start()方法只调用了一次,相应的,其中currentOne.moveDrop()方法也只调用了一次,那么如果想让他动起来,就得使得方法不断的去自己调用,所以,此处我们需要一个死循环来让系统不断的自动调用moveDrop方法
代码如下:
package com.practict;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Tetris extends JPanel{
public void start(){
while(true){
currentOne.softMoveDrop();
}
}
}
现在我们再次运行,发现仍然没有下落,这是因为虽然面板在启动时会自动调用paing(Graphics g)方法,但是也仅仅只是调用一次,所以系统只画出了最初的界面的样子,虽然在后台我们的方块在下落,但是面板并没有将其画出,那么现在我们就需要一个repaint()方法来重新将游戏界面画出,那么这样,我们就可以肉眼看到方块在下落了
代码如下:
package com.practict;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Tetris extends JPanel{
public void start(){
while(true){
currentOne.softMoveDrop();
repaint();
}
}
}
再次运行,发现界面上竟然没有了方块!为什么呢?这涉及到一个系统线程的问题,因为系统的运算速度非常快,所以我们还没有看到方块最开始到下落,他就已经落到了最下面,直至消失,所以现在我们需要一个小方法来控制系统的运行速度,使其能够使得我们肉眼可见,其中,Thread.sleep()方法中的参数单位为ms。
代码如下:
package com.practict;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Tetris extends JPanel{
public void start(){
while(true){
try{
Thread.sleep(500);
}catch(InterruptedException e){
e.printStackTrace;
}
currentOne.softMoveDrop();
repaint();
}
}
}
现在我们再运行后可以发现,方块已经可以开始下落了,但是我们也可以发现一个问题,方块无法停留在最下层,那么现在我们就需要一个方法来使方块当下落到最底层后使其停留在最底层,而不是消失在最底层。由此,我们可以定义一个canDrop()方法,当满足此方法时,调用softMoveDrop()方法,当不满足时,便停留在最底层。我们再来分析,什么情况下不可下落?当然是当方块下落到最底层时或者下一层有方块,那么如何表示?。
注:当判断语句进行判断时,若第一个对象满足if条件,则余下对象不再进行遍历判断,所以,这是第一次想到的不可行方法:我们的墙是由一个二维数组组成的,公有20行20列,换句话说,就是当小方块的行属性小于19时,是可以下落的,那么我们就可以对最CurrentOne方块的每个最基本小方块的行属性进行遍历,当小于19时,可以下落。
更:当某方块的row+1行的col列有方块时,或者行数等于19时是不可下落的。
代码如下:
package com.practict;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Tetris extends JPanel{
public boolean canDrop() {
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;
}
现在方块已经可以停留在最底层,可是实际上小方块并未真正落到界面内,如果要做到方块真正的嵌入墙内,就要让该方块将墙填满,所以现在需要一个langToWall()方法来实现该功能。
代码如下:
package com.practict;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Tetris extends JPanel{
public void landToWall() {
Cell[] cells=currentOne.cells;
for(Cell c:cells) {
int row=c.getRow();
int col=c.getCol();
wall[row][col]=c;
}
}
}
现在呢,游戏已经有一点小小的效果了,那么我们现在需要人和游戏进行交互,也就是通过键盘来对方块进行操控,怎样进行操控呢,对键盘进行监听,创建监听器及适配器后将其添加到游戏面板上,之后我们再想,如果桌面上当前打开了多个页面,那监听程序究竟监听哪一个?所以我们需要加入一个requestFocus()方法来确定当当前界面获取焦点后才进行监听游戏界面。
代码如下:
package com.practict;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Tetris extends JPanel{
public void start() {
KeyListener l=new KeyAdapter() {
public void keyPressed(KeyEvent e) {
int code=e.getKeyCode();
switch(code) {
case KeyEvent.VK_LEFT:
currentOne.moveLeft();
break;
case KeyEvent.VK_RIGHT:
currentOne.moveRight();
break;
case KeyEvent.VK_DOWN:
currentOne.moveDrop();
break;
}
repaint();
}
};
this.addKeyListener(l);
this.requestFocus();
}
此时,我们再次运行可以看到已经可以左右移动了,但是如果移动出界的话会抛出异常,同时方块停止下落,所以我们现在需要就好像方块停在最底层一样的方法来判断是否处于边界,如果处于边界的话我们应该阻止方块继续向该方向移动,同样,如果该方块的左边位置或者右边位置在移动前已经有方块在该位置,我们也应该阻止方块的继续移动,所以我们应该封装两个方法,判断出界方法outOfBounds()和coincident()方法。
代码如下:
package com.practict;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Tetris extends JPanel{
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<0||row>19) {
return true;
}
}
return false;
}
public boolean coincident() {
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;
}
现在我们可以在监听器中加入这两个判断语句来实现越界及重合判断,与其加入到监听器中,不如直接生成一个左移右移等的方法,在此方法中进行该判断,监听器中就只调用该方法,此种做法可以使代码更加简洁明了。在下面代码所示中,有两个下落的方法一个是软下落,一个是硬下落,什么是软下落,就是当我们在按下键时方块会加快下落的速度,当我们松开下键时,方块会恢复到自动下落的状态,硬下落是当我们按空格键时(我将硬下落的键设置为空格),方块会直接下落到底部。
代码如下:
package com.practict;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Tetris extends JPanel{
public void start() {
KeyListener l=new KeyAdapter() {
public void keyPressed(KeyEvent e) {
int code=e.getKeyCode();
switch(code) {
case KeyEvent.VK_LEFT:
moveLeftAction();
break;
case KeyEvent.VK_RIGHT:
moveRightAction();
break;
case KeyEvent.VK_DOWN:
softMoveDropAction();
break;
case KeyEvent.VK_SPACE:
hardMoveDropAction();
break;
}
repaint();
}
};
this.addKeyListener(l);
this.requestFocus();
}
public void moveLeftAction() {
currentOne.moveLeft();
if(outOfBounds()||coincident()) {
currentOne.moveRight();
}
}
public void moveRightAction() {
currentOne.moveRight();
if(outOfBounds()||coincident()) {
currentOne.moveLeft();
}
}
public void softMoveDropAction() {
if(canDrop()) {
currentOne.moveDrop();
}else {
landToWall();
currentOne=nextOne;
nextOne=Tetromino.RandomeOne();
}
}
public void hardMoveDropAction() {
while(canDrop()) {
currentOne.moveDrop();
}
landToWall();
currentOne=nextOne;
nextOne=Tetromino.RandomeOne();
}
现在,我们再次移动方块时不仅左右移动不会出界,还增加了两项功能,软下落和硬下落,现在的游戏已经开始有点意思了,那我们现在需要干什么?我们知道,方块不仅仅只有一种形态,他们都是可以变形的,或者有两种形态,或者四种,现在,我们需要将这四种状态写出来。如何去写,我们可以可以先在Tetromino类中写一个State内部类,来表示状态,之后在Tetromino类中定义一个状态数组,因为七个方块的状态是不相同的,所以我们需要分别在七个方块的类中定义该方块的四种状态。
代码如下:
package com.practict;
public class Tetromino {
protected State[] states;
public class State{
protected 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;
}
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;
}
public String toString() {
return "state [row0=" + row0 + ", col0=" + col0 + ", row1=" + row1 + ", col1=" + col1 + ", row2=" + row2
+ ", col2=" + col2 + ", row3=" + row3 + ", col3=" + col3 + "]";
}
}
}
现在状态类定义好了,接下来我们就在每个方块类中定义该方块的状态,当我们旋转方块时,我们都需要有一个轴,而根据我们“java俄罗斯方块小游戏编写心得01”中的图,我们可以以第0块基本小方块为轴进行旋转,即第0块的行列均为0,其他基本小方块的行列均在此基础上进行书写,有几个状态就写new几个State类的对象,依据State类的构造方法,在row,col,row1,col1,row2,col2,row3,col3上写对应的状态的形状的行列。
代码如下:
package com.CSDN;
public class I extends Tetromino{
public 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);
}
}
package com.CSDN;
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);
}
}
package com.CSDN;
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);
}
}
package com.CSDN;
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[1];
states[0]=new State(0,0,0,1,1,0,1,1);
}
}
package com.CSDN;
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[4];
states[0]=new State(0,0,0,1,1,-1,1,0);
states[1]=new State(0,0,1,0,-1,-1,0,-1);
states[2]=new State(0,0,0,-1,-1,1,-1,0);
states[3]=new State(0,0,-1,0,1,1,0,1);
}
}
package com.CSDN;
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);
}
}
package com.CSDN;
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[4];
states[0]=new State(0,0,-1,-1,-1,0,0,1);
states[1]=new State(0,0,-1,1,0,1,1,0);
states[2]=new State(0,0,1,1,1,0,0,-1);
states[3]=new State(0,0,-1,-1,0,-1,-1,0);
}
}
现在各个方块的状态已经定义完成,我们在按上键时方块会旋转,可是怎么旋转?我们仅仅有了状态,而状态仅仅是相对于第0块基本小方块的相对位置,并不是真正的行列,所以我们需要一个方法来得出小方块变化后的行列位置,即变化后小方块的各个基本小方块的行列,也可以说是定义一个小方块旋转的方法,我们现在思考这样一个问题,当我们的小方块在边界时,我们是不是也有可能会旋转出界,从而引发数组下标越界异常呢?答案是当然会,所以我们效仿之前判断越界的方法,我们每一次按上键都是向右旋转,当我们在边界发现越界时,我们可以使其向左旋转,这样就避免了出现异常,所以我们不光需要一个向右旋转的方法,我们还需要一个向左旋转的方法。
代码如下:
package com.CSDN;
public class Tetromino {
protected Cell[] cells=new Cell[4];
private int count=100000;
public void rotateRight() {
count++;
State 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(row+s.col1);
cells[2].setRow(row+s.row2);
cells[2].setCol(row+s.col2);
cells[3].setRow(row+s.row3);
cells[3].setCol(row+s.col3);
}
public void rotateLeft() {
count--;
State 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);
}
}
此时,我们的方法都封装好,那么接下来我们所需要做的就是在主类中去调用它实现对游戏的操控。
代码如下:
package com.CSDN;
import java.awt.Graphics;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Tetris extends JPanel{
public void start() {
KeyListener l=new KeyAdapter() {
public void keyPressed(KeyEvent e) {
int code=e.getKeyCode();
switch(code) {
case KeyEvent.VK_UP:
rotateAction();
break;
}
repaint();
}
};
this.addKeyListener(l);
this.requestFocus();
}
public void rotateAction() {
currentOne.rotateRight();
if(outOfBounds()||coincident()) {
currentOne.rotateLeft();
}
}
}
现在我们再运行代码,基本的左右移动,上变形,即软硬下落都可以使用,都已经进行了实现,游戏也已经开发到了2/3,余下的开发会在java俄罗斯方块小游戏编写心得03中继续演示。