俄罗斯方块java
本文中的代码只是部分代码,全部代码及用到的图片下载地址:链接: https://pan.baidu.com/s/1Ryvr0uxPVX22Ag3aVzGQKQ 密码: tm48
1、我们来分析一下游戏界面,从中能抽象出来:小方块类型,组合方块类型
先来创建 Cell() 小方块类型,此类代表的时游戏中的最小单位(自身的属性有:row--行号 , col--列号 , image--图片)(行为方法有:left()--左移 , right()--右移 , drop()--下移),下面是具体代码:
package com.tetris2;
import java.awt.image.BufferedImage;
public class 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;
}
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 "(" + row + ", " + col + ")";
}
//小方块向左移动
public void left(){
col--;
}
//小方块向右移动
public void rigth(){
col++;
}
//小方块向下移动
public void drop(){
row++;
}
}
2、通过游戏中的7种不同的方块,可以抽象出它们的父类Tetromino(),这7中不同的方块有着共同的特征:都是由4个小方块Cell()组成的,都可以左/右/下移动并且旋转,代码如下:
package com.tetris2;
import java.util.Arrays;
public class Tetromino {
//组合的方块都是有最小单位的4个方块组成的
protected Cell[] cells = new Cell[4];
public Cell[] getCells() {
return cells;
}
public void setCells(Cell[] cells) {
this.cells = cells;
}
//方块向左移动
public void moveLeft(){
for(Cell c:cells){
c.left();
}
}
//方块向右移动
public void moveRigth(){
for(Cell c:cells){
c.rigth();
}
}
//方块向下移动
public void softDrop(){
for(Cell c:cells){
c.drop();
}
}
@Override
public String toString() {
return "[" + Arrays.toString(cells) + "]";
}
/*
* 随机生成一个四个方块
*/
public static Tetromino randomOne(){
Tetromino t = null;
int num = (int)(Math.random()*7);//生成0~6随机数,来生成7种不同的随机方块
switch (num) {
case 0:t = new T();break;
case 1:t = new Z();break;
case 2:t = new O();break;
case 3:t = new I();break;
case 4:t = new J();break;
case 5:t = new L();break;
case 6:t = new S();break;
}
return t;
}
}
3、让7中不同方块子类来继承父类Tetromino()7种方块都有着自己的坐标和图片,所以在写7种方的类时提供一个构造器用来进行初始化(7中不同方块初始位置可参考图Enter.PNG),下面代码只给出T的代码如下:
package com.tetris2;
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);
}
}
4、还需要定义一个主类Tetris() 来传入方块的图片
一、显示出一个窗口作为游戏的界面,要绘制出背景,及游戏区域的网格(程序完事后可注释掉)
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();
}
在创建窗口时要重写paint()方法来进行墙,方块的绘制
public void paint(Graphics g) {
// 绘制背景
/*
* g:画笔 g.drawImage(image,x,y,null) image:绘制的图片 x:开始绘制的横坐标 y:开始绘制的纵坐标
*/
g.drawImage(background, 0, 0, null);
// 平移坐标轴
g.translate(15, 15);
// 绘制墙
paintWall(g);
// 绘制正在下落的四格方块
paintCurrentOne(g);
// 绘制下一个将要下落的四格方块
paintNextOne(g);
paintScore(g);
paintState(g);
}
二、绘制出正在下落的方块和即将下落的方块(这两个方块时随机生成的),在绘制方块是方块的长款是相同的,这是
就可以定义一个常量来存放方块的宽,因为每个大方块有4个小方块租场,所以要循环来确定每个小方块的初始下
落坐标(每个大方块的初始下落的位置是不变的)。代码如下:
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);
}
}
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);
}
}
三、让大方块开始下落,停止下落条件:大方块到达底部 或 大方块中任意一个小方块下面有小方块时,大方块停止下落,当大方块停止下落时,将大方块嵌入到墙中,在下落时,要进行进程休眠,这样才能看到下落过程。
停止下落:停止下落有两种状态:
1是方块到底后停止(判断大方块中的小方块有任意一个坐标到达底边界时,停止下落方块)
2是碰到其他方块(这个判断是要在下方已有下落到底的方块的前提下进行的,方块下落倒底后,
会嵌入到墙的二维数组中,之后在下落的方块只需判断在下落中的大方块下方的
墙的二维数组是否为空就行,不为空就无法在下落了)
代码如下:
while (true) {
/**
* 当程序运行到此,会进入睡眠状态, 睡眠时间为300毫秒,单位为毫秒 300毫秒后,会自动执行后续代码
*/
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方法
*/
}
}
四、开启键盘监听事件来控制方块的移动(下左右翻转都要判断停止下落)
(1)向下:判断能否继续下落,调用下落方法,每次按键调用后都要重新绘制,以保证画面流畅
public void softDropAction() {
if (canDrop()) {
currentOne.softDrop();
} else {
landToWall();
destroyLine();
currentOne = nextOne;
nextOne = Tetromino.randomOne();
}
}
(2)向左:如果大方块不越界,或不与其它方块重合,就可向左移动(如果出现数组下标越界异常,可以先向左移,
如果越界,再向右移回来)
protected void moveLeftAction() {
currentOne.moveLeft();
if (outOfBounds() || coincide()) {
currentOne.moveRight();
}
}
(3)向右:和向左原理相同但方向不同
(4)旋转:方块旋转的方向是顺时针的,不同方块有着不同的旋转后的状态(T,L,J行方块有4种,I,S,
Z行方块有2种,O型方块有一种)因为大方块是由4格小方块组成,所以大方块旋转就是小方块
的位置发生变化,我们要定义一个类来存放大方块在旋转时,小方块的相对坐标位置,并且要在
每一个大方块类中写出不同状态下1-3号方块相对于0号方块的相对位置(每一个大方块都有4个小
方块,每个小方块都有行列坐标,所以要定义8个属性来存放小方格的坐标),想要确定当前大方
块的旋转位置,需要统计旋转按键的点击次数, 之后和4进行取余(因为大方块最多只有4种旋转状态)
旋转方块时是以其中的一个小方块为轴来进行旋转的,所以在旋转之前要确定轴的行号和列号
(1-3号小方块的坐标为行/列 + 相对坐标)
注:方块的相对坐标参考图“Enter.PNG”。参考代码:
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 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) {
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;
}
}
(5)快速下落:多次无休眠调用方块下移(要判断方块能否下落),当大方块不能下移时,将大方块嵌入到墙中.
代码如下:
public void softDropAction() {
if (canDrop()) {
currentOne.softDrop();
} else {
landToWall();
destroyLine();
currentOne = nextOne;
nextOne = Tetromino.randomOne();
}
}
(6)满行消除下落:
方法1:清空行:循环检查下落后的四格方块大行坐标,根据大方块下落停止后所在的位置的行号,
还循环判断这个行号上对应的墙上的数组是否为空,如果为空,则表示次行未满,
跳出循环判断下一行。
向下平移行:将清空后的行的上一行的空满状态赋值给清空的这一行,以此类推直到最上面一行,
在清空行和向下平移行外面加一个row<20的循环,这样可以避免在清空行并且向下
平移后行号改变,导致的原来行上已满但平移后原来已满的行的行号现在没有满,
导致的不能被清空的现象。
方法2:先进行判断行是否满了,满了后进行清空,并记住行号,之后再从小的行号开始向下平移。
方法3:
先找出大方块停止下落后中的小方块所在最小的行,以最小的行开始到最后一行结束为循环,
进行清空行和平移行操作;
方法1代码:
public void destroyLine() {
// 统计销毁行的行数
int lines = 0;
Cell[] cells = currentOne.cells;
for (Cell c : cells) {
int row = c.getRow();
while (row < 20) {
if (isFullLine(row)) {
lines++;
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;
}
(7)游戏状态:游戏状态有 结束(GAMEOVER = 2),暂停(pause = 1),从新开始(PLAYING = 0)
结束:
如果下一个大方块初始的位置的墙上不为空,游戏结束,显示结束画面;
(结束的判断要加在,自动下落,快速下落处)
暂停:在自动下落中添加一条判断游戏状态的if语句=1时游戏暂停(也就是不走下落的方法);
重新开始:
游戏结束后显示结束界面,点击重新开始后,墙清空,下落开始
(8)游戏计分/消行数:
消除不同数量的行分数是不相同的;分数与消行数在从新开始后清0;
相关阅读
工程师为消费类设备开发了这种语言,并使其与当时适度的CPU兼容时保持了简单性。从那时起,这种面向对象的语言已用于创建简单到
JavaScript主要作用是什么呢?学习编程的同学对JavaScript并不陌生,JavaScript是前端技术中非常重要的内容,是网站搭建必不可少的
java.lang.UnsupportedClassVersionError
截图: 错误日志: Exception in thr
学了一段时间Java了,但是还是很菜,看到反射这一节的时候,就有点厌烦,看不下去了,过了一段时间后我又翻了回来,因为要学习后面的,所以反射
Java 混淆器就是给.class加密以防止反编译的工具 开源的 RetroGuard http://www.retrologic.com/ IBM的 JAX