Skip to content

Commit

Permalink
work on album jumble
Browse files Browse the repository at this point in the history
  • Loading branch information
th0mk committed Jun 19, 2024
1 parent 8f90ef8 commit cdc0270
Show file tree
Hide file tree
Showing 7 changed files with 230 additions and 39 deletions.
1 change: 0 additions & 1 deletion src/FMBot.Bot/Builders/AlbumBuilders.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1075,7 +1075,6 @@ public class AlbumBuilders
$"cover-{StringExtensions.ReplaceInvalidChars($"{albumSearch.Album.ArtistName}_{albumSearch.Album.AlbumName}")}";
response.Spoiler = safeForChannel == CensorService.CensorResult.Nsfw;


var cacheFilePath = ChartService.AlbumUrlToCacheFilePath(albumSearch.Album.AlbumName, albumSearch.Album.ArtistName);
await ChartService.OverwriteCache(cacheStream, cacheFilePath);

Expand Down
109 changes: 87 additions & 22 deletions src/FMBot.Bot/Builders/GameBuilders.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,33 +23,15 @@ public class GameBuilders
private readonly GameService _gameService;
private readonly ArtistsService _artistsService;
private readonly CountryService _countryService;
private readonly AlbumService _albumService;

public GameBuilders(UserService userService, GameService gameService, ArtistsService artistsService, CountryService countryService)
public GameBuilders(UserService userService, GameService gameService, ArtistsService artistsService, CountryService countryService, AlbumService albumService)
{
this._userService = userService;
this._gameService = gameService;
this._artistsService = artistsService;
this._countryService = countryService;
}

public static ResponseModel GameModePick(ContextModel context)
{
var response = new ResponseModel
{
ResponseType = ResponseType.Embed,
Components = new ComponentBuilder()
.WithButton("First correct answer wins", InteractionConstants.Game.StartJumbleFirstWins)
.WithButton("Play as group", InteractionConstants.Game.StartJumbleGroup)
};

var description = new StringBuilder();

description.AppendLine("Start jumble game");

response.Embed.WithDescription(description.ToString());
response.Embed.WithColor(DiscordConstants.InformationColorBlue);

return response;
this._albumService = albumService;
}

public async Task<ResponseModel> StartJumbleFirstWins(ContextModel context, int userId,
Expand Down Expand Up @@ -122,7 +104,7 @@ public static ResponseModel GameModePick(ContextModel context)
artistCountry = this._countryService.GetValidCountry(databaseArtist.CountryCode);
}

var hints = this._gameService.GetJumbleHints(databaseArtist, artist.userPlaycount, artistCountry);
var hints = this._gameService.GetJumbleArtistHints(databaseArtist, artist.userPlaycount, artistCountry);
await this._gameService.JumbleStoreShowedHints(game, hints);

BuildJumbleEmbed(response.Embed, game.JumbledArtist, game.Hints);
Expand Down Expand Up @@ -363,4 +345,87 @@ public async Task<ResponseModel> JumbleTimeExpired(ContextModel context, int gam

return response;
}

