特别说明:由于第五次作业不在迭代开发范围内,因此没有解析文档和笔记。
📢完整的题目要求和源代码请按下面指示前往我的Github仓库~

BUAA-2023-OOpre

题目信息

第六次作业指导书

第零部分:提交要求 && Junit要求

请保证提交项目的顶层目录存在两个文件夹:srctest(命名需严格与此保持一致),请将作业的功能代码存放于src文件夹下,同时将相关junit测试类代码文件存放于test文件夹下,以保证评测的正常进行(评测时只会针对src目录下的文件进行程序功能的评测以及代码风格检测,也就是说,test目录下的junit测试代码风格不会被检测)。参考目录结构如下:

1
2
3
4
5
6
7
8
|-src
|- Bottle.java
|- Equipment.java
|- ...
|-test
|- BottleTest.java
|- EquipmentTest.java
|- ...

本次作业,要求Junit测试覆盖率保证method >= 90%line >= 60%branch >= 60%。(idea显示的覆盖率和评测测到的覆盖率可能略有差别,请同学们以评测为准。同时请不要使用assert进行断言以免造成不必要的覆盖率损失)

第一部分:训练目标

  • 掌握继承以及接口的使用

第二部分:题目描述

背景

本次作业将继续基于第四次作业的“冒险者游戏”进行迭代开发,同学们应当在实现前序题目所要求内容的前提下基于前序作业的代码完成本次作业。

在本任务中,我们允许冒险者雇佣另一个冒险者,且赋予装备、药水瓶、食物、冒险者价值的概念,把装备、药水瓶、食物和冒险者都看作是价值体 commodity。另一方面,我们细化装备药水瓶的类型,使不同类型的装备药水瓶具有不同的效果。

细节如下:

  • 增加 Commodity 接口,并使冒险者 Adventurer 类、装备 Equipment 类、药水瓶 Bottle 类、食物 Food 类实现 Commodity 接口。接口中应定义实现该接口的所有类的共有方法。
  • 定义冒险者的价值为其拥有的所有价值体的价值之和,即冒险者的价值是其装备食物药水瓶的价值及其雇佣的冒险者的价值的和。(拥有即可,不需要携带,药水瓶为空仍具有价值)
  • 增加冒险者之间的雇佣关系:冒险者 A 雇佣冒险者 B,可以认为是把冒险者 B 看成一个价值体,且该价值体被冒险者A所持有。
  • 将装备和药水瓶分别分为三个子类型,建议通过继承实现。

关于不同类型药水瓶的具体说明

类型type 其他属性others 意义
RegularBottle 常规药水瓶,按原先方式计算
ReinforcedBottle ratio 百分比强化药水,使用该药水增加的体力值为 [(1+ratio)*capacity],[x]表示不大于x的最大整数
RecoverBottle ratio 百分比恢复药水,使用该药水增加的体力值为 [ratio*冒险者使用前HitPoint],[x]表示不大于x的最大整数

关于不同类型装备的具体说明

类型type 其他属性others 意义
RegularEquipment 常规装备,按原先方式计算HitPoint_decrease = star * level
CritEquipment critical 暴击装备,受攻击者损失生命值为HitPoint_decrease = star * level + critical
EpicEquipment ratio 史诗装备,受攻击者损失生命值为HitPoint_decrease = [受攻击者被攻击前HitPoint * ratio],[x]表示不大于x的最大整数

操作要求

在本次作业中,初始时,你没有需要管理的冒险者,我们通过若干条操作指令来修改当前的状态:

新增指令18~21,指令2、4、7的输入格式有变,其余指令若无特殊说明,则要求和限制同第四次作业

加入一个需要管理的冒险者(新加入的冒险者不携带任何药水瓶、食物和装备,并且等级为 $1$,初始体力为 $500$)

给某个冒险者增加一个药水瓶

删除某个冒险者的某个药水瓶

给某个冒险者增加一个装备

删除某个冒险者的某个装备

给某个冒险者的某个装备提升一个星级(星级加1)

给冒险者增加一个食物

删除冒险者的一个食物

冒险者尝试携带他拥有的某件装备

冒险者尝试携带他拥有的某个药水瓶

冒险者尝试携带他拥有的某个食物

冒险者使用某个药水瓶

冒险者使用某个食物

指定部分冒险者进入战斗模式及战斗模式中所发生的事件

查询战斗模式下某日期发生的事件

查询战斗模式下某冒险者发起的攻击

查询战斗模式下某冒险者受到的攻击

一个冒险者雇佣另一个冒险者

查询冒险者所拥有的价值体数量、价值体的价值总和

查询冒险者所拥有的价值体中价值的最大值

查询冒险者对应价值体的属性

输入格式

第一行一个整数 n,表示操作的个数。

