20135130实验三报告

实验三报告

 北京电子科技学院(BESTI)

                     实     验    报     告

      课程:Java   班级: 1351     姓名:王川东    学号:20135130

      成绩:                指导教师:娄嘉鹏          实验日期:2015.5.8

      实验密级:           预习程度:                实验时间:15:30~18:00

      仪器组次:20         必修/选修:选修           实验序号:02

      实验名称:敏捷开发与XP实践                                            

      实验仪器:

名称

型号

数量

计算机

lenovo

1

实验楼

 

1

 

              

实验步骤

(一)敏捷开发与XP

软件工程是把系统的、有序的、可量化的方法应用到软件的开发、运营和维护上的过程。软件工程包括下列领域:软件需求分析、软件设计、软件构建、软件测试和软件维护。 人们在开发、运营、维护软件的过程中有很多技术、做法、习惯和思想体系。软件工程把这些相关的技术和过程统一到一个体系中,叫“软件开发流程”。软件开发流程的目的是为了提高软件开发、运营、维护的效率,并提高软件的质量、用户满意度、可靠性和软件的可维护性。 光有各种流程的思想是不够的,我们还要有一系列的工具来保证这些思想能够在实践中有效率地运作。软件开发很重要的一点不是看你能对多少理论讲的头头是道,还要看你对相关工具应用的如何,比如Java中单元测试要和JUnit的应用结合起来,建模要和Umbrello或StarUML的应用结合起来。编程学习是一个习而学的过程。 一个常见的公式是:软件工程=开发流程+工具 邹欣老师给出的两个公式:软件=程序+软件工程软件企业=软件+商业模式 开发流程大家可以参考学习邹欣老师的软件团队和开发流程。常见的开发流程有:

  • RUP(Rational Unified Process)
  • PSP(Personal Software Process )
  • TSP(Team Software Process )
  • Agile Process
  • ……

敏捷开发(Agile Development)是一种以人为核心、迭代、循序渐进的开发方法。“敏捷流程”是一系列价值观和方法论的集合。从2001年开始,一些软件界的专家开始倡导“敏捷”的价值观和流程,他们肯定了流行做法的价值,但是强调敏捷的做法更能带来价值。

敏捷开发包括很多模式:

其中,极限编程(eXtreme Programming,XP)是 是一种全新而快捷的软件开发方法。XP团队使用现场客户、特殊计划方法和持续测试来提供快速的反馈和全面的交流:

  • XP是以开发符合客户需要的软件为目标而产生的一种方法论
  • XP是一种以实践为基础的软件工程过程和思想
  • XP认为代码质量的重要程度超出人们一般所认为的程度
  • XP特别适合于小型的有责任心的、自觉自励的团队开发需求不确定或者迅速变化的软件

XP软件开发是什么样的通过 XP准则来表达:

  • 沟通 :XP认为项目成员之间的沟通是项目成功的关键,并把沟通看作项目中间协调与合作的主要推动因素。
  • 简单 :XP假定未来不能可靠地预测,在现在考虑它从经济上是不明智的,所以不应该过多考虑未来的问题而是应该集中力量解决燃眉之急。
  • 反馈 :XP认为系统本身及其代码是报告系统开发进度和状态的可靠依据。系统开发状态的反馈可以作为一种确定系统开发进度和决定系统下一步开发方向的手段。
  • 勇气:代表了XP认为人是软件开发中最重要的一个方面的观点。在一个软件产品的开发中人的参与贯穿其整个生命周期,是人的勇气来排除困境,让团队把局部的最优抛之脑后,达到更重大的目标。表明了XP对“人让项目取得成功”的基本信任态度。

一项实践在XP环境中成功使用的依据通过XP的法则呈现,包括:快速反馈、假设简单性、递增更改、提倡更改、优质工作。

XP软件开发的基石是XP的活动,包括:编码、测试、倾听、设计。

项目成员用户成功执行XP活动的技术通过XP实践来呈现,包括编程、团队、过程相关的12条实践:

我们关注其中的编码标准结对编程代码集体所有测试重构等实践。上次实验已经讲过TDD,通过学习这些实践,可以形成以测试为核心的开发流程:

敏捷可以作为一种做事的方式,掌握好的在以后的工作中也会受益无穷。

(二)编码标准

编写代码一个重要的认识是“程序大多时候是给人看的”,编程标准使代码更容易阅读和理解,甚至可以保证其中的错误更少。编程标准包含:具有说明性的名字、清晰的表达式、直截了当的控制流、可读的代码和注释,以及在追求这些内容时一致地使用某些规则和惯用法的重要性。

编码标准中的版式就是一个很好的例子,版式虽然不会影响程序的功能,但会影响可读性。程序的版式追求清晰、美观,是程序风格的重要因素。

我们常见的是这样的代码:

public class CodeStandard { public static void main(String [] args){ StringBuffer buffer = new StringBuffer(); buffer.append('S'); buffer.append("tringBuffer"); System.out.println(buffer.charAt(1)); System.out.println(buffer.capacity()); System.out.println(buffer.indexOf("tring")); System.out.println("buffer = " + buffer.toString()); if(buffer.capacity()<20) buffer.append("1234567"); for(int i=0; i<buffer.length();i++) System.out.println(buffer.charAt(i)); } } 

