北京电子科技学院(BESTI)
实 验 报 告
课程:Java 班级: 1351 姓名:王川东 学号:20135130
成绩: 指导教师:娄嘉鹏 实验日期:2015.5.8
实验密级: 预习程度: 实验时间:15:30~18:00
仪器组次:20 必修/选修:选修 实验序号:02
实验名称:敏捷开发与XP实践
实验仪器:
名称 |
型号 |
数量 |
计算机 |
lenovo |
1 |
实验楼 |
|
1
|
软件工程是把系统的、有序的、可量化的方法应用到软件的开发、运营和维护上的过程。软件工程包括下列领域:软件需求分析、软件设计、软件构建、软件测试和软件维护。 人们在开发、运营、维护软件的过程中有很多技术、做法、习惯和思想体系。软件工程把这些相关的技术和过程统一到一个体系中,叫“软件开发流程”。软件开发流程的目的是为了提高软件开发、运营、维护的效率,并提高软件的质量、用户满意度、可靠性和软件的可维护性。 光有各种流程的思想是不够的,我们还要有一系列的工具来保证这些思想能够在实践中有效率地运作。软件开发很重要的一点不是看你能对多少理论讲的头头是道,还要看你对相关工具应用的如何,比如Java中单元测试要和JUnit的应用结合起来,建模要和Umbrello或StarUML的应用结合起来。编程学习是一个习而学
的过程。 一个常见的公式是:软件工程=开发流程+工具
邹欣老师给出的两个公式:软件=程序+软件工程
和软件企业=软件+商业模式
开发流程大家可以参考学习邹欣老师的软件团队和开发流程。常见的开发流程有:
敏捷开发(Agile Development)是一种以人为核心、迭代、循序渐进的开发方法。“敏捷流程”是一系列价值观和方法论的集合。从2001年开始,一些软件界的专家开始倡导“敏捷”的价值观和流程,他们肯定了流行做法的价值,但是强调敏捷的做法更能带来价值。
敏捷开发包括很多模式:
其中,极限编程(eXtreme Programming,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中的一般的命名规则有:
标识符名字应当直观且可以拼读,可望文知意,不必进行“解码”,一般采用英文单词或其组合,便于记忆和阅读,切忌使用汉语拼音来命名,用词要准确例如“当前值”应该起名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中的重要实践。在结对编程模式下,一对程序员肩并肩、平等地、互补地进行开发工作。他们并排坐在一台电脑前,面对同一个显示器,使用同一个键盘、同一个鼠标一起工作。他们一起分析,一起设计,一起写测试用例,一起编码,一起做单元测试,一起做集成测试,一起写文档等。 结对编程中有两个角色:
如何结对编程,为何要结对编程,大家参考一下结对编程和两人合作 ,重点是:
团队精神是好多地方都强调的一个精神,最小的团队就是一对一的二人团队了,培养团队精神从结对编程开始吧。社会生活中人与人相处最重要的是诚信,有同理心,互利。结对编程中大家会出现分歧,如何更有效地合作要做到对事不对人
,掌握这些是可以终生受益的,如何影响小伙伴,大家参考一下两人合作:要会做汉堡包。
XP的集体所有制意味着每个人都对所有的代码负责;这一点,反过来又意味着每个人都可以更改代码的任意部分。结对编程
对这一实践贡献良多:借由在不同的结对中工作,所有的程序员都能看到完全的代码。集体所有制的一个主要优势是提升了开发程序的速度,因为一旦代码中出现错误,任何程序员都能修正它。 这意味着代码要放到一个大家都能方便获取的地方,我们叫代码仓库。这引出另外一个话题叫版本控制(Version Control)。
不论是对于团队还是个体,版本控制都提供了很多好处。
流行的版本控制工具有CVS,SVN,Git等,更多的可以参考这里。Git是Linus除了Linux操作系统外的另外一个重要发明。
首先开通我的代码库功能。
实验截图如下:
我们先看看重构的概念:
重构(Refactor),就是在不改变软件外部行为的基础上,改变软件内部的结构,使其更加易于阅读、易于维护和易于变更 。
重构中一个非常关键的前提就是“不改变软件外部行为”,它保证了我们在重构原有系统的同时,不会为原系统带来新的BUG,以确保重构的安全。如何保证不改变软件外部行为?重构后的代码要能通过单元测试。如何使其更加易于阅读、易于维护和易于变更?设计模式给出了重构的目标。
重构重要吗?你看看Eclipse菜单中有个refactor
菜单就知道了,重构几乎是现代IDE的标配了
我们在编码标准
中说“给标识符命名”是程序员一项重要技能,以前没有这个意识,现在知道了怎么办?没问题,上图中重构的第一项功能就是Rename
,可以给类、包、方法、变量改名字。
修改方法是,用鼠标单击要改的名字,选择Eclipse中菜单中的Refactor
->Rename...
:
学过C语言的学生学Java时常犯的毛病是不会封装,该用类的地方都用了结构体。比如要定义一个类Student
,会出现这样的代码
Eclipse中菜单中的Refactor
->Encapsulate Field...
注意分析一下重构前后的代码变化:
同样可以封装id
和age
两个成员变量,结果如下:
每次打印学生信息都这么写代码违反了DRY原则,造成代码重复,正常的重构可以使用Eclipse中的Extract Method...
由于Java中所有的类都有个专门的toString方法,我们使用Eclipse中Source
->Generate toString()...
给Student
类产生一个toString
方法
修改main的代码,结果如下:
(以下为阅读了解的)
我们要修改软件,万变不离其宗,无非就是四种动机:
第一种和第二种动机,都是源于客户的功能需求,而第四种是源于客户的非功能需求。软件的外部质量,其衡量的标准就是客户对软件功能需求与非功能需求的满意度。它涉及到一个企业、一个软件的信誉度与生命力,因此为所有软件企业所高度重视。要提高软件内部质量,毫无疑问就是软件修改的第三个动机:改善原有程序的结构。它的价值是隐性的,并不体现在某一次或两次开发中,而是逐渐体现在日后长期维护的软件过程中。 高质量的软件,可以保证开发人员(即使是新手)能够轻易看懂软件代码,能够保证日后的每一次软件维护都可以轻易地完成(不论软件经历了多少次变更,维护了多少年),能够保证日后的每一次需求变更都能够轻易地进行(而不是伤筋动骨地大动)。要做到这几点其实并不容易,它需要我们持续不断地对系统内部质量进行优化与改进。这,就是系统重构的价值。 下面一个重要问题是哪些地方需要重构?有臭味道(Bad Smell)的代码。 什么是臭味道?想象一下你打开冰箱门,出来一股臭味道你就知道冰箱里有东西腐坏了,要清除了。代码一样有臭味道:
臭味行列中首当其冲的就是Duplicated Code(重复的代码)。如果你在一个以上的地点看到相同的程序结构,那么当可肯定:设法将它们合而为一,程序会变得更好。
Duplicated Code
就是[同一个class内的两个方法含有相同表达式(expression)]。这时候你需要做的就是采用Extract Method
提炼出重复的代码,然后让这两个地点都调用被提炼出来的那一段代码。Extract Method
,然后再对被提炼出的代码使用Pull Up Method
,将它推入superclass内。Extract Method
将相似部分和差异部分割开,构成单独一个方法。然后你可能发现或许可以运用Form Template Method
获得一个Template Method
设计模式。Substitute Algorithm
将其它方法的算法替换掉。Duplicaded Code
,你应该考虑对其中一个使用Extract Class
,将重复代码提炼到一个独立class中,然后在另一个class内使用这个新class。但是,重复代码所在的方法也可能的确只应该属于某个class,另一个class只能调用它,抑或这个方法可能属于第三个class,而另两个classes应该引用这第三个class。你必须决定这个方法放在哪儿最合适,并确保它被安置后就不会再在其它任何地方出现。其他Bad Smell
与相应的重构手法如下表所示:
Eclipse中Refactor
菜单中的重构手法的应用时机如下图所示:
一个完整的重构流程包括:
(六)实践项目
此次试验中,我们结对的游戏项目是坦克大战
1.代码库地址:http://git.shiyanlou.com/shiyanlou5130
2.游戏界面如下:
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个重构:
游戏的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测试类 自动生成测试代码,修改部分代码进行测试,如下图:
总结与体会
PSP时间:
步骤 |
耗时 |
百分比 |
需求分析 |
40 |
11% |
设计 |
50 |
14% |
代码实现 |
180 |
50% |
测试 |
60 |
17% |
分析总结 |
30 |
8% |
体会:通过结对项目,我认识到了合作的重要性,紧密的合作能够提高我们的能力。代码测试过程中出现很多错误,但经过互相的合作和探讨,加以改进,便成功运行
通过本次实验,学会了如何使用github来管理代码。如果是开源的项目,通过网站托管方式进行统一管理,当然是非常棒的,并且有很多功能,如果不能开源,仅搭建内部Git服务器进行配置管理,团队管理部分的功能就需要自己开发,相比其他配置工具,在这方面没有太大的优势,当然在各大开源网站上已经有大量这方面的实现共享出来。
同时通过结对项目,我认识到了合作的重要性,紧密的合作能够提高我们的能力。代码测试过程中出现很多错误,但经过互相的合作和探讨,加以改进,便成功运行
结对伙伴:20135104刘帅
博客地址 http://www.cnblogs.com/dvew/