-宝箱抽奖模块与代码设计
Posted kakashi8841
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了-宝箱抽奖模块与代码设计相关的知识,希望对你有一定的参考价值。
第一章-宝箱抽奖模块与代码设计(一)
简要 | 信息 |
---|---|
作者 | 卡卡 |
博客 | http://blog.csdn.net/kakashi8841 |
邮箱 | john.cha@qq.com |
本文所属专栏 | http://blog.csdn.net/column/details/12687.html |
无聊的开场白
每篇文章的背后都有个”高大上”的故事
大家好,我是卡卡(取自火影忍者中卡卡西)。其实这篇文章是快到截止日期才写的,因为自己创业中,然后最近刚好有个同学创业,很多技术上的东西需要我帮忙,因此本来和CSDN约好的这篇文章也是一拖再拖。然后在快到文章截止日期的某个晚上,有个朋友刚好和我聊起怎样提升代码质量的问题。于是就有了这篇文章。
怎样的代码才算是好的代码
Linux大神大概是这么说的,两个程序员写出的代码不同在于他们的对程序的品味不同。确实,有的人对代码比较敏感,一眼就能看出,这里写的不好,这里不灵活,这里可能有问题。
那么很多积极上进的少年肯定还是希望能提高自己代码设计的质量。本文将通过一个实例来说明怎样逐步优化代码。
简单的需求
很多游戏中都有开宝箱的功能。比如现在你收到一个任务。让你去做一个开宝箱的模块。需求如下:
1. 玩家可以看到三种白银、黄金、钻石三种类型的宝箱,分别消耗游戏中的白银、黄金和钻石。
2. 这三种宝箱玩家可以请求开1次和开10次。
简单的代码(Java实现)
一些基本的类
玩家类
存放玩家资源和其他信息的类,这里为了演示,去掉无关的属性,只有资源属性。
这个类目前的写法是比较常见的写法,但是这个类有很多优化的地方。文章后面会说。
package com.kakashi01.player.domain;
public class Player
private static final int MAX_SLIVER = Integer.MAX_VALUE;
private static final int MAX_GOLD = Integer.MAX_VALUE;
private static final int MAX_DIAMOND = Integer.MAX_VALUE;
private int id;
private int sliver; // 白银
private int gold; // 黄金
private int diamond; // 钻石
public int getId()
return id;
public void setId(int id)
this.id = id;
public int getSliver()
return sliver;
public void setSliver(int sliver)
this.sliver = sliver;
public int getGold()
return gold;
public void setGold(int gold)
this.gold = gold;
public int getDiamond()
return diamond;
public void setDiamond(int diamond)
this.diamond = diamond;
/**
* 修改白银
*
* @param alter
* 修改值。正数表示增加,负数表示减少
* @return
*/
public boolean alterSliver(int alter)
int old = sliver;
sliver = alter(sliver, alter, 0, MAX_SLIVER);
return old != sliver;
/**
* 修改钻石
*
* @param alter
* 修改值。正数表示增加,负数表示减少
* @return
*/
public boolean alterDiamond(int alter)
int old = diamond;
diamond = alter(diamond, alter, 0, MAX_DIAMOND);
return old != diamond;
/**
* 修改黄金
*
* @param alter
* 修改值。正数表示增加,负数表示减少
* @return
*/
public boolean alterGold(int alter)
int old = gold;
gold = alter(gold, alter, 0, MAX_GOLD);
return old != gold;
/**
* 修改传入的current,修改量为alter。修改后应该在[min, max]范围内。
*
* @param current
* 修改前的值
* @param alter
* 修改量
* @param min
* 下限(包含)
* @param max
* 上限(包含)
* @return 返回修改后的值
*/
private int alter(int current, int alter, int min, int max)
if (alter > 0)
current += alter;
if (current < min || current > max)
current = max;
else if (alter < 0)
if (current >= -alter)
current += alter;
if (current < min)
current = min;
return current;
抽奖服务类
这个类暂时只有一个空方法。这也是我们下面将实现的内容。
package com.kakashi01.lottery;
public class LotteryService
/**
* 抽奖方法
* @param lotteryType 宝箱类型
* @param timesType 次数类型
*/
public void lottery(int lotteryType, int timesType)
我们需要定义一个用于表示抽奖信息的类ConfigLottery。根据宝箱类型和次数类型,就可以取到抽奖信息。然后进行抽奖。
因此在LotteryService中增加一个lotteryMap(Map类型),以及getConfigLottery方法用于从lotteryType和timesType映射到ConfigLottery。
private final Map<Integer, Map<Integer, ConfigLottery>> lotteryMap = new HashMap<>();
public ConfigLottery getConfigLottery(int lotteryType, int timesType)
Map<Integer, ConfigLottery> map = lotteryMap.get(lotteryType);
if (map == null)
return null;
return map.get(timesType);
先大致确定抽奖方法lottery的逻辑,确定后的LotteryService代码如下:
package com.kakashi01.lottery;
import java.util.HashMap;
import java.util.Map;
import com.kakashi01.lottery.domain.ConfigLottery;
import com.kakashi01.player.domain.Player;
public class LotteryService
private final Map<Integer, Map<Integer, ConfigLottery>> lotteryMap = new HashMap<>();
public ConfigLottery getConfigLottery(int lotteryType, int timesType)
Map<Integer, ConfigLottery> map = lotteryMap.get(lotteryType);
if (map == null)
return null;
return map.get(timesType);
/**
* @param player
* 进行抽奖的玩家
* @param lotteryType
* 宝箱类型
* @param timesType
* 次数类型
*/
public void lottery(Player player, int lotteryType, int timesType)
ConfigLottery configLottery = getConfigLottery(lotteryType, timesType);
if (configLottery == null)
System.err.println("Can not found such ConfigLottery : " + lotteryType + ", " + timesType);
return;
if (tryCostResource(player, configLottery))
dropItem(configLottery);
else
System.err.println("Not enough resource for lottery : " + lotteryType + ", " + timesType);
return;
private void dropItem(ConfigLottery configLottery)
// TODO 掉落物品
private boolean tryCostResource(Player player, ConfigLottery configLottery)
// TODO 扣除资源
return false;
可以看到LotteryService中lottery的方法签名变了,增加了Player对象,而且还增加了存根方法dropItem和tryCostResource。这两个方法都被lottery调用。
有了上面大致的逻辑流程。我们就可以一步步来实现功能了。
扣除资源方法tryCostResource,应该怎么实现?
大家应该很容易就想到了,既然已经取到了抽奖的配置ConfigLottery,那么只要把这个抽奖需要多少资源保存在ConfigLottery中的一个字段就可以了。比如ConfigLottery中增加一个cost字段代表消耗多少资源,当lotteryTyoe为1、2、3时分别代表白银宝箱、黄金宝箱、钻石宝箱,那么自然可以根据lotteryType的值判断需要消耗什么资源。
那么,此时tryCostResource的实现代码为:
private boolean tryCostResource(Player player, ConfigLottery configLottery)
switch (configLottery.getLotteryType())
case ConfigLottery.SLIVER:
return player.alterSliver(-configLottery.getCost());
case ConfigLottery.GOLD:
return player.alterGold(-configLottery.getCost());
case ConfigLottery.DIAMOND:
return player.alterDiamond(-configLottery.getCost());
return false;
很直观,根据不同的宝箱类型。然后扣除不同的资源。并返回是否扣除成功。
对了忘记说,ConfigLottery的代码已经被改为:
package com.kakashi01.lottery.domain;
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 int cost; // 抽奖需要消耗的资源
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 int getCost()
return cost;
public void setCost(int cost)
this.cost = cost;
简单,粗暴,有效。然而这么简单的代码,也是存在一些可以优化的地方。我们文章后面再说。现在还是先赶紧实现需求。只剩下dropItem方法写完就可以下班了~
dropItem与大转盘
抽奖,开宝箱其实很像平常见到的转盘,转盘上画满各种奖品,然后旋转转盘,等待转盘停下时,指针指向的物品就是奖品。那么,应该怎样用代码描述这样的一个行为。其实,根据数学的一些基础知识,我们知道,转盘为360度。如果某个物品占据了一个80度的扇形,那么意味着这个物品被抽中的概率为80/360。而转盘上所有扇形加起来的角度之和为360度,即为概率的基数。同样的道理,我们只要有以下数据,就可以模拟转盘进行随机抽取。
我们需要该宝箱会掉落什么物品,多少数量,以及每个物品所占的比重。那么修改ConfigLottery为如下代码:
package com.kakashi01.lottery.domain;
import java.util.List;
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 int cost; // 抽奖需要消耗的资源
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 int getCost()
return cost;
public void setCost(int cost)
this.cost = cost;
public List<ConfigLotteryItem> getItems()
return items;
public void setItems(List<ConfigLotteryItem> items)
this.items = items;
可以看到相对之前只是增加了items字段。ConfigLotteryItem的代码如下:
package com.kakashi01.lottery.domain;
public class ConfigLotteryItem
private int modelID; // 物品模型ID
private int num; // 物品数量
private int weight; // 权重
public int getModelID()
return modelID;
public void setModelID(int modelID)
this.modelID = modelID;
public int getNum()
return num;
public void setNum(int num)
this.num = num;
public int getWeight()
return weight;
public void setWeight(int weight)
this.weight = weight;
可以看到ConfigLotteryItem的代码很简单,它只记录了掉落的物品模型ID,掉落的物品数量,该物品的权重。
那么,这时候就可以使用该信息来掉落物品了。修改LotteryService中dropItem的代码如下:
private void dropItem(ConfigLottery configLottery)
Random rand = new Random();
for (int i = 0; i < configLottery.getTimesType(); i++)
List<ConfigLotteryItem> items = configLottery.getItems();
int totalWeight = 0;
for (ConfigLotteryItem item : items)
totalWeight += item.getWeight();
int randNum = rand.nextInt(totalWeight);
for (ConfigLotteryItem item : items)
if (randNum < item.getWeight())
System.out.println("Drop item " + item.getModelID() + ", " + item.getNum());
break;
randNum -= item.getWeight();
这里面先计算这个抽奖信息的总权重totalWeight。策划的配置不一定使得所有物品的权重之和为100,更不一定为360。权重更多的表示的是该物品在整个转盘中所占的比例,是相对其他物品而言。因此,需要把所有物品的权重进行求和。
然后,在[0, totalWeight)区间产生随机数。你可以理解为在圆盘中的0~360之间选择一个角度。然后第二层循环则是判断该随机数落在哪个区间。落在这个区间,则表示掉落这个物品。
接下来编写测试代码观察运行结果:
package com.kakashi01.lottery;
import java.util.LinkedList;
import java.util.List;
import com.kakashi01.lottery.domain.ConfigLottery;
import com.kakashi01.lottery.domain.ConfigLotteryItem;
import com.kakashi01.player.domain.Player;
public class LotteryDemo
private static final int[] lotteryTypes = new int[] ConfigLottery.SLIVER, ConfigLottery.GOLD,
ConfigLottery.DIAMOND ;
private static final int[] timesTpyes = new int[] 1, 10 ; // 只能抽1次或10连抽
private static final LotteryService loggterService = new LotteryService();
static
// 这个static代码块中的代码实际上正式开发应该读取策划的配置表。为了演示方便在这里通过程序生成数据
for (int lotteryType : lotteryTypes)
for (int timesType : timesTpyes)
ConfigLottery configLottery = new ConfigLottery();
configLottery.setLotteryType(lotteryType);
configLottery.setTimesType(timesType);
configLottery.setCost(10000 * timesType);
List<ConfigLotteryItem> items = new LinkedList<>();
items.add(new ConfigLotteryItem(1000 * lotteryType, 1, 10));
items.add(new ConfigLotteryItem(1001 * lotteryType, 2, 20));
items.add(new ConfigLotteryItem(1002 * lotteryType, 3, 30));
configLottery.setItems(items);
loggterService.addConfigLottery(configLottery);
public static void main(String[] args)
Player player = null;
for (int lotteryType : lotteryTypes)
for (int timesType : timesTpyes)
System.out.println("Lottery#" + lotteryType + "#" + timesType);
for (int i = 0; i < 10; i++)
player = newPlayer();
System.out.println("-- " + i);
loggterService.lottery(player, lotteryType, timesType);
private static Player newPlayer()
Player player = new Player();
player.setSliver(100000);
player.setGold(100000);
player.setDiamond(100000);
return player;
以上,一个简单的测试代码就算完成了。
优化资源处理
终于做完需求了。但是,咱作为优秀的忍者,哦不,作为优秀的程序员。还记得之前提过的需要优化的地方吗。
回到最开始的Player代码。有木有发现,alterSliver、alterGold、alterDiamond这几个方法的实现和类似。虽然我们已经通过抽象逻辑,使用alter方法同时处理3种资源的修改逻辑。但是,还是不那么完美。比如,加入你游戏里面突然加入了一种新资源Power(体力)。你就得在Player中增加如下代码:
private static final int MAX_POWER = Integer.MAX_VALUE;
private int power;
public int getPower()
return power;
public void setPower(int power)
this.power = power;
public boolean alterPower(int alter)
int old = power;
power = alter(power, alter, 0, MAX_POWER);
return old != power;
相信对代码充满的追求的同学已经开始思考怎样避免这种行为了。这里你可以先想一下,再继续看下面对Player的修改:
请先思考
请先思考
请先思考
下面是修改后的Player
package com.kakashi01.player.domain;
import java.util.HashMap;
import java.util.Map;
public class Player
public static final int RESOURCE_SLIVER = 1;
public static final int RESOURCE_GOLD = 2;
public static final int RESOURCE_DIAMOND = 3;
public static final int RESOURCE_POWER = 4;
public static final int[] ALL_RESOURCES =
RESOURCE_SLIVER,
RESOURCE_GOLD,
RESOURCE_DIAMOND,
RESOURCE_POWER ;
private static final Map<Integer, Integer> RESOURCE_MAX = new HashMap<>();
static
for (int resource : ALL_RESOURCES)
RESOURCE_MAX.put(resource, Integer.MAX_VALUE);
private int id;
private Map<Integer, Integer> resources = new HashMap<>();
public int getId()
return id;
public void setId(int id)
this.id = id;
public int getResource(int resource)
Integer r = resources.get(resource);
if (r == null)
return 0;
return r.intValue();
public void setResource(int resource, int value)
resources.put(resource, value);
public boolean alterResource(int resource, int alter)
int current = getResource(resource);
int old = current;
int min = 0;
int max = getMaxResource(resource);
if (alter > 0)
current += alter;
if (current < min || current > max)
current = max;
else if (alter < 0)
if (current >= -alter)
current += alter;
if (current < min)
current = min;
setResource(resource, current);
return old != current;
public static int getMaxResource(int resource)
Integer r = RESOURCE_MAX.get(resource);
if (r == null)
return 0;
return r.intValue();
可以看到修改后的Player代码对资源的处理更加统一,甚至说,忍者,哦不,开发者,可以对资源的类型不那么敏感,如果策划想增加另一种资源,其实程序只是定义多一个资源类型而已。不需要增加大段的代码。有的人说,快速完成功能才是王道,代码不需要好的设计。难道这个设计不是能让你更快速完成功能吗?因此,很多东西不是非此即彼。不是你代码写的差,你开发效率就高的。(偷笑,别砸我)
由于Player对资源处理进行了修改,那么相应的,修改LotteryService中的tryCostResource方法。修改后代码如下:
private boolean tryCostResource(Player player, ConfigLottery configLottery)
return player.alterResource(configLottery.getLotteryType(), -configLottery.getCost());
看,又是一个好的设计减少了代码量的例子。LotteryDemo中的那几个setXXX方法,也要自己修改为setResource方法。
下班前的悬念
优化完了,也快到时间下班了。此时策划向你走了过来。拍了你的肩膀,向你投来崇拜的眼神,你已经完成了抽奖了呀。不错呀。不过。。。可能有几个需求还要改一下。策划向你留下了几个需求:
1. 每种宝箱有可能不是消耗对应的资源。比如,白银宝箱可能也可以消耗黄金来开启。
2. 10连抽现在只是简单的循环抽了10次,需要改成10连抽里面9次是正常抽的,而有1次会掉落更高级的东西。
此时如果是你,你会在上面的代码进行怎样的调整,使得适应新的需求。由于时间和篇幅的关系,下一篇文章会继续宝箱抽奖模块的制作。程序员和策划之间的斗智斗勇还在继续。
本文项目可以在https://github.com/johncha/CodeDesign-1找到。请先阅读git中的README.md查看项目的使用说明。
如果你对本文有什么建议或意见,可以发邮件到john.cha@qq.com或到blog.csdn.net/kakashi8841中留言。
以上是关于-宝箱抽奖模块与代码设计的主要内容,如果未能解决你的问题,请参考以下文章