[Game] Playmats (#7101)

* [Game] Playmats

Took 19 seconds

Took 1 minute

* [Playmats] Add fixed override and configurable fallbacks to settings.

Took 29 minutes

Took 43 seconds

* Add main to test.

Took 1 minute

Took 29 seconds

* Move settings to own group

Took 11 minutes

* Some attempts to refresh macOS compositor

Took 2 minutes

* Try something else

Took 17 minutes

* Don't manipulate live list

Took 11 minutes

* Change things about resolution, address comments.

Took 45 minutes

Took 12 minutes

* Comments.

Took 14 minutes

Took 8 seconds

* Re-order settings menu location

Took 2 minutes

* Rename PlaymatResolution to Info and add enums

Took 8 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
BruebachL 2026-08-21 10:40:49 +02:00 committed by GitHub
parent 74a454552a
commit 9eafd90a91
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
52 changed files with 1931 additions and 28 deletions

View file

@ -7,6 +7,7 @@ add_test(NAME expression_test COMMAND expression_test)
add_test(NAME clamped_arithmetic_test COMMAND clamped_arithmetic_test)
add_test(NAME test_age_formatting COMMAND test_age_formatting)
add_test(NAME password_hash_test COMMAND password_hash_test)
add_test(NAME playmat_resolver_test COMMAND playmat_resolver_test)
add_test(NAME server_card_counter_test COMMAND server_card_counter_test)
add_test(NAME server_counter_test COMMAND server_counter_test)
add_test(NAME server_rate_limiter_test COMMAND server_rate_limiter_test)
@ -21,6 +22,7 @@ add_executable(expression_test expression_test.cpp)
add_executable(clamped_arithmetic_test clamped_arithmetic_test.cpp)
add_executable(test_age_formatting test_age_formatting.cpp)
add_executable(password_hash_test password_hash_test.cpp)
add_executable(playmat_resolver_test playmat_resolver_test.cpp)
add_executable(deck_hash_performance_test deck_hash_performance_test.cpp)
add_executable(server_card_counter_test server_card_counter_test.cpp)
add_executable(server_counter_test server_counter_test.cpp)
@ -56,6 +58,7 @@ if(NOT GTEST_FOUND)
add_dependencies(clamped_arithmetic_test gtest)
add_dependencies(test_age_formatting gtest)
add_dependencies(password_hash_test gtest)
add_dependencies(playmat_resolver_test gtest)
add_dependencies(deck_hash_performance_test gtest)
add_dependencies(server_card_counter_test gtest)
add_dependencies(server_counter_test gtest)
@ -74,6 +77,10 @@ target_link_libraries(
target_link_libraries(
password_hash_test libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES}
)
target_link_libraries(
playmat_resolver_test libcockatrice_deck_list libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES}
${TEST_QT_MODULES}
)
target_link_libraries(
deck_hash_performance_test libcockatrice_deck_list libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES}
${TEST_QT_MODULES}

View file

