团队项目结题
团队项目结题
小组项目名称:
俄罗斯方块代码实现
项目简介:
我们小组学习的是俄罗斯方块代码的简单实现,在原有代码的基础上,对代码进行理解和测试。我们的代码主要分两部分模块:一部分是图形界面的,一部分是操作界面的代码,对于操作界面的代码,由荆玉茗和史婧瑶同学负责,另一部分图形化的代码由韩昊辰和祁玮同学负责。
设计思路
1.界面设计 首先进行游戏区域的设计,主要的游戏功能都在游戏区域显示,比如方块的随机选择,画布的背景显示等等。 其次是系统功能显示区域,这里显示了当前所玩游戏的等级,右方可以选择不同的游戏级别,一共有1、2、3、4、5五种游戏级别的选择;下方是游戏画布背景的选择,一共有浅绿、浅黄、黑色三种背景颜色的选择;最下方有开始游戏、暂停游戏、结束游戏和退出游戏四中功能选项。
2.游戏功能设计 在无人操作时,游戏开始后会随机产生一个下坠物,先显示在界面右上角的显示区域,然后转到游戏区域,右上角又随机产生一个新的下坠物,当该下坠物下落到底后,新的下坠物再次进入游戏区域,如此循环,直到游戏结束/暂停,这就是游戏的正常工作。 上述过程是在无人操作时游戏自动运行的,当用户进行一定的操作交互的时候,运行程序可以根据用户的操作指示进行当前下坠物的控制。而这些操作都是响应相关的功能键而执行的,所以这里可以把它看成一种“中断”的操作。 在中断过程中,可以通过键盘包括按某些键进行操作。为了便于用户操作,用户可以自主选择操作键,但他们的作用不变。但还是应该设置默认键来方便操作。例如:光标向上键,光标向下键,光标向左键,光标向右键。
(1)按光标向上键 此事件产生下坠物旋转操作,下坠物并非任何情况都能旋转,如果旋转后与小方格矩阵显示的下坠物有冲突或超出边界时,均不能发生旋转。因此首先要判断是否有足够的空间进行旋转,然后选择是否旋转。
(2)按光标向下键 此事件产生下坠物加速下落操作,如果下坠物已经到达游戏区域底部或者已经有其他方块遮挡,则下坠物停止下降。
(3)按光标向左键此事件产生下落下坠物左移操作。首先要判断此下坠物是否能够发生左移,当越界或被其他显示下坠物阻挡时,则不能左移。
(4)按光标向右键 此事件产生下落下坠物右移操作。首先要判断此下坠物是否能够发生右移,当越界或被其他显示下坠物阻挡时,则不能右移。
代码注释(图形界面部分)
package fangkuai;
/**
* Created by Administrator on 2016/6/5 0005.
*/
public class RussionBlockGame
{
private int aa=0;
private int ic=0;
private final int sp_width=10; //游戏界面宽格
private final int sp_height=20; //游戏界面高格
private final int types[][][]={ //游戏方块每种ID的方块的形状数据存储好(都是一些固定的数据)
{{-1,0},{0,0},{1,0},{2,0}}, //长条
{{0,-1},{0,0},{0,1},{0,2}},
{{-1,0},{0,0},{1,0},{1,1}}, //直角(右)
{{0,1},{0,0},{0,-1},{1,-1}},
{{1,0},{0,0},{-1,0},{-1,-1}},
{{0,-1},{0,0},{0,1},{-1,1}},
{{-1,0},{0,0},{0,1},{1,0}}, //直角(中)
{{0,1},{0,0},{1,0},{0,-1}},
{{1,0},{0,0},{0,-1},{-1,0}},
{{0,-1},{0,0},{-1,0},{0,1}},
{{-1,1},{-1,0},{0,0},{1,0}}, //直接(左)
{{1,1},{0,1},{0,0},{0,-1}},
{{1,-1},{1,0},{0,0},{-1,0}},
{{-1,-1},{0,-1},{0,0},{0,1}},
{{0,-1},{0,0},{1,0},{1,1}},
{{-1,0},{0,0},{0,-1},{1,-1}},
{{0,1},{0,0},{1,0},{1,-1}},
{{1,0},{0,0},{0,-1},{-1,-1}},
{{0,0},{0,1},{1,0},{1,1}} //正方形
};
private int[][] block_box=new int[4][2]; //四个方块坐标
private int[][] block_box_tt=new int[4][2];
private int block_x=0,block_y=0; //游戏方块在游戏界面中的坐标
private int block_type=0; //方块类别
private int[][] game_space=new int[20][10]; //空间数据
private int movetype=0;
private int scroe=0;
private int speed=5;
public RussionBlockGame()
{
clearspace();
makenewblock();
}
public void clearspace() //初始化空间数据
{
for(int i=0;i<sp_height;i++)
for(int j=0;j<sp_width;j++)
game_space[i][j]=0;
}
public void makenewblock() //随机出现方块
{
aa=(int)(Math.random()*100%7+1);
ic=aa*10+1;
switch(aa)
{
case 1:
block_type=0; //生成长条
break;
case 2:
block_type=2; //生成直角右
break;
case 3:
block_type=6; //生成直角中
break;
case 4:
block_type=10; //生成直角左
break;
case 5:
block_type=14;
break;
case 6:
block_type=16;
break;
case 7:
block_type=18; //生成正方形
break;
}
block_x=1;
block_y=sp_width/2; //产生一个方块时只需要提供其ID即可
for(int i=0;i<4;i++)
{
block_box[i][0]=block_x-types[block_type][i][1];
block_box[i][1]=block_y+types[block_type][i][0];
}
}
public void movedown() //方块下移
{
block_x++; //ID的x自加,y不变
for(int i=0;i<4;i++)
{
block_box[i][0]=block_x-types[block_type][i][1];
}
movetype=1;
}
public void moveleft() //方块左移
{
block_y--; //ID的y自减,x不变
for(int i=0;i<4;i++)
{
block_box[i][1]=block_y+types[block_type][i][0];
}
movetype=2;
}
public void moveright() //方块右移
{
block_y++; //ID的y自加,x不变
for(int i=0;i<4;i++)
{
block_box[i][1]=block_y+types[block_type][i][0];
}
movetype=3;
}
public void turnright() //方块的旋转
{
int[][] block_box_temp=new int[4][2];
int ic_temp=ic;
int block_type_temp=block_type;
int id=ic%10;
for(int i=0;i<4;i++)
{
block_box_temp[i][0]=block_box[i][0];
block_box_temp[i][1]=block_box[i][1];
}
if(aa==7) //如果是正方形,这旋转不变
return;
else if(aa==1||aa==5||aa==6) //这些方块旋转周期为2
{
if(id==2) //id记录旋转次数,满二次就返回最开始形态,同时记录清零
{
block_type--;
ic--;
}
else
{
block_type++;
ic++;
}
}
else //这些方块旋转次数周期为4
{
if(id==4) //旋转周期已满,返回最开始形态,同时记录清零
{
block_type=block_type-3;
ic=ic-3;
}
{
block_type++;
ic++;
}
}
for(int i=0;i<4;i++)
{
block_box[i][0]=block_x-types[block_type][i][1];
block_box[i][1]=block_y+types[block_type][i][0];
}
if(Iscanmoveto()==false) //不满足可旋转条件,则不改变值,记录并返回
{
ic=ic_temp;
block_type=block_type_temp;
for(int i=0;i<4;i++)
{
block_box[i][0]=block_box_temp[i][0];
block_box[i][1]=block_box_temp[i][1];
}
}
}
public void moveback()
{
if(movetype==1)
{
block_x--;
for(int i=0;i<4;i++)
{
block_box[i][0]=block_x-types[block_type][i][1];
}
}
else if(movetype==2)
{
block_y++;
for(int m=0;m<4;m++)
{
block_box[m][1]=block_y+types[block_type][m][0];
}
}
else if(movetype==3)
{
block_y--;
for(int n=0;n<4;n++)
{
block_box[n][1]=block_y+types[block_type][n][0];
}
}
}
public boolean Iscanmoveto()
{
for(int i=0;i<4;i++)
{
if(block_box[i][0]<0||block_box[i][0]>19) //超过游戏空间高度,不可移动
{
moveback();
return false;
}
else if(block_box[i][1]<0||block_box[i][1]>9) //超过游戏空间宽度,不可移动
{
moveback();
return false;
}
else if(game_space[block_box[i][0]][block_box[i][1]]==1) //碰到已知模块,不可再移动
{
moveback();
return false;
}
}
return true;
}
public boolean Ishitbottom()
{
for(int i=0;i<4;i++)//适用游戏高度如果没有变化
{
if(block_box[i][0]+1>19) //方块中任意一个部分满足碰底条件
{
for(int m=0;m<4;m++)
{
game_space[block_box[m][0]][block_box[m][1]]=1;//改变了游戏原有的空间大小了
block_box_tt[m][0]=block_box[m][0];//利用新数组存储碰底的模块
block_box_tt[m][1]=block_box[m][1];
block_box[m][0]=0;//原模块清零
block_box[m][1]=0;
}
return true;
}
}
for(int i=0;i<4;i++)
{
if(game_space[block_box[i][0]+1][block_box[i][1]]==1)//适用游戏高度如果变化了
{
for(int m=0;m<4;m++)
{
game_space[block_box[m][0]][block_box[m][1]]=1;
block_box_tt[m][0]=block_box[m][0];
block_box_tt[m][1]=block_box[m][1];
block_box[m][0]=0;
block_box[m][1]=0;
}
return true;
}
}
return false;//都不满足,则游戏中的模块还未碰底
}
public void CheckAndCutLine()
{
int a[]={block_box_tt[0][0],block_box_tt[1][0],block_box_tt[2][0],block_box_tt[3][0]};
int b[]={30,30,30,30};
int temp=0;
int temp1=0;
int count=0;
int ss=0;
for(int i=0;i<4;i++)//一次性消去的最高高度
{
for(int j=0;j<10;j++)//判断底部是否都为1
{
if(game_space[a[i]][j]==1)
temp++;//底部每有一个,记录1
}
if(temp==10)//倒数四行中某一行满足都为1的话
{
for(int m=0;m<4;m++)
if(b[m]==a[i])
{
break;
}
else
ss++;
if(ss==4)
{
b[count]=a[i];
count++;
}
}
temp=0;
ss=0;
}
for(int i=0;i<3;i++)
for(int j=i+1;j<4;j++)
{
if(b[i]>b[j])
{
temp1=b[i];
b[i]=b[j];
b[j]=temp1;
}
}
for(int n=0;n<4;n++)
{
if(b[n]==30)
break;
else
{
for(int aa=b[n]-1;aa>=0;aa--)
{
for(int bb=0;bb<10;bb++)
{
game_space[aa+1][bb]=game_space[aa][bb];//消行
}
}
for(int cc=0;cc<10;cc++)//保险起见,第一行全部清空
game_space[0][cc]=0;
}
}
}
public boolean IsGameOver()
{
boolean flag=false;
for(int i=0;i<sp_width;i++)
{
if(game_space[0][i]==1)//bug存在的地方,条件不充分
{
flag=true;
break;
}
}
return flag;
}
public void sure()
{
for(int i=0;i<4;i++)
game_space[block_box[i][0]][block_box[i][1]]=1;
}
public void notsure()
{
for(int i=0;i<4;i++)
game_space[block_box[i][0]][block_box[i][1]]=0;
}
public boolean judge(int i,int j)
{
if(game_space[i][j]==1)
return true;
else
return false;
}
}
代码注释(操作部分)
package afterclass;
import java.awt.*; //引入AWT包,因为要使用到颜色类
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;
public class Win
{
public static void main(String[] args)
{
GameWin frame=new GameWin(); //frame是带有标题和边框的顶层窗口
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //利用JFrame设置单机关闭按钮时执行关闭窗口
frame.setVisible(true); //JFrame创建的窗口默认是不可见的,设置为可见的方法,并将入口参数设为true
}
public static boolean isplaying=true;
}
class GameWin extends JFrame
{
public GameWin()
{
this.setFocusable(true); //将控件设置成可获取焦点状态,设置成true,才能获得控件的点击事件
getContentPane().requestFocus();
this.setAlwaysOnTop(true); //将窗口一直处于屏幕的最前端,永远挡住前的窗口
setTitle("super russionBlock");
setBounds(350,90,Default_X,Default_Y); //获得边界,前两个参数是窗体左上角在容器中的坐标,后两个参数是窗体的宽度和高度
setResizable(false); //当resizable为false时,表示生成的窗体大小是由程序员决定的,用户不可以自由改变该窗体的大小
add(jpy1);
jpy1.setDividerLocation(304); //设置分隔条的大小
jpy1.setDividerSize(4); //给定一个整数,以像素为单位
addKeyListener(jpy2); //为每个按钮添加消息监听
Thread t=new Thread(jpy2);
t.start();
}
public static final int Default_X=500;
public static final int Default_Y=630;
private left jpy2=new left();
private right jpy3=new right();
private JSplitPane jpy1=new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,jpy2,jpy3);
} //
class right extends JPanel implements ActionListener //JPanel是java图形化界面中最常用的容器
{
public right()
{
initialframe();
initialListener();
}
public void initialframe() //初始化框架
{
setLayout(null); // 设置窗体布局为空布局
add(jlArray[0]);
jlArray[0].setBounds(30,60,70,30); //前两个参数是组件左上角在容器中的坐标,后两个参数是组件的宽度和高度
jlArray[0].setFocusable(false); //将控件设置成可获取焦点状态,设置成true,才能获得控件的点击事件
add(jlArray[1]);
jlArray[1].setBounds(30,140,70,30);
jlArray[1].setFocusable(false);
add(jcArray[0]);
jcArray[0].setBounds(100,60,70,30);
jcArray[0].setFocusable(false);
add(jcArray[1]);
jcArray[1].setBounds(100,140,70,30);
jcArray[1].setFocusable(false);
add(jbArray[0]);
jbArray[0].setBounds(50,240,100,35);
jbArray[0].setFocusable(false);
add(jbArray[1]);
jbArray[1].setBounds(50,310,100,35);
jbArray[1].setFocusable(false);
add(jbArray[2]);
jbArray[2].setBounds(50,380,100,35);
jbArray[2].setFocusable(false);
add(jbArray[3]);
jbArray[3].setBounds(50,450,100,35);
jbArray[3].setFocusable(false);
}
public void initialListener() //初始化监听哨
{
for(int i=0;i<4;i++)
jbArray[i].addActionListener(this); //ActionListener是用于接收操作事件的监听器接口
}
public void actionPerformed(ActionEvent e) //ActionListener接口里的方法
{
if(e.getSource()==jbArray[0]) //获取事件中所作用的对象
{
Win.isplaying=true;
}
else if(e.getSource()==jbArray[1])
{
Win.isplaying=false;
}
else if(e.getSource()==jbArray[2])
{
Win.isplaying=false;
}
else if(e.getSource()==jbArray[3])
{
System.exit(0);
}
}
private String[] level={"1"};
private String[] color={"浅黄"};
private JComboBox[] jcArray={new JComboBox(level),new JComboBox(color)}; //构造函数
private JLabel[] jlArray={new JLabel("游戏等级"),new JLabel("空间背景")};
private JButton[] jbArray={new JButton("开始游戏"),new JButton("暂停游戏")
,new JButton("结束游戏"),new JButton("退出游戏")};
}
class left extends JComponent implements KeyListener,Runnable //JComponent类是除容器顶层外所有Swing组件的基类
{
public left()
{
game=new RussionBlockGame();
}
public void paintComponent(Graphics g) //绘制容器中的每个组件
{
Graphics2D g2=(Graphics2D)g; //抽象类,将paint()方法作为画笔
super.paintComponent(g); //把整个面板用背景色重画一遍,起到清屏的作用
double width=300,height=600;
Rectangle2D rect=new Rectangle2D.Double(0,0,width,height); //指定坐标空间的一个区域左上方(0,0)点 ,宽度、高度
g2.setColor(Color.black); //设置颜色
g2.draw(rect);
g2.setColor(Color.yellow); //背景为黄色填充
g2.fill(rect);
g2.setColor(Color.black); //边框为黑色
g2.draw(rect); //绘制一个矩形
for(int i=0;i<20;i++)
for(int j=0;j<10;j++)
{
if(game.judge(i,j)==true)
{
Rectangle2D rect3=new Rectangle2D.Double(j*boxSize,i*boxSize,boxSize,boxSize);
g2.setColor(Color.black);
g2.draw(rect3);
g2.setColor(Color.red); //方块为红色
g2.fill(rect3);
g2.setColor(Color.black);
g2.draw(rect3);
}
}
game.notsure();
}
public void keyTyped(KeyEvent e) //keyTyped表示键盘按下,然后释放
{}
public void keyPressed(KeyEvent e) //keyPressed表示键盘按下,未释放
{
if(Win.isplaying==false)
return;
switch(e.getKeyCode())
{
case KeyEvent.VK_LEFT: //LEFT
game.moveleft();
movejudge();
break;
case KeyEvent.VK_UP: //UP
game.turnright();
movejudge();
break;
case KeyEvent.VK_RIGHT: //RIGHT
game.moveright();
movejudge();
break;
case KeyEvent.VK_DOWN: //DOWN
game.movedown();
movejudge();
break;
}
}
public void keyReleased(KeyEvent e) //keyReleased表示键盘被释放
{}
public void movejudge() //判断消行的方法
{
if(game.Iscanmoveto()==true)
{
game.sure();
repaint();
}
else if(game.Ishitbottom()==true)
{
game.CheckAndCutLine();
game.makenewblock();
repaint();
if(game.IsGameOver()==true)
Win.isplaying=false;
}
}
public void run() //下落线程
{
try
{
while(Win.isplaying)
{
Thread.sleep(500); //通过设置睡眠时间控制下落速度
game.movedown();
movejudge();
}
}
catch(Exception e)
{
e.printStackTrace();
}
}
private RussionBlockGame game;
public static final int boxSize=30;
}
程序结果运行
程序暂停
俄罗斯方块削行前
俄罗斯方块削行后
程序流程图
小组具体分工
- 韩昊辰:代码的调试以及前几周博客的编写,以及对图形化代码部分的注释
- 祁 玮:代码的寻找以及前几周博客的编写,以及对图形化代码部分的注释
- 荆玉茗:对于代码进行bug测试,以及对操作代码的注释
- 史婧瑶:对于代码进行bug测试,以及对操作代码的注释
最后分值分配
- 韩昊辰:30分
- 祁 玮:20分
- 荆玉茗:25分
- 史婧瑶:25分
总结体会
因为之前学的java内容还不是很熟练,所以我们对代码的注释差不多都是一行一行百度到的。通过本次学习我了解到,很多我们看似复杂的程序其实都是有据可循,比如一个小游戏,他是在图形界面里面完成的,将我们以前看见的代码行以图形的界面展示出来,并且很多东西在java的API中都有相应的内容,比如frame类是带有标题和边框的顶层窗口;利用JFrame设置单机关闭按钮时执行关闭窗口......很多很多我们所获取的每一个界面都有相应的程序与之对应,其实掌握并不难,API的东西就像固定的语法,只要知道它是怎么用,用来干什么的就可以。通过本次的学习让我真正的感受到了一个游戏的实现,其实并没有我们想象的那么难,只要找到问题,将问题细化,我们就能得到找到解决的方法。