阶段项目-宠物小精灵——3

java编写的多态项目,宠物小精灵图形化界面,第三节

Adventure(冒险家)是游戏核心控制类,负责管理玩家资源(装备、药品、宠物);

驱动游戏流程(关卡切换、地图探索、事件交互)

处理用户输入与反馈(移动、战斗、物品获取)

三、核心方法解析

1. start():游戏入口方法

功能:初始化游戏,启动主循环处理用户输入与事件。
流程

关键逻辑

  • 关卡切换:通过传送门(Portar)实现,通往下一关时动态生成新关卡(Level),并更新currentLevel
  • 地图刷新:每次操作后调用LevelMap.show()重新绘制地图,确保用户看到最新状态;
  • 输入校验:使用Tools.getInputChar()Tools.getInputNumber()限制用户输入范围,避免非法操作。
  •   /**
         * 开始闯关
         * start():游戏入口,
         * 初始化关卡和地图,进入主循环处理用户输入和事件。
         *
         */
        public void start(){
            currentLevel = new Level(null,1,null);
            LevelMap map = currentLevel.getMap();
            //冒险家进入地图
            map.addAdventure(this);
            while(true){
                currentLevel.getMap().show();
                System.out.println("请选择移动方向:W(上)、A(左)、S(下)、D(右)、E(退出)");
                char direct = Tools.getInputChar();
                if(direct == 'E')//退出
                {
                    System.out.println("确定要退出吗?Y/N");
                    char quit = Tools.getInputChar();
                    if(Character.toUpperCase(quit) == 'Y'){
                        System.out.println("感谢使用宠物小精灵闯关");
                        break;
                    }
                }
                else
                {
                    Item item = discovery(direct);
                    if(item != null){
                        //物品被发现
                        item.setDiscovery(true);
                        currentLevel.getMap().show();
                    }
    
                    if(item instanceof Treasure){//宝箱
                        processTreasure((Treasure) item,direct);
                    }else if(item instanceof Monster){//怪物
                        processMonster((Monster) item,direct);
                    } else if (item instanceof Portar) {//传送门
                        System.out.println("发现传送门是否通过? Y/N");
                        char pass = Tools.getInputChar();
                        if(Character.toUpperCase(pass) == 'Y'){
                            if(((Portar) item).isNext()){//是否是通往下一关卡的传送门
                                //获取当前关卡的下一关卡
                                Level nextLevel = currentLevel.getNextLevel();
                                if(nextLevel == null)
                                {
                                    nextLevel = new Level(currentLevel,currentLevel.getNumber()+1,null);
                                    //将冒险家加载到地图中
                                    nextLevel.getMap().addAdventure(this);
                                    //当前关卡的下一关卡即为新创建的关卡
                                    currentLevel.setNextLevel(nextLevel);
    
                                }
                                //经过传送门后,下一关卡即为当前关卡
                                currentLevel = nextLevel;
                            }else{//通往上一关卡的传送门
                                Level prevLevel = currentLevel.getPrevLevel();
                                if(prevLevel == null){
                                    System.out.println("非法操作");
                                }else{
                                    currentLevel = prevLevel;
                                }
                            }
                        }
    
                    }
                    else {//其他情况
                        move(direct);
                    }
                }
            }
        }
    
    
    
    2. processMonster(Monster monster, char direct):处理怪物战斗

    功能:与怪物交互,触发战斗逻辑,处理药品使用、宠物选择及战斗结果。

    关键步骤

  • 确认战斗:用户选择是否清除怪物(输入Y/N);
  • 选择宠物:展示所有宠物信息,用户选择出战宠物;
  • 战斗循环
    • 宠物与怪物交替攻击(Pokemon.attackMonster()Monster.attackPokemon());
    • 宠物生命值低于50%时,询问是否使用药品(调用getCurrentLevelHP()查找可用药品);
    • 药品使用后更新背包(若药品数量耗尽则移除,否则减少数量);
  • 战斗结果
    • 怪物被击败:掉落物品(Monster.drop()),物品添加至背包(processItem());
    • 宠物被击败:怪物恢复满血(Monster.resume())。
 /**
     * 处理怪物方法
     * @param monster
     * @param direct
     *
     */
    private void processMonster(Monster monster,char direct){
        //询问是否清除怪物
        System.out.println("发现" + monster.getName() + ",是否清除?Y/N");
        char clear = Tools.getInputChar();//交互
        if(Character.toUpperCase(clear) == 'Y'){
            for (int i=0;i<pokemons.length;i++)
            {//展示怪物信息
                System.out.println((i+1) + "\t" + pokemons[i].getItemInformation());
            }
            //选择出战宠物:
            System.out.println("请选择出站宠物小精灵:");
            /**
             * 调用Tools.getInputNumber(1, pokemons.length)方法
             * 来获取用户的输入。该方法会保证用户输入的数字在 1 到pokemons.length这个范围之内。
             */
            int number = Tools.getInputNumber(1,pokemons.length);
            //由于用户输入的序号是从 1 开始的,而数组的索引是从 0 开始的,所以要使用number-1来获取对应的宠物小精灵
            Pokemon pokemon = pokemons[number-1];
            //循环条件:怪物和宠物都存活
            //当任一生命值≤0 时,跳出循环,进入结果处理
            while (monster.getCurrentHealth() > 0 && pokemon.getCurrentHealth() > 0)
            {
                //获取宠物小精灵的剩余生命值的比例
                double rate = pokemon.getHealthPercent();
                /**
                 * 这个方法返回当前生命值相对于最大生命值的百分比,用小数形式表示(范围从 0.0 到 1.0)。例如:
                 * 满血状态返回 1.0
                 * 半血状态返回 0.5
                 * 空血状态返回 0.0
                 *
                 * return currentHealth * 1.0 / getHealth();
                 * // 代入具体数值:
                 * return 100 * 1.0 / 100;
                 * // 计算步骤:
                 * 1. 100 * 1.0 = 100.0(转换为double类型)
                 * 2. 100.0 / 100 = 1.0(浮点数除法,结果为1.0)
                 *
                 * 关键点 1:为什么要乘以 1.0?
                 * 这涉及 Java 的运算规则:
                 * 整数除法:如果两个操作数都是整数,结果会被截断为整数(如50/100结果是 0)
                 * 浮点数除法:只要有一个操作数是浮点数,结果就会保留小数(如50.0/100结果是 0.5)
                 * 通过乘以1.0(这是一个 double 类型的常量),将整个表达式转换为浮点数运算,确保结果的准确性
                 */
                if(rate < 0.5){//生命值低于50%,询问是否使用药品
                    System.out.println(pokemon.getName() + "生命值低于50%,是否使用药品?Y/N");
                    char eatHp = Tools.getInputChar();
                    if(Character.toUpperCase(eatHp) == 'Y')
                    {
                        HP hp = getCurrentLevelHP(currentLevel.getNumber());
                        if(hp == null){
                            System.out.println("背包中没有药品。请探索其他地图");
                            break;
                        }else {
                            //如果药品可以被销毁,说明没有可用数量
                            if(hp.canDestroy()){
                                int index = -1;
                                for (int i = 0;i<medicines.length;i++){
                                    //目的:找到与当前药品等级相同的药品在数组中的位置
                                    //假设当前使用的是 2 级药品,遍历数组找到所有 2 级药品中的第一个(索引 i)
                                    if(hp.getLevelNumber() == medicines[i].getLevelNumber()){
                                        index = i;
                                        break;
                                    }
                                }
                                /**
                                 * 作用:将数组中index位置后的元素整体前移一位,覆盖要移除的元素
                                 * 参数解析:
                                 * medicines:源数组(要复制的数组)
                                 * index + 1:从源数组的index+1位置开始复制
                                 * medicines:目标数组(复制到哪个数组)
                                 * index:目标数组的起始位置
                                 * medicines.length - index - 1:复制的元素数量
                                 *
                                 *
                                 * 4. arraycopy数组操作可视化示例
                                 * 假设medicines数组存储了 4 个药品,索引 0~3:
                                 * plaintext
                                 * [1级药品, 2级药品, 3级药品, 2级药品]
                                 *
                                 * 现在要移除索引 1 的 2 级药品(index=1)
                                 * System.arraycopy 的操作是:
                                 * 从源数组的index+1=2位置(3 级药品)开始
                                 * 复制到目标数组的index=1位置
                                 * 复制长度为 4-1-1=2 个元素(3 级药品和 2 级药品)
                                 * 操作后数组变为:
                                 * plaintext
                                 * [1级药品, 3级药品, 2级药品, 2级药品]
                                 *
                                 * 注意:数组长度并未改变,只是索引 1 的元素被覆盖了
                                 *
                                 */
                                System.arraycopy(medicines,index + 1,medicines,index,medicines.length - index - 1);
                                System.out.println("药品已经使用完毕");
                            }else {
                                int health = hp.use();
                                pokemon.setCurrentHealth(pokemon.getCurrentHealth() + health);
                            }
                        }
                    }
                }

                Tools.lazy(300L);//延迟300ms,控制战斗节奏
                pokemon.attackMonster(monster);//宠物攻击怪物
                Tools.lazy(300L);
                monster.attackPokemon(pokemon);//怪物攻击宠物
                Tools.lazy(300L);
            }
            //怪物已被击败
            if(monster.getCurrentHealth() == 0) {
                System.out.println("怪物已被击败");
                //怪物掉落物品
                Item dropItem = monster.drop();//drop方法里面有Tools.getRandomItem(levelNumber);
                //展示获取的物品信息
                System.out.println("怪物已被击败,掉落" + dropItem.getItemInformation());
                processItem(dropItem);//处理获得的物品(如添加到背包)
                //怪物被击败后
                move(direct);//继续移动,避免卡在战斗位置
            }else {//宠物小精灵被击败
                monster.resume();//怪物回血
                System.out.println(pokemon.getName() + "已被击败");
            }

        }
    }
