分析规则、搭建框架:
类名 |
作用 |
Grid |
单位小方格的属性和操作 |
GridController |
组合方格的属性和操作 |
RulesController |
控制一系列的游戏规则和操作 |
Test |
入口 |
设计操作方式
按键 |
作用 |
按键 |
作用 |
P |
暂停 |
← |
左移一格 |
C |
继续 |
→ |
右移一格 |
↑ |
旋转 |
空格 |
直降到底 |
↓ |
下移一格 |
S |
重新开始 |
设计界面元素
背景音乐:先輩、優しくするならちゃんと最後まで優しくしてください
方块消除音效:Technology Electronic Button Beep Digital Camera 04
注:音效下载地址 (这里面有非常多的音效 免费下载 很棒)
类和方法:
Grid类
属性名 |
描述 |
row |
小方格所在行 |
col |
小方格所在列 |
image |
小方格对应样式图片 |
方法名 |
描述 |
Grid(int row, int col, Image image) |
构造函数 |
getRow() |
获取行 |
setRow() |
设置行 |
getCol() |
获取列 |
setCol() |
设置列 |
getImage() |
获取图形 |
moveRight() |
右移1格 |
moveLeft() |
左移1格 |
moveDown() |
下降1格 |
GridController类
属性名 |
描述 |
grids[] |
四个小方格构成的组合 |
State[] |
旋转状态 |
方法名 |
描述 |
randomTetris() |
随机生成方块 |
getGrids() |
返回组合方块 |
softDrop() |
组合方块下降一格 |
moveRight() |
组合方块右移一格 |
moveLeft() |
组合方块左移一格 |
spinRight() |
组合方块向右旋转 |
spinLeft() |
组合方块向左旋转 |
内部类名 |
描述 |
State |
记录旋转状态 |
Yellow |
黄色组合方格 |
Orangish |
橘色组合方格 |
Cyan |
深蓝组合方格 |
Gray |
深灰色组合方格 |
Green |
绿色组合方格 |
Blue |
蓝色组合方格 |
Red |
红色组合方格 |
RulesControllers类
属性名 |
描述 |
ROWS |
游戏区高20格子 |
COLS |
游戏区宽10个格子 |
CELL_SIZE |
单位小格子边长41 |
mound[][] |
下方已落下的方块 |
ingGrids |
正在下落的方块 |
nextGrids |
下一个下落方块 |
lines |
已消除的行数 |
score |
分数 |
background |
背景图片 |
Orangish |
单位小格子图片 |
Gray |
单位小格子图片 |
Cyan |
单位小格子图片 |
Green |
单位小格子图片 |
Blue |
单位小格子图片 |
Red |
单位小格子图片 |
Yellow |
单位小格子图片 |
pause |
是否暂停 |
gameOver |
游戏是否结束 |
timer |
计时器 |
FONT_COLOR |
右边文字区域文字颜色 |
FONT_SIZE |
右边文字区域字号 |
gridsCanDrop() |
判断能不能继续落下 |
方法名 |
描述 |
checkGameOver() |
判断游戏是否结束 |
startAction() |
重新开始/游戏就绪 |
action() |
控制游戏进行 |
paint() |
绘制一系列呈现的内容 |
outOfBound() |
判断是否碰到了边界 |
isCoincide() |
判断是否碰到了其他格子 |
slowlyDropAction() |
下移 |
moveRightAction() |
右移 |
moveLeftAction() |
左移 |
spinRightAction() |
向右旋转 |
spinLeftAction() |
向左旋转 |
directlyDropAction() |
直接下落到底 |
gridLandToWall() |
落到下面 |
destroyLines() |
消除一行并积分 |
Music1() |
播放消除一行并得分的音效 |
Music2() |
播放背景音乐 |
实现代码:
Grid类:
package Tetris;
import java.awt.Image;
public class Grid {
private int row; //行
private int col; //列
private Image image; //格子的图
public Grid(int row, int col, Image 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 Image getImage() {
return image;
}
public void setImage(Image image) {
this.image = image;
}
//下面是一系列移动方式
public void moveRight(){
col++;
}
public void moveLeft(){
col--;
}
public void moveDown(){
row++;
}
@Override
public String toString() {
return "["+row+","+col+"]";
}
}
GridController类:
package Tetris;
import java.util.Arrays;
import java.util.Random;
public class GridController {
protected 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;
}
}
protected Grid[] grids = new Grid[4];
protected State[] states; //保存旋转的相对于轴位置状态
public static GridController randomTetris(){ //随机生成方块
Random r = new Random();
int type = r.nextInt(7);
switch(type){
case 0: return new Yellow();
case 1: return new Orangish();
case 2: return new Gray();
case 3: return new Cyan();
case 4: return new Red();
case 5: return new Green();
case 6: return new Blue();
}
return null;
}
public Grid[] getGrids() {
return grids;
}
//以下 下落左移右移
public void softDrop(){ //下落
for(int i=0; i<grids.length; i++){
grids[i].moveDown();
}
}
public void moveRight(){
for(int i=0; i<grids.length; i++){
this.grids[i].moveRight();
}
}
public void moveLeft(){
for(int i=0; i<grids.length; i++){
grids[i].moveLeft();
}
}
private int index=100000;
public void spinRight() {
index++;
State s = states[index%states.length];//s1
Grid o = grids[0];//获取当前的轴
//轴与相对位置的和作为旋转以后的格子位置
grids[1].setRow(o.getRow()+s.row1);
grids[1].setCol(o.getCol()+s.col1);
grids[2].setRow(o.getRow()+s.row2);
grids[2].setCol(o.getCol()+s.col2);
grids[3].setRow(o.getRow()+s.row3);
grids[3].setCol(o.getCol()+s.col3);
}
public void spinLeft() {
index--;//index = 10001
// index % states.length = 10001 % 4 = 1
State s = states[index%states.length];//s1
// [0] + s1 = [1]
Grid o = grids[0];//获取当前的轴
grids[1].setRow(o.getRow()+s.row1);
grids[1].setCol(o.getCol()+s.col1);
grids[2].setRow(o.getRow()+s.row2);
grids[2].setCol(o.getCol()+s.col2);
grids[3].setRow(o.getRow()+s.row3);
grids[3].setCol(o.getCol()+s.col3);
}
@Override
public String toString() {
return Arrays.toString(grids);
}
}
class Yellow extends GridController{
public Yellow() {
grids[0] = new Grid(0, 4, RulesController.Yellow);
grids[1] = new Grid(0, 3, RulesController.Yellow);
grids[2] = new Grid(0, 5, RulesController.Yellow);
grids[3] = new Grid(1, 4, RulesController.Yellow);
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)};
}
}
class Orangish extends GridController{
public Orangish() {
grids[0] = new Grid(0, 4, RulesController.Orangish);
grids[1] = new Grid(0, 3, RulesController.Orangish);
grids[2] = new Grid(0, 5, RulesController.Orangish);
grids[3] = new Grid(0, 6, RulesController.Orangish);
states = new State[]{
new State(0,0, 0,1, 0,-1, 0,-2),
new State(0,0, -1,0, 1,0,2,0)};
}
}
class Cyan extends GridController{
public Cyan() {
grids[0] = new Grid(0, 4,RulesController.Cyan);
grids[1] = new Grid(0, 3,RulesController.Cyan);
grids[2] = new Grid(0, 5, RulesController.Cyan);
grids[3] = new Grid(1, 3,RulesController.Cyan);
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)};
}
}
class Gray extends GridController{
public Gray() {
grids[0] = new Grid(0, 4, RulesController.Gray);
grids[1] = new Grid(0, 3, RulesController.Gray);
grids[2] = new Grid(0, 5, RulesController.Gray);
grids[3] = new Grid(1, 5, RulesController.Gray);
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 )};
}
}
class Green extends GridController{
public Green() {
grids[0] = new Grid(0, 4, RulesController.Green);
grids[1] = new Grid(0, 5, RulesController.Green);
grids[2] = new Grid(1, 3, RulesController.Green);
grids[3] = new Grid(1, 4, RulesController.Green);
states = new State[]{
new State(0,0, 0,1, 1,-1, 1,0 ),
new State(0,0, -1,0, 1,1, 0,1 )};
}
}
class Blue extends GridController{
public Blue() {
grids[0] = new Grid(1, 4, RulesController.Blue);
grids[1] = new Grid(0, 3, RulesController.Blue);
grids[2] = new Grid(0, 4, RulesController.Blue);
grids[3] = new Grid(1, 5, RulesController.Blue);
states = new State[]{
new State(0,0, -1,-1, -1,0, 0,1 ),
new State(0,0, -1,1, 0,1, 1,0 )};
}
}
class Red extends GridController {
public Red() {
grids[0] = new Grid(0, 4, RulesController.Red);
grids[1] = new Grid(0, 5, RulesController.Red);
grids[2] = new Grid(1, 4, RulesController.Red);
grids[3] = new Grid(1, 5, RulesController.Red);
states = new State[]{
new State(0,0, 0,1, 1,0, 1,1 ),
new State(0,0, 0,1, 1,0, 1,1 )};
}
}
RulesController类:
package Tetris;
import java.applet.Applet;
import java.applet.AudioClip;
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.io.File;
import java.net.URI;
import java.net.URL;
import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;
import javax.imageio.ImageIO;
import javax.swing.JPanel;
public class RulesController extends JPanel {
public static final int ROWS=20; //游戏区高20个格子
public static final int COLS=10; //游戏区宽10个格子
public static final int CELL_SIZE = 41; //一方格的边长
private Grid[][] mound = new Grid[ROWS][COLS]; //存落下的方块
private GridController ingGrids; //正在下落的方块
private GridController nextGrids; //下一个下落的方块
private int lines; //消掉的行数
private int score ;//得分
private static Image background;//背景图片
public static Image Orangish;
public static Image Gray;
public static Image Cyan;
public static Image Green;
public static Image Blue;
public static Image Red;
public static Image Yellow;
static { //加载图片
try {
background = ImageIO.read(
RulesController.class.getResource("tetris3.png"));
Yellow=ImageIO.read(RulesController.class.getResource("Yellow.png"));
Orangish=ImageIO.read(RulesController.class.getResource("Orangish.png"));
Green=ImageIO.read(RulesController.class.getResource("Green.png"));
Blue=ImageIO.read(RulesController.class.getResource("Blue.png"));
Cyan=ImageIO.read(RulesController.class.getResource("Cyan.png"));
Gray=ImageIO.read(RulesController.class.getResource("Gray.png"));
Red=ImageIO.read(RulesController.class.getResource("Red.png"));
}catch(Exception e){
e.printStackTrace();
}
}
/************************
* 下面搞 控制游戏开始结束暂停继续
* **********************/
private boolean pause; //布尔变量pause表示是否暂停
private boolean gameOver; //布尔变量gameOver表示游戏是否结束
private Timer timer;
public void checkGameOver(){ //判断游戏是否该结束了
if(mound[0][4]==null){
return;
}
gameOver = true;
timer.cancel();
repaint();// 重绘此组件
}
public void startAction(){ //重新开始 开始前就绪
for(int row=0; row<ROWS; row++){
Arrays.fill(mound[row], null);
}
ingGrids = GridController.randomTetris();
nextGrids = GridController.randomTetris();
lines = 0; score = 0;
pause=false; gameOver=false;
timer = new Timer();
timer.schedule(new TimerTask(){
public void run() {
slowlyDropAction(); //下降
repaint();
}
}, 700, 700);
}
public void action(){ //开始
startAction();
repaint();
Music2();
KeyAdapter l = new KeyAdapter() {
public void keyPressed(KeyEvent e) {
int key = e.getKeyCode();
if(key == KeyEvent.VK_Q){ //按下Q退出
System.exit(0);//退出当前的Java进程
}
if(gameOver){
if(key==KeyEvent.VK_S){ //如果结束了 按S重新开始
startAction();
}
return;
}
//如果暂停并且按键是[C]就继续动作
if(pause){//pause = false
if(key==KeyEvent.VK_C)
{
timer = new Timer();
timer.schedule(new TimerTask() {
public void run() {
slowlyDropAction();
repaint();
}
}, 700, 700); //每700执行700
pause = false;
repaint();
}
return;
}
//否则处理其它按键
switch(key){
case KeyEvent.VK_RIGHT: moveRightAction(); break; //右
case KeyEvent.VK_LEFT: moveLeftAction(); break; //左
case KeyEvent.VK_DOWN: slowlyDropAction() ; break; //下
case KeyEvent.VK_UP: spinRightAction() ; break; //转
case KeyEvent.VK_SPACE: directlyDropAction() ; break; //直接下到底
case KeyEvent.VK_P: //P暂停
{
timer.cancel(); //停止定时器
pause = true;
repaint();
}; break;
}
repaint();
}
};
this.requestFocus();
this.addKeyListener(l);
}
/***********************
* 搞一系列呈现的内容
* *********************/
public static final int FONT_COLOR = 0x2F4F2F; //字体颜色
public static final int FONT_SIZE = 0x20; //字号
public void paint(Graphics g){
g.drawImage(background, 0, 0, null);//使用this 作为观察者
g.translate(20, 20);//平移绘图坐标系
//绘制正在下落的方块
Grid[] cells = ingGrids.getGrids();
for(int i=0; i<cells.length; i++){
Grid c = cells[i];
int x = c.getCol() * CELL_SIZE-1;
int y = c.getRow() * CELL_SIZE-1;
g.drawImage(c.getImage(), x, y, null);
}
//画墙
for(int row=0; row<mound.length; row++){
Grid[] line = mound[row];
for(int col=0; col<line.length; col++){
Grid cell = line[col];
int x = col*CELL_SIZE;
int y = row*CELL_SIZE;
if(cell==null){
}else{
g.drawImage(cell.getImage(), x-1, y-1, null);
}
}
}
// 画即将下落的方块
Grid[] cells1 = nextGrids.getGrids();
for(int i=0; i<cells1.length; i++){
Grid c = cells1[i];
int x = (c.getCol()+10) * CELL_SIZE-1;
int y = (c.getRow()+1) * CELL_SIZE-1;
g.drawImage(c.getImage(), x-60, y+50, null);
}
// 右边的文字提示区
Font f = getFont();//获取当前的 面板默认字体
Font font = new Font(
f.getName(), Font.BOLD, FONT_SIZE);
int x = 450;
int y = 300;
g.setColor(new Color(FONT_COLOR));
g.setFont(font);
String str = "分数:"+this.score;
g.drawString(str, x, y);
y+=56;
str = "已消除:"+this.lines+"行";
g.drawString(str, x, y);
y+=56;
str = "下一个:";
g.drawString(str, 433, 59);
y+=15;
str = "操作方法:";
g.drawString(str, x, y);
y+=56;
str = "[↑]:旋转";
g.drawString(str, x, y);
y+=56;
str = "[←]:左移";
g.drawString(str, x, y);
y+=56;
str = "[→]:右移";
g.drawString(str, x, y);
y+=56;
str = "[空格]:落底";
g.drawString(str, x, y);
y+=56;
str = "[P]暂停";
if(pause){str = "[C]继续";} //布尔变量pause:是否暂停
if(gameOver){ str = "[S]重新开始";} // 布尔变量gameOver:是否结束
g.drawString(str, x, y);
}
/****************************
* 下面搞游戏区方块的各种运动
****************************/
private boolean outOfBound(){ //判断碰到了边界
Grid[] grids = ingGrids.getGrids();
for(int i=0; i<grids.length; i++){
Grid grid = grids[i];
int col = grid.getCol();
if(col<0 || col>=COLS){
return true;//出界了
}
}
return false;
}
private boolean isCoincide(){ //判断 碰到了其他格子
Grid[] grids = ingGrids.getGrids();
//for each 循环、迭代,简化了"数组迭代书写"
//Java 5 以后提供增强版for循环 :for (循环变量类型 循环变量名称 : 要被遍历的对象) 循环体
for(Grid grid: grids){
int row = grid.getRow();
int col = grid.getCol();
if(row<0 || row>=ROWS || col<0 || col>=COLS ||
mound[row][col]!=null){
return true; //墙上有格子对象,发生重合
}
}
return false;
}
public boolean gridsCanDrop(){ //判断能不能继续落下
Grid[] grids = ingGrids.getGrids();
for(int i = 0; i<grids.length; i++){
Grid grid = grids[i];
int row = grid.getRow(); int col = grid.getCol();
if(row == ROWS-1){return false;}//到底就不能下降了
}
for(int i = 0; i<grids.length; i++){
Grid grid = grids[i];
int row = grid.getRow(); int col = grid.getCol();
if(mound[row+1][col] != null){
return false;//下方墙上有方块就不能下降了
}
}
return true;
}
public void slowlyDropAction(){ //一格一格下落
if(gridsCanDrop()){
ingGrids.softDrop();
}else{
gridLandToWall();
destroyLines();//破坏满的行
checkGameOver();
ingGrids = nextGrids;
nextGrids = GridController.randomTetris();
}
}
public void directlyDropAction(){ //直接下落到底
while(gridsCanDrop()){
ingGrids.softDrop();
}
gridLandToWall();
destroyLines();
checkGameOver();
ingGrids = nextGrids;
nextGrids = GridController.randomTetris();
}
public void moveRightAction(){ //
ingGrids.moveRight();
if(outOfBound() || isCoincide()){
ingGrids.moveLeft();
}
}
public void moveLeftAction(){
ingGrids.moveLeft();
if(outOfBound() || isCoincide()){
ingGrids.moveRight();
}
}
public void spinRightAction(){ //向右旋转
ingGrids.spinRight();
if(outOfBound() || isCoincide()){
ingGrids.spinLeft();
}
}
public void spinLeftAction(){ //向左旋转
ingGrids.spinLeft();
if(outOfBound() || isCoincide()){
ingGrids.spinRight();
}
}
public void gridLandToWall(){ //落在墙上
Grid[] cells = ingGrids.getGrids();
for(int i=0; i<cells.length; i++){
Grid cell = cells[i];
int row = cell.getRow();
int col = cell.getCol();
mound[row][col] = cell;
}
}
public boolean fullRows(int row){//判断是不是填满了一行
Grid[] line = mound[row];
for(int i=0; i<line.length; i++){
if(line[i]==null){//如果有空格式就不是满行
return false;
}
}
return true;
}
public void destroyLines(){ //判断满没 消除整行 并且积分
for(int row = 0; row<mound.length; row++){
if(fullRows(row)){
for(int i=row; i>=1; i--){
System.arraycopy(mound[i-1], 0, mound[i], 0, COLS);
}
Music1();
Arrays.fill(mound[0], null);
this.lines = this.lines+1;
this.score=this.score+10;
}
}
}
public void Music1(){
try {
File f;
URI uri;
URL url;
f = new File("D:\\学习资料\\大二\\java\\LYTetris\\src\\Tetris\\bi.wav");
uri = f.toURI();
url = uri.toURL(); //解析地址
AudioClip aau;
aau = Applet.newAudioClip(url);
aau.play(); //
} catch (Exception e)
{ e.printStackTrace();
}
}
public void Music2(){
try {
File f;
URI uri;
URL url;
f = new File("D:\\学习资料\\大二\\java\\LYTetris\\src\\Tetris\\backgroundmusic.wav");
uri = f.toURI();
url = uri.toURL(); //解析地址
AudioClip aau;
aau = Applet.newAudioClip(url);
aau.loop(); //
} catch (Exception e)
{ e.printStackTrace();
}
}
}
Test类:
package Tetris;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Test extends JPanel{
public static void main(String[] args) {
JFrame frame = new JFrame();
RulesController tetris = new RulesController();
frame.add(tetris);
frame.setSize(713, 910); //以上加载页面格式 宽 高
frame.setUndecorated(false); //true去掉窗口框!
frame.setTitle("刘妍的俄罗斯方块");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Location 位置 RelativeTo相对于
frame.setLocationRelativeTo(null);//使当前窗口居中
frame.setVisible(true);
tetris.action();
}
}
实现效果:
参考博客:https://www.cnblogs.com/Renyi-Fan/p/7220327.html
java学习了一个学期,第一次做一个比较大、比较完整的小项目,有很多地方都参考了前辈的作品。
俄罗斯方块小时候经常在电视机上或者游戏机上玩的游戏,现在能自己亲自动手实现一个自己的俄罗斯方块,我觉得很开心。
一开始的时候觉得这个游戏实现起来不会难,因为规则很简单,就是控制一些方块的运动而已,但是真正开始做的时候,很无措,这并不像之前的作业题,只是简单的几个类几个包就能搞定,以前的题目都会有要求或者提示说,“建几个包设置几个内部类、几个接口”,没有这些提示的话,就会不知从何入手,所以面临的第一个问题就是,怎样搭建一个框架,设置几个类,都干什么用。这个问题是我之前很少深思熟虑过的。思考过程中发现这其实很难划分合理,可能有重叠或者遗漏的部分。网上有几个现成的例子,基本都是按照[单位格子][格子组合][规则控制]设置三个类,按照平时的习惯,我自己增加了一个Test类作为程序的入口。
一开始决定好整体的框架之后就直接开始写具体的代码了,写着写着就发现,不能这样毫无根据的码,因为脑子里没有这个游戏的样子,就很难去真正实现它,我需要一个直观的界面和详细的规则,之前做项目的时候,老师就特意提醒过我们,“盲目”开发是不行的,方案需求一定要准备好,有根据地去开发。做好界面、方块格子、设计好操作方式和规则之后,思路更清晰,写起来就容易多了。
在具体实现过程中,也遇到很多问题,之前一学期学过的知识完全不够用,或者说,以前学过的东西知识浮于表面,缺乏进一步的理解和实际操作的经验,
有很多功能的实现要用到很多以前没见过的新方法
比如说:
计时器的使用、键盘的控制、布尔变量的应用、Arrays.fill数组填充、AudioClip接口播放音频、repaint()函数重绘组件、requestFocus()方法聚焦、键盘监听事件addKeyListener()、增强版FOR循环等
如果不去真正动手去做,去遇到问题的话,这些问题是很难有机会遇到并解决的,所以说,动手实践真的很重要。
除了以上这些,我发现编程对于思维的锻炼真的很神奇,由于这次做的东西比以往都要复杂,并且要独立完成,所以一整个做下来的过程中,经常被自己写的各种变量和方法绕晕,在写代码的过程中不全神贯注就很容易一团糟,要在脑子里理清楚各种方法和属性和类的关系,这个过程很过瘾。
并且,如何找资源如何借鉴前辈的经验,也是一个问题,网上有很多资料,多去看,多去了解,可以增长很多见识。
这个游戏做完,还有很多需要完善的地方,比如说增加注册登录的功能,添加一个排名,每消除一行得十分得规则可以改进,可以添加更多更好玩得音效和视觉效果等等
这次的课设真的学到很多东西,受益匪浅。