Files
Yi.Framework/Yi.Abp.Net8/module/digital-collectibles/Yi.Framework.DigitalCollectibles.Domain/Managers/MiningPoolManager.cs
2024-10-16 17:49:47 +08:00

243 lines
8.5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Microsoft.Extensions.Caching.Distributed;
using Volo.Abp.Caching;
using Volo.Abp.Domain.Services;
using Volo.Abp.Settings;
using Volo.Abp.Threading;
using Yi.Framework.DigitalCollectibles.Domain.Dtos;
using Yi.Framework.DigitalCollectibles.Domain.Entities;
using Yi.Framework.DigitalCollectibles.Domain.Shared.Consts;
using Yi.Framework.DigitalCollectibles.Domain.Shared.Enums;
using Yi.Framework.SettingManagement.Domain;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.DigitalCollectibles.Domain.Managers;
/// <summary>
/// 矿池领域服务
/// 处理矿池相关业务,例如挖矿等
/// </summary>
public class MiningPoolManager : DomainService
{
private readonly ISqlSugarRepository<CollectiblesAggregateRoot> _collectiblesRepository;
private readonly ISqlSugarRepository<OnHookAggregateRoot> _onHookRepository;
private readonly ISettingProvider _settingProvider;
private readonly IDistributedCache<MiningPoolContent> _cache;
private readonly ISqlSugarRepository<CollectiblesUserStoreAggregateRoot> _userStoreRepository;
public MiningPoolManager(ISettingProvider settingProvider, IDistributedCache<MiningPoolContent> cache,
ISqlSugarRepository<CollectiblesAggregateRoot> collectiblesRepository,
ISqlSugarRepository<OnHookAggregateRoot> onHookRepository,
ISqlSugarRepository<CollectiblesUserStoreAggregateRoot> userStoreRepository)
{
_settingProvider = settingProvider;
this._cache = cache;
_collectiblesRepository = collectiblesRepository;
_onHookRepository = onHookRepository;
_userStoreRepository = userStoreRepository;
}
/// <summary>
/// 每次挖矿概率,每天根据特定算法计算
/// </summary>
private static decimal CurrentMiningProbability => AsyncHelper.RunSync(async () =>
{
return await ComputeMiningProbabilityAsync();
});
public async Task<MiningPoolContent> GetMiningPoolContentAsync()
{
var pool = await _cache.GetAsync(CacheConst.MiningPoolContent);
return pool;
}
/// <summary>
/// 用户进行一次挖矿
/// </summary>
/// <returns></returns>
public async Task<MiningPoolResult> MiningAsync(Guid userId)
{
var result = new MiningPoolResult();
//从矿池中开挖,判断矿池是否还有矿
//根据挖矿概率,进行挖掘
//挖到了放到用户仓库即可
var miningMaxLimit = int.Parse(await _settingProvider.GetOrNullAsync("MiningMaxLimit"));
var miningMinIntervalSeconds = int.Parse(await _settingProvider.GetOrNullAsync("MiningMinIntervalSeconds"));
//如果概率是挖到了矿,再从矿物中随机选择一个稀有度,再在当前稀有度中的矿物列表,随机选择一个具体的矿物
var pool =await GetMiningPoolContentAsync();
if (pool.TotalNumber == 0)
{
result.Result = MiningResultEnum.PoolIsEmpty;
return result;
}
// 生成一个 0 到 1 之间的随机数
double randomValue = new Random().NextDouble();
//如果随机的概率在当前概率内,成功
if (randomValue.To<decimal>() < CurrentMiningProbability)
{
//成功后在藏品中根据稀有度概率必定获取一个
var probabilityArray = RarityEnumExtensions.GetProbabilityArray();
var index = GetRandomIndex(probabilityArray);
var rarityType = (RarityEnum)Enum.GetValues(typeof(RarityEnum)).GetValue(index)!;
var collectiblesList =
await _collectiblesRepository._DbQueryable.Where(x => x.Rarity == rarityType).ToListAsync();
int randomIndex = new Random().Next(collectiblesList.Count);
var currentCollectibles = collectiblesList[randomIndex];
result.Result = MiningResultEnum.Success;
result.Collectibles = currentCollectibles;
//使用结果新增给对应的用户
await _userStoreRepository.InsertAsync(new CollectiblesUserStoreAggregateRoot
{
UserId = userId,
CollectiblesId = result.Collectibles.Id,
IsRead = false
});
return result;
}
result.Result = MiningResultEnum.Empty;
return result;
}
/// <summary>
/// 挂机挖矿
/// </summary>
public async Task OnHookMiningAsync()
{
//获取当前激活的挂机挖矿
var currentOnHook = await _onHookRepository._DbQueryable.Where(x => x.IsActive == true)
.Where(x => x.EndTime <= DateTime.Now).ToListAsync();
//根据用户对挂机卡hash关系
var userOnHookDic = currentOnHook.GroupBy(x => x.UserId).ToDictionary(x => x.Key, y => y.First());
foreach (var onHookItem in userOnHookDic)
{
await MiningAsync(onHookItem.Value.UserId);
}
}
private int GetRandomIndex(decimal[] probabilities)
{
// 生成0到1之间的随机数
Random random = new Random();
decimal randomValue = random.NextDouble().To<decimal>();
decimal cumulativeProbability = 0.0m;
for (int i = 0; i < probabilities.Length; i++)
{
cumulativeProbability += probabilities[i];
// 判断随机数是否小于当前的累积概率
if (randomValue < cumulativeProbability)
{
return i; // 返回中标的索引
}
}
//剩余情况都是普通
return 0;
}
/// <summary>
/// 计算当前挖矿概率
/// </summary>
/// <returns></returns>
public static Task<decimal> ComputeMiningProbabilityAsync()
{
//当前的挖矿期望:当天的所有藏品能被刚好挖完
//保底概率最大不能高过一个值百分之10
//手动挖矿1天可挖10次每次至少间隔6秒
//自动挖矿1天可以挖24次 每次间隔60分钟需要使用自动挖矿卡
//可影响因素:剩余手动挖矿次数+剩余自动挖矿次数
//简单模式1/15
return Task.FromResult(1m / 15);
}
/// <summary>
/// 刷新矿池
/// </summary>
/// <returns></returns>
public async Task RefreshMiningPoolAsync()
{
//获取当前最大的限制
var maximumPoolLimit = int.Parse(await _settingProvider.GetOrNullAsync("MaxPoolLimit"));
DateTime startTime = DateTime.Today.AddHours(10);
DateTime endTime = startTime.AddDays(1);
var probabilityValues = RarityEnumExtensions.GetProbabilityArray();
var result = GenerateDistribution(maximumPoolLimit, probabilityValues);
//根据配置,将不同比例的矿,塞入矿池,
//矿池交给redis
await _cache.SetAsync(CacheConst.MiningPoolContent, new MiningPoolContent(startTime, endTime)
{
I0_OrdinaryNumber = result[0],
I1_SeniorNumber = result[1],
I2_RareNumber = result[2],
I3_GemNumber = result[3],
I4_LegendNumber = result[4]
}, new DistributedCacheEntryOptions
{
AbsoluteExpiration = endTime
});
}
/// <summary>
/// 根据概率生成给对应稀有度藏品
/// </summary>
/// <param name="totalCount"></param>
/// <param name="probabilities"></param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
private int[] GenerateDistribution(int totalCount, decimal[] probabilities)
{
int[] counts = new int[probabilities.Length];
decimal totalProbability = 0;
// 计算概率总和,确保为 1
foreach (var prob in probabilities)
{
totalProbability += prob;
}
// 检查概率之和是否为 1
if (totalProbability < 0.99m || totalProbability > 1.01m)
{
throw new ArgumentException("概率总和必须接近1");
}
// 生成分布
for (int i = 0; i < probabilities.Length; i++)
{
counts[i] = (int)(totalCount * probabilities[i]);
}
// 处理可能因精度问题导致的总数不足
int sum = 0;
foreach (var count in counts)
{
sum += count;
}
int difference = totalCount - sum;
// 将差值分配给概率最大的一项
if (difference > 0)
{
int maxIndex = Array.IndexOf(counts, Math.Max(counts[0], counts[1]));
counts[maxIndex] += difference;
}
return counts;
}
}