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
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
using System.Collections;

using static Codebreaker.GameAPIs.Models.Colors;

namespace Codebreaker.GameAPIs.Analyzer.Tests;

public class SimpleGameGuessAnalyzerTests
{
[Fact]
public void SetMoveShouldReturnThreeCorrectColor()
{
SimpleColorResult expectedKeyPegs = new(
[
ResultValue.CorrectColor,
ResultValue.CorrectColor,
ResultValue.CorrectColor,
ResultValue.Incorrect
]);
SimpleColorResult resultKeyPegs = TestSkeleton(
[Green, Yellow, Green, Black],
[Yellow, Green, Black, Blue]
);

Assert.Equal(expectedKeyPegs, resultKeyPegs);
}

[Theory]
[ClassData(typeof(TestData6x4Mini))]
public void SetMoveUsingVariousDataUsingDataClass(string[] code, string[] guess, SimpleColorResult expectedKeyPegs)
{
SimpleColorResult actualKeyPegs = TestSkeleton(code, guess);
Assert.Equal(expectedKeyPegs, actualKeyPegs);
}

[Fact]
public void SetMove_ShouldThrowOnInvalidGuessCount()
{
Assert.Throws<ArgumentException>(() =>
TestSkeleton(
["Black", "Black", "Black", "Black"],
["Black"]
));
}

[Fact]
public void SetMove_ShouldThrowOnInvalidGuessValues()
{
Assert.Throws<ArgumentException>(() =>
TestSkeleton(
["Black", "Black", "Black", "Black"],
["Black", "Der", "Blue", "Yellow"] // "Der" is the wrong value
));
}

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

[Fact]
public void GetResult_ShouldNotIncrementMoveNumberOnInvalidMove()
{
IGame game = TestSkeletonWithGame(
[Green, Yellow, Green, Black],
[Yellow, Green, Black, Blue], moveNumber: 2);

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

[Fact]
public void GetResult_WithGameWon_ShouldSetCorrectGameEndInformation()
{
// Arrange
var game = new MockColorGame
{
GameType = GameTypes.Game6x4Mini,
NumberCodes = 4,
MaxMoves = 12,
IsVictory = false,
FieldValues = new Dictionary<string, IEnumerable<string>>()
{
[FieldCategories.Colors] = [.. TestData6x4.Colors6]
},
Codes = ["Red", "Blue", "Green", "Yellow"]
};

var guesses = new string[] { "Red", "Blue", "Green", "Yellow" }.ToPegs<ColorField>().ToArray();
var analyzer = new SimpleGameGuessAnalyzer(game, guesses, 1);

// Act
var result = analyzer.GetResult();

// Assert
Assert.NotNull(game.EndTime);
Assert.NotNull(game.Duration);
Assert.True(game.IsVictory);
}

[Fact]
public void GetResult_WithGameNotComplete_ShouldSetCorrectGameEndInformation()
{
// Arrange
var game = new MockColorGame
{
GameType = GameTypes.Game6x4Mini,
NumberCodes = 4,
MaxMoves = 12,
IsVictory = false,
FieldValues = new Dictionary<string, IEnumerable<string>>()
{
[FieldCategories.Colors] = [.. TestData6x4.Colors6]
},
Codes = ["Red", "Blue", "Green", "Yellow"]
};

var guesses = new string[] { "Red", "Yellow", "Green", "Yellow" }.ToPegs<ColorField>().ToArray();
var analyzer = new SimpleGameGuessAnalyzer(game, guesses, 1);

// Act
var result = analyzer.GetResult();

// Assert
Assert.Null(game.EndTime);
Assert.False(game.IsVictory);
}

private static SimpleColorResult TestSkeleton(string[] codes, string[] guesses, int moveNumber = 1)
{
MockColorGame game = new()
{
GameType = GameTypes.Game6x4Mini,
NumberCodes = 4,
MaxMoves = 12,
IsVictory = false,
FieldValues = new Dictionary<string, IEnumerable<string>>()
{
[FieldCategories.Colors] = [.. TestData6x4.Colors6]
},
Codes = codes
};

SimpleGameGuessAnalyzer analyzer = new(game,guesses.ToPegs<ColorField>().ToArray(), moveNumber);
return analyzer.GetResult();
}

private static IGame TestSkeletonWithGame(string[] codes, string[] guesses, int moveNumber = 1)
{
MockColorGame game = new()
{
GameType = GameTypes.Game6x4,
NumberCodes = 4,
MaxMoves = 12,
IsVictory = false,
FieldValues = new Dictionary<string, IEnumerable<string>>()
{
[FieldCategories.Colors] = [.. TestData6x4.Colors6]
},
Codes = codes
};

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

}
return game;
}
}