@ -0,0 +1,126 @@
#include <gtest/gtest.h>
#include <libcockatrice/deck_list/deck_list.h>
#include <libcockatrice/deck_list/playmat_resolver.h>
namespace
{
PlaymatInfo makePlaymatInfo(const QString &name, const QString &providerId = QString())
{
PlaymatInfo info;
info.card = {name, providerId};
return info;
}
} // namespace
TEST(PlaymatResolverTest, EmptyChainReturnsEmpty)
{
DeckList deck;
const PlaymatInfo resolved = resolveEffectivePlaymat(deck, {}, {}, PlaymatFallbackModeFixed, 0);
EXPECT_TRUE(resolved.card.isEmpty());
}
TEST(PlaymatResolverTest, OverrideWinsOverDeckAndFallback)
{
DeckList deck;
deck.setPlaymat({{QStringLiteral("Deck Mat"), QStringLiteral("deck-provider")}, {}});
const PlaymatInfo force = makePlaymatInfo(QStringLiteral("Force Mat"), QStringLiteral("force-provider"));
const QList<PlaymatInfo> fallback = {makePlaymatInfo(QStringLiteral("Fallback Mat"))};
const PlaymatInfo resolved = resolveEffectivePlaymat(deck, force, fallback, PlaymatFallbackModeFixed, 0);
EXPECT_EQ(resolved.card.name, QStringLiteral("Force Mat"));
EXPECT_EQ(resolved.card.providerId, QStringLiteral("force-provider"));
}
TEST(PlaymatResolverTest, DeckWinsOverFallback)
{
DeckList deck;
deck.setPlaymat({{QStringLiteral("Deck Mat"), QStringLiteral("deck-provider")}, {0.1, 0.2, 0.3, 1.5}});
const QList<PlaymatInfo> fallback = {makePlaymatInfo(QStringLiteral("Fallback Mat"))};
const PlaymatInfo resolved = resolveEffectivePlaymat(deck, {}, fallback, PlaymatFallbackModeFixed, 0);
EXPECT_EQ(resolved.card.name, QStringLiteral("Deck Mat"));
EXPECT_EQ(resolved.card.providerId, QStringLiteral("deck-provider"));
EXPECT_DOUBLE_EQ(resolved.params.marginPctL, 0.1);
EXPECT_DOUBLE_EQ(resolved.params.verticalOffset, 0.3);
}
TEST(PlaymatResolverTest, FallbackUsedWhenDeckHasNone)
{
DeckList deck;
const QList<PlaymatInfo> fallback = {makePlaymatInfo(QStringLiteral("Fallback Mat"))};
const PlaymatInfo resolved = resolveEffectivePlaymat(deck, {}, fallback, PlaymatFallbackModeFixed, 0);
EXPECT_EQ(resolved.card.name, QStringLiteral("Fallback Mat"));
}
TEST(PlaymatResolverTest, FixedAlwaysUsesFirst)
{
DeckList deck;
const QList<PlaymatInfo> fallback = {makePlaymatInfo(QStringLiteral("First")),
makePlaymatInfo(QStringLiteral("Second"))};
for (int i = 0; i < 5; ++i) {
const PlaymatInfo resolved = resolveEffectivePlaymat(deck, {}, fallback, PlaymatFallbackModeFixed, i);
EXPECT_EQ(resolved.card.name, QStringLiteral("First"));
}
}
TEST(PlaymatResolverTest, RoundRobinCyclesAndWraps)
{
DeckList deck;
const QList<PlaymatInfo> fallback = {makePlaymatInfo(QStringLiteral("First")),
makePlaymatInfo(QStringLiteral("Second")),
makePlaymatInfo(QStringLiteral("Third"))};
const QStringList expected = {QStringLiteral("First"), QStringLiteral("Second"), QStringLiteral("Third"),
QStringLiteral("First"), QStringLiteral("Second"), QStringLiteral("Third")};
for (int i = 0; i < expected.size(); ++i) {
const PlaymatInfo resolved = resolveEffectivePlaymat(deck, {}, fallback, PlaymatFallbackModeRoundRobin, i);
EXPECT_EQ(resolved.card.name, expected.at(i));
}
}
TEST(PlaymatResolverTest, RoundRobinRespectsCursor)
{
DeckList deck;
const QList<PlaymatInfo> fallback = {makePlaymatInfo(QStringLiteral("First")),
makePlaymatInfo(QStringLiteral("Second"))};
const PlaymatInfo resolved = resolveEffectivePlaymat(deck, {}, fallback, PlaymatFallbackModeRoundRobin, 5);
EXPECT_EQ(resolved.card.name, QStringLiteral("Second")); // 5 % 2 == 1
}
TEST(PlaymatResolverTest, RandomStaysWithinList)
{
DeckList deck;
const QList<PlaymatInfo> fallback = {makePlaymatInfo(QStringLiteral("First")),
makePlaymatInfo(QStringLiteral("Second")),
makePlaymatInfo(QStringLiteral("Third"))};
for (int i = 0; i < 50; ++i) {
const PlaymatInfo resolved = resolveEffectivePlaymat(deck, {}, fallback, PlaymatFallbackModeRandom, i);
ASSERT_FALSE(resolved.card.name.isEmpty());
EXPECT_TRUE(fallback.contains(resolved));
}
}
TEST(PlaymatResolverTest, ForceWithEmptyCardIgnoresFallbackParamsButNotFallback)
{
DeckList deck;
deck.setPlaymat({{QStringLiteral("Deck Mat")}, {}});
// An empty force entry must not mask the deck-configured playmat.
const PlaymatInfo emptyForce;
const PlaymatInfo resolved = resolveEffectivePlaymat(deck, emptyForce, {}, PlaymatFallbackModeFixed, 0);
EXPECT_EQ(resolved.card.name, QStringLiteral("Deck Mat"));
}
int main(int argc, char **argv)
{
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}