Recently, I entered the pit of peace Elite, which is the first mobile game of Tencent I started to play. We all know that mobile games are expensive, but for the peace elite, the main reason for this is to smoke all kinds of skin. What kind of clothing, backpack, helmet, gun look, even the brand of car, can be obtained through kryptonite. As a beginner, AFTER opening the activity page, I was directly shocked by all kinds of complicated rules: what ordinary add-on, protection add-on, guarantee reward, promotion probability, a variety of concepts emerged in endlessly, full of beautiful things in eyes. On the principle of “it’s all math in the end”, I decided to write a code that simulats the wiggle egg test to see how much it costs on average to get the skin I want.

Rule description

To start with, a few rules, here’s an example from the recent “Guild Wars Future” series:

  1. Spend 6 lucky coins to start the twisted egg machine, randomly get a 1-3 star twisted egg, different types of twisted egg and star arrangement combination, corresponding to different rewards;
  2. Normal add-on: free, 20% chance to raise a star (because there are five types of twisted eggs, the same as the current type to raise a star), random 1-3 star increase; If 80% star rise fails, you will be randomly reduced by 1-2 stars, and you will receive rewards directly.
  3. Protection additional: need to spend lucky coins, 20% of the probability of successful star rise, failure does not fall star, 3 times guaranteed bottom must rise star success, random 1-3 star increase. But the current twist egg star is higher, the protection of an additional lucky coin consumption is higher;
  4. Star lift probability: 82% 1 star, 17% 2 star, and 1% 3 star 75% lose 1 star, 25% lose 2 star;
  5. Receive rewards: Depending on the current level and star rating, you can either receive the item directly or convert it to shard (a currency that can be exchanged for other items), but be aware that if you don’t have the item right now, the shard will be half the price of the item (more on that later).

Then let’s look at the shard value for different stars:

The star Pieces of value
0 2
1 12
2 36
3 108
4 320
5 960
6 2880
7 8640

Just to be clear: The shard value is the price per star of the item at the exchange store. For example, if you want a level 5 “Gorgeousness” skin, you need to exchange 960 shards in the store, or you need to draw a gorgeousness “Gorgeousness” twisted egg and add five stars to it. But with the star is not equivalent exchange between different twisted egg, unless you already have the star of the corresponding items, for example: if I now what all have no, and I want to “ribbon golden ares” five-star skin, so when I draw the “pale blue phantom of the opera” the twisted egg and appended to the five star, I only have two choices: 1. Exchange “pale blue phantom” five-star skin, give up “gorgeous gold warlord”; 2. Convert to twisted egg shards 960/2 = 480, then keep getting shards until you get 960 shards to redeem at the exchange shop. But if I already have the “Pale Blue Phantom” five-star skin, and I draw the “pale Blue Phantom” twisted egg and add five stars, then I can directly convert 960 pieces and exchange them for other five star skin at the same price, which is equivalent to exchanging the different twisted egg types.

This is an important part of the rules, because it allows you to “settle for second best” and “stick” to make a trade-off between, in most cases we can choose “settle for second best”, that is to exchange the higher value but not the favorite model, and leading to still want the paragraph after the beginning, and then pay to smoke again. So the choices you make here must be carefully considered at the beginning.

Strategy analysis

So here we can lead to the common use of several strategies: fool flat A, ordinary append, protected append. Let’s explain the strategies in turn, and then run a simulation test with the code to evaluate what kind of purpose the different strategies fit by calculating the average return of 10,000 tests.

Let’s create a few helper classes that we need to use:

/** * Single pull twist egg result class */
data class DrawEggResult(
    val egg: Egg,   // Twist the egg type
    val level: Int  / / star
)

/** * The final result class of different strategies to pull the twisted egg */
data class DrawingResult(
    val spending: Int.// The total cost of lucky coins
    val points: Int.// Get the total number of fragments

    // Records of exchanged items
    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)}})/** * Defines an enumeration class */
enum class Egg(val description: String) {
    EGG_1("The Pale blue Shadow."),
    EGG_2("The Emerald Knight"),
    EGG_3("The Golden God of war."),
    EGG_4("Bright edge."),
    EGG_5("Phantom Light Shuttle")}Copy the code

Fool type flat A

The strategy is simple: draw only one round, never add more, exchange for tier 3 items first, then for shards. Because of the fact that there is a big risk: 80% of the possibility of the twisted egg type is not consistent, then will drop 1-2 stars, so we only draw round, do not give it a chance to drop stars, if it is three stars, immediately exchange for the corresponding items, other times will directly exchange for shard. Through the rapid accumulation of fragments to directly exchange for the item we ultimately want. Without further ado, the code:

