在墨刀上找了一个盲盒的项目练练手,有个开盲盒的功能,效果图如下
点击开盒后端处理后返回给前端一个盲盒奖品,这个功能一开始的时候我觉得挺简单的,直到我动手的时候,感觉没那么简单,也可能是我太笨了。。。
下面是最终的判断逻辑,这是 node 中的代码,其实就是一个 if...else if... 判断
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min;
}
const randomNum=getRandomInt(1,101)
const data=await openBoxItemModel.find()
var list=[]
if(randomNum<=100 && randomNum>65){ //有35%的概率为流量卡
list.push(data[0].title)
}else if(randomNum<=65 && randomNum>35){ //有30%的概率为洗发水
list.push(data[3].title)
}else if(randomNum<=35 && randomNum>10){ //有25%的概率为抽纸
list.push(data[1].title)
}else if(randomNum<=10 && randomNum>0){ //有10%的概率为沙发
list.push(data[2].title)
}
console.log(list,'list');
刚开始的想法
我想的是双重for循环或者for..in循环,就像下面这样
const data=await openBoxItemModel.find()
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min;
}
const randomNum=getRandomInt(1,36)
//算出总概率 就是100
const totalWeight = data.reduce((acc, prize) => acc + prize.probability, 0);
//获得奖品的列表
var list=[]
const sort=[10,25,30,35] //对应着物品中奖概率,比如沙发为10、抽纸为25...
function drawPrize(){
const random=Math.random()*totalWeight;
let sum=0
for(let prize of data){
sum+=prize.probability
for(let i=0;i<sort.length;i++){
if(random<sort[i]){
list.push(prize)
}
}
}
}
drawPrize()
遇到的问题
这样做会把所有 random 小于 sort 中物品概率的物品加入到 list 中,这个随机数一开始的想法就是错的,考虑到假如随机数为15,那么list数组里就会有 抽纸、流量卡、洗发水 那应该将哪个奖品作为最终的返回前端。如果按照最接近某个概率的奖品并且是小于的关系来说,15这个随机数最接近25,也就是抽纸这个奖品,但是15又小于35(流量卡),所以每次的随机数如果为10以上到35以下,都会将抽纸作为最终的奖品给予用户,但随机数是25以上35以下时,奖品应该是流量卡,不应该是抽纸。而且还有个问题,11到25(包括25)有15个随机数是抽纸的奖品,26到35(包括35)只有10个随机数,这样一来 抽纸 :流量卡 的比例是 3 :2,但设定的奖品概率是流量卡大于抽纸的,所以这显然不对
改变随机数和判断思路
考虑的是将每个中奖概率除以总概率(100)得出每个物品概率的百分比,然后从上一个和下一个概率中间抽取随机数。比如 10/100 为 0.1 百分比为10% 那么随机数在大于0小于等于10时认为抽到单人沙发 25/100 为 0.25 百分比为25% 那么随机数在大于10小于等于25时认为抽到抽纸,以此类推,随机数设定为1到100。然后从100开始到65,中间有35个随机数,包括100的,这样流量卡的概率为35%,然后从65到35,中间有30个随机数,包括65,这样洗发水的概率为30%......
这是我的数据
num是库存,probability是中奖概率 可以看出 流量卡 > 洗发水 > 抽纸 > 沙发
经过上面的判断,最终实验了100次抽奖,结果如下
这样看来确实是 流量卡 > 洗发水 > 抽纸 > 沙发 但还是不确定这样判断是否正确,如果不对的话,希望帮忙指正。
全部代码
接口部分
router.post('/openBox',async (req,res)=>{
const data1=await boxSituationModel.find({user_id:req.body.user_id})
if(data1[0].no_open_box<=0){
res.send({code:400,msg:'没有未开启的盲盒啦,快去购买吧~'})
}else{
await boxSituationModel.updateOne({user_id:req.body.user_id},{
$inc:{no_open_box:-1}
})
await boxSituationModel.updateOne({user_id:req.body.user_id},{
$inc:{open_box_count:1}
})
const data=await openBoxItemModel.find()
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min;
}
const randomNum=getRandomInt(1,101)
if(randomNum<=100 && randomNum>65){ //有35%的概率为流量卡
await openBoxItemModel.updateOne({_id:'65eafe12871fbae51bb9c47c'},{
$inc:{num:-1}
})
res.send({code:200,msg:'恭喜你获得流量卡',data:data[0]})
}else if(randomNum<=65 && randomNum>35){ //有30%的概率为洗发水
await openBoxItemModel.updateOne({_id:'65eafe4b3763eeca074a8717'},{
$inc:{num:-1}
})
res.send({code:200,msg:'恭喜你获得洗发水',data:data[3]})
}else if(randomNum<=35 && randomNum>10){ //有25%的概率为抽纸
await openBoxItemModel.updateOne({_id:'65eafe2b81ddcb7c727d53a6'},{
$inc:{num:-1}
})
res.send({code:200,msg:'恭喜你获得抽纸',data:data[1]})
}else if(randomNum<=10 && randomNum>0){ //有10%的概率为沙发
await openBoxItemModel.updateOne({_id:'65eafe3b4df8513729d0edef'},{
$inc:{num:-1}
})
res.send({code:200,msg:'恭喜你获得沙发',data:data[2]})
}
}
})
前端部分
const openBox=async ()=>{
const {data} = await axios.post('/welfare/openBox',{user_id:'65db28959400000014001e74',open,noOpen})
if(data.code==400){
showToast(data.msg)
}else if(data.code==200){
goodsImg.value='data:image/png;base64,'+data.data.img
goodsImg.value=data.data.img
goodsTitle.value=data.data.title
getBox() //刷新累计开盒数量及代开盲盒数量
}
}
效果图
user_id 我暂时先写固定的了,后面再改,你们换一下
大概的流程就是,通过用户id,如果开盒一次,减去相应的待开盲盒数量,然后判断抽到了哪个奖品,将奖品库存数量-1,但是没有做库存不足的判断,因为我只是练习的,就不写了。
推荐组件
我觉得这个组件库开盒效果还挺酷的,后面我再添上
相关阅读
在mongodb数据库中存本地图片要转base64格式
const fs=require('fs')
const path=require('path')
const image = fs.readFileSync(path.join(__dirname,'../','./imgs/image.jpg'));
const imageBase64 = image.toString('base64');
这样做在存大量图片的时候特别麻烦,这也是我在网上找的,暂时没去看更好的方法,如果有希望能私信或评论区告知。图片路径是与 public 同级的 imgs 下面的,感觉不如放到 public 下。图片多的话也不知道会不会增加服务器压力。此时保存的是没有 data URL 前缀的 base64 ,所以在渲染时要加上,当然也可以在存储的时候处理
goodsImg.value='data:image/png;base64,'+data.data.img