接下来的 n 个指令,每条指令占一行,是一个形如 {type} {attribute} 的操作,{type}{attribute} 间、若干个 {attribute} 间使用若干个空格分割,操作输入形式及其含义如下。战斗日志的内容同样每条占一行,但是注意战斗日志内容不属于指令,指令数目n中不包含战斗日志占有的行数

操作14中,除了本身的指令占一行外,其余的fightLog每条占一行。保证操作14除了fightLog外所有{type} {attribute}均在一行内

具体要求表
typeattribute意义输出(每条对应占一行)
1{adv_id} {name}加入一个 ID 为 {adv_id}、名字为 {name} 的冒险者 (新加入的冒险者不携带任何瓶子和装备,并且等级为1,初始血量为500)
2{adv_id} {bot_id} {name} {capacity} {price} {type} {others}给 ID 为 {adv_id} 的冒险者增加一个药水瓶,药水瓶的 ID、名字、容量、价值、类型、其他属性分别为 {bot_id}{name}{capacity}{price}{type}{others},关于其他属性在上文有具体定义,且默认为已装满(isEmpty==false)
3{adv_id} {bot_id}将 ID 为 {adv_id} 的冒险者的 id 为 {bot_id} 的药水瓶删除{一个整数} {一个字符串},整数为删除后冒险者药水瓶数目,字符串为删除的药水瓶的name
4{adv_id} {equ_id} {name} {star} {price} {type} {others}给 ID 为 {adv_id} 的冒险者增加一个装备,装备的 ID、名字、星级、价值、类型、其他属性分别为 {equ_id}{name}{star}{price}{type}{others},关于其他属性在上文有具体定义
5{adv_id} {equ_id}将 ID 为 {adv_id} 的冒险者的 id 为 {equ_id} 的装备删除{一个整数} {一个字符串},整数为删除后冒险者装备数目,字符串为删除的装备的name
6{adv_id} {equ_id}将 ID 为 {adv_id} 的冒险者的 id 为 {equ_id} 的装备提升一个星级{一个字符串} {一个整数},字符串为装备的name,整数为装备升星后的星级
7{adv_id} {food_id} {name} {energy} {price}给 ID 为 {adv_id} 的冒险者增加一个食物,食物的 ID、名字、能量、价值分别为 {food_id}{name}{energy}{price}
8{adv_id} {food_id}将 ID 为 {adv_id} 的冒险者的 id 为 {food_id} 的食物删除{一个整数} {一个字符串},整数为删除后冒险者食物数目,字符串为删除的食物的name
9{adv_id} {equ_id}ID 为 {adv_id} 的冒险者尝试携带ID为 {equ_id} 的装备
10{adv_id} {bot_id}ID 为 {adv_id} 的冒险者尝试携带ID为 {bot_id} 的瓶子
11{adv_id} {food_id}ID 为 {adv_id} 的冒险者尝试携带ID为 {food_id} 的食物
12{adv_id} {name}ID 为 {adv_id} 的冒险者尝试使用名字为{name}的药水瓶成功:{一个整数A} {一个整数B},整数A为该被使用药水瓶的id,整数B为该冒险者使用该药水瓶后的体力值
失败: fail to use {name}(其中name为输入中的name)
13{adv_id} {name}ID 为 {adv_id} 的冒险者尝试使用名字为{name}的食物成功:{一个整数A} {一个整数B},整数A为该食物的id,整数B为该冒险者使用该食物后的等级
失败:fail to eat {name}(其中name为输入中的name)
14m k {adv_name_1} {adv_name_2}{adv_name_m}m 为进入战斗模式的人数,k 为此次战斗模式下战斗日志的条数,name 为 {adv_name_j} 的冒险者进入战斗模式,(m、k和name在一行)接下来的 k 行均为战斗日志首先第一行输出 Enter Fight Mode ,接下来 k 行输出 k 条战斗日志的反馈,参见下文关于战斗日志输出的表格
15YYYY/MM查询在 YYYY/MM 发生的有效战斗日志按输入顺序每条日志输出一行:使用药水:YYYY/MM {adv_name_1} used {name}、一对一攻击:YYYY/MM {adv_name_1} attacked {adv_name_2} with {name}、一对多攻击:YYYY/MM {adv_name_1} AOE-attacked with {name}。如果不存在符合条件的日志,输出No Matched Log
16adv_id查询 id 为 adv_id 的冒险者在战斗模式下作为攻击者的有效战斗日志按输入顺序每条日志输出一行:一对一攻击:YYYY/MM {adv_name_1} attacked {adv_name_2} with {name}、一对多攻击:YYYY/MM {adv_name_1} AOE-attacked with {name}。如果不存在符合条件的日志,输出No Matched Log
17adv_id查询 id 为 adv_id 的冒险者在战斗模式下作为被攻击者的有效战斗日志按输入顺序每条日志输出一行:一对一攻击:YYYY/MM {adv_name_1} attacked {adv_name_2} with {name}、一对多攻击:YYYY/MM {adv_name_1} AOE-attacked with {name}。如果不存在符合条件的日志,输出No Matched Log
18{adv_id1} {adv_id2}ID 为adv_id1的冒险者雇佣 ID 为adv_id2的冒险者,对于多次出现的雇佣关系仅算作一次雇佣
19{adv_id}查询 ID 为 {adv_id} 的冒险者所持有价值体的数量与价值之和
如果价值体是装备、药水瓶或食物,则价值就是 price,对数量的贡献是 1
如果价值体是冒险者,则其价值计算按照本次作业最开始定义的规则,对数量的贡献也是 1,不需要考虑被雇佣冒险者所拥有的其他价值体
{一个整数 A} {一个整数 B},整数 A 表示某冒险者所拥有价值体数量,整数 B 表示某人所有价值体的价值总和
20{adv_id}查询 ID 为 {adv_id} 的冒险者所持有价值体的价值的最大值
如果价值体是装备、药水瓶或食物,则价值就是 price
如果价值体是冒险者,则其价值计算按照本次作业最开始定义的规则
{一个整数},表示该冒险者所持有价值体的价值的最大值,若不持有价值体则输出 0
21{adv_id} {com_id}查询 ID 为 {adv_id} 的冒险者所持有的 ID 为 {com_id} 的价值体所属类的类名一个字符串 Commodity whose id is {com_id} belongs to {x},其中{com_id}为价值体的 ID,{x}AdventurerFoodRegularBottleReinforcedBottleRecoverBottleRegularEquipmentCritEquipmentEpicEquipment中价值体所属类的类名