3. processTreasure(Treasure treasure, char direct):处理宝箱

功能:开启宝箱,获取随机物品(药品、装备、宠物),并将物品添加至背包。

关键逻辑

  • 用户确认开启宝箱(输入Y/N);
  • 调用Treasure.open()生成随机物品(通过Tools.getRandomItem());
  • 调用processItem()将物品分类存入对应背包(药品合并、装备更换、宠物融合或新增);
  • 开启后冒险家移动至宝箱位置(move(direct))。
/**
     * processTreasure():
     * 处理宝箱事件(打开、获取物品、合并资源)。
     * @param treasure
     */
    private void processTreasure(Treasure treasure,char direct){
        System.out.println("发现宝箱,是否打开? Y/N");
        char open = Tools.getInputChar();
        //将输入字符转为大写(Character.toUpperCase),如果输入是 Y,则进入开箱逻辑。
        if(Character.toUpperCase(open) == 'Y'){
            /**
             * 开启宝箱获得一个物品
             * 调用宝箱的 open() 方法,随机生成一个物品
             * (可能是药品、装备或精灵)。
             */
            Item item = treasure.open();
            //展示获取的物品信息
            System.out.println("获得" + item.getItemInformation());
            processItem(item);
            //宝箱处理后,冒险家移动至宝箱的位置
            move(direct);
        }
    }
