第三章-宝箱抽奖模块与代码设计(三)
作者 卡卡
博客 http://blog.csdn.net/kakashi8841
邮箱 john.cha@qq.com
上集回顾
* 由于本文章关联性较强,因此建议先阅读前一篇文章http://blog.csdn.net/kakashi8841/article/details/52374714 *
上集我们完成了代码后,虽然心中还想着优化代码,但是最终还是按捺不住诱惑想跑去约会了。
我:喂,是小丽吗?嗯,对,我们上次说好的今天吃饭。
小丽:啊?!啊。。。对。。可是我刚好有事,不好意思。要不下次?
我:。。。。
小丽:怎么啦?你怎么不说话啦?
我:没事,我。。。你刚刚说的是啥?我没听清楚。要不我们下次再聚吧?我突然想到怎么优化代码。
小丽:。。。
我:好嘛,就这次。88。
小丽:。。。
有时候我真佩服自己真**的敬业~才刚刚想到的三个问题居然瞬间来了解决的灵感。
哈哈。还吃啥呐。走,回去写代码去。^0^
* 三个问题是这样的:*
1. 如果某些抽奖需要同时消耗两种资源怎么办
2. 感觉需求2的实现好像不是很通用,能不能把抽1次和抽10次的代码统一化,这样不需要if分支。
3. 10连抽的时候能不能不要刻意去区分前九次抽奖和第10次抽奖。
需求1实现
由于我们之前章节的处理,使得我们可以透明(“透明”指的是,策划可以随意配置消耗任意的资源类型和资源值,而程序不关心到底消耗的是什么)地处理资源。因此,想从消耗一种资源变成消耗两种。其实,可以进行这样的修改。
首先新增一个类,用于表示资源的类型和资源的值
package com.kakashi01.common;
public class KeyAndValue {
private int key;
private int value;
public KeyAndValue(int key, int value) {
super();
this.key = key;
this.value = value;
}
public int getKey() {
return key;
}
public int getValue() {
return value;
}
}
修改ConfigLottery
去掉之前的cost和costType字段,增加KeyValue类型的数组字段costs。修改后的ConfigLottery代码如下
package com.kakashi01.lottery.domain;
import java.util.List;
import com.kakashi01.common.KeyAndValue;
public class ConfigLottery {
public static final int SLIVER = 1;
public static final int GOLD = 2;
public static final int DIAMOND = 3;
private int lotteryType;
private int timesType;
private KeyAndValue[] costs; // 抽奖需要消耗的资源
private List<ConfigLotteryItem> items; // 掉落的物品
public int getLotteryType() {
return lotteryType;
}
public void setLotteryType(int lotteryType) {
this.lotteryType = lotteryType;
}
public int getTimesType() {
return timesType;
}
public void setTimesType(int timesType) {
this.timesType = timesType;
}
public List<ConfigLotteryItem> getItems() {
return items;
}
public void setItems(List<ConfigLotteryItem> items) {
this.items = items;
}
public KeyAndValue[] getCosts() {
return costs;
}
public void setCosts(KeyAndValue[] costs) {
this.costs = costs;
}
}
LotteryService
修改tryCostResource方法,改为扣除ConfigLottery中costs数组的指定的资源。
private boolean tryCostResource(Player player, ConfigLottery configLottery) {
return player.costResources(configLottery.getCosts());
}
而调用player的costResources方法代码如下:
修改Player
Player中增加如下方法
public boolean costResources(KeyAndValue[] costs) {
if (!isEnough(costs)) {
return false;
}
for (KeyAndValue keyAndValue : costs) {
alterResource(keyAndValue.getKey(), keyAndValue.getValue());
}
return true;
}
public boolean isEnough(KeyAndValue[] keyAndValues) {
for (KeyAndValue keyAndValue : keyAndValues) {
if (getResource(keyAndValue.getKey()) < keyAndValue.getValue()) {
return false;
}
}
return true;
}
至于LotteryDemo中报错的代码,就留给你修改了。如果你懒得修改了,也可以切换代码分支到step-2-0来获得目前的完整代码。
至此,我们完成了第1个需求的实现。
需求2、3实现
先回顾下我们现在抽奖的方法,看LotteryService中的dropItem方法,如下:
private void dropItem(ConfigLottery configLottery) {
Random rand = new Random();
if (configLottery.getTimesType() == 1) {
dropOnce(rand, configLottery);
} else if (configLottery.getTimesType() > 1) {
for (int i = 0; i < configLottery.getTimesType() - 1; i++) {
dropOnce(rand, configLottery);
}
dropOnce(rand, getConfigLottery(configLottery.getLotteryType(), 0));
}
}
看代码可以发现,我们之所以判断timesType是否为1,是因为,如果为1,则采用策划配置的数据掉落1次。如果不为1,则前N-1次采用策划配置的数据进行掉落。而第N次则采用策划配置的另一条特殊数据,这条数据的timesType为0。
举个直观的例子:
假如策划想要配置了1个黄金宝箱(lotteryType为2)的10连抽,而且要求是前9次从一个转盘随机掉物品,第10次则从另一个更高级的转盘(比如这个转盘全部是紫色的物品)掉物品。那么目前策划需要配置两个ConfigLottery数据。分别如下:
lotteryType | timesType | costs | items |
---|---|---|---|
2 | 10 | 1:1000,2:1 | 100:2,101:2,103:3 |
2 | 0 | 1:1000,2:1 | 200:2,201:2,203:3 |
也就是我们引用了timesType为0这个特殊情况来处理策划的10连抽必出xxx的需求。
看起来好像是个“小聪明”,至少抽奖的掉落代码我们是重用。但是,还有没有更优雅的方式来实现这个需求呢。答案是肯定的。我们继续看。
如果我们能把10连抽每次抽奖得到的东西交给策划来配置,那么不就可以使用代码统一处理1次和10次抽奖吗?
因此,我想了下面的处理方法。一个ConfigLottery代表的是一种抽奖。比如黄金宝箱抽1次和黄金宝箱10连抽,这就是两种抽奖。而我代码其实根本不需要关心他们到底是几连抽。我们只要扣除抽奖所需要的资源,然后抽奖。扣除抽奖所需要的资源上面的需求1的实现已经支持策划配置消耗任意资源。那么,接下来要做的就是,支持策划配置一个抽奖到底能抽几次东西,每次出什么。
增加一个抽奖物品库的类ConfigLotteryItemLib
package com.kakashi01.lottery.domain;
import java.util.List;
import java.util.Random;
public class ConfigLotteryItemLib {
private int modelID;
private List<ConfigLotteryItem> items; // 掉落的物品
private int totalWeight;
public ConfigLotteryItemLib(int modelID, List<ConfigLotteryItem> items) {
super();
this.modelID = modelID;
this.items = items;
for (ConfigLotteryItem item : items) {
totalWeight += item.getWeight();
}
}
public int getModelID() {
return modelID;
}
public ConfigLotteryItem randomItem(Random rand) {
int randNum = rand.nextInt(totalWeight);
for (ConfigLotteryItem item : items) {
if (randNum < item.getWeight()) {
System.out.println("Drop item " + item.getModelID() + ", " + item.getNum());
return item;
}
randNum -= item.getWeight();
}
return null;
}
}
这个类用于表示一个随机物品库,调用randomItem则可以产生一个随机物品。
可以看到这个类其实的大部分代码都比较熟悉了。构造函数中的for循环是之前LotteryService中dropOnce的第一个循环的内容。用于计算总的权重,在构造函数进行计算,避免了每次调用randomItem都计算。randomItem的方法则是之前LotteryService中dropOnce的第二个循环中的代码稍微修改而来。那么这个类应该怎么用呢。这个类主要为了将ConfigLottery中的items字段的功能进行增强。之前一条ConfigLottery调用一次代表的是一次掉落。所以掉落10次只能通过timesType来识别。然后进行循环,最后导致策划需要在第十次掉落不同东西的时候,我们代码还额外处理了一下。
现在有了这个类,我们修改ConfigLottery中的代码。
修改ConfigLottery
修改之前的items字段的类型,由
List<ConfigLotteryItem> items
改为:
int[] configLotteryItemLibModelIDs
这样修改是什么意思?
之前的List代表的是一个转盘中的所有物品。而现在的ConfigLotteryItemLib其实已经是对应一个转盘了。那么为什么要改成int[] configLotteryItemLibModelIDs,如果你再想想,或许你也可以猜测到我这么改的意图,也就是,刚刚说的,我想把每一条ConfigLottery应该怎么抽的这个控制权完全交给策划。
比如现在有两个转盘ConfigLotteryItemLib的数据为:
modelID | items |
---|---|
1 | 100:2,101:2,103:3 |
2 | 200:2,201:2,203:3 |
利用这个数据,我们可以配置黄金宝箱10连抽的数据为:
lotteryType | timesType | costs | configLotteryItemLibModelIDs |
---|---|---|---|
2 | 10 | 1:1000,2:1 | 1,1,1,1,1,1,1,1,1,2 |
没错,我们只要根据items中配置的转盘ID数组来取得具体的转盘配置,再进行该转盘的掉落就OK了。上面的配置就表示前9次都掉落转盘1,第10次掉了转盘2。如果想配置黄金宝箱抽一次的配置,只要像下面的一样:
lotteryType | timesType | costs | configLotteryItemLibModelIDs |
---|---|---|---|
2 | 1 | 1:120 | 1 |
可以发现配置掉1次和掉10次其实是一样的,只是消耗的资源(cost)不同,抽多少次以及从哪个物品库抽不同(由configLotteryItemLibModelIDs字段决定)。
既然配置是统一的,那么代码应该也是统一的。
修改LotteryService
修改dropItem方法
private void dropItem(ConfigLottery configLottery) {
Random rand = new Random();
int[] configLotteryItemLibModelIDs = configLottery.getConfigLotteryItemLibModelIDs();
for (int configLotteryItemLibModelID : configLotteryItemLibModelIDs) {
ConfigLotteryItemLib configLotteryItemLib = getConfigLotteryItemLib(configLotteryItemLibModelID);
configLotteryItemLib.getRandomItem(rand);
}
}
可以看到,已经去掉了抽取次数的判断,也没有去取timesType为0的特殊数据作为第十次抽奖的配置。代码的处理更加的统一。LotteryService中还需要增加的代码有:
private final Map<Integer, ConfigLotteryItemLib> lotteryItemLibMap = new HashMap<>();
public void addConfigLotteryItemLib(ConfigLotteryItemLib configLotteryItemLib) {
lotteryItemLibMap.put(configLotteryItemLib.getModelID(), configLotteryItemLib);
}
private ConfigLotteryItemLib getConfigLotteryItemLib(int modelID) {
return lotteryItemLibMap.get(modelID);
}
这几行代码比较简单,就不赘述了。
再修改LotteryDemo中配置数据的代码就OK啦。
LotteryDemo中的修改这里就不列出来了。我已经修改并提交到git中。大家可以在git中切换到step-2-1来获取本章最终代码。
最终代码分支
- 项目git地址 https://github.com/johncha/CodeDesign-1
- step-2-0 分支中获得本章最终代码。
如果你对本文有什么建议或意见,可以发邮件到john.cha@qq.com或到blog.csdn.net/kakashi8841中留言。