Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds support for fakemon transmutation. #340

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions TPP.Core/Commands/Definitions/TransmuteCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ public class TransmuteCommands : ICommandCollection
{
new Command("transmute", Transmute)
{
Description = "Transform 3 or more badges into a (usually) rarer badge. Costs one token. " +
"Arguments: <several Pokemon>(at least 3) t1"
Description = "Transform 3 or more badges into a (usually) rarer badge. Costs one or ten token(s). T1 shall be provided" +
" for regular transmutation, T10 will start a special transmutation that results in a fakemon badge. " +
"Arguments: <several Pokemon>(at least 3) T1 / T10"
},
};

Expand Down Expand Up @@ -52,7 +53,7 @@ public async Task<CommandResult> Transmute(CommandContext context)
if (!tokensArg.IsPresent)
return new CommandResult
{
Response = $"Please include payment 'T{ITransmutationCalculator.TransmutationCost}' in your command"
Response = $"Please include payment 'T{ITransmutationCalculator.TransmutationCostStandard}' or 'T{ITransmutationCalculator.TransmutationCostSpecial}' in your command"
};
ImmutableList<PkmnSpecies> speciesList = speciesArg.Values;

Expand Down
10 changes: 8 additions & 2 deletions TPP.Core/Setups.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,21 @@ public static ITransmuter SetUpTransmuter(
Databases databases,
OverlayConnection overlayConnection)
{
ImmutableSortedSet<PkmnSpecies> transmutableSpecies = knownSpecies
ImmutableSortedSet<PkmnSpecies> transmutableSpeciesStandard = knownSpecies
.Where(s => s.GetGeneration()
is Generation.Gen1 or Generation.Gen2 or Generation.Gen3 or Generation.Gen4
or Generation.Gen5 or Generation.Gen6 or Generation.Gen7 or Generation.Gen8
)
.ToImmutableSortedSet();
ImmutableSortedSet<PkmnSpecies> transmutableSpeciesSpecial = knownSpecies
.Where(s => s.GetGeneration()
is Generation.GenFake
)
.ToImmutableSortedSet();
ITransmutationCalculator transmutationCalculator = new TransmutationCalculator(
badgeStatsRepo: databases.BadgeStatsRepo,
transmutableSpecies: transmutableSpecies,
transmutableSpeciesStandard: transmutableSpeciesStandard,
transmutableSpeciesSpecial: transmutableSpeciesSpecial,
random: new Random().NextDouble);
ITransmuter transmuter = new Transmuter(
databases.BadgeRepo, transmutationCalculator, databases.TokensBank, databases.TransmutationLogRepo,
Expand Down
52 changes: 35 additions & 17 deletions TPP.Core/Transmutation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@ public TransmuteException(string? message) : base(message) { }

public interface ITransmutationCalculator
{
public const int TransmutationCost = 1;
public const int TransmutationCostStandard = 1;
public const int TransmutationCostSpecial = 10;
public const double TransmutationSpecialMinRarity = 11.0; // Minimum logarithmic inverse rarity at least one of
// the badges used in special transmutation needs to have
public const int MinTransmuteBadges = 3;

Task<PkmnSpecies> Transmute(IImmutableList<PkmnSpecies> inputSpecies);
Task<PkmnSpecies> Transmute(IImmutableList<PkmnSpecies> inputSpecies, int tokens);
}

/// <summary>
Expand All @@ -36,18 +39,23 @@ public class TransmutationCalculator : ITransmutationCalculator

private readonly IBadgeStatsRepo _badgeStatsRepo;
private readonly Func<double> _random;
private readonly IImmutableSet<PkmnSpecies> _transmutableSpecies;
private readonly IImmutableSet<PkmnSpecies> _transmutableSpeciesStandard;
private readonly IImmutableSet<PkmnSpecies> _transmutableSpeciesSpecial;

public TransmutationCalculator(
IBadgeStatsRepo badgeStatsRepo,
IImmutableSet<PkmnSpecies> transmutableSpecies,
IImmutableSet<PkmnSpecies> transmutableSpeciesStandard,
IImmutableSet<PkmnSpecies> transmutableSpeciesSpecial,
Func<double> random)
{
_badgeStatsRepo = badgeStatsRepo;
_transmutableSpecies = transmutableSpecies;
_transmutableSpeciesStandard = transmutableSpeciesStandard;
_transmutableSpeciesSpecial = transmutableSpeciesSpecial;
_random = random;
if (!transmutableSpecies.Any())
throw new ArgumentException("must provide at least 1 transmutable", nameof(transmutableSpecies));
if (!transmutableSpeciesStandard.Any())
throw new ArgumentException("must provide at least 1 transmutable", nameof(transmutableSpeciesStandard));
if (!transmutableSpeciesSpecial.Any())
throw new ArgumentException("must provide at least 1 transmutable", nameof(transmutableSpeciesSpecial));
}

private static double CombineRarities(IEnumerable<double> rarities) =>
Expand Down Expand Up @@ -103,13 +111,23 @@ private PkmnSpecies GetBadgeForRarity(double rarity, IDictionary<PkmnSpecies, do
return randomRounding < rarity ? closestNeg.Value.Item1 : closestPos.Value.Item1;
}

public async Task<PkmnSpecies> Transmute(IImmutableList<PkmnSpecies> inputSpecies)
public async Task<PkmnSpecies> Transmute(IImmutableList<PkmnSpecies> inputSpecies, int tokens)
{
if (inputSpecies.Count < ITransmutationCalculator.MinTransmuteBadges)
throw new TransmuteException(
$"Must transmute at least {ITransmutationCalculator.MinTransmuteBadges} badges");
IDictionary<PkmnSpecies, BadgeStat> stats = await _badgeStatsRepo.GetBadgeStats();
IImmutableSet<PkmnSpecies> transmutables = _transmutableSpecies
if (tokens == ITransmutationCalculator.TransmutationCostSpecial)
{
double minRarity = inputSpecies.Where(i => stats.ContainsKey(i)).Select(i => stats[i]).Min(t => t.Rarity);
double logInverseRarity = -1 * Math.Log(minRarity);
if (logInverseRarity < ITransmutationCalculator.TransmutationSpecialMinRarity)
throw new TransmuteException(
$"Have to provide a badge that has at least {ITransmutationCalculator.TransmutationSpecialMinRarity} in logarithmic inverse rarity");
}

IImmutableSet<PkmnSpecies> transmutables = (tokens == ITransmutationCalculator.TransmutationCostSpecial
? _transmutableSpeciesSpecial : _transmutableSpeciesStandard)
.Except(inputSpecies)
.Where(t => stats.ContainsKey(t) && stats[t].Rarity > 0)
.ToImmutableHashSet();
Expand All @@ -118,7 +136,7 @@ public async Task<PkmnSpecies> Transmute(IImmutableList<PkmnSpecies> inputSpecie
throw new TransmuteException(
"there are no transmutables left after removing all input species from the pool");
}
HashSet<PkmnSpecies> illegalInputs = inputSpecies.Except(_transmutableSpecies).ToHashSet();
HashSet<PkmnSpecies> illegalInputs = inputSpecies.Except(_transmutableSpeciesStandard).ToHashSet();
if (illegalInputs.Any())
throw new TransmuteException(string.Join(", ", illegalInputs) + " cannot be used for transmutation");

Expand Down Expand Up @@ -193,15 +211,15 @@ public Transmuter(

public async Task<Badge> Transmute(User user, int tokens, IImmutableList<PkmnSpecies> speciesList)
{
if (tokens != ITransmutationCalculator.TransmutationCost)
if (tokens != ITransmutationCalculator.TransmutationCostStandard && tokens != ITransmutationCalculator.TransmutationCostSpecial)
throw new TransmuteException(
$"Must pay exactly {ITransmutationCalculator.TransmutationCost} token to transmute.");
$"Must pay exactly {ITransmutationCalculator.TransmutationCostStandard} or {ITransmutationCalculator.TransmutationCostSpecial} token to transmute.");
if (speciesList.Count < ITransmutationCalculator.MinTransmuteBadges)
throw new TransmuteException(
$"Must transmute at least {ITransmutationCalculator.MinTransmuteBadges} badges.");
if (await _tokenBank.GetAvailableMoney(user) < tokens)
throw new TransmuteException(
$"You don't have the T{ITransmutationCalculator.TransmutationCost} required to transmute.");
$"You don't have the T{tokens} required to transmute.");

IImmutableList<Badge> inputBadges = ImmutableList<Badge>.Empty;
foreach (var grouping in speciesList.GroupBy(s => s))
Expand All @@ -215,7 +233,7 @@ public async Task<Badge> Transmute(User user, int tokens, IImmutableList<PkmnSpe
inputBadges = inputBadges.Concat(badges).ToImmutableList();
}

PkmnSpecies resultSpecies = await _transmutationCalculator.Transmute(speciesList);
PkmnSpecies resultSpecies = await _transmutationCalculator.Transmute(speciesList, tokens);

Dictionary<string, object?> additionalData = new();
IImmutableList<Badge> consumedBadges = await _badgeRepo
Expand All @@ -231,15 +249,15 @@ await _tokenBank.PerformTransaction(new Transaction<User>(user, -tokens,
}));
await _transmutationLogRepo.Log(user.Id, _clock.GetCurrentInstant(), tokens, inputIds, resultBadge.Id);

await OnTransmuted(user, speciesList, resultSpecies);
await OnTransmuted(user, speciesList, resultSpecies, tokens);
return resultBadge;
}

private async Task OnTransmuted(User user, IImmutableList<PkmnSpecies> inputs, PkmnSpecies output)
private async Task OnTransmuted(User user, IImmutableList<PkmnSpecies> inputs, PkmnSpecies output, int tokens)
{
List<PkmnSpecies> candidates = new();
for (int i = 0; i < 5; i++)
candidates.Add(await _transmutationCalculator.Transmute(inputs));
candidates.Add(await _transmutationCalculator.Transmute(inputs, tokens));
candidates.Insert(Random.Next(0, candidates.Count), output);
var args = new TransmuteEventArgs(user, inputs, output, candidates.ToImmutableList());
Transmuted?.Invoke(this, args);
Expand Down
11 changes: 7 additions & 4 deletions tests/TPP.Core.Tests/TransmutationTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,14 @@ public void TestSomeTransmutationCalculations()
ITransmutationCalculator transmutationCalculator = new TransmutationCalculator(
badgeStatsRepoMock.Object,
badgeStats.Keys.ToImmutableSortedSet(),
badgeStats.Keys.ToImmutableSortedSet(),
random: random.NextDouble
);

ImmutableSortedDictionary<PkmnSpecies, int> result = Enumerable
.Range(0, numTransmutations)
.Select(_ => transmutationCalculator
.Transmute(ImmutableList.Create(speciesCommon, speciesCommon, speciesCommon)).Result)
.Transmute(ImmutableList.Create(speciesCommon, speciesCommon, speciesCommon), 1).Result)
.GroupBy(species => species)
.ToImmutableSortedDictionary(grp => grp.Key, grp => grp.Count());

Expand Down Expand Up @@ -82,13 +83,14 @@ public void TestCannotTransmuteNonExistingBadge()
ITransmutationCalculator transmutationCalculator = new TransmutationCalculator(
badgeStatsRepoMock.Object,
badgeStats.Keys.ToImmutableSortedSet(),
badgeStats.Keys.ToImmutableSortedSet(),
random: random.NextDouble
);

ImmutableSortedDictionary<PkmnSpecies, int> result = Enumerable
.Range(0, numTransmutations)
.Select(_ => transmutationCalculator
.Transmute(Enumerable.Repeat(speciesUncommon, 100).ToImmutableList()).Result)
.Transmute(Enumerable.Repeat(speciesUncommon, 100).ToImmutableList(), 1).Result)
.GroupBy(species => species)
.ToImmutableSortedDictionary(grp => grp.Key, grp => grp.Count());

