Warship v2.0(二)
文章来源:《Head First Java》修炼感悟。
上一篇,老白介绍了 Java 的「黑科技」- API。 为什么提及它,因为我们在 API 中找到了更适合 Warship v2.0 项目的工具 ArrayList,本篇文章就为大家详细分析一下使用数组列表替代传统数组的种种便利。 老白希望借此帮助师兄们正确使用 ArrayList 对象,助力你们的创作灵感!
任务进度:
- 解决 v1.0 遗留问题
- 分析 v2.0 的设计思路及实现技术
- 重新设计的 Warship 战舰类
- 更加专业的 WarshipManager 调度类
- 功能更强的 GameHelper 辅助类
任务一、解决 v1.0 遗留问题
v1.0 结束时,老白曾提出「重复攻击」的问题,不知道大家是如何解决的? 一般来说,解决某个 Bug 的方式因人而异,但修为高深的玩家可能会以更合理的方式去解决。
1、分析产生 BUG 的原因
- 错误逻辑:如果玩家重复攻击战舰的某个格子,同样判为 kill。
- 正常逻辑:玩家必须击中战舰的不同位置,才会判为 kill。
出现这个错误,是因为下面这段代码出了问题:
java
// 代码位置:Warship 类的 returnFireResult() 方法
for (int cell : cellsLocation) {
if (loc == cell) { // 注意:这条判断语句是产生 Bug 的「罪魁祸首」
result = "hit"; // 击中
hitPoints--; // 减掉1点生命值
break; // 中断循环
}
}
上面代码片段中的判断条件 if (loc == cell)
不严谨。 它只能判断出两个格子是否相等,并不能判断出两个格子是否重复。 所以即便是重复攻击某一个格子,依然会叠加攻击效果,达到 kill 条件。
2、寻找解决此 BUG 的最优方案
知道 Bug 产生的原因,解决起来就相对简单了。 老白想到了以下三种方式,大家看看哪种方式更优。
- 新创建一个 boolean 数组,如果玩家击中格子,就把新数组对应位置设为 true。 每次攻击时,同时检查新数组对应格子的状态,忽略重复击中的格子。
- 依然使用原来的位置数组,如果有格子被击中则把这个格子设置为
-1
,下次击中时忽略-1
的格子,只检查非负格子就可以了。 - 位置数组及时删除命中的格子,数组也随之变小,直至成为空数组。
老白认为,第一种方式效率太低,检查的项目太多,不适合。 第二种方式要好很多,但也得遍历所有格子,还是做了无用功。 第三种方式很好,并且提到的数组好像和 ArrayList 很像,对不对? 看来有必要使用 ArrayList 来尝试一次。
3、ArrayList 的首次尝试
对 ArrayList 有了初步了解后,我们来尝试一下能带来哪些便利。 找出前面写的 Warship.java 文件,把其中的数组对象改成数组列表,看下面代码:
java
/**
* 文件:Warship.java
* 描述:战舰类,主要提供自身坐标设置、攻击检测等功能。
* 版本:v2.0
*/
public class Warship {
// 用来保存战舰的生命点数
// private int hitPoints;
// 用来保存战舰格子坐标的列表
// private int[] cellsLocation;
private ArrayList<String> cellsLocation;
// 设置战舰格子坐标的数组
public void setCellsLocation(/*int[]*/ ArrayList<String> locs) {
this.cellsLocation = locs;
// this.hitPoints = locs.length; // 生命点数赋值
}
// 检查指定格子是否被击中,然后返回攻击结果
public String returnFireResult(String /*hitLoc*/ userInput) {
// 把字符串解析为整型数字,不要问如何做到的
// int loc = Integer.parseInt(hitLoc);
// 用于保存每次攻击的效果,默认为「未击中」
String result = "miss";
// 查询列表中是否存在玩家输入的位置,如果没有则返回 -1
int index = cellsLocation.indexOf(userInput);
// 如果索引值大于或者等于零,表示本次攻击命中,删除命中位置,
// 然后检查列表是否为空,根据检查结果对变量 result 赋值
if (index >= 0) {
cellsLocation.remove(index);
if (cellsLocation.isEmpty()) {
result = "kill";
} else {
result = "hit";
}
}
// for 循环,自身所有格子与玩家猜测的位置逐个进行比较,
// 如果某个格子与玩家猜测值相等,则表示那个格子被玩家击中,
// 生命值就会减掉1点,然后中断循环,忽略后面的比较。
// for (int cell : cellsLocation) {
// if (loc == cell) {
// result = "hit"; // 击中
// hitPoints--; // 减掉1点生命值
// break; // 中断循环
// }
// }
// 判断是否被击杀
// if (hitPoints < 1) {
// result = "kill";
// }
// 返回本次攻击结果
System.out.println(result);
return result;
}
}
老白把旧代码做了注释,看起来很乱,但如果去掉注释,你会发现代码逻辑是如此清晰、整洁。 这次修改了以下内容:
- 取消了变量
hitPoints
,原先用它记录战舰的生命值; - 变量
cellsLocation
由数组类型改为 ArrayList<String> 类型; - 方法
setCellsLocation()
的参数类型,由数组改为 ArrayList<String>; - 方法
returnFireResult()
的参数名称,由hitLoc
改为userInput
; - 增加了局部变量
index
,用来记录列表索引值,兼做判断是否命中;
下面来看看 ArrayList 是如何进行命中检测的:
java
// 查询列表中是否存在玩家输入的位置,如果没有则返回 -1
int index = cellsLocation.indexOf(userInput);
我们用到了 ArrayList 对象的 indexOf()
方法,用来查询玩家输入的位置是否在列表中。 如果返回 -1
表示列表中没有玩家输入的位置,即没有命中。
再来看看命中检测的代码:
java
// 如果索引值大于或者等于零,表示本次攻击命中,删除命中位置,
// 然后检查列表是否为空,根据检查结果对变量 result 赋值
if (index >= 0) {
cellsLocation.remove(index);
if (cellsLocation.isEmpty()) {
result = "kill";
} else {
result = "hit";
}
}
上面的代码逻辑清晰,很容易理解是不是? 要注意的是,每执行一次 remove()
方法,都会使 cellsLocation
自动缩小,直至列表完全为空。
目前,Warship 类暂时还不能使用,后续我们还会陆续添加一些新内容。 下一篇主要分析一下 v2.0 设计思路以及实现的关键技术,请保持关注。
《 上一篇 Warship v2.0(一) | 下一篇 Warship v2.0(三) |
---|