javaOOP实现跳高大挑战!手把手教你实现小游戏!

本文涉及的产品
智能开放搜索 OpenSearch行业算法版,1GB 20LCU 1个月
实时数仓Hologres,5000CU*H 100GB 3个月
实时计算 Flink 版,5000CU*H 3个月
简介: javaOOP实现跳高大挑战!手把手教你实现小游戏!

跳高大挑战

由于该游戏复杂度稍高,笔者建议先学习猜拳游戏的文章之后再来完成该游戏。

题目

image.png

设计

image.png
常规:游戏主类,规则类
角色player类->因为有多个角色,所以应该有playData数据类
裁判judge类
根据题目要求需要体力能力比数据类

玩家:
属性:当前体力值,跳跃体力消耗率,能力峰值
跳跃行为:(跳跃高度、累计跳跃高度,累计跳跃次数),发现这三个量都与跳跃高度有关系,然后又发现求出跳跃高度的关键是求出率,所以就需要一个根据当前体力值获取率的方法。

体力能力比数据类:
数据:体力边界,体力能力比值(题目给的)
需要确定下界,所以需要有isMatched()方法

裁判:
单次裁决:add()方法灌输数据
多次裁决:排序

规则类:
可以先定义好常量和常规的方法(getxxx...)
一些具体的方法根据上述的需求分析再来确定。

结果呈现

image.png
image.png
image.png

运动员数据类

public class PlayerData implements Comparable<PlayerData>{
   
   
    /**
     * 玩家的数组下标
     */
    private int playerIndex;
    /**
     * 玩家的跳跃数据
     */
    private int count;



    public PlayerData(int playerIndex, int count) {
   
   
        this.playerIndex = playerIndex;
        this.count = count;
    }

    public int getPlayerIndex() {
   
   
        return playerIndex;
    }

    public int getCount() {
   
   
        return count;
    }


    @Override
    public int compareTo(PlayerData pd) {
   
   
        return count-pd.getCount();
    }
}

1 .数据类的定义:通常指包含一些get,set方法,同时也可能会实现Serialzable,Comparable等接口来实现数据类的可序列化和可比较特性,通常并不包含特殊方法。
2 .何时定义一个数据类?
数据类可以将不同种类的多个数据封装成为一个实体对象 ,当需要被多个模块或者方法使用的时候则可以定义数据类。(将数据提取出来,并进行操作)

体力能力比数据类

/**
 * 数据类:体力和能力的比值
 */
public class StrengthAbilityRatio {
   
   
    /**
     * 封装(超出基本类型范畴)
     * 体力边界值
     */
    private int strengthBound;
    /**
     * 当前体力边界值条件下所能发挥的能力比值
     */
    private float ratio;

    public StrengthAbilityRatio(int strengthBound, float ratio) {
   
   
        this.strengthBound = strengthBound;
        this.ratio = ratio;
    }

    /**
     * 没有用set的原因是 不能修改 构造器已经赋值了
     * 提取体力值边界
     * @return int
     */
    public int getStrengthBound() {
   
   
        return strengthBound;
    }

    /**
     * 提取当前体力值对应的比值
     * @return float
     */
    public float getRatio() {
   
   
        return ratio;
    }
    public boolean isMatched(float strength){
   
   
        return strength >= strengthBound;
    }
}

该类是在普通数据类的基础上添加了isMatched()方法便于将当前体力值与体力边界值的界限进行匹配,isMatched()方法本身是配合规则类中的getRatiosByStrength()方法一起使用的,通过当前体力值得到体力界限,通过体力界限和ratios数组得到跳跃高度比率。

规则类

