最近入坑了和平精英,算是我开始玩的第一款腾讯系的手游。众所周知手游是氪金大户,而对于和平精英来说,氪金主要也就是为了抽各种皮肤了。什么人物的衣服,背包,头盔,枪械的外观,甚至是车的品牌,都是可以通过氪金来获得的。作为刚入坑的小白,我在打开活动页面之后,直接被各种复杂的规则吓傻了:什么普通追加,保护追加,保底奖励,升降级概率,各种概念层出不穷,琳琅满目。本着“一切到最后都是数学问题”的原则,我决定写个模拟抽扭蛋测试的代码,来看看到底平均需要花多少钱,才能抽到自己想要的皮肤。
规则说明
首先稍微介绍一下规则,这里以最近出的“激战未来”系列为例:
- 花费6幸运币启动扭蛋机,随机获得一个1-3星的扭蛋,不同的扭蛋类型与星级排列组合,对应不同的奖励;
- 普通追加:免费,20%概率升星成功(因为有五个扭蛋类型,和当前类型一致才能升星),随机提高1-3星;80%升星失败,随机降低1-2星,并直接领取奖励;
- 保护追加:需要花费幸运币,20%概率升星成功,失败不掉星,3次保底必升星成功,随机提高1-3星。但当前扭蛋星级越高,保护追加一次的幸运币消耗越高;
- 升降星概率:82%升1星、17%升2星和1%升3星;75%掉1星,25%掉2星;
- 领取奖励:根据当前的扭蛋等级与星级,可以选择直接领取对应物品或者换算成碎片(一种可以兑换其他物品的货币),但要注意的是:如果对应的物品你现在没有的话,换算碎片会是物品价格的一半(后面会详细说明)。
然后我们来看一下不同星级对应的碎片价值:
星级 | 碎片价值 |
---|---|
0 | 2 |
1 | 12 |
2 | 36 |
3 | 108 |
4 | 320 |
5 | 960 |
6 | 2880 |
7 | 8640 |
要说明一下的是:这里的碎片价值是指每一星级对应物品在兑换商店的价格,比如说你想要五级的“绚金战神”的皮肤,那么你需要在商店里用960个碎片去兑换,或者你需要抽到“绚金战神”的扭蛋并追加到五星。但同星级的不同扭蛋之间是不可以等价兑换的,除非你已经拥有了那个星级的对应物品,举个例子:假如我现在什么物品都没有,而我想要“绚金战神”的五星皮肤,那么当我抽到“苍蓝魅影”的扭蛋并追加到五星,我只有两个选择:1.兑换“苍蓝魅影”的五星皮肤,放弃“绚金战神”;2.折算成扭蛋碎片960/2 = 480个,然后继续获得碎片直到获得960个碎片,去兑换商店去进行兑换。但如果我已经有了“苍蓝魅影”的五星皮肤,而此时我又抽到“苍蓝魅影”的扭蛋并追加到五星,那么我就可以直接折算960个碎片并去兑换其他同价格的五星皮肤,此时相当于不同扭蛋类型之间进行了等价兑换。
这其实是一个重要的规则,因为它让你在“退而求其次”和“从一而终”之间做出一个取舍,大部分情况下我们都会选择“退而求其次”,也就是兑换了价值更高但并不是最喜欢的那款,从而导致之后还是想要一开始看上的那款,然后重新花钱去抽。所以这里做出的选择,一定要在一开始就好好考虑清楚。
策略分析
于是这里就可以引出我们平常一般使用的几种策略:傻瓜式平A,普通追加,保护追加。我们来依次解释一下策略内容,然后用代码进行模拟试抽,通过计算10000次试抽的平均收益,来评估一下不同的策略适合什么样的氪金目的。
先来创建几个需要用到的辅助类:
/**
* 单次抽扭蛋的结果类
*/
data class DrawEggResult(
val egg: Egg, // 扭蛋类型
val level: Int // 星级
)
/**
* 不同策略下抽扭蛋的最终结果类
*/
data class DrawingResult(
val spending: Int, // 幸运币总花销
val points: Int, // 获得碎片总数
// 已兑换物品记录
val eggItems: Map<Egg, MutableList<Boolean>> = mutableMapOf<Egg, MutableList<Boolean>>().apply {
Egg.values().forEach { egg ->
this[egg] = mutableListOf(true, true, true, false, false, false, false, false)
}
}
)
/**
* 定义扭蛋的枚举类
*/
enum class Egg(val description: String) {
EGG_1("苍蓝魅影"),
EGG_2("翡翠骑士"),
EGG_3("绚金战神"),
EGG_4("炫彩锋芒"),
EGG_5("幻彩光梭")
}
傻瓜式平A
这个策略简单来说就是:只抽一轮,决不追加,先兑换三级物品,再兑换碎片。因为追加其实存在很大的风险:80%的可能性扭蛋类型不一致,那么就会降1-2星,所以我们只抽一轮,不给它降星的机会,如果是三星,立刻兑换对应物品,其他时候则直接兑换碎片。通过碎片的快速积累来直接兑换我们最终想要的物品。话不多说,上代码:
private val pointsList = listOf(2, 12, 36, 108, 320, 960, 2880, 8640) // 碎片价值表
/**
* 傻瓜式平A,直接兑换三级物品,然后兑换碎片
* @param expectedPoints 目标碎片数
* @param spendingBudget 幸运币预算
*/
fun dummyDrawing(expectedPoints: Int, spendingBudget: Int): DrawingResult {
var spending = 0
var points = 0
// 记录已兑换物品
val eggItemRetrieved = mutableMapOf<Egg, MutableList<Boolean>>().apply {
Egg.values().forEach { egg ->
this[egg] = mutableListOf(true, true, true, false, false, false, false, false)
}
}
// 当碎片数达到目标或者幸运币超出预算时停止
while (points < expectedPoints && spending < spendingBudget) {
spending += 6
val result = getNextDrawResult()
if (eggItemRetrieved[result.egg]?.get(result.level) == true) {
points += pointsList[result.level] // 已有兑换物品,获得全额碎片返还
} else {
eggItemRetrieved[result.egg]?.set(result.level, true) // 没有当前物品,兑换该物品
}
}
return DrawingResult(spending, points, eggItemRetrieved)
}
/**
* 抽一次扭蛋并返回结果
*/
internal fun getNextDrawResult(): DrawEggResult {
// 扭蛋类型
val resultEgg = Egg.values()[randomPick(Egg.values().size)]
// 扭蛋级别
return when (randomPick(100)) {
0 -> DrawEggResult(resultEgg, 3)
in 1 until 18 -> DrawEggResult(resultEgg, 2)
else -> DrawEggResult(resultEgg, 1)
}
}
private fun randomPick(total: Int): Int {
return Random.nextInt(total)
}
然后在单元测试中,设置幸运币预算为648,相当于充一个648,打印10000次测试的结果
@Test
fun testAverageDummyDrawing() {
var totalSpending = 0 // 幸运币总花费
var totalGetPoints = 0 // 净获得的碎片数量
var totalEqualPoints = 0 // 物品折算的总碎片数量
val drawingTimes = 10000
for (i in 0 until drawingTimes) {
val drawingResult = drawing.dummyDrawing(10000, 648) // 满足最高幸运币预算的条件即可
print("第 $i 次傻瓜式平A,花费幸运币:${drawingResult.spending},获得碎片:${drawingResult.points} ")
for (egg in Egg.values()) {
if (drawingResult.hasItemRetrieved(3, egg)) {
print("3星物品:${egg.description} ")
}
}
println("折算碎片: ${drawingResult.points + drawingResult.retrievedItemToPoints()} ")
totalSpending += drawingResult.spending
totalGetPoints += drawingResult.points
totalEqualPoints += drawingResult.points + drawingResult.retrievedItemToPoints()
}
val averageSpending = totalSpending.toDouble() / drawingTimes
val averageGetPoints = totalGetPoints.toDouble() / drawingTimes
val averageEqualPoints = totalEqualPoints.toDouble() / drawingTimes
println("平均花费幸运币:${averageSpending},平均获得碎片:${averageGetPoints},平均折算碎片:${averageEqualPoints},获得性价比:${averageGetPoints / averageSpending},折算性价比:${averageEqualPoints / averageSpending}")
}
最终结果: 平均花费幸运币:648.0,平均获得碎片:1734.8796,平均折算碎片:1840.0176,获得性价比:2.68,折算性价比:2.84
其实如果我们用上面的价值表格来计算性价比期望:
E = (P1V1 + P2V2 + P3V3) / 6 = 0.822 + 0.176 + 0.0118 = 2.84
可以看出这个值就是”折算性价比“,而”获得性价比“将近0.2的降幅主要是因为我们需要先兑换三级物品,才能拿到满额的108碎片返还。
优点
- 策略简单有效,最终比例稳定
- 同时有一定概率获得三级物品
- 适合有着明确的目标(只想要一套五级皮肤,或者只想要一辆六星车皮等情况)的玩家选择
缺点
- 花费时间(平A需要不停地抽&兑换,比较慢)
- 只能获得三星物品,高于三星的物品都需要用碎片兑换
- 对于想追高7星奖励的玩家来说,可能效率比较低
- 对于想体验风险,获得高回报的玩家来说,收获太低
普通追加
这个策略的大致思路就是:见好就收,只使用普通追加,一旦抽并追加到五星及以上,则先兑换物品,再兑换碎片。因为普通追加是免费的,所以我们抽一次的成本并没有增加,而与此同时我们有更大的概率直接获得5,6,7星的物品。跟上面的傻瓜式平A一样,我们需要先兑换物品,在兑换碎片,这样可以使我们再次到达同样的星级时获得100%的碎片返还。代码如下:
/**
* 普通追加,一旦抽并追加到五星及以上,则先兑换物品,再兑换碎片
* @param expectedPoints 目标碎片数
* @param spendingBudget 幸运币预算
*/
fun normalAddingDrawing(expectedPoints: Int, spendingBudget: Int): DrawingResult {
var spending = 0
var points = 0
// 记录已兑换物品
val eggItemRetrieved = mutableMapOf<Egg, MutableList<Boolean>>().apply {
Egg.values().forEach { egg ->
this[egg] = mutableListOf(true, true, true, false, false, false, false, false)
}
}
// 当碎片数达到目标或者幸运币超出预算时停止
while (points < expectedPoints && spending < spendingBudget) {
spending += 6
var currentLevel = 0
var currentEgg: Egg? = null
while (true) {
val result = getNextDrawResult()
if (currentEgg == null) {
// 第一次抽取结束,准备继续追加
currentEgg = result.egg
currentLevel = result.level
} else {
if (currentEgg == result.egg) {
// 追加成功
currentLevel = min(currentLevel + result.level, 7)
if (currentLevel > 4) {
if (eggItemRetrieved[result.egg]?.get(currentLevel) == true) {
points += pointsList[currentLevel] // 积分100%返还
} else {
eggItemRetrieved[result.egg]?.set(currentLevel, true) // 兑换对应物品
}
// 本轮结束,直接退出
break
}
} else {
// 追加失败,本轮结束,结算碎片并退出
val nextLevel = max(currentLevel - getDecreasingLevel(), 0)
points += pointsList[nextLevel] / (if (eggItemRetrieved[result.egg]?.get(nextLevel) == true) 1 else 2)
break
}
}
}
}
return DrawingResult(spending, points, eggItemRetrieved)
}
/**
* 返回随机降星的星级
*/
private fun getDecreasingLevel(): Int {
return when (randomPick(4)) {
0 -> 2
else -> 1
}
}
同样通过单元测试,设置幸运币预算为648,相当于充一个648,打印10000次测试的结果
@Test
fun testAverageNormalAddingDrawing() {
var totalSpending = 0 // 幸运币总花费
var totalGetPoints = 0 // 净获得的碎片数量
var totalEqualPoints = 0 // 物品折算的总碎片数量
val drawingTimes = 10000
for (i in 0 until drawingTimes) {
val drawingResult = drawing.normalAddingDrawing(10000, 648) // 满足最高幸运币预算的条件即可
print("第 $i 次普通追加,花费幸运币:${drawingResult.spending},获得碎片:${drawingResult.points} ")
for (egg in Egg.values()) {
for (level in 5..7) {
if (drawingResult.hasItemRetrieved(level, egg)) {
print("${level}星物品:${egg.description} ")
}
}
}
println("折算碎片: ${drawingResult.points + drawingResult.retrievedItemToPoints()} ")
totalSpending += drawingResult.spending
totalGetPoints += drawingResult.points
totalEqualPoints += drawingResult.points + drawingResult.retrievedItemToPoints()
}
val averageSpending = totalSpending.toDouble() / drawingTimes
val averageGetPoints = totalGetPoints.toDouble() / drawingTimes
val averageEqualPoints = totalEqualPoints.toDouble() / drawingTimes
println("平均花费幸运币:${averageSpending},平均获得碎片:${averageGetPoints},平均折算碎片:${averageEqualPoints},获得性价比:${averageGetPoints / averageSpending},折算性价比:${averageEqualPoints / averageSpending}")
}
最终结果为:平均花费幸运币:648.0,平均获得碎片:797.8586,平均折算碎片:1934.5946,获得性价比:1.23,折算性价比:2.99
可以看出,如果只算净获得的碎片,那么性价比相对较低,但如果把兑换的物品按碎片价格折算一下,那么可以看到,折算后的性价比和傻瓜式平A策略相比提高了0.15。
优点
- 折算后性价比更高,总收益更大
- 对于欧皇,更容易一发入魂,获得相当大的收益
- 适合喜欢承受一定风险,想获得更高回报的玩家选择
缺点
- 兑换的物品不一定是你最想要的,对于已经有确定目标的玩家来说,容易抽到自己不想要的皮肤或其他物品
- 普通追加上下限更高,更看运气,一旦接近了下限,很有可能获得性价比接近1,甚至不到1的惨痛结果
- 对于想稳定只一套物品就够的玩家,风险太大,不一定能承受
保护追加
对于保护追加,可以参考这篇文章对于它的分析,基本上可以说肯定是亏的,毕竟是拿钱来补花费的时间嘛。所以这里就不再用代码来模拟了,性价比肯定是没有前两种策略高的。
总结 & 说明
对于只想花一小部分钱(1000以下)获得1-2个特定皮肤的小伙伴,直接选择傻瓜式平A
对于想搏一搏,单车变摩托,或者收集控,想攒齐皮肤获的小伙伴,可以选择普通追加
对于重度氪金玩家,不差钱,差时间的小伙伴,直接保护追加,短平快
最后说明一下,这不是软文,腾讯也没有给我任何好处。毕竟“买的不如卖的精”,这些概率早就是算好的了,作为玩家再怎么模拟也占不到任何便宜,这里只是想让没有抽扭蛋经验的小伙伴有一个简单的了解和期望。最后一句:游戏有风险,氪金需谨慎,希望大家玩得开心就好。