Skip to content

Commit

Permalink
Created game score by series calculator
Browse files Browse the repository at this point in the history
  • Loading branch information
SparrowBrain committed Apr 14, 2024
1 parent 57c0200 commit d0d31f7
Show file tree
Hide file tree
Showing 5 changed files with 311 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using AutoFixture.Xunit2;
using PlayNext.Model.Score.GameScore;
using Playnite.SDK.Models;
using Xunit;

Expand All @@ -11,63 +12,214 @@ public class GameScoreBySeriesCalculatorTests
{
[Theory]
[AutoData]
public void Calculate_NoGamesMatchSeriesWithScore_AllScoreIs0(
public void Calculate_GamesListIsEmpty_ScoreListIsEmpty(
CalculateSeriesScoreBy calculateSeriesScoreBy,
Dictionary<Guid, float> attributeScore,
GameScoreBySeriesCalculator sut)
{
// Arrange
var games = Array.Empty<Game>();

// Act
var result = sut.Calculate(calculateSeriesScoreBy, games, attributeScore);

// Arrange
Assert.Empty(result);
}

[Theory]
[AutoData]
public void Calculate_AttributeScoreListIsEmpty_ScoreListIsEmpty(
CalculateSeriesScoreBy calculateSeriesScoreBy,
Game[] games,
GameScoreBySeriesCalculator sut)
{
// Arrange
var attributeScore = new Dictionary<Guid, float>();

// Act
var result = sut.Calculate(calculateSeriesScoreBy, games, attributeScore);

// Arrange
Assert.Empty(result);
}

[Theory]
[AutoData]
public void Calculate_NoGamesMatchSeriesWithScore_ScoreListIsEmpty(
CalculateSeriesScoreBy calculateSeriesScoreBy,
Dictionary<Guid, float> attributeScore,
Game[] games,
GameScoreBySeriesCalculator sut)
{
// Act
var result = sut.Calculate(games, attributeScore);
var result = sut.Calculate(calculateSeriesScoreBy, games, attributeScore);

// Arrange
Assert.All(result, x => Assert.Equal(0, x.Value));
Assert.Empty(result);
}

[Theory]
[AutoData]
public void Calculate_OneGameMatchesSeriesWithScore_ThatGamesScoreIs100(
public void Calculate_OneGameMatchesSeriesWithScore_ThatGameScoreIs100(
CalculateSeriesScoreBy calculateSeriesScoreBy,
Dictionary<Guid, float> attributeScore,
Game[] games,
GameScoreBySeriesCalculator sut)
{
// Arrange
games.Last().SeriesIds.Add(attributeScore.Last().Key);
var seriesAttributeScore = attributeScore.Last();
games.Last().SeriesIds.Add(seriesAttributeScore.Key);

// Act
var result = sut.Calculate(games, attributeScore);
var result = sut.Calculate(calculateSeriesScoreBy, games, attributeScore);

// Arrange
var actualGame = Assert.Single(result, x => x.Value != 0);
Assert.Equal(100, actualGame.Value);
}
}

public class GameScoreBySeriesCalculator
{
public Dictionary<Guid, float> Calculate(IEnumerable<Game> games, Dictionary<Guid, float> attributeScore)
[Theory]
[AutoData]
public void Calculate_TwoGamesMatchesSeriesWithScoreAndCalculatingByReleaseDate_OlderReleaseDateGameIs100NewerOneIs50Score(
Dictionary<Guid, float> attributeScore,
Game[] games,
GameScoreBySeriesCalculator sut)
{
// Arrange
var seriesAttributeScore = attributeScore.Last();
var olderGame = games.Last();
var newerGame = games.First();
olderGame.SeriesIds.Add(seriesAttributeScore.Key);
newerGame.SeriesIds.Add(seriesAttributeScore.Key);
olderGame.ReleaseDate = new ReleaseDate(1988);
newerGame.ReleaseDate = new ReleaseDate(2002, 05, 12);

// Act
var result = sut.Calculate(CalculateSeriesScoreBy.ReleaseDate, games, attributeScore);

// Arrange
var olderGameScore = result[olderGame.Id];
var newerGameScore = result[newerGame.Id];
Assert.Equal(100, olderGameScore);
Assert.Equal(50, newerGameScore);
}

[Theory]
[AutoData]
public void Calculate_ThreeGamesMatchesSeriesWithScoreAndCalculatingByReleaseDate_OlderReleaseDateGameIs100NewerOneIs67NewestIs33Score(
Dictionary<Guid, float> attributeScore,
Game[] games,
GameScoreBySeriesCalculator sut)
{
var maxScore = 0f;
var gamesWithSeriesScores = games.ToDictionary(x => x.Id, x => x.SeriesIds.ToDictionary(s => s, s =>
{
if (!attributeScore.TryGetValue(s, out var value))
{
return 0;
}
if (value > maxScore)
{
maxScore = value;
}
return value;
}));

if (maxScore == 0)
{
return new Dictionary<Guid, float>();
}

return gamesWithSeriesScores.ToDictionary(x => x.Key, x => x.Value.Values.Sum() * 100 / maxScore);
// Arrange
var seriesAttributeScore = attributeScore.Last();
var olderGame = games.Last();
var newerGame = games.First();
var newestGame = games[1];
olderGame.SeriesIds.Add(seriesAttributeScore.Key);
newerGame.SeriesIds.Add(seriesAttributeScore.Key);
newestGame.SeriesIds.Add(seriesAttributeScore.Key);
olderGame.ReleaseDate = new ReleaseDate(1988);
newerGame.ReleaseDate = new ReleaseDate(2002, 05, 12);
newestGame.ReleaseDate = new ReleaseDate(2002, 05, 13);

// Act
var result = sut.Calculate(CalculateSeriesScoreBy.ReleaseDate, games, attributeScore);

// Arrange
var olderGameScore = result[olderGame.Id];
var newerGameScore = result[newerGame.Id];
var newestGameScore = result[newestGame.Id];
Assert.Equal(100, olderGameScore);
Assert.Equal(200f / 3, newerGameScore, 0.5);
Assert.Equal(100f / 3, newestGameScore, 0.5);
}

[Theory]
[AutoData]
public void Calculate_TwoGamesMatchesSeriesWithScoreAndCalculatingBySortingName_FirstGameIs100SecondIs50Score(
Dictionary<Guid, float> attributeScore,
Game[] games,
GameScoreBySeriesCalculator sut)
{
// Arrange
var seriesAttributeScore = attributeScore.Last();
var olderGame = games.Last();
var newerGame = games.First();
olderGame.SeriesIds.Add(seriesAttributeScore.Key);
newerGame.SeriesIds.Add(seriesAttributeScore.Key);
olderGame.SortingName = "ABC1";
newerGame.SortingName = "ABC2";

// Act
var result = sut.Calculate(CalculateSeriesScoreBy.SortingName, games, attributeScore);

// Arrange
var olderGameScore = result[olderGame.Id];
var newerGameScore = result[newerGame.Id];
Assert.Equal(100, olderGameScore);
Assert.Equal(50, newerGameScore);
}

[Theory]
[AutoData]
public void Calculate_ThreeGamesMatchesSeriesWithScoreAndCalculatingBySortingName_FirstIs100SecondIs67ThirdIs33Score(
Dictionary<Guid, float> attributeScore,
Game[] games,
GameScoreBySeriesCalculator sut)
{
// Arrange
var seriesAttributeScore = attributeScore.Last();
var olderGame = games.Last();
var newerGame = games.First();
var newestGame = games[1];
olderGame.SeriesIds.Add(seriesAttributeScore.Key);
newerGame.SeriesIds.Add(seriesAttributeScore.Key);
newestGame.SeriesIds.Add(seriesAttributeScore.Key);
olderGame.SortingName = "ABC1";
newerGame.SortingName = "ABC2";
newestGame.SortingName = "ABC3";

// Act
var result = sut.Calculate(CalculateSeriesScoreBy.SortingName, games, attributeScore);

// Arrange
var olderGameScore = result[olderGame.Id];
var newerGameScore = result[newerGame.Id];
var newestGameScore = result[newestGame.Id];
Assert.Equal(100, olderGameScore);
Assert.Equal(200f / 3, newerGameScore, 0.5);
Assert.Equal(100f / 3, newestGameScore, 0.5);
}

[Theory]
[AutoData]
public void Calculate_OneGameWithOneSeriesOneGameWithTwoSeriesWithScoreAndScoreIsAllEqual_TwoSeriesGameIs100(
CalculateSeriesScoreBy calculateSeriesScoreBy,
Dictionary<Guid, float> attributeScore,
float score,
Game[] games,
GameScoreBySeriesCalculator sut)
{
// Arrange
attributeScore = attributeScore.ToDictionary(x => x.Key, x => score);
var firstSeries = attributeScore.Last();
var secondSeries = attributeScore.First();
var gameWithOneSeries = games.Last();
var gameWithTwoSeries = games.First();
gameWithOneSeries.SeriesIds.Add(firstSeries.Key);
gameWithTwoSeries.SeriesIds.Add(firstSeries.Key);
gameWithTwoSeries.SeriesIds.Add(secondSeries.Key);

// Act
var result = sut.Calculate(calculateSeriesScoreBy, games, attributeScore);

// Arrange
var gameWithOneSeriesScore = result[gameWithOneSeries.Id];
var gameWithTwoSeriesScore = result[gameWithTwoSeries.Id];
Assert.Equal(100, gameWithTwoSeriesScore);
Assert.NotEqual(100, gameWithOneSeriesScore);
}
}
}
8 changes: 8 additions & 0 deletions PlayNext/Model/Score/GameScore/CalculateSeriesScoreBy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace PlayNext.Model.Score.GameScore
{
public enum CalculateSeriesScoreBy
{
ReleaseDate,
SortingName,
}
}
115 changes: 115 additions & 0 deletions PlayNext/Model/Score/GameScore/GameScoreBySeriesCalculator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Playnite.SDK.Models;

namespace PlayNext.Model.Score.GameScore
{
public class GameScoreBySeriesCalculator
{
public Dictionary<Guid, float> Calculate(
CalculateSeriesScoreBy calculateSeriesScoreBy,
IEnumerable<Game> games,
Dictionary<Guid, float> attributeScore)
{
if (!games.Any())
{
return new Dictionary<Guid, float>();
}

var gamesWithSeriesScores = GetGamesWithSeriesScores(games, attributeScore);
var seriesWithGames = GetSeriesWithGames(games, gamesWithSeriesScores);
var gamesWithScores = GetGamesWithSummedUpScores(calculateSeriesScoreBy, gamesWithSeriesScores, seriesWithGames);

var maxScore = gamesWithScores.Max(x => x.Value);
if (maxScore == 0)
{
return new Dictionary<Guid, float>();
}

return CalculateGamesWithNormalizedScores(gamesWithScores, maxScore);
}

private static Dictionary<Guid, Dictionary<Guid, float>> GetGamesWithSeriesScores(IEnumerable<Game> games, Dictionary<Guid, float> attributeScore)
{
return games.ToDictionary(x => x.Id, x => x.SeriesIds.ToDictionary(s => s, s =>
{
if (!attributeScore.TryGetValue(s, out var value))
{
return 0;
}
return value;
}));
}

private static Dictionary<Guid, IEnumerable<Game>> GetSeriesWithGames(IEnumerable<Game> games, Dictionary<Guid, Dictionary<Guid, float>> gamesWithSeriesScores)
{
return gamesWithSeriesScores
.SelectMany(x => x.Value.Keys.Select(s => new { SeriesId = s, Game = games.First(g => g.Id == x.Key) }))
.GroupBy(x => x.SeriesId)
.ToDictionary(x => x.Key, x => x.Select(g => g.Game));
}

private static Dictionary<Guid, float> GetGamesWithSummedUpScores(CalculateSeriesScoreBy calculateSeriesScoreBy, Dictionary<Guid, Dictionary<Guid, float>> gamesWithSeriesScores, Dictionary<Guid, IEnumerable<Game>> seriesWithGames)
{
return gamesWithSeriesScores.ToDictionary(x => x.Key, x =>
{
var score = 0f;
foreach (var seriesScore in x.Value)
{
var scoreMultiplier = GetScoreMultiplierByPositionInSeries(x.Key, calculateSeriesScoreBy, seriesWithGames, seriesScore);
score += seriesScore.Value * scoreMultiplier;
}
return score;
});
}

private static Dictionary<Guid, float> CalculateGamesWithNormalizedScores(Dictionary<Guid, float> gamesWithScores, float maxScore)
{
return gamesWithScores.ToDictionary(x => x.Key, x => x.Value * 100 / maxScore);
}

private static float GetScoreMultiplierByPositionInSeries(
Guid gameId,
CalculateSeriesScoreBy calculateSeriesScoreBy,
Dictionary<Guid, IEnumerable<Game>> seriesWithGames,
KeyValuePair<Guid, float> seriesScore)
{
var seriesGames = seriesWithGames[seriesScore.Key].ToList();
seriesGames.Sort((a, b) =>
{
if (calculateSeriesScoreBy == CalculateSeriesScoreBy.ReleaseDate)
{
if (a.ReleaseDate != null && b.ReleaseDate != null)
{
return a.ReleaseDate.Value.Date.CompareTo(b.ReleaseDate.Value.Date);
}
if (a.ReleaseDate != null)
{
return -1;
}
if (b.ReleaseDate != null)
{
return 1;
}
}
if (calculateSeriesScoreBy == CalculateSeriesScoreBy.SortingName)
{
return string.Compare(a.SortingName, b.SortingName, StringComparison.CurrentCulture);
}
throw new NotSupportedException("Not supported series calculation method");
});

var seriesGameIds = seriesGames.Select(g => g.Id).ToList();
var gameCountInSeries = seriesGameIds.Count;
var scoreMultiplier = ((float)gameCountInSeries - seriesGameIds.IndexOf(gameId)) / gameCountInSeries;
return scoreMultiplier;
}
}
}
2 changes: 2 additions & 0 deletions PlayNext/PlayNext.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@
<Compile Include="HowLongToBeat\HowLongToBeatExtension.cs" />
<Compile Include="HowLongToBeat\IHowLongToBeatExtension.cs" />
<Compile Include="Model\Filters\UnplayedFilter.cs" />
<Compile Include="Model\Score\GameScore\CalculateSeriesScoreBy.cs" />
<Compile Include="Model\Score\GameScore\GameLengthCalculator.cs" />
<Compile Include="Model\Score\GameScore\GameScoreBySeriesCalculator.cs" />
<Compile Include="Settings\Old\SettingsV2.cs" />
<Compile Include="Settings\Old\SettingsV1.cs" />
<Compile Include="ViewModels\CompletionStatusItem.cs" />
Expand Down
Loading

0 comments on commit d0d31f7

Please sign in to comment.