OOPre课程作业总结:属于我自己的博德之门3

徐珺超-24371106 2025-11-08 21:59:17

属于我自己的博德之门3


一、冒险者作业最终架构设计

1. 核心实体类

Adventurer(冒险者)

系统的核心实体类,是整个系统的中心。

主要属性:

  • 基础属性:idhitPointbaseatkbasedefmanamoney
  • 物品管理:Map<String, Bottle> bottlesMap<String, Equipment> equipmentsMap<String, Spell> spells
  • 携带物品:LinkedHashSet<String> bottleBackpack(最多10个,FIFO)、carriedWeaponcarriedArmour
  • 雇佣关系:Set<String> employeesString employer

核心方法:

  • 物品管理:addBottle()addEquipment()addSpell()removeItem()takeItem()
  • 属性计算:getAtk()(基础攻击+武器)、getDef()(基础防御+护甲)
  • 状态查询:isDead()isCarrying()
  • 关系管理:hireEmployee()isBoss()(递归查找上级)、isAlly()(判断盟友)
  • 援助相关:getAllSubordinates()(递归获取所有下属)

Equipment(装备)

装备基类,采用三层继承结构。

继承体系:

Equipment
├── Weapon(武器)
│   ├── Sword(剑)
│   └── Magicbook(魔法书)
└── Armour(护甲)

属性:idce(战斗力)

设计考虑:Weapon作为中间层便于统一管理武器,与Armour区分开来。

Bottle(药水)

药水基类,四种子类对应不同效果。

子类:

  • HpBottle:恢复生命值
  • AtkBottle:永久增加基础攻击力
  • DefBottle:永久增加基础防御力
  • ManaBottle:恢复魔法值

属性:ideffect

特点:使用后自动从背包移除。

Spell(法术)

法术基类,两种子类实现不同功能。

子类:

  • HealSpell:治疗法术,为盟友恢复生命值
  • AttackSpell:攻击法术,对敌人造成伤害

属性:idmanaCost(魔法消耗)、power(威力)

使用规则:

  • 需要消耗魔法值
  • 治疗法术只能对盟友使用
  • 攻击法术不能攻击上级

2. 工具类

Factory(工厂类)

集中管理所有对象的创建,实现工厂设计模式。

方法:

  • createBottle(String type, String id, int effect):创建药水对象
  • createSpell(String type, String id, int cost, int power):创建法术对象
  • createEquipment(String type, String id, int ce):创建装备对象

设计目的:

  • 将对象创建逻辑从业务代码中分离
  • 便于统一管理和扩展新类型
  • 符合开闭原则

Lexer(词法分析器)

用于解析输入命令,将字符串分解为token序列。

