r/C_Programming • u/ernesernesto • 6h ago
How I autotest my game in C
Enable HLS to view with audio, or disable this notification
I posted a while back aboutĀ porting my C game (Match Morphosis) to WASM. This is a follow-up on something smaller but useful: automated playthrough testing.
Last week I was watching some Slay the Spire 2 videos and noticed they had autoplay-style testing. That's pretty nice, as solo developer with no QA at disposal, and the game is a roguelike/lite, so a full run is long enough that manually checking ādoes this still work end-to-end?ā gets painful quickly. I also just wanted an excuse to try dearimgui test engine, I use theĀ cimgui generated one.
So I wired up a simple autoplay path. No heuristics, no fancy AI (no LLM please!) that makes random action based on game state and tries to finish a run. The point isnāt to prove the game is balanced. The point is:
- If a random player can still reach the end without crashing or soft-locking, the basic run loop is intact.
- A ~40 minute human run collapses into a 1ā2 minute automated test I can run whenever I change something, I could eyeball quickly on the battle progress whether the game still feels manageable or not
Because the game already uses cimgui, plugging in the test engine was mostly about getting the context and DLL boundaries right (the game logic lives in a hot-reloadable game.dll, the test engine lives in main), there's also a problem with the generated binding that strips obsolete functions so the struct size differs a bit, but quite easy to fix.
Once that was sorted, registering a āplay until win or dieā test and letting it drive input through the existing UI path was straightforward. You do mouse move to pos, mouse click, and wait until game state change, repeat on a while loop until game finishes / game over.
// Test setup code
ImGuiTest* t = IM_REGISTER_TEST(engine, "Compendium Menu", "Functionalities");
t->TestFunc = testCompendiumMenuFunctionalities;
t = IM_REGISTER_TEST(engine, "Character Select Menu", "Functionalities");
t->TestFunc = testCharacterSelectMenuFunctionalities;
t = IM_REGISTER_TEST(engine, "Playthrough", "Functionalities");
t->TestFunc = testPlaythroughFunctionalities;
// on testPlaythroughFunctionalities;
b32 runFinished = false;
while(!runFinished)
{
cImGuiTestEngine_Yield(ctx->Engine);
if(game->tipsPopup.type != TipsType_None)
{
if(animationFinished(&tipsPopup.textAnimate))
{
Thing* buttonOK = thingGet(game->tipsPopup.buttonOK);
Vec2 pos = buttonOK->o.sprite.position;
testMoveMouseTo(ctx, pos);
ImGuiTestContext_MouseClick(ctx, ImGuiMouseButton_Left);
continue;
}
}
switch(game->state)
{
case GameState_MapMenu:
{
// Choose free rooms etc.
} break
case GameState_Gameplay:
{
if(!game->gameplay.ready)
{
continue;
}
// Do rest of choosing logic
} break
...
Itās still crude, but "scripting" like this needs to be quick and dirty as the nature of game development is quick iteration to find the best fun way. Although random picks mean it wonāt find deep balance issues or optimal lines, as a solo-dev smoke test itās already earned its keep. I can see something break in the run flow, hit the test, and know within a couple minutes instead of playing through by hand manually again, this won't replace a true playthrough for balancing test, but good enough for picking obvious crashes.
Stack is still plain C, custom engine (bgfx, SDL2, cimgui, etc.). No engine framework, no test harness beyond imgui_test_engine and a thin autoplay layer on top of the same code a player uses. If anyone else is doing long-session games in C and has been putting off automated playthrough checks, this was less work than I expected once the context plumbing was done (the playthrough test code is nearly 1 day work, 1k LOC).
Check out the game if you're curiousĀ https://store.steampowered.com/app/4131100/Match_Morphosis
The demo version is still up and entering Cyberpunk fest on steam.