public class Rule {
   
   
    private Scanner input;
    private Random rand;
    private final String NAME_REGEX = "^[\u4e00-\u9fa5]{2,}$";
    private final String DIGIT_REGEX = "^[0-9]{1,}$";
    /**
     * 体力和能力比值
     */
    private StrengthAbilityRatio[] ratios;
    public Rule(){
   
   
        ratios = new StrengthAbilityRatio[5];
        ratios[0] = new StrengthAbilityRatio(9,1);
        ratios[1] = new StrengthAbilityRatio(8,0.85f);
        ratios[2] = new StrengthAbilityRatio(6,0.7f);
        ratios[3] = new StrengthAbilityRatio(5,0.55f);
        ratios[4] = new StrengthAbilityRatio(0,0.3f);
        rand = new Random();//避免重复创建随机的工具对象
        input = new Scanner(System.in);
    }


    public String getInputName(String title){
   
   
        String name = null;
        System.out.printf("请输入"+title+"昵称:");
        do{
   
   
            name = input.nextLine();
            if(name.matches(NAME_REGEX))
                break;
            System.out.println("昵称必须为两个汉字以上的形式!请重新输入:");
        }while(true);
        return name;
    }

    /**
     * 根据用户剩余的体力值,获取能够发挥的能力值
     * @param strength 剩余体力
     * @return 比值
     */

    private float getRatioByStrength(float strength){
   
   
        for(StrengthAbilityRatio sar : ratios){
   
   
            if (sar.isMatched(strength)) {
   
   
                return sar.getRatio();
            }
        }
        return 0;
    }

    /**
     * 获取指定范围内的随机小数值
     * @param min 区间下限
     * @param max 区间上限
     * @return float
     */
    public float randomBetween(int min,int max){
   
   
        return min+rand.nextInt((max-min)*100)/100.0f;
    }

    /**
     * 获取控制台输入一个指定范围的整数
     * @param title
     * @param min
     * @param max
     * @return
     */
    public int getInputInt(String title,int min,int max){
   
   
        int digit = 0;
        do{
   
   
            System.out.print("请输入"+title+":");
            String num = input.next();
            if(num.matches(DIGIT_REGEX)){
   
   
                digit = Integer.parseInt(num);
                if(digit >= min && digit <= max){
   
   
                    break;
                }
                System.out.println("数值必须位于"+min+"~"+max+"之间,请重新输入:");
            }
        }while(true);
        return digit;
    }

    /**计算跳跃力和体力的消耗(选择合并为一个方法的原因:每次跳跃都会产生跳跃力消耗 并【伴随着】体力消耗 【伴随】说明【关联性】)
     * 注意临界:{
     *     如果达到了目标高度,但剩余体力为负值,说明剩余体力不足以支撑一次跳跃。
     *     如果要考虑此问题,只能按实际剩余的体力实施跳跃,而实际跳跃高度也成对应的比例关系。【临界问题】
     * }
     * @param strengthLeft 剩余体力
     * @param jumpAbility 跳跃力峰值
     * @param costRatio 体力消耗率
     * @return float[0]:实际的跳跃力 float[1]:体力的消耗值
     */
    public float[] getJumpAbilityAndStrengthCost(float strengthLeft,float jumpAbility,float costRatio){
   
   
        // 此处:strengthLeft 就是剩余体力,当剩余体力为0时,跳的高度肯定为0,但还没有达到目标高度
        float realJumpAbility = getRatioByStrength(strengthLeft)*jumpAbility;
        float realStrengthCost = realJumpAbility*costRatio;
        //如果实际消耗 超过跳前剩余的体力(最后一次的临界情况)
        if(strengthLeft < realStrengthCost){
   
   
            realJumpAbility *= strengthLeft / realStrengthCost;//比例关系!!!!!!
            realStrengthCost = strengthLeft;//只能消耗这么多的体力
        }
        return new float[] {
   
   realJumpAbility,realStrengthCost};
    }
}

关键点:在猜拳游戏中,笔者已经对getxxx方法的基本形式做出了总结,此处新增了一个常用方法,获取一个在一定范围内的随机小数。

public float randBetween(int min,int max){
   
   
    return min + (rand.randInt(max))*100/100.0f;
}

玩家类

