Skip to content

Commit 3ff55be

Browse files
Create CodeBreaker.BotWithString project with string-based algorithms and comprehensive tests
Co-authored-by: christiannagel <1908285+christiannagel@users.noreply.github.com>
1 parent 71e3577 commit 3ff55be

7 files changed

Lines changed: 581 additions & 0 deletions

File tree

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFramework>net9.0</TargetFramework>
4+
<Nullable>enable</Nullable>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<IsPackable>false</IsPackable>
7+
</PropertyGroup>
8+
<ItemGroup>
9+
<PackageReference Include="Microsoft.NET.Test.Sdk" />
10+
<PackageReference Include="xunit" />
11+
<PackageReference Include="xunit.runner.visualstudio">
12+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
13+
<PrivateAssets>all</PrivateAssets>
14+
</PackageReference>
15+
<PackageReference Include="coverlet.collector">
16+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
17+
<PrivateAssets>all</PrivateAssets>
18+
</PackageReference>
19+
</ItemGroup>
20+
<ItemGroup>
21+
<ProjectReference Include="..\CodeBreaker.BotWithString\CodeBreaker.BotWithString.csproj" />
22+
</ItemGroup>
23+
</Project>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
global using Xunit;
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
using System.Collections;
2+
using Codebreaker.GameAPIs.Client.Models;
3+
using Xunit;
4+
5+
namespace CodeBreaker.BotWithString.Tests;
6+
7+
public class StringCodeBreakerAlgorithmsTests
8+
{
9+
[Theory]
10+
[InlineData(GameType.Game6x4, 4)]
11+
[InlineData(GameType.Game8x5, 5)]
12+
[InlineData(GameType.Game5x5x4, 4)]
13+
public void SelectPeg_Should_ReturnCorrectPeg(GameType gameType, int expectedFieldsCount)
14+
{
15+
// Arrange
16+
string[] testCodes = gameType switch
17+
{
18+
GameType.Game6x4 => ["Red", "Blue", "Green", "Yellow"],
19+
GameType.Game8x5 => ["Red", "Blue", "Green", "Yellow", "Orange"],
20+
GameType.Game5x5x4 => ["Red", "Blue", "Green", "Yellow"],
21+
_ => ["Red", "Blue", "Green", "Yellow"]
22+
};
23+
24+
// Act & Assert
25+
for (int i = 0; i < expectedFieldsCount; i++)
26+
{
27+
string actual = testCodes.SelectPeg(gameType, i);
28+
Assert.Equal(testCodes[i], actual);
29+
}
30+
}
31+
32+
[Theory]
33+
[InlineData(GameType.Game6x4, 4)]
34+
[InlineData(GameType.Game6x4, -1)]
35+
[InlineData(GameType.Game8x5, 5)]
36+
[InlineData(GameType.Game8x5, -1)]
37+
[InlineData(GameType.Game5x5x4, 4)]
38+
[InlineData(GameType.Game5x5x4, -1)]
39+
public void SelectPeg_Should_ThrowException_ForInvalidPegNumber(GameType gameType, int invalidPegNumber)
40+
{
41+
// Arrange
42+
string[] testCodes = gameType switch
43+
{
44+
GameType.Game6x4 => ["Red", "Blue", "Green", "Yellow"],
45+
GameType.Game8x5 => ["Red", "Blue", "Green", "Yellow", "Orange"],
46+
GameType.Game5x5x4 => ["Red", "Blue", "Green", "Yellow"],
47+
_ => ["Red", "Blue", "Green", "Yellow"]
48+
};
49+
50+
// Act & Assert
51+
Assert.Throws<InvalidOperationException>(() => testCodes.SelectPeg(gameType, invalidPegNumber));
52+
}
53+
54+
[Fact]
55+
public void HandleBlackMatches_Should_FilterCorrectly_Game6x4()
56+
{
57+
// Arrange
58+
var possibleValues = new List<string[]>
59+
{
60+
new string[] { "Red", "Blue", "Green", "Yellow" }, // 4 black matches with selection
61+
new string[] { "Red", "Blue", "Green", "Black" }, // 3 black matches with selection
62+
new string[] { "Red", "Blue", "Black", "White" }, // 2 black matches with selection
63+
new string[] { "Red", "Black", "White", "Orange" }, // 1 black match with selection
64+
new string[] { "Black", "White", "Orange", "Purple" } // 0 black matches with selection
65+
};
66+
string[] selection = new string[] { "Red", "Blue", "Green", "Yellow" };
67+
68+
// Act
69+
var result = possibleValues.HandleBlackMatches(GameType.Game6x4, 4, selection);
70+
71+
// Assert
72+
Assert.Single(result);
73+
Assert.Equal(new string[] { "Red", "Blue", "Green", "Yellow" }, result[0]);
74+
}
75+
76+
[Fact]
77+
public void HandleBlackMatches_Should_FilterCorrectly_Game8x5()
78+
{
79+
// Arrange
80+
var possibleValues = new List<string[]>
81+
{
82+
new string[] { "Red", "Blue", "Green", "Yellow", "Orange" }, // 5 black matches with selection
83+
new string[] { "Red", "Blue", "Green", "Yellow", "Black" }, // 4 black matches with selection
84+
new string[] { "Red", "Blue", "Green", "Black", "White" }, // 3 black matches with selection
85+
};
86+
string[] selection = new string[] { "Red", "Blue", "Green", "Yellow", "Orange" };
87+
88+
// Act
89+
var result = possibleValues.HandleBlackMatches(GameType.Game8x5, 3, selection);
90+
91+
// Assert
92+
Assert.Single(result);
93+
Assert.Equal(new string[] { "Red", "Blue", "Green", "Black", "White" }, result[0]);
94+
}
95+
96+
[Fact]
97+
public void HandleWhiteMatches_Should_FilterCorrectly()
98+
{
99+
// Arrange
100+
var possibleValues = new List<string[]>
101+
{
102+
new string[] { "Blue", "Red", "Yellow", "Green" }, // All colors match but in different positions (4 white matches)
103+
new string[] { "Blue", "Red", "Green", "Yellow" }, // 3 colors match in different positions
104+
new string[] { "Red", "Blue", "Green", "Yellow" }, // All colors match in same positions (0 white matches)
105+
new string[] { "Black", "White", "Orange", "Purple" } // No matching colors
106+
};
107+
string[] selection = new string[] { "Red", "Blue", "Green", "Yellow" };
108+
109+
// Act
110+
var result = possibleValues.HandleWhiteMatches(GameType.Game6x4, 4, selection);
111+
112+
// Assert
113+
Assert.Single(result);
114+
Assert.Equal(new string[] { "Blue", "Red", "Yellow", "Green" }, result[0]);
115+
}
116+
117+
[Fact]
118+
public void HandleNoMatches_Should_RemoveAllWithMatchingColors()
119+
{
120+
// Arrange
121+
var possibleValues = new List<string[]>
122+
{
123+
new string[] { "Red", "Blue", "Green", "Yellow" }, // Contains Red and Blue from selection
124+
new string[] { "Black", "White", "Orange", "Purple" }, // No matching colors
125+
new string[] { "Red", "Black", "White", "Orange" }, // Contains Red from selection
126+
new string[] { "Pink", "Brown", "Gray", "Cyan" } // No matching colors
127+
};
128+
string[] selection = new string[] { "Red", "Blue", "Green", "Yellow" };
129+
130+
// Act
131+
var result = possibleValues.HandleNoMatches(GameType.Game6x4, selection);
132+
133+
// Assert
134+
Assert.Equal(2, result.Count);
135+
Assert.Contains(new string[] { "Black", "White", "Orange", "Purple" }, result);
136+
Assert.Contains(new string[] { "Pink", "Brown", "Gray", "Cyan" }, result);
137+
}
138+
139+
[Fact]
140+
public void HandleBlueMatches_Should_ReturnUnfiltered_ForNonGame5x5x4()
141+
{
142+
// Arrange
143+
var possibleValues = new List<string[]>
144+
{
145+
new string[] { "Red", "Blue", "Green", "Yellow" },
146+
new string[] { "Black", "White", "Orange", "Purple" }
147+
};
148+
string[] selection = new string[] { "Red", "Blue", "Green", "Yellow" };
149+
150+
// Act
151+
var result6x4 = possibleValues.HandleBlueMatches(GameType.Game6x4, 0, selection);
152+
var result8x5 = possibleValues.HandleBlueMatches(GameType.Game8x5, 0, selection);
153+
154+
// Assert
155+
Assert.Equal(possibleValues.Count, result6x4.Count);
156+
Assert.Equal(possibleValues.Count, result8x5.Count);
157+
}
158+
159+
[Fact]
160+
public void HandleBlueMatches_Should_FilterCorrectly_ForGame5x5x4()
161+
{
162+
// Arrange
163+
var possibleValues = new List<string[]>
164+
{
165+
new string[] { "RedCircle", "BlueSquare", "GreenTriangle", "YellowStar" }, // Should have some partial matches
166+
new string[] { "RedSquare", "BlueCircle", "GreenStar", "YellowTriangle" }, // Should have some partial matches
167+
new string[] { "BlackCircle", "WhiteSquare", "OrangeTriangle", "PurpleStar" } // Should have no partial matches
168+
};
169+
string[] selection = new string[] { "RedCircle", "BlueSquare", "GreenTriangle", "YellowStar" };
170+
171+
// Act
172+
var result = possibleValues.HandleBlueMatches(GameType.Game5x5x4, 1, selection);
173+
174+
// Assert
175+
// The exact result depends on the partial match logic implementation
176+
Assert.True(result.Count <= possibleValues.Count);
177+
}
178+
179+
[Theory]
180+
[ClassData(typeof(GenerateAllPossibleCombinationsTestData))]
181+
public void GenerateAllPossibleCombinations_Should_GenerateCorrectCount(GameType gameType, string[] possibleValues, int expectedCount)
182+
{
183+
// Act
184+
var result = StringCodeBreakerAlgorithms.GenerateAllPossibleCombinations(gameType, possibleValues);
185+
186+
// Assert
187+
Assert.Equal(expectedCount, result.Count);
188+
189+
// Verify all combinations are unique
190+
var uniqueCount = result.Select(arr => string.Join(",", arr)).Distinct().Count();
191+
Assert.Equal(expectedCount, uniqueCount);
192+
}
193+
194+
[Fact]
195+
public void HandleBlackMatches_Should_ThrowException_ForInvalidHits()
196+
{
197+
// Arrange
198+
var possibleValues = new List<string[]>
199+
{
200+
new string[] { "Red", "Blue", "Green", "Yellow" }
201+
};
202+
string[] selection = new string[] { "Red", "Blue", "Green", "Yellow" };
203+
204+
// Act & Assert
205+
Assert.Throws<ArgumentException>(() => possibleValues.HandleBlackMatches(GameType.Game6x4, -1, selection));
206+
Assert.Throws<ArgumentException>(() => possibleValues.HandleBlackMatches(GameType.Game6x4, 5, selection));
207+
}
208+
209+
[Fact]
210+
public void StringPegWithFlag_Should_WorkCorrectly()
211+
{
212+
// Arrange
213+
var peg = new StringPegWithFlag("Red", false);
214+
215+
// Act
216+
var usedPeg = peg with { Used = true };
217+
218+
// Assert
219+
Assert.Equal("Red", peg.Value);
220+
Assert.False(peg.Used);
221+
Assert.Equal("Red", usedPeg.Value);
222+
Assert.True(usedPeg.Used);
223+
}
224+
}
225+
226+
public class GenerateAllPossibleCombinationsTestData : IEnumerable<object[]>
227+
{
228+
public IEnumerator<object[]> GetEnumerator()
229+
{
230+
// Game6x4: 4 positions, 2 colors = 2^4 = 16 combinations
231+
yield return new object[] { GameType.Game6x4, new string[] { "Red", "Blue" }, 16 };
232+
233+
// Game6x4: 4 positions, 3 colors = 3^4 = 81 combinations
234+
yield return new object[] { GameType.Game6x4, new string[] { "Red", "Blue", "Green" }, 81 };
235+
236+
// Game8x5: 5 positions, 2 colors = 2^5 = 32 combinations
237+
yield return new object[] { GameType.Game8x5, new string[] { "Red", "Blue" }, 32 };
238+
239+
// Game5x5x4: 4 positions, 3 colors = 3^4 = 81 combinations
240+
yield return new object[] { GameType.Game5x5x4, new string[] { "Red", "Blue", "Green" }, 81 };
241+
}
242+
243+
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
244+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
<PropertyGroup>
3+
<TargetFramework>net9.0</TargetFramework>
4+
<Nullable>enable</Nullable>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Configurations>Debug;Release</Configurations>
7+
<EnableSdkContainerSupport>true</EnableSdkContainerSupport>
8+
</PropertyGroup>
9+
<PropertyGroup>
10+
<ContainerRepository>codebreaker-botwithstring</ContainerRepository>
11+
</PropertyGroup>
12+
<ItemGroup>
13+
<PackageReference Include="CNinnovation.Codebreaker.GamesClient" />
14+
<PackageReference Include="Microsoft.AspNetCore.OpenApi" />
15+
<PackageReference Include="Swashbuckle.AspNetCore" />
16+
</ItemGroup>
17+
<ItemGroup>
18+
<ProjectReference Include="..\..\common\Codebreaker.ServiceDefaults\Codebreaker.ServiceDefaults.csproj" />
19+
</ItemGroup>
20+
</Project>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
global using System.Text.Json;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
var builder = WebApplication.CreateBuilder(args);
2+
3+
builder.AddServiceDefaults();
4+
5+
// Swagger & EndpointDocumentation
6+
builder.Services.AddEndpointsApiExplorer();
7+
builder.Services.AddSwaggerGen();
8+
9+
var app = builder.Build();
10+
11+
app.UseSwagger();
12+
app.UseSwaggerUI();
13+
14+
app.MapDefaultEndpoints();
15+
16+
app.Run();

0 commit comments

Comments
 (0)