Skip to content
Merged
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
1 change: 1 addition & 0 deletions src/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
<PackageVersion Include="Aspire.StackExchange.Redis.DistributedCaching" Version="9.4.2" />
<PackageVersion Include="Azure.Identity" Version="1.16.0" />
<PackageVersion Include="Azure.Monitor.OpenTelemetry.AspNetCore" Version="1.3.0" />
<PackageVersion Include="BenchmarkDotNet" Version="0.15.0" />
<PackageVersion Include="BlazorApplicationInsights" Version="3.2.1" />
<PackageVersion Include="CNinnovation.Codebreaker.Analyzers" Version="3.8.0" />
<PackageVersion Include="CNinnovation.Codebreaker.BackendModels" Version="3.8.0" />
Expand Down
217 changes: 217 additions & 0 deletions src/services/bot/CodeBreaker.Bot.Benchmarks/AlgorithmBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Diagnosers;
using Codebreaker.GameAPIs.Client.Models;

namespace CodeBreaker.Bot.Benchmarks;

/// <summary>
/// Benchmarks for the core CodeBreaker algorithm methods
/// Measures execution time and memory consumption for filtering and matching operations
/// </summary>
[MemoryDiagnoser]
[SimpleJob]
[RankColumn]
public class AlgorithmBenchmarks
{
private List<int> _fullGame6x4Values = null!;
private List<int> _fullGame8x5Values = null!;
private List<int> _reducedGame6x4Values = null!;
private List<int> _reducedGame8x5Values = null!;
private List<int> _smallGame6x4Values = null!;

private int _testSelection6x4;
private int _testSelection8x5;
private int _testSelection5x5x4;

private Dictionary<int, string> _colorNames6x4 = null!;
private Dictionary<int, string> _colorNames8x5 = null!;
private Dictionary<int, string> _colorNames5x5x4 = null!;

[GlobalSetup]
public void Setup()
{
// Initialize full possible values for different game types
_fullGame6x4Values = BenchmarkTestData.CreateGame6x4PossibleValues();
_fullGame8x5Values = BenchmarkTestData.CreateGame8x5PossibleValues();

// Create reduced lists simulating games in progress
_reducedGame6x4Values = BenchmarkTestData.CreateReducedPossibleValues(_fullGame6x4Values, 100);
_reducedGame8x5Values = BenchmarkTestData.CreateReducedPossibleValues(_fullGame8x5Values, 200);
_smallGame6x4Values = BenchmarkTestData.CreateReducedPossibleValues(_fullGame6x4Values, 20);

// Create test selections
_testSelection6x4 = BenchmarkTestData.CreateTestSelection(GameType.Game6x4);
_testSelection8x5 = BenchmarkTestData.CreateTestSelection(GameType.Game8x5);
_testSelection5x5x4 = BenchmarkTestData.CreateTestSelection(GameType.Game5x5x4);

// Create color name mappings
_colorNames6x4 = BenchmarkTestData.CreateColorNames(GameType.Game6x4);
_colorNames8x5 = BenchmarkTestData.CreateColorNames(GameType.Game8x5);
_colorNames5x5x4 = BenchmarkTestData.CreateColorNames(GameType.Game5x5x4);
}

#region Black Matches Benchmarks

[Benchmark]
[BenchmarkCategory("BlackMatches", "Game6x4", "FullList")]
public List<int> HandleBlackMatches_Game6x4_FullList()
{
return _fullGame6x4Values.HandleBlackMatches(GameType.Game6x4, 2, _testSelection6x4);
}

[Benchmark]
[BenchmarkCategory("BlackMatches", "Game6x4", "ReducedList")]
public List<int> HandleBlackMatches_Game6x4_ReducedList()
{
return _reducedGame6x4Values.HandleBlackMatches(GameType.Game6x4, 2, _testSelection6x4);
}

[Benchmark]
[BenchmarkCategory("BlackMatches", "Game6x4", "SmallList")]
public List<int> HandleBlackMatches_Game6x4_SmallList()
{
return _smallGame6x4Values.HandleBlackMatches(GameType.Game6x4, 1, _testSelection6x4);
}

[Benchmark]
[BenchmarkCategory("BlackMatches", "Game8x5", "FullList")]
public List<int> HandleBlackMatches_Game8x5_FullList()
{
return _fullGame8x5Values.HandleBlackMatches(GameType.Game8x5, 3, _testSelection8x5);
}

[Benchmark]
[BenchmarkCategory("BlackMatches", "Game8x5", "ReducedList")]
public List<int> HandleBlackMatches_Game8x5_ReducedList()
{
return _reducedGame8x5Values.HandleBlackMatches(GameType.Game8x5, 2, _testSelection8x5);
}

#endregion

#region White Matches Benchmarks

[Benchmark]
[BenchmarkCategory("WhiteMatches", "Game6x4", "FullList")]
public List<int> HandleWhiteMatches_Game6x4_FullList()
{
return _fullGame6x4Values.HandleWhiteMatches(GameType.Game6x4, 3, _testSelection6x4);
}

[Benchmark]
[BenchmarkCategory("WhiteMatches", "Game6x4", "ReducedList")]
public List<int> HandleWhiteMatches_Game6x4_ReducedList()
{
return _reducedGame6x4Values.HandleWhiteMatches(GameType.Game6x4, 2, _testSelection6x4);
}

[Benchmark]
[BenchmarkCategory("WhiteMatches", "Game8x5", "ReducedList")]
public List<int> HandleWhiteMatches_Game8x5_ReducedList()
{
return _reducedGame8x5Values.HandleWhiteMatches(GameType.Game8x5, 4, _testSelection8x5);
}

#endregion

#region Blue Matches Benchmarks

[Benchmark]
[BenchmarkCategory("BlueMatches", "Game5x5x4", "ReducedList")]
public List<int> HandleBlueMatches_Game5x5x4_ReducedList()
{
return _reducedGame6x4Values.HandleBlueMatches(GameType.Game5x5x4, 2, _testSelection5x5x4);
}

[Benchmark]
[BenchmarkCategory("BlueMatches", "Game6x4", "ReducedList")]
public List<int> HandleBlueMatches_Game6x4_ReducedList()
{
// For non-Game5x5x4, this should return the list unchanged
return _reducedGame6x4Values.HandleBlueMatches(GameType.Game6x4, 2, _testSelection6x4);
}

#endregion

#region No Matches Benchmarks

[Benchmark]
[BenchmarkCategory("NoMatches", "Game6x4", "FullList")]
public List<int> HandleNoMatches_Game6x4_FullList()
{
return _fullGame6x4Values.HandleNoMatches(GameType.Game6x4, _testSelection6x4);
}

[Benchmark]
[BenchmarkCategory("NoMatches", "Game6x4", "ReducedList")]
public List<int> HandleNoMatches_Game6x4_ReducedList()
{
return _reducedGame6x4Values.HandleNoMatches(GameType.Game6x4, _testSelection6x4);
}

[Benchmark]
[BenchmarkCategory("NoMatches", "Game8x5", "ReducedList")]
public List<int> HandleNoMatches_Game8x5_ReducedList()
{
return _reducedGame8x5Values.HandleNoMatches(GameType.Game8x5, _testSelection8x5);
}

#endregion

#region Peg Selection Benchmarks

[Benchmark]
[BenchmarkCategory("PegSelection", "Game6x4")]
public int SelectPeg_Game6x4_Position0()
{
return _testSelection6x4.SelectPeg(GameType.Game6x4, 0);
}

[Benchmark]
[BenchmarkCategory("PegSelection", "Game6x4")]
public int SelectPeg_Game6x4_Position3()
{
return _testSelection6x4.SelectPeg(GameType.Game6x4, 3);
}

[Benchmark]
[BenchmarkCategory("PegSelection", "Game8x5")]
public int SelectPeg_Game8x5_Position0()
{
return _testSelection8x5.SelectPeg(GameType.Game8x5, 0);
}

[Benchmark]
[BenchmarkCategory("PegSelection", "Game8x5")]
public int SelectPeg_Game8x5_Position4()
{
return _testSelection8x5.SelectPeg(GameType.Game8x5, 4);
}

#endregion

#region Color Conversion Benchmarks

[Benchmark]
[BenchmarkCategory("ColorConversion", "Game6x4")]
public string[] IntToColors_Game6x4()
{
return _testSelection6x4.IntToColors(GameType.Game6x4, _colorNames6x4);
}

[Benchmark]
[BenchmarkCategory("ColorConversion", "Game8x5")]
public string[] IntToColors_Game8x5()
{
return _testSelection8x5.IntToColors(GameType.Game8x5, _colorNames8x5);
}

[Benchmark]
[BenchmarkCategory("ColorConversion", "Game5x5x4")]
public string[] IntToColors_Game5x5x4()
{
return _testSelection5x5x4.IntToColors(GameType.Game5x5x4, _colorNames5x5x4);
}

#endregion
}
167 changes: 167 additions & 0 deletions src/services/bot/CodeBreaker.Bot.Benchmarks/BenchmarkTestData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
using Codebreaker.GameAPIs.Client.Models;