public async Task<ResponseModel> StartPixelation(ContextModel context, int userId,
CancellationTokenSource cancellationTokenSource)
{
var response = new ResponseModel
{
ResponseType = ResponseType.Embed,
};

//var existingGame = await this._gameService.GetJumbleSessionForChannelId(context.DiscordChannel.Id);
//if (existingGame != null && !existingGame.DateEnded.HasValue)
//{
// if (existingGame.DateStarted <= DateTime.UtcNow.AddSeconds(-(GameService.SecondsToGuess + 10)))
// {
// await this._gameService.JumbleEndSession(existingGame);
// }
// else
// {
// response.CommandResponse = CommandResponse.Cooldown;
// return response;
// }
//}

//var recentJumbles = await this._gameService.GetJumbleSessionsCountToday(context.ContextUser.UserId);
//var jumblesPlayedToday = recentJumbles.Count(c => c.DateStarted.Date == DateTime.Today);
//const int jumbleLimit = 30;
//if (!SupporterService.IsSupporter(context.ContextUser.UserType) && jumblesPlayedToday > jumbleLimit)
//{
// response.Embed.WithColor(DiscordConstants.InformationColorBlue);
// response.Embed.WithDescription($"You've used up all your {jumbleLimit} jumbles of today. [Get supporter]({Constants.GetSupporterDiscordLink}) to play unlimited jumble games and much more.");
// response.Components = new ComponentBuilder()
// .WithButton(Constants.GetSupporterButton, style: ButtonStyle.Link, url: Constants.GetSupporterDiscordLink);
// response.CommandResponse = CommandResponse.SupporterRequired;
// return response;
//}

var topAlbums = await this._albumService.GetUserAllTimeTopAlbums(userId, true);

if (topAlbums.Count(c => c.UserPlaycount > 50) <= 5)
{
response.Embed.WithColor(DiscordConstants.WarningColorOrange);
response.Embed.WithDescription($"Sorry, you haven't listened to enough artists yet to use this command. Please scrobble more music and try again later.");
response.CommandResponse = CommandResponse.NoScrobbles;
return response;
}

await this._albumService.FillMissingAlbumCovers(topAlbums);
var album = GameService.PickAlbumForPixelation(topAlbums, null);

if (album == null)
{
response.Embed.WithDescription($"You've played all jumbles that are available for you today. Come back tomorrow or scrobble more music to play again.");
response.CommandResponse = CommandResponse.NotFound;
return response;
}

var databaseAlbum = await this._albumService.GetAlbumFromDatabase(album.ArtistName, album.AlbumName);
if (databaseAlbum == null)
{
// Pick someone else and hope for the best
album = GameService.PickAlbumForPixelation(topAlbums, null);
databaseAlbum = await this._albumService.GetAlbumFromDatabase(album.ArtistName, album.AlbumName);
}


var game = await this._gameService.StartJumbleGame(userId, context, JumbleType.JumbleFirstWins, album.AlbumName, cancellationTokenSource);

var databaseArtist = await this._artistsService.GetArtistFromDatabase(album.ArtistName);
CountryInfo artistCountry = null;
if (databaseArtist?.CountryCode != null)
{
artistCountry = this._countryService.GetValidCountry(databaseArtist.CountryCode);
}

var hints = this._gameService.GetJumbleAlbumHints(databaseAlbum, databaseArtist, album.UserPlaycount.GetValueOrDefault(), artistCountry);
await this._gameService.JumbleStoreShowedHints(game, hints);

BuildJumbleEmbed(response.Embed, game.JumbledArtist, game.Hints);
response.Components = BuildJumbleComponents(game.JumbleSessionId, game.Hints);
response.GameSessionId = game.JumbleSessionId;

return response;
}
}
3 changes: 0 additions & 3 deletions src/FMBot.Bot/Services/ChartService.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Discord;
using FMBot.Bot.Extensions;
using FMBot.Bot.Models;
using FMBot.Domain;
Expand Down
147 changes: 142 additions & 5 deletions src/FMBot.Bot/Services/GameService.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
Expand All @@ -10,6 +12,7 @@
using Dapper;
using FMBot.Bot.Extensions;
using FMBot.Bot.Models;
using FMBot.Domain;
using FMBot.Domain.Models;
using FMBot.Persistence.Domain.Models;
using FMBot.Persistence.EntityFrameWork;
Expand All @@ -18,6 +21,7 @@
using Microsoft.Extensions.Options;
using Npgsql;
using Serilog;
using SkiaSharp;

namespace FMBot.Bot.Services;

Expand All @@ -26,13 +30,15 @@ public class GameService
private readonly IMemoryCache _cache;
private readonly IDbContextFactory<FMBotDbContext> _contextFactory;
private readonly BotSettings _botSettings;
private readonly HttpClient _client;

