前言:基于别人的作品上进行分析
1、划分对象
(1)小方块:Cell对象
(2)4个小方块组成的图形:Tetromino对象
(3)游戏面板:TetrisPanel对象
2、分析对象中应包含的属性和方法
(1)Cell类
private int row;//当前方块所在的行;方块的高度是行高,方块的宽度是列宽
private int col;//当前方块所在的列
private Image image;//当前方块的贴图
moveRight();//右移一步
moveLeft();//左移一步
moveDown();//下移一步
(2)Tetromino类
public Cell[] cells = new Cell[4]; //每一个图形对象都是由4个方块对象组成
public State[] states;//状态数组存放图形的旋转状态,比如T形状图形,有4种状态
randomTetromino();//随机生成图形的方法
softDrop();//图形下移一步的方法
moveRight();//图形右移一步的方法
moveLeft();//图形左移一步的方法
rotateRight();//图形向右旋转一步的方法
rotateLeft(); //若向右旋转超出了面板强,则可以用该方法及时矫正
对该类的其它设计:
State类可以作为Tetromino类的内部类:int row0,col0,row1,col1,row2,col2,row3,col3;//每一种状态由8个参数确定,对应每个
方块在面板中的横纵坐标
随机生成的图形一共有七种,因此Tetromino类可以有七个子类分别对应这七种图形,子类的构造方法要设计好自己的cells和
states数组。
(3)TetrisPanel类
private Tetromino tetromino; //正在下落图形
private Tetromino nextOne; //下一个下落图形
public static final int ROWS = 20;//面板的行数
public static final int COLS = 10; //面板的列数
private Cell[][] wall = new Cell[ROWS][COLS]; //面板墙
private int lines; //消掉的行数
private int score;//分数
public static final int CELL_SIZE = 26;//表示方块是宽高都为26像素的正方块
private static Image background;//面板的背景图片
public static Image I;//每一种图形都有一张背景图填充它
public static Image J;
public static Image L;
public static Image S;
public static Image Z;
public static Image O;
public static Image T;
绘制面板涉及的方法:
ImageIO.read(url); //读取磁盘上的图片到内存,存放到各个变量中
paint(Graphics g); //绘制面板的方法
g.drawImage(background, 0, 0, null);//将背景图添加到面板中
g.translate(15, 15);//平移绘图坐标系,使(0,0)坐标在面板墙的左上角
paintTetromino(g);//绘制正在下落的方块
paintWall(g);//绘制面板墙
paintNextOne(g);//绘制下一个要下落的方块
paintScore(g);//绘制分数
图形下落过程涉及的方法:
softDropAction();//图形自动下落过程
tetrominoCanDrop();//检查当前的图形能否继续下落
tetrominoLandToWall();//让当前图形着陆
destroyLines();//是否删除一行,计算得分的方法
fullCells(int row); //判断一行是否满了
deleteRow(int row); //删除给出行,即让上一行取代给出行
checkGameOver();//检查游戏是否结束的方法
控制图形的方法:
moveRightAction(); //图形向右移动的过程,右键
moveLeftAction(); //图形向左移动的过程,左键
rotateRightAction(); //图形向右旋转的过程,上键
outOfBound();//判断图形是否越界
coincide(); //判断是否与原有的图形重合
hardDropAction();//主动使图形向下移动的过程,下键
控制游戏进程的方法:
startAction();//开始游戏的方法
clearWall();//清空面板墙的方法
pauseAction();//暂停游戏
continueAction();//继续游戏
action();//将按键和方法绑定
3、代码
package tetris;
import java.awt.Image;
public class Cell {
private int row;//当前方块所在的行
private int col;//当前方块所在的列
private Image image;//当前方块的贴图
public Cell() {
}
public Cell(int row, int col, Image image) {
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 Image getImage() {
return image;
}
public void setImage(Image image) {
this.image = image;
}
public void moveRight(){
col++;
}
public void moveLeft(){
col--;
}
public void moveDown(){
row++;
}
}
package tetris;
import java.util.Random;
public class Tetromino {
public Cell[] cells = new Cell[4]; //每一个下落图形都是由4个方块对象组成
public State[] states;//每个图形都有几种状态,全部存放在其状态数组中
/*
* 静态工厂,随机生产一个4方块组成的图形
*/
public static Tetromino randomTetromino(){
Random r = new Random();
int type = r.nextInt(7);//一共有7种图形
switch(type){
case 0: return new T();
case 1: return new I();
case 2: return new J();
case 3: return new L();
case 4: return new O();
case 5: return new S();
case 6: return new Z();
}
return null;
}
//图形下落,则每一个方块都下移
public void softDrop(){
for(int i=0; i<cells.length; i++){
cells[i].moveDown();
}
}
//图形右移,则每一个方块都右移
public void moveRight(){
for(int i=0; i<cells.length; i++){
this.cells[i].moveRight();
}
}
//图形左移,则每一个方块都左移
public void moveLeft(){
for(int i=0; i<cells.length; i++){
cells[i].moveLeft();
}
}
// 用于记录旋转状态
public class State{
int row0,col0,row1,col1,row2,col2,row3,col3;
public State(int row0, int col0, int row1, int col1,int row2, int col2,int row3, int col3) {
this.row0 = row0;
this.col0 = col0;
this.row1 = row1;
this.col1 = col1;
this.row2 = row2;
this.col2 = col2;
this.row3 = row3;
this.col3 = col3;
}
}
//图形旋转,则每个方块都在旋转;需要记录每个方块旋转一步之后的坐标(row,col)
private int index = 4;
public void rotateRight() {
index++;
State s = states[index%states.length];//获取下一个状态
Cell o = cells[0];//获取当前的轴;要求构造每个图形的时候将轴作为cells[0]
//轴与相对位置的和作为旋转以后的格子位置
//每种图形的s数组是提前设计好的,相对于轴的方块,就能计算出旋转后格子的位置
cells[1].setRow(o.getRow()+s.row1);
cells[1].setCol(o.getCol()+s.col1);
cells[2].setRow(o.getRow()+s.row2);
cells[2].setCol(o.getCol()+s.col2);
cells[3].setRow(o.getRow()+s.row3);
cells[3].setCol(o.getCol()+s.col3);
}
//该方法的作用是,若向右旋转超出了面板墙,则可以用该方法及时矫正
public void rotateLeft() {
index--;
State s = states[index%states.length];//s1
Cell o = cells[0];//获取当前的轴
cells[1].setRow(o.getRow()+s.row1);
cells[1].setCol(o.getCol()+s.col1);
cells[2].setRow(o.getRow()+s.row2);
cells[2].setCol(o.getCol()+s.col2);
cells[3].setRow(o.getRow()+s.row3);
cells[3].setCol(o.getCol()+s.col3);
}
}
package tetris;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class TetrisPanel extends JPanel{
/** 正在下落方块 */
private Tetromino tetromino;
/** 下一个下落方块 */
private Tetromino nextOne;
/** 行数 */
public static final int ROWS = 20;
/** 列数 */
public static final int COLS = 10;
/** 墙 */
private Cell[][] wall = new Cell[ROWS][COLS];
/** 消掉的行数 */
private int lines;
/** 分数 */
private int score;
public static final int CELL_SIZE = 26;
private static Image background;//背景图片
public static Image I;
public static Image J;
public static Image L;
public static Image S;
public static Image Z;
public static Image O;
public static Image T;
static{
try{
background = ImageIO.read(TetrisPanel.class.getResource("tetris.png"));
T=ImageIO.read(TetrisPanel.class.getResource("T.png"));
I=ImageIO.read(TetrisPanel.class.getResource("I.png"));
S=ImageIO.read(TetrisPanel.class.getResource("S.png"));
Z=ImageIO.read(TetrisPanel.class.getResource("Z.png"));
L=ImageIO.read(TetrisPanel.class.getResource("L.png"));
J=ImageIO.read(TetrisPanel.class.getResource("J.png"));
O=ImageIO.read(TetrisPanel.class.getResource("O.png"));
}catch(Exception e){
e.printStackTrace();
}
}
public void action(){
//tetromino = Tetromino.randomTetromino();
//nextOne = Tetromino.randomTetromino();
//wall[19][2] = new Cell(19,2,Tetris.T);
startAction();
repaint();
KeyAdapter l = new KeyAdapter() {
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
if(key == KeyEvent.VK_Q){
System.exit(0);//退出当前的Java进程
}
if(gameOver){
if(key==KeyEvent.VK_S){
startAction();
}
return;
}
//如果暂停并且按键是[C]就继续动作
if(pause){//pause = false
if(key==KeyEvent.VK_C){ continueAction(); }
return;
}
//否则处理其它按键
switch(key){
case KeyEvent.VK_RIGHT: moveRightAction(); break;
case KeyEvent.VK_LEFT: moveLeftAction(); break;
case KeyEvent.VK_DOWN: softDropAction() ; break;
case KeyEvent.VK_UP: rotateRightAction() ; break;
case KeyEvent.VK_Z: rotateLeftAction() ; break;
case KeyEvent.VK_SPACE: hardDropAction() ; break;
case KeyEvent.VK_P: pauseAction() ; break;
}
repaint();
}
};
this.requestFocus();
this.addKeyListener(l);
}
public void paint(Graphics g){
g.drawImage(background, 0, 0, null);//使用this 作为观察者
g.translate(15, 15);//平移绘图坐标系
paintTetromino(g);//绘制正在下落的方块
paintWall(g);//画墙
paintNextOne(g);
paintScore(g);
}
public static final int FONT_COLOR = 0x667799;
public static final int FONT_SIZE = 0x20;
private void paintScore(Graphics g) {
Font f = getFont();//获取当前的 面板默认字体
Font font = new Font(
f.getName(), Font.BOLD, FONT_SIZE);
int x = 290;
int y = 162;
g.setColor(new Color(FONT_COLOR));
g.setFont(font);
String str = "SCORE:"+this.score;
g.drawString(str, x, y);
y+=56;
str = "LINES:"+this.lines;
g.drawString(str, x, y);
y+=56;
str = "[P]Pause";
if(pause){str = "[C]Continue";}
if(gameOver){ str = "[S]Start!";}
g.drawString(str, x, y);
}
private void paintNextOne(Graphics g) {
Cell[] cells = nextOne.cells;
for(int i=0; i<cells.length; i++){
Cell c = cells[i];
int x = (c.getCol()+10) * CELL_SIZE-1;
int y = (c.getRow()+1) * CELL_SIZE-1;
g.drawImage(c.getImage(), x, y, null);
}
}
private void paintTetromino(Graphics g) {
Cell[] cells = tetromino.cells;
for(int i=0; i<cells.length; i++){
Cell c = cells[i];
int x = c.getCol() * CELL_SIZE-1;
int y = c.getRow() * CELL_SIZE-1;
//g.setColor(new Color(c.getColor()));
//g.fillRect(x, y, CELL_SIZE, CELL_SIZE);
g.drawImage(c.getImage(), x, y, null);
}
}
//在 Tetris 类 中添加 方法 paintWall
private void paintWall(Graphics g){
for(int row=0; row<wall.length; row++){
//迭代每一行, i = 0 1 2 ... 19
Cell[] line = wall[row];
//line.length = 10
for(int col=0; col<line.length; col++){
Cell cell = line[col];
int x = col*CELL_SIZE;
int y = row*CELL_SIZE;
if(cell==null){
// g.setColor(new Color(5));
//画方形
// g.drawRect(x, y, CELL_SIZE, CELL_SIZE);
}else{
g.drawImage(cell.getImage(), x-1, y-1, null);
}
}
}
}
/**
* 在 Tetris(俄罗斯方块) 类中增加方法
* 这个方法的功能是:软下落的动作 控制流程
* 完成功能:如果能够下落就下落,否则就着陆到墙上,
* 而新的方块出现并开始落下。
*/
public void softDropAction(){
if(tetrominoCanDrop()){
tetromino.softDrop();
}else{
tetrominoLandToWall();
destroyLines();//破坏满的行
checkGameOver();
tetromino = nextOne;
nextOne = Tetromino.randomTetromino();
}
}
/** 销毁已经满的行,并且计分
* 1)迭代每一行
* 2)如果(检查)某行满是格子了 就销毁这行
**/
public void destroyLines(){
int lines = 0;
for(int row = 0; row<wall.length; row++){
if(fullCells(row)){
deleteRow(row);
lines++;
}
}
// lines = ?
this.lines += lines;//0 1 2 3 4
this.score += SCORE_TABLE[lines];
}
private static final int[] SCORE_TABLE={0,1,10,30,200};
public boolean fullCells(int row){
Cell[] line = wall[row];
for(int i=0; i<line.length; i++){
if(line[i]==null){//如果有空格式就不是满行
return false;
}
}
return true;
}
public void deleteRow(int row){
for(int i=row; i>=1; i--){
//复制 [i-1] -> [i]
System.arraycopy(wall[i-1], 0, wall[i], 0, COLS);
}
Arrays.fill(wall[0], null);
}
/** 检查当前的4格方块能否继续下落 */
public boolean tetrominoCanDrop(){
Cell[] cells = tetromino.cells;
for(int i = 0; i<cells.length; i++){
Cell cell = cells[i];
int row = cell.getRow(); int col = cell.getCol();
if(row == ROWS-1){return false;}//到底就不能下降了
}
for(int i = 0; i<cells.length; i++){
Cell cell = cells[i];
int row = cell.getRow(); int col = cell.getCol();
if(wall[row+1][col] != null){
return false;//下方墙上有方块就不能下降了
}
}
return true;
}
/** 4格方块着陆到墙上 */
public void tetrominoLandToWall(){
Cell[] cells = tetromino.cells;
for(int i=0; i<cells.length; i++){
Cell cell = cells[i];
int row = cell.getRow();
int col = cell.getCol();
wall[row][col] = cell;
}
}
public void moveRightAction(){
tetromino.moveRight();
if(outOfBound() || coincide()){
tetromino.moveLeft();
}
}
public void moveLeftAction(){
tetromino.moveLeft();
if(outOfBound() || coincide()){
tetromino.moveRight();
}
}
private boolean outOfBound(){
Cell[] cells = tetromino.cells;
for(int i=0; i<cells.length; i++){
Cell cell = cells[i];
int col = cell.getCol();
if(col<0 || col>=COLS){
return true;//出界了
}
}
return false;
}
private boolean coincide(){
Cell[] cells = tetromino.cells;
for(Cell cell: cells){
int row = cell.getRow();
int col = cell.getCol();
if(row<0 || row>=ROWS || col<0 || col>=COLS ||
wall[row][col]!=null){
return true; //墙上有格子对象,发生重合
}
}
return false;
}
/** 向右旋转动作 */
public void rotateRightAction(){
tetromino.rotateRight();
//旋转之后
if(outOfBound() || coincide()){
tetromino.rotateLeft();
}
}
/** Tetris 类中添加的方法 */
public void rotateLeftAction(){
tetromino.rotateLeft();
if(outOfBound() || coincide()){
tetromino.rotateRight();
}
}
public void hardDropAction(){
while(tetrominoCanDrop()){
tetromino.softDrop();
}
tetrominoLandToWall();
destroyLines();
checkGameOver();
tetromino = nextOne;
nextOne = Tetromino.randomTetromino();
}
private boolean pause;
private boolean gameOver;
private Timer timer;
/** Tetris 类中添加的方法, 用于启动游戏 */
public void startAction(){
clearWall();
tetromino = Tetromino.randomTetromino();
nextOne = Tetromino.randomTetromino();
lines = 0; score = 0; pause=false; gameOver=false;
timer = new Timer();
timer.schedule(new TimerTask(){
public void run() {
softDropAction();
repaint();
}
}, 700, 700);
}
private void clearWall(){
//将墙的每一行的每个格子清理为null
for(int row=0; row<ROWS; row++){
Arrays.fill(wall[row], null);
}
}
/** 在Tetris 类中添加方法 */
public void pauseAction(){
timer.cancel(); //停止定时器
pause = true;
repaint();
}
public void continueAction(){
timer = new Timer();
timer.schedule(new TimerTask() {
public void run() {
softDropAction();
repaint();
}
}, 700, 700);
pause = false;
repaint();
}
/** 在 Tetris 类中添加 方法 */
public void checkGameOver(){
if(wall[0][4]==null){
return;
}
gameOver = true;
timer.cancel();
repaint();
}
public static void main(String[] args) {
JFrame frame = new JFrame();
TetrisPanel tetris = new TetrisPanel();
frame.add(tetris);
frame.setSize(525, 590);
frame.setUndecorated(false);//true去掉窗口框!
frame.setTitle("俄罗斯方块");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Location 位置 RelativeTo相对于
frame.setLocationRelativeTo(null);//使当前窗口居中
frame.setVisible(true);
tetris.action();
}
}
public class T extends Tetromino {
public T() {
cells[0] = new Cell(0, 4, TetrisPanel.T);
cells[1] = new Cell(0, 3, TetrisPanel.T);
cells[2] = new Cell(0, 5, TetrisPanel.T);
cells[3] = new Cell(1, 4, TetrisPanel.T);
states = new State[]{
new State(0,0, 0,-1, 0,1, 1, 0),
new State(0,0, -1,0, 1,0, 0,-1),
new State(0,0, 0,1, 0,-1, -1,0),
new State(0,0, 1,0, -1,0, 0,1)};
}
}
-----
public class I extends Tetromino {
public I() {
cells[0] = new Cell(0, 4, TetrisPanel.I);
cells[1] = new Cell(0, 3, TetrisPanel.I);
cells[2] = new Cell(0, 5, TetrisPanel.I);
cells[3] = new Cell(0, 6, TetrisPanel.I);
states = new State[]{
new State(0,0, 0,1, 0,-1, 0,-2),
new State(0,0, -1,0, 1,0,2,0)};
}
}
-----
public class L extends Tetromino {
public L() {
cells[0] = new Cell(0, 4, TetrisPanel.L);
cells[1] = new Cell(0, 3, TetrisPanel.L);
cells[2] = new Cell(0, 5, TetrisPanel.L);
cells[3] = new Cell(1, 3, TetrisPanel.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)};
}
}
-----
public class J extends Tetromino {
public J() {
cells[0] = new Cell(0, 4, TetrisPanel.J);
cells[1] = new Cell(0, 3, TetrisPanel.J);
cells[2] = new Cell(0, 5, TetrisPanel.J);
cells[3] = new Cell(1, 5, TetrisPanel.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 )};
}
}
-----
public class O extends Tetromino {
public O() {
cells[0] = new Cell(0, 4, TetrisPanel.O);
cells[1] = new Cell(0, 5, TetrisPanel.O);
cells[2] = new Cell(1, 4, TetrisPanel.O);
cells[3] = new Cell(1, 5, TetrisPanel.O);
states = new State[]{
new State(0,0, 0,1, 1,0, 1,1 ),
new State(0,0, 0,1, 1,0, 1,1 )};
}
}
------
public class S extends Tetromino {
public S() {
cells[0] = new Cell(0, 4, TetrisPanel.S);
cells[1] = new Cell(0, 5, TetrisPanel.S);
cells[2] = new Cell(1, 3, TetrisPanel.S);
cells[3] = new Cell(1, 4, TetrisPanel.S);
states = new State[]{
new State(0,0, 0,1, 1,-1, 1,0 ),
new State(0,0, -1,0, 1,1, 0,1 )};
}
}
-----
public class Z extends Tetromino {
public Z() {
cells[0] = new Cell(1, 4, TetrisPanel.Z);
cells[1] = new Cell(0, 3, TetrisPanel.Z);
cells[2] = new Cell(0, 4, TetrisPanel.Z);
cells[3] = new Cell(1, 5, TetrisPanel.Z);
states = new State[]{
new State(0,0, -1,-1, -1,0, 0,1 ),
new State(0,0, -1,1, 0,1, 1,0 )};
}
}
4、难点分析
(1)如何确定一个图形的states数组?
以T图形为例,我们设计cells[0]就是其旋转的轴,旋转不会改变cells[0]的坐标,T图形的初始states[0]={0,0, 0,-1, 0,1, 1, 0}就是相对
于其轴的坐标设计出来的。而之后的states[i]可以根据states[0]画个图推算出。
(2)一个图形由cells数组构成,那么每一次旋转之后的cells数组如何确定的?
图形的初始cells数组是已知的,并且其旋转之后的状态也是已知的,可以利用cells[0]的坐标去计算旋转之后cells[i]的坐标。
5、源码
链接:https://pan.baidu.com/s/1IBh1C9e2Ewxv_pkhskYbHw
提取码:72v3
6、打包成exe的方法
https://blog.csdn.net/Carl_changxin/article/details/82935635