public class Player extends Rule{
   
   
    /**
     * 昵称
     */
    private String nickName;
    /**
     * 能力峰值
     */
    private float jumpAbility;
    /**
     * 体力消耗率
     */
    private float strengthCostRatio;
    /**
     * 当期体力
     */
    private float currentStrength;
    public Player(){
   
   
        nickName = getInputName("运动员");
        jumpAbility = randomBetween(1,3);
        strengthCostRatio = randomBetween(0,1);
        currentStrength = randomBetween(5,10);
    }

    /**
     * 获取运动员姓名
     * @return String
     */
    public String getNickName() {
   
   
        return nickName;
    }

    /**
     * 根据目标高度实施跳跃并获取实际跳跃的次数
     * @param targetHeight 未知的东西选择入参 targetHeight 对运动员来说是未知的 在方法中不能改变的值一定要设置为常量
     * @return int 达到目标高度的次数 0:表示淘汰
     *                              >0: 表示成功
     */
    public int jump(final int targetHeight){
   
   
        int count = 0;
        float accJump = 0;
        /*存储累计跳跃的高度
          累计跳跃的高度小于targetHeight 是循环条件
         */
        while(accJump < targetHeight && currentStrength > 0){
   
   
            float [] arr = getJumpAbilityAndStrengthCost(currentStrength,jumpAbility,strengthCostRatio);
            // arr[0]表示单次跳跃高度,arr[1]单次表示消耗体力
            accJump += arr[0];
            currentStrength -= arr[1];
            count++;
            System.out.println(nickName+"本次跳了"+ arr[0] +"米 累计跳跃高度 " + accJump + "米 用了"+ count +" 次 剩余体力 "+currentStrength);
        }
        return currentStrength <=0 ? 0 : count;
    }


}

裁判类

public class Judge extends Rule {
   
   
    private PlayerData[] pds;// 运动员数据
    private int size;// 下标
    private int targetHeight = getInputInt("目标高度",10,15);//目标高度 目标高度的范围通过测试大概得出比较合适(有一定概率能够达到)的目标高度的范围
    public Judge(int size){
   
   //运动员数量
        pds = new PlayerData[size];
        this.size = 0;
    }

    /**获取目标高度
     * @return int 目标高度
     */

    public int getTargetHeight() {
   
   
        return targetHeight;
    }

    /**
     * 添加完成比赛的运动员的数据(此处的数据表示玩家相关联的下标和跳跃次数 如何使其相关联?---构造一个类和对象数组)
     * @param playerIndex 玩家下标
     * @param count 玩家跳跃的次数
     */
    public void add(int playerIndex,int count){
   
   
        pds[size++] = new PlayerData(playerIndex, count);
    }

    /**
     * 归并排序法 对所有玩家数据中的跳跃数据进行降序排序
     * @param pds
     * @param begin
     * @param end
     * @return
     */

    private PlayerData[] mergeSort(int begin,int end,PlayerData[] pds){
   
   
        if(begin == end){
   
   
            return new PlayerData[]{
   
   pds[begin]};
        }
        int mid = begin + (end - begin)/2;
        final PlayerData[] leftPds = mergeSort(begin, mid, pds);
        final PlayerData[] rightPds= mergeSort(mid + 1, end, pds);
        int left = 0,right = 0,mix = 0;
        PlayerData[] mixPds = new PlayerData[end-begin+1];
        while(left<leftPds.length && right<rightPds.length){
   
   
            mixPds[mix++] = leftPds[left].compareTo(rightPds[right]) < 0?leftPds[left++] : rightPds[right++];
        }
        while(left<leftPds.length){
   
   
            mixPds[mix++] = leftPds[left++];
        }
        while(right<rightPds.length){
   
   
            mixPds[mix++] = rightPds[right++];
        }
        return mixPds;
    }
    public PlayerData[] mergeSort(){
   
   
        return mergeSort(0,size-1,pds);
    }
    /**
     * 这里用一个无参的重载来调用mergeSort的原因是 mergeSort的其中一个参数是pds数组(运动员数据)
     * 而在外部调用的时候 是无法拿到运动员数据的 所以选择通过一个无参构造使得在JumpGame中能够进行排序
     */

}

