pokerogue/test/escape-calculations.test.ts
Sirz Benjie 5854b21da0
[Refactor] Remove circular imports part 1 (#5663)
* Extract Mode enum out of UI and into its own file

Reduces circular imports from 909 to 773

* Move around utility files

Reduces cyclical dependencies from 773 to 765

* Remove starterColors and bypassLogin from battle-scene

Reduces cyclical dependencies from 765 to 623

* Fix test runner error

* Update import for bypassLogin in test

* Update mocks for utils in tests

* Fix broken tests

* Update selectWithTera override

* Update path for utils in ab-attr.ts

* Update path for utils in ability-class.ts

* Fix utils import path in healer.test.ts
2025-04-19 11:57:03 +00:00

363 lines
18 KiB
TypeScript

import { AttemptRunPhase } from "#app/phases/attempt-run-phase";
import type { CommandPhase } from "#app/phases/command-phase";
import { Command } from "#app/ui/command-ui-handler";
import { NumberHolder } from "#app/utils/common";
import { Abilities } from "#enums/abilities";
import { Species } from "#enums/species";
import GameManager from "#test/testUtils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
describe("Escape chance calculations", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.battleStyle("single")
.enemySpecies(Species.BULBASAUR)
.enemyAbility(Abilities.INSOMNIA)
.ability(Abilities.INSOMNIA);
});
it("single non-boss opponent", async () => {
await game.classicMode.startBattle([Species.BULBASAUR]);
const playerPokemon = game.scene.getPlayerField();
const enemyField = game.scene.getEnemyField();
const enemySpeed = 100;
// set enemyPokemon's speed to 100
vi.spyOn(enemyField[0], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, enemySpeed]);
const commandPhase = game.scene.getCurrentPhase() as CommandPhase;
commandPhase.handleCommand(Command.RUN, 0);
await game.phaseInterceptor.to(AttemptRunPhase, false);
const phase = game.scene.getCurrentPhase() as AttemptRunPhase;
const escapePercentage = new NumberHolder(0);
// this sets up an object for multiple attempts. The pokemonSpeedRatio is your speed divided by the enemy speed, the escapeAttempts are the number of escape attempts and the expectedEscapeChance is the chance it should be escaping
const escapeChances: {
pokemonSpeedRatio: number;
escapeAttempts: number;
expectedEscapeChance: number;
}[] = [
{ pokemonSpeedRatio: 0.01, escapeAttempts: 0, expectedEscapeChance: 5 },
{ pokemonSpeedRatio: 0.1, escapeAttempts: 0, expectedEscapeChance: 7 },
{ pokemonSpeedRatio: 0.25, escapeAttempts: 0, expectedEscapeChance: 11 },
{ pokemonSpeedRatio: 0.5, escapeAttempts: 0, expectedEscapeChance: 16 },
{ pokemonSpeedRatio: 0.8, escapeAttempts: 0, expectedEscapeChance: 23 },
{ pokemonSpeedRatio: 1, escapeAttempts: 0, expectedEscapeChance: 28 },
{ pokemonSpeedRatio: 1.2, escapeAttempts: 0, expectedEscapeChance: 32 },
{ pokemonSpeedRatio: 1.5, escapeAttempts: 0, expectedEscapeChance: 39 },
{ pokemonSpeedRatio: 3, escapeAttempts: 0, expectedEscapeChance: 73 },
{ pokemonSpeedRatio: 3.8, escapeAttempts: 0, expectedEscapeChance: 91 },
{ pokemonSpeedRatio: 4, escapeAttempts: 0, expectedEscapeChance: 95 },
{ pokemonSpeedRatio: 4.2, escapeAttempts: 0, expectedEscapeChance: 95 },
{ pokemonSpeedRatio: 10, escapeAttempts: 0, expectedEscapeChance: 95 },
// retries section
{ pokemonSpeedRatio: 0.4, escapeAttempts: 1, expectedEscapeChance: 24 },
{ pokemonSpeedRatio: 1.6, escapeAttempts: 2, expectedEscapeChance: 61 },
{ pokemonSpeedRatio: 3.7, escapeAttempts: 5, expectedEscapeChance: 95 },
{ pokemonSpeedRatio: 0.2, escapeAttempts: 2, expectedEscapeChance: 30 },
{ pokemonSpeedRatio: 1, escapeAttempts: 3, expectedEscapeChance: 58 },
{ pokemonSpeedRatio: 2.9, escapeAttempts: 0, expectedEscapeChance: 70 },
{ pokemonSpeedRatio: 0.01, escapeAttempts: 7, expectedEscapeChance: 75 },
{ pokemonSpeedRatio: 16.2, escapeAttempts: 4, expectedEscapeChance: 95 },
{ pokemonSpeedRatio: 2, escapeAttempts: 3, expectedEscapeChance: 80 },
];
for (let i = 0; i < escapeChances.length; i++) {
// sets the number of escape attempts to the required amount
game.scene.currentBattle.escapeAttempts = escapeChances[i].escapeAttempts;
// set playerPokemon's speed to a multiple of the enemySpeed
vi.spyOn(playerPokemon[0], "stats", "get").mockReturnValue([
20,
20,
20,
20,
20,
escapeChances[i].pokemonSpeedRatio * enemySpeed,
]);
phase.attemptRunAway(playerPokemon, enemyField, escapePercentage);
expect(escapePercentage.value).toBe(escapeChances[i].expectedEscapeChance);
}
}, 20000);
it("double non-boss opponent", async () => {
game.override.battleStyle("double");
await game.classicMode.startBattle([Species.BULBASAUR, Species.ABOMASNOW]);
const playerPokemon = game.scene.getPlayerField();
const enemyField = game.scene.getEnemyField();
const enemyASpeed = 70;
const enemyBSpeed = 30;
// gets the sum of the speed of the two pokemon
const totalEnemySpeed = enemyASpeed + enemyBSpeed;
// this is used to find the ratio of the player's first pokemon
const playerASpeedPercentage = 0.4;
// set enemyAPokemon's speed to 70
vi.spyOn(enemyField[0], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, enemyASpeed]);
// set enemyBPokemon's speed to 30
vi.spyOn(enemyField[1], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, enemyBSpeed]);
const commandPhase = game.scene.getCurrentPhase() as CommandPhase;
commandPhase.handleCommand(Command.RUN, 0);
await game.phaseInterceptor.to(AttemptRunPhase, false);
const phase = game.scene.getCurrentPhase() as AttemptRunPhase;
const escapePercentage = new NumberHolder(0);
// this sets up an object for multiple attempts. The pokemonSpeedRatio is your speed divided by the enemy speed, the escapeAttempts are the number of escape attempts and the expectedEscapeChance is the chance it should be escaping
const escapeChances: {
pokemonSpeedRatio: number;
escapeAttempts: number;
expectedEscapeChance: number;
}[] = [
{ pokemonSpeedRatio: 0.3, escapeAttempts: 0, expectedEscapeChance: 12 },
{ pokemonSpeedRatio: 0.7, escapeAttempts: 0, expectedEscapeChance: 21 },
{ pokemonSpeedRatio: 1.5, escapeAttempts: 0, expectedEscapeChance: 39 },
{ pokemonSpeedRatio: 3, escapeAttempts: 0, expectedEscapeChance: 73 },
{ pokemonSpeedRatio: 9, escapeAttempts: 0, expectedEscapeChance: 95 },
{ pokemonSpeedRatio: 0.01, escapeAttempts: 0, expectedEscapeChance: 5 },
{ pokemonSpeedRatio: 1, escapeAttempts: 0, expectedEscapeChance: 28 },
{ pokemonSpeedRatio: 4.3, escapeAttempts: 0, expectedEscapeChance: 95 },
{ pokemonSpeedRatio: 2.7, escapeAttempts: 0, expectedEscapeChance: 66 },
{ pokemonSpeedRatio: 2.1, escapeAttempts: 0, expectedEscapeChance: 52 },
{ pokemonSpeedRatio: 1.8, escapeAttempts: 0, expectedEscapeChance: 46 },
{ pokemonSpeedRatio: 6, escapeAttempts: 0, expectedEscapeChance: 95 },
// retries section
{ pokemonSpeedRatio: 0.9, escapeAttempts: 1, expectedEscapeChance: 35 },
{ pokemonSpeedRatio: 3.6, escapeAttempts: 2, expectedEscapeChance: 95 },
{ pokemonSpeedRatio: 0.03, escapeAttempts: 7, expectedEscapeChance: 76 },
{ pokemonSpeedRatio: 0.02, escapeAttempts: 7, expectedEscapeChance: 75 },
{ pokemonSpeedRatio: 1, escapeAttempts: 5, expectedEscapeChance: 78 },
{ pokemonSpeedRatio: 0.7, escapeAttempts: 3, expectedEscapeChance: 51 },
{ pokemonSpeedRatio: 2.4, escapeAttempts: 9, expectedEscapeChance: 95 },
{ pokemonSpeedRatio: 1.8, escapeAttempts: 7, expectedEscapeChance: 95 },
{ pokemonSpeedRatio: 2, escapeAttempts: 10, expectedEscapeChance: 95 },
];
for (let i = 0; i < escapeChances.length; i++) {
// sets the number of escape attempts to the required amount
game.scene.currentBattle.escapeAttempts = escapeChances[i].escapeAttempts;
// set the first playerPokemon's speed to a multiple of the enemySpeed
vi.spyOn(playerPokemon[0], "stats", "get").mockReturnValue([
20,
20,
20,
20,
20,
Math.floor(escapeChances[i].pokemonSpeedRatio * totalEnemySpeed * playerASpeedPercentage),
]);
// set the second playerPokemon's speed to the remaining value of speed
vi.spyOn(playerPokemon[1], "stats", "get").mockReturnValue([
20,
20,
20,
20,
20,
escapeChances[i].pokemonSpeedRatio * totalEnemySpeed - playerPokemon[0].stats[5],
]);
phase.attemptRunAway(playerPokemon, enemyField, escapePercentage);
// checks to make sure the escape values are the same
expect(escapePercentage.value).toBe(escapeChances[i].expectedEscapeChance);
// checks to make sure the sum of the player's speed for all pokemon is equal to the appropriate ratio of the total enemy speed
expect(playerPokemon[0].stats[5] + playerPokemon[1].stats[5]).toBe(
escapeChances[i].pokemonSpeedRatio * totalEnemySpeed,
);
}
}, 20000);
it("single boss opponent", async () => {
game.override.startingWave(10);
await game.classicMode.startBattle([Species.BULBASAUR]);
const playerPokemon = game.scene.getPlayerField()!;
const enemyField = game.scene.getEnemyField()!;
const enemySpeed = 100;
// set enemyPokemon's speed to 100
vi.spyOn(enemyField[0], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, enemySpeed]);
const commandPhase = game.scene.getCurrentPhase() as CommandPhase;
commandPhase.handleCommand(Command.RUN, 0);
await game.phaseInterceptor.to(AttemptRunPhase, false);
const phase = game.scene.getCurrentPhase() as AttemptRunPhase;
const escapePercentage = new NumberHolder(0);
// this sets up an object for multiple attempts. The pokemonSpeedRatio is your speed divided by the enemy speed, the escapeAttempts are the number of escape attempts and the expectedEscapeChance is the chance it should be escaping
const escapeChances: {
pokemonSpeedRatio: number;
escapeAttempts: number;
expectedEscapeChance: number;
}[] = [
{ pokemonSpeedRatio: 0.01, escapeAttempts: 0, expectedEscapeChance: 5 },
{ pokemonSpeedRatio: 0.1, escapeAttempts: 0, expectedEscapeChance: 5 },
{ pokemonSpeedRatio: 0.25, escapeAttempts: 0, expectedEscapeChance: 6 },
{ pokemonSpeedRatio: 0.5, escapeAttempts: 0, expectedEscapeChance: 7 },
{ pokemonSpeedRatio: 0.8, escapeAttempts: 0, expectedEscapeChance: 8 },
{ pokemonSpeedRatio: 1, escapeAttempts: 0, expectedEscapeChance: 8 },
{ pokemonSpeedRatio: 1.2, escapeAttempts: 0, expectedEscapeChance: 9 },
{ pokemonSpeedRatio: 1.5, escapeAttempts: 0, expectedEscapeChance: 10 },
{ pokemonSpeedRatio: 3, escapeAttempts: 0, expectedEscapeChance: 15 },
{ pokemonSpeedRatio: 3.8, escapeAttempts: 0, expectedEscapeChance: 18 },
{ pokemonSpeedRatio: 4, escapeAttempts: 0, expectedEscapeChance: 18 },
{ pokemonSpeedRatio: 4.2, escapeAttempts: 0, expectedEscapeChance: 19 },
{ pokemonSpeedRatio: 4.7, escapeAttempts: 0, expectedEscapeChance: 21 },
{ pokemonSpeedRatio: 5, escapeAttempts: 0, expectedEscapeChance: 22 },
{ pokemonSpeedRatio: 5.9, escapeAttempts: 0, expectedEscapeChance: 25 },
{ pokemonSpeedRatio: 6, escapeAttempts: 0, expectedEscapeChance: 25 },
{ pokemonSpeedRatio: 6.7, escapeAttempts: 0, expectedEscapeChance: 25 },
{ pokemonSpeedRatio: 10, escapeAttempts: 0, expectedEscapeChance: 25 },
// retries section
{ pokemonSpeedRatio: 0.4, escapeAttempts: 1, expectedEscapeChance: 8 },
{ pokemonSpeedRatio: 1.6, escapeAttempts: 2, expectedEscapeChance: 14 },
{ pokemonSpeedRatio: 3.7, escapeAttempts: 5, expectedEscapeChance: 25 },
{ pokemonSpeedRatio: 0.2, escapeAttempts: 2, expectedEscapeChance: 10 },
{ pokemonSpeedRatio: 1, escapeAttempts: 3, expectedEscapeChance: 14 },
{ pokemonSpeedRatio: 2.9, escapeAttempts: 0, expectedEscapeChance: 15 },
{ pokemonSpeedRatio: 0.01, escapeAttempts: 7, expectedEscapeChance: 19 },
{ pokemonSpeedRatio: 16.2, escapeAttempts: 4, expectedEscapeChance: 25 },
{ pokemonSpeedRatio: 2, escapeAttempts: 3, expectedEscapeChance: 18 },
{ pokemonSpeedRatio: 4.5, escapeAttempts: 1, expectedEscapeChance: 22 },
{ pokemonSpeedRatio: 6.8, escapeAttempts: 6, expectedEscapeChance: 25 },
{ pokemonSpeedRatio: 5.2, escapeAttempts: 8, expectedEscapeChance: 25 },
{ pokemonSpeedRatio: 4.7, escapeAttempts: 10, expectedEscapeChance: 25 },
{ pokemonSpeedRatio: 5.1, escapeAttempts: 1, expectedEscapeChance: 24 },
{ pokemonSpeedRatio: 6, escapeAttempts: 0, expectedEscapeChance: 25 },
{ pokemonSpeedRatio: 5.9, escapeAttempts: 2, expectedEscapeChance: 25 },
{ pokemonSpeedRatio: 6.1, escapeAttempts: 3, expectedEscapeChance: 25 },
];
for (let i = 0; i < escapeChances.length; i++) {
// sets the number of escape attempts to the required amount
game.scene.currentBattle.escapeAttempts = escapeChances[i].escapeAttempts;
// set playerPokemon's speed to a multiple of the enemySpeed
vi.spyOn(playerPokemon[0], "stats", "get").mockReturnValue([
20,
20,
20,
20,
20,
escapeChances[i].pokemonSpeedRatio * enemySpeed,
]);
phase.attemptRunAway(playerPokemon, enemyField, escapePercentage);
expect(escapePercentage.value).toBe(escapeChances[i].expectedEscapeChance);
}
}, 20000);
it("double boss opponent", async () => {
game.override.battleStyle("double");
game.override.startingWave(10);
await game.classicMode.startBattle([Species.BULBASAUR, Species.ABOMASNOW]);
const playerPokemon = game.scene.getPlayerField();
const enemyField = game.scene.getEnemyField();
const enemyASpeed = 70;
const enemyBSpeed = 30;
// gets the sum of the speed of the two pokemon
const totalEnemySpeed = enemyASpeed + enemyBSpeed;
// this is used to find the ratio of the player's first pokemon
const playerASpeedPercentage = 0.8;
// set enemyAPokemon's speed to 70
vi.spyOn(enemyField[0], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, enemyASpeed]);
// set enemyBPokemon's speed to 30
vi.spyOn(enemyField[1], "stats", "get").mockReturnValue([20, 20, 20, 20, 20, enemyBSpeed]);
const commandPhase = game.scene.getCurrentPhase() as CommandPhase;
commandPhase.handleCommand(Command.RUN, 0);
await game.phaseInterceptor.to(AttemptRunPhase, false);
const phase = game.scene.getCurrentPhase() as AttemptRunPhase;
const escapePercentage = new NumberHolder(0);
// this sets up an object for multiple attempts. The pokemonSpeedRatio is your speed divided by the enemy speed, the escapeAttempts are the number of escape attempts and the expectedEscapeChance is the chance it should be escaping
const escapeChances: {
pokemonSpeedRatio: number;
escapeAttempts: number;
expectedEscapeChance: number;
}[] = [
{ pokemonSpeedRatio: 0.3, escapeAttempts: 0, expectedEscapeChance: 6 },
{ pokemonSpeedRatio: 0.7, escapeAttempts: 0, expectedEscapeChance: 7 },
{ pokemonSpeedRatio: 1.5, escapeAttempts: 0, expectedEscapeChance: 10 },
{ pokemonSpeedRatio: 3, escapeAttempts: 0, expectedEscapeChance: 15 },
{ pokemonSpeedRatio: 9, escapeAttempts: 0, expectedEscapeChance: 25 },
{ pokemonSpeedRatio: 0.01, escapeAttempts: 0, expectedEscapeChance: 5 },
{ pokemonSpeedRatio: 1, escapeAttempts: 0, expectedEscapeChance: 8 },
{ pokemonSpeedRatio: 4.3, escapeAttempts: 0, expectedEscapeChance: 19 },
{ pokemonSpeedRatio: 2.7, escapeAttempts: 0, expectedEscapeChance: 14 },
{ pokemonSpeedRatio: 2.1, escapeAttempts: 0, expectedEscapeChance: 12 },
{ pokemonSpeedRatio: 1.8, escapeAttempts: 0, expectedEscapeChance: 11 },
{ pokemonSpeedRatio: 6, escapeAttempts: 0, expectedEscapeChance: 25 },
{ pokemonSpeedRatio: 4, escapeAttempts: 0, expectedEscapeChance: 18 },
{ pokemonSpeedRatio: 5.7, escapeAttempts: 0, expectedEscapeChance: 24 },
{ pokemonSpeedRatio: 5, escapeAttempts: 0, expectedEscapeChance: 22 },
{ pokemonSpeedRatio: 6.1, escapeAttempts: 0, expectedEscapeChance: 25 },
{ pokemonSpeedRatio: 6.8, escapeAttempts: 0, expectedEscapeChance: 25 },
{ pokemonSpeedRatio: 10, escapeAttempts: 0, expectedEscapeChance: 25 },
// retries section
{ pokemonSpeedRatio: 0.9, escapeAttempts: 1, expectedEscapeChance: 10 },
{ pokemonSpeedRatio: 3.6, escapeAttempts: 2, expectedEscapeChance: 21 },
{ pokemonSpeedRatio: 0.03, escapeAttempts: 7, expectedEscapeChance: 19 },
{ pokemonSpeedRatio: 0.02, escapeAttempts: 7, expectedEscapeChance: 19 },
{ pokemonSpeedRatio: 1, escapeAttempts: 5, expectedEscapeChance: 18 },
{ pokemonSpeedRatio: 0.7, escapeAttempts: 3, expectedEscapeChance: 13 },
{ pokemonSpeedRatio: 2.4, escapeAttempts: 9, expectedEscapeChance: 25 },
{ pokemonSpeedRatio: 1.8, escapeAttempts: 7, expectedEscapeChance: 25 },
{ pokemonSpeedRatio: 2, escapeAttempts: 10, expectedEscapeChance: 25 },
{ pokemonSpeedRatio: 3, escapeAttempts: 1, expectedEscapeChance: 17 },
{ pokemonSpeedRatio: 4.5, escapeAttempts: 3, expectedEscapeChance: 25 },
{ pokemonSpeedRatio: 3.7, escapeAttempts: 1, expectedEscapeChance: 19 },
{ pokemonSpeedRatio: 6.5, escapeAttempts: 1, expectedEscapeChance: 25 },
{ pokemonSpeedRatio: 12, escapeAttempts: 4, expectedEscapeChance: 25 },
{ pokemonSpeedRatio: 5.2, escapeAttempts: 2, expectedEscapeChance: 25 },
];
for (let i = 0; i < escapeChances.length; i++) {
// sets the number of escape attempts to the required amount
game.scene.currentBattle.escapeAttempts = escapeChances[i].escapeAttempts;
// set the first playerPokemon's speed to a multiple of the enemySpeed
vi.spyOn(playerPokemon[0], "stats", "get").mockReturnValue([
20,
20,
20,
20,
20,
Math.floor(escapeChances[i].pokemonSpeedRatio * totalEnemySpeed * playerASpeedPercentage),
]);
// set the second playerPokemon's speed to the remaining value of speed
vi.spyOn(playerPokemon[1], "stats", "get").mockReturnValue([
20,
20,
20,
20,
20,
escapeChances[i].pokemonSpeedRatio * totalEnemySpeed - playerPokemon[0].stats[5],
]);
phase.attemptRunAway(playerPokemon, enemyField, escapePercentage);
// checks to make sure the escape values are the same
expect(escapePercentage.value).toBe(escapeChances[i].expectedEscapeChance);
// checks to make sure the sum of the player's speed for all pokemon is equal to the appropriate ratio of the total enemy speed
expect(playerPokemon[0].stats[5] + playerPokemon[1].stats[5]).toBe(
escapeChances[i].pokemonSpeedRatio * totalEnemySpeed,
);
}
}, 20000);
});