Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public enum GameType
Game6x4Mini,
Game8x5,
Game5x5x4,
Game5x3,
}

/// <summary>
Expand Down
19 changes: 19 additions & 0 deletions src/services/bot/CodeBreaker.Bot/CodeBreakerAlgorithms.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public static string[] IntToColors(this int value, GameType gameType, Dictionary
GameType.Game6x4 => IntToColors6x4(value, colorNames),
GameType.Game8x5 => IntToColors8x5(value, colorNames),
GameType.Game5x5x4 => IntToColors5x5x4(value, colorNames),
GameType.Game5x3 => IntToColors5x3(value, colorNames),
_ => IntToColors6x4(value, colorNames)
};
}
Expand Down Expand Up @@ -81,6 +82,22 @@ private static string[] IntToColors5x5x4(int value, Dictionary<int, string>? col
return colorNamesArray;
}

private static string[] IntToColors5x3(int value, Dictionary<int, string>? colorNames)
{
int i1 = (value >> 0) & 0b111111;
int i2 = (value >> 6) & 0b111111;
int i3 = (value >> 12) & 0b111111;

string[] colorNamesArray =
[
colorNames?[i3] ?? $"Unknown{i3}",
colorNames?[i2] ?? $"Unknown{i2}",
colorNames?[i1] ?? $"Unknown{i1}"
];

return colorNamesArray;
}

/// <summary>
/// Reduces the possible values based on the black matches with the selection
/// </summary>
Expand Down Expand Up @@ -292,6 +309,7 @@ private static int GetFieldsCount(GameType gameType) =>
GameType.Game6x4 => 4,
GameType.Game8x5 => 5,
GameType.Game5x5x4 => 4,
GameType.Game5x3 => 3,
_ => 4
};

Expand All @@ -301,6 +319,7 @@ private static int GetBitsPerField(GameType gameType) =>
GameType.Game6x4 => 6, // 6 bits for up to 64 values (using 6)
GameType.Game8x5 => 6, // 6 bits for up to 64 values (using 8)
GameType.Game5x5x4 => 6, // 6 bits for up to 64 values (using 25, but limited per position)
GameType.Game5x3 => 6, // 6 bits for up to 64 values (using 5)
_ => 6
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ private static int GetFieldsCount(GameType gameType) =>
GameType.Game6x4 => 4,
GameType.Game8x5 => 5,
GameType.Game5x5x4 => 4,
GameType.Game5x3 => 3,
_ => 4
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="CNinnovation.Codebreaker.BackendModels" Version="3.8.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Cosmos" Version="9.0.0" />
<PackageReference Include="CNinnovation.Codebreaker.BackendModels" Condition="'$(UseLocalProjects)' != 'true'" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Cosmos" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Codebreaker.GameAPIs.Models\Codebreaker.GameAPIs.Models.csproj" Condition="'$(UseLocalProjects)' == 'true'" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="CNinnovation.Codebreaker.BackendModels" Version="3.8.0"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.0"/>
<PackageReference Include="CNinnovation.Codebreaker.BackendModels" Condition="'$(UseLocalProjects)' != 'true'" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Codebreaker.GameAPIs.Models\Codebreaker.GameAPIs.Models.csproj" Condition="'$(UseLocalProjects)' == 'true'" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
using System.Collections;

using static Codebreaker.GameAPIs.Models.Colors;

namespace Codebreaker.GameAPIs.Analyzer.Tests;

