mirror of
https://github.com/smogon/pokemon-showdown.git
synced 2026-04-26 02:39:38 -05:00
PS's choice system has now been majorly rewritten! Battle#parseChoice has been eliminated, and Battle#choose is now a very lightweight wrapper around the BattleSide#choose* functions, which now handle validation. Partial decisions have been mostly removed. You can manually construct decisions partially with the side.choose* functions, but there's no other support for them. Partial undo has been removed completely. Choice tracking has been renamed from side.choiceData to side.choice. side.choices has been removed and is now autogenerated from side.choice when needed. side.choiceData.decisions has been renamed side.choice.actions. In the future, "decision" is a deprecated term and should be called "action" wherever it shows up. side.choiceData.waiting and side.getDecisionsFinished() have been merged into side.isChoiceDone(). Other values in side.choiceData have either been rendered unnecessary or renamed to something clearer. The "skip" and "pass" choices have been merged together. Passes can still be filled in automatically (so you can just use `/move 1` in doubles when you have only one Pokémon left).
134 lines
4.0 KiB
JavaScript
134 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');
|
|
|
|
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(
|
|
format.id,
|
|
undefined,
|
|
undefined,
|
|
prng
|
|
);
|
|
battle.LEGACY_API_DO_NOT_USE = true;
|
|
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);
|