diff --git a/include/random.h b/include/random.h index e7c2a3e28..83bc3e0d5 100644 --- a/include/random.h +++ b/include/random.h @@ -32,6 +32,10 @@ void SeedRng2(u16 seed); * RandomUniform(tag, lo, hi) returns a number from lo to hi inclusive * with uniform probability. * + * RandomUniformExcept(tag, lo, hi, reject) returns a number from lo to + * hi inclusive with uniform probability, excluding those for which + * reject returns TRUE. + * * RandomElement(tag, array) returns an element in array with uniform * probability. The array must be known at compile-time (e.g. a global * const array). @@ -56,8 +60,11 @@ enum RandomTag RNG_FLAME_BODY, RNG_FORCE_RANDOM_SWITCH, RNG_FROZEN, + RNG_HITS, RNG_HOLD_EFFECT_FLINCH, RNG_INFATUATION, + RNG_LOADED_DICE, + RNG_METRONOME, RNG_PARALYSIS, RNG_POISON_POINT, RNG_RAMPAGE_TURNS, @@ -103,10 +110,12 @@ enum RandomTag }) u32 RandomUniform(enum RandomTag, u32 lo, u32 hi); +u32 RandomUniformExcept(enum RandomTag, u32 lo, u32 hi, bool32 (*reject)(u32)); u32 RandomWeightedArray(enum RandomTag, u32 sum, u32 n, const u8 *weights); const void *RandomElementArray(enum RandomTag, const void *array, size_t size, size_t count); u32 RandomUniformDefault(enum RandomTag, u32 lo, u32 hi); +u32 RandomUniformExceptDefault(enum RandomTag, u32 lo, u32 hi, bool32 (*reject)(u32)); u32 RandomWeightedArrayDefault(enum RandomTag, u32 sum, u32 n, const u8 *weights); const void *RandomElementArrayDefault(enum RandomTag, const void *array, size_t size, size_t count); diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index bc85f371c..ccd4a404f 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -12206,30 +12206,14 @@ static void Cmd_setmultihitcounter(void) } else { - #if B_MULTI_HIT_CHANCE >= GEN_5 - // Based on Gen 5's odds - // 35% for 2 hits - // 35% for 3 hits - // 15% for 4 hits - // 15% for 5 hits - gMultiHitCounter = Random() % 100; - if (gMultiHitCounter < 35) - gMultiHitCounter = 2; - else if (gMultiHitCounter < 35 + 35) - gMultiHitCounter = 3; - else if (gMultiHitCounter < 35 + 35 + 15) - gMultiHitCounter = 4; - else - gMultiHitCounter = 5; - #else - // 2 and 3 hits: 37.5% - // 4 and 5 hits: 12.5% - gMultiHitCounter = Random() % 4; - if (gMultiHitCounter > 1) - gMultiHitCounter = (Random() % 4) + 2; - else - gMultiHitCounter += 2; - #endif + // WARNING: These seem to be unused, see SetRandomMultiHitCounter. + #if B_MULTI_HIT_CHANCE >= GEN_5 + // 35%: 2 hits, 35%: 3 hits, 15% 4 hits, 15% 5 hits. + gMultiHitCounter = RandomWeighted(RNG_HITS, 0, 0, 7, 7, 3, 3); + #else + // 37.5%: 2 hits, 37.5%: 3 hits, 12.5% 4 hits, 12.5% 5 hits. + gMultiHitCounter = RandomWeighted(RNG_HITS, 0, 0, 3, 3, 1, 1); + #endif } } @@ -12977,41 +12961,37 @@ static void Cmd_mimicattackcopy(void) } } +static bool32 InvalidMetronomeMove(u32 move) +{ + return gBattleMoves[move].effect == EFFECT_PLACEHOLDER + || sForbiddenMoves[move] & FORBIDDEN_METRONOME; +} + static void Cmd_metronome(void) { CMD_ARGS(); #if B_METRONOME_MOVES >= GEN_9 - u16 moveCount = MOVES_COUNT_GEN9; + u32 moveCount = MOVES_COUNT_GEN9; #elif B_METRONOME_MOVES >= GEN_8 - u16 moveCount = MOVES_COUNT_GEN8; + u32 moveCount = MOVES_COUNT_GEN8; #elif B_METRONOME_MOVES >= GEN_7 - u16 moveCount = MOVES_COUNT_GEN7; + u32 moveCount = MOVES_COUNT_GEN7; #elif B_METRONOME_MOVES >= GEN_6 - u16 moveCount = MOVES_COUNT_GEN6; + u32 moveCount = MOVES_COUNT_GEN6; #elif B_METRONOME_MOVES >= GEN_5 - u16 moveCount = MOVES_COUNT_GEN5; + u32 moveCount = MOVES_COUNT_GEN5; #elif B_METRONOME_MOVES >= GEN_4 - u16 moveCount = MOVES_COUNT_GEN4; + u32 moveCount = MOVES_COUNT_GEN4; #elif B_METRONOME_MOVES >= GEN_3 - u16 moveCount = MOVES_COUNT_GEN3; + u32 moveCount = MOVES_COUNT_GEN3; #endif - while (TRUE) - { - gCurrentMove = (Random() % (moveCount - 1)) + 1; - if (gBattleMoves[gCurrentMove].effect == EFFECT_PLACEHOLDER) - continue; - - if (!(sForbiddenMoves[gCurrentMove] & FORBIDDEN_METRONOME)) - { - gHitMarker &= ~HITMARKER_ATTACKSTRING_PRINTED; - SetAtkCancellerForCalledMove(); - gBattlescriptCurrInstr = gBattleScriptsForMoveEffects[gBattleMoves[gCurrentMove].effect]; - gBattlerTarget = GetMoveTarget(gCurrentMove, NO_TARGET_OVERRIDE); - return; - } - } + gCurrentMove = RandomUniformExcept(RNG_METRONOME, 1, moveCount - 1, InvalidMetronomeMove); + gHitMarker &= ~HITMARKER_ATTACKSTRING_PRINTED; + SetAtkCancellerForCalledMove(); + gBattlescriptCurrInstr = gBattleScriptsForMoveEffects[gBattleMoves[gCurrentMove].effect]; + gBattlerTarget = GetMoveTarget(gCurrentMove, NO_TARGET_OVERRIDE); } static void Cmd_dmgtolevel(void) diff --git a/src/battle_util.c b/src/battle_util.c index baa365a0e..0a2a99447 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -10899,35 +10899,19 @@ bool32 CanTargetBattler(u8 battlerAtk, u8 battlerDef, u16 move) static void SetRandomMultiHitCounter() { -#if (B_MULTI_HIT_CHANCE >= GEN_5) - // Based on Gen 5's odds - // 35% for 2 hits - // 35% for 3 hits - // 15% for 4 hits - // 15% for 5 hits - gMultiHitCounter = Random() % 100; - if (gMultiHitCounter < 35) - gMultiHitCounter = 2; - else if (gMultiHitCounter < 35 + 35) - gMultiHitCounter = 3; - else if (gMultiHitCounter < 35 + 35 + 15) - gMultiHitCounter = 4; - else - gMultiHitCounter = 5; -#else - // 2 and 3 hits: 37.5% - // 4 and 5 hits: 12.5% - gMultiHitCounter = Random() % 4; - if (gMultiHitCounter > 1) - gMultiHitCounter = (Random() % 4) + 2; - else - gMultiHitCounter += 2; -#endif - - if (gMultiHitCounter < 4 && GetBattlerHoldEffect(gBattlerAttacker, TRUE) == HOLD_EFFECT_LOADED_DICE) + if (GetBattlerHoldEffect(gBattlerAttacker, TRUE) == HOLD_EFFECT_LOADED_DICE) { - // If roll 4 or 5 Loaded Dice doesn't do anything. Otherwise it rolls the number of hits as 5 minus a random integer from 0 to 1 inclusive. - gMultiHitCounter = 5 - (Random() & 1); + gMultiHitCounter = RandomUniform(RNG_LOADED_DICE, 4, 5); + } + else + { +#if B_MULTI_HIT_CHANCE >= GEN_5 + // 35%: 2 hits, 35%: 3 hits, 15% 4 hits, 15% 5 hits. + gMultiHitCounter = RandomWeighted(RNG_HITS, 0, 0, 7, 7, 3, 3); +#else + // 37.5%: 2 hits, 37.5%: 3 hits, 12.5% 4 hits, 12.5% 5 hits. + gMultiHitCounter = RandomWeighted(RNG_HITS, 0, 0, 3, 3, 1, 1); +#endif } } diff --git a/src/random.c b/src/random.c index 14983e776..0c2509bd9 100644 --- a/src/random.c +++ b/src/random.c @@ -35,6 +35,9 @@ u16 Random2(void) __attribute__((weak, alias("RandomUniformDefault"))) u32 RandomUniform(enum RandomTag tag, u32 lo, u32 hi); +__attribute__((weak, alias("RandomUniformExceptDefault"))) +u32 RandomUniformExcept(enum RandomTag, u32 lo, u32 hi, bool32 (*reject)(u32)); + __attribute__((weak, alias("RandomWeightedArrayDefault"))) u32 RandomWeightedArray(enum RandomTag tag, u32 sum, u32 n, const u8 *weights); @@ -46,6 +49,16 @@ u32 RandomUniformDefault(enum RandomTag tag, u32 lo, u32 hi) return lo + (((hi - lo + 1) * Random()) >> 16); } +u32 RandomUniformExceptDefault(enum RandomTag tag, u32 lo, u32 hi, bool32 (*reject)(u32)) +{ + while (TRUE) + { + u32 n = RandomUniformDefault(tag, lo, hi); + if (!reject(n)) + return n; + } +} + u32 RandomWeightedArrayDefault(enum RandomTag tag, u32 sum, u32 n, const u8 *weights) { s32 i, targetSum; diff --git a/test/move_effect_metronome.c b/test/move_effect_metronome.c index 11a906f94..1e68603e6 100644 --- a/test/move_effect_metronome.c +++ b/test/move_effect_metronome.c @@ -6,19 +6,13 @@ ASSUMPTIONS ASSUME(gBattleMoves[MOVE_METRONOME].effect == EFFECT_METRONOME); } -// To do: Turn the seeds to work with WITH_RNG for Metronome. -#define RNG_METRONOME_SCRATCH 0x118 -#define RNG_METRONOME_PSN_POWDER 0x119 -#define RNG_METRONOME_ROCK_BLAST 0x1F5 - SINGLE_BATTLE_TEST("Metronome picks a random move") { GIVEN { - RNGSeed(RNG_METRONOME_SCRATCH); PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_METRONOME); } + TURN { MOVE(player, MOVE_METRONOME, WITH_RNG(RNG_METRONOME, MOVE_SCRATCH)); } } SCENE { MESSAGE("Wobbuffet used Metronome!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_METRONOME, player); @@ -34,11 +28,10 @@ SINGLE_BATTLE_TEST("Metronome's called powder move fails against Grass Types") ASSUME(gBattleMoves[MOVE_POISON_POWDER].flags & FLAG_POWDER); ASSUME(gSpeciesInfo[SPECIES_TANGELA].types[0] == TYPE_GRASS); ASSUME(gBattleMoves[MOVE_POISON_POWDER].effect == EFFECT_POISON); - RNGSeed(RNG_METRONOME_PSN_POWDER); - PLAYER(SPECIES_WOBBUFFET) {Speed(5);} - OPPONENT(SPECIES_TANGELA) {Speed(2);} + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_TANGELA); } WHEN { - TURN { MOVE(player, MOVE_METRONOME); } + TURN { MOVE(player, MOVE_METRONOME, WITH_RNG(RNG_METRONOME, MOVE_POISON_POWDER)); } } SCENE { MESSAGE("Wobbuffet used Metronome!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_METRONOME, player); @@ -53,17 +46,16 @@ SINGLE_BATTLE_TEST("Metronome's called multi-hit move hits multiple times") { GIVEN { ASSUME(gBattleMoves[MOVE_ROCK_BLAST].effect == EFFECT_MULTI_HIT); - RNGSeed(RNG_METRONOME_ROCK_BLAST); PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_METRONOME); } + TURN { MOVE(player, MOVE_METRONOME, WITH_RNG(RNG_METRONOME, MOVE_ROCK_BLAST)); } } SCENE { MESSAGE("Wobbuffet used Metronome!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_METRONOME, player); MESSAGE("Wobbuffet used Rock Blast!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_ROCK_BLAST, player); HP_BAR(opponent); - MESSAGE("Hit 4 time(s)!"); + MESSAGE("Hit 5 time(s)!"); } } diff --git a/test/move_effect_mirror_move.c b/test/move_effect_mirror_move.c index 2b8b9dbe9..ff21c0efa 100644 --- a/test/move_effect_mirror_move.c +++ b/test/move_effect_mirror_move.c @@ -9,8 +9,8 @@ ASSUMPTIONS SINGLE_BATTLE_TEST("Mirror Move copies the last used move by the target") { GIVEN { - PLAYER(SPECIES_WOBBUFFET) {Speed(2);} - OPPONENT(SPECIES_WOBBUFFET) {Speed(5);} + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); } WHEN { TURN { MOVE(opponent, MOVE_TACKLE); MOVE(player, MOVE_MIRROR_MOVE); } } SCENE { @@ -26,10 +26,10 @@ SINGLE_BATTLE_TEST("Mirror Move copies the last used move by the target") SINGLE_BATTLE_TEST("Mirror Move fails if no move was used before") { GIVEN { - PLAYER(SPECIES_WOBBUFFET) {Speed(5);} - OPPONENT(SPECIES_WOBBUFFET) {Speed(2);} + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(opponent, MOVE_TACKLE); MOVE(player, MOVE_MIRROR_MOVE); } + TURN { MOVE(player, MOVE_MIRROR_MOVE); MOVE(opponent, MOVE_TACKLE); } } SCENE { MESSAGE("Wobbuffet used Mirror Move!"); MESSAGE("The Mirror Move failed!"); @@ -44,8 +44,8 @@ SINGLE_BATTLE_TEST("Mirror Move's called powder move fails against Grass Types") ASSUME(gBattleMoves[MOVE_STUN_SPORE].flags & FLAG_POWDER); ASSUME(gSpeciesInfo[SPECIES_ODDISH].types[0] == TYPE_GRASS); ASSUME(gBattleMoves[MOVE_STUN_SPORE].effect == EFFECT_PARALYZE); - PLAYER(SPECIES_ODDISH) {Speed(5);} - OPPONENT(SPECIES_WOBBUFFET) {Speed(2);} + PLAYER(SPECIES_ODDISH); + OPPONENT(SPECIES_WOBBUFFET); } WHEN { TURN { MOVE(player, MOVE_STUN_SPORE); MOVE(opponent, MOVE_MIRROR_MOVE); } } SCENE { @@ -59,19 +59,18 @@ SINGLE_BATTLE_TEST("Mirror Move's called powder move fails against Grass Types") } } -// It hits first 2 times, then 5 times with the default rng seed. SINGLE_BATTLE_TEST("Mirror Move's called multi-hit move hits multiple times") { GIVEN { ASSUME(gBattleMoves[MOVE_BULLET_SEED].effect == EFFECT_MULTI_HIT); - PLAYER(SPECIES_WOBBUFFET) {Speed(5);} - OPPONENT(SPECIES_WOBBUFFET) {Speed(2);} + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); } WHEN { TURN { MOVE(player, MOVE_BULLET_SEED); MOVE(opponent, MOVE_MIRROR_MOVE); } } SCENE { ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLET_SEED, player); HP_BAR(opponent); - MESSAGE("Hit 2 time(s)!"); + MESSAGE("Hit 5 time(s)!"); MESSAGE("Foe Wobbuffet used Mirror Move!"); MESSAGE("Foe Wobbuffet used Bullet Seed!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_BULLET_SEED, opponent); diff --git a/test/random.c b/test/random.c index 39f587c06..a56e91a10 100644 --- a/test/random.c +++ b/test/random.c @@ -16,6 +16,25 @@ TEST("RandomUniform generates lo..hi") } } +static bool32 InvalidEven(u32 n) +{ + return n % 2 == 0; +} + +TEST("RandomUniformExcept generates lo..hi") +{ + u32 lo, hi, i; + PARAMETRIZE { lo = 0; hi = 1; } + PARAMETRIZE { lo = 0; hi = 2; } + PARAMETRIZE { lo = 0; hi = 3; } + PARAMETRIZE { lo = 2; hi = 4; } + for (i = 0; i < 1024; i++) + { + u32 r = RandomUniformExceptDefault(RNG_NONE, lo, hi, InvalidEven); + EXPECT(lo <= r && r <= hi && r % 2 != 0); + } +} + TEST("RandomWeighted generates 0..n-1") { u32 n, sum, i; @@ -65,6 +84,29 @@ TEST("RandomUniform generates uniform distribution") EXPECT_LT(error, UQ_4_12(0.025)); } +TEST("RandomUniformExcept generates uniform distribution") +{ + u32 i, error; + u16 distribution[4]; + + memset(distribution, 0, sizeof(distribution)); + for (i = 0; i < 4096; i++) + { + u32 r = RandomUniformExceptDefault(RNG_NONE, 0, ARRAY_COUNT(distribution) - 1, InvalidEven); + EXPECT(0 <= r && r < ARRAY_COUNT(distribution)); + distribution[r]++; + } + + error = 0; + for (i = 0; i < ARRAY_COUNT(distribution); i++) + { + if (i % 2 != 0) + error += abs(UQ_4_12(0.5) - distribution[i]); + } + + EXPECT_LT(error, UQ_4_12(0.05)); +} + TEST("RandomWeighted generates distribution in proportion to the weights") { u32 i, sum, error; diff --git a/test/test_battle.h b/test/test_battle.h index 039506c1b..b4f38ed91 100644 --- a/test/test_battle.h +++ b/test/test_battle.h @@ -603,8 +603,9 @@ struct BattleTestRunnerState u8 parameters; u8 runParameter; u16 rngTag; - u8 trials; - u8 runTrial; + u16 rngTrialOffset; + u16 trials; + u16 runTrial; u16 expectedRatio; u16 observedRatio; u16 trialRatio; diff --git a/test/test_runner_battle.c b/test/test_runner_battle.c index 592d0db98..4de144c38 100644 --- a/test/test_runner_battle.c +++ b/test/test_runner_battle.c @@ -307,17 +307,13 @@ static void BattleTest_Run(void *data) u32 RandomUniform(enum RandomTag tag, u32 lo, u32 hi) { const struct BattlerTurn *turn = NULL; - u32 default_ = hi; if (gCurrentTurnActionNumber < gBattlersCount) { u32 battlerId = gBattlerByTurnOrder[gCurrentTurnActionNumber]; turn = &DATA.battleRecordTurns[gBattleResults.battleTurnCounter][battlerId]; - } - - if (turn && turn->rng.tag == tag) - { - default_ = turn->rng.value; + if (turn && turn->rng.tag == tag) + return turn->rng.value; } if (tag == STATE->rngTag) @@ -332,53 +328,76 @@ u32 RandomUniform(enum RandomTag tag, u32 lo, u32 hi) { Test_ExitWithResult(TEST_RESULT_ERROR, "RandomUniform called with inconsistent trials %d and %d", STATE->trials, n); } - STATE->trialRatio = Q_4_12(1) / n; + STATE->trialRatio = Q_4_12(1) / STATE->trials; return STATE->runTrial + lo; } + return hi; +} + +u32 RandomUniformExcept(enum RandomTag tag, u32 lo, u32 hi, bool32 (*reject)(u32)) +{ + const struct BattlerTurn *turn = NULL; + u32 default_; + + if (gCurrentTurnActionNumber < gBattlersCount) + { + u32 battlerId = gBattlerByTurnOrder[gCurrentTurnActionNumber]; + turn = &DATA.battleRecordTurns[gBattleResults.battleTurnCounter][battlerId]; + if (turn && turn->rng.tag == tag) + { + if (reject(turn->rng.value)) + Test_ExitWithResult(TEST_RESULT_INVALID, "WITH_RNG specified a rejected value (%d)", turn->rng.value); + return turn->rng.value; + } + } + + if (tag == STATE->rngTag) + { + if (STATE->trials == 1) + { + u32 n = 0, i; + for (i = lo; i < hi; i++) + if (!reject(i)) + n++; + STATE->trials = n; + PrintTestName(); + } + STATE->trialRatio = Q_4_12(1) / STATE->trials; + + while (reject(STATE->runTrial + lo + STATE->rngTrialOffset)) + { + if (STATE->runTrial + lo + STATE->rngTrialOffset > hi) + Test_ExitWithResult(TEST_RESULT_INVALID, "RandomUniformExcept called with inconsistent reject"); + STATE->rngTrialOffset++; + } + + return STATE->runTrial + lo + STATE->rngTrialOffset; + } + + default_ = hi; + while (reject(default_)) + { + if (default_ == lo) + Test_ExitWithResult(TEST_RESULT_INVALID, "RandomUniformExcept rejected all values"); + default_--; + } return default_; } u32 RandomWeightedArray(enum RandomTag tag, u32 sum, u32 n, const u8 *weights) { const struct BattlerTurn *turn = NULL; - u32 default_ = n-1; + + if (sum == 0) + Test_ExitWithResult(TEST_RESULT_ERROR, "RandomWeightedArray called with zero sum"); if (gCurrentTurnActionNumber < gBattlersCount) { u32 battlerId = gBattlerByTurnOrder[gCurrentTurnActionNumber]; turn = &DATA.battleRecordTurns[gBattleResults.battleTurnCounter][battlerId]; - } - - if (turn && turn->rng.tag == tag) - { - default_ = turn->rng.value; - } - else - { - switch (tag) - { - case RNG_ACCURACY: - ASSUME(n == 2); - if (turn && turn->hit) - return turn->hit - 1; - default_ = TRUE; - break; - - case RNG_CRITICAL_HIT: - ASSUME(n == 2); - if (turn && turn->criticalHit) - return turn->criticalHit - 1; - default_ = FALSE; - break; - - case RNG_SECONDARY_EFFECT: - ASSUME(n == 2); - if (turn && turn->secondaryEffect) - return turn->secondaryEffect - 1; - default_ = TRUE; - break; - } + if (turn && turn->rng.tag == tag) + return turn->rng.value; } if (tag == STATE->rngTag) @@ -397,7 +416,38 @@ u32 RandomWeightedArray(enum RandomTag tag, u32 sum, u32 n, const u8 *weights) return STATE->runTrial; } - return default_; + switch (tag) + { + case RNG_ACCURACY: + ASSUME(n == 2); + if (turn && turn->hit) + return turn->hit - 1; + else + return TRUE; + + case RNG_CRITICAL_HIT: + ASSUME(n == 2); + if (turn && turn->criticalHit) + return turn->criticalHit - 1; + else + return FALSE; + + case RNG_SECONDARY_EFFECT: + ASSUME(n == 2); + if (turn && turn->secondaryEffect) + return turn->secondaryEffect - 1; + else + return TRUE; + + default: + while (weights[n-1] == 0) + { + if (n == 1) + Test_ExitWithResult(TEST_RESULT_ERROR, "RandomWeightedArray called with all zero weights"); + n--; + } + return n-1; + } } const void *RandomElementArray(enum RandomTag tag, const void *array, size_t size, size_t count) @@ -409,22 +459,17 @@ const void *RandomElementArray(enum RandomTag tag, const void *array, size_t siz { u32 battlerId = gBattlerByTurnOrder[gCurrentTurnActionNumber]; turn = &DATA.battleRecordTurns[gBattleResults.battleTurnCounter][battlerId]; - } - - if (turn && turn->rng.tag == tag) - { - u32 element = 0; - for (index = 0; index < count; index++) - { - memcpy(&element, (const u8 *)array + size * index, size); - if (element == turn->rng.value) - break; - } - if (index == count) + if (turn && turn->rng.tag == tag) { + u32 element = 0; + for (index = 0; index < count; index++) + { + memcpy(&element, (const u8 *)array + size * index, size); + if (element == turn->rng.value) + return (const u8 *)array + size * index; + } // TODO: Incorporate the line number. - const char *filename = gTestRunnerState.test->filename; - Test_ExitWithResult(TEST_RESULT_ERROR, "%s: RandomElement illegal value requested: %d", filename, turn->rng.value); + Test_ExitWithResult(TEST_RESULT_ERROR, "%s: RandomElement illegal value requested: %d", gTestRunnerState.test->filename, turn->rng.value); } } @@ -440,10 +485,8 @@ const void *RandomElementArray(enum RandomTag tag, const void *array, size_t siz Test_ExitWithResult(TEST_RESULT_ERROR, "RandomElement called with inconsistent trials %d and %d", STATE->trials, count); } STATE->trialRatio = Q_4_12(1) / count; - index = STATE->runTrial; + return (const u8 *)array + size * STATE->runTrial; } - - return (const u8 *)array + size * index; } static s32 TryAbilityPopUp(s32 i, s32 n, u32 battlerId, u32 ability) @@ -962,6 +1005,7 @@ void Randomly(u32 sourceLine, u32 passes, u32 trials, struct RandomlyContext ctx INVALID_IF(test->resultsSize > 0, "PASSES_RANDOMLY is incompatible with results"); INVALID_IF(passes > trials, "%d passes specified, but only %d trials", passes, trials); STATE->rngTag = ctx.tag; + STATE->rngTrialOffset = 0; STATE->runTrial = 0; STATE->expectedRatio = Q_4_12(passes) / trials; STATE->observedRatio = 0;