public class TestData6x4Mini : IEnumerable<object[]>
{
public static readonly string[] Colors6 = [Red, Green, Blue, Yellow, Black, White];

public IEnumerator<object[]> GetEnumerator()
{
yield return new object[]
{
new string[] { Green, Blue, Green, Yellow }, // code
new string[] { Green, Green, Black, White }, // inputdata
new SimpleColorResult(
[
ResultValue.CorrectPositionAndColor,
ResultValue.CorrectColor,
ResultValue.Incorrect,
ResultValue.Incorrect
]) // expected
};
yield return new object[]
{
new string[] { Red, Blue, Black, White },
new string[] { Black, Black, Red, Yellow },
new SimpleColorResult(
[
ResultValue.CorrectColor,
ResultValue.Incorrect,
ResultValue.CorrectColor,
ResultValue.Incorrect
])
};
yield return new object[]
{
new string[] { Yellow, Black, Yellow, Green },
new string[] { Black, Black, Black, Black },
new SimpleColorResult(
[
ResultValue.Incorrect,
ResultValue.CorrectPositionAndColor,
ResultValue.Incorrect,
ResultValue.Incorrect
])
};
yield return new object[]
{
new string[] { Yellow, Yellow, White, Red },
new string[] { Green, Yellow, White, Red },
new SimpleColorResult(
[
ResultValue.Incorrect,
ResultValue.CorrectPositionAndColor,
ResultValue.CorrectPositionAndColor,
ResultValue.CorrectPositionAndColor
])
};
yield return new object[]
{
new string[] { White, Black, Yellow, Black },
new string[] { Black, Blue, Black, White },
new SimpleColorResult(
[
ResultValue.CorrectColor,
ResultValue.Incorrect,
ResultValue.CorrectColor,
ResultValue.CorrectColor
])
};
}

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,13 @@
/// </summary>
/// <typeparam name="TField">The type for guesses.</typeparam>
/// <typeparam name="TResult">The type returned from the analysis.</typeparam>
public abstract class GameGuessAnalyzer<TField, TResult> : IGameGuessAnalyzer<TResult>
public abstract class GameGuessAnalyzer<TField, TResult>(IGame game, TField[] guesses, int moveNumber) : IGameGuessAnalyzer<TResult>
where TResult : struct
{
protected readonly IGame _game;
private readonly int _moveNumber;
protected readonly IGame _game = game;
private readonly int _moveNumber = moveNumber;

protected TField[] Guesses { get; private set; }

protected GameGuessAnalyzer(IGame game, TField[] guesses, int moveNumber)
{
_game = game;
Guesses = guesses;
_moveNumber = moveNumber;
}
protected TField[] Guesses { get; private set; } = guesses;

/// <summary>
/// Override this method to return the result of the guess analysis.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,39 +11,40 @@ protected override void ValidateGuessValues()
protected override SimpleColorResult GetCoreResult()
{
// Check black and white keyPegs
List<ColorField> codesToCheck = new(_game.Codes.ToPegs<ColorField>());
List<ColorField> guessPegsToCheck = new(Guesses);
List<string> codesToCheck = [.. _game.Codes.ToPegs<ColorField>().Select(cf => cf.ToString()) ];
List<string> guessPegsToCheck = [.. Guesses.Select(g => g.ToString())];
List<int> positionsToIgnore = [];

var results = Enumerable.Repeat(ResultValue.Incorrect, 4).ToArray();

for (int i = 0; i < results.Length; i++)
{
results[i] = ResultValue.Incorrect;
}
ResultValue[] results = [.. Enumerable.Repeat(ResultValue.Incorrect, 4)];

// check black
for (int i = 0; i < _game.Codes.Length; i++)
for (int i = 0; i < guessPegsToCheck.Count; i++)
{
// check black
if (guessPegsToCheck[i] == codesToCheck[i])
{
results[i] = ResultValue.CorrectPositionAndColor;
}
else // check white
{
if (codesToCheck.Contains(codesToCheck[i]) && results[i] == ResultValue.Incorrect)
{
results[i] = ResultValue.CorrectColor;
}
positionsToIgnore.Add(i);
codesToCheck[i] = string.Empty;
}
}

// check white
for (int i = 0; i < guessPegsToCheck.Count; i++)
{
if (positionsToIgnore.Contains(i)) continue;
int ix = codesToCheck.IndexOf(guessPegsToCheck[i]);
if (ix == -1) continue;
results[i] = ResultValue.CorrectColor;
codesToCheck[ix] = string.Empty;
}

return new SimpleColorResult(results);
}

protected override void SetGameEndInformation(SimpleColorResult result)
{
bool allCorrect = result.Results.Any(r => r == ResultValue.CorrectColor);
bool allCorrect = result.Results.All(r => r == ResultValue.CorrectPositionAndColor);

if (allCorrect || _game.LastMoveNumber >= _game.MaxMoves)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
<PackageTags>
Codebreaker;CNinnovation;GameAnalyzers;
Codebreaker;CNinnovation;GameAnalyzers
</PackageTags>
<Description>
This library contains game analyzers for the Codebreaker app. Reference this library when creating a Codebreaker service.
Expand Down