public class ColorGame5x3AnalyzerTests
{
[Fact]
public void SetMove_ShouldReturnTwoWhite()
{
ColorResult expectedKeyPegs = new(0, 2);
ColorResult? resultKeyPegs = AnalyzeGame(
[Red, Green, Blue],
[Green, Blue, Yellow]
);

Assert.Equal(expectedKeyPegs, resultKeyPegs);
}

[InlineData(2, 0, Red, Green, Red)]
[InlineData(3, 0, Red, Green, Blue)]
[Theory]
public void SetMove_UsingVariousData(int expectedBlack, int expectedWhite, params string[] guessValues)
{
string[] code = [Red, Green, Blue];
ColorResult expectedKeyPegs = new(expectedBlack, expectedWhite);
ColorResult resultKeyPegs = AnalyzeGame(code, guessValues);
Assert.Equal(expectedKeyPegs, resultKeyPegs);
}

[Theory]
[ClassData(typeof(TestData5x3))]
public void SetMove_UsingVariousDataUsingDataClass(string[] code, string[] guess, ColorResult expectedKeyPegs)
{
ColorResult actualKeyPegs = AnalyzeGame(code, guess);
Assert.Equal(expectedKeyPegs, actualKeyPegs);
}

[Fact]
public void SetMove_ShouldThrowOnInvalidGuessCount()
{
Assert.Throws<ArgumentException>(() =>
AnalyzeGame(
[Red, Green, Blue],
[Red]
));
}

[Fact]
public void SetMove_ShouldThrowOnInvalidGuessValues()
{
Assert.Throws<ArgumentException>(() =>
AnalyzeGame(
[Red, Green, Blue],
[Red, "Invalid", Blue]
));
}

[Fact]
public void SetMove_ShouldThrowOnInvalidMoveNumber()
{
Assert.Throws<ArgumentException>(() =>
AnalyzeGame(
[Red, Green, Blue],
[Green, Red, Blue], moveNumber: 2));
}

[Fact]
public void SetMove_ShouldNotIncrementMoveNumberOnInvalidMove()
{
IGame game = AnalyzeGameCatchingException(
[Red, Green, Blue],
[Green, Red, Blue], moveNumber: 2);

Assert.Equal(0, game?.LastMoveNumber);
}

[Fact]
public void SetMove_ShouldIncludeExpectedMoveNumberInErrorMessage()
{
var exception = Assert.Throws<ArgumentException>(() =>
AnalyzeGame(
[Red, Green, Blue],
[Green, Red, Blue], moveNumber: 3));

Assert.Contains("received 3", exception.Message);
Assert.Contains("expected 1", exception.Message);
}

private static MockColorGame CreateGame(string[] codes) =>
new()
{
GameType = GameTypes.Game5x3,
NumberCodes = 3,
MaxMoves = 10,
IsVictory = false,
FieldValues = new Dictionary<string, IEnumerable<string>>()
{
[FieldCategories.Colors] = [.. TestData5x3.Colors5]
},
Codes = codes
};

private static ColorResult AnalyzeGame(string[] codes, string[] guesses, int moveNumber = 1)
{
MockColorGame game = CreateGame(codes);
ColorGameGuessAnalyzer analyzer = new(game, [.. guesses.ToPegs<ColorField>()], moveNumber);
return analyzer.GetResult();
}

private static IGame AnalyzeGameCatchingException(string[] codes, string[] guesses, int moveNumber = 1)
{
MockColorGame game = CreateGame(codes);

ColorGameGuessAnalyzer analyzer = new(game, guesses.ToPegs<ColorField>().ToArray(), moveNumber);
try
{
analyzer.GetResult();
}
catch (ArgumentException)
{

}
return game;
}
}

public class TestData5x3 : IEnumerable<object[]>
{
public static readonly string[] Colors5 = [Red, Green, Blue, Yellow, Purple];

public IEnumerator<object[]> GetEnumerator()
{
yield return new object[]
{
new string[] { Red, Green, Blue }, // code
new string[] { Red, Red, Yellow }, // inputdata
new ColorResult(1, 0) // expected
};
yield return new object[]
{
new string[] { Red, Green, Blue },
new string[] { Green, Blue, Red },
new ColorResult(0, 3)
};
yield return new object[]
{
new string[] { Yellow, Purple, Green },
new string[] { Purple, Purple, Purple },
new ColorResult(1, 0)
};
yield return new object[]
{
new string[] { Red, Green, Blue },
new string[] { Red, Green, Blue },
new ColorResult(3, 0)
};
yield return new object[]
{
new string[] { Yellow, Blue, Red },
new string[] { Blue, Yellow, Yellow },
new ColorResult(0, 2)
};
}

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ public class GameTypes
public const string Game6x4Mini = nameof(Game6x4Mini);
public const string Game8x5 = nameof(Game8x5);
public const string Game5x5x4 = nameof(Game5x5x4);
public const string Game5x3 = nameof(Game5x3);
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="CNinnovation.Codebreaker.Analyzers" />
<PackageReference Include="CNinnovation.Codebreaker.Analyzers" Condition="'$(UseLocalProjects)' != 'true'" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Codebreaker.GameAPIs.Analyzers\Codebreaker.Analyzers.csproj" Condition="'$(UseLocalProjects)' == 'true'" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,17 @@ public void Create5x5x4GameShouldCreate5x5x4Game()
Assert.Equal(5, shapes);
}

