diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 5b12bc8..8013b13 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -29,6 +29,7 @@ + diff --git a/src/services/bot/CodeBreaker.Bot.Benchmarks/AlgorithmBenchmarks.cs b/src/services/bot/CodeBreaker.Bot.Benchmarks/AlgorithmBenchmarks.cs new file mode 100644 index 0000000..440f22a --- /dev/null +++ b/src/services/bot/CodeBreaker.Bot.Benchmarks/AlgorithmBenchmarks.cs @@ -0,0 +1,217 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Diagnosers; +using Codebreaker.GameAPIs.Client.Models; + +namespace CodeBreaker.Bot.Benchmarks; + +/// +/// Benchmarks for the core CodeBreaker algorithm methods +/// Measures execution time and memory consumption for filtering and matching operations +/// +[MemoryDiagnoser] +[SimpleJob] +[RankColumn] +public class AlgorithmBenchmarks +{ + private List _fullGame6x4Values = null!; + private List _fullGame8x5Values = null!; + private List _reducedGame6x4Values = null!; + private List _reducedGame8x5Values = null!; + private List _smallGame6x4Values = null!; + + private int _testSelection6x4; + private int _testSelection8x5; + private int _testSelection5x5x4; + + private Dictionary _colorNames6x4 = null!; + private Dictionary _colorNames8x5 = null!; + private Dictionary _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 HandleBlackMatches_Game6x4_FullList() + { + return _fullGame6x4Values.HandleBlackMatches(GameType.Game6x4, 2, _testSelection6x4); + } + + [Benchmark] + [BenchmarkCategory("BlackMatches", "Game6x4", "ReducedList")] + public List HandleBlackMatches_Game6x4_ReducedList() + { + return _reducedGame6x4Values.HandleBlackMatches(GameType.Game6x4, 2, _testSelection6x4); + } + + [Benchmark] + [BenchmarkCategory("BlackMatches", "Game6x4", "SmallList")] + public List HandleBlackMatches_Game6x4_SmallList() + { + return _smallGame6x4Values.HandleBlackMatches(GameType.Game6x4, 1, _testSelection6x4); + } + + [Benchmark] + [BenchmarkCategory("BlackMatches", "Game8x5", "FullList")] + public List HandleBlackMatches_Game8x5_FullList() + { + return _fullGame8x5Values.HandleBlackMatches(GameType.Game8x5, 3, _testSelection8x5); + } + + [Benchmark] + [BenchmarkCategory("BlackMatches", "Game8x5", "ReducedList")] + public List HandleBlackMatches_Game8x5_ReducedList() + { + return _reducedGame8x5Values.HandleBlackMatches(GameType.Game8x5, 2, _testSelection8x5); + } + + #endregion + + #region White Matches Benchmarks + + [Benchmark] + [BenchmarkCategory("WhiteMatches", "Game6x4", "FullList")] + public List HandleWhiteMatches_Game6x4_FullList() + { + return _fullGame6x4Values.HandleWhiteMatches(GameType.Game6x4, 3, _testSelection6x4); + } + + [Benchmark] + [BenchmarkCategory("WhiteMatches", "Game6x4", "ReducedList")] + public List HandleWhiteMatches_Game6x4_ReducedList() + { + return _reducedGame6x4Values.HandleWhiteMatches(GameType.Game6x4, 2, _testSelection6x4); + } + + [Benchmark] + [BenchmarkCategory("WhiteMatches", "Game8x5", "ReducedList")] + public List HandleWhiteMatches_Game8x5_ReducedList() + { + return _reducedGame8x5Values.HandleWhiteMatches(GameType.Game8x5, 4, _testSelection8x5); + } + + #endregion + + #region Blue Matches Benchmarks + + [Benchmark] + [BenchmarkCategory("BlueMatches", "Game5x5x4", "ReducedList")] + public List HandleBlueMatches_Game5x5x4_ReducedList() + { + return _reducedGame6x4Values.HandleBlueMatches(GameType.Game5x5x4, 2, _testSelection5x5x4); + } + + [Benchmark] + [BenchmarkCategory("BlueMatches", "Game6x4", "ReducedList")] + public List 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 HandleNoMatches_Game6x4_FullList() + { + return _fullGame6x4Values.HandleNoMatches(GameType.Game6x4, _testSelection6x4); + } + + [Benchmark] + [BenchmarkCategory("NoMatches", "Game6x4", "ReducedList")] + public List HandleNoMatches_Game6x4_ReducedList() + { + return _reducedGame6x4Values.HandleNoMatches(GameType.Game6x4, _testSelection6x4); + } + + [Benchmark] + [BenchmarkCategory("NoMatches", "Game8x5", "ReducedList")] + public List 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 +} \ No newline at end of file diff --git a/src/services/bot/CodeBreaker.Bot.Benchmarks/BenchmarkTestData.cs b/src/services/bot/CodeBreaker.Bot.Benchmarks/BenchmarkTestData.cs new file mode 100644 index 0000000..f193df6 --- /dev/null +++ b/src/services/bot/CodeBreaker.Bot.Benchmarks/BenchmarkTestData.cs @@ -0,0 +1,167 @@ +using Codebreaker.GameAPIs.Client.Models; + +namespace CodeBreaker.Bot.Benchmarks; + +/// +/// Helper class to generate test data for benchmarks +/// +public static class BenchmarkTestData +{ + /// + /// Creates a list of possible values for Game6x4 (similar to InitializePossibleValues6x4) + /// + public static List CreateGame6x4PossibleValues() + { + static List CreateColors(int colorCount, int shift) + { + List pin = []; + for (int i = 0; i < colorCount; i++) + { + int x = 1 << i + shift; + pin.Add(x); + } + return pin; + } + + static List AddColorsToList(List list1, List list2) + { + List 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; + } + + /// + /// Creates a list of possible values for Game8x5 + /// + public static List CreateGame8x5PossibleValues() + { + static List Create8Colors(int shift) + { + List pin = []; + for (int i = 0; i < 8; i++) + { + int x = 1 << (i + shift); + pin.Add(x); + } + return pin; + } + + static List AddColorsToList(List list1, List list2) + { + List 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; + } + + /// + /// Creates a reduced list simulating a game in progress + /// + public static List CreateReducedPossibleValues(List 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(targetSize); + + for (int i = 0; i < fullList.Count && reducedList.Count < targetSize; i += step) + { + reducedList.Add(fullList[i]); + } + + return reducedList; + } + + /// + /// Creates typical selection values for testing + /// + 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 + }; + } + + /// + /// Creates color name mappings for testing + /// + public static Dictionary CreateColorNames(GameType gameType) + { + var colorNames = new Dictionary(); + 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; + } +} \ No newline at end of file diff --git a/src/services/bot/CodeBreaker.Bot.Benchmarks/CodeBreaker.Bot.Benchmarks.csproj b/src/services/bot/CodeBreaker.Bot.Benchmarks/CodeBreaker.Bot.Benchmarks.csproj new file mode 100644 index 0000000..3a08c22 --- /dev/null +++ b/src/services/bot/CodeBreaker.Bot.Benchmarks/CodeBreaker.Bot.Benchmarks.csproj @@ -0,0 +1,15 @@ + + + net9.0 + enable + enable + false + Exe + + + + + + + + \ No newline at end of file diff --git a/src/services/bot/CodeBreaker.Bot.Benchmarks/GameScenarioBenchmarks.cs b/src/services/bot/CodeBreaker.Bot.Benchmarks/GameScenarioBenchmarks.cs new file mode 100644 index 0000000..ec6a332 --- /dev/null +++ b/src/services/bot/CodeBreaker.Bot.Benchmarks/GameScenarioBenchmarks.cs @@ -0,0 +1,176 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Diagnosers; +using Codebreaker.GameAPIs.Client.Models; + +namespace CodeBreaker.Bot.Benchmarks; + +/// +/// Benchmarks that simulate realistic game scenarios +/// These test combinations of operations as they would occur during actual gameplay +/// +[MemoryDiagnoser] +[SimpleJob] +[RankColumn] +public class GameScenarioBenchmarks +{ + private List _initialValues6x4 = null!; + private List _initialValues8x5 = null!; + + [GlobalSetup] + public void Setup() + { + _initialValues6x4 = BenchmarkTestData.CreateGame6x4PossibleValues(); + _initialValues8x5 = BenchmarkTestData.CreateGame8x5PossibleValues(); + } + + #region Typical Game Progression Scenarios + + [Benchmark] + [BenchmarkCategory("GameScenario", "Game6x4", "EarlyGame")] + public List SimulateEarlyGame6x4_Move1() + { + // Simulate first move with no matches + var selection = BenchmarkTestData.CreateTestSelection(GameType.Game6x4); + return _initialValues6x4.HandleNoMatches(GameType.Game6x4, selection); + } + + [Benchmark] + [BenchmarkCategory("GameScenario", "Game6x4", "MidGame")] + public List SimulateMidGame6x4_Move3() + { + // Simulate mid-game with some black matches + var values = _initialValues6x4.ToList(); + var selection1 = BenchmarkTestData.CreateTestSelection(GameType.Game6x4); + + // First move: no matches + values = values.HandleNoMatches(GameType.Game6x4, selection1); + + // Second move: 1 black match + var selection2 = 0b_001000_000100_000100_000100; // Different first position + values = values.HandleBlackMatches(GameType.Game6x4, 1, selection2); + + // Third move: 2 white matches + var selection3 = 0b_000100_001000_000100_000100; // Rearranged colors + return values.HandleWhiteMatches(GameType.Game6x4, 2, selection3); + } + + [Benchmark] + [BenchmarkCategory("GameScenario", "Game6x4", "LateGame")] + public List SimulateLateGame6x4_Move5() + { + // Simulate late game with high precision + var values = _initialValues6x4.ToList(); + var baseSelection = BenchmarkTestData.CreateTestSelection(GameType.Game6x4); + + // Apply multiple filtering operations + values = values.HandleNoMatches(GameType.Game6x4, baseSelection); + values = values.HandleBlackMatches(GameType.Game6x4, 2, 0b_001000_000100_000100_000100); + values = values.HandleWhiteMatches(GameType.Game6x4, 3, 0b_000100_001000_010000_000100); + + // Final precise move + return values.HandleBlackMatches(GameType.Game6x4, 3, 0b_001000_010000_000100_100000); + } + + [Benchmark] + [BenchmarkCategory("GameScenario", "Game8x5", "EarlyGame")] + public List SimulateEarlyGame8x5_Move1() + { + // Simulate first move for 8x5 game + var selection = BenchmarkTestData.CreateTestSelection(GameType.Game8x5); + return _initialValues8x5.HandleNoMatches(GameType.Game8x5, selection); + } + + [Benchmark] + [BenchmarkCategory("GameScenario", "Game8x5", "MidGame")] + public List SimulateMidGame8x5_Move3() + { + // Simulate more complex 8x5 game progression + var values = _initialValues8x5.ToList(); + + // Move 1: Some white matches + values = values.HandleWhiteMatches(GameType.Game8x5, 3, 0b_000100_001000_010000_100000_000010); + + // Move 2: Black matches + values = values.HandleBlackMatches(GameType.Game8x5, 2, 0b_001000_000100_010000_000010_100000); + + // Move 3: More precise filtering + return values.HandleBlackMatches(GameType.Game8x5, 4, 0b_000100_001000_000010_010000_100000); + } + + #endregion + + #region Memory-Intensive Scenarios + + [Benchmark] + [BenchmarkCategory("GameScenario", "Memory", "WorstCase")] + public List SimulateWorstCaseFiltering() + { + // Scenario where filtering operations don't reduce the list much + var values = _initialValues6x4.ToList(); + + // Multiple operations that don't filter much + for (int i = 0; i < 5; i++) + { + var selection = 0b_000001_000001_000001_000001 << i; // Different selections + values = values.HandleWhiteMatches(GameType.Game6x4, 1, selection); + } + + return values; + } + + [Benchmark] + [BenchmarkCategory("GameScenario", "Memory", "BestCase")] + public List SimulateBestCaseFiltering() + { + // Scenario where filtering operations reduce the list significantly + var values = _initialValues6x4.ToList(); + + // Operations that should filter aggressively + values = values.HandleBlackMatches(GameType.Game6x4, 3, BenchmarkTestData.CreateTestSelection(GameType.Game6x4)); + values = values.HandleBlackMatches(GameType.Game6x4, 3, 0b_001000_010000_100000_000010); + + return values; + } + + #endregion + + #region Combined Operations Benchmark + + [Benchmark] + [BenchmarkCategory("GameScenario", "Combined", "FullGameSimulation")] + public int SimulateCompleteGame6x4() + { + var values = _initialValues6x4.ToList(); + int moveCount = 0; + + // Simulate a complete game until very few values remain + while (values.Count > 10 && moveCount < 8) + { + moveCount++; + var selection = 0b_000100_000100_000100_000100 << (moveCount % 6); + + switch (moveCount % 4) + { + case 0: + values = values.HandleNoMatches(GameType.Game6x4, selection); + break; + case 1: + if (values.Count > 100) + values = values.HandleBlackMatches(GameType.Game6x4, 1, selection); + break; + case 2: + if (values.Count > 50) + values = values.HandleWhiteMatches(GameType.Game6x4, 2, selection); + break; + case 3: + if (values.Count > 25) + values = values.HandleBlackMatches(GameType.Game6x4, 2, selection); + break; + } + } + + return values.Count; + } + + #endregion +} \ No newline at end of file diff --git a/src/services/bot/CodeBreaker.Bot.Benchmarks/GlobalUsings.cs b/src/services/bot/CodeBreaker.Bot.Benchmarks/GlobalUsings.cs new file mode 100644 index 0000000..ad0ed40 --- /dev/null +++ b/src/services/bot/CodeBreaker.Bot.Benchmarks/GlobalUsings.cs @@ -0,0 +1,3 @@ +global using System; +global using System.Collections.Generic; +global using System.Linq; \ No newline at end of file diff --git a/src/services/bot/CodeBreaker.Bot.Benchmarks/InitializationBenchmarks.cs b/src/services/bot/CodeBreaker.Bot.Benchmarks/InitializationBenchmarks.cs new file mode 100644 index 0000000..a0f0144 --- /dev/null +++ b/src/services/bot/CodeBreaker.Bot.Benchmarks/InitializationBenchmarks.cs @@ -0,0 +1,73 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Diagnosers; + +namespace CodeBreaker.Bot.Benchmarks; + +/// +/// Benchmarks for the initialization methods that create possible values lists +/// These are typically called once per game but can be memory-intensive +/// +[MemoryDiagnoser] +[SimpleJob] +[RankColumn] +public class InitializationBenchmarks +{ + #region Possible Values Initialization + + [Benchmark] + [BenchmarkCategory("Initialization", "Game6x4")] + public List InitializePossibleValues_Game6x4() + { + return BenchmarkTestData.CreateGame6x4PossibleValues(); + } + + [Benchmark] + [BenchmarkCategory("Initialization", "Game8x5")] + public List InitializePossibleValues_Game8x5() + { + return BenchmarkTestData.CreateGame8x5PossibleValues(); + } + + #endregion + + #region List Operations + + [Benchmark] + [BenchmarkCategory("ListOperations", "Memory")] + public List CreateAndSortLargeList() + { + var values = BenchmarkTestData.CreateGame6x4PossibleValues(); + values.Sort(); + return values; + } + + [Benchmark] + [BenchmarkCategory("ListOperations", "Memory")] + public List CreateReducedList() + { + var fullList = BenchmarkTestData.CreateGame6x4PossibleValues(); + return BenchmarkTestData.CreateReducedPossibleValues(fullList, 100); + } + + #endregion + + #region Memory Intensive Operations + + [Benchmark] + [BenchmarkCategory("Memory", "Large")] + public int CountGame6x4Values() + { + var values = BenchmarkTestData.CreateGame6x4PossibleValues(); + return values.Count; + } + + [Benchmark] + [BenchmarkCategory("Memory", "Large")] + public int CountGame8x5Values() + { + var values = BenchmarkTestData.CreateGame8x5PossibleValues(); + return values.Count; + } + + #endregion +} \ No newline at end of file diff --git a/src/services/bot/CodeBreaker.Bot.Benchmarks/Program.cs b/src/services/bot/CodeBreaker.Bot.Benchmarks/Program.cs new file mode 100644 index 0000000..66e0000 --- /dev/null +++ b/src/services/bot/CodeBreaker.Bot.Benchmarks/Program.cs @@ -0,0 +1,15 @@ +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Running; + +namespace CodeBreaker.Bot.Benchmarks; + +public class Program +{ + public static void Main(string[] args) + { + var config = DefaultConfig.Instance + .WithOptions(ConfigOptions.DisableOptimizationsValidator); + + BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, config); + } +} \ No newline at end of file diff --git a/src/services/bot/CodeBreaker.Bot.Benchmarks/README.md b/src/services/bot/CodeBreaker.Bot.Benchmarks/README.md new file mode 100644 index 0000000..9ac8016 --- /dev/null +++ b/src/services/bot/CodeBreaker.Bot.Benchmarks/README.md @@ -0,0 +1,213 @@ +# CodeBreaker.Bot Benchmarks + +This project provides comprehensive performance benchmarks for the CodeBreaker.Bot algorithms. It measures execution time and memory consumption of the core algorithms used for playing Codebreaker games. + +## Overview + +The benchmarks evaluate the performance of: + +### Core Algorithm Methods +- **HandleBlackMatches**: Filters possible values based on exact position matches (black pegs) +- **HandleWhiteMatches**: Filters based on correct color/wrong position matches (white pegs) +- **HandleBlueMatches**: Filters based on partial matches (specific to Game5x5x4) +- **HandleNoMatches**: Filters when no colors match the selection +- **SelectPeg**: Extracts individual peg values from integer representation +- **IntToColors**: Converts integer representation to color names + +### Initialization Methods +- **InitializePossibleValues**: Creates initial possible values lists for different game types +- **Memory-intensive list operations**: Sorting, reducing, and managing large collections + +### Game Scenarios +- **Early game**: Initial moves with large possibility spaces +- **Mid-game**: Progressive filtering with mixed match types +- **Late game**: High-precision filtering with small possibility spaces +- **Complete game simulation**: Full game progression scenarios + +## Game Types Tested + +- **Game6x4**: 6 colors, 4 positions (traditional Mastermind) +- **Game8x5**: 8 colors, 5 positions +- **Game5x5x4**: 25 shape+color combinations, 4 positions + +## Benchmark Categories + +### 1. AlgorithmBenchmarks +Core algorithm performance with different list sizes: +- Full lists (1,000+ values) +- Reduced lists (20-200 values) +- Various game types and match scenarios + +### 2. InitializationBenchmarks +One-time setup operations: +- Possible values generation +- List creation and sorting +- Memory allocation patterns + +### 3. GameScenarioBenchmarks +Realistic gameplay simulations: +- Progressive game states +- Combined operation sequences +- Best/worst-case filtering scenarios + +## Running the Benchmarks + +### Prerequisites + +- .NET 9.0 SDK +- Windows, macOS, or Linux environment + +### Quick Start + +1. **Build the project**: + ```bash + cd src/services/bot/CodeBreaker.Bot.Benchmarks + dotnet build -c Release + ``` + +2. **Run all benchmarks**: + ```bash + dotnet run -c Release + ``` + +3. **Run specific categories**: + ```bash + # Run only algorithm benchmarks + dotnet run -c Release -- --filter "*AlgorithmBenchmarks*" + + # Run only Game6x4 benchmarks + dotnet run -c Release -- --filter "*Game6x4*" + + # Run only memory-intensive benchmarks + dotnet run -c Release -- --filter "*Memory*" + ``` + +### Advanced Options + +1. **Export results to different formats**: + ```bash + # Export to CSV + dotnet run -c Release -- --exporters csv + + # Export to JSON + dotnet run -c Release -- --exporters json + + # Export to HTML + dotnet run -c Release -- --exporters html + ``` + +2. **Run specific benchmark methods**: + ```bash + # Run only black matches benchmarks + dotnet run -c Release -- --filter "*BlackMatches*" + + # Run only initialization benchmarks + dotnet run -c Release -- --filter "*Initialization*" + ``` + +3. **Memory profiling**: + ```bash + # Run with detailed memory analysis + dotnet run -c Release -- --memory + ``` + +## Understanding the Results + +### Key Metrics + +- **Mean**: Average execution time +- **Error**: Half of the 99.9% confidence interval +- **StdDev**: Standard deviation of measurements +- **Median**: Middle value of all measurements +- **Allocated**: Memory allocated during execution +- **Gen 0/1/2**: Garbage collection counts + +### Typical Performance Expectations + +| Operation | List Size | Expected Range | +|-----------|-----------|----------------| +| HandleBlackMatches | 1,000+ values | 10-100 μs | +| HandleWhiteMatches | 1,000+ values | 50-500 μs | +| HandleNoMatches | 1,000+ values | 5-50 μs | +| SelectPeg | Single value | < 1 μs | +| IntToColors | Single value | 1-5 μs | +| InitializePossibleValues | N/A | 1-10 ms | + +### Memory Usage Patterns + +- **Game6x4 initialization**: ~50-100 KB +- **Game8x5 initialization**: ~200-500 KB +- **Large list filtering**: Proportional to input size +- **String conversions**: Additional overhead for color names + +## Interpreting Results for Optimization + +### Performance Baselines + +Use these benchmarks to: + +1. **Establish baselines** before implementing algorithm changes +2. **Compare alternative implementations** of the same functionality +3. **Identify bottlenecks** in real game scenarios +4. **Monitor regression** when making code changes + +### Common Optimization Targets + +Based on the benchmarks, focus optimization efforts on: + +1. **HandleWhiteMatches**: Often the most expensive operation +2. **Large list operations**: When possibility space is still large +3. **Memory allocations**: Frequent list creation and destruction +4. **Game8x5 scenarios**: Larger search spaces require more processing + +### Red Flags + +Watch for: +- **Execution times > 1ms** for individual filtering operations +- **Memory allocations > 1MB** for single operations +- **High GC pressure** (frequent Gen 1/2 collections) +- **Inconsistent timing** (high standard deviation) + +## Benchmark Configuration + +The benchmarks use BenchmarkDotNet's default configuration with: +- **SimpleJob**: Reasonable number of iterations for accurate results +- **MemoryDiagnoser**: Tracks memory allocations and GC behavior +- **RankColumn**: Shows relative performance ranking + +## Troubleshooting + +### Common Issues + +1. **"No benchmarks found"**: Ensure you're running in Release configuration +2. **Inconsistent results**: Run on a dedicated machine without other heavy processes +3. **Out of memory**: Reduce the size of test data if running on constrained environments + +### Performance Tips + +1. **Close unnecessary applications** before running benchmarks +2. **Use Release configuration** for accurate performance measurements +3. **Run multiple times** to ensure consistency +4. **Consider thermal throttling** on laptops during long benchmark runs + +## Contributing + +When adding new benchmarks: + +1. Follow the existing naming conventions +2. Use appropriate benchmark categories +3. Include memory diagnostics for operations that allocate +4. Add realistic test scenarios that represent actual usage +5. Document expected performance characteristics + +## Example Output + +``` +| Method | Mean | Error | StdDev | Median | Allocated | +|-------------------------------------- |----------:|---------:|---------:|----------:|----------:| +| HandleBlackMatches_Game6x4_FullList | 45.23 μs | 0.891 μs | 1.024 μs | 45.12 μs | 1.95 KB | +| HandleNoMatches_Game6x4_FullList | 12.67 μs | 0.234 μs | 0.219 μs | 12.71 μs | 1.23 KB | +| InitializePossibleValues_Game6x4 | 3.45 ms | 0.068 ms | 0.064 ms | 3.43 ms | 52.3 KB | +``` + +This output shows that black match handling takes about 45 microseconds on average for a full Game6x4 list, while initializing the possible values takes about 3.5 milliseconds but only happens once per game. \ No newline at end of file