4. processItem(Item item):处理获得的物品

功能:根据物品类型(药品、装备、宠物),将其添加至对应背包或执行特殊操作(如融合)。

分支逻辑

  • 药品(HP):遍历medicines背包,若存在同等级药品则合并数量(HP.addCount());
  • 装备(Equipment):询问用户是否更换宠物装备(Pokemon.changeEquipment()),换下的旧装备存入equipments背包;
  • 宠物(Pokemon)
    • 若背包已有同类型宠物(getClass()比较),询问是否融合(Pokemon.merge());
    • 若无同类型宠物,直接扩容pokemons背包并添加新宠物。
/**
     * 处理获得的物品
     * @param item
     */
     private void processItem(Item item){

         if(item instanceof HP)//药品
         {
             /**
              *         宝箱开出的 item(HP类型)
              *                 ↓
              *         强制转换为 HP 类型:((HP) item)
              *                 ↓
              *         获取它的数量:((HP) item).getCount()
              *                 ↓
              *   调用背包中同等级药品的 addCount() 方法:hp.addCount(数量)
              *                 ↓
              *         背包中药水数量增加 ✅
              */
             //循环遍历当前背包中的药品列表 medicines
             for(HP hp:medicines){
                 //检查背包中是否存在和当前药品 相同等级(levelNumber)的药品
                 if(hp.getLevelNumber() == item.getLevelNumber()){
                     //强制类型转换为了使用HP中getCount方法,getCount方法:获取药品数量
                     hp.addCount(((HP) item).getCount());
                     break;
                 }
             }
         }
         else if(item instanceof Equipment)
         {//装备
             System.out.println("发现新的装备,是否给宠物小精灵更换? Y/N");
             char change = Tools.getInputChar();
             if(Character.toUpperCase(change) == 'Y'){
                 Equipment old = null;
                 //遍历玩家拥有的所有小精灵
                 for(Pokemon pokemon: pokemons)
                 {
                     //小精灵更换装备
                     old = pokemon.changeEquipment((Equipment) item);
                     //如果换下来的装备为空,说明后面的小精灵不需要再看
                     //如果小精灵 原本没有装备,会直接装备新装备,返回 null
                     if(old == null) break;
                 }
                 //如果换下来的装备不为空,则直接放入背包中
                 //如果小精灵 已有装备,会脱下旧装备,返回旧装备对象
                 if(old != null){
                     /**
                      * 检查旧装备:如果 old != null,说明有小精灵换下了旧装备。
                      *
                      * 扩容背包:使用 Arrays.copyOf 将装备背包 equipments 扩容(长度+1)。
                      *
                      * 存入旧装备:将旧装备放入背包最后一个位置。
                      */
                     equipments = Arrays.copyOf(equipments,equipments.length+1);
                     equipments[equipments.length-1] = old;
                 }
             }

         }
         else {//宠物小精灵
             int index = -1;
             for(int i=0;i<pokemons.length;i++)
             {
                 //getClass() == 严格比较类对象,要求精灵类型完全一致
                 //(例如 Bulbasaur.class vs Bulbasaur.class)。
                 if(item.getClass() == pokemons[i].getClass()){
                     //若存在同类型精灵,记录其位置 index;否则 index 保持为 -1
                     index = i;
                     break;
                 }
             }
             //不存在同类型宠物小精灵
             /**
              * 扩容背包:通过 Arrays.copyOf 将精灵背包扩容 1 个位置。
              *
              * 存入新精灵:将宝箱中获得的新精灵放入扩容后的最后一个位置。
              */
             if(index == -1){
                 pokemons = Arrays.copyOf(pokemons,pokemons.length + 1);
                 pokemons[pokemons.length - 1] = (Pokemon) item;
             }

             else {//存在同类型宠物小精灵
                 System.out.println("发现可融合宠物小精灵,是否融合? Y/N");
                 char merge = Tools.getInputChar();
                 if(Character.toUpperCase(merge) == 'Y'){
                     //用户选择融合(Y):调用 merge() 方法,将新精灵融合到已有的同类型精灵中。
                     pokemons[index].merge((Pokemon) item);
                 }else{//用户拒绝融合(N):将新精灵直接存入背包,不做其他操作。

                     pokemons = Arrays.copyOf(pokemons,pokemons.length + 1);
                     pokemons[pokemons.length - 1] = (Pokemon) item;
                 }
             }
         }
     }
