cakefoot/src/test/test.cpp

709 lines
25 KiB
C++

/*@~@~@ C A K E F O O T <presented by> 💫dank.game💫
|~)~)~)
|\~*~*| Licensed under the zlib and CC-BY licenses. Source is available at
|\\~*~|
,~|#\\~*|~, <https://open.shampoo.ooo/shampoo/cakefoot>
: \\@\\~| :
: \\#\\| : Created with the SPACE🪐BOX engine for cross-platform PC, WebGL and mobile games
: \\@\' :
: \\/ : <https://open.shampoo.ooo/shampoo/spacebox>
`~ ~ ~`~ */
#include "test/setup.hpp"
#include "filesystem.hpp"
#include "../Cakefoot.hpp"
nlohmann::json mock_preferences_json = R"({
"config": {
"fullscreen": true,
"use display button": false
}})"_json;
nlohmann::json mock_stats_json = R"({
"achievements": [],
"config": {},
"stats": {
"STAT_CONSECUTIVE_DAYS_PLAYED": 7.0,
"STAT_WARPED_LEVELS_BEATEN": 21
}
})"_json;
nlohmann::json mock_progress_json = R"({
"progress": {
"all time bank": "--++-+----------------",
"arcade bank": 0,
"arcade checkpoint": 0.0,
"arcade difficulty": 0,
"arcade level": 1,
"arcade max distance": 0,
"arcade time": 0.0,
"current challenge": 0,
"current difficulty": 0,
"current level": 7,
"current view": 2,
"jackpot": 0,
"max challenge": 5,
"max difficulty": 0,
"max level": 7,
"max view": 2,
"quest bank": "--++-+----------------",
"quest best": 0.0,
"quest checkpoint": 0,
"quest deaths": 1,
"quest difficulty": 0,
"quest level": 7,
"quest time": 9999.934000015258789,
"total time": 9999.934000015258789,
"warped": "+++++++++++++++++++++-"
}
})"_json;
nlohmann::json mock_deprecated_progress_json = R"({
"progress": {
"arcade bank": "-++++++-+-----+---++-+",
"arcade checkpoint": 0,
"arcade coin": false,
"arcade difficulty": 0,
"arcade level": 8,
"arcade max distance": 14037,
"arcade time": 91.6259937286377,
"current challenge": 0,
"current difficulty": 2,
"current level": 11,
"current view": 0,
"deaths": 3583,
"jackpot": 0,
"max challenge": 5,
"max difficulty": 2,
"max level": 11,
"max view": 0,
"quest bank": "++++++++++------------",
"quest best": 4214.365234375,
"quest checkpoint": 0,
"quest coin": false,
"quest difficulty": 2,
"quest level": 11,
"quest time": 987.0749969482422,
"total time": 26409.425424814224
}})"_json;
nlohmann::json mock_zero_arcade_bank_json = R"({
"progress": {
"arcade bank": 0,
"quest bank": "++++++++++------------"
}})"_json;
nlohmann::json mock_zero_quest_bank_json = R"({
"progress": {
"arcade bank": "-++++++-+-----+---++-+",
"quest bank": 0
}})"_json;
nlohmann::json mock_scores_json = R"([
{
"date": "2024/10/16 18:57",
"distance": 14037,
"name": "ZZZ",
"time": 5.3380126953125
},
{
"date": "2024/10/16 18:57",
"distance": 14037,
"name": "ZZZ",
"time": 305.3380126953125
},
{
"date": "2024/10/16 18:57",
"distance": 1912,
"name": "UAA",
"time": 0.0
},
{
"date": "2024/10/16 18:57",
"distance": 1337,
"name": "UAA",
"time": 0.0
},
{
"date": "2024/10/16 18:57",
"distance": 777,
"name": "UAA",
"time": 0.0
},
{
"date": "2024/10/16 18:57",
"distance": 420,
"name": "UAA",
"time": 0.0
},
{
"date": "2024/10/16 18:57",
"distance": 49,
"name": "UWU",
"time": 0.0
},
{
"date": "2024/10/16 18:58",
"distance": 47,
"name": "BUB",
"time": 0.0
},
{
"date": "2024/10/16 18:57",
"distance": 35,
"name": "TOE",
"time": 0.0
},
{
"date": "2024/10/16 18:57",
"distance": 19,
"name": "AAA",
"time": 0.0
},
{
"date": "2024/10/16 18:57",
"distance": 19,
"name": "EGG",
"time": 0.0
},
{
"date": "2024/10/16 18:57",
"distance": 0,
"name": "AAA",
"time": 0.0
}
])"_json;
/* Use temporary directory to create mock paths for mock save-data JSON */
std::shared_ptr<fs::path> mock_preferences_path {
new fs::path { fs::temp_directory_path() / "cakefoot_mock_preferences.json" },
[] (fs::path* ptr) { fs::remove(*ptr); delete ptr; }
};
std::shared_ptr<fs::path> mock_stats_path {
new fs::path { fs::temp_directory_path() / "cakefoot_mock_stats.json" },
[] (fs::path* ptr) { fs::remove(*ptr); delete ptr; }
};
std::shared_ptr<fs::path> mock_progress_path {
new fs::path { fs::temp_directory_path() / "cakefoot_mock_progress.json" },
[] (fs::path* ptr) { fs::remove(*ptr); delete ptr; }
};
std::shared_ptr<fs::path> mock_deprecated_progress_path {
new fs::path { fs::temp_directory_path() / "cakefoot_mock_deprecated_progress.json" },
[] (fs::path* ptr) { fs::remove(*ptr); delete ptr; }
};
std::shared_ptr<fs::path> mock_scores_path {
new fs::path { fs::temp_directory_path() / "cakefoot_mock_scores.json" },
[] (fs::path* ptr) { fs::remove(*ptr); delete ptr; }
};
/* Mock paths for non-existing files */
std::shared_ptr<fs::path> mock_empty_stats_path {
new fs::path { fs::temp_directory_path() / "cakefoot_mock_empty_stats.json" },
[] (fs::path* ptr) { if (fs::exists(*ptr)) { fs::remove(*ptr); } delete ptr; }
};
std::shared_ptr<fs::path> mock_empty_progress_path {
new fs::path { fs::temp_directory_path() / "cakefoot_mock_empty_progress.json" },
[] (fs::path* ptr) { if (fs::exists(*ptr)) { fs::remove(*ptr); } delete ptr; }
};
std::shared_ptr<fs::path> mock_empty_scores_path {
new fs::path { fs::temp_directory_path() / "cakefoot_mock_empty_scores.json" },
[] (fs::path* ptr) { if (fs::exists(*ptr)) { fs::remove(*ptr); } delete ptr; }
};
std::shared_ptr<fs::path> mock_empty_preferences_path {
new fs::path { fs::temp_directory_path() / "cakefoot_mock_empty_preferences.json" },
[] (fs::path* ptr) { if (fs::exists(*ptr)) { fs::remove(*ptr); } delete ptr; }
};
/* Mock paths for testing bank parses */
std::shared_ptr<fs::path> mock_zero_arcade_bank_path {
new fs::path { fs::temp_directory_path() / "cakefoot_mock_zero_arcade_bank.json" },
[] (fs::path* ptr) { if (fs::exists(*ptr)) { fs::remove(*ptr); } delete ptr; }
};
std::shared_ptr<fs::path> mock_zero_quest_bank_path {
new fs::path { fs::temp_directory_path() / "cakefoot_mock_zero_quest_bank.json" },
[] (fs::path* ptr) { if (fs::exists(*ptr)) { fs::remove(*ptr); } delete ptr; }
};
namespace
{
void _write_json(fs::path path, const nlohmann::json& json)
{
std::ofstream file {path};
if (file << std::setw(4) << json << std::endl)
{
sb::Log::Multi() << "Successfully wrote JSON to " << path << sb::Log::end;
}
else
{
sb::Log::Multi(sb::Log::ERR) << "Could not write JSON to " << path << sb::Log::end;
}
file.close();
}
}
int main(int argc, char** argv)
{
Catch::Session session;
/* Set up the CLI and reporters */
sb::test::setup(session, argc, argv);
/* Rig the stats so the last time stamp was recent and the day will not match today */
std::chrono::time_point<std::chrono::system_clock> now { std::chrono::system_clock::now() };
mock_stats_json["timestamp"] = sb::time::epoch_minutes(now - std::chrono::hours(23));
mock_stats_json["day"] = "(o. 3.o)";
/* Write mock data to temporary files */
_write_json(*mock_preferences_path, mock_preferences_json);
_write_json(*mock_progress_path, mock_progress_json);
_write_json(*mock_deprecated_progress_path, mock_deprecated_progress_json);
_write_json(*mock_stats_path, mock_stats_json);
_write_json(*mock_scores_path, mock_scores_json);
_write_json(*mock_zero_arcade_bank_path, mock_zero_arcade_bank_json);
_write_json(*mock_zero_quest_bank_path, mock_zero_quest_bank_json);
return session.run();
}
class MockCakefoot
{
private:
class NoClipCakefoot : public Cakefoot
{
public:
NoClipCakefoot(bool noclip, std::vector<nlohmann::json> configs) : Cakefoot(configs)
{
this->noclip = noclip;
}
template<typename Clock>
void mock_validate_date(std::chrono::time_point<Clock> time)
{
validate_date(time);
}
};
public:
NoClipCakefoot cakefoot;
sb::progress::Stats stats;
sb::progress::Achievements achievements;
sb::progress::Progress stat_progress;
sb::progress::Progress progress;
MockCakefoot(
fs::path progress, fs::path scores, fs::path preferences, fs::path stats, nlohmann::json merge = {},
bool noclip = true
) :
cakefoot(NoClipCakefoot { noclip, { merge, nlohmann::json {
{ "display", {{ "use play button", false }} },
/* Uncomment the next line to get log output on the console */
// { "log", {{ "stdout enabled", true }, {"debug to stdout", true }} },
{ "storage", {
{"progress file", progress},
{"scores file", scores},
{"preferences file", preferences},
{"stats file", stats},
{"stats write frequency", 2.0f}
}}}}}),
stats(sb::progress::Stats { cakefoot.configuration() }),
achievements(sb::progress::Achievements { cakefoot.configuration() })
{
refresh_progress();
}
void refresh_progress()
{
stat_progress.load(cakefoot.configuration()("storage", "stats file").get<fs::path>());
if (fs::exists(cakefoot.configuration()("storage", "progress file").get<fs::path>()))
{
progress.load(cakefoot.configuration()("storage", "progress file").get<fs::path>());
}
}
};
void run(MockCakefoot& mock, int count = 2)
{
bool started = false;
int resets = 0;
int downs = 0;
int toggles = 0;
mock.cakefoot.run([&](float timestamp){
if (mock.cakefoot.configuration()("progress", "quest deaths") > 2)
{
mock.cakefoot.flag_to_end();
}
else
{
if (!started)
{
sb::Delegate::post("any");
started = true;
}
else if (mock.cakefoot.configuration()("progress", "max difficulty") < 1)
{
if (mock.cakefoot.configuration()("progress", "current level") < 22)
{
sb::Delegate::post("skip forward");
}
else
{
sb::Delegate::post("any");
}
}
else if (resets++ < 1)
{
if (count > 1)
{
sb::Delegate::post("reset");
}
else
{
mock.cakefoot.flag_to_end();
}
}
else if (downs++ < 1)
{
sb::Delegate::post("down");
}
else if (toggles++ < 2)
{
sb::Delegate::post("any");
}
else if (mock.cakefoot.configuration()("progress", "arcade level") < 22)
{
sb::Delegate::post("skip forward");
}
else if (!mock.stat_progress.achievement_unlocked(mock.achievements["ACH_HOTCAKES"]))
{
sb::Delegate::post("any");
mock.refresh_progress();
}
else
{
mock.cakefoot.flag_to_end();
}
}
mock.cakefoot.draw(timestamp);
});
}
TEST_CASE("Empty storage")
{
/* Create a game with no progress or existing stats. */
MockCakefoot mock {
*mock_empty_progress_path,
*mock_empty_scores_path,
*mock_empty_preferences_path,
*mock_empty_stats_path
};
/* Confirm that only one stat and achievement were written */
CHECK(mock.stat_progress.read("stats").size() == 1);
CHECK(mock.stat_progress.stat_value(mock.stats["STAT_CONSECUTIVE_DAYS_PLAYED"]) == 1);
CHECK(mock.stat_progress.achievement_count() == 1);
CHECK(mock.stat_progress.achievement_unlocked(mock.achievements["ACH_EMPTY_STOMACH"]));
CHECK_FALSE(mock.stat_progress.achievement_unlocked(mock.achievements["ACH_CAKE_MY_DAY"]));
CHECK_FALSE(mock.stat_progress.achievement_unlocked(mock.achievements["ACH_BIRTHDAY_CAKE"]));
/* Force unlock date-based achievements */
std::tm birthday_tm;
std::istringstream ss("2024-05-10 00:00:00");
std::string format = "%Y-%m-%d %H:%M:%S";
ss >> std::get_time(&birthday_tm, format.c_str());
birthday_tm.tm_isdst = true;
std::chrono::time_point<std::chrono::system_clock> birthday {
std::chrono::system_clock::from_time_t(std::mktime(&birthday_tm))
};
std::tm cake_day_tm;
ss = std::istringstream("2024-11-26 00:00:00");
ss >> std::get_time(&cake_day_tm, format.c_str());
std::chrono::time_point<std::chrono::system_clock> cake_day {
std::chrono::system_clock::from_time_t(std::mktime(&cake_day_tm))
};
mock.cakefoot.mock_validate_date(birthday);
mock.cakefoot.mock_validate_date(cake_day);
mock.refresh_progress();
CHECK(mock.stat_progress.achievement_unlocked(mock.achievements["ACH_BIRTHDAY_CAKE"]));
CHECK(mock.stat_progress.achievement_unlocked(mock.achievements["ACH_CAKE_MY_DAY"]));
#if !defined(__EMSCRIPTEN__)
/* Launch a game */
run(mock);
/* Stats are written when quit is called */
mock.cakefoot.quit();
mock.refresh_progress();
/* Check achievements */
CHECK(mock.stat_progress.achievement_unlocked(mock.achievements["ACH_PIECE_OF_CAKE"]));
CHECK(mock.stat_progress.achievement_unlocked(mock.achievements["ACH_EASY_AS_PIE"]));
CHECK(mock.stat_progress.achievement_unlocked(mock.achievements["ACH_TAKES_THE_CAKE"]));
CHECK(mock.stat_progress.achievement_unlocked(mock.achievements["ACH_CAKEWALK"]));
CHECK(mock.stat_progress.achievement_unlocked(mock.achievements["ACH_MOIST"]));
CHECK(mock.stat_progress.achievement_unlocked(mock.achievements["ACH_KNEAD_FOR_SPEED"]));
CHECK(mock.stat_progress.achievement_unlocked(mock.achievements["ACH_CAKEFEAT"]));
CHECK(mock.stat_progress.achievement_unlocked(mock.achievements["ACH_HOTCAKES"]));
CHECK(mock.stat_progress.achievement_unlocked(mock.achievements["ACH_SLICE_OF_LIFE"]));
CHECK_FALSE(mock.stat_progress.achievement_unlocked(mock.achievements["ACH_WHISKED"]));
/* Check stats */
CHECK(mock.stat_progress.stat_exists(mock.stats["STAT_FASTEST_QUEST_TIME"]));
CHECK(mock.stat_progress.stat_value(mock.stats["STAT_FASTEST_QUEST_TIME"]) > 10.0f);
CHECK(mock.stat_progress.stat_value(mock.stats["STAT_FASTEST_QUEST_TIME"]) < 30.0f);
CHECK(mock.stat_progress.stat_exists(mock.stats["STAT_BEST_ARCADE_CLOCK"]));
CHECK(mock.stat_progress.stat_value(mock.stats["STAT_BEST_ARCADE_CLOCK"]) > 700.0f);
CHECK(mock.stat_progress.stat_value(mock.stats["STAT_BEST_ARCADE_CLOCK"]) < 750.0f);
CHECK(mock.stat_progress.stat_value(mock.stats["STAT_CHECKPOINTS_REACHED"]) == 1);
CHECK(mock.stat_progress.stat_value(mock.stats["STAT_ARCADE_RUNS"]) == 1);
CHECK(mock.stat_progress.stat_value(mock.stats["STAT_FARTHEST_ARCADE_DISTANCE"]) == 14'037);
CHECK(mock.stat_progress.stat_value(mock.stats["STAT_FARTHEST_DISTANCE_REACHED"]) == 14'037);
CHECK(mock.stat_progress.stat_value(mock.stats["STAT_QUESTS_COMPLETED"]) == 1);
CHECK(mock.stat_progress.stat_value(mock.stats["STAT_LEVELS_UNLOCKED"]) == 22);
CHECK_FALSE(mock.stat_progress.stat_exists(mock.stats["STAT_WARPED_LEVELS_BEATEN"]));
/* Check progress */
CHECK(mock.progress.read("progress", "quest deaths").get<int>() == 0);
CHECK(mock.progress.read("progress", "arcade deaths").get<int>() == 0);
#endif
/* Delete progress files, so they won't affect the next test */
fs::remove(*mock_empty_stats_path);
fs::remove(*mock_empty_progress_path);
}
#if !defined(__EMSCRIPTEN__)
TEST_CASE("D'ohnut")
{
/* Create a game with no progress or existing stats, and override the config. */
MockCakefoot mock {
*mock_empty_progress_path,
*mock_empty_scores_path,
*mock_empty_preferences_path,
*mock_empty_stats_path,
{},
false
};
/* Mock the D'ohnut achievement so it happens at any point after the beginning of the line */
mock.cakefoot.configuration()["achievements"][4]["goal"] = 0.01f;
/* Run until three deaths */
run(mock, 1);
/* Quit game and write save progress */
mock.cakefoot.quit();
/* Check results */
mock.refresh_progress();
CHECK(mock.stat_progress.achievement_unlocked(mock.achievements["ACH_DOHNUT"]));
CHECK(mock.progress.read("progress", "quest deaths").get<int>() == 3);
CHECK(mock.stat_progress.stat_value(mock.stats["STAT_TOTAL_DEATHS"], mock.stats) == 3);
/* Delete progress files, so they won't affect the next test */
fs::remove(*mock_empty_stats_path);
fs::remove(*mock_empty_progress_path);
}
#endif
TEST_CASE("In-progress")
{
/* Create a game with an in-progress quest. */
MockCakefoot mock {
*mock_progress_path,
*mock_empty_scores_path,
*mock_empty_preferences_path,
*mock_stats_path
};
/* Check initial stats after construction */
CHECK(mock.stat_progress.achievement_unlocked(mock.achievements["ACH_EMPTY_STOMACH"]));
#if !defined(__EMSCRIPTEN__)
/* One full quest run with existing progress */
run(mock, 1);
/* Stats are written when quit is called */
mock.cakefoot.quit();
mock.refresh_progress();
/* Check achievements */
CHECK(mock.stat_progress.achievement_unlocked(mock.achievements["ACH_PIECE_OF_CAKE"]));
CHECK(mock.stat_progress.achievement_unlocked(mock.achievements["ACH_EASY_AS_PIE"]));
CHECK(mock.stat_progress.achievement_unlocked(mock.achievements["ACH_TAKES_THE_CAKE"]));
CHECK(mock.stat_progress.achievement_unlocked(mock.achievements["ACH_CAKEWALK"]));
CHECK(mock.stat_progress.achievement_unlocked(mock.achievements["ACH_MOIST"]));
CHECK_FALSE(mock.stat_progress.achievement_unlocked(mock.achievements["ACH_SLICE_OF_LIFE"]));
CHECK_FALSE(mock.stat_progress.achievement_unlocked(mock.achievements["ACH_KNEAD_FOR_SPEED"]));
CHECK_FALSE(mock.stat_progress.achievement_unlocked(mock.achievements["ACH_CAKEFEAT"]));
CHECK(mock.stat_progress.achievement_unlocked(mock.achievements["ACH_WHISKED"]));
/* Check stats */
CHECK(mock.stat_progress.stat_value(mock.stats["STAT_FASTEST_QUEST_TIME"]) > 9999.9f);
CHECK(mock.stat_progress.stat_value(mock.stats["STAT_FARTHEST_DISTANCE_REACHED"]) == 14'037);
CHECK(mock.stat_progress.stat_value(mock.stats["STAT_QUESTS_COMPLETED"]) == 1);
CHECK(mock.stat_progress.stat_value(mock.stats["STAT_LEVELS_UNLOCKED"]) == 22);
CHECK(mock.stat_progress.stat_value(mock.stats["STAT_WARPED_LEVELS_BEATEN"]) == 22);
/* Check progress */
CHECK(mock.progress.read("progress", "warped").get<std::string>() == "++++++++++++++++++++++");
#endif
/* Reset progress files, so they won't affect the next test */
fs::remove(*mock_stats_path);
_write_json(*mock_stats_path, mock_stats_json);
fs::remove(*mock_empty_progress_path);
}
TEST_CASE("Zero arcade bank")
{
/* Create a game with coins saved in the quest bank and an empty arcade bank. */
MockCakefoot mock {
*mock_zero_arcade_bank_path,
*mock_empty_scores_path,
*mock_empty_preferences_path,
*mock_empty_stats_path
};
/* Check the contents */
CHECK(mock.stat_progress.read("stats").size() == 3);
CHECK(mock.stat_progress.stat_exists(mock.stats["STAT_COINS_COLLECTED"]));
CHECK(mock.stat_progress.stat_exists(mock.stats["STAT_COINS_UNLOCKED"]));
CHECK(mock.stat_progress.stat_value(mock.stats["STAT_COINS_COLLECTED"]) == 10);
CHECK(mock.stat_progress.stat_value(mock.stats["STAT_COINS_UNLOCKED"]) == 10);
CHECK(mock.cakefoot.configuration()("progress", "all time bank") == "++++++++++------------");
/* Quit so another game can launch */
mock.cakefoot.quit();
/* Delete stats file, so it won't affect the next test */
fs::remove(*mock_empty_stats_path);
}
TEST_CASE("Zero quest bank")
{
/* Create a game with coins saved in the arcade bank and an empty quest bank. */
MockCakefoot mock {
*mock_zero_quest_bank_path,
*mock_empty_scores_path,
*mock_empty_preferences_path,
*mock_empty_stats_path
};
/* Check the contents */
CHECK(mock.stat_progress.read("stats").size() == 3);
CHECK(mock.stat_progress.stat_exists(mock.stats["STAT_COINS_COLLECTED"]));
CHECK(mock.stat_progress.stat_exists(mock.stats["STAT_COINS_UNLOCKED"]));
CHECK(mock.stat_progress.stat_value(mock.stats["STAT_COINS_COLLECTED"]) == 11);
CHECK(mock.stat_progress.stat_value(mock.stats["STAT_COINS_UNLOCKED"]) == 11);
CHECK(mock.cakefoot.configuration()("progress", "all time bank") == "-++++++-+-----+---++-+");
/* Quit so another game can launch */
mock.cakefoot.quit();
/* Delete stats file, so it won't affect the next test */
fs::remove(*mock_empty_stats_path);
}
TEST_CASE("Estimate stats")
{
/* Create a game with deprecated progress file and no existing stats file. */
MockCakefoot mock {
*mock_deprecated_progress_path,
*mock_scores_path,
*mock_preferences_path,
*mock_empty_stats_path
};
/* Check the contents */
REQUIRE(mock.stat_progress.stat_exists(mock.stats["STAT_SLICER_DEATHS"]));
CHECK(mock.stat_progress.stat_value(mock.stats["STAT_SLICER_DEATHS"]) == 895);
CHECK(mock.stat_progress.stat_value(mock.stats["STAT_TOTAL_DEATHS"], mock.stats) == 4 * 895);
CHECK(mock.stat_progress.stat_value(mock.stats["STAT_DISTANCE_TRAVELED"]) == 2 * 20'000 + 10 * 800 + 32'689);
CHECK(mock.stat_progress.stat_value(mock.stats["STAT_ARCADE_RUNS"]) == 12);
CHECK(mock.stat_progress.stat_value(mock.stats["STAT_FARTHEST_ARCADE_DISTANCE"]) == 14'037);
CHECK(mock.stat_progress.stat_value(mock.stats["STAT_FARTHEST_DISTANCE_REACHED"]) == 14'037);
CHECK(mock.stat_progress.stat_value(mock.stats["STAT_QUESTS_COMPLETED"]) == 2);
CHECK(mock.stat_progress.stat_value(mock.stats["STAT_CAKES_UNLOCKED"]) == 2);
CHECK(mock.stat_progress.stat_value(mock.stats["STAT_FASTEST_QUEST_TIME"]) == 4'214.365234375);
CHECK(mock.stat_progress.stat_value(mock.stats["STAT_BEST_ARCADE_CLOCK"]) == 305.3380126953125);
CHECK(mock.stat_progress.stat_value(mock.stats["STAT_LEVELS_UNLOCKED"]) == 22);
CHECK(mock.stat_progress.stat_value(mock.stats["STAT_COINS_COLLECTED"]) == 21);
CHECK(mock.stat_progress.stat_value(mock.stats["STAT_COINS_UNLOCKED"]) == 14);
CHECK(mock.stat_progress.stat_value(mock.stats["STAT_CONSECUTIVE_DAYS_PLAYED"]) == 1);
CHECK(mock.stat_progress.achievement_unlocked(mock.achievements["ACH_EMPTY_STOMACH"]));
CHECK(mock.stat_progress.achievement_unlocked(mock.achievements["ACH_LAYER_CAKE"]));
CHECK(mock.stat_progress.achievement_unlocked(mock.achievements["ACH_FRESHLY_BAKED"]));
CHECK(mock.stat_progress.achievement_unlocked(mock.achievements["ACH_FORKED_UP"]));
CHECK(mock.stat_progress.achievement_unlocked(mock.achievements["ACH_OVERCOOKED"]));
CHECK(mock.stat_progress.achievement_unlocked(mock.achievements["ACH_BAKERS_DOZEN"]));
CHECK(mock.stat_progress.achievement_unlocked(mock.achievements["ACH_HOTCAKES"]));
CHECK(mock.stat_progress.achievement_unlocked(mock.achievements["ACH_CAKEWALK"]));
CHECK(mock.stat_progress.achievement_unlocked(mock.achievements["ACH_MOIST"]));
CHECK(mock.stat_progress.achievement_unlocked(mock.achievements["ACH_SUPER_MOIST"]));
CHECK(mock.stat_progress.achievement_unlocked(mock.achievements["ACH_PIECE_OF_CAKE"]));
CHECK(mock.stat_progress.achievement_unlocked(mock.achievements["ACH_EASY_AS_PIE"]));
CHECK(mock.stat_progress.achievement_unlocked(mock.achievements["ACH_TAKES_THE_CAKE"]));
CHECK(mock.cakefoot.configuration()("progress", "all time bank") == "++++++++++----+---++-+");
/* Quit so another game can launch */
mock.cakefoot.quit();
}
TEST_CASE("Existing stats")
{
/* Create a game with existing stats. */
MockCakefoot mock {
*mock_empty_progress_path,
*mock_scores_path,
*mock_preferences_path,
*mock_stats_path
};
/* Launch a game */
mock.cakefoot.run([&](float timestamp){
if (timestamp > 5.0f)
{
mock.cakefoot.flag_to_end();
}
else
{
mock.cakefoot.draw(timestamp);
}
});
/* Check that a consecutive day was added */
mock.refresh_progress();
CHECK(mock.stat_progress.stat_value(mock.stats["STAT_CONSECUTIVE_DAYS_PLAYED"]) == 8);
CHECK(mock.stat_progress.achievement_unlocked(mock.achievements["ACH_C8KE"]));
/* Quit so another game can launch */
mock.cakefoot.quit();
}
/* Test loading stats and achievements. Test transferring stats from progress to stats. Test auto unlocking
* achievements from progress data. */
/* Test reading, sorting, and adding scores */
/* Test distance traveled stat increase */
/* Test timing of stat writing */