程序没有最基本的缩进,让人读起来很费劲,这个问题在Eclipse中比较容易解决,我们单击Eclipse菜单中的source->Format 或用快捷键Ctrl+Shift+F就可以按Eclipse规定的规范缩进。

代码标准中很重要的一项是如何给包、类、变量、方法等标识符命名,能很好的命名可以让自己的代码立马上升一个档次。Java中的一般的命名规则有:

  • 要体现各自的含义
  • 包、类、变量用名词
  • 方法名用动宾
  • 包名全部小写,如:io,awt
  • 类名第一个字母要大写,如:HelloWorldApp
  • 变量名第一个字母要小写,如:userName
  • 方法名第一个字母要小写:setName
  • ...

标识符名字应当直观且可以拼读,可望文知意,不必进行“解码”,一般采用英文单词或其组合,便于记忆和阅读,切忌使用汉语拼音来命名,用词要准确例如“当前值”应该起名currentValue,写成nowValue就不准确了,但还凑合,写成dqz(dang qian zhi 首字母)就是笑话了。

标识符的长度“min-length && max-information”的原则,比如:maxVal 比maxValueUntilOverflow要好些,可以通过去元音法把变量名变短,如returnValue->rtnVal ,message->msg;一般全局变量用具有说明性的名字,局部变量用短名字:单字符的名字,常见的如i,j,k等用作局部变量。

其他的可以参考邹欣老师写的代码规范与代码复审.

关于代码标准,可以遵循以下原则:

有一些公司比如Google公开了自己的编码标准,可以作为学习不错的参考,大家参考一下范飞龙老师写的代码规范&代码风格,有兴趣的可以尝试如何在Eclipse中实践Google Java Style(中文版),也就是说如何做到“按一下快捷键Ctrl+Shift+F就可以让自己的代码符合Google Java Style(中文版)的要求”,完成后单独写一篇Blog,有加分的

(三)结对编程

结对编程是XP中的重要实践。在结对编程模式下,一对程序员肩并肩、平等地、互补地进行开发工作。他们并排坐在一台电脑前,面对同一个显示器,使用同一个键盘、同一个鼠标一起工作。他们一起分析,一起设计,一起写测试用例,一起编码,一起做单元测试,一起做集成测试,一起写文档等。 结对编程中有两个角色:

  • 驾驶员(Driver)是控制键盘输入的人。
  • 领航员(Navigator)起到领航、提醒的作用。

如何结对编程,为何要结对编程,大家参考一下结对编程和两人合作 ,重点是:

  • 驾驶员:写设计文档,进行编码和单元测试等XP开发流程。
  • 领航员:审阅驾驶员的文档、驾驶员对编码等开发流程的执行;考虑单元测试的覆盖率;思考是否需要和如何重构;帮助驾驶员解决具体的技术问题。
  • 驾驶员和领航员不断轮换角色,不要连续工作超过一小时,每工作一小时休息15分钟。领航员要控制时间。
  • 主动参与。任何一个任务都首先是两个人的责任,也是所有人的责任。没有“我的代码”、“你的代码”或“他/她的代码”,只有“我们的代码”。
  • 只有水平上的差距,没有级别上的差异。两人结对,尽管可能大家的级别资历不同,但不管在分析、设计或编码上,双方都拥有平等的决策权利。

团队精神是好多地方都强调的一个精神,最小的团队就是一对一的二人团队了,培养团队精神从结对编程开始吧。社会生活中人与人相处最重要的是诚信,有同理心,互利。结对编程中大家会出现分歧,如何更有效地合作要做到对事不对人,掌握这些是可以终生受益的,如何影响小伙伴,大家参考一下两人合作:要会做汉堡包。

 

(以上为阅读了解到的)

(四)版本控制(以下为实验楼自己动手做的)

XP的集体所有制意味着每个人都对所有的代码负责;这一点,反过来又意味着每个人都可以更改代码的任意部分。结对编程对这一实践贡献良多:借由在不同的结对中工作,所有的程序员都能看到完全的代码。集体所有制的一个主要优势是提升了开发程序的速度,因为一旦代码中出现错误,任何程序员都能修正它。 这意味着代码要放到一个大家都能方便获取的地方,我们叫代码仓库。这引出另外一个话题叫版本控制(Version Control)。