[Fact]
public void Create5x3GameShouldCreate5x3Game()
{
Game game = GamesFactory.CreateGame("Game5x3", "Test");

Assert.Equal("Test", game.PlayerName);
Assert.Equal(3, game.Codes.Length);
int colors = game.FieldValues["colors"].Count();
Assert.Equal(5, colors);
}

[Fact]
public void Apply6x4MoveShouldAddAMoveToTheGame()
{
Expand Down Expand Up @@ -72,4 +83,15 @@ public void Apply5x5x4MoveShouldAddAMoveToTheGame()

Assert.Single(game.Moves);
}

[Fact]
public void Apply5x3MoveShouldAddAMoveToTheGame()
{
Game game = GamesFactory.CreateGame("Game5x3", "Test");
var values = game.FieldValues["colors"];
string[] guesses = Enumerable.Repeat(values.First(), 3).ToArray();
game.ApplyMove(guesses, 1);

Assert.Single(game.Moves);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<PackageReference Include="Grpc.Net.ClientFactory" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CNinnovation.Codebreaker.BackendModels" />
<PackageReference Include="CNinnovation.Codebreaker.BackendModels" Condition="'$(UseLocalProjects)' != 'true'" />
<PackageReference Include="Grpc.AspNetCore" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" />
<PackageReference Include="Microsoft.Extensions.ApiDescription.Server">
Expand All @@ -35,11 +35,14 @@
<PackageReference Include="Microsoft.Extensions.Caching.Memory" />
<PackageReference Include="Swashbuckle.AspNetCore" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" />
<PackageReference Include="CNinnovation.Codebreaker.SqlServer" />
<PackageReference Include="CNinnovation.Codebreaker.Cosmos" />
<PackageReference Include="CNinnovation.Codebreaker.SqlServer" Condition="'$(UseLocalProjects)' != 'true'" />
<PackageReference Include="CNinnovation.Codebreaker.Cosmos" Condition="'$(UseLocalProjects)' != 'true'" />
<PackageReference Include="System.Text.Json" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\common\Codebreaker.GameAPIs.Models\Codebreaker.GameAPIs.Models.csproj" Condition="'$(UseLocalProjects)' == 'true'" />
<ProjectReference Include="..\..\common\Codebreaker.Data.SqlServer\Codebreaker.Data.SqlServer.csproj" Condition="'$(UseLocalProjects)' == 'true'" />
<ProjectReference Include="..\..\common\Codebreaker.Data.Cosmos\Codebreaker.Data.Cosmos.csproj" Condition="'$(UseLocalProjects)' == 'true'" />
<ProjectReference Include="..\..\common\Codebreaker.ServiceDefaults\Codebreaker.ServiceDefaults.csproj" />
</ItemGroup>
<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,24 @@ Game Create5x5x4Game() =>
.Select(item => string.Join(';', item.Shape, item.Color))
.ToArray()
};

Game Create5x3Game() =>
new(Guid.NewGuid(), gameType, playerName, DateTime.UtcNow, 3, 10)
{
FieldValues = new Dictionary<string, IEnumerable<string>>()
{
{ FieldCategories.Colors, s_colors5 }
},
Codes = Random.Shared.GetItems(s_colors5, 3)
};

return gameType switch
{
GameTypes.Game6x4Mini => Create6x4SimpleGame(),
GameTypes.Game6x4 => Create6x4Game(),
GameTypes.Game8x5 => Create8x5Game(),
GameTypes.Game5x5x4 => Create5x5x4Game(),
GameTypes.Game5x3 => Create5x3Game(),
_ => throw new CodebreakerException("Invalid game type") { Code = CodebreakerExceptionCodes.InvalidGameType }
};
}
Expand Down Expand Up @@ -113,6 +124,7 @@ string[] GetShapeGameGuessAnalyzerResult()
GameTypes.Game8x5 => GetColorGameGuessAnalyzerResult(),
GameTypes.Game6x4Mini => GetSimpleGameGuessAnalyzerResult(),
GameTypes.Game5x5x4 => GetShapeGameGuessAnalyzerResult(),
GameTypes.Game5x3 => GetColorGameGuessAnalyzerResult(),
_ => throw new CodebreakerException("Invalid game type") { Code = CodebreakerExceptionCodes.InvalidGameType }
};

Expand Down