在计算冒险者持有价值体的价值之和的时候,按照题目说明流程即可,不必特殊考虑。例如A雇佣了B,B雇佣了C,A雇佣了C,则计算A价值之和的时候,作业要求的算法是通过自己的雇佣加上了一次C的价值,又通过B对C的雇佣加上了一次C的价值,不必剔除对C的多次计算

循环雇佣:存在一个雇佣序列,A0雇佣A1,A1雇佣A2…..An雇佣A0.

数据保证不会出现循环雇佣的情况。

关于战斗日志的具体说明

输入格式 意义 输出格式
YYYY/MM-{adv_name_1}-{name} YYYY/MM这个月,名字为 {adv_name_1} 的冒险者使用了名字为 {name} 的药水 成功:{一个整数A} {一个整数B},整数A为该被使用药水的id,整数B为该冒险者使用该药水后的体力值 失败: Fight log error
YYYY/MM-{adv_name_1}@{adv_name_2}-{name} YYYY/MM这个月,名字为 {adv_name_1} 的冒险者对名字为 {adv_name_2} 的冒险者发起攻击,使用了名字为 {name} 的装备 成功:{一个整数A} {一个整数B},整数A为被攻击者的id,整数B为该冒险者受到攻击后的体力值 失败: Fight log error
YYYY/MM-{adv_name_1}@#-{name} YYYY/MM这个月,名字为 {adv_name_1} 的冒险者对 剩余所有进入战斗模式的冒险者发起群体攻击,使用了名字为 {name} 的装备 成功:按照 进入战斗状态 的次序输出受攻击冒险者被攻击后的体力值,以一个空格隔开 失败: Fight log error

上述 “Fight log error” 的输出场景为:非法冒险者名(冒险者不处于战斗模式)、非法药水名(该药水未被携带)、非法武器名(该武器未被携带)。保证战斗日志不会出现其他形式的错误。一旦出现错误,则该行战斗日志无效,不产生任何作用,同时 不应出现在15/16/17号命令的查询中

特别地,对于一对多攻击,只要出现上述错误场景,视这条战斗日志无效,所有被攻击者均不会损失体力。

YYYY/MM代表输入的字符串位数必然为4位数字/2位数字,你在输出的时候也应当采取这样的格式

保证实际月份值在1-12月之间。

特别提醒:在有些程序的实现里,从字符匹配角度来看,一个战斗日志可能既符合YYYY/MM-{adv_name_1}-{name}也符合YYYY/MM-{adv_name_1}@{adv_name_2}-{name},但是在数据限制的情况下有唯一意义,请思考这一点并将其应用于你的程序

样例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
13
1 123 advName1
1 124 advName2
1 125 advName3
18 123 124
18 123 125
2 124 1 botName 20 10 RegularBottle
4 125 2 equName 12 35 CritEquipment 123
20 123
1 126 advName4
18 124 126
21 124 126
21 125 2
19 123
1
2
3
4
35
Commodity whose id is 126 belongs to Adventurer
Commodity whose id is 2 belongs to CritEquipment
2 45

数据限制