不论是对于团队还是个体,版本控制都提供了很多好处。

  • 版本控制提供项目级的 undo(撤销) 功能: 没有什么事情是终结版本, 任何错误必须很容易回滚。 假设你在使用世界上最复杂的文字处理系统。 它具备了所有的能想到的功能,就是没有支持 DELETE(删除) 键。想象你打字的时候得多么的谨慎和缓慢吧, 特别是一篇超大的文档的快临近末尾的时候, 一个不小心就要重头再来(试想你选中所有的文字, 不小心按了 DELETE 键, 因为没有撤销功能,只好重新录入)。编辑文字和版本控制相同,任何时候都需要回滚,无论是一个小时, 一天, 还是一周, 这让你的团队工作自由快速的工作, 而且对于修正错误也非常自信。
  • 版本控制允许多人在同一代码上工作, 只要遵守一定的控制原则就行。 再也不会发生诸如一个人覆盖了另一个人编辑的代码,导致那个人的修改无效这样的情况。
  • 版本控制系统保存了过去所作的修改的历史记录。如果你遭遇到一些惊讶的代码,通过版本控制系统可以很容易找出是谁干的, 修改了什么, 修改的时间, 如果幸运的话,还能找出原因。
  • 版本控制系统还支持在主线上开发的同时发布多个软件版本。在软件发布的时候也不需要整个团队的停止工作,不需要冻结代码。
  • 版本控制也是项目级的时间机器,你可以选择任何一个时间, 精确地查看项目在当时的情况。 这对研究非常有用, 也是重现以前某个有问题的发布版本的基础。

流行的版本控制工具有CVS,SVN,Git等,更多的可以参考这里。Git是Linus除了Linux操作系统外的另外一个重要发明。

首先开通我的代码库功能。

实验截图如下:

20135130实验三报告_第1张图片20135130实验三报告_第2张图片20135130实验三报告_第3张图片

 

 

(五)重构

我们先看看重构的概念:

重构(Refactor),就是在不改变软件外部行为的基础上,改变软件内部的结构,使其更加易于阅读、易于维护和易于变更 。

重构中一个非常关键的前提就是“不改变软件外部行为”,它保证了我们在重构原有系统的同时,不会为原系统带来新的BUG,以确保重构的安全。如何保证不改变软件外部行为?重构后的代码要能通过单元测试。如何使其更加易于阅读、易于维护和易于变更设计模式给出了重构的目标。

重构重要吗?你看看Eclipse菜单中有个refactor菜单就知道了,重构几乎是现代IDE的标配了

 

我们在编码标准中说“给标识符命名”是程序员一项重要技能,以前没有这个意识,现在知道了怎么办?没问题,上图中重构的第一项功能就是Rename,可以给类、包、方法、变量改名字。

修改方法是,用鼠标单击要改的名字,选择Eclipse中菜单中的Refactor->Rename...

 20135130实验三报告_第4张图片20135130实验三报告_第5张图片20135130实验三报告_第6张图片

学过C语言的学生学Java时常犯的毛病是不会封装,该用类的地方都用了结构体。比如要定义一个类Student,会出现这样的代码

20135130实验三报告_第7张图片20135130实验三报告_第8张图片

 

Eclipse中菜单中的Refactor->Encapsulate Field...

 

 20135130实验三报告_第9张图片20135130实验三报告_第10张图片20135130实验三报告_第11张图片20135130实验三报告_第12张图片

注意分析一下重构前后的代码变化:

 

 

同样可以封装idage两个成员变量,结果如下:

 

 

每次打印学生信息都这么写代码违反了DRY原则,造成代码重复,正常的重构可以使用Eclipse中的Extract Method...

由于Java中所有的类都有个专门的toString方法,我们使用Eclipse中Source->Generate toString()... 给Student类产生一个toString方法

修改main的代码,结果如下:

 

 

 

 

(以下为阅读了解的)

我们要修改软件,万变不离其宗,无非就是四种动机:

  • 增加新功能;
  • 原有功能有BUG;
  • 改善原有程序的结构;
  • 优化原有系统的性能 。

第一种和第二种动机,都是源于客户的功能需求,而第四种是源于客户的非功能需求。软件的外部质量,其衡量的标准就是客户对软件功能需求与非功能需求的满意度。它涉及到一个企业、一个软件的信誉度与生命力,因此为所有软件企业所高度重视。要提高软件内部质量,毫无疑问就是软件修改的第三个动机:改善原有程序的结构。它的价值是隐性的,并不体现在某一次或两次开发中,而是逐渐体现在日后长期维护的软件过程中。 高质量的软件,可以保证开发人员(即使是新手)能够轻易看懂软件代码,能够保证日后的每一次软件维护都可以轻易地完成(不论软件经历了多少次变更,维护了多少年),能够保证日后的每一次需求变更都能够轻易地进行(而不是伤筋动骨地大动)。要做到这几点其实并不容易,它需要我们持续不断地对系统内部质量进行优化与改进。这,就是系统重构的价值。 下面一个重要问题是哪些地方需要重构?有臭味道(Bad Smell)的代码。 什么是臭味道?想象一下你打开冰箱门,出来一股臭味道你就知道冰箱里有东西腐坏了,要清除了。代码一样有臭味道:

