幸运大转盘抽奖逻辑实现

@[toc]

1. 需求

幸运大转盘抽奖逻辑实现

如图,实现一个转盘抽奖,

有个n个奖项,每个奖项配置中奖概率,点击开始抽奖进行抽奖;

本文主要讲后端抽奖逻辑算法;

2. 实现思路

2.1 实现逻辑步骤

  • 前端使用现有的转盘插件 如 https://github.com/LuckDraw/vue-luck-draw
  • 后台能够添加配置抽奖的奖项,以及每个奖项的名称、数量、中奖概率
  • 前端调用接口查询所有奖项,初始化转盘
  • 点击开始抽奖,调用后端接口,接口计算中奖概率得到中奖奖项,返回中奖的奖项给前端,前端提示中奖

2.2 计算中奖概率算法

下面会贴出概率计算的代码,封装成了工具类,可以直接在项目里面用;

这里讲思路。

举例: 奖项及中奖概率 苹果 10%,橘子 20%, 香蕉 15%, 柠檬 5%, 谢谢参与 35%

  1. 根据奖项概率生成区间(从0开始,累加当前奖项的概率)

    分别如下:

    苹果 [0,10) 橘子 [10,30) 香蕉 [30,45) 柠檬 [45,50) 谢谢参与 [50,85)

  2. 生成随机数,落在哪个区间,就返回对应的中奖项

3. 实现代码

调用方式

public static void main(String[] args) {
    //实际上我们的奖项可能是个实体类;这面的map就是HashMap<奖项类, Integer>
    HashMap<String, Integer> map = new HashMap<>();
    map.put("苹果", 10);
    map.put("橘子", 20);
    map.put("香蕉", 15);
    map.put("柠檬", 5);
    map.put("波罗", 15);
    map.put("谢谢参与", 35);
    //直接调用工具类 RandomUtil
    WeightMeta<String> md = RandomUtil.buildWeightMeta(map);
    //md.random()的结果就是奖项; 
    System.out.println("恭喜你抽到了:"+md.random());
}

概率计算工具类,调用方式参考里面的main方法

RandomUtil.java

public class RandomUtil {

    /***
     * @param weightMap key是奖项,value是概率
     * @param <T>
     * @return
     */
    public static <T> WeightMeta<T> buildWeightMeta(final Map<T, Integer> weightMap) {
        final int size = weightMap.size();
        Object[] nodes = new Object[size];
        int[] weights = new int[size];
        int index = 0;
        int weightAdder = 0;
        for (Map.Entry<T, Integer> each : weightMap.entrySet()) {
            nodes[index] = each.getKey();
            weights[index++] = (weightAdder = weightAdder + each.getValue());
        }
        return new WeightMeta<T>((T[]) nodes, weights);
    }
}

奖项权重计算类

WeightMeta.java

package com.happy.netshop.wechat.helper;

import java.util.Arrays;
import java.util.Random;

public class WeightMeta<T> {
    private final Random ran = new Random();
    private final T[] nodes;
    private final int[] weights;
    private final int maxW;

    public WeightMeta(T[] nodes, int[] weights) {
        this.nodes = nodes;
        this.weights = weights;
        this.maxW = weights[weights.length - 1];
    }

    /**
     * 该方法返回权重随机对象
     * @return
     */
    public T random() {
        int index = Arrays.binarySearch(weights, ran.nextInt(maxW) + 1);
        if (index < 0) {
            index = -1 - index;
        }
        return nodes[index];
    }

    public T random(int ranInt) {
        if (ranInt > maxW) {
            ranInt = maxW;
        } else if(ranInt < 0){
            ranInt = 1;
        } else {
            ranInt ++;
        }
        int index = Arrays.binarySearch(weights, ranInt);
        if (index < 0) {
            index = -1 - index;
        }
        return nodes[index];
    }

    @Override
    public String toString() {
        StringBuilder l1 = new StringBuilder();
        StringBuilder l2 = new StringBuilder("[random]\t");
        StringBuilder l3 = new StringBuilder("[node]\t\t");
        l1.append(this.getClass().getName()).append(":").append(this.hashCode()).append(":\n").append("[index]\t\t");
        for (int i = 0; i < weights.length; i++) {
            l1.append(i).append("\t");
            l2.append(weights[i]).append("\t");
            l3.append(nodes[i]).append("\t");
        }
        l1.append("\n");
        l2.append("\n");
        l3.append("\n");
        return l1.append(l2).append(l3).toString();
    }
}

4. 其它问题处理

实际上抽奖并不是公平的按照概率去抽的;会有一些黑幕操作在里面;

例如:

4.1 奖项设置有次数限制

例如 个别奖项,设置三天内只能抽到2次

思路1:
简单点直接存redis,抽中的时候存入redis,记录次数,key到期时间设置3天;抽中的时候获取redis存的次数,判断达到2次,就直接返回谢谢参与
思路2:
存数据库,抽中的时候查询三天内已经抽中的次数,大于2次就直接返回谢谢参与奖项

4.2 奖项设置有总数量

例如 个别奖项,设置总共有5个

跟上边思路是一样的;做好库存管理,没有数量了就返回谢谢参与

4.3 让大奖在前半段时间内不被抽到

实现思路就是:
1. 默认概率最低的奖项就是大奖或者在后台配置得知哪个是大奖;
2. 如果抽到的是大奖,则计算当前时间是否在整个活动时间的1/2时间内,如果是的话,就返回谢谢参与

5.总结

这里主要是记录下这次做的功能,以后用到了可以直接使用上面的工具类计算概率,比较方便。

上一篇:古瑞瓦特监控系统为安装商解除后顾之忧


下一篇:引入安全驾驶员、安装监控摄像头……Waymo推额外安全措施