a) 7种小块儿随机生成
i. 一块放置在顶部(随时间下降)
ii. 另一块置于右侧,提示下一个随机生成块
iii. 七种形状:“O、L、J、I、T、S、Z”
b) 墙与边界
i. 小块的移动范围在边框之内;
ii. 第一个小块下降到最底部,触底碰“墙”,自身嵌入“墙”;
iii. 下一小块儿的其中某个小方格,底部碰到“墙”,自身嵌入“墙”;
c) 小块儿的下降、左移、右移、旋转与平移
i. 添加键盘监听事件
ii. 不允许方块的越界
iii. 确定旋转中心,以旋转中心的相对位置做其余小块的行列号修改
d) 消除与积分
i. 当某一排(col)被小方格占满,消除某一排
ii. 被消除排 的上方所有小方块整体下移
iii. 积分累加
f) 游戏的三种状态:下落、暂停与重新开始
g) 游戏结束的条件
i. 最上方的生成区域被占,GameOver
(1)添加两个(至少)构造器【一个无参数,一个有参数】
(2)属性一般为私有化【外部类访问不到】
(3)提供公有的get/set 【提供访问途径】
(4)toString用来描述对象信息
重写toString属性的信息
(5)重写equals 【对象的值】
(6)重写hashCode 【对象的哈希码(地址)】
几大部件:【我们要把这几个家伙抽象到类 (class) 中】
俄罗斯方块中的最小单位:小方格
* 特征:属性
* row--行号
* col--列号
* image--对应图片
*
* 行为:
* 向左移动
* 向右移动
* 向下移动
对于image属性,我们可以添加BufferImage类的对象image
记得导包(import)
形状有:O、L、J、I、T、S、Z
* Cell类继承4块整体(Trtromino)的属性
* 构造器初始化
* Cell[] cells = new Cell[4];
* Cell(行号,列号,BufferedImage image)构造器
整体的四种状态(旋转),其中S、Z、I型可以只有两种状态,O只有一种状态
*states = new State[4];
*
*/
四格方块作为一个整体
共同特征:七种组合的父类
cell--四个方块(Cell 数组存放)--protected Cell[ ] cells = new Cell[4]; //4个小方格创建,初值为null
修饰词protected【子类可访问】
====================================================================
共同行为:向左向右..
* 属性:
* 4cells
* 行为:
* 重写toString
*
* moveLeft()
* moveRight()
* softDrop()软下降——【按键盘'↓'只降一格】
=====================================================================
四种状态:(相对与旋转中心)
按照顺时针顺序:
此类作为程序的入口,加载静态资源【图片缓冲区】
拥有主方法main(){}
在主方法中添加:
1.JFrame(窗体框框)
2.JPanel(面板&画笔)
(1).面板会自动调用绘制方法paint(Graphics g)
(2).重写paint方法,绘制图片背景
(3).绘制网格和嵌入墙中的方块
3.在Panel中调用主逻辑start( )
====================================================================
把游戏的主逻辑封装入start( )方法中
1.KeyListener(开启键盘监听)
封装匿名内部类 keyPressed(KeyEvent e){
【注意:k要小写】
【Action后马上重绘】
repaint();
};
2.面板添加监听事件
3.当前对象设置成焦点
4.设置程序睡眠【每300ms生成新的小块儿】
首先强调,Leo遵循的编写顺序为:
(1).Cell类——小细胞
(2).Tetromino类——四个小细胞组成一个整体
(3).七种形状类“O、L、J、I、T、S、Z”——继承Tetromino的属性和行为
(4).最后是主类Tetris——作为游戏的主逻辑和程序的入口
import java.awt.image.BufferedImage;
/*
* 俄罗斯方块中的最小单位:小方格
* 特征:属性
* row--行号
* col--列号
* image--对应图片
*
* 行为:
* 向左
* 向右
* 向下
*/
public class Cell {
private int row; //行号
private int col; //列号
private BufferedImage image;
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;
}
public void left(){
col --;
}
public void right(){
col ++;
}
public void drop(){
row ++;
}
@Override
public String toString() {
return "(" + row + ", " + col + ")" ;
}
}
import java.util.Arrays;
/*
* 4格方块作为一个整体
* 属性:
* 4cells
* 行为:
* 重写toString
*
* moveLeft()
* moveRight()
* softDrop()软下降,按↓箭头只下移一个单位
*/
public class Tetromino {
protected Cell[] cells = new Cell[4]; //4个null
//每个方块都向左 一个单位移动
public void moveLeft(){
for (int i = 0; i < cells.length; i++) {
Cell cell = cells[i];
cell.left();
}
}
//每个方块都向右 一个单位移动
public void moveRight(){
for (int i = 0; i < cells.length; i++) {
Cell cell = cells[i];
cell.right();
}
}
//每个方块都向下 一个单位移动
public void softDrop(){
//强循环数组遍历
for(Cell c:cells){
c.drop();
}
}
@Override
public String toString() {
return Arrays.toString(cells) ;
}
public static Tetromino randomOne(){
//随机生成4格方块
//四小块作为一整体,t赋为null
Tetromino t = null ;
int num = (int)(Math.random()*7);
switch(num){
case 0:t=new T();break;
case 1:t=new O();break;
case 2:t=new L();break;
case 3:t=new J();break;
case 4:t=new I();break;
case 5:t=new Z();break;
case 6:t=new S();break;
}
return t;
}
public static Tetromino randomOne(){
//随机生成4格方块
//四小块作为一整体,t赋为null
Tetromino t = null ;
int num = (int)(Math.random()*7);
switch(num){
case 0:t=new T();break;
case 1:t=new O();break;
case 2:t=new L();break;
case 3:t=new J();break;
case 4:t=new I();break;
case 5:t=new Z();break;
case 6:t=new S();break;
}
return t;
}
//旋转四格方块:顺时针
public void rotateRight(){
//旋转一次,计数器自增1
count++;
State s = state[count%state.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(){
//旋转一次,计数器自减1
count--;
State s = state[count%state.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{
/*
* 设置8个坐标int
* 分别存储四格方块元素的相对位置
*/
int row0,col0;
int row1,col1;
int row2,col2;
int 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;
}
}
下面以“T”形状为例
public class T extends Tetromino{
/*
* 继承4块整体(Trtromino)的属性
* 构造器初始化
* T型四格方块的位置
* 000000
* 00
*/
public T(){
/*
* Cell[] cells = new Cell[4];
* Cell(行号,列号,BufferedImage image)构造器
*/
cells[0]= new Cell(0,4,Tetris.T); // 初始状态旋转中心的坐标
cells[1]= new Cell(0,3,Tetris.T); // 初始状态1号小细胞..
cells[2]= new Cell(0,5,Tetris.T); // 初始状态2号...
cells[3]= new Cell(1,4,Tetris.T); // ...
state = new State[4]; // 【注意】,不要重新声明State,在Tetromino.class中已声明过了
//第一个状态
state [0] = new State(0,0, 0,-1, 0,1, 1,0);
//第二个状态
state [1] = new State(0,0, -1,0, 1,0, 0,-1);
state [2] = new State(0,0, 0,1, 0,-1, -1,0);
state [3] = new State(0,0, 1,0, -1,0, 0,1);
}
}
import java.awt.Graphics;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
/*
* 俄罗斯方块的主类:
* 前提: 一块面板JPanel,可以嵌入窗口
* 面板上自带一个画笔,有一个功能,自动绘制。
* 调用了JPanel
* 加载静态资源
*
*/
import java.awt.image.BufferedImage;
import java.util.Arrays;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Tetris extends JPanel{
/*
* 俄罗斯方块的主类
* 功能: 1.加载静态资源(背景图,7种形状的原始图)
* 2.设置窗口
* 3.重写JPanel中的paint()方法,构造器中添加画笔Graphics g
* 前提:是一块面板Panel,可被嵌入窗口
* 属性:1.正在下落的方块
* 2.即将下落的方块
* 3.添加墙(方格),画举矩形
*
*/
//属性:正在下落的方块
private Tetromino currentOne = Tetromino.randomOne();
//属性:即将下落的方块
private Tetromino nextOne = Tetromino.randomOne();
//属性:墙 20行10列的方格
private Cell[][] wall= new Cell[20][10];
//属性:统计分数
int [] scoresPool = {0,1,3,5,10};
private int totalScore = 0;
private int totalLine = 0;
//图片加载:静态
private static final int CELL_SIZE = 26;
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;
static {
try{
/*
* getResource(String url)
* url:加载图片的路径
* 操作:将图片文件拖入package Tetris_Day01
* 相对位置是同一个包下
*/
T = ImageIO.read(Tetris.class.getResource("T.png"));
I = ImageIO.read(Tetris.class.getResource("I.png"));
O = ImageIO.read(Tetris.class.getResource("O.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"));
}catch (Exception e) {
e.printStackTrace();
}
}
/*
* 重写JPanel中的paint()方法
*/
public void paint(Graphics g){
//绘制背景(图片,横坐标,纵坐标,observer)
/*
* g是画笔
* g.drawImage(image,x,y,null)
* x,y为开始绘制时的坐标
*/
g.drawImage(background, 0, 0, null);
//平移坐标轴
g.translate(15, 15);
//绘制墙
paintWall(g);
//绘制正在下落的4格方块
paintCurrentOne(g);
//绘制下一个将要绘制的4格方块
paintNextOne(g);
}
/*
* 绘制下一个将要绘制的4格方块
* 绘制到右上角相应区域
*/
public void paintNextOne(Graphics g) {
//获取Next对象的4个小方格
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);
}
}
public void paintCurrentOne(Graphics a){
//Cell类型的数组指向 4方格整体的对象currentOne的cells数组
Cell[] cells = currentOne.cells;
for (Cell c : cells) {
int x = c.getCol()*CELL_SIZE;
int y = c.getRow()*CELL_SIZE;
a.drawImage(c.getImage(), x, y, null);
}
}
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 void start(){
//开启键盘监听事件
KeyListener l = new KeyAdapter(){
//匿名内部类
@Override
public void keyPressed(KeyEvent e) {
int code = e.getKeyCode();
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();
}
//Action后马上重绘
repaint();
}
};
//面板添加监听事件
this.addKeyListener(l);
//当前对象设置成焦点
this.requestFocus();
while(true){
/*
* 当程序运行到此,进入睡眠状态
* 睡眠时间为300毫秒
*/
try {
Thread.sleep(900);
} catch (InterruptedException e) {
//打断异常
e.printStackTrace();
}
//判断可以下落
if(canDrop()){
currentOne.softDrop();
}else{
landToWall();
//将下一个下落的四格方块赋值给CurrentOne
destroy();
currentOne = nextOne;
nextOne = Tetromino.randomOne();
}
/*
* 下落之后,重新进行绘制
* 才会在看到下一步的动作
* repaint方法,也是JPanel类的方法
* 此方法调用paint
*/
repaint();
}
}
/*
*满一行就消除,上方所有方块向下移
*/
public void destroy(){
//统计销毁行的次数
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 += scoresPool[lines];
}
//判断行是否为
public boolean isFullLine(int row) {
Cell[] line = wall[row];
for(Cell r : line){
if(r == null){
return false;
}
}
return true;
}
//顺时针旋转的动作
public void rotateRightAction() {
currentOne.rotateRight();
//先判断越界,再判断重合
if(outOfBounds()||coincide()){
currentOne.rotateLeft();
}
}
protected void moveRightAction() {
currentOne.moveRight();
//先判断越界,再判断重合
if(outOfBounds()||coincide()){
currentOne.moveLeft();
}
}
//监听使用left键控制向左的行为
protected void moveLeftAction() {
currentOne.moveLeft();
//先判断越界,再判断重合
if(outOfBounds()||coincide()){
currentOne.moveRight(); //越界,到-1,再移动回来
}
}
//判断越界
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){
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;
}
public void softDropAction(){
if(canDrop()){
currentOne.softDrop();
}else{
//将下一个下落的四格方块赋值给CurrentOne
landToWall();
destroy();
currentOne = nextOne;
nextOne = Tetromino.randomOne();
}
}
public void handDropAction(){
// while(canDrop()){
//
// }
for(;;){
if(canDrop()){
currentOne.softDrop();
}else{
break;
}
}
landToWall();
destroy();
currentOne = nextOne;
nextOne = Tetromino.randomOne();
}
public boolean canDrop(){
Cell[] cells = currentOne.cells;
/*
* 4格拿出来,遍历
*/
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;
}
public void landToWall(){
Cell[] cells = currentOne.cells;
/*
* 4格拿出来,遍历
*/
for(Cell c:cells){
int row = c.getRow();
int col = c.getCol();
wall[row][col] = c;
}
}
public static void main(String[] args) {
//1.创建窗口对象
JFrame frame = new JFrame("Tetris~");
frame.setVisible(true); //2.可见性
frame.setSize(535, 580); //3.size
frame.setLocationRelativeTo(null); //4.居中
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //5.关闭并终止游戏
Tetris panel = new Tetris(); //6.创建游戏界面,即面板
frame.add(panel); //7.将面板嵌入窗口
// panel.setBackground(Color.yellow); //8.先把面板画成yellow
//其实调用的是JPanel中的paint()方法
//游戏的主要逻辑封装在start()
panel.start();
}
}