5. getCurrentLevelHP(int levelNumber):查找可用药品

功能:递归查找当前关卡或更低关卡的可用药品(优先使用当前关卡,逐级回退)。

逻辑

  • 从当前关卡levelNumber开始遍历medicines背包,查找同等级药品;
  • 若未找到,递归调用levelNumber-1继续查找;
  • 若到第0关仍未找到,返回null(无可用药品)。
 /**
     * 获取当前关卡使用的药品,如果当前关卡的药品已经使用完,那么可以使用上一关卡的药品,依次类推
     * @param levelNumber
     *
     * 假如目前是第3关,调用getCurrentLevelHP传入3,首先看关卡是否为0,为0就代表关卡为0,没有要
     * 之后hp赋值null,for循环遍历数组长度,获取该药品对应的关卡等级,和当前关卡比较,能使用直接退出走else,return hp
     * 如果没有的话为null,接着调用getCurrentLevelHP方法,参数是关卡-1,刚开始是第三关,之后变成第二关判断第二关是否有药品能使用
     *
     * @return
     */
    private HP getCurrentLevelHP(int levelNumber){
        if(levelNumber == 0)return null;
        HP hp = null;
        for (int i= 0;i<medicines.length;i++)
        {
            //getLevelNumber():获取该药品对应的关卡等级(例如 1 级药品、2 级药品)
            //levelNumber 获取当前游戏所处的关卡编号(例如第 1 关、第 2 关)
            if(medicines[i].getLevelNumber() == levelNumber){
              hp = medicines[i];
              break;
            }
        }
        if(hp == null){
            return getCurrentLevelHP(levelNumber - 1);
        }else {
            return hp;
        }
     }
6. discovery(char direct)move(char direct):探索与移动
  • discovery:调用LevelMap.getPositionItem()获取目标方向的物品,并标记为可见(item.setDiscovery(true));
  • move:调用LevelMap.move()移动冒险家位置,更新地图中冒险家坐标。
 /**
     * 探索给定方向地图位置
     * @param direct 方向
     * @return
     */
    private Item discovery(char direct){
        return (Item) currentLevel.getMap().getPositionItem(Character.toUpperCase(direct));
    }

    /**
     * 向给定方向地图位置移动
     * @param direct
     */
    private void move(char direct){
        currentLevel.getMap().move(Character.toUpperCase(direct));
    }

 

最后创建一个启动类调用游戏入口方法就能运行了

以上就是冒险家的实现,感谢大家观看! 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值