Expand Down Expand Up @@ -119,11 +121,12 @@ public void TestCannotTransmuteUntransmutableBadge()
ITransmutationCalculator transmutationCalculator = new TransmutationCalculator(
badgeStatsRepoMock.Object,
ImmutableHashSet.Create(speciesTransmutable),
ImmutableHashSet.Create(speciesUntransmutable),
random: () => 12345
);

TransmuteException exception = Assert.ThrowsAsync<TransmuteException>(async () => await transmutationCalculator
.Transmute(ImmutableList.Create(speciesUntransmutable, speciesUntransmutable, speciesUntransmutable)))!;
.Transmute(ImmutableList.Create(speciesUntransmutable, speciesUntransmutable, speciesUntransmutable), 1))!;
Assert.That(exception.Message, Is.EqualTo(speciesUntransmutable + " cannot be used for transmutation"));
}
}
Expand Down Expand Up @@ -161,7 +164,7 @@ public async Task TestSuccessfulTransmute()

bankMock.Setup(b => b.GetAvailableMoney(user)).ReturnsAsync(1);
List<Dictionary<string, object?>> transferData = new();
transmutationCalculatorMock.Setup(c => c.Transmute(inputSpeciesList)).ReturnsAsync(speciesOut);
transmutationCalculatorMock.Setup(c => c.Transmute(inputSpeciesList, 1)).ReturnsAsync(speciesOut);
badgeRepoMock.Setup(r => r.FindByUserAndSpecies(user.Id, speciesIn, inputSpeciesList.Count))
.ReturnsAsync(inputBadges);
badgeRepoMock.Setup(r => r.TransferBadges(inputBadges, null, "transmutation", Capture.In(transferData)))
Expand Down