俄罗斯方块游戏的设计与实现JavaSwingEclipse
俄罗斯方块游戏的设计与实现(Java+Swing+Eclipse)
目录
基于Java的俄罗斯方块游戏的设计与实现 I
摘 要 I
Based on the design and implementation of Java game Tetris II
Abstract II
1 绪论 1
1.1程序开发背景及意义 1
1.2开发技术概述 2
1.3俄罗斯方块游戏的研究现状 2
1.3.1 国内外研究现状 2
1.3.2 文献综述 3
2相关技术 4
2.1JAVA简介 4
2.2 Swing组件简介 4
2.3Eclipse开发平台简介 5
2.4系统平台环境: 6
2.4.1 硬件平台: 6
2.4.2 软件平台: 6
3 系统分析 7
3.1可行性分析 7
3.1.1经济可行性分析 8
3.1.2技术可行性分析 8
3.1.3社会可行性分析 8
3.2需求分析 8
3.2.1 功能性需求 9
3.2.2 非功能性需求 9
3.2.3 接口控制 9
4 系统的概要设计 11
4.1 系统的功能设计 11
4.1.1 手动处理业务的基本流程 11
4.1.2 基本流程的功能模块 12
5 系统的详细设计与实现 15
5.1 游戏主界面显示模块 15
5.2 画布、方块显示模块 16
5.2.1 背景画布模块设计 16
5.2.2 预览方块模块设计 19
5.2.3 方块移动、旋转模块设计 21
5.3 控制面版模块 25
5.3.1 菜单栏模块设计 25
5.3.2 控制面板按钮设计 26
6 系统的测试运行 29
6.1 测试概述 29
6.1.1 测试原则 29
6.1.2 测试方法 30
6.1.3 测试意义及注意事项 31
6.2 游戏代码、算法的测试 31
6.3 游戏界面菜单选项的功能测试 32
6.4 按键事件的功能测试 34
6.5 方块的堆砌与消行功能测试 35
6.6 测试结果 35
结 论 36
参考文献 38
致 谢 39
附录A 外文原文 40
The psychology of Tetris 40
附录B 外文翻译 43
俄罗斯方块的心理效应 43
3.2需求分析
所谓的“需求分析”是指对待解决的问题的详细分析,澄清问题的要求,包括需要输入什么数据,得到什么结果,最后应该输出什么。可以说,在软件工程中“需求分析”是确定电脑“做什么”,达到什么样的效果。可以说,需求分析是在系统完成之前完成的。
在软件工程中,需求分析是指在创建新的或更改现有计算机系统时描述新系统的目的,范围,定义和功能所需的所有工作。需求分析是软件工程中的关键过程。在这个过程中,系统分析师和软件工程师决定了客户的需求。只有在确定了这些需求之后,才能分析和寻求新系统的解决方案。需求分析阶段的任务是确定软件系统功能。
在软件工程的历史上,人们一直认为需求分析是软件工程中最简单的一步。但是在过去十年中,越来越多的人意识到需求分析是最重要的过程。如果分析师在需求分析时无法正确地了解客户的需求,则最终软件无法真正达到客户的需求,或软件项目在指定时间内无法完成。
3.2.1 功能性需求
整个游戏系统会随机产生7种由四个小方块组成的不同形状的方块,经过旋转后得到28种状态,如果增加游戏难度,会增加六种不同形状的方块形状,同样经过旋转后增加24种状态。方块按一定的速度自由下落,玩家通过键盘上的上下左右按键控制方块的左右移动和旋转,将方块落下后放在合适的位置。当方块落下后,如果方块落下后有一整行被方块填满,那么该一整行消去。当一行被消去时,玩家得分增加10分,当得分达到100分后,玩家等级增加一级,速度加快一级,难度加大。如果当方块落下后整个游戏画布界面被占满,则方块不再下落,游戏宣告失败。游戏具体的功能需求有如下几个:
▪ 游戏界面需求:游戏的良好的界面会让玩家眼前一亮,更加能充分的感受到游戏带来的娱乐性,放松性。本游戏的默认背景色是深绿色,游戏主界面画面自定义为自己的所喜欢的图片,并可以更改,主界面方块默认用橘黄色,预显方块颜色默认为淡紫色。背景色、前景色对比鲜明,以达到让玩家眼前一亮的感觉,并能在游戏达到高等级状态,方块下落速度渐高的情况下使玩家能够清楚的分辨出下落方块的形状,增加游戏的刺激性。
▪ 游戏形状需求:用数组作为存储方块52种状态的数据结构,即初级等级长条形、Z字形、反Z形、田字形、7字形、反7形、T字型一共7种形状的向4个方向的旋转变形,和中级等级的三种方块12种不同的状态,高级等级的三种方块12种不同的状态。各个方块可以实现按逆时针的旋转方式旋转,并且方块能否旋转需要用条件加以判断,如果旋转后可能发生越界,则不能旋转,需要调整位置来保证他可以旋转。
▪ 键盘处理事件需求:当方块下落时,玩家可以通过键盘上的方向键:上键实现旋转,下键实现加速下落,左键实现左移,右键实现右移,和空格键实现一键下落,字母P键实现暂停,字母C键实现继续等一系列的操作。
▪ 鼠标处理事件需求:通过鼠标,可以点击控制面板中的菜单按钮和帮助按钮,选择菜单栏的菜单项,可以实现游戏的开局,选择游戏等级,更改游戏中方块的颜色显示,游戏主界面背景色和前景色的显示,更改游戏背景图片,方块下落速度,是否播放游戏中的声音等一系列的功能。
▪ 显示需求:本游戏程序的显示需求是要求当方块落下后填满一整行,则该行消除,其余剩下的未填满的行自动逐次向下移动,消去一行右界面得分增加十分,当分数增加到100分时,等级增加一等级。当方块落下叠加到主界面的全部所有行时,方块不再下落,游戏结束,主界面提示“Game Over”字样。
3.2.2 非功能性需求
非功能性需求:俄罗斯方块游戏系统的非功能性需求包括游戏主界面左上角图标显示,调整窗口尺寸最大化最小化(但不包括主界面的尺寸大小),游戏运行时弹出窗口的位置居中等一系列非功能性需求。
3.2.3 接口控制
本俄罗斯游戏系统在Windows操作系统下,主要是通过键盘进行游戏的操
作,通过鼠标进行开局,退出,设置等一系列操作。首先,游戏利用键盘的按键进行游戏的操作,所以需要使用键盘的接口事件。其次,在游戏进行的全过程中,需要使用鼠标进行游戏的控制,包括开始,选择等级,改变设置,改变颜色,查看版本信息,退出等,所以要对鼠标的单击,按键添加接口监听事件,编写相应的代码来实现鼠标和键盘的相应功能。
4 系统的概要设计
4.1 系统的功能设计
4.1.1 手动处理业务的基本流程
本游戏的设计以娱乐为初衷,以益智为目的,在综合研究以往俄罗斯方块经典游戏功能的基础上推陈出新,加之新的功能,赋以新的生机和活力。以下具体阐述游戏的基本流程。
运行说明:
1>运行程序,点击右侧控制面板内的“开始”或“控制”菜单内的“开始”按钮开始游戏。
2>使用上、下、左、右键和空格键,P键,C键控制方块的变形、下落、向左和向右移动和一键迅速下落,暂停,继续。
3>方块满行消除,分数自动增加,等级自动增加一级。
4>等级增加、方块下落速度增加,按右侧控制面板或“游戏”菜单内的“初级”“中级”,“高级”按钮来手动改变游戏难易程度。也可点击“方块颜色”菜单内的选项,更改方块颜色等,也可以通过“自定义”菜单内的选项,来更改游戏的一些属性。
5>按键盘键字母P键可以控制游戏暂停,然后按子母键C键可以控制游戏继续上次游戏。按“结束游戏”按钮,游戏会彻底停止正在进行的当局游戏,再按“开始”或“控制”菜单内的“重新开始”会开始新游戏。
6>当方块占满整个窗口,不能再有新方块下落时,游戏会弹出“Game Over”的对话框提示游戏结束。
游戏的基本流程图如图4—1所示:
图4-1游戏的基本流程图
4.1.2 基本流程的功能模块
本系统基于游戏的各项功能来设计游戏的各个功能模块。图4-2为本游戏的系统功能模块示意图,如图所示,本游戏主要有两大模块:游戏界面区,游戏控制区。游戏界面区分显示玩家可选操作、显示玩家操作结果两个部分。游戏控制区分更改颜色、开始、更改游戏等级为初级、更改游戏等级为中级、更改游戏等级为高级、自定义下落速度、更改背景、退出以及其他等一些功能模块。
图4-2系统功能模块示意图
package view;
import javax.swing.*;
import javax.swing.border.EtchedBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import util.Constant;
//import javax.swing.Timer;
import java.util.TimerTask;
import java.util.Timer;
import javax.management.*;
import control.MusicPlayer;
import control.PreView;
import model.Block;
import model.Box;
import java.awt.*;
import java.awt.event.*;
public class MyFrame extends JFrame{
JPanel jp_pan=new JPanel();
JPanel jp_pre=new JPanel();
JPanel jp_ctrl=new JPanel();
JPanel jp_scor=new JPanel();
Zidingyi zi;
JRadioButtonMenuItem jr1=new JRadioButtonMenuItem("初级",true);
JRadioButtonMenuItem jr2=new JRadioButtonMenuItem("中级");
JRadioButtonMenuItem jr3=new JRadioButtonMenuItem("高级");
JLabel jt9=new JLabel("得分:0" );
static JLabel jt10=new JLabel("等级:1" );
JMenu m1=new JMenu("游戏");
JMenu m2=new JMenu("帮助");
JCheckBox jc1;
JSlider jsl;
// Dialog dia;//创建对话框
static ImageIcon background = new ImageIcon(Constant.backGround1);
// 把背景图片加到label
static JLabel label = new JLabel(background);
// Dialog dia=new Dialog(this, "自定义", false);
int scor=0;//初始化分数为0
static int rank=0;//初始化等级为0
int highC=0;
boolean upspeed=false;
boolean isTime=true;
boolean runstop;
static boolean isRank=false;
static boolean changeBack=false;
public static boolean playing=false;
static boolean isMusic=true;
static boolean high=false;
PreView pv=new PreView();
JMenuItem ji1=new JMenuItem("开局");
GameCanvas gc=new GameCanvas(20, 12);//画出20行12列
private Block block=new Block();//当前块
private int newspeed=1000;//默认当前等级为1
MusicPlayer mp=new MusicPlayer();
Timer time=new Timer();
MyTask mytask;
int temp=1;
// 游戏主构造函数
public MyFrame(String str){
super(str);
this.setSize(450, 570);
Dimension scrSize =
Toolkit.getDefaultToolkit().getScreenSize();//获取屏幕尺寸
setLocation((scrSize.width - getSize().width) / 2,
(scrSize.height - getSize().height) / 2);//设置屏幕居中
this.setLayout(null);
//label的大小为jframe的大小
label.setBounds(0, 0, this.getWidth(), this.getHeight());
//把label加到jframe的最底层,比jframe上的那个panel还下面
this.getLayeredPane().add(label, new Integer(Integer.MIN_VALUE));
//label比jframe上的那个panel还下面,那需要把那个panel设为透明的,不然就盖住背景了
JPanel imagePanel = (JPanel) this.getContentPane();
imagePanel.setOpaque(false);
addMenu();
//游戏开始按钮
ji1.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
// 如果游戏已经开始,再按一次即显示游戏结束
if (playing == true) {
ji1.setText("开局");
if(isMusic==true)
{mp.playStart();}
gc.setGameOver(true);
gc.repaint();
MyFrame.rank=11-Constant.step;
MyFrame.jt10.setText("等级:"+MyFrame.rank);
runstop=true;
block.isAlive=false;
block=new Block();
mytask.cancel();
playing = false;
} else {
reset();
if(isMusic==true)
{mp.playStart();}
MyFrame.rank=11-Constant.step;
MyFrame.jt10.setText("等级:"+MyFrame.rank);
ji1.setText("结束游戏");
playing = true;
mytask=new MyTask();
time.schedule(mytask, 0, 100);//100毫秒执行一次
Thread thread = new Thread(new play());// 调用开始游戏的方法
thread.start();
}
};
});
this.add(gc);//添加游戏画布
addRight();//添加右边
this.setFocusable(true);//设置可获得焦点
this.requestFocus();//设置焦点
this.addKeyListener(new MyListener());
this.setVisible(true);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void setBackGround(){
label.setIcon(background);
}
/**
* 定时下落,用计数方式来实现速度的改变
*
*/
private class MyTask extends TimerTask {
@Override
public void run() {
temp++;
if (temp % Constant.step == 0) {
block.newL = block.x;
block.newH = block.y + 1;
// block.yy();
if (block.pausing == true)
return;
if (high == true) {
block.earse();
highC++;
if (highC == 4) {
gc.addRow();
highC = 0;
}
}
if (block.isMoveAble(block.newH, block.newL)) {
block.earse();
block.y++;
block.display();
gc.repaint();
} else {
block.isAlive = false;
gc.repaint();
// cancel();
} // 取消定时器任务
temp = 1;
}
}
}
private class play implements Runnable {
public void run() {
/* if(killThread==true)
return;*/
int col = (int) (Math.random() * (gc.getCols() - 3));//随即位置生成列
int style = Constant.STYLES[(int) (Math.random() * Block.get_addl())][(int) (Math
.random() * 4)];
while (playing) {
if (block != null) {
//判断当前方块是否死亡
if (block.isAlive) {
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
continue;
}
}
isFullLine();
// if(upspeed)//判断是否加速,是的话则进行调用
// upLevel();
if(isGameOver()){
if(isMusic==true)
{mp.playGameOver();}
gc.setGameOver(true);
gc.repaint();
ji1.setText("开局");
mytask.cancel();
playing=false;
return;
}
block = new Block(style, -1, col, gc);
block.jixu();//防止在暂停时候重新开始游戏,暂停字还不消失
gc.repaint();// 将创建出来的方块显示出来
block.isAlive = true;
col = (int) (Math.random() * (gc.getCols() - 3));//随即位置生成列
style = Constant.STYLES[(int) (Math.random() * Block.get_addl())][(int) (Math
.random() * 4)];
pv.setStyle(style);
}
}
/**
* 增加速度
*/
private void upLevel() {
if(Constant.step-1<1){
return;}
Constant.step=Constant.step-1; //速度增加一级
rank++;
jt10.setText("等级:"+rank);
upspeed=false;//将标志位至为false
}
/**
* 判断是否满行,满行则调用消行方法。
*/
private void isFullLine() {
// TODO Auto-generated method stub
for (int i = 0; i < 20; i++) {
int row = 0;
boolean flag = true;
for (int j = 0; j < 12; j++) {
if (!gc.getBox(i, j).isColorBox()) {
flag = false;
break;
}
}
if (flag == true) {
row = i;
gc.delete(row);//删除行
if(isMusic==true)
{mp.playEraseSound();}
addScor();//增加分数
if(scor%10==0)//设置为10分增加一个等级
upspeed=true;//将速度增加标志位至为true
if(upspeed==true)
upLevel();
}
}
}
/**
* 得分的计算方法
*/
private void addScor() {
scor=scor+10;
jt9.setText("得分:"+scor);
}
}
/**
* 判断最顶层是否有被占用,游戏是否结束
*/
private boolean isGameOver() {
for (int i = 0; i < 12; i++) {
Box box = gc.getBox(0, i);
if (box.isColorBox())
return true;
}return false;
}
private void reset() {
scor=0;
rank=0;
jt10.setText("等级:"+rank);
jt9.setText("得分:"+scor);
upspeed=false;
playing=true;
runstop=false;
// block.pausing=false;
// isTime=true;
// block=new Block();
// block.isAlive=false;
gc.setGameOver(false);
gc.repaint();
gc.reset();
}
/* private class MenuKeyListener extends KeyAdapter{
public void keyPressed(KeyEvent e) {
int i = e.getKeyCode();
switch (i) {
case KeyEvent.VK_C:
System.out.println("111");;
break;
case KeyEvent.VK_DOWN:
block.moveDown();
break;
case KeyEvent.VK_LEFT:
block.moveLeft();
break;
}
}
}*/
/**
*
*按键监听,上下左右。
*/
private class MyListener extends KeyAdapter{
@Override
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
int i = e.getKeyCode();
switch (i) {
case KeyEvent.VK_UP:
block.moveUp();
break;
case KeyEvent.VK_DOWN:
block.moveDown();
break;
case KeyEvent.VK_LEFT:
block.moveLeft();
break;
case KeyEvent.VK_RIGHT:
block.moveRight();
break;
case KeyEvent.VK_SPACE:
block.quickDown();
break;
case KeyEvent.VK_P:
block.pause();
break;
case KeyEvent.VK_C:
block.jixu();
break;
}
}
}
/**
* 菜单添加方法
*/
private void addMenu() {
// TODO Auto-generated method stub
JMenuBar jb1=new JMenuBar();
// m1.addKeyListener(new MenuKeyListener());
//监听Dialog对话框,如果有等级改变则改变选择
m1.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
// TODO Auto-generated method stub
int i=Block.get_addl();
if(i==7)
jr1.setSelected(true);
else if(i==10)
jr2.setSelected(true);
else
jr3.setSelected(true);
}
});
// JMenuItem ji1=new JMenuItem("开局(O)");
jr1.addActionListener(new MenuActionListener());
jr2.addActionListener(new MenuActionListener());
jr3.addActionListener(new MenuActionListener());
ButtonGroup bg=new ButtonGroup();
bg.add(jr1);
bg.add(jr2);
bg.add(jr3);
JMenuItem ji2=new JMenuItem("自定义");
ji2.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
// TODO Auto-generated method stub
zi=new Zidingyi(MyFrame.this,"自定义",false,block,gc);
zi.setVisible(true);
if(playing==true)
block.pause();
}
});
JMenuItem ji3=new JMenuItem("退出");
ji3.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
// TODO Auto-generated method stub
System.exit(1);//退出程序
}
});
JMenuItem ji4=new JMenuItem("关于");
ji4.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
JDialog dl=new Version(MyFrame.this,"版本信息",false);
}
});
//调用颜色对话框设置方块颜色
JMenuItem ji_color=new JMenuItem("方块颜色");
ji_color.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
Color newFrontColor = JColorChooser.showDialog(
MyFrame.this,"设置方块颜色",
gc.getBlockColor());
if (newFrontColor != null)
gc.setBlockColor(newFrontColor);
}
}
);
MyFrame.this.setJMenuBar(jb1);
jb1.add(m1);
jb1.add(m2);
m1.add(ji1);
m1.add(jr1);
m1.add(jr2);
m1.add(jr3);
m1.add(ji2);
m1.add(ji_color);
m1.add(ji3);
m2.add(ji4);
}
/**
* 右界面的添加
*/
private void addRight() {
// TODO Auto-generated method stub
// JTextField jt1=new JTextField("下一块");
JLabel jt1=new JLabel("下一块");
jt1.setFont(new Font("华文行楷", Font.BOLD, 18));
jt1.setOpaque(false);
// jt1.setEditable(false);
jp_pre.setLayout(null);
jt1.setBounds(5, 0, 80, 20);
jp_pre.add(jt1);
pv.setBounds(10, 20, 102, 102);
jp_pre.add(pv);//添加预览窗口
jp_pre.setBounds(308, 5, 120, 125);//设置坐标
jp_pre.setOpaque(false);//设置背景为透明
MyFrame.this.add(jp_pre);
// JTextField jt2=new JTextField("功能键盘" );
JLabel jt2=new JLabel("功能键盘");
jt2.setFont(new Font("华文行楷", Font.BOLD, 23));
// jt2.setEditable(false);
jt2.setOpaque(false);
// JTextField jt3=new JTextField("快速向下:↓" );
JLabel jt3=new JLabel("快速向下:↓");
jt3.setFont(new Font("华文行楷", Font.BOLD, 15));
// jt3.setEditable(false);
jt3.setOpaque(false);
// JTextField jt4=new JTextField("旋转:↑" );
JLabel jt4=new JLabel("旋转:↑");
jt4.setFont(new Font("华文行楷", Font.BOLD, 15));
// jt4.setEditable(false);
jt4.setOpaque(false);
// JTextField jt5=new JTextField("向左:←" );
JLabel jt5=new JLabel("向左:←");
jt5.setFont(new Font("华文行楷", Font.BOLD, 15));
// jt5.setEditable(false);
jt5.setOpaque(false);
// JTextField jt6=new JTextField("向右:→" );
JLabel jt6=new JLabel("向右:→");
jt6.setFont(new Font("华文行楷", Font.BOLD, 15));
// jt6.setEditable(false);
jt6.setOpaque(false);
JLabel jt11=new JLabel("一键下落:空格");
jt11.setFont(new Font("华文行楷", Font.BOLD, 15));
// jt6.setEditable(false);
jt6.setOpaque(false);
// JTextField jt7=new JTextField("暂停:P" );
JLabel jt7=new JLabel("暂停:P");
jt7.setFont(new Font("华文行楷", Font.BOLD, 15));
// jt7.setEditable(false);
jt7.setOpaque(false);
// JTextField jt8=new JTextField("继续:C" );
JLabel jt8=new JLabel("继续:C");
jt8.setFont(new Font("华文行楷", Font.BOLD, 15));
// jt8.setEditable(false);
jt8.setOpaque(false);
jp_ctrl.setLayout(new GridLayout(8, 1, 0, 0));
// jp_ctrl.setBorder(BorderFactory.createBevelBorder(EtchedBorder.LOWERED));
jp_ctrl.add(jt2);
jp_ctrl.add(jt3);
jp_ctrl.add(jt4);
jp_ctrl.add(jt5);
jp_ctrl.add(jt6);
jp_ctrl.add(jt11);
jp_ctrl.add(jt7);
jp_ctrl.add(jt8);
jp_ctrl.setOpaque(false);
jp_ctrl.setBounds(310, 145, 120, 200);
MyFrame.this.add(jp_ctrl);
// jt9.setEditable(false);
jt9.setOpaque(false);
jt9.setForeground(Color.BLACK);
// jt10.setEditable(false);
jt10.setOpaque(false);
jt10.setForeground(Color.BLACK);
jp_scor.setLayout(new GridLayout(2, 1, 0, 20));
jp_scor.add(jt9);
jt9.setFont(new Font("华文行楷", Font.BOLD, 26));
jt10.setFont(new Font("华文行楷", Font.BOLD, 26));
jp_scor.add(jt10);
jt9.setBackground(Color.LIGHT_GRAY);
jt10.setBackground(Color.LIGHT_GRAY);
jp_scor.setOpaque(false);
jp_scor.setBounds(320, 360, 100, 140);
MyFrame.this.add(jp_scor);
}
/**
* 菜单等级的监听
*
*/
private class MenuActionListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
//获取JMenuItem对象
JMenuItem j=((JMenuItem)e.getSource());
if(j==jr1){
// newLevel=Constant.LEVEL_1;
Block.set_addl(7);
}
if(j==jr2){
Block.set_addl(10);
}
if(j==jr3){
// high=true;
Block.set_addl(13);
}
}
}
public static void main(String[] args) {
new MyFrame("俄罗斯方块");
}
}