核心功能:

  • 去除所有空白字符
  • 识别标识符(字母、数字、下划线)
  • 识别特殊符号((),

接口:

  • peek():查看当前token
  • next():移动到下一个token

Parser(语法分析器)

使用递归下降法解析复杂的冒险者雇佣关系。

解析文法:

冒险者 → 标识符 [被雇佣者]
被雇佣者 → '(' 冒险者序列 ')'
冒险者序列 → 冒险者 {',' 冒险者}

示例:A(B(C,D),E(F)) 表示 A雇佣B和E,B雇佣C和D,E雇佣F

设计亮点:采用编译原理中的递归下降算法,优雅处理嵌套结构。

3. 主程序类

Main

程序入口,负责读取输入、初始化数据结构,并调用相关方法处理命令。

核心职责:

  • 维护全局冒险者Map:Map<String, Adventurer> adventurers
  • 读取和解析命令
  • 分发到对应的处理方法
  • 协调战斗、援助等复杂逻辑

主要方法:

  • addAdventurer():添加冒险者
  • addBottle()addEquipment()learnSpell():添加物品
  • useItem():使用物品或法术
  • fight():处理战斗
  • addRelation()removeRelation()loadRelation():管理雇佣关系
  • checkAndTriggerHelp():检查并触发援助

二、迭代中的架构调整及考虑

1. 第二次作业(初始架构)

实现内容

  • 实现冒险者类Adventurer
  • 实现药水瓶类Bottle
  • 实现装备类Equipment
  • 利用容器管理所有冒险者,并管理每一个冒险者所拥有的药水瓶和装备

核心设计

数据结构选择:

// 全局管理所有冒险者
private static Map<String, Adventurer> adventurers = new HashMap<>();

// 冒险者内部管理物品
public class Adventurer {
    private Map<String, Bottle> bottles;
    private Map<String, Equipment> equipments;
}

为什么使用Map?

  • O(1)时间复杂度查找
  • 键值对结构直观,便于通过ID快速访问
  • 支持高效的增删改查操作

基础命令实现:

  • aa advId:添加冒险者
  • ab advId bottleId type effect:添加药水
  • ae advId equipmentId type ce:添加装备

架构特点:

  • 简单直接,面向过程的思维
  • 所有逻辑集中在Main类
  • 为后续扩展预留了空间

2. 第三次作业(引入继承)

实现内容

  • 实现法术类Spell
  • 对冒险者添加属性(HP、ATK、DEF、Mana)
  • 对Bottle类、Spell类添加子类

继承体系建立

Bottle子类化:

class HpBottle extends Bottle { }
class AtkBottle extends Bottle { }
class DefBottle extends Bottle { }
class ManaBottle extends Bottle { }

Spell类设计:

public class Spell {
    private String id;
    private int manaCost;
    private int power;
}

class HealSpell extends Spell { }
class AttackSpell extends Spell { }

Adventurer属性扩展:

private int hitPoint = 500;
private int baseatk = 1;
private int basedef = 0;
private int mana = 10;
private Map<String, Spell> spells;

使用物品功能:

  • use advId usableId targetId:使用药水或法术
  • 药水使用后自动移除
  • 法术消耗魔法值

架构改进:

  • 引入继承体系,建立类层次结构
  • 通过子类区分不同类型,代码更清晰
  • Adventurer成为真正的核心实体

3. 第五次作业(装备体系与战斗系统)

实现内容

  • 为装备添加子类
  • 引入战斗功能
  • 实现金钱系统

装备继承体系

Equipment(基类)
├── Weapon(武器,中间层)
│   ├── Sword(剑)
│   └── Magicbook(魔法书)
└── Armour(护甲)

设计考虑:

  • Weapon作为中间层统一管理所有武器
  • 冒险者可携带一件武器和一件护甲
  • 通过getAtk()getDef()计算总战斗力

携带装备机制:

private Weapon carriedWeapon;
private Armour carriedArmour;

public int getAtk() {
    if (carriedWeapon == null) {
        return baseatk;
    } else {
        return carriedWeapon.getCe() + baseatk;
    }
}

战斗系统实现:

命令格式:fight advId k targetId1 targetId2 ... targetIdk

战斗逻辑:

  • 魔法书:消耗sqrt(ce)点魔法值,伤害=攻击力(无视防御)
  • 普通武器:伤害=攻击力-最大防御力(需攻击力>防御力)
  • 击杀获得目标的总价值(金钱+物品价值之和)

金钱系统:

private int money = 50;  // 初始金钱

// bi命令:购买物品
private static void buyItem(String advId, String itemId, String type) {
    int cost = getCost(type);
    if (adventurer.getMoney() >= cost) {
        adventurer.setMoney(adventurer.getMoney() - cost);
        // 创建并添加物品
    }
}

架构改进:

  • 装备体系完善,三层继承清晰
  • 战斗系统引入多种机制
  • 金钱与战斗、物品形成闭环

4. 第六次作业(雇佣关系与援助系统)

实现内容

  • 实现雇佣关系
  • 实现操作后援助功能

雇佣关系数据结构

private Set<String> employees;  // 雇员集合
private String employer;        // 雇主

为什么用Set?

  • 避免重复雇佣
  • 快速查找和删除
  • 不需要保持顺序

关系查询算法:

判断是否为上级(递归向上):

public boolean isBoss(String targetId, Map<String, Adventurer> allAdventurers) {
    if (this.employer == null) return false;
    if (this.employer.equals(targetId)) return true;
    
    Adventurer boss = allAdventurers.get(this.employer);
    if (boss != null && !boss.isDead()) {
        return boss.isBoss(targetId, allAdventurers);  // 递归
    }
    return false;
}

判断是否为盟友:

public boolean isAlly(String targetId, Map<String, Adventurer> allAdventurers) {
    if (this.id.equals(targetId)) return true;  // 自己
    if (isBoss(targetId, allAdventurers)) return true;  // 上级
    return isSubordinate(targetId, allAdventurers);  // 下属
}

关系检查:

  • 不能攻击上级
  • 治疗法术和药水只能对盟友使用
  • 死亡时自动清除所有雇佣关系

援助系统实现:

触发条件:生命值降至原来的一半以下

援助逻辑:

private static void checkAndTriggerHelp(Adventurer target, int oldHp) {
    int newHp = target.getHitPoint();
    if (newHp > 0 && newHp <= oldHp / 2) {
        // 获取所有下属(递归)
        List<Adventurer> subordinates = target.getAllSubordinates(adventurers);
        
        int helpCount = 0;
        for (Adventurer helper : subordinates) {
            if (tryHelp(helper, target)) {
                helpCount++;
            }
        }
        
        if (helpCount > 0) {
            System.out.println(target.getId() + " is helped by " + helpCount +
                             " adventurer(s), now Hp is " + target.getHitPoint());
        }
    }
}

选择最优法术:

  • 优先选择power最大的治疗法术
  • power相同时选择manaCost最小的
  • 贪心算法

架构改进:

  • 图结构思维:雇佣关系本质是有向图
  • 递归算法优雅处理多层级关系
  • 自动化援助机制

5. 第七次作业(最终架构)

实现内容

利用递归下降法导入复杂冒险者关系

命令格式

lr A(B(C,D),E(F))

含义:

  • A雇佣了B和E
  • B雇佣了C和D
  • E雇佣了F

Lexer + Parser设计

词法分析(Lexer):

将输入字符串分解为token序列:

输入:A(B(C,D),E(F))
Token序列:A ( B ( C , D ) , E ( F ) )

实现要点:

  • 去除所有空白字符
  • 识别标识符和特殊符号
  • 提供peek()next()接口

语法分析(Parser):

文法定义:

冒险者 → 标识符 [被雇佣者]
被雇佣者 → '(' 冒险者序列 ')'
冒险者序列 → 冒险者 {',' 冒险者}

递归下降实现:

private void parseAdventurer() {
    String adventurerId = lexer.peek();
    lexer.next();
    
    if (lexer.peek() != null && lexer.peek().equals("(")) {
        parseEmployees(adventurerId);  // 递归处理雇员
    }
}

private void parseEmployees(String employerId) {
    if (!lexer.peek().equals("(")) {
        throw new RuntimeException("Expected '('");
    }
    lexer.next();
    
    parseAdventurerList(employerId);  // 递归解析雇员列表
    
    if (!lexer.peek().equals(")")) {
        throw new RuntimeException("Expected ')'");
    }
    lexer.next();
}

架构完善:

  • 编译原理应用:Lexer + Parser的经典组合
  • 递归下降优雅处理嵌套结构
  • 职责分离:词法分析和语法分析独立

三、JUnit单元测试心得体会

     可以很好用来测试代码,每次迭代后运行全部测试,确保新功能没有影响旧功能,还可以快速反馈,修改代码后立即知道是否破坏了现有功能,不过一开始写起来确实遇到了不少困难,毕竟大一学C的时候并不需要写这些额外的东西,在文档和ai的帮助下,写着写着就会了,不过覆盖率有时候还是不尽如人意,我会在OO中更精进一下的。

 


四、学习OOPre的心得体会

   在知道OOPre的迭代作业是编一个类似于rpg的程序,我就非常感兴趣,因为从小到大挺喜欢打游戏,mc,tr,LOL,二游,博德之门3,艾尔登法环等等大小游戏,还不少做将来进游戏大厂编游戏的梦,所以看到作业是做一个“游戏”,非常的感兴趣,但是毕竟从前没有面向对象编程的经历,所以第二次作业很懵逼,因为一下子难度飙升,第一次只是debug,但第二次就直接是一个独立的程序了,而且需要的内容还不少,确实困难很大,毕竟之前都是面向过程编程,不知道该怎么办,从哪里起手,遂问了问s7h学长,说是要先想好都设置哪些类,每个类起什么作用,怎么交互,再写代码,有点似懂非懂,但好歹是有了个大方向,做的过程中也在问起了编程中遇到的具体问题,助教们都非常非常耐心!在这十分感谢所有的助教!随着药水,法术,战斗,雇佣,支援的引入,真的真的很有成就感,仿佛自己做了一个自己的八方旅人,做了一个自己的博德之门3,做出了一个真正的东西,真的很喜欢这样的作业,不过可能是自己能力问题?感觉一次迭代内容好多啊,一周的时间真的好仓促,希望之后能迭代速度慢一点。

五、总结与展望

    建议:

            1:迭代速度能放缓一些嘛?感觉进度好赶啊(也有可能是自己菜菜的

 

致谢

感谢OOPre课程组的老师和助教,通过精心设计的迭代作业,让我们在实践中真正理解了面向对象的精髓!做出了属于自己的博德之门3!


文章首发于CSDN
如有问题或建议,欢迎在评论区交流讨论!
如果这篇文章对你有帮助,请点赞支持 👍

...全文
63 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

271

社区成员

发帖
与我相关
我的任务
社区描述
2026年北航面向对象设计与构造
java 高校
社区管理员
  • 孙琦航
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