Skip to content

Commit f6929de

Browse files
Merge pull request #319 from CodebreakerApp/copilot/fix-318
Create CodeBreaker.BotWithString project using string-based algorithm and add unit tests
2 parents fe35511 + c049af8 commit f6929de

11 files changed

Lines changed: 948 additions & 0 deletions
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
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="Moq" />
11+
<PackageReference Include="xunit" />
12+
<PackageReference Include="xunit.runner.visualstudio">
13+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
14+
<PrivateAssets>all</PrivateAssets>
15+
</PackageReference>
16+
<PackageReference Include="coverlet.collector">
17+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
18+
<PrivateAssets>all</PrivateAssets>
19+
</PackageReference>
20+
</ItemGroup>
21+
<ItemGroup>
22+
<ProjectReference Include="..\CodeBreaker.BotWithString\CodeBreaker.BotWithString.csproj" />
23+
</ItemGroup>
24+
</Project>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
global using Xunit;
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
using Codebreaker.GameAPIs.Client;
2+
using Codebreaker.GameAPIs.Client.Models;
3+
using Moq;
4+
5+
namespace CodeBreaker.BotWithString.Tests;
6+
7+
public class StringBotGameRunnerTests
8+
{
9+
[Fact]
10+
public async Task PlayGameAsync_Should_StartGameAndMakeMove()
11+
{
12+
// Arrange
13+
var mockClient = new Mock<IGamesClient>();
14+
var gameId = Guid.NewGuid();
15+
var fieldValues = new Dictionary<string, string[]>
16+
{
17+
["Colors"] = new[] { "Red", "Blue", "Green", "Yellow" }
18+
};
19+
20+
mockClient.Setup(x => x.StartGameAsync(GameType.Game6x4, "TestPlayer", It.IsAny<CancellationToken>()))
21+
.ReturnsAsync((gameId, 4, 12, fieldValues));
22+
23+
mockClient.Setup(x => x.SetMoveAsync(gameId, "TestPlayer", GameType.Game6x4, 1,
24+
It.IsAny<string[]>(), It.IsAny<CancellationToken>()))
25+
.ReturnsAsync((new[] { "4", "0" }, true, true)); // 4 black hits = win
26+
27+
var runner = new StringBotGameRunner(mockClient.Object);
28+
29+
// Act
30+
var result = await runner.PlayGameAsync(GameType.Game6x4, "TestPlayer");
31+
32+
// Assert
33+
Assert.Equal(gameId, result.GameId);
34+
Assert.Equal(GameType.Game6x4, result.GameType);
35+
Assert.Equal("TestPlayer", result.PlayerName);
36+
Assert.True(result.GameWon);
37+
Assert.True(result.GameEnded);
38+
Assert.Equal(1, result.MovesUsed);
39+
Assert.NotNull(result.WinningCombination);
40+
Assert.Equal(4, result.WinningCombination.Length);
41+
}
42+
43+
[Fact]
44+
public async Task PlayGameAsync_Should_HandleMultipleMoves()
45+
{
46+
// Arrange
47+
var mockClient = new Mock<IGamesClient>();
48+
var gameId = Guid.NewGuid();
49+
var fieldValues = new Dictionary<string, string[]>
50+
{
51+
["Colors"] = new[] { "Red", "Blue" }
52+
};
53+
54+
mockClient.Setup(x => x.StartGameAsync(GameType.Game6x4, "TestPlayer", It.IsAny<CancellationToken>()))
55+
.ReturnsAsync((gameId, 4, 12, fieldValues));
56+
57+
// First move: no matches
58+
mockClient.Setup(x => x.SetMoveAsync(gameId, "TestPlayer", GameType.Game6x4, 1,
59+
It.IsAny<string[]>(), It.IsAny<CancellationToken>()))
60+
.ReturnsAsync((new[] { "0", "0" }, false, false));
61+
62+
// Second move: win
63+
mockClient.Setup(x => x.SetMoveAsync(gameId, "TestPlayer", GameType.Game6x4, 2,
64+
It.IsAny<string[]>(), It.IsAny<CancellationToken>()))
65+
.ReturnsAsync((new[] { "4", "0" }, true, true));
66+
67+
var runner = new StringBotGameRunner(mockClient.Object);
68+
69+
// Act
70+
var result = await runner.PlayGameAsync(GameType.Game6x4, "TestPlayer");
71+
72+
// Assert
73+
Assert.Equal(gameId, result.GameId);
74+
Assert.True(result.GameWon);
75+
Assert.True(result.GameEnded);
76+
Assert.Equal(2, result.MovesUsed);
77+
78+
// Verify both moves were called
79+
mockClient.Verify(x => x.SetMoveAsync(gameId, "TestPlayer", GameType.Game6x4, 1,
80+
It.IsAny<string[]>(), It.IsAny<CancellationToken>()), Times.Once);
81+
mockClient.Verify(x => x.SetMoveAsync(gameId, "TestPlayer", GameType.Game6x4, 2,
82+
It.IsAny<string[]>(), It.IsAny<CancellationToken>()), Times.Once);
83+
}
84+
85+
[Fact]
86+
public async Task PlayGameAsync_Should_HandleGameLoss()
87+
{
88+
// Arrange
89+
var mockClient = new Mock<IGamesClient>();
90+
var gameId = Guid.NewGuid();
91+
var fieldValues = new Dictionary<string, string[]>
92+
{
93+
["Colors"] = new[] { "Red", "Blue" }
94+
};
95+
96+
mockClient.Setup(x => x.StartGameAsync(GameType.Game6x4, "TestPlayer", It.IsAny<CancellationToken>()))
97+
.ReturnsAsync((gameId, 4, 2, fieldValues)); // Only 2 max moves
98+
99+
// Both moves: no matches, game ends after max moves
100+
mockClient.Setup(x => x.SetMoveAsync(gameId, "TestPlayer", GameType.Game6x4, It.IsAny<int>(),
101+
It.IsAny<string[]>(), It.IsAny<CancellationToken>()))
102+
.ReturnsAsync((new[] { "0", "0" }, false, false));
103+
104+
var runner = new StringBotGameRunner(mockClient.Object);
105+
106+
// Act
107+
var result = await runner.PlayGameAsync(GameType.Game6x4, "TestPlayer");
108+
109+
// Assert
110+
Assert.Equal(gameId, result.GameId);
111+
Assert.False(result.GameWon);
112+
Assert.False(result.GameEnded); // Game didn't officially end, just reached max moves
113+
Assert.Equal(2, result.MovesUsed);
114+
Assert.Null(result.WinningCombination);
115+
}
116+
117+
[Fact]
118+
public async Task PlayGameAsync_Should_HandleGame8x5()
119+
{
120+
// Arrange
121+
var mockClient = new Mock<IGamesClient>();
122+
var gameId = Guid.NewGuid();
123+
var fieldValues = new Dictionary<string, string[]>
124+
{
125+
["Colors"] = new[] { "Red", "Blue", "Green", "Yellow", "Orange" }
126+
};
127+
128+
mockClient.Setup(x => x.StartGameAsync(GameType.Game8x5, "TestPlayer", It.IsAny<CancellationToken>()))
129+
.ReturnsAsync((gameId, 5, 14, fieldValues));
130+
131+
mockClient.Setup(x => x.SetMoveAsync(gameId, "TestPlayer", GameType.Game8x5, 1,
132+
It.IsAny<string[]>(), It.IsAny<CancellationToken>()))
133+
.ReturnsAsync((new[] { "5", "0" }, true, true)); // 5 black hits = win
134+
135+
var runner = new StringBotGameRunner(mockClient.Object);
136+
137+
// Act
138+
var result = await runner.PlayGameAsync(GameType.Game8x5, "TestPlayer");
139+
140+
// Assert
141+
Assert.Equal(GameType.Game8x5, result.GameType);
142+
Assert.True(result.GameWon);
143+
Assert.NotNull(result.WinningCombination);
144+
Assert.Equal(5, result.WinningCombination.Length);
145+
}
146+
}

0 commit comments

Comments
 (0)