跳高大挑战
由于该游戏复杂度稍高,笔者建议先学习猜拳游戏的文章之后再来完成该游戏。
题目
设计
常规:游戏主类,规则类
角色player类->因为有多个角色,所以应该有playData数据类
裁判judge类
根据题目要求需要体力能力比数据类
玩家:
属性:当前体力值,跳跃体力消耗率,能力峰值
跳跃行为:(跳跃高度、累计跳跃高度,累计跳跃次数),发现这三个量都与跳跃高度有关系,然后又发现求出跳跃高度的关键是求出率,所以就需要一个根据当前体力值获取率的方法。
体力能力比数据类:
数据:体力边界,体力能力比值(题目给的)
需要确定下界,所以需要有isMatched()方法
裁判:
单次裁决:add()方法灌输数据
多次裁决:排序
规则类:
可以先定义好常量和常规的方法(getxxx...)
一些具体的方法根据上述的需求分析再来确定。
结果呈现
运动员数据类
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);
}
}
}
}