-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathAffixSetGenerator.cs
228 lines (196 loc) · 8.45 KB
/
AffixSetGenerator.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
using Shockah.Kokoro;
using StardewModdingAPI;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace Shockah.SeasonAffixes;
internal interface IAffixSetGenerator
{
IEnumerable<IReadOnlySet<ISeasonAffix>> Generate(OrdinalSeason season);
}
internal static class IAffixSetGeneratorExt
{
public static IAffixSetGenerator WeightedRandom(this IAffixSetGenerator affixSetGenerator, Random random, IAffixSetWeightProvider weightProvider)
=> new WeightedRandomAffixSetGenerator(affixSetGenerator, random, weightProvider);
public static IAffixSetGenerator AsLittleAsPossible(this IAffixSetGenerator affixSetGenerator)
=> new AsLittleAsPossibleAffixSetGenerator(affixSetGenerator);
public static IAffixSetGenerator AvoidingDuplicatesBetweenChoices(this IAffixSetGenerator affixSetGenerator)
=> new AvoidingDuplicatesBetweenChoicesAffixSetGenerator(affixSetGenerator);
public static IAffixSetGenerator Benchmarking(this IAffixSetGenerator affixSetGenerator, IMonitor monitor, string tag, LogLevel logLevel = LogLevel.Debug)
=> new BenchmarkingAffixSetGenerator(affixSetGenerator, monitor, tag, logLevel);
}
internal sealed class AllCombinationsAffixSetGenerator : IAffixSetGenerator
{
private IAffixesProvider AffixesProvider { get; init; }
private IAffixScoreProvider ScoreProvider { get; init; }
private IReadOnlyList<Func<IReadOnlySet<ISeasonAffix>, OrdinalSeason, bool?>> ConflictInfoProviders { get; init; }
private IReadOnlySet<ISeasonAffix> OtherAffixes { get; init; }
private int Positivity { get; init; }
private int Negativity { get; init; }
private int MaxAffixes { get; init; }
public AllCombinationsAffixSetGenerator(IAffixesProvider affixesProvider, IAffixScoreProvider scoreProvider, IReadOnlyList<Func<IReadOnlySet<ISeasonAffix>, OrdinalSeason, bool?>> conflictInfoProviders, IReadOnlySet<ISeasonAffix> otherAffixes, int positivity, int negativity, int maxAffixes)
{
this.AffixesProvider = affixesProvider;
this.ScoreProvider = scoreProvider;
this.ConflictInfoProviders = conflictInfoProviders;
this.OtherAffixes = otherAffixes;
this.Positivity = positivity;
this.Negativity = negativity;
this.MaxAffixes = maxAffixes;
}
public IEnumerable<IReadOnlySet<ISeasonAffix>> Generate(OrdinalSeason season)
{
var allAffixes = AffixesProvider.Affixes
.OrderByDescending(a => ScoreProvider.GetPositivity(a, season) + ScoreProvider.GetNegativity(a, season))
.ThenByDescending(a => ScoreProvider.GetPositivity(a, season) - ScoreProvider.GetNegativity(a, season))
.ToArray();
var affixPositivities = allAffixes.Select(a => ScoreProvider.GetPositivity(a, season)).ToArray();
var affixNegativities = allAffixes.Select(a => ScoreProvider.GetNegativity(a, season)).ToArray();
return GetAllCombinations(season, allAffixes, 0, affixPositivities, affixNegativities, new(0), 0, 0);
}
private bool IsConflicting(OrdinalSeason season, HashSet<ISeasonAffix> combination)
{
var toCheck = combination;
if (OtherAffixes.Count != 0)
toCheck = toCheck.Union(OtherAffixes).ToHashSet();
foreach (var provider in ConflictInfoProviders)
{
var result = provider(toCheck, season);
if (result is not null)
return result.Value;
}
return false;
}
private IEnumerable<HashSet<ISeasonAffix>> GetAllCombinations(OrdinalSeason season, ISeasonAffix[] allAffixes, int allAffixesIndex, int[] affixPositivities, int[] affixNegativities, HashSet<ISeasonAffix> combination, int currentPositivity, int currentNegativity)
{
if (currentPositivity > Positivity || currentNegativity > Negativity || combination.Count > MaxAffixes)
yield break;
if (IsConflicting(season, combination))
yield break;
if (currentPositivity == Positivity && currentNegativity == Negativity)
{
yield return combination;
yield break;
}
if (allAffixesIndex >= allAffixes.Length)
yield break;
IEnumerable<int> baseEnumerable = Enumerable.Range(allAffixesIndex, allAffixes.Length - allAffixesIndex);
if (combination.Count == 0 && Positivity + Negativity >= 3)
baseEnumerable = baseEnumerable.AsParallel().AsOrdered();
var enumerable = baseEnumerable.SelectMany(i => GetAllCombinations(season, allAffixes, i + 1, affixPositivities, affixNegativities, new HashSet<ISeasonAffix>(combination) { allAffixes[i] }, currentPositivity + affixPositivities[i], currentNegativity + affixNegativities[i]));
foreach (var result in enumerable)
yield return result;
}
}
internal sealed class WeightedRandomAffixSetGenerator : IAffixSetGenerator
{
private IAffixSetGenerator AffixSetGenerator { get; init; }
private Random Random { get; init; }
private IAffixSetWeightProvider WeightProvider { get; init; }
public WeightedRandomAffixSetGenerator(IAffixSetGenerator affixSetGenerator, Random random, IAffixSetWeightProvider weightProvider)
{
this.AffixSetGenerator = affixSetGenerator;
this.Random = random;
this.WeightProvider = weightProvider;
}
public IEnumerable<IReadOnlySet<ISeasonAffix>> Generate(OrdinalSeason season)
{
var weightedItems = AffixSetGenerator.Generate(season)
.Select(combination => new WeightedItem<IReadOnlySet<ISeasonAffix>>(WeightProvider.GetWeight(combination, season), combination))
.ToList();
if (weightedItems.Count == 0)
yield break;
double maxWeight = weightedItems.Max(item => item.Weight);
WeightedRandom<IReadOnlySet<ISeasonAffix>> weightedRandom = new(weightedItems.Where(item => item.Weight >= maxWeight / 100));
foreach (var result in weightedRandom.GetConsumingEnumerable(Random))
yield return result;
}
}
internal sealed class AsLittleAsPossibleAffixSetGenerator : IAffixSetGenerator
{
private IAffixSetGenerator AffixSetGenerator { get; init; }
public AsLittleAsPossibleAffixSetGenerator(IAffixSetGenerator affixSetGenerator)
{
this.AffixSetGenerator = affixSetGenerator;
}
public IEnumerable<IReadOnlySet<ISeasonAffix>> Generate(OrdinalSeason season)
{
var remainingResults = AffixSetGenerator.Generate(season).ToList();
int currentAllowedCount = 0;
while (remainingResults.Count != 0)
{
for (int i = 0; i < remainingResults.Count; i++)
{
if (remainingResults[i].Count != currentAllowedCount)
continue;
yield return remainingResults[i];
remainingResults.RemoveAt(i--);
}
currentAllowedCount++;
}
}
}
internal sealed class AvoidingDuplicatesBetweenChoicesAffixSetGenerator : IAffixSetGenerator
{
private IAffixSetGenerator AffixSetGenerator { get; init; }
public AvoidingDuplicatesBetweenChoicesAffixSetGenerator(IAffixSetGenerator affixSetGenerator)
{
this.AffixSetGenerator = affixSetGenerator;
}
public IEnumerable<IReadOnlySet<ISeasonAffix>> Generate(OrdinalSeason season)
{
List<HashSet<string>> yielded = new();
var remainingResults = AffixSetGenerator.Generate(season).ToList();
int allowedDuplicates = 0;
while (remainingResults.Count != 0)
{
for (int i = 0; i < remainingResults.Count; i++)
{
var ids = remainingResults[i].Select(a => a.UniqueID).ToHashSet();
foreach (var yieldedEntry in yielded)
{
if (yieldedEntry.Intersect(ids).Count() > allowedDuplicates)
goto remainingResultsContinue;
}
yielded.Add(ids);
yield return remainingResults[i];
remainingResults.RemoveAt(i--);
remainingResultsContinue:;
}
allowedDuplicates++;
}
}
}
internal sealed class BenchmarkingAffixSetGenerator : IAffixSetGenerator
{
private IAffixSetGenerator AffixSetGenerator { get; init; }
private IMonitor Monitor { get; init; }
private string Tag { get; init; }
private LogLevel LogLevel { get; init; }
public BenchmarkingAffixSetGenerator(IAffixSetGenerator affixSetGenerator, IMonitor monitor, string tag, LogLevel logLevel = LogLevel.Debug)
{
this.AffixSetGenerator = affixSetGenerator;
this.Monitor = monitor;
this.Tag = tag;
this.LogLevel = logLevel;
}
public IEnumerable<IReadOnlySet<ISeasonAffix>> Generate(OrdinalSeason season)
{
Monitor.Log($"[{Tag}] Generating affix sets...", LogLevel);
Stopwatch stopwatch = Stopwatch.StartNew();
int index = 0;
foreach (var result in AffixSetGenerator.Generate(season))
{
#if DEBUG
if (index < 10 || (index < 100 && index % 10 == 0) || (index < 1000 && index % 100 == 0) || (index < 10000 && index % 1000 == 0) || (index < 100000 && index % 10000 == 0))
Monitor.Log($"> [{Tag}] Generated affix set #{index + 1}, took {stopwatch.ElapsedMilliseconds}ms", LogLevel);
#endif
yield return result;
index++;
}
#if DEBUG
Monitor.Log($"> [{Tag}] Done generating affix sets after {index} results, took {stopwatch.ElapsedMilliseconds}ms.", LogLevel);
#endif
}
}