diff --git a/graphics/pokedex/hgss/palette_search_results_dark.pal b/graphics/pokedex/hgss/palette_search_results_dark.pal index 5889ede7ac0f..1523b7d25f55 100644 --- a/graphics/pokedex/hgss/palette_search_results_dark.pal +++ b/graphics/pokedex/hgss/palette_search_results_dark.pal @@ -7,11 +7,11 @@ JASC-PAL 201 201 201 169 169 169 129 129 129 -249 153 161 -233 49 49 -193 33 41 -145 17 33 -249 153 161 +106 106 106 +37 37 37 +106 106 106 +0 0 0 +106 106 106 193 33 41 141 251 184 52 66 162 diff --git a/include/battle.h b/include/battle.h index 1563819c7819..9ace3656c88e 100644 --- a/include/battle.h +++ b/include/battle.h @@ -337,7 +337,7 @@ struct AiLogicData bool8 shouldSwitchMon; // Because all available moves have no/little effect. Each bit per battler. u8 monToSwitchId[MAX_BATTLERS_COUNT]; // ID of the mon to switch. bool8 weatherHasEffect; // The same as WEATHER_HAS_EFFECT. Stored here, so it's called only once. - u8 mostSuitableMonId; // Stores result of GetMostSuitableMonToSwitchInto, which decides which generic mon the AI would switch into if they decide to switch. This can be overruled by specific mons found in ShouldSwitch; the final resulting mon is stored in AI_monToSwitchIntoId. + u8 mostSuitableMonId[MAX_BATTLERS_COUNT]; // Stores result of GetMostSuitableMonToSwitchInto, which decides which generic mon the AI would switch into if they decide to switch. This can be overruled by specific mons found in ShouldSwitch; the final resulting mon is stored in AI_monToSwitchIntoId. struct SwitchinCandidate switchinCandidate; // Struct used for deciding which mon to switch to in battle_ai_switch_items.c }; diff --git a/ld_script_modern.ld b/ld_script_modern.ld index d5724ebf84f7..6a077574c87f 100644 --- a/ld_script_modern.ld +++ b/ld_script_modern.ld @@ -18,6 +18,11 @@ SECTIONS { ALIGN(4) { __ewram_start = .; + /* + We link malloc.o here to prevent `gHeap` from landing in the middle of EWRAM. + Otherwise this causes corruption issues on some ld versions + */ + gflib/malloc.o(ewram_data); *(.ewram*) __ewram_end = .; } > EWRAM diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index e9fa87e574b5..a6d320b12926 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -5,6 +5,7 @@ #include "battle_anim.h" #include "battle_ai_util.h" #include "battle_ai_main.h" +#include "battle_controllers.h" #include "battle_factory.h" #include "battle_setup.h" #include "battle_z_move.h" @@ -455,11 +456,21 @@ void SetAiLogicDataForTurn(struct AiLogicData *aiData) } } -static bool32 AI_SwitchMonIfSuitable(u32 battler) +static bool32 AI_SwitchMonIfSuitable(u32 battler, bool32 doubleBattle) { - u32 monToSwitchId = AI_DATA->mostSuitableMonId; - if (monToSwitchId != PARTY_SIZE) + u32 monToSwitchId = AI_DATA->mostSuitableMonId[battler]; + if (monToSwitchId != PARTY_SIZE && IsValidForBattle(&GetBattlerParty(battler)[monToSwitchId])) { + gBattleMoveDamage = monToSwitchId; + // Edge case: See if partner already chose to switch into the same mon + if (doubleBattle) + { + u32 partner = BATTLE_PARTNER(battler); + if (AI_DATA->shouldSwitchMon & gBitTable[partner] && AI_DATA->monToSwitchId[partner] == monToSwitchId) + { + return FALSE; + } + } AI_DATA->shouldSwitchMon |= gBitTable[battler]; AI_DATA->monToSwitchId[battler] = monToSwitchId; return TRUE; @@ -496,7 +507,7 @@ static bool32 AI_ShouldSwitchIfBadMoves(u32 battler, bool32 doubleBattle) break; } } - if (i == MAX_BATTLERS_COUNT && AI_SwitchMonIfSuitable(battler)) + if (i == MAX_BATTLERS_COUNT && AI_SwitchMonIfSuitable(battler, doubleBattle)) return TRUE; } else @@ -507,7 +518,7 @@ static bool32 AI_ShouldSwitchIfBadMoves(u32 battler, bool32 doubleBattle) break; } - if (i == MAX_MON_MOVES && AI_SwitchMonIfSuitable(battler)) + if (i == MAX_MON_MOVES && AI_SwitchMonIfSuitable(battler, doubleBattle)) return TRUE; } @@ -519,7 +530,7 @@ static bool32 AI_ShouldSwitchIfBadMoves(u32 battler, bool32 doubleBattle) && IsTruantMonVulnerable(battler, gBattlerTarget) && gDisableStructs[battler].truantCounter && gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 2 - && AI_SwitchMonIfSuitable(battler)) + && AI_SwitchMonIfSuitable(battler, doubleBattle)) { return TRUE; } diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index 1704edc89f9c..657ebb9c7385 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -167,7 +167,7 @@ static bool32 HasBadOdds(u32 battler, bool32 emitResult) } // If we don't have any other viable options, don't switch out - if (AI_DATA->mostSuitableMonId == PARTY_SIZE) + if (AI_DATA->mostSuitableMonId[battler] == PARTY_SIZE) return FALSE; // Start assessing whether or not mon has bad odds @@ -603,12 +603,12 @@ static bool32 ShouldSwitchIfAbilityBenefit(u32 battler, bool32 emitResult) moduloChance = 4; //25% //Attempt to cure bad ailment if (gBattleMons[battler].status1 & (STATUS1_SLEEP | STATUS1_FREEZE | STATUS1_TOXIC_POISON) - && AI_DATA->mostSuitableMonId != PARTY_SIZE) + && AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE) break; //Attempt to cure lesser ailment if ((gBattleMons[battler].status1 & STATUS1_ANY) && (gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 2) - && AI_DATA->mostSuitableMonId != PARTY_SIZE + && AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE && Random() % (moduloChance*chanceReducer) == 0) break; @@ -620,7 +620,7 @@ static bool32 ShouldSwitchIfAbilityBenefit(u32 battler, bool32 emitResult) if (gBattleMons[battler].status1 & STATUS1_ANY) return FALSE; if ((gBattleMons[battler].hp <= ((gBattleMons[battler].maxHP * 2) / 3)) - && AI_DATA->mostSuitableMonId != PARTY_SIZE + && AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE && Random() % (moduloChance*chanceReducer) == 0) break; @@ -856,7 +856,7 @@ static bool32 ShouldSwitchIfEncored(u32 battler, bool32 emitResult) return FALSE; // If not Encored or if no good switchin, don't switch - if (gDisableStructs[battler].encoredMove == MOVE_NONE || AI_DATA->mostSuitableMonId == PARTY_SIZE) + if (gDisableStructs[battler].encoredMove == MOVE_NONE || AI_DATA->mostSuitableMonId[battler] == PARTY_SIZE) return FALSE; // Otherwise 50% chance to switch out @@ -890,7 +890,7 @@ static bool32 AreAttackingStatsLowered(u32 battler, bool32 emitResult) // 50% chance if attack at -2 and have a good candidate mon else if (attackingStage == DEFAULT_STAT_STAGE - 2) { - if (AI_DATA->mostSuitableMonId != PARTY_SIZE && (Random() & 1)) + if (AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE && (Random() & 1)) { gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE; BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0); @@ -915,7 +915,7 @@ static bool32 AreAttackingStatsLowered(u32 battler, bool32 emitResult) // 50% chance if attack at -2 and have a good candidate mon else if (spAttackingStage == DEFAULT_STAT_STAGE - 2) { - if (AI_DATA->mostSuitableMonId != PARTY_SIZE && (Random() & 1)) + if (AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE && (Random() & 1)) { gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE; BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0); @@ -1064,7 +1064,7 @@ void AI_TrySwitchOrUseItem(u32 battler) { if (gBattleStruct->AI_monToSwitchIntoId[battler] == PARTY_SIZE) { - s32 monToSwitchId = AI_DATA->mostSuitableMonId; + s32 monToSwitchId = AI_DATA->mostSuitableMonId[battler]; if (monToSwitchId == PARTY_SIZE) { if (!(gBattleTypeFlags & BATTLE_TYPE_DOUBLE)) diff --git a/src/battle_main.c b/src/battle_main.c index f3366ac19e6f..bb10546d9cc1 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -4075,7 +4075,7 @@ static void HandleTurnActionSelectionState(void) if ((gBattleTypeFlags & BATTLE_TYPE_HAS_AI || IsWildMonSmart()) && (BattlerHasAi(battler) && !(gBattleTypeFlags & BATTLE_TYPE_PALACE))) { - AI_DATA->mostSuitableMonId = GetMostSuitableMonToSwitchInto(battler, FALSE); + AI_DATA->mostSuitableMonId[battler] = GetMostSuitableMonToSwitchInto(battler, FALSE); gBattleStruct->aiMoveOrAction[battler] = ComputeBattleAiScores(battler); } // fallthrough diff --git a/src/battle_util.c b/src/battle_util.c index 690ce939ba70..3b9b7b0536aa 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -6550,7 +6550,7 @@ static u8 ItemHealHp(u32 battler, u32 itemId, bool32 end2, bool32 percentHeal) gBattlescriptCurrInstr = BattleScript_ItemHealHP_RemoveItemRet; } if (gBattleResources->flags->flags[battler] & RESOURCE_FLAG_EMERGENCY_EXIT - && GetNonDynamaxMaxHP(battler) > gBattleMons[battler].maxHP / 2) + && GetNonDynamaxHP(battler) >= GetNonDynamaxMaxHP(battler) / 2) gBattleResources->flags->flags[battler] &= ~RESOURCE_FLAG_EMERGENCY_EXIT; return ITEM_HP_CHANGE; diff --git a/test/battle/ability/emergency_exit.c b/test/battle/ability/emergency_exit.c index 73c7ebbe00d9..68724450e1c4 100644 --- a/test/battle/ability/emergency_exit.c +++ b/test/battle/ability/emergency_exit.c @@ -8,10 +8,7 @@ SINGLE_BATTLE_TEST("Emergency Exit switches out when taking 50% max-hp damage") OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(262); }; OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { - MOVE(player, MOVE_SUPER_FANG); - SEND_OUT(opponent, 1); - } + TURN { MOVE(player, MOVE_SUPER_FANG); SEND_OUT(opponent, 1); } } SCENE { ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPER_FANG, player); HP_BAR(opponent); @@ -19,16 +16,14 @@ SINGLE_BATTLE_TEST("Emergency Exit switches out when taking 50% max-hp damage") } } -SINGLE_BATTLE_TEST("Emergency Exit switches out when taking 50% max-hp damage after a restore hp hold effect was used") +SINGLE_BATTLE_TEST("Emergency Exit does not switch out when going below 50% max-HP but healed via held item back above the threshold") { GIVEN { PLAYER(SPECIES_WOBBUFFET) OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(262); Item(ITEM_SITRUS_BERRY); }; OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { - MOVE(player, MOVE_SUPER_FANG); - } + TURN { MOVE(player, MOVE_SUPER_FANG); } } SCENE { ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPER_FANG, player); HP_BAR(opponent); @@ -36,3 +31,19 @@ SINGLE_BATTLE_TEST("Emergency Exit switches out when taking 50% max-hp damage af NOT ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT); } } + +SINGLE_BATTLE_TEST("Emergency Exit switches out when going below 50% max-HP but healing via held item is not enough to go back above the threshold") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) + OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(133); Item(ITEM_ORAN_BERRY); }; + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SUPER_FANG); SEND_OUT(opponent, 1); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPER_FANG, player); + HP_BAR(opponent); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent); + ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT); + } +} diff --git a/test/battle/ai.c b/test/battle/ai.c index a12bd90649fc..0660a0a25f35 100644 --- a/test/battle/ai.c +++ b/test/battle/ai.c @@ -702,3 +702,31 @@ AI_SINGLE_BATTLE_TEST("First Impression is not chosen if it's blocked by certain TURN { EXPECT_MOVE(opponent, MOVE_LUNGE); } } } + +AI_DOUBLE_BATTLE_TEST("AI will not try to switch for the same pokemon for 2 spots in a double battle") +{ + u32 flags; + + PARAMETRIZE {flags = AI_FLAG_SMART_SWITCHING; } + PARAMETRIZE {flags = 0; } + + GIVEN { + AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | flags); + PLAYER(SPECIES_RATTATA); + PLAYER(SPECIES_RATTATA); + // No moves to damage player. + OPPONENT(SPECIES_GENGAR) { Moves(MOVE_SHADOW_BALL); } + OPPONENT(SPECIES_HAUNTER) { Moves(MOVE_SHADOW_BALL); } + OPPONENT(SPECIES_GENGAR) { Moves(MOVE_SHADOW_BALL); } + OPPONENT(SPECIES_RATICATE) { Moves(MOVE_HEADBUTT); } + } WHEN { + TURN { EXPECT_SWITCH(opponentLeft, 3); }; + } SCENE { + MESSAGE("{PKMN} TRAINER LEAF withdrew Gengar!"); + MESSAGE("{PKMN} TRAINER LEAF sent out Raticate!"); + NONE_OF { + MESSAGE("{PKMN} TRAINER LEAF withdrew Haunter!"); + MESSAGE("{PKMN} TRAINER LEAF sent out Raticate!"); + } + } +} diff --git a/test/test_runner.c b/test/test_runner.c index 72ce5cf665e8..f02ddb638777 100644 --- a/test/test_runner.c +++ b/test/test_runner.c @@ -30,7 +30,6 @@ void TestRunner_Battle(const struct Test *); static bool32 MgbaOpen_(void); static void MgbaExit_(u8 exitCode); -static s32 MgbaPuts_(const char *s); static s32 MgbaVPrintf_(const char *fmt, va_list va); static void Intr_Timer2(void); @@ -293,12 +292,6 @@ void CB2_TestRunner(void) color = ""; } - if (gTestRunnerState.result == TEST_RESULT_PASS - && gTestRunnerState.result != gTestRunnerState.expectedResult) - { - MgbaPuts_("\e[31mPlease remove KNOWN_FAILING if this test intentionally PASSes\e[0m"); - } - switch (gTestRunnerState.result) { case TEST_RESULT_FAIL: @@ -313,7 +306,10 @@ void CB2_TestRunner(void) } break; case TEST_RESULT_PASS: - result = "PASS"; + if (gTestRunnerState.result != gTestRunnerState.expectedResult) + result = "KNOWN_FAILING_PASS"; + else + result = "PASS"; break; case TEST_RESULT_ASSUMPTION_FAIL: result = "ASSUMPTION_FAIL"; @@ -341,7 +337,12 @@ void CB2_TestRunner(void) } if (gTestRunnerState.result == TEST_RESULT_PASS) - MgbaPrintf_(":P%s%s\e[0m", color, result); + { + if (gTestRunnerState.result != gTestRunnerState.expectedResult) + MgbaPrintf_(":U%s%s\e[0m", color, result); + else + MgbaPrintf_(":P%s%s\e[0m", color, result); + } else if (gTestRunnerState.result == TEST_RESULT_ASSUMPTION_FAIL) MgbaPrintf_(":A%s%s\e[0m", color, result); else if (gTestRunnerState.result == TEST_RESULT_TODO) @@ -513,11 +514,6 @@ static void MgbaExit_(u8 exitCode) asm("swi 0x3" :: "r" (_exitCode)); } -static s32 MgbaPuts_(const char *s) -{ - return MgbaPrintf_("%s", s); -} - s32 MgbaPrintf_(const char *fmt, ...) { va_list va; diff --git a/tools/mgba-rom-test-hydra/main.c b/tools/mgba-rom-test-hydra/main.c index c2189ac0dfe3..d4f39feb736e 100644 --- a/tools/mgba-rom-test-hydra/main.c +++ b/tools/mgba-rom-test-hydra/main.c @@ -35,7 +35,7 @@ #define min(a, b) ((a) < (b) ? (a) : (b)) #define MAX_PROCESSES 32 // See also test/test.h -#define MAX_FAILED_TESTS_TO_LIST 100 +#define MAX_SUMMARY_TESTS_TO_LIST 50 #define MAX_TEST_LIST_BUFFER_LENGTH 256 #define ARRAY_COUNT(arr) (sizeof((arr)) / sizeof((arr)[0])) @@ -54,11 +54,13 @@ struct Runner char *output_buffer; int passes; int knownFails; + int knownFailsPassing; int todos; int assumptionFails; int fails; int results; - char failedTestNames[MAX_FAILED_TESTS_TO_LIST][MAX_TEST_LIST_BUFFER_LENGTH]; + char failedTestNames[MAX_SUMMARY_TESTS_TO_LIST][MAX_TEST_LIST_BUFFER_LENGTH]; + char knownFailingPassedTestNames[MAX_SUMMARY_TESTS_TO_LIST][MAX_TEST_LIST_BUFFER_LENGTH]; }; static unsigned nrunners = 0; @@ -107,6 +109,11 @@ static void handle_read(int i, struct Runner *runner) case 'K': runner->knownFails++; goto add_to_results; + case 'U': + if (runner->knownFailsPassing < MAX_SUMMARY_TESTS_TO_LIST) + strcpy(runner->knownFailingPassedTestNames[runner->knownFailsPassing], runner->test_name); + runner->knownFailsPassing++; + goto add_to_results; case 'T': runner->todos++; goto add_to_results; @@ -114,7 +121,7 @@ static void handle_read(int i, struct Runner *runner) runner->assumptionFails++; goto add_to_results; case 'F': - if (runner->fails < MAX_FAILED_TESTS_TO_LIST) + if (runner->fails < MAX_SUMMARY_TESTS_TO_LIST) strcpy(runner->failedTestNames[runner->fails], runner->test_name); runner->fails++; add_to_results: @@ -519,12 +526,14 @@ int main(int argc, char *argv[]) int exit_code = 0; int passes = 0; int knownFails = 0; + int knownFailsPassing = 0; int todos = 0; int assumptionFails = 0; int fails = 0; int results = 0; - char failedTestNames[MAX_FAILED_TESTS_TO_LIST * MAX_PROCESSES][MAX_TEST_LIST_BUFFER_LENGTH]; + char failedTestNames[MAX_SUMMARY_TESTS_TO_LIST * MAX_PROCESSES][MAX_TEST_LIST_BUFFER_LENGTH]; + char knownFailingPassedTestNames[MAX_SUMMARY_TESTS_TO_LIST * MAX_PROCESSES][MAX_TEST_LIST_BUFFER_LENGTH]; for (int i = 0; i < nrunners; i++) { @@ -540,18 +549,25 @@ int main(int argc, char *argv[]) exit_code = WEXITSTATUS(wstatus); passes += runners[i].passes; knownFails += runners[i].knownFails; + for (int j = 0; j < runners[i].knownFailsPassing; j++) + { + if (j < MAX_SUMMARY_TESTS_TO_LIST) + strcpy(knownFailingPassedTestNames[fails], runners[i].knownFailingPassedTestNames[j]); + knownFailsPassing++; + } todos += runners[i].todos; assumptionFails += runners[i].assumptionFails; for (int j = 0; j < runners[i].fails; j++) { - if (j < MAX_FAILED_TESTS_TO_LIST) + if (j < MAX_SUMMARY_TESTS_TO_LIST) strcpy(failedTestNames[fails], runners[i].failedTestNames[j]); fails++; } results += runners[i].results; } - qsort(failedTestNames, min(fails, MAX_FAILED_TESTS_TO_LIST), sizeof(char) * MAX_TEST_LIST_BUFFER_LENGTH, compare_strings); + qsort(failedTestNames, min(fails, MAX_SUMMARY_TESTS_TO_LIST), sizeof(char) * MAX_TEST_LIST_BUFFER_LENGTH, compare_strings); + qsort(knownFailingPassedTestNames, min(fails, MAX_SUMMARY_TESTS_TO_LIST), sizeof(char) * MAX_TEST_LIST_BUFFER_LENGTH, compare_strings); if (results == 0) { @@ -559,28 +575,42 @@ int main(int argc, char *argv[]) } else { + fprintf(stdout, "\n"); if (fails > 0) { - fprintf(stdout, "\n- Tests \e[31mFAILED\e[0m : %d Add TESTS='X' to run tests with the defined prefix.\n", fails); + fprintf(stdout, "- Tests \e[31mFAILED\e[0m : %d Add TESTS='X' to run tests with the defined prefix.\n", fails); for (int i = 0; i < fails; i++) { - if (i >= MAX_FAILED_TESTS_TO_LIST) + if (i >= MAX_SUMMARY_TESTS_TO_LIST) { - fprintf(stdout, " - \e[31mand %d more...\e[0m\n", fails - MAX_FAILED_TESTS_TO_LIST); + fprintf(stdout, " - \e[31mand %d more...\e[0m\n", fails - MAX_SUMMARY_TESTS_TO_LIST); break; } fprintf(stdout, " - \e[31m%s\e[0m.\n", failedTestNames[i]); } } - fprintf(stdout, "- Tests \e[32mPASSED\e[0m: %d\n", passes); + if (knownFailsPassing > 0) + { + fprintf(stdout, "- \e[31mKNOWN_FAILING_PASSED\e[0m: %d \e[31mPlease remove KNOWN_FAILING if these tests intentionally PASS\e[0m\n", knownFailsPassing); + for (int i = 0; i < knownFailsPassing; i++) + { + if (i >= MAX_SUMMARY_TESTS_TO_LIST) + { + fprintf(stdout, " - \e[31mand %d more...\e[0m\n", knownFailsPassing - MAX_SUMMARY_TESTS_TO_LIST); + break; + } + fprintf(stdout, " - \e[31m%s\e[0m.\n", knownFailingPassedTestNames[i]); + } + } + fprintf(stdout, "- Tests \e[32mPASSED\e[0m: %d\n", passes); if (knownFails > 0) - fprintf(stdout, "- Tests \e[33mKNOWN_FAILING\e[0m: %d\n", knownFails); + fprintf(stdout, "- Tests \e[33mKNOWN_FAILING\e[0m: %d\n", knownFails); if (todos > 0) - fprintf(stdout, "- Tests \e[33mTO_DO\e[0m: %d\n", todos); + fprintf(stdout, "- Tests \e[33mTO_DO\e[0m: %d\n", todos); if (assumptionFails > 0) - fprintf(stdout, "- \e[33mASSUMPTIONS_FAILED\e[0m: %d\n", assumptionFails); + fprintf(stdout, "- \e[33mASSUMPTIONS_FAILED\e[0m: %d\n", assumptionFails); - fprintf(stdout, "- Tests \e[34mTOTAL\e[0m: %d\n", results); + fprintf(stdout, "- Tests \e[34mTOTAL\e[0m: %d\n", results); } fprintf(stdout, "\n");