Skip to content

Commit

Permalink
Remove misleading KO chance outputs (#536)
Browse files Browse the repository at this point in the history
This removes a good amount of the text duplication in getKOChance() , but really the point is to resolve #448: where previously 100% chance to be KOed would be returned will instead yield 99.9%, and 0% will instead show 0.1%.

I also added a test for the specific case mentioned. I can't think of an example for 0%, but I have seen it before.
  • Loading branch information
Zrp200 authored Mar 5, 2024
1 parent f840e76 commit 168db24
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 47 deletions.
74 changes: 27 additions & 47 deletions calc/src/desc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,20 +287,28 @@ export function getKOChance(
? ' after ' + serializeText(hazards.texts.concat(eot.texts))
: '';

function KOChance(
chance: number | undefined,
n: number,
multipleTurns = false,
) {
if (chance === 0) return {chance: undefined, n, text: qualifier + 'not a KO'};
let text = chance === undefined ? qualifier + 'possible '
: chance === 1 ? qualifier || 'guaranteed '
// prevent displaying misleading 100% or 0% chances
: `${qualifier}${Math.max(Math.min(Math.round(chance * 1000), 999), 1) / 10}% chance to `;
// using the number of hits we can determine the type of KO we are checking for
text += n === 1 ? 'OHKO' + hazardsText
: (multipleTurns ? `KO in ${n} turns` : `${n}HKO`) + afterText;
return {chance, n, text};
}

if ((move.timesUsed === 1 && move.timesUsedWithMetronome === 1) || move.isZ) {
const chance = computeKOChance(
damage, defender.curHP() - hazards.damage, 0, 1, 1, defender.maxHP(), toxicCounter
);
if (chance === 1) {
return {chance, n: 1, text: `guaranteed OHKO${hazardsText}`}; // eot wasn't considered
} else if (chance > 0) {
// note: still not accounting for EOT due to poor eot damage handling
return {
chance,
n: 1,
text: qualifier + Math.round(chance * 1000) / 10 + `% chance to OHKO${hazardsText}`,
};
}
// note: still not accounting for EOT due to poor eot damage handling
if (chance > 0) return KOChance(chance, 1);

// Parental Bond's combined first + second hit only is accurate for chance to OHKO, for
// multihit KOs its only approximated. We should be doing squashMultihit here instead of
Expand All @@ -315,28 +323,21 @@ export function getKOChance(
const chance = computeKOChance(
damage, defender.curHP() - hazards.damage, eot.damage, i, 1, defender.maxHP(), toxicCounter
);
if (chance === 1) {
return {chance, n: i, text: `${qualifier || 'guaranteed '}${i}HKO${afterText}`};
} else if (chance > 0) {
return {
chance,
n: i,
text: qualifier + Math.round(chance * 1000) / 10 + `% chance to ${i}HKO${afterText}`,
};
}
if (chance > 0) return KOChance(chance, i);
}

for (let i = 5; i <= 9; i++) {
if (
predictTotal(damage[0], eot.damage, i, 1, toxicCounter, defender.maxHP()) >=
defender.curHP() - hazards.damage
) {
return {chance: 1, n: i, text: `${qualifier || 'guaranteed '}${i}HKO${afterText}`};
return KOChance(1, i);
} else if (
predictTotal(damage[damage.length - 1], eot.damage, i, 1, toxicCounter, defender.maxHP()) >=
defender.curHP() - hazards.damage
) {
return {n: i, text: qualifier + `possible ${i}HKO${afterText}`};
// possible but no concrete chance
return KOChance(undefined, i);
}
}
} else {
Expand All @@ -348,22 +349,7 @@ export function getKOChance(
defender.maxHP(),
toxicCounter
);
if (chance === 1) {
return {
chance,
n: move.timesUsed,
text: `${qualifier || 'guaranteed '}KO in ${move.timesUsed} turns${afterText}`,
};
} else if (chance > 0) {
return {
chance,
n: move.timesUsed,
text:
qualifier +
Math.round(chance * 1000) / 10 +
`% chance to ${move.timesUsed}HKO${afterText}`,
};
}
if (chance > 0) return KOChance(chance, move.timesUsed, chance === 1);

if (predictTotal(
damage[0],
Expand All @@ -375,11 +361,7 @@ export function getKOChance(
) >=
defender.curHP() - hazards.damage
) {
return {
chance: 1,
n: move.timesUsed,
text: `${qualifier || 'guaranteed '}KO in ${move.timesUsed} turns${afterText}`,
};
return KOChance(1, move.timesUsed, true);
} else if (
predictTotal(
damage[damage.length - 1],
Expand All @@ -391,12 +373,10 @@ export function getKOChance(
) >=
defender.curHP() - hazards.damage
) {
return {
n: move.timesUsed,
text: qualifier + `possible KO in ${move.timesUsed} turns${afterText}`,
};
// possible but no real idea
return KOChance(undefined, move.timesUsed, true);
}
return {n: move.timesUsed, text: qualifier + 'not a KO'};
return KOChance(0, move.timesUsed);
}

return {chance: 0, n: 0, text: ''};
Expand Down
22 changes: 22 additions & 0 deletions calc/src/test/calc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1210,5 +1210,27 @@ describe('calc', () => {
);
});
});
describe('Descriptions', () => {
inGen(9, ({gen, calculate, Pokemon, Move}) => {
test('displayed chances should not round to 100%', () => {
const result = calculate(
Pokemon('Xerneas', {item: 'Choice Band', nature: 'Adamant', evs: {atk: 252}}),
Pokemon('Necrozma-Dusk-Mane', {nature: 'Impish', evs: {hp: 252, def: 252}}),
Move('Close Combat')
);
expect(result.kochance().chance).toBeGreaterThanOrEqual(0.9995);
expect(result.kochance().text).toBe('99.9% chance to 3HKO');
});
test('displayed chances should not round to 0%', () => {
const result = calculate(
Pokemon('Deoxys-Attack', {evs: {spa: 44}}),
Pokemon('Blissey', {nature: 'Calm', evs: {hp: 252, spd: 252}}),
Move('Psycho Boost')
);
expect(result.kochance().chance).toBeLessThan(0.005); // it would round down.
expect(result.kochance().text).toBe('0.1% chance to 4HKO');
});
});
});
});
});

0 comments on commit 168db24

Please sign in to comment.