Skip to content

Commit fe35511

Browse files
Merge pull request #317 from CodebreakerApp/copilot/fix-316
Create comprehensive benchmark project for CodeBreaker.Bot algorithm performance measurement
2 parents bba18b3 + 8566f4c commit fe35511

9 files changed

Lines changed: 880 additions & 0 deletions

src/Directory.Packages.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
<PackageVersion Include="Aspire.StackExchange.Redis.DistributedCaching" Version="9.4.2" />
3030
<PackageVersion Include="Azure.Identity" Version="1.16.0" />
3131
<PackageVersion Include="Azure.Monitor.OpenTelemetry.AspNetCore" Version="1.3.0" />
32+
<PackageVersion Include="BenchmarkDotNet" Version="0.15.0" />
3233
<PackageVersion Include="BlazorApplicationInsights" Version="3.2.1" />
3334
<PackageVersion Include="CNinnovation.Codebreaker.Analyzers" Version="3.8.0" />
3435
<PackageVersion Include="CNinnovation.Codebreaker.BackendModels" Version="3.8.0" />
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
using BenchmarkDotNet.Attributes;
2+
using BenchmarkDotNet.Diagnosers;
3+
using Codebreaker.GameAPIs.Client.Models;
4+
5+
namespace CodeBreaker.Bot.Benchmarks;
6+
7+
/// <summary>
8+
/// Benchmarks for the core CodeBreaker algorithm methods
9+
/// Measures execution time and memory consumption for filtering and matching operations
10+
/// </summary>
11+
[MemoryDiagnoser]
12+
[SimpleJob]
13+
[RankColumn]
14+
public class AlgorithmBenchmarks
15+
{
16+
private List<int> _fullGame6x4Values = null!;
17+
private List<int> _fullGame8x5Values = null!;
18+
private List<int> _reducedGame6x4Values = null!;
19+
private List<int> _reducedGame8x5Values = null!;
20+
private List<int> _smallGame6x4Values = null!;
21+
22+
private int _testSelection6x4;
23+
private int _testSelection8x5;
24+
private int _testSelection5x5x4;
25+
26+
private Dictionary<int, string> _colorNames6x4 = null!;
27+
private Dictionary<int, string> _colorNames8x5 = null!;
28+
private Dictionary<int, string> _colorNames5x5x4 = null!;
29+
30+
[GlobalSetup]
31+
public void Setup()
32+
{
33+
// Initialize full possible values for different game types
34+
_fullGame6x4Values = BenchmarkTestData.CreateGame6x4PossibleValues();
35+
_fullGame8x5Values = BenchmarkTestData.CreateGame8x5PossibleValues();
36+
37+
// Create reduced lists simulating games in progress
38+
_reducedGame6x4Values = BenchmarkTestData.CreateReducedPossibleValues(_fullGame6x4Values, 100);
39+
_reducedGame8x5Values = BenchmarkTestData.CreateReducedPossibleValues(_fullGame8x5Values, 200);
40+
_smallGame6x4Values = BenchmarkTestData.CreateReducedPossibleValues(_fullGame6x4Values, 20);
41+
42+
// Create test selections
43+
_testSelection6x4 = BenchmarkTestData.CreateTestSelection(GameType.Game6x4);
44+
_testSelection8x5 = BenchmarkTestData.CreateTestSelection(GameType.Game8x5);
45+
_testSelection5x5x4 = BenchmarkTestData.CreateTestSelection(GameType.Game5x5x4);
46+
47+
// Create color name mappings
48+
_colorNames6x4 = BenchmarkTestData.CreateColorNames(GameType.Game6x4);
49+
_colorNames8x5 = BenchmarkTestData.CreateColorNames(GameType.Game8x5);
50+
_colorNames5x5x4 = BenchmarkTestData.CreateColorNames(GameType.Game5x5x4);
51+
}
52+
53+
#region Black Matches Benchmarks
54+
55+
[Benchmark]
56+
[BenchmarkCategory("BlackMatches", "Game6x4", "FullList")]
57+
public List<int> HandleBlackMatches_Game6x4_FullList()
58+
{
59+
return _fullGame6x4Values.HandleBlackMatches(GameType.Game6x4, 2, _testSelection6x4);
60+
}
61+
62+
[Benchmark]
63+
[BenchmarkCategory("BlackMatches", "Game6x4", "ReducedList")]
64+
public List<int> HandleBlackMatches_Game6x4_ReducedList()
65+
{
66+
return _reducedGame6x4Values.HandleBlackMatches(GameType.Game6x4, 2, _testSelection6x4);
67+
}
68+
69+
[Benchmark]
70+
[BenchmarkCategory("BlackMatches", "Game6x4", "SmallList")]
71+
public List<int> HandleBlackMatches_Game6x4_SmallList()
72+
{
73+
return _smallGame6x4Values.HandleBlackMatches(GameType.Game6x4, 1, _testSelection6x4);
74+
}
75+
76+
[Benchmark]
77+
[BenchmarkCategory("BlackMatches", "Game8x5", "FullList")]
78+
public List<int> HandleBlackMatches_Game8x5_FullList()
79+
{
80+
return _fullGame8x5Values.HandleBlackMatches(GameType.Game8x5, 3, _testSelection8x5);
81+
}
82+
83+
[Benchmark]
84+
[BenchmarkCategory("BlackMatches", "Game8x5", "ReducedList")]
85+
public List<int> HandleBlackMatches_Game8x5_ReducedList()
86+
{
87+
return _reducedGame8x5Values.HandleBlackMatches(GameType.Game8x5, 2, _testSelection8x5);
88+
}
89+
90+
#endregion
91+
92+
#region White Matches Benchmarks
93+
94+
[Benchmark]
95+
[BenchmarkCategory("WhiteMatches", "Game6x4", "FullList")]
96+
public List<int> HandleWhiteMatches_Game6x4_FullList()
97+
{
98+
return _fullGame6x4Values.HandleWhiteMatches(GameType.Game6x4, 3, _testSelection6x4);
99+
}
100+
101+
[Benchmark]
102+
[BenchmarkCategory("WhiteMatches", "Game6x4", "ReducedList")]
103+
public List<int> HandleWhiteMatches_Game6x4_ReducedList()
104+
{
105+
return _reducedGame6x4Values.HandleWhiteMatches(GameType.Game6x4, 2, _testSelection6x4);
106+
}
107+
108+
[Benchmark]
109+
[BenchmarkCategory("WhiteMatches", "Game8x5", "ReducedList")]
110+
public List<int> HandleWhiteMatches_Game8x5_ReducedList()
111+
{
112+
return _reducedGame8x5Values.HandleWhiteMatches(GameType.Game8x5, 4, _testSelection8x5);
113+
}
114+
115+
#endregion
116+
117+
#region Blue Matches Benchmarks
118+
119+
[Benchmark]
120+
[BenchmarkCategory("BlueMatches", "Game5x5x4", "ReducedList")]
121+
public List<int> HandleBlueMatches_Game5x5x4_ReducedList()
122+
{
123+
return _reducedGame6x4Values.HandleBlueMatches(GameType.Game5x5x4, 2, _testSelection5x5x4);
124+
}
125+
126+
[Benchmark]
127+
[BenchmarkCategory("BlueMatches", "Game6x4", "ReducedList")]
128+
public List<int> HandleBlueMatches_Game6x4_ReducedList()
129+
{
130+
// For non-Game5x5x4, this should return the list unchanged
131+
return _reducedGame6x4Values.HandleBlueMatches(GameType.Game6x4, 2, _testSelection6x4);
132+
}
133+
134+
#endregion
135+
136+
#region No Matches Benchmarks
137+
138+
[Benchmark]
139+
[BenchmarkCategory("NoMatches", "Game6x4", "FullList")]
140+
public List<int> HandleNoMatches_Game6x4_FullList()
141+
{
142+
return _fullGame6x4Values.HandleNoMatches(GameType.Game6x4, _testSelection6x4);
143+
}
144+
145+
[Benchmark]
146+
[BenchmarkCategory("NoMatches", "Game6x4", "ReducedList")]
147+
public List<int> HandleNoMatches_Game6x4_ReducedList()
148+
{
149+
return _reducedGame6x4Values.HandleNoMatches(GameType.Game6x4, _testSelection6x4);
150+
}
151+
152+
[Benchmark]
153+
[BenchmarkCategory("NoMatches", "Game8x5", "ReducedList")]
154+
public List<int> HandleNoMatches_Game8x5_ReducedList()
155+
{
156+
return _reducedGame8x5Values.HandleNoMatches(GameType.Game8x5, _testSelection8x5);
157+
}
158+
159+
#endregion
160+
161+
#region Peg Selection Benchmarks
162+
163+
[Benchmark]
164+
[BenchmarkCategory("PegSelection", "Game6x4")]
165+
public int SelectPeg_Game6x4_Position0()
166+
{
167+
return _testSelection6x4.SelectPeg(GameType.Game6x4, 0);
168+
}
169+
170+
[Benchmark]
171+
[BenchmarkCategory("PegSelection", "Game6x4")]
172+
public int SelectPeg_Game6x4_Position3()
173+
{
174+
return _testSelection6x4.SelectPeg(GameType.Game6x4, 3);
175+
}
176+
177+
[Benchmark]
178+
[BenchmarkCategory("PegSelection", "Game8x5")]
179+
public int SelectPeg_Game8x5_Position0()
180+
{
181+
return _testSelection8x5.SelectPeg(GameType.Game8x5, 0);
182+
}
183+
184+
[Benchmark]
185+
[BenchmarkCategory("PegSelection", "Game8x5")]
186+
public int SelectPeg_Game8x5_Position4()
187+
{
188+
return _testSelection8x5.SelectPeg(GameType.Game8x5, 4);
189+
}
190+
191+
#endregion
192+
193+
#region Color Conversion Benchmarks
194+
195+
[Benchmark]
196+
[BenchmarkCategory("ColorConversion", "Game6x4")]
197+
public string[] IntToColors_Game6x4()
198+
{
199+
return _testSelection6x4.IntToColors(GameType.Game6x4, _colorNames6x4);
200+
}
201+
202+
[Benchmark]
203+
[BenchmarkCategory("ColorConversion", "Game8x5")]
204+
public string[] IntToColors_Game8x5()
205+
{
206+
return _testSelection8x5.IntToColors(GameType.Game8x5, _colorNames8x5);
207+
}
208+
209+
[Benchmark]
210+
[BenchmarkCategory("ColorConversion", "Game5x5x4")]
211+
public string[] IntToColors_Game5x5x4()
212+
{
213+
return _testSelection5x5x4.IntToColors(GameType.Game5x5x4, _colorNames5x5x4);
214+
}
215+
216+
#endregion
217+
}
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
using Codebreaker.GameAPIs.Client.Models;
2+
3+
namespace CodeBreaker.Bot.Benchmarks;
4+
5+
/// <summary>
6+
/// Helper class to generate test data for benchmarks
7+
/// </summary>
8+
public static class BenchmarkTestData
9+
{
10+
/// <summary>
11+
/// Creates a list of possible values for Game6x4 (similar to InitializePossibleValues6x4)
12+
/// </summary>
13+
public static List<int> CreateGame6x4PossibleValues()
14+
{
15+
static List<int> CreateColors(int colorCount, int shift)
16+
{
17+
List<int> pin = [];
18+
for (int i = 0; i < colorCount; i++)
19+
{
20+
int x = 1 << i + shift;
21+
pin.Add(x);
22+
}
23+
return pin;
24+
}
25+
26+
static List<int> AddColorsToList(List<int> list1, List<int> list2)
27+
{
28+
List<int> result = new(capacity: 1300);
29+
for (int i = 0; i < list1.Count; i++)
30+
{
31+
for (int j = 0; j < list2.Count; j++)
32+
{
33+
int x = list1[i] ^ list2[j];
34+
result.Add(x);
35+
}
36+
}
37+
return result;
38+
}
39+
40+
var digits1 = CreateColors(6, 0);
41+
var digits2 = CreateColors(6, 6);
42+
var list2 = AddColorsToList(digits1, digits2);
43+
var digits3 = CreateColors(6, 12);
44+
var list3 = AddColorsToList(list2, digits3);
45+
var digits4 = CreateColors(6, 18);
46+
var list4 = AddColorsToList(list3, digits4);
47+
list4.Sort();
48+
return list4;
49+
}
50+
51+
/// <summary>
52+
/// Creates a list of possible values for Game8x5
53+
/// </summary>
54+
public static List<int> CreateGame8x5PossibleValues()
55+
{
56+
static List<int> Create8Colors(int shift)
57+
{
58+
List<int> pin = [];
59+
for (int i = 0; i < 8; i++)
60+
{
61+
int x = 1 << (i + shift);
62+
pin.Add(x);
63+
}
64+
return pin;
65+
}
66+
67+
static List<int> AddColorsToList(List<int> list1, List<int> list2)
68+
{
69+
List<int> result = new(capacity: list1.Count * list2.Count);
70+
for (int i = 0; i < list1.Count; i++)
71+
{
72+
for (int j = 0; j < list2.Count; j++)
73+
{
74+
int x = list1[i] ^ list2[j];
75+
result.Add(x);
76+
}
77+
}
78+
return result;
79+
}
80+
81+
var digits1 = Create8Colors(0);
82+
var digits2 = Create8Colors(6);
83+
var list2 = AddColorsToList(digits1, digits2);
84+
var digits3 = Create8Colors(12);
85+
var list3 = AddColorsToList(list2, digits3);
86+
var digits4 = Create8Colors(18);
87+
var list4 = AddColorsToList(list3, digits4);
88+
var digits5 = Create8Colors(24);
89+
var list5 = AddColorsToList(list4, digits5);
90+
list5.Sort();
91+
return list5;
92+
}
93+
94+
/// <summary>
95+
/// Creates a reduced list simulating a game in progress
96+
/// </summary>
97+
public static List<int> CreateReducedPossibleValues(List<int> fullList, int targetSize)
98+
{
99+
if (fullList.Count <= targetSize)
100+
return fullList;
101+
102+
// Create a reduced list by taking every nth element
103+
var step = fullList.Count / targetSize;
104+
var reducedList = new List<int>(targetSize);
105+
106+
for (int i = 0; i < fullList.Count && reducedList.Count < targetSize; i += step)
107+
{
108+
reducedList.Add(fullList[i]);
109+
}
110+
111+
return reducedList;
112+
}
113+
114+
/// <summary>
115+
/// Creates typical selection values for testing
116+
/// </summary>
117+
public static int CreateTestSelection(GameType gameType)
118+
{
119+
return gameType switch
120+
{
121+
GameType.Game6x4 => 0b_000100_000100_000100_000100, // Same color in all positions for 6x4
122+
GameType.Game8x5 => 0b_000100_000100_000100_000100_000100, // Same color in all positions for 8x5
123+
GameType.Game5x5x4 => 0b_000100_000100_000100_000100, // Same combination in all positions for 5x5x4
124+
_ => 0b_000100_000100_000100_000100
125+
};
126+
}
127+
128+
/// <summary>
129+
/// Creates color name mappings for testing
130+
/// </summary>
131+
public static Dictionary<int, string> CreateColorNames(GameType gameType)
132+
{
133+
var colorNames = new Dictionary<int, string>();
134+
int key = 1;
135+
136+
if (gameType == GameType.Game5x5x4)
137+
{
138+
// Create shape+color combinations
139+
var colors = new[] { "Red", "Green", "Blue", "Yellow", "Orange" };
140+
var shapes = new[] { "Circle", "Square", "Triangle", "Diamond", "Star" };
141+
142+
foreach (var shape in shapes)
143+
{
144+
foreach (var color in colors)
145+
{
146+
colorNames[key] = $"{shape};{color}";
147+
key <<= 1;
148+
}
149+
}
150+
}
151+
else
152+
{
153+
// Color-only games
154+
var colors = gameType == GameType.Game8x5
155+
? new[] { "Red", "Green", "Blue", "Yellow", "Orange", "Purple", "Pink", "White" }
156+
: new[] { "Red", "Green", "Blue", "Yellow", "Orange", "Purple" };
157+
158+
foreach (var color in colors)
159+
{
160+
colorNames[key] = color;
161+
key <<= 1;
162+
}
163+
}
164+
165+
return colorNames;
166+
}
167+
}

0 commit comments

Comments
 (0)