自己的第一个项目--俄罗斯方块(JAVA)

 

一点一点写出来的程序,想跟大家分享一下自己的心得,可能有错误,还请多多包涵~

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() {//向右旋转
		//旋转有一次,计算器增长1
		count++;//100001
		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);	
	}
	
	public void rotateLeft() {//向左旋转方法有什么用呢?稍后就会就知道了.
		count--;//100001
		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);
	}

 以及随机生成七种方块中的一种的方法randomOne()

public static Tetromino randomOne() {
//随机生成方块,七种方块形状分别为O,T,I,J,L,S,Z
		Tetromino  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);//状态0
		states[1] = new State(0,0,-1,0,1,0,0,-1);//状态1
		states[2] = new State(0,0,0,1,0,-1,-1,0);//状态2
		states[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);
		// 绘制正在下落的四格方块,在区域5
		paintCurrentOne(g);
		// 绘制下一个将要下落的四格方块,在区域2
		paintNextOne(g);
		paintScore(g);//绘制游戏分数和列数,分数在区域3,列数在区域4
		paintState(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();//无需判断列数,所以不需要col
			while (row < 20) {
				if (isFullLine(row)) {//判断是否消除
					lines++;//消除的行数+1
					wall[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;//分数置为0
	totalLine = 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();
	}
}

至此一个简单的俄罗斯方块小游戏就编写完成了,点击运行,即可开始游戏!

总结:一个小程序打下来,感觉收获颇多,对面向对象编程更加熟悉了,打算多打几遍,争取只看简单的提示就可以完成这个程序.

  • 40
    点赞
  • 185
    收藏
    觉得还不错? 一键收藏
  • 16
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值