forked from EllieBotDevs/elliebot
161 lines
No EOL
4.9 KiB
C#
161 lines
No EOL
4.9 KiB
C#
#nullable disable
|
|
using EllieBot.Modules.Gambling.Common.AnimalRacing.Exceptions;
|
|
using EllieBot.Modules.Games.Common;
|
|
|
|
namespace EllieBot.Modules.Gambling.Common.AnimalRacing;
|
|
|
|
public sealed class AnimalRace : IDisposable
|
|
{
|
|
public const double BASE_MULTIPLIER = 0.87;
|
|
public const double MAX_MULTIPLIER = 0.94;
|
|
public const double MULTI_PER_USER = 0.005;
|
|
|
|
public enum Phase
|
|
{
|
|
WaitingForPlayers,
|
|
Running,
|
|
Ended
|
|
}
|
|
|
|
public event Func<AnimalRace, Task> OnStarted = delegate { return Task.CompletedTask; };
|
|
public event Func<AnimalRace, Task> OnStartingFailed = delegate { return Task.CompletedTask; };
|
|
public event Func<AnimalRace, Task> OnStateUpdate = delegate { return Task.CompletedTask; };
|
|
public event Func<AnimalRace, Task> OnEnded = delegate { return Task.CompletedTask; };
|
|
|
|
public Phase CurrentPhase { get; private set; } = Phase.WaitingForPlayers;
|
|
|
|
public IReadOnlyCollection<AnimalRacingUser> Users
|
|
=> _users.ToList();
|
|
|
|
public List<AnimalRacingUser> FinishedUsers { get; } = new();
|
|
public int MaxUsers { get; }
|
|
|
|
private readonly SemaphoreSlim _locker = new(1, 1);
|
|
private readonly HashSet<AnimalRacingUser> _users = new();
|
|
private readonly ICurrencyService _currency;
|
|
private readonly RaceOptions _options;
|
|
private readonly Queue<RaceAnimal> _animalsQueue;
|
|
|
|
public AnimalRace(RaceOptions options, ICurrencyService currency, IEnumerable<RaceAnimal> availableAnimals)
|
|
{
|
|
_currency = currency;
|
|
_options = options;
|
|
_animalsQueue = new(availableAnimals);
|
|
MaxUsers = _animalsQueue.Count;
|
|
|
|
if (_animalsQueue.Count == 0)
|
|
CurrentPhase = Phase.Ended;
|
|
}
|
|
|
|
public void Initialize() //lame name
|
|
=> _ = Task.Run(async () =>
|
|
{
|
|
await Task.Delay(_options.StartTime * 1000);
|
|
|
|
await _locker.WaitAsync();
|
|
try
|
|
{
|
|
if (CurrentPhase != Phase.WaitingForPlayers)
|
|
return;
|
|
|
|
await Start();
|
|
}
|
|
finally { _locker.Release(); }
|
|
});
|
|
|
|
public async Task<AnimalRacingUser> JoinRace(ulong userId, string userName, long bet = 0)
|
|
{
|
|
ArgumentOutOfRangeException.ThrowIfNegative(bet);
|
|
|
|
var user = new AnimalRacingUser(userName, userId, bet);
|
|
|
|
await _locker.WaitAsync();
|
|
try
|
|
{
|
|
if (_users.Count == MaxUsers)
|
|
throw new AnimalRaceFullException();
|
|
|
|
if (CurrentPhase != Phase.WaitingForPlayers)
|
|
throw new AlreadyStartedException();
|
|
|
|
if (!await _currency.RemoveAsync(userId, bet, new("animalrace", "bet")))
|
|
throw new NotEnoughFundsException();
|
|
|
|
if (_users.Contains(user))
|
|
throw new AlreadyJoinedException();
|
|
|
|
var animal = _animalsQueue.Dequeue();
|
|
user.Animal = animal;
|
|
_users.Add(user);
|
|
|
|
if (_animalsQueue.Count == 0) //start if no more spots left
|
|
await Start();
|
|
|
|
return user;
|
|
}
|
|
finally { _locker.Release(); }
|
|
}
|
|
|
|
private async Task Start()
|
|
{
|
|
CurrentPhase = Phase.Running;
|
|
if (_users.Count <= 1)
|
|
{
|
|
foreach (var user in _users)
|
|
{
|
|
if (user.Bet > 0)
|
|
await _currency.AddAsync(user.UserId, (long)(user.Bet + BASE_MULTIPLIER), new("animalrace", "refund"));
|
|
}
|
|
|
|
_ = OnStartingFailed?.Invoke(this);
|
|
CurrentPhase = Phase.Ended;
|
|
return;
|
|
}
|
|
|
|
_ = OnStarted?.Invoke(this);
|
|
_ = Task.Run(async () =>
|
|
{
|
|
var rng = new EllieRandom();
|
|
while (!_users.All(x => x.Progress >= 60))
|
|
{
|
|
foreach (var user in _users)
|
|
{
|
|
user.Progress += rng.Next(1, 10);
|
|
if (user.Progress >= 60)
|
|
user.Progress = 60;
|
|
}
|
|
|
|
var finished = _users.Where(x => x.Progress >= 60 && !FinishedUsers.Contains(x)).Shuffle();
|
|
|
|
FinishedUsers.AddRange(finished);
|
|
|
|
_ = OnStateUpdate?.Invoke(this);
|
|
await Task.Delay(1750);
|
|
}
|
|
|
|
if (FinishedUsers[0].Bet > 0)
|
|
{
|
|
Multi = FinishedUsers.Count
|
|
* Math.Min(MAX_MULTIPLIER, BASE_MULTIPLIER + (MULTI_PER_USER * FinishedUsers.Count));
|
|
await _currency.AddAsync(FinishedUsers[0].UserId,
|
|
(long)(FinishedUsers[0].Bet * Multi),
|
|
new("animalrace", "win"));
|
|
}
|
|
|
|
_ = OnEnded?.Invoke(this);
|
|
});
|
|
}
|
|
|
|
public double Multi { get; set; } = BASE_MULTIPLIER;
|
|
|
|
public void Dispose()
|
|
{
|
|
CurrentPhase = Phase.Ended;
|
|
OnStarted = null;
|
|
OnEnded = null;
|
|
OnStartingFailed = null;
|
|
OnStateUpdate = null;
|
|
_locker.Dispose();
|
|
_users.Clear();
|
|
}
|
|
} |