本文中的代码只是部分代码,全部代码及用到的图片下载地址:链接: https://pan.baidu.com/s/1Ryvr0uxPVX22Ag3aVzGQKQ
<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;




友情链接
ioDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:ixiaoyang8@qq.com
QQ群:637538335
关注微信