mirror of
https://github.com/smogon/pokemon-showdown.git
synced 2026-05-21 23:15:44 -05:00
This enables battles in tests to reset their RNG to what it originally
was when they were created. A number of tests do this already by
breaking encapsulating and modifying the prng variable directly. This
should fix that.
We also remove createBattleWithPRNG and min/maxSeed properties.
Firstly, the tests that were still using the maxSeed property were
setting it to Battle.seed which has no effect since the PRNG class does
not look at this property any more - so these were no-ops.
After removing this property from tests, maxRollSeed was never used, so
I removed it and renamed minRollSeed to DEFAULT_ROLL_SEED (since it's
constant).
The places that were still using minRollSeed also were no-ops or were
using createBattleWithPRNG. Since every single instance was actually
just using the same code, I removed createBattleWithPRNG and made
createBattle default to giving you a seed with DEFAULT_ROLL_SEED and
removed the minRollSeed property too.
This makes tests much simpler and reduces the usage surface of
TestTools; now, you must define your *own* seed if you're making a PRNG
for a test, or you use one that TestTools gives you; you may not use a
public property that TestTools gives you.
We also remove the code that removes a listener in the Battle Engine.
This was rendered obsolete by f6a7c4b (see more info [in the discussion
on github][disc]
[disc]:
https://github.com/Zarel/Pokemon-Showdown/pull/3272#discussion_r102414795
135 lines
4.0 KiB
JavaScript
135 lines
4.0 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('assert');
|
|
const Tools = require('./../tools').includeFormats();
|
|
const PRNG = require('./../prng');
|
|
const MockBattle = require('./mocks/Battle');
|
|
|
|
let battleNum = 1;
|
|
const cache = new Map();
|
|
|
|
const RULE_FLAGS = {
|
|
pokemon: 1,
|
|
legality: 2,
|
|
preview: 4,
|
|
sleepClause: 8,
|
|
cancel: 16,
|
|
partialDecisions: 32,
|
|
};
|
|
|
|
function capitalize(word) {
|
|
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
}
|
|
|
|
/**
|
|
* The default random number generator seed used if one is not given.
|
|
*/
|
|
const DEFAULT_SEED = [0x09917, 0x06924, 0x0e1c8, 0x06af0];
|
|
|
|
class TestTools {
|
|
constructor(options) {
|
|
if (!options) options = {};
|
|
|
|
const mod = options.mod || 'base';
|
|
this.baseFormat = options.baseFormat || {effectType: 'Format', mod: mod};
|
|
this.tools = Tools.mod(mod);
|
|
|
|
this.modPrefix = this.baseFormat.name ? `[${this.baseFormat.name}]` : '';
|
|
if (!this.modPrefix && !this.tools.isBase) {
|
|
this.modPrefix = (/^gen\d$/.test(mod) ? `[Gen ${this.tools.gen}]` : `[${mod}]`);
|
|
}
|
|
|
|
// Handle caches
|
|
this.formats = new Map([['singles', new Map()], ['doubles', new Map()], ['triples', new Map()]]);
|
|
cache.set(this.baseFormat.id || mod, this);
|
|
}
|
|
|
|
mod(mod) {
|
|
if (cache.has(mod)) return cache.get(mod);
|
|
if (Tools.dexes[mod]) return new TestTools({mod: mod});
|
|
const baseFormat = Tools.getFormat(mod);
|
|
if (baseFormat.effectType === 'Format') return new TestTools({mod: baseFormat.mod, baseFormat});
|
|
throw new Error(`Mod ${mod} does not exist`);
|
|
}
|
|
|
|
gen(genNum) {
|
|
return this.mod('gen' + genNum);
|
|
}
|
|
|
|
getFormat(options) {
|
|
let mask = 0;
|
|
for (let property in options) {
|
|
if (property === 'gameType' || !options[property]) continue;
|
|
mask |= RULE_FLAGS[property];
|
|
}
|
|
const gameType = Tools.getId(options.gameType || 'singles');
|
|
if (this.formats.get(gameType).has(mask)) return this.formats.get(gameType).get(mask);
|
|
|
|
const gameTypePrefix = gameType === 'singles' ? '' : capitalize(gameType);
|
|
const formatName = [this.modPrefix, gameTypePrefix, "Custom Game", '' + mask].filter(part => part).join(" ");
|
|
const formatId = Tools.getId(formatName);
|
|
|
|
const format = Object.assign(Object.assign({}, this.baseFormat), {
|
|
id: formatId,
|
|
name: formatName,
|
|
|
|
mask: mask,
|
|
gameType: options.gameType || 'singles',
|
|
isCustomGameFormat: true,
|
|
|
|
rated: false,
|
|
});
|
|
if (!format.ruleset) format.ruleset = [];
|
|
if (!format.banlist) format.banlist = [];
|
|
|
|
if (options.pokemon) format.ruleset.push('Pokemon');
|
|
if (options.legality) format.banlist.push('Illegal', 'Unreleased');
|
|
if (options.preview) format.ruleset.push('Team Preview');
|
|
if (options.sleepClause) format.ruleset.push('Sleep Clause Mod');
|
|
if (options.cancel) format.ruleset.push('Cancel Mod');
|
|
// if (options.partialDecisions) format.ruleset.push('Partial Decisions');
|
|
|
|
this.tools.installFormat(formatId, format);
|
|
return format;
|
|
}
|
|
|
|
/**
|
|
* Creates a new Battle and returns it.
|
|
*
|
|
* @param {Object} [options]
|
|
* @param {Team[]} [teams]
|
|
* @param {PRNG} [prng] A pseudo-random number generator. If not provided, a pseudo-random number
|
|
* generator will be generated for the user with a seed that is guaranteed to be the same across
|
|
* test executions to help with determinism.
|
|
* @returns {MockBattle} A mocked battle. This has mostly the same behaviour as regular Battles,
|
|
* but some behaviour may differ specifically for testing.
|
|
*/
|
|
createBattle(options, teams, prng = new PRNG(DEFAULT_SEED)) {
|
|
if (Array.isArray(options)) {
|
|
teams = options;
|
|
options = {};
|
|
}
|
|
const format = this.getFormat(options || {});
|
|
const battle = MockBattle.construct(
|
|
`battle-test-${battleNum++}`,
|
|
format.id,
|
|
undefined,
|
|
undefined,
|
|
prng
|
|
);
|
|
if (options && options.partialDecisions) battle.supportPartialDecisions = true;
|
|
if (teams) {
|
|
for (let i = 0; i < teams.length; i++) {
|
|
assert(Array.isArray(teams[i]), "Team provided is not an array");
|
|
const slotNum = i + 1;
|
|
battle.join('p' + slotNum, 'Guest ' + slotNum, 1, teams[i]);
|
|
}
|
|
}
|
|
return battle;
|
|
}
|
|
}
|
|
|
|
const common = exports = module.exports = new TestTools();
|
|
cache.set('base', common);
|
|
cache.set('gen7', common);
|