变量约束
变量约束
变量类型说明
id整数取值范围:0 - 2147483647
name字符串保证不会出现空白字符, @, -, #;长度满足(0,40)
capacity整数取值范围:1 - 2147483647
star整数取值范围:1 - 2147483647
level整数取值范围:1 - 2147483647
hitPoint整数取值范围:1 - 2147483647
energy整数取值范围: 0-2147483647
ratio浮点数0 < ratio < 1,double 精度范围内
price长整数所有价值体的价值均在 long 精度范围内,且保证不小于0
critical整数取值范围:0 - 2147483647

表格中a-b指变量范围为[a,b]

注意,变量约束指的是,在程序正确运行时,输入和对应属性值均保证在表格中给出的范围内。

操作约束
操作约束
  1. 保证所有的冒险者、药水瓶、装备、食物 id 均不相同,冒险者之间name均不相同
  2. 保证删除了的药水瓶/装备/食物的 id 不会再次出现
  3. 2-6/14/16-17保证所有 id 对应的冒险者均已存在
  4. 3/5/6/8保证该冒险者拥有操作中提到 id 的药水瓶/装备/食物
  5. 保证增加的装备,食物和药水瓶原本不存在
  6. 操作数满足1≤n≤2000
  7. 9-11保证该冒险者拥有操作中提到 id 的药水瓶/装备/食物
  8. 12-13 保证以提到的 name 为名字的物品已经被携带
  9. 14保证战斗模式结束时,任意一个冒险者的体力均大于0
  10. 保证战斗日志出现的时候一定处于战斗模式
  11. 同一次战斗模式下,保证日志输入中日期随输入顺序单调不减;如果多次进入战斗模式,进入战斗模式的日期也随输入顺序单调不减
  12. 保证 14 中所有 name 均存在,18-21 中所有 id 均存在,21 中冒险者一定拥有对应 id 的价值体
  13. 保证不会出现两个或多个冒险者之间循环雇佣的情况

关于第六次作业的解析与说明

第六次作业在第四次的基础上进行扩展,本文也从已经通过第四次作业强测的程序基础上进行讲解分析。

注意:划删除线的部分并非过时信息!

Part 1

指导书要求分析

这一部分就是上面的指导书内容,不再重复。

具体分析

本次作业需要解决两个核心问题:

实现Commodity接口

  • Commodity接口由AdventureEquipmentBottleFood实现,需要在这四个类中实现接口中的方法
  • Commodity接口提供两个抽象方法:int getPrice()String getClassName()分别用于获取该价值体的价值和所属类类名(运行时实际类的类名)
    • getPrice直接返回属性pricegetClassName使用getClass().getSimpleName()获取类名(因为BottleEquipment
      实际类型是它们的子类)
  • Adventure类新增属性HashMap<Integer,Commodity> commodities,用于存储冒险者拥有的价值体,并且可以按照id快速查找

实现BottleEquipment的各三个子类

  • BottleEquipment的各三个子类分别为RegularBottleReinforcedBottleRecoverBottle
    RegularEquipmentCritEquipmentEpicEquipment
  • 三个子类需要重写构造方法,以及使用这个对象时造成增益或者伤害的方法, 并按题意要求新增属性
  • Main类添加装备和药水瓶时,需要根据输入的type来判断具体实例化哪个子类,可以使用switch语句;注意other
    参数可能为空,因此需要判断orders指令的长度

Part 2

Junit测试,按上个文档新增内容的格式基础上扩展即可

Part 3 bug修复记录

Bug 001

说明

给定的测试用例中,有一条指令为:

1
4 674341 573832 /MpITw8 0 165922655 EpicEquipment  0.728

检查发现,EpicEquipment0.728之间有两个空格
因此在处理指令时,按照原先以一个空格做为分隔符会出现orders数组长度为8的情况(多出一个空格字符串)
解决方案是将分隔符改为一个及以上个空格,即orders.split("\\s+");

改动详情

Main.java文件中:

1
2
3
//现第374行
- String[] strings = line.trim().split(" ");
+ String[] strings = line.trim().split("\\s+");

Bug 002

说明

对于雇佣的冒险者,当其本身的价值变化时,其雇主的价值也要变化。
当瓶子和食物因为使用被丢弃时,其拥有者的价值也要变化。

解决方案是:
修改Adventure类中的:
getPrice方法,在计算价值时,用递归的方法一直计算到没有雇佣任何冒险者的冒险者,然后逐层返回就可以实现价值的更新
useBottleeatFood方法,在使用完瓶子和食物达到丢弃条件时,更新价值并将其从commodities中移除

改动详情

只展示getPrice方法的修改。
Adventure.java文件中:

1
2
3
4
5
6
7
8
//现第249行
- return this.price;
+ long totalPrice = 0;
+ for (Commodity commodity : commodities.values()) {
+ totalPrice += commodity.getPrice();
+ }
+ this.price = totalPrice;
+ return this.price;