namespace CodeBreaker.Bot.Benchmarks;

/// <summary>
/// Helper class to generate test data for benchmarks
/// </summary>
public static class BenchmarkTestData
{
/// <summary>
/// Creates a list of possible values for Game6x4 (similar to InitializePossibleValues6x4)
/// </summary>
public static List<int> CreateGame6x4PossibleValues()
{
static List<int> CreateColors(int colorCount, int shift)
{
List<int> pin = [];
for (int i = 0; i < colorCount; i++)
{
int x = 1 << i + shift;
pin.Add(x);
}
return pin;
}

static List<int> AddColorsToList(List<int> list1, List<int> list2)
{
List<int> result = new(capacity: 1300);
for (int i = 0; i < list1.Count; i++)
{
for (int j = 0; j < list2.Count; j++)
{
int x = list1[i] ^ list2[j];
result.Add(x);
}
}
return result;
}

var digits1 = CreateColors(6, 0);
var digits2 = CreateColors(6, 6);
var list2 = AddColorsToList(digits1, digits2);
var digits3 = CreateColors(6, 12);
var list3 = AddColorsToList(list2, digits3);
var digits4 = CreateColors(6, 18);
var list4 = AddColorsToList(list3, digits4);
list4.Sort();
return list4;
}

/// <summary>
/// Creates a list of possible values for Game8x5
/// </summary>
public static List<int> CreateGame8x5PossibleValues()
{
static List<int> Create8Colors(int shift)
{
List<int> pin = [];
for (int i = 0; i < 8; i++)
{
int x = 1 << (i + shift);
pin.Add(x);
}
return pin;
}

static List<int> AddColorsToList(List<int> list1, List<int> list2)
{
List<int> result = new(capacity: list1.Count * list2.Count);
for (int i = 0; i < list1.Count; i++)
{
for (int j = 0; j < list2.Count; j++)
{
int x = list1[i] ^ list2[j];
result.Add(x);
}
}
return result;
}

var digits1 = Create8Colors(0);
var digits2 = Create8Colors(6);
var list2 = AddColorsToList(digits1, digits2);
var digits3 = Create8Colors(12);
var list3 = AddColorsToList(list2, digits3);
var digits4 = Create8Colors(18);
var list4 = AddColorsToList(list3, digits4);
var digits5 = Create8Colors(24);
var list5 = AddColorsToList(list4, digits5);
list5.Sort();
return list5;
}

/// <summary>
/// Creates a reduced list simulating a game in progress
/// </summary>
public static List<int> CreateReducedPossibleValues(List<int> fullList, int targetSize)
{
if (fullList.Count <= targetSize)
return fullList;

// Create a reduced list by taking every nth element
var step = fullList.Count / targetSize;
var reducedList = new List<int>(targetSize);

for (int i = 0; i < fullList.Count && reducedList.Count < targetSize; i += step)
{
reducedList.Add(fullList[i]);
}

return reducedList;
}

/// <summary>
/// Creates typical selection values for testing
/// </summary>
public static int CreateTestSelection(GameType gameType)
{
return gameType switch
{
GameType.Game6x4 => 0b_000100_000100_000100_000100, // Same color in all positions for 6x4
GameType.Game8x5 => 0b_000100_000100_000100_000100_000100, // Same color in all positions for 8x5
GameType.Game5x5x4 => 0b_000100_000100_000100_000100, // Same combination in all positions for 5x5x4
_ => 0b_000100_000100_000100_000100
};
}

/// <summary>
/// Creates color name mappings for testing
/// </summary>
public static Dictionary<int, string> CreateColorNames(GameType gameType)
{
var colorNames = new Dictionary<int, string>();
int key = 1;

if (gameType == GameType.Game5x5x4)
{
// Create shape+color combinations
var colors = new[] { "Red", "Green", "Blue", "Yellow", "Orange" };
var shapes = new[] { "Circle", "Square", "Triangle", "Diamond", "Star" };

foreach (var shape in shapes)
{
foreach (var color in colors)
{
colorNames[key] = $"{shape};{color}";
key <<= 1;
}
}
}
else
{
// Color-only games
var colors = gameType == GameType.Game8x5
? new[] { "Red", "Green", "Blue", "Yellow", "Orange", "Purple", "Pink", "White" }
: new[] { "Red", "Green", "Blue", "Yellow", "Orange", "Purple" };

foreach (var color in colors)
{
colorNames[key] = color;
key <<= 1;
}
}

return colorNames;
}
}
Loading