public const int SecondsToGuess = 25;

public GameService(IMemoryCache cache, IDbContextFactory<FMBotDbContext> contextFactory, IOptions<BotSettings> botSettings)
public GameService(IMemoryCache cache, IDbContextFactory<FMBotDbContext> contextFactory, IOptions<BotSettings> botSettings, HttpClient client)
{
this._cache = cache;
this._contextFactory = contextFactory;
this._client = client;
this._botSettings = botSettings.Value;
}

Expand Down Expand Up @@ -205,9 +211,9 @@ public async Task CancelToken(ulong channelId)
await token.CancelAsync();
}

public List<JumbleSessionHint> GetJumbleHints(Artist artist, long userPlaycount, CountryInfo country = null)
public List<JumbleSessionHint> GetJumbleArtistHints(Artist artist, long userPlaycount, CountryInfo country = null)
{
var hints = GetRandomHints(artist, country);
var hints = GetRandomArtistHints(artist, country);
hints.Add(new JumbleSessionHint(JumbleHintType.Playcount, $"- You have **{userPlaycount}** {StringExtensions.GetPlaysString(userPlaycount)} on this artist"));

RandomNumberGenerator.Shuffle(CollectionsMarshal.AsSpan(hints));
Expand All @@ -221,6 +227,22 @@ public List<JumbleSessionHint> GetJumbleHints(Artist artist, long userPlaycount,
return hints;
}

public List<JumbleSessionHint> GetJumbleAlbumHints(Album album,Artist artist, long userPlaycount, CountryInfo country = null)
{
var hints = GetRandomArtistHints(artist, country);
hints.Add(new JumbleSessionHint(JumbleHintType.Playcount, $"- You have **{userPlaycount}** {StringExtensions.GetPlaysString(userPlaycount)} on this album"));

RandomNumberGenerator.Shuffle(CollectionsMarshal.AsSpan(hints));

for (int i = 0; i < Math.Min(hints.Count, 3); i++)
{
hints[i].HintShown = true;
hints[i].Order = i;
}

return hints;
}

public static string HintsToString(List<JumbleSessionHint> hints, int count = 3)
{
var hintDescription = new StringBuilder();
Expand Down Expand Up @@ -296,7 +318,7 @@ public async Task JumbleAddAnswer(JumbleSession game, ulong discordUserId, bool
await db.SaveChangesAsync();
}

private static List<JumbleSessionHint> GetRandomHints(Artist artist, CountryInfo country = null)
private static List<JumbleSessionHint> GetRandomArtistHints(Artist artist, CountryInfo country = null)
{
var hints = new List<JumbleSessionHint>();

Expand Down Expand Up @@ -360,7 +382,7 @@ private static List<JumbleSessionHint> GetRandomHints(Artist artist, CountryInfo
return hints;
}

public static string JumbleWords(string input)
private static string JumbleWords(string input)
{
var words = input.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

Expand Down Expand Up @@ -450,5 +472,120 @@ public static int GetLevenshteinDistance(string source1, string source2)
return matrix[source1Length, source2Length];
}

public static TopAlbum PickAlbumForPixelation(List<TopAlbum> topAlbums, List<JumbleSession> recentJumbles = null)
{
recentJumbles ??= [];

var today = DateTime.Today;
var recentJumblesHashset = recentJumbles
.Where(w => w.DateStarted.Date == today)
.GroupBy(g => g.CorrectAnswer)
.Select(s => s.Key)
.ToHashSet(StringComparer.OrdinalIgnoreCase);

if (topAlbums.Count > 250 && recentJumbles.Count > 50)
{
var recentJumbleAnswers = recentJumbles
.GroupBy(g => g.CorrectAnswer)
.Select(s => s.Key)
.ToHashSet(StringComparer.OrdinalIgnoreCase);

recentJumblesHashset.UnionWith(recentJumbleAnswers);
}

topAlbums = topAlbums
.Where(w => w.AlbumCoverUrl != null &&
!recentJumblesHashset.Contains(w.ArtistName))
.OrderByDescending(o => o.UserPlaycount)
.ToList();

var multiplier = topAlbums.Count switch
{
> 5000 => 6,
> 2500 => 4,
> 1200 => 3,
> 500 => 2,
_ => 1
};

var minPlaycount = recentJumbles.Count(w => w.DateStarted.Date >= today.AddDays(-2)) switch
{
>= 75 => 1,
>= 40 => 2,
>= 12 => 5,
>= 4 => 15,
_ => 30
};

var finalMinPlaycount = minPlaycount * multiplier;
if (recentJumbles.Count(w => w.DateStarted.Date == today) >= 250)
{
finalMinPlaycount = 1;
}

var eligibleAlbums = topAlbums
.Where(w => w.UserPlaycount >= finalMinPlaycount)
.ToList();

Log.Information("PickAlbumForPixelation: {topArtistCount} top artists - {jumblesPlayedTodayCount} jumbles played today - " +
"{multiplier} multiplier - {minPlaycount} min playcount - {finalMinPlaycount} final min playcount",
topAlbums.Count, recentJumbles.Count, multiplier, minPlaycount, finalMinPlaycount);

if (eligibleAlbums.Count == 0)
{
TopAlbum fallbackAlbum = null;
if (topAlbums.Count > 0)
{
var fallBackIndex = RandomNumberGenerator.GetInt32(topAlbums.Count);
fallbackAlbum = topAlbums
.Where(w => !recentJumblesHashset.Contains(w.ArtistName))
.OrderByDescending(o => o.UserPlaycount)
.ElementAtOrDefault(fallBackIndex);
}

return fallbackAlbum;
}

var randomIndex = RandomNumberGenerator.GetInt32(eligibleAlbums.Count);
return eligibleAlbums[randomIndex];
}

public async Task<SKBitmap> GetSkImage(string url, string albumName, string artistName)
{
SKBitmap coverImage;
var localPath = ChartService.AlbumUrlToCacheFilePath(albumName, artistName);

if (localPath != null && File.Exists(localPath))
{
coverImage = SKBitmap.Decode(localPath);
Statistics.LastfmCachedImageCalls.Inc();
}
else
{
var bytes = await this._client.GetByteArrayAsync(url);

Statistics.LastfmImageCalls.Inc();

await using var stream = new MemoryStream(bytes);
coverImage = SKBitmap.Decode(stream);
}

return coverImage;
}

public async Task<SKBitmap> BlurCoverImage(SKBitmap coverImage, int blurLevel)
{
using var image = SKImage.FromBitmap(coverImage);

using var paint = new SKPaint();
paint.ImageFilter = SKImageFilter.CreateBlur(blurLevel, blurLevel);

var info = new SKImageInfo(coverImage.Width, coverImage.Height);
var blurredBitmap = new SKBitmap(info);
using var canvas = new SKCanvas(blurredBitmap);
canvas.Clear();
canvas.DrawImage(image, 0, 0, paint);

return blurredBitmap;
}
}
2 changes: 0 additions & 2 deletions src/FMBot.Bot/Services/WhoKnows/WhoKnowsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,11 @@
using Discord;
using FMBot.Bot.Extensions;
using FMBot.Bot.Models;
using FMBot.Domain;
using FMBot.Domain.Extensions;
using FMBot.Domain.Models;
using FMBot.Persistence.Domain.Models;
using FMBot.Persistence.EntityFrameWork;
using Microsoft.EntityFrameworkCore;
using static SpotifyAPI.Web.PlaylistRemoveItemsRequest;

namespace FMBot.Bot.Services.WhoKnows;

Expand Down
Loading

0 comments on commit cdc0270

Please sign in to comment.