pokemon-showdown/test/sim/choice-parser.js
Guangcong Luo 78439b4a02
Update to ESLint 9 (#10926)
ESLint has a whole new config format, so I figure it's a good time to
make the config system saner.

- First, we no longer have separate eslint-no-types configs. Lint
  performance shouldn't be enough of a problem to justify the
  relevant maintenance complexity.

- Second, our base config should work out-of-the-box now. `npx eslint`
  will work as expected, without any CLI flags. You should still use
  `npm run lint` which adds the `--cached` flag for performance.

- Third, whatever updates I did fixed style linting, which apparently
  has been bugged for quite some time, considering all the obvious
  mixed-tabs-and-spaces issues I found in the upgrade.

Also here are some changes to our style rules. In particular:

- Curly brackets (for objects etc) now have spaces inside them. Sorry
  for the huge change. ESLint doesn't support our old style, and most
  projects use Prettier style, so we might as well match them in this way.
  See https://github.com/eslint-stylistic/eslint-stylistic/issues/415

- String + number concatenation is no longer allowed. We now
  consistently use template strings for this.
2025-02-25 20:03:46 -08:00

405 lines
16 KiB
JavaScript

'use strict';
const assert = require('./../assert');
const common = require('./../common');
let battle;
describe('Choice parser', () => {
afterEach(() => battle.destroy());
describe('Team Preview requests', () => {
it('should accept only `team` choices', () => {
battle = common.createBattle({ preview: true }, [
[{ species: "Mew", ability: 'synchronize', moves: ['recover'] }],
[{ species: "Rhydon", ability: 'prankster', moves: ['splash'] }],
]);
const validChoice = 'team 1';
assert(battle.choose('p1', validChoice));
battle.p1.clearChoice();
assert(battle.choose('p2', validChoice));
battle.p1.clearChoice();
const badChoices = ['move 1', 'move 2 mega', 'switch 1', 'pass', 'shift'];
for (const badChoice of badChoices) {
assert.throws(() => battle.choose('p1', badChoice));
}
});
it('should reject non-numerical choice details', () => {
battle = common.createBattle({ preview: true }, [
[{ species: "Mew", ability: 'synchronize', moves: ['recover'] }],
[{ species: "Rhydon", ability: 'prankster', moves: ['splash'] }],
]);
for (const side of battle.sides) {
assert.throws(() => battle.choose(side.id, 'team Rhydon'));
assert.throws(() => battle.choose(side.id, 'team Mew'));
assert.throws(() => battle.choose(side.id, 'team first'));
}
});
it('should reject zero-based choice details', () => {
battle = common.createBattle({ preview: true }, [
[{ species: "Mew", ability: 'synchronize', moves: ['recover'] }],
[{ species: "Rhydon", ability: 'prankster', moves: ['splash'] }],
]);
for (const side of battle.sides) {
assert.throws(
() => battle.choose(side.id, 'team 0'),
/\[Invalid choice\] Can't choose for Team Preview:/i,
`Input should have been rejected`
);
}
});
});
describe('Switch requests', () => {
describe('Generic', () => {
it('should reject non-numerical input for `switch` choices', () => {
battle = common.createBattle();
battle.setPlayer('p1', { team: [
{ species: "Mew", ability: 'synchronize', moves: ['lunardance'] },
{ species: "Bulbasaur", ability: 'overgrow', moves: ['tackle', 'growl'] },
] });
battle.setPlayer('p2', { team: [{ species: "Rhydon", ability: 'prankster', moves: ['splash'] }] });
battle.makeChoices('move lunardance', 'move splash');
assert.throws(() => battle.choose('p1', 'switch first'));
assert.throws(() => battle.choose('p1', 'switch second'));
});
});
describe('Singles', () => {
it('should accept only `switch` choices', () => {
battle = common.createBattle();
battle.setPlayer('p1', { team: [
{ species: "Mew", ability: 'synchronize', moves: ['lunardance'] },
{ species: "Bulbasaur", ability: 'overgrow', moves: ['tackle', 'growl'] },
] });
battle.setPlayer('p2', { team: [{ species: "Rhydon", ability: 'prankster', moves: ['splash'] }] });
battle.makeChoices('move lunardance', 'move splash');
const badChoices = ['move 1', 'move 2 mega', 'team 1', 'pass', 'shift'];
for (const badChoice of badChoices) {
assert.throws(() => battle.p1.choose(badChoice));
}
const validChoice = 'switch Bulbasaur';
assert(battle.p1.choose(validChoice));
battle.p1.clearChoice();
});
});
describe('Doubles/Triples', () => {
it('should accept only `switch` and `pass` choices', () => {
battle = common.createBattle({ gameType: 'doubles' });
battle.setPlayer('p1', { team: [
{ species: "Pineco", ability: 'sturdy', moves: ['selfdestruct'] },
{ species: "Geodude", ability: 'sturdy', moves: ['selfdestruct'] },
{ species: "Koffing", ability: 'levitate', moves: ['smog'] },
] });
battle.setPlayer('p2', { team: [
{ species: "Skarmory", ability: 'sturdy', moves: ['roost'] },
{ species: "Aggron", ability: 'sturdy', moves: ['irondefense'] },
{ species: "Ekans", ability: 'shedskin', moves: ['wrap'] },
] });
battle.makeChoices('move selfdestruct, move selfdestruct', 'move roost, move irondefense'); // Both p1 active Pokémon faint
const badChoices = ['move 1', 'move 2 mega', 'team 1', 'shift'];
for (const badChoice of badChoices) {
assert.throws(() => battle.p1.choose(badChoice));
}
assert(battle.p1.choose(`pass, switch 3`), `Choice 'pass, switch 3' should be valid`);
});
it('should reject choice details for `pass` choices', () => {
battle = common.createBattle({ gameType: 'doubles' });
battle.setPlayer('p1', { team: [
{ species: "Pineco", ability: 'sturdy', moves: ['selfdestruct'] },
{ species: "Geodude", ability: 'sturdy', moves: ['selfdestruct'] },
{ species: "Koffing", ability: 'levitate', moves: ['smog'] },
] });
battle.setPlayer('p2', { team: [
{ species: "Skarmory", ability: 'sturdy', moves: ['roost'] },
{ species: "Aggron", ability: 'sturdy', moves: ['irondefense'] },
{ species: "Ekans", ability: 'shedskin', moves: ['wrap'] },
] });
battle.makeChoices('move selfdestruct, move selfdestruct', 'move roost, move irondefense'); // Both p1 active Pokémon faint
const switchChoice = 'switch 3';
const passChoice = 'pass';
assert.throws(() => battle.choose('p1', `${switchChoice}, ${passChoice} 1`));
assert.throws(() => battle.choose('p1', `${passChoice} 1, ${switchChoice}`));
assert.throws(() => battle.choose('p1', `${switchChoice}, ${passChoice} a`));
assert.throws(() => battle.choose('p1', `${passChoice} a, ${switchChoice}`));
});
it.skip(`should only allow switching to left slots on double KOs with only one Pokemon remaining`, () => {
battle = common.createBattle({ gameType: 'doubles' }, [[
{ species: 'tornadus', moves: ['sleeptalk'] },
{ species: 'landorus', moves: ['earthquake'] },
], [
{ species: 'roggenrola', level: 1, moves: ['sleeptalk'] },
{ species: 'aron', level: 1, moves: ['sleeptalk'] },
{ species: 'wynaut', moves: ['sleeptalk'] },
]]);
battle.makeChoices();
assert.throws(() => battle.choose('p2', 'pass, switch 3'), 'Wynaut should only be able to switch on the left.');
});
});
});
describe('Move requests', () => {
describe('Generic', () => {
it('should reject `pass` choices for non-fainted Pokémon', () => {
battle = common.createBattle();
battle.setPlayer('p1', { team: [{ species: "Mew", ability: 'synchronize', moves: ['recover'] }] });
battle.setPlayer('p2', { team: [{ species: "Rhydon", ability: 'prankster', moves: ['splash'] }] });
for (const side of battle.sides) {
assert.throws(() => battle.choose(side.id, 'pass'));
}
});
it('should allow mega evolving and targeting in the same move in either order', () => {
battle = common.createBattle({ gameType: 'doubles' });
battle.setPlayer('p1', { team: [
{ species: "Gengar", ability: 'cursedbody', item: 'gengarite', moves: ['shadowball'] },
{ species: "Zigzagoon", ability: 'pickup', moves: ['tackle'] },
] });
battle.setPlayer('p2', { team: [
{ species: "Blaziken", ability: 'blaze', item: 'firiumz', moves: ['blazekick'] },
{ species: "Aggron", ability: 'sturdy', moves: ['irondefense'] },
] });
const badChoices = [`move 1 1 2`, `move 1 1 mega ultra`, `move 1 mega zmove 2`];
for (const badChoice of badChoices) {
const choice = `${badChoice}, move tackle 1`;
assert.throws(() => battle.choose('p1', choice));
}
assert(battle.choose('p1', `move 1 +1 mega, move tackle 1`));
assert(battle.choose('p2', `move Blaze Kick zmove 1, move irondefense`));
});
it('should allow Dynamax use in multiple possible formats', () => {
battle = common.gen(8).createBattle([[
{ species: "Mew", moves: ['psychic'] },
], [
{ species: "Mew", moves: ['psychic'] },
]]);
battle.makeChoices(`move max mindstorm`, `move psychic max`);
assert(battle.p1.active[0].volatiles['dynamax']);
assert(battle.p2.active[0].volatiles['dynamax']);
});
it('should handle Conversion 2', () => {
battle = common.createBattle({ gameType: 'doubles' });
battle.setPlayer('p1', { team: [
{ species: "Porygon-Z", ability: 'adaptability', item: 'normaliumz', moves: ['conversion', 'conversion2'] },
{ species: "Porygon", ability: 'download', moves: ['conversion', 'conversion2'] },
] });
battle.setPlayer('p2', { team: [
{ species: "Gengar", ability: 'cursedbody', moves: ['lick'] },
{ species: "Aggron", ability: 'sturdy', moves: ['irondefense'] },
] });
assert(battle.choose('p1', `move 1, move Conversion 2 2`));
assert.equal(battle.p1.getChoice(), `move conversion, move conversion2 +2`);
battle.p1.clearChoice();
assert.throws(() => battle.choose('p1', `move 1, move Conversion -2`));
battle.p1.clearChoice();
assert(battle.choose('p1', `move Conversion 2 zmove 2, move 1`));
assert.equal(battle.p1.getChoice(), `move conversion2 +2 zmove, move conversion`);
battle.p1.clearChoice();
});
});
describe('Singles', () => {
it('should accept only `move` and `switch` choices', () => {
battle = common.createBattle();
battle.setPlayer('p1', { team: [
{ species: "Mew", ability: 'synchronize', moves: ['lunardance', 'recover'] },
{ species: "Bulbasaur", ability: 'overgrow', moves: ['tackle', 'growl'] },
] });
battle.setPlayer('p2', { team: [
{ species: "Rhydon", ability: 'prankster', moves: ['splash', 'horndrill'] },
{ species: "Charmander", ability: 'blaze', moves: ['tackle', 'growl'] },
] });
const validChoices = ['move 1', 'move 2', 'switch 2'];
for (const action of validChoices) {
assert(battle.choose('p1', action), `Choice '${action}' should be valid`);
battle.p1.clearChoice();
}
const badChoices = ['move 1 zmove', 'move 2 mega', 'team 1', 'pass', 'shift'];
for (const badChoice of badChoices) {
assert.throws(() => battle.choose('p1', badChoice));
}
});
});
describe('Doubles', () => {
it('should enforce `pass` choices for fainted Pokémon', () => {
battle = common.createBattle({ gameType: 'doubles' });
battle.setPlayer('p1', { team: [
{ species: "Pineco", ability: 'sturdy', moves: ['selfdestruct'] },
{ species: "Geodude", ability: 'sturdy', moves: ['selfdestruct'] },
{ species: "Koffing", ability: 'levitate', moves: ['smog'] },
] });
battle.setPlayer('p2', { team: [
{ species: "Skarmory", ability: 'sturdy', moves: ['roost'] },
{ species: "Aggron", ability: 'sturdy', moves: ['irondefense'] },
] });
const p1 = battle.p1;
battle.makeChoices('move selfdestruct, move selfdestruct', 'move roost, move irondefense'); // Both p1 active Pokémon faint
battle.makeChoices('pass, switch 3', ''); // Koffing switches in at slot #2
assert.fainted(p1.active[0]);
assert.species(p1.active[1], 'Koffing');
assert.false.fainted(p1.active[1]);
assert(battle.choose('p1', 'move smog 2'));
assert.equal(battle.p1.getChoice(), `pass, move smog +2`, `Choice mismatch`);
});
});
describe('Triples', () => {
it('should accept only `move` and `switch` choices for a healthy Pokémon on the center', () => {
battle = common.gen(5).createBattle({ gameType: 'triples' });
battle.setPlayer('p1', { team: [
{ species: "Pineco", ability: 'sturdy', moves: ['selfdestruct'] },
{ species: "Geodude", ability: 'sturdy', moves: ['selfdestruct'] },
{ species: "Gastly", ability: 'levitate', moves: ['lick'] },
{ species: "Forretress", ability: 'levitate', moves: ['spikes'] },
] });
battle.setPlayer('p2', { team: [
{ species: "Skarmory", ability: 'sturdy', moves: ['roost'] },
{ species: "Aggron", ability: 'sturdy', moves: ['irondefense'] },
{ species: "Golem", ability: 'sturdy', moves: ['defensecurl'] },
] });
const validChoices = ['move 1', 'switch 4'];
for (const action of validChoices) {
const choiceString = `move 1, ${action}, move 1 1`;
assert(battle.choose('p1', choiceString), `Choice '${choiceString}' should be valid`);
battle.p1.clearChoice();
}
const badChoices = ['move 1 zmove', 'move 2 mega', 'team 1', 'pass', 'shift'];
for (const badChoice of badChoices) {
const choiceString = `move 1, ${badChoice}, move 1 1`;
assert.throws(() => battle.choose('p1', choiceString));
}
});
it('should accept only `move`, `switch` and `shift` choices for a healthy Pokémon on the left', () => {
battle = common.gen(5).createBattle({ gameType: 'triples' });
battle.setPlayer('p1', { team: [
{ species: "Pineco", ability: 'sturdy', moves: ['selfdestruct'] },
{ species: "Geodude", ability: 'sturdy', moves: ['selfdestruct'] },
{ species: "Gastly", ability: 'levitate', moves: ['lick'] },
{ species: "Forretress", ability: 'levitate', moves: ['spikes'] },
] });
battle.setPlayer('p2', { team: [
{ species: "Skarmory", ability: 'sturdy', moves: ['roost'] },
{ species: "Aggron", ability: 'sturdy', moves: ['irondefense'] },
{ species: "Golem", ability: 'sturdy', moves: ['defensecurl'] },
{ species: "Magnezone", ability: 'magnetpull', moves: ['discharge'] },
] });
const validChoices = ['move 1', 'switch 4', 'shift'];
for (const action of validChoices) {
const choiceString = `${action}, move 1, move 1 1`;
assert(battle.choose('p1', choiceString), `Choice '${choiceString}' should be valid`);
battle.p1.clearChoice();
}
const badChoices = ['move 1 zmove', 'move 2 mega', 'team 1', 'pass'];
for (const badChoice of badChoices) {
const choiceString = `${badChoice}, move 1, move 1 1`;
assert.throws(() => battle.choose('p1', choiceString));
}
});
it('should accept only `move`, `switch` and `shift` choices for a healthy Pokémon on the right', () => {
battle = common.gen(5).createBattle({ gameType: 'triples' });
battle.setPlayer('p1', { team: [
{ species: "Pineco", ability: 'sturdy', moves: ['selfdestruct'] },
{ species: "Geodude", ability: 'sturdy', moves: ['selfdestruct'] },
{ species: "Gastly", ability: 'levitate', moves: ['lick'] },
{ species: "Forretress", ability: 'levitate', moves: ['spikes'] },
] });
battle.setPlayer('p2', { team: [
{ species: "Skarmory", ability: 'sturdy', moves: ['roost'] },
{ species: "Aggron", ability: 'sturdy', moves: ['irondefense'] },
{ species: "Golem", ability: 'sturdy', moves: ['defensecurl'] },
{ species: "Magnezone", ability: 'magnetpull', moves: ['discharge'] },
] });
const validChoices = ['move 1 1', 'switch 4', 'shift'];
for (const action of validChoices) {
const choiceString = `move 1, move 1, ${action}`;
assert(battle.choose('p1', choiceString), `Choice '${choiceString}' should be valid`);
battle.p1.clearChoice();
}
const badChoices = ['move 1 zmove', 'move 2 mega', 'team 1', 'pass', 'shift blah'];
for (const badChoice of badChoices) {
const choiceString = `move 1, move 1, ${badChoice}`;
assert.throws(() => battle.choose('p1', choiceString));
}
});
it('should enforce `pass` choices for fainted Pokémon', () => {
battle = common.gen(5).createBattle({ gameType: 'triples' });
battle.setPlayer('p1', { team: [
{ species: "Pineco", ability: 'sturdy', moves: ['selfdestruct'] },
{ species: "Geodude", ability: 'sturdy', moves: ['selfdestruct'] },
{ species: "Gastly", ability: 'levitate', moves: ['lunardance'] },
{ species: "Forretress", ability: 'levitate', moves: ['spikes'] },
] });
battle.setPlayer('p2', { team: [
{ species: "Skarmory", ability: 'sturdy', moves: ['roost'] },
{ species: "Aggron", ability: 'sturdy', moves: ['irondefense'] },
{ species: "Golem", ability: 'sturdy', moves: ['defensecurl'] },
] });
const p1 = battle.p1;
battle.makeChoices('move selfdestruct, move selfdestruct, move lunardance', 'move roost, move irondefense, move defensecurl'); // All p1 active Pokémon faint
battle.makeChoices('pass, switch 4, default', ''); // Forretress switches in to slot #2
assert.species(p1.active[1], 'Forretress');
const validChoices = ['move spikes', 'move 1'];
for (const action of validChoices) {
battle.choose('p1', action);
assert.equal(battle.p1.getChoice(), `pass, move spikes, pass`);
battle.p1.clearChoice();
battle.choose('p1', `pass, ${action}, pass`);
assert.equal(battle.p1.getChoice(), `pass, move spikes, pass`);
battle.p1.clearChoice();
battle.choose('p1', `pass, ${action}`);
assert.equal(battle.p1.getChoice(), `pass, move spikes, pass`);
battle.p1.clearChoice();
battle.choose('p1', `${action}, pass`);
assert.equal(battle.p1.getChoice(), `pass, move spikes, pass`);
battle.p1.clearChoice();
}
});
});
});
});