臭味行列中首当其冲的就是Duplicated Code(重复的代码)。如果你在一个以上的地点看到相同的程序结构,那么当可肯定:设法将它们合而为一,程序会变得更好。

  • 最单纯的Duplicated Code就是[同一个class内的两个方法含有相同表达式(expression)]。这时候你需要做的就是采用Extract Method提炼出重复的代码,然后让这两个地点都调用被提炼出来的那一段代码。
  • 另一种常见情况就是[两个互为兄弟(sibling)的subclasses内含有相同表达式]。要避免这种情况,只需要对两个classes都使用Extract Method,然后再对被提炼出的代码使用Pull Up Method,将它推入superclass内。
  • 如果代码之间只是类似,并非完全相同,那么就得运用Extract Method将相似部分和差异部分割开,构成单独一个方法。然后你可能发现或许可以运用Form Template Method获得一个Template Method设计模式。
  • 如果有些方法以不同的算法做相同的事,你可以择定其中较清晰的一个,并使用Substitute Algorithm将其它方法的算法替换掉。
  • 如果两个毫不相关的classes内出现Duplicaded Code,你应该考虑对其中一个使用Extract Class,将重复代码提炼到一个独立class中,然后在另一个class内使用这个新class。但是,重复代码所在的方法也可能的确只应该属于某个class,另一个class只能调用它,抑或这个方法可能属于第三个class,而另两个classes应该引用这第三个class。你必须决定这个方法放在哪儿最合适,并确保它被安置后就不会再在其它任何地方出现。

其他Bad Smell与相应的重构手法如下表所示:

Eclipse中Refactor菜单中的重构手法的应用时机如下图所示:

一个完整的重构流程包括:

  1. 从版本控制系统代码库中Check out code
  2. 读懂代码(包括测试代码)
  3. 发现bad smell
  4. Refactoring
  5. 运行所有的Unit Tests
  6. 往代码库中Check in code

 

(六)实践项目

此次试验中,我们结对的游戏项目是坦克大战

1.代码库地址:http://git.shiyanlou.com/shiyanlou5130

2.游戏界面如下:

20135130实验三报告_第13张图片20135130实验三报告_第14张图片20135130实验三报告_第15张图片

3.部分源代码如下:

子弹类:

