709 lines
25 KiB
C++
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 */
|