1 .裁判的裁决可以分为单次裁决(在单次游戏进程之后,通过add()方法灌输运动员数据),多次裁决(进行排序),这点和猜拳游戏相类似。
2 .信息的权限:目标高度只有裁判知道,玩家并不知道。

游戏主类

public class JumpGame extends Rule {
   
   
    /**
     * 玩家信息
     */
    private Player[] players;
    /**
     * 裁判信息
     */
    private Judge judge;

    public JumpGame(){
   
   
        final int playerNum = getInputInt("运动员数量",1,10);
        players = new Player[playerNum];
        for (int i = 0; i < playerNum; i++) {
   
   //利用遍历型循环的目的 是能够通过下标进行关联型地存储数据
            players[i] = new Player();
        }
        judge = new Judge(playerNum);
    }
    public void start() {
   
   
        System.out.println("跳跃比赛开始...");
        for (int i = 0; i < players.length; i++) {
   
   //遍历型循环 使下标能够存入PlayerData的数据类中
            Player player = players[i];
            System.out.println("-------" + player.getNickName() + " 开始跳跃-------");
            final int count = player.jump(judge.getTargetHeight());
            System.out.println("-------" + player.getNickName() + " 完成跳跃-------\n");
            judge.add(i, count);
        }
        System.out.println("------- 最终结果 -------");
        PlayerData[] pds = judge.mergeSort();
        int rank = 0,index = 0;
        final Player[] eliminatedPlayers = new Player[pds.length];
        for(PlayerData pd : pds){
   
   
            if(pd.getCount() > 0){
   
   
                rank++;
                Player player = players[pd.getPlayerIndex()];//通过玩家的下标 获取玩家的信息 从而才能输出结果的nickName等信息
                String rst = "跳跃"+pd.getCount()+"次";
                System.out.println("第"+rank+"名\t"+player.getNickName()+"\t"+rst);
            }else{
   
   
                eliminatedPlayers[index++] = players[pd.getPlayerIndex()];
            }
        }
        for(Player player:eliminatedPlayers){
   
   
            if(null != player){
   
   
                String rst = "淘汰";
                System.out.println("       \t"+player.getNickName() + "\t"+rst);
            }
        }
    }
}
目录
相关文章
|
7月前
|
存储 缓存 监控
No180.精选前端面试题,享受每天的挑战和学习
No180.精选前端面试题,享受每天的挑战和学习
|
7月前
|
缓存 前端开发 NoSQL
No192.精选前端面试题,享受每天的挑战和学习
No192.精选前端面试题,享受每天的挑战和学习
|
7月前
|
XML 缓存 JavaScript
No31.精选前端面试题,享受每天的挑战和学习
No31.精选前端面试题,享受每天的挑战和学习
|
7月前
|
JSON 前端开发 JavaScript
No44.精选前端面试题,享受每天的挑战和学习
No44.精选前端面试题,享受每天的挑战和学习
|
7月前
|
缓存 网络协议 前端开发
No48.精选前端面试题,享受每天的挑战和学习
No48.精选前端面试题,享受每天的挑战和学习
|
7月前
|
JavaScript 前端开发 算法
No97.精选前端面试题,享受每天的挑战和学习
No97.精选前端面试题,享受每天的挑战和学习
|
7月前
|
JavaScript 前端开发 安全
No49.精选前端面试题,享受每天的挑战和学习
No49.精选前端面试题,享受每天的挑战和学习
|
7月前
|
存储 前端开发 小程序
No146.精选前端面试题,享受每天的挑战和学习
No146.精选前端面试题,享受每天的挑战和学习
|
7月前
|
前端开发 搜索推荐 算法
No54.精选前端面试题,享受每天的挑战和学习
No54.精选前端面试题,享受每天的挑战和学习
|
7月前
|
自然语言处理 JavaScript 前端开发
No27.精选前端面试题,享受每天的挑战和学习
No27.精选前端面试题,享受每天的挑战和学习