import java.awt.Color; import java.awt.Graphics; import java.awt.Rectangle; import java.util.List;  /**   * 这个类用于生成子弹,及已控制子弹。   *   */ public class Bullet {

 private int x, y; //标记子弹的初始位置。  private static final int WIDTH = 10; //子弹的宽度。  private static final int HEIGHT = 10; //子弹的高度。    private Direction dir; //子弹的方向,与对应的坦克保持一致。  private static final int BSEEPD = 15; //子弹飞行的速度。  private TankClient tc; //持有对主程序的引用。    private boolean ilive; //子弹是否存活,活着为true,死了为false。  private boolean good; //子弹是否为自己的坦克所发射,是为true,不是为false。

 /**   * 子弹的构造函数,生成一颗按参数要求的子弹   */  public Bullet(int x, int y, boolean good, Direction dir, TankClient tc) {   this.x = x + 10;   this.y = y + 10;   this.good = good;   this.dir = dir;   this.ilive = true;   this.tc = tc;  }    /**   * 重写draw方法,当子弹状态为true时画一颗子弹,否则不画。   */  public void draw(Graphics g) {   if(!ilive) { //即子弹已失效,则移除子弹    tc.bullets.remove(this);    return;   }   Color c = g.getColor();   g.setColor(Color.red); //将子弹颜色写成红色   g.fillOval(x, y, 10, 10); //进行颜色填充   g.setColor(c);   move(); //移动子弹  }     /**   * 根据子弹的方向和速度进行移动。   */  public void move() { //根据方向修改子弹的位置   switch(dir) {   case L:    x -= BSEEPD; break;   case U:    y -= BSEEPD; break;   case R:    x += BSEEPD; break;   case D:    y += BSEEPD; break;   case STOP:    break;   }   if(x < 0 || y < 0 || x > TankClient.getGAME_WIDTH() || y > TankClient.getGAME_HEIGHT()) {    ilive = false;   }  }    /**   * 获得当前子弹对象的所占区域。   */  public Rectangle getRect() {   return new Rectangle(x, y, WIDTH, HEIGHT);  }    /**   * 判断子弹是否击中墙。   */  public boolean hitWall(Wall w) {   if(this.ilive&&this.getRect().intersects(w.getRect())) {    this.setIlive(false);    return true;   }   return false;  }    /**   * 判断是否击中所有墙中的任何一面墙,是返回true,否则返回false。   */  public boolean hitWalls(List<Wall> walls) {   for(int i=0; i<walls.size(); i++) {    Wall w = walls.get(i);    if(this.hitWall(w)) {     return true;    }   }   return false;  }    /**   * 判断是否击中坦克。   */  public boolean hitTank(Tank t) {   if(this.ilive&&this.getRect().intersects(t.getRect())&&t.isIlive()&&this.good!=t.isGood()) {  //通过判断子弹的区域和坦克的区域是否重合判断是否击中    if(t.isGood()) { //如果坦克是自己坦克     t.setLife(t.getLife()-25); //减25生命值     if(t.getLife()<=0) {      Explode e = new Explode(x, y, tc); //产生爆炸实例,并在主窗口显示      tc.explodes.add(e);      t.setIlive(false); //将坦克消灭     }    }    else {     Explode e = new Explode(x, y, tc);     tc.explodes.add(e);     t.setIlive(false);    }    ilive = false;       return true;   }   return false;  }    /**   * 判断是否击中坦克链表中的任何一辆,击中返回true,否则返回false。   */  public boolean hitTanks(List<Tank> tanks) {   for(int i=0; i<tanks.size(); i++) {    Tank t = tanks.get(i); //用t遍历所有坦克    if(this.hitTank(t)) {     return true;    }   }   return false;  }

 /**   * 获取ilive的值。   */  public boolean isIlive() {   return ilive;  }

 /**   * 设置ilive的值。   */  public void setIlive(boolean ilive) {   this.ilive = ilive;  }   }

坦克类:

import java.awt.Color; import java.awt.Graphics; import java.awt.Rectangle; import java.awt.event.KeyEvent; import java.util.List; import java.util.Random;

 /**   * 生成和控制坦克。   * @author carefree2005   *   */ public class Tank {

 private int x, y; //坦克的初始位置。  private static final int WIDTH = 30; //坦克的宽度。  private static final int HEIGHT = 30; //坦克的高度。    private static final int BSEEPD = 5; //敌人坦克的移动速度。  private static final int GSEEPD = 30; //我军坦克的移动速度。    private int oldX = 0; //用于记忆坦克上一步的x坐标。  private int oldY = 0; //用于记忆坦克上一步的y坐标。    private Direction dir = Direction.STOP; //坦克的行驶方向。    private Random r = new Random(); //随机数。  private int step = r.nextInt(80) + 3; //    private boolean good = true; //坦克是否为我军坦克,是为true,否则为false。  private boolean ilive; //坦克是否还活着,是为true,否则为false。    private int life = 100; //坦克的生命值。  private BloodBar bb = new BloodBar(); //用来显示坦克生命值的血条。    TankClient tc = null; //持有对主程序的引用。

 /**   * 新生成一辆坦克。   */  public Tank(int x, int y, boolean good) {   this.x = x;   this.y = y;   this.oldX = x;   this.oldY = y;   this.good = good;   if(good) {    dir = Direction.U;   }   else {    dir = Direction.D;   }   this.ilive = true;  }    /**   * Tank的另一个构造方法,生成一辆坦克。   */  public Tank(int x, int y, boolean good, TankClient tc) {   this(x, y, good);   this.tc = tc;  }    /**   * 根据坦克的方向和位置控制坦克的移动。   */  public void move() {   this.oldX = x;   this.oldY = y;   switch(dir) {   case L:    if((x - Tank.WIDTH)>0) {     if(!good) {      x -= BSEEPD; break;     }     else {      x -= GSEEPD; break;     }    }    else {     Direction[] dirs = Direction.values();     int rn = r.nextInt(dirs.length-1);     dir = dirs[rn];    }   case U:    if((y - Tank.HEIGHT)>0) {     if(!good) {      y -= BSEEPD; break;     }     else {      y -= GSEEPD; break;     }    }    else {     Direction[] dirs = Direction.values();     int rn = r.nextInt(dirs.length-1);     dir = dirs[rn];    }   case R:    if((x + Tank.WIDTH)<TankClient.getGAME_WIDTH()) {     if(!good) {      x += BSEEPD; break;     }     else {      x += GSEEPD; break;     }    }    else {     Direction[] dirs = Direction.values();     int rn = r.nextInt(dirs.length-1);     dir = dirs[rn];    }   case D:    if((y + Tank.HEIGHT)<TankClient.getGAME_HEIGHT()) {     if(!good) {      y += BSEEPD; break;     }     else {      y += GSEEPD; break;     }    }    else {     Direction[] dirs = Direction.values();     int rn = r.nextInt(dirs.length-1);     dir = dirs[rn];    }   case STOP:    break;   }      if(!good) {    Direction[] dirs = Direction.values();    if(step == 0) {     step = r.nextInt(80) + 3;     int rn = r.nextInt(dirs.length-1);     dir = dirs[rn];    }    step--;    if(r.nextInt(50)>47) {     this.fire(dir);    }   }  }    /**   * 画一辆坦克。   */  public void draw(Graphics g) {   if(!ilive) {    if(!good) {     tc.tanks.remove(this);     return;    }     }   Color c = g.getColor();   if(good) {    g.setColor(Color.RED);     }   else {    g.setColor(Color.BLUE);   }   g.fillRect(x, y, WIDTH, HEIGHT);   g.setColor(Color.BLACK);   switch(dir) {   case L:    g.fillRect(x-Tank.WIDTH/3, y+Tank.WIDTH/3, Tank.WIDTH/3, Tank.HEIGHT/3); break;   case U:    g.fillRect(x+Tank.WIDTH/3, y-Tank.WIDTH/3, Tank.WIDTH/3, Tank.HEIGHT/3); break;   case R:    g.fillRect(x+Tank.WIDTH, y+Tank.WIDTH/3, Tank.WIDTH/3, Tank.HEIGHT/3); break;   case D:    g.fillRect(x+Tank.WIDTH/3, y+Tank.WIDTH, Tank.WIDTH/3, Tank.HEIGHT/3); break;   }   g.setColor(c);   if(!good) {    move();   }   if(good) {    bb.draw(g);   }  }

 /**   * 监听键盘的按下按键动作。   */  public void keyPressed(KeyEvent e) {   int key = e.getKeyCode();   switch(key) {   case KeyEvent.VK_D:    dir = Direction.R; move(); break;   case KeyEvent.VK_S:    dir = Direction.D; move(); break;   case KeyEvent.VK_A:    dir = Direction.L; move(); break;   case KeyEvent.VK_W:    dir = Direction.U; move(); break;   case KeyEvent.VK_N:    superFire(); break;   case KeyEvent.VK_F1:    if(tc.getTankNum() == 20) tc.newGame(); break;   case KeyEvent.VK_1:    this.setIlive(true); this.setLife(100); break;   default :    break;   }    }    /**   * 监听键盘的松开按键动作。   */  public void keyReleased(KeyEvent e) {   int key = e.getKeyCode();   if(KeyEvent.VK_J == key) {    fire(dir);   }  }    /**   * 控制坦克开火。   */  public Bullet fire(Direction dir) {   if(!ilive) {    return null;   }   Bullet b = new Bullet(x, y, good, dir, tc);   tc.bullets.add(b);   return b;  }    /**   * 朝多个方向开火。   */  private void superFire() {   Direction[] dirs = Direction.values();   for(int i=0; i<4; i++) {    fire(dirs[i]);   }  }    /**   * 获取坦克的所占区域。   */  public Rectangle getRect() {   return new Rectangle(x, y, Tank.WIDTH, Tank.HEIGHT);  }    /**   * 判断坦克是否吃到血块。   */  public boolean eatBlood(Blood b) {   if(this.ilive && b.isIlive() && this.getRect().intersects(b.getRect())) {    this.setLife(100);    b.setIlive(false);    return true;   }   return false;  }    /**   * 坦克退回上一个位置。   */  public void stay() {   x = this.oldX;   y = this.oldY;  }    /**   * 坦克之间是否碰撞,是的话返回true,否则返回false。   */  public boolean collideTank(Tank t) {   if(this.ilive&&this.getRect().intersects(t.getRect())&&t.ilive) {    this.stay();    Direction[] dirs = Direction.values();    int rn = r.nextInt(dirs.length-1);    dir = dirs[rn];    return true;   }   return false;  }    /**   * 判断这辆坦克是否与其他坦克碰撞。   */  public boolean collideTanks(List<Tank> tanks) {   for(int i=0 ;i<tanks.size(); i++) {    Tank t = tanks.get(i);    if(this != t) {     if(this.collideTank(t)) {      return true;     }    }   }   return false;  }    /**   * 判断这辆坦克是否与墙碰撞。   * @param w 一个墙的实例。   * @return 碰撞了返回返回true,否则返回false。   */  public boolean collideWall(Wall w) {   if(this.ilive&&this.getRect().intersects(w.getRect())) {       this.stay();    Direction[] dirs = Direction.values();    int rn = r.nextInt(dirs.length-1);    dir = dirs[rn];    return true;   }   return false;  }    /**   * 判断这辆坦克是否与任何一面墙相碰撞。   */  public boolean collideWalls(List<Wall> walls) {   if(!good) {    for(int i=0; i<walls.size(); i++) {     Wall w = walls.get(i);     if(this.collideWall(w)) {      return true;     }    }    return false;   }   return false;  }

 /**   * 获取ilive的值。   */  public boolean isIlive() {   return ilive;  }

 /**   * 设置ilive的值。   */  public void setIlive(boolean ilive) {   this.ilive = ilive;  }

 /**   * 获取good的值。   */  public boolean isGood() {   return good;  }

 /**   * 设置good的值。   */  public void setGood(boolean good) {   this.good = good;  }

 /**   * 获取life的值。   */  public int getLife() {   return life;  }

 /**   * 设置life的值。   */  public void setLife(int life) {   this.life = life;  }    /**   * 生成一个表示生命值的血条。   * @author carefree2005   *   */  public class BloodBar {      /**    * 画一个血条。    */   public void draw(Graphics g) {    Color c = g.getColor();    g.setColor(Color.MAGENTA);    g.drawRect(x, y-25, Tank.WIDTH, 8);    int w = Tank.WIDTH*life/100;    g.setColor(Color.MAGENTA);    g.fillRect(x, y-25, w, 8);    g.setColor(c);   }  }   }

点击运行类:

import java.awt.Color; import java.awt.Frame; import java.awt.Graphics; import java.awt.Image; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Random; //import java.util.concurrent.*;

 /**   * 坦克大战的主程序,创建游戏窗口。   */ //建立一个窗口 public class TankClient extends Frame {    private static final long serialVersionUID = 1L; //序列号。    private static final int GAME_WIDTH = 600; //主窗口的宽度。  private static final int GAME_HEIGHT = 600; //主窗口的高度。    private int tankNum = 0;  //最初敌军坦克的数量。  private int score = 0; //最初得分    private Random r = new Random(); //随机数。    //private boolean flag = true;    Image offScreenImage = null;  //整个窗口图像的镜像。    Tank myT = new Tank(285, 570, true, this);  //一个自己坦克的实例。    List<Tank> tanks = new LinkedList<Tank>();  //存储敌人坦克的链表。  List<Bullet> bullets = new ArrayList<Bullet>(); //存储子弹的链表。  List<Explode> explodes = new ArrayList<Explode>(); //存储爆炸的链表。  List<Wall> walls = new ArrayList<Wall>();  //存储墙的链表。  List<Blood> bloods = new ArrayList<Blood>(); //存储血块的链表。

 /**   * 重写父类的paint方法,画出所有窗口中的事物(non-Javadoc)。   * @see java.awt.Container#paint(java.awt.Graphics)   */  public void paint(Graphics g) {       score = (tankNum-tanks.size())*10;      g.drawString("还剩下坦克:" + (20-tankNum+tanks.size()), 495, 50); //在图的这些位置显示当前剩余坦克数量和得分   g.drawString("你的得分为:" + score, 495, 60);   if((20-tankNum+tanks.size())==0) {    g.drawString("恭喜你过关了!", 280, 290);   }   //bullets是存储子弹的链表,b是每次从中提取的元素,是Bullet类的一个实例   for(int i=0; i<bullets.size(); i++) {    Bullet b = bullets.get(i);    b.hitTanks(tanks); //判断子弹是否击中敌坦克    b.hitTank(myT); //判断子弹是否击中己方坦克    b.hitWalls(walls); //判断子弹是否击中墙    b.draw(g); //根据返回值重画图   }      for(int i=0; i<explodes.size(); i++) {    Explode e = explodes.get(i);    e.draw(g);   }      for(int i = 0; i<tanks.size(); i++) {    Tank t = tanks.get(i);    t.collideWalls(walls); //判断坦克是否撞墙及撞坦克    t.collideTanks(tanks);    t.collideTank(myT);    t.draw(g);         }      for(int i=0; i<walls.size(); i++) {    Wall w = walls.get(i);    w.draw(g);   }      for(int i=0; i<bloods.size(); i++) {    Blood b = bloods.get(i);    myT.eatBlood(b);    b.draw(g);   }      if(myT.isIlive()) {    myT.draw(g);   }  }    /**   * 重写父类的update方法,生成镜像图片,实现双缓冲(non-Javadoc)   * @see java.awt.Container#update(java.awt.Graphics)   */  public void update(Graphics g) {   if(offScreenImage == null) {    offScreenImage = this.createImage(GAME_WIDTH, GAME_HEIGHT);   }   Graphics gOffScreen = offScreenImage.getGraphics();   Color c = gOffScreen.getColor();   gOffScreen.setColor(Color.lightGray);   gOffScreen.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT);   gOffScreen.setColor(c);   paint(gOffScreen);   g.drawImage(offScreenImage, 0, 0, null);  }

 /**   * 生成主窗口,并启动监听线程。   */  public void launchFrame() {   //生成边框的墙   for(int i=0; i<5; i++) {    walls.add(new Wall(220+30*i, 200));   }   for(int i=0; i<5; i++) {    walls.add(new Wall(120+30*i, 400));   }   for(int i=0; i<5; i++) {    walls.add(new Wall(400, 300+30*i));   }   this.setSize(GAME_WIDTH, GAME_HEIGHT);   this.setLocation(150, 100);   this.setBackground(Color.lightGray);   this.setTitle("坦克大战5130vs5104版");   this.setVisible(true);   this.setResizable(false); //设置为不可调大小   Thread t1 = new Thread(new RePaintThread());   Thread t2 = new Thread(new NewTankThread());   t1.start();   t2.start();      if(this.getTankNum()==20&&this.tanks.size()==0) {    t1.interrupt();    t2.interrupt();   }  }    public void Monitor() {   this.addWindowListener(new WindowAdapter() {    public void windowClosing(WindowEvent e) {     setVisible(false);     System.exit(0);    }   });   this.addKeyListener(new KeyMonitor());  }

 /**   * 内部类,实现窗口画面的更新重画。   */  private class RePaintThread implements Runnable {

  TankClient tc = new TankClient();   public void run() {    while((20-tankNum+tanks.size()) != 0) {     repaint();     try {      Thread.sleep(50);     } catch (InterruptedException e) {      e.printStackTrace();     }    }    repaint();    try {     this.finalize();    } catch (Throwable e) {     e.printStackTrace();    }   }   }    /**   * 生成一辆敌军坦克。   */  public void newTank() {   tanks.add(new Tank(5+280*(tankNum%3), 30, false, this));  }    /**   * 生成一个血块。   */  public void newBlood() {   bloods.add(new Blood());  }    /**   * 内部类,用于不断的生成敌军坦克。   */  private class NewTankThread implements Runnable {

  TankClient tc = new TankClient();   public void run() {    while(tankNum<20) {     if(r.nextInt(50) > 46) {      newBlood();     }     //即图中至少有4辆坦克同时跑     if(tanks.size()<4) {      newTank();      tankNum++;     }     try {      Thread.sleep(2000);     } catch (InterruptedException e) {      e.printStackTrace();     }    }    try {     this.finalize();    } catch (Throwable e) {     // TODO Auto-generated catch block     e.printStackTrace();    }   }  }    /**   * 内部类,监听键盘。   */  private class KeyMonitor extends KeyAdapter {      public void keyReleased(KeyEvent e) {    myT.keyReleased(e);   }

  public void keyPressed(KeyEvent e) {    myT.keyPressed(e);   }  }    /**   * main方法,启动游戏。   */  public static void main(String[] args) {   TankClient tc = new TankClient();   tc.Monitor();   tc.launchFrame();    }

 /**   * 获取主窗口的宽度。   */  public static int getGAME_WIDTH() {   return GAME_WIDTH;  }    /**   * 获取主窗口的高度。   */  public static int getGAME_HEIGHT() {   return GAME_HEIGHT;  }

 /**   * 获取敌军坦克的数量。   */  public int getTankNum() {   return tankNum;  }

 /**   * 设置敌军坦克的数量。   */  public void setTankNum(int tankNum) {   this.tankNum = tankNum;  }

 public int getScore() {   return score;  }

 public void setScore(int score) {   this.score = score;  }

 public void newGame() {     this.setTankNum(0);   this.setScore(0);   myT = null;   myT = new Tank(285, 570, true, this);   this.launchFrame();   }  }

其余类的代码保留

游戏的5个重构:

20135130实验三报告_第16张图片

20135130实验三报告_第17张图片

20135130实验三报告_第18张图片

20135130实验三报告_第19张图片

20135130实验三报告_第20张图片

20135130实验三报告_第21张图片

20135130实验三报告_第22张图片

20135130实验三报告_第23张图片

20135130实验三报告_第24张图片

20135130实验三报告_第25张图片

20135130实验三报告_第26张图片

20135130实验三报告_第27张图片

20135130实验三报告_第28张图片

游戏的TDD如下:

程序单元描述

游戏程序目前主要分为一下几个单元:

1. 游戏主面板类:包括所有游戏元素在窗口的绘制显示,实现双缓冲,事件监听处理等,面板重绘,各个对象的实例化操作。

2. 坦克类:游戏中所有坦克对象

属性

1)坦克的好坏

2)生命值

3)移动速度

4)坦克高度和宽度

5)坦克起始坐标

6)坦克的起始方向

以及一些方法

1)坦克的初始化

2)绘制坦克

3)控制坦克移动

4)确定坦克的方向

5)坦克开火

6)碰撞检测

7)坦克撞墙检测

8)坦克发射超级炮弹

还包括一个内部类

1)血条类

3. 子弹类:描述坦克打出的子弹的属性等 属性

1)子弹的速度

2)子弹宽高

3)子弹生存情况

4)子弹的“好坏

5)子弹起始坐标及方向

一些方法

1)子弹的初始化方法

2)子弹的绘制

3)子弹的移动

4)击中坦克的检测

5)击中墙的检测

4. 爆炸类:描述子弹击中坦克产生的爆炸

属性

1)爆炸产生的坐标

2)爆炸的存在情况

一些方法

1)初始化

2)绘制爆炸

5. 墙类:描述游戏场景中阻挡坦克的墙

属性

1)墙的坐标

2)主面板的引用

一些方法

1)初始化

2)绘制墙

3)检测碰撞

利用junit对目标类Tank.java生成TankTest.java测试类 自动生成测试代码,修改部分代码进行测试,如下图:

 20135130实验三报告_第29张图片

20135130实验三报告_第30张图片

20135130实验三报告_第31张图片

20135130实验三报告_第32张图片

20135130实验三报告_第33张图片

20135130实验三报告_第34张图片

20135130实验三报告_第35张图片

20135130实验三报告_第36张图片

20135130实验三报告_第37张图片

20135130实验三报告_第38张图片

20135130实验三报告_第39张图片

总结与体会

PSP时间:

                                                                

步骤

耗时

百分比

需求分析

40

11%

设计

50

14%

代码实现

180

50%

测试

60

17%

分析总结

30

8%

 

体会:通过结对项目,我认识到了合作的重要性,紧密的合作能够提高我们的能力。代码测试过程中出现很多错误,但经过互相的合作和探讨,加以改进,便成功运行

通过本次实验,学会了如何使用github来管理代码。如果是开源的项目,通过网站托管方式进行统一管理,当然是非常棒的,并且有很多功能,如果不能开源,仅搭建内部Git服务器进行配置管理,团队管理部分的功能就需要自己开发,相比其他配置工具,在这方面没有太大的优势,当然在各大开源网站上已经有大量这方面的实现共享出来。

同时通过结对项目,我认识到了合作的重要性,紧密的合作能够提高我们的能力。代码测试过程中出现很多错误,但经过互相的合作和探讨,加以改进,便成功运行

 

结对伙伴:20135104刘帅

       博客地址 http://www.cnblogs.com/dvew/ 

你可能感兴趣的:(20135130实验三报告)