private val pointsList = listOf(2.12.36.108.320.960.2880.8640)       // Scrap value table

/** * Fool type flat A, direct exchange for level 3 items, then exchange for shards *@paramExpectedPoints Number of target fragments *@paramSpendingBudget Lucky coin budget */
fun dummyDrawing(expectedPoints: Int, spendingBudget: Int): DrawingResult {
    var spending = 0
    var points = 0

    // Record the exchanged items
    val eggItemRetrieved = mutableMapOf<Egg, MutableList<Boolean>>().apply {
        Egg.values().forEach { egg ->
            this[egg] = mutableListOf(true.true.true.false.false.false.false.false)}}// Stop when shards reach target or lucky coins exceed budget
    while (points < expectedPoints && spending < spendingBudget) {
        spending += 6
        val result = getNextDrawResult()
        if(eggItemRetrieved[result.egg]? .get(result.level) == true) {
            points += pointsList[result.level]   // Have exchanged items, get the full amount of fragments returned
        } else{ eggItemRetrieved[result.egg]? .set(result.level, true)   // There is no current item, exchange the item}}return DrawingResult(spending, points, eggItemRetrieved)
}

/** * Draw the twisted egg once and return the result */
internal fun getNextDrawResult(a): DrawEggResult {
    // Twist the egg type
    val resultEgg = Egg.values()[randomPick(Egg.values().size)]
    // Twisted egg level
    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)
}
Copy the code

Then in the unit test, set the lucky coin budget to 648, equivalent to charging a 648, and print the results of 10,000 tests

@Test
fun testAverageDummyDrawing(a) {
    var totalSpending = 0       // The total cost of lucky coins
    var totalGetPoints = 0      // The net number of fragments obtained
    var totalEqualPoints = 0    // The total number of pieces converted by the item
    val drawingTimes = 10000
    for (i in 0 until drawingTimes) {
        val drawingResult = drawing.dummyDrawing(10000.648)    // Meet the requirements of the highest lucky coin budget
        print("The first$iSecond fool type flat A, spend lucky coin:${drawingResult.spending}, obtain fragments:${drawingResult.points} ")
        for (egg in Egg.values()) {
            if (drawingResult.hasItemRetrieved(3, egg)) {
                print("3 star items:${egg.description} ")
            }
        }
        println("Converted fragments:${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("Average lucky coin spent:${averageSpending}, obtain the fragments on average:${averageGetPoints}, average converted fragments:${averageEqualPoints}, obtain the cost performance:${averageGetPoints / averageSpending}, conversion cost performance ratio:${averageEqualPoints / averageSpending}")}Copy the code

The final result: the average cost of lucky coins: 648.0, the average fragment obtained: 1734.8796, the average converted fragments: 1840.0176, the cost performance: 2.68, the conversion cost performance: 2.84

In fact, if we use the above value table to calculate the cost performance expectation:

E = (P1V1 + P2V2 + P3V3) / 6 = 0.822 + 0.176 + 0.0118 = 2.84

It can be seen that this value is “converted performance-to-price ratio”, and the decrease of nearly 0.2 in “obtained performance-to-price ratio” is mainly because we need to exchange third-level items before we can get the full amount of 108 fragments returned.

advantages

  • The strategy is simple and effective, and the final ratio is stable
  • Also has a certain probability of obtaining a level 3 item
  • Suitable for players with specific goals (just a level 5 skin set, or just a six star car, etc.)

disadvantages

  • It takes time (even A needs to draw and exchange constantly, which is relatively slow)
  • You can only get Samsung items, anything higher than Samsung needs to be redeemed with shards
  • This may not be effective for players who want to increase the 7 star reward
  • For players who want to experience risk and get high rewards, the rewards are too low

Common additional

The general idea of this strategy is: good to collect, only use ordinary additional, once draw and add to five stars and above, first exchange items, and then exchange for shards. Since the regular add-ons are free, we don’t increase the cost of a draw, and we also have a higher chance of getting 5,6,7 star items directly. Like the dummy flat A above, we need to exchange items first and then exchange shards so that we can get 100% shards back when we reach the same star again. The code is as follows:

/** * Ordinary add, once draw and add to five stars and above, first exchange items, then exchange shards *@paramExpectedPoints Number of target fragments *@paramSpendingBudget Lucky coin budget */
fun normalAddingDrawing(expectedPoints: Int, spendingBudget: Int): DrawingResult {
    var spending = 0
    var points = 0

    // Record the exchanged items
    val eggItemRetrieved = mutableMapOf<Egg, MutableList<Boolean>>().apply {
        Egg.values().forEach { egg ->
            this[egg] = mutableListOf(true.true.true.false.false.false.false.false)}}// Stop when shards reach target or lucky coins exceed budget
    while (points < expectedPoints && spending < spendingBudget) {
        spending += 6

        var currentLevel = 0
        var currentEgg: Egg? = null
        while (true) {
            val result = getNextDrawResult()
            if (currentEgg == null) {
                // The first extraction is finished, ready to continue the append
                currentEgg = result.egg
                currentLevel = result.level
            } else {
                if (currentEgg == result.egg) {
                    // Append succeeded
                    currentLevel = min(currentLevel + result.level, 7)
                    if (currentLevel > 4) {
                        if(eggItemRetrieved[result.egg]? .get(currentLevel) == true) {
                            points += pointsList[currentLevel]   // Return 100% of the points
                        } else{ eggItemRetrieved[result.egg]? .set(currentLevel, true)   // Exchange the corresponding articles
                        }
                        // Exit at the end of this round
                        break}}else {
                    // If the addition fails, the round ends, the fragment is settled, and exit
                    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)
}

/** * returns the star of a random falling star */
private fun getDecreasingLevel(a): Int {
    return when (randomPick(4)) {
        0 -> 2
        else -> 1}}Copy the code

Also through the unit test, set the lucky coin budget to 648, equivalent to a 648, print the results of 10,000 tests

@Test
fun testAverageNormalAddingDrawing(a) {
    var totalSpending = 0       // The total cost of lucky coins
    var totalGetPoints = 0      // The net number of fragments obtained
    var totalEqualPoints = 0    // The total number of pieces converted by the item
    val drawingTimes = 10000
    for (i in 0 until drawingTimes) {
        val drawingResult = drawing.normalAddingDrawing(10000.648)    // Meet the requirements of the highest lucky coin budget
        print("The first$iSecond ordinary add, spend lucky coin:${drawingResult.spending}, obtain fragments:${drawingResult.points} ")
        for (egg in Egg.values()) {
            for (level in 5.7.) {
                if (drawingResult.hasItemRetrieved(level, egg)) {
                    print("${level}Star items:${egg.description} ")
                }
            }
        }
        println("Converted fragments:${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("Average lucky coin spent:${averageSpending}, obtain the fragments on average:${averageGetPoints}, average converted fragments:${averageEqualPoints}, obtain the cost performance:${averageGetPoints / averageSpending}, conversion cost performance ratio:${averageEqualPoints / averageSpending}")}Copy the code

The final result is: the average cost of lucky coin: 648.0, the average fragment: 797.8586, the average converted fragment: 1934.5946, the cost performance: 1.23, the conversion cost performance: 2.99

It can be seen that if only the net fragments are counted, the cost performance is relatively low. However, if the redeemed items are converted at the fragment price, it can be seen that the converted cost performance increases by 0.15 compared with the fool-flat A strategy.

advantages

  • After conversion, the cost performance is higher, and the total income is greater
  • For the Emperor, it is easier to get into the soul, and obtain considerable benefits
  • Suitable for players who like to take a certain amount of risk and want to get a higher return

disadvantages

  • It’s not necessarily what you want the most, it’s easy for players to draw skins or other items they don’t want if they already have a certain goal
  • The average upper and lower limits are higher, and the lower limit is more lucky. Once it is close to the lower limit, it is likely to obtain the painful result that the cost performance is close to 1, or even less than 1
  • For players who want to stabilize only one set of items, the risk is too great to bear

Protect the additional

For the protection of additional, you can refer to the analysis of this article for it, basically can say that it must be a loss, after all, it is to take money to compensate for the time spent. So I’m not going to simulate it in code, but the cost performance is definitely not as good as the first two strategies.

Summary & Explanation

For those who only want to spend A small amount of money (less than 1000) to get one or two specific skins, go straight to the idiot flat A

For those who want to fight, change bike into motorcycle, or collect control, and want to accumulate skin, they can choose ordinary additional

For heavy krypton gold players, not poor money, poor time partners, direct protection of additional, short and fast

Finally explain, this is not soft text, Tencent also did not give me any benefit. After all, “buy is not as good as sell the fine”, these probabilities have long been calculated, as a player again how to simulate also can not take any cheap, here just want to let not smoke twist egg experience partners have a simple understanding and expectations. The last sentence: the game is risky, the cost should be careful, I hope everyone has fun.

Reference links:

Peace elite twist egg machine super detailed overview