forked from EllieBotDevs/elliebot
Removing all the broken code and remaking it
This commit is contained in:
parent
9c94a66323
commit
b12102f735
858 changed files with 0 additions and 266344 deletions
src/EllieBot/Modules/Gambling/BlackJack
|
@ -1,183 +0,0 @@
|
|||
#nullable disable
|
||||
using EllieBot.Common.TypeReaders;
|
||||
using EllieBot.Modules.Gambling.Common;
|
||||
using EllieBot.Modules.Gambling.Common.Blackjack;
|
||||
using EllieBot.Modules.Gambling.Services;
|
||||
|
||||
namespace EllieBot.Modules.Gambling;
|
||||
|
||||
public partial class Gambling
|
||||
{
|
||||
public partial class BlackJackCommands : GamblingSubmodule<BlackJackService>
|
||||
{
|
||||
public enum BjAction
|
||||
{
|
||||
Hit = int.MinValue,
|
||||
Stand,
|
||||
Double
|
||||
}
|
||||
|
||||
private readonly ICurrencyService _cs;
|
||||
private readonly DbService _db;
|
||||
private IUserMessage msg;
|
||||
|
||||
public BlackJackCommands(ICurrencyService cs, DbService db, GamblingConfigService gamblingConf)
|
||||
: base(gamblingConf)
|
||||
{
|
||||
_cs = cs;
|
||||
_db = db;
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public async Task BlackJack([OverrideTypeReader(typeof(BalanceTypeReader))] long amount)
|
||||
{
|
||||
if (!await CheckBetMandatory(amount))
|
||||
return;
|
||||
|
||||
var newBj = new Blackjack(_cs);
|
||||
Blackjack bj;
|
||||
if (newBj == (bj = _service.Games.GetOrAdd(ctx.Channel.Id, newBj)))
|
||||
{
|
||||
if (!await bj.Join(ctx.User, amount))
|
||||
{
|
||||
_service.Games.TryRemove(ctx.Channel.Id, out _);
|
||||
await Response().Error(strs.not_enough(CurrencySign)).SendAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
bj.StateUpdated += Bj_StateUpdated;
|
||||
bj.GameEnded += Bj_GameEnded;
|
||||
bj.Start();
|
||||
|
||||
await Response().NoReply().Confirm(strs.bj_created(ctx.User.ToString())).SendAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (await bj.Join(ctx.User, amount))
|
||||
await Response().NoReply().Confirm(strs.bj_joined(ctx.User.ToString())).SendAsync();
|
||||
else
|
||||
{
|
||||
Log.Information("{User} can't join a blackjack game as it's in {BlackjackState} state already",
|
||||
ctx.User,
|
||||
bj.State);
|
||||
}
|
||||
}
|
||||
|
||||
await ctx.Message.DeleteAsync();
|
||||
}
|
||||
|
||||
private Task Bj_GameEnded(Blackjack arg)
|
||||
{
|
||||
_service.Games.TryRemove(ctx.Channel.Id, out _);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task Bj_StateUpdated(Blackjack bj)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (msg is not null)
|
||||
_ = msg.DeleteAsync();
|
||||
|
||||
var c = bj.Dealer.Cards.Select(x => x.GetEmojiString())
|
||||
.ToList();
|
||||
var dealerIcon = "❔ ";
|
||||
if (bj.State == Blackjack.GameState.Ended)
|
||||
{
|
||||
if (bj.Dealer.GetHandValue() == 21)
|
||||
dealerIcon = "💰 ";
|
||||
else if (bj.Dealer.GetHandValue() > 21)
|
||||
dealerIcon = "💥 ";
|
||||
else
|
||||
dealerIcon = "🏁 ";
|
||||
}
|
||||
|
||||
var cStr = string.Concat(c.Select(x => x[..^1] + " "));
|
||||
cStr += "\n" + string.Concat(c.Select(x => x.Last() + " "));
|
||||
var embed = _sender.CreateEmbed()
|
||||
.WithOkColor()
|
||||
.WithTitle("BlackJack")
|
||||
.AddField($"{dealerIcon} Dealer's Hand | Value: {bj.Dealer.GetHandValue()}", cStr);
|
||||
|
||||
if (bj.CurrentUser is not null)
|
||||
embed.WithFooter($"Player to make a choice: {bj.CurrentUser.DiscordUser}");
|
||||
|
||||
foreach (var p in bj.Players)
|
||||
{
|
||||
c = p.Cards.Select(x => x.GetEmojiString()).ToList();
|
||||
cStr = "-\t" + string.Concat(c.Select(x => x[..^1] + " "));
|
||||
cStr += "\n-\t" + string.Concat(c.Select(x => x.Last() + " "));
|
||||
var full = $"{p.DiscordUser.ToString().TrimTo(20)} | Bet: {N(p.Bet)} | Value: {p.GetHandValue()}";
|
||||
if (bj.State == Blackjack.GameState.Ended)
|
||||
{
|
||||
if (p.State == User.UserState.Lost)
|
||||
full = "❌ " + full;
|
||||
else
|
||||
full = "✅ " + full;
|
||||
}
|
||||
else if (p == bj.CurrentUser)
|
||||
full = "▶ " + full;
|
||||
else if (p.State == User.UserState.Stand)
|
||||
full = "⏹ " + full;
|
||||
else if (p.State == User.UserState.Bust)
|
||||
full = "💥 " + full;
|
||||
else if (p.State == User.UserState.Blackjack)
|
||||
full = "💰 " + full;
|
||||
|
||||
embed.AddField(full, cStr);
|
||||
}
|
||||
|
||||
msg = await Response().Embed(embed).SendAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private string UserToString(User x)
|
||||
{
|
||||
var playerName = x.State == User.UserState.Bust
|
||||
? Format.Strikethrough(x.DiscordUser.ToString().TrimTo(30))
|
||||
: x.DiscordUser.ToString();
|
||||
|
||||
// var hand = $"{string.Concat(x.Cards.Select(y => "〖" + y.GetEmojiString() + "〗"))}";
|
||||
|
||||
|
||||
return $"{playerName} | Bet: {x.Bet}\n";
|
||||
}
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public Task Hit()
|
||||
=> InternalBlackJack(BjAction.Hit);
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public Task Stand()
|
||||
=> InternalBlackJack(BjAction.Stand);
|
||||
|
||||
[Cmd]
|
||||
[RequireContext(ContextType.Guild)]
|
||||
public Task Double()
|
||||
=> InternalBlackJack(BjAction.Double);
|
||||
|
||||
private async Task InternalBlackJack(BjAction a)
|
||||
{
|
||||
if (!_service.Games.TryGetValue(ctx.Channel.Id, out var bj))
|
||||
return;
|
||||
|
||||
if (a == BjAction.Hit)
|
||||
await bj.Hit(ctx.User);
|
||||
else if (a == BjAction.Stand)
|
||||
await bj.Stand(ctx.User);
|
||||
else if (a == BjAction.Double)
|
||||
{
|
||||
if (!await bj.Double(ctx.User))
|
||||
await Response().Error(strs.not_enough(CurrencySign)).SendAsync();
|
||||
}
|
||||
|
||||
await ctx.Message.DeleteAsync();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
#nullable disable
|
||||
using EllieBot.Modules.Gambling.Common.Blackjack;
|
||||
|
||||
namespace EllieBot.Modules.Gambling.Services;
|
||||
|
||||
public class BlackJackService : IEService
|
||||
{
|
||||
public ConcurrentDictionary<ulong, Blackjack> Games { get; } = new();
|
||||
}
|
|
@ -1,329 +0,0 @@
|
|||
#nullable disable
|
||||
using Ellie.Econ;
|
||||
|
||||
namespace EllieBot.Modules.Gambling.Common.Blackjack;
|
||||
|
||||
public class Blackjack
|
||||
{
|
||||
public enum GameState
|
||||
{
|
||||
Starting,
|
||||
Playing,
|
||||
Ended
|
||||
}
|
||||
|
||||
public event Func<Blackjack, Task> StateUpdated;
|
||||
public event Func<Blackjack, Task> GameEnded;
|
||||
|
||||
private Deck Deck { get; } = new QuadDeck();
|
||||
public Dealer Dealer { get; set; }
|
||||
|
||||
|
||||
public List<User> Players { get; set; } = new();
|
||||
public GameState State { get; set; } = GameState.Starting;
|
||||
public User CurrentUser { get; private set; }
|
||||
|
||||
private TaskCompletionSource<bool> currentUserMove;
|
||||
private readonly ICurrencyService _cs;
|
||||
|
||||
private readonly SemaphoreSlim _locker = new(1, 1);
|
||||
|
||||
public Blackjack(ICurrencyService cs)
|
||||
{
|
||||
_cs = cs;
|
||||
Dealer = new();
|
||||
}
|
||||
|
||||
public void Start()
|
||||
=> _ = GameLoop();
|
||||
|
||||
public async Task GameLoop()
|
||||
{
|
||||
try
|
||||
{
|
||||
//wait for players to join
|
||||
await Task.Delay(20000);
|
||||
await _locker.WaitAsync();
|
||||
try
|
||||
{
|
||||
State = GameState.Playing;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_locker.Release();
|
||||
}
|
||||
|
||||
await PrintState();
|
||||
//if no users joined the game, end it
|
||||
if (!Players.Any())
|
||||
{
|
||||
State = GameState.Ended;
|
||||
_ = GameEnded?.Invoke(this);
|
||||
return;
|
||||
}
|
||||
|
||||
//give 1 card to the dealer and 2 to each player
|
||||
Dealer.Cards.Add(Deck.Draw());
|
||||
foreach (var usr in Players)
|
||||
{
|
||||
usr.Cards.Add(Deck.Draw());
|
||||
usr.Cards.Add(Deck.Draw());
|
||||
|
||||
if (usr.GetHandValue() == 21)
|
||||
usr.State = User.UserState.Blackjack;
|
||||
}
|
||||
|
||||
//go through all users and ask them what they want to do
|
||||
foreach (var usr in Players.Where(x => !x.Done))
|
||||
{
|
||||
while (!usr.Done)
|
||||
{
|
||||
Log.Information("Waiting for {DiscordUser}'s move", usr.DiscordUser);
|
||||
await PromptUserMove(usr);
|
||||
}
|
||||
}
|
||||
|
||||
await PrintState();
|
||||
State = GameState.Ended;
|
||||
await Task.Delay(2500);
|
||||
Log.Information("Dealer moves");
|
||||
await DealerMoves();
|
||||
await PrintState();
|
||||
_ = GameEnded?.Invoke(this);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "REPORT THE MESSAGE BELOW IN Ellie's Home SERVER PLEASE");
|
||||
State = GameState.Ended;
|
||||
_ = GameEnded?.Invoke(this);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PromptUserMove(User usr)
|
||||
{
|
||||
using var cts = new CancellationTokenSource();
|
||||
var pause = Task.Delay(20000, cts.Token); //10 seconds to decide
|
||||
CurrentUser = usr;
|
||||
currentUserMove = new();
|
||||
await PrintState();
|
||||
// either wait for the user to make an action and
|
||||
// if he doesn't - stand
|
||||
var finished = await Task.WhenAny(pause, currentUserMove.Task);
|
||||
if (finished == pause)
|
||||
await Stand(usr);
|
||||
else
|
||||
cts.Cancel();
|
||||
|
||||
CurrentUser = null;
|
||||
currentUserMove = null;
|
||||
}
|
||||
|
||||
public async Task<bool> Join(IUser user, long bet)
|
||||
{
|
||||
await _locker.WaitAsync();
|
||||
try
|
||||
{
|
||||
if (State != GameState.Starting)
|
||||
return false;
|
||||
|
||||
if (Players.Count >= 5)
|
||||
return false;
|
||||
|
||||
if (!await _cs.RemoveAsync(user, bet, new("blackjack", "gamble")))
|
||||
return false;
|
||||
|
||||
Players.Add(new(user, bet));
|
||||
_ = PrintState();
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_locker.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> Stand(IUser u)
|
||||
{
|
||||
var cu = CurrentUser;
|
||||
|
||||
if (cu is not null && cu.DiscordUser == u)
|
||||
return await Stand(cu);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<bool> Stand(User u)
|
||||
{
|
||||
await _locker.WaitAsync();
|
||||
try
|
||||
{
|
||||
if (State != GameState.Playing)
|
||||
return false;
|
||||
|
||||
if (CurrentUser != u)
|
||||
return false;
|
||||
|
||||
u.State = User.UserState.Stand;
|
||||
currentUserMove.TrySetResult(true);
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_locker.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DealerMoves()
|
||||
{
|
||||
var hw = Dealer.GetHandValue();
|
||||
while (hw < 17
|
||||
|| (hw == 17
|
||||
&& Dealer.Cards.Count(x => x.Number == 1) > (Dealer.GetRawHandValue() - 17) / 10)) // hit on soft 17
|
||||
{
|
||||
/* Dealer has
|
||||
A 6
|
||||
That's 17, soft
|
||||
hw == 17 => true
|
||||
number of aces = 1
|
||||
1 > 17-17 /10 => true
|
||||
|
||||
AA 5
|
||||
That's 17, again soft, since one ace is worth 11, even though another one is 1
|
||||
hw == 17 => true
|
||||
number of aces = 2
|
||||
2 > 27 - 17 / 10 => true
|
||||
|
||||
AA Q 5
|
||||
That's 17, but not soft, since both aces are worth 1
|
||||
hw == 17 => true
|
||||
number of aces = 2
|
||||
2 > 37 - 17 / 10 => false
|
||||
* */
|
||||
Dealer.Cards.Add(Deck.Draw());
|
||||
hw = Dealer.GetHandValue();
|
||||
}
|
||||
|
||||
if (hw > 21)
|
||||
{
|
||||
foreach (var usr in Players)
|
||||
{
|
||||
if (usr.State is User.UserState.Stand or User.UserState.Blackjack)
|
||||
usr.State = User.UserState.Won;
|
||||
else
|
||||
usr.State = User.UserState.Lost;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var usr in Players)
|
||||
{
|
||||
if (usr.State == User.UserState.Blackjack)
|
||||
usr.State = User.UserState.Won;
|
||||
else if (usr.State == User.UserState.Stand)
|
||||
usr.State = hw < usr.GetHandValue() ? User.UserState.Won : User.UserState.Lost;
|
||||
else
|
||||
usr.State = User.UserState.Lost;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var usr in Players)
|
||||
{
|
||||
if (usr.State is User.UserState.Won or User.UserState.Blackjack)
|
||||
await _cs.AddAsync(usr.DiscordUser.Id, usr.Bet * 2, new("blackjack", "win"));
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> Double(IUser u)
|
||||
{
|
||||
var cu = CurrentUser;
|
||||
|
||||
if (cu is not null && cu.DiscordUser == u)
|
||||
return await Double(cu);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<bool> Double(User u)
|
||||
{
|
||||
await _locker.WaitAsync();
|
||||
try
|
||||
{
|
||||
if (State != GameState.Playing)
|
||||
return false;
|
||||
|
||||
if (CurrentUser != u)
|
||||
return false;
|
||||
|
||||
if (!await _cs.RemoveAsync(u.DiscordUser.Id, u.Bet, new("blackjack", "double")))
|
||||
return false;
|
||||
|
||||
u.Bet *= 2;
|
||||
|
||||
u.Cards.Add(Deck.Draw());
|
||||
|
||||
if (u.GetHandValue() == 21)
|
||||
//blackjack
|
||||
u.State = User.UserState.Blackjack;
|
||||
else if (u.GetHandValue() > 21)
|
||||
// user busted
|
||||
u.State = User.UserState.Bust;
|
||||
else
|
||||
//with double you just get one card, and then you're done
|
||||
u.State = User.UserState.Stand;
|
||||
currentUserMove.TrySetResult(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_locker.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> Hit(IUser u)
|
||||
{
|
||||
var cu = CurrentUser;
|
||||
|
||||
if (cu is not null && cu.DiscordUser == u)
|
||||
return await Hit(cu);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<bool> Hit(User u)
|
||||
{
|
||||
await _locker.WaitAsync();
|
||||
try
|
||||
{
|
||||
if (State != GameState.Playing)
|
||||
return false;
|
||||
|
||||
if (CurrentUser != u)
|
||||
return false;
|
||||
|
||||
u.Cards.Add(Deck.Draw());
|
||||
|
||||
if (u.GetHandValue() == 21)
|
||||
//blackjack
|
||||
u.State = User.UserState.Blackjack;
|
||||
else if (u.GetHandValue() > 21)
|
||||
// user busted
|
||||
u.State = User.UserState.Bust;
|
||||
|
||||
currentUserMove.TrySetResult(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_locker.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public Task PrintState()
|
||||
{
|
||||
if (StateUpdated is null)
|
||||
return Task.CompletedTask;
|
||||
return StateUpdated.Invoke(this);
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
#nullable disable
|
||||
using Ellie.Econ;
|
||||
|
||||
namespace EllieBot.Modules.Gambling.Common.Blackjack;
|
||||
|
||||
public abstract class Player
|
||||
{
|
||||
public List<Deck.Card> Cards { get; } = new();
|
||||
|
||||
public int GetHandValue()
|
||||
{
|
||||
var val = GetRawHandValue();
|
||||
|
||||
// while the hand value is greater than 21, for each ace you have in the deck
|
||||
// reduce the value by 10 until it drops below 22
|
||||
// (emulating the fact that ace is either a 1 or a 11)
|
||||
var i = Cards.Count(x => x.Number == 1);
|
||||
while (val > 21 && i-- > 0)
|
||||
val -= 10;
|
||||
return val;
|
||||
}
|
||||
|
||||
public int GetRawHandValue()
|
||||
=> Cards.Sum(x => x.Number == 1 ? 11 : x.Number >= 10 ? 10 : x.Number);
|
||||
}
|
||||
|
||||
public class Dealer : Player
|
||||
{
|
||||
}
|
||||
|
||||
public class User : Player
|
||||
{
|
||||
public enum UserState
|
||||
{
|
||||
Waiting,
|
||||
Stand,
|
||||
Bust,
|
||||
Blackjack,
|
||||
Won,
|
||||
Lost
|
||||
}
|
||||
|
||||
public UserState State { get; set; } = UserState.Waiting;
|
||||
public long Bet { get; set; }
|
||||
public IUser DiscordUser { get; }
|
||||
|
||||
public bool Done
|
||||
=> State != UserState.Waiting;
|
||||
|
||||
public User(IUser user, long bet)
|
||||
{
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(bet);
|
||||
|
||||
Bet = bet;
|
||||
DiscordUser = user;
|
||||
}
|
||||
}
|
Reference in a new issue