mirror of
https://github.com/smogon/pokemon-showdown.git
synced 2026-03-21 17:25:10 -05:00
Remove inactive ladders
This commit is contained in:
parent
5fb1a1dd85
commit
8797d4628c
|
|
@ -1,8 +1,7 @@
|
|||
config/formats.ts @KrisXV @Marty-D
|
||||
data/mods/gen9ssb/ @HoeenCoder @HisuianZoroark @KrisXV
|
||||
data/random-battles/ @AnnikaCodes @KrisXV @MathyFurret @ACakeWearingAHat @livid-washed @adrivrie
|
||||
data/random-battles/ @KrisXV @MathyFurret @ACakeWearingAHat @livid-washed @adrivrie
|
||||
data/text/ @Marty-D
|
||||
data/cg-team*.ts @KrisXV @pyuk
|
||||
databases/ @monsanto
|
||||
lib/sql.ts @mia-pi-git
|
||||
server/artemis/* @mia-pi-git
|
||||
|
|
|
|||
|
|
@ -327,6 +327,7 @@ export const Formats: import('../sim/dex-formats').FormatList = [
|
|||
{
|
||||
name: "[Gen 9] Ubers UU",
|
||||
mod: 'gen9',
|
||||
searchShow: false,
|
||||
ruleset: ['[Gen 9] Ubers'],
|
||||
banlist: [
|
||||
// Ubers OU
|
||||
|
|
@ -3175,6 +3176,7 @@ export const Formats: import('../sim/dex-formats').FormatList = [
|
|||
name: "[Gen 9] BSS Factory",
|
||||
desc: `Randomized 3v3 Singles featuring Pokémon and movesets popular in Battle Stadium Singles.`,
|
||||
mod: 'gen9',
|
||||
searchShow: false,
|
||||
team: 'randomBSSFactory',
|
||||
bestOfDefault: true,
|
||||
ruleset: ['Flat Rules', 'VGC Timer'],
|
||||
|
|
@ -3183,6 +3185,7 @@ export const Formats: import('../sim/dex-formats').FormatList = [
|
|||
name: "[Gen 9] Draft Factory",
|
||||
desc: `Randomized matchups sourced from various 6v6 singles draft leagues.`,
|
||||
mod: 'gen9',
|
||||
searchShow: false,
|
||||
team: 'randomDraftFactory',
|
||||
bestOfDefault: true,
|
||||
ruleset: ['Standard Draft', '!Team Preview'],
|
||||
|
|
@ -3218,16 +3221,6 @@ export const Formats: import('../sim/dex-formats').FormatList = [
|
|||
bestOfDefault: true,
|
||||
ruleset: ['Obtainable', 'Species Clause', 'HP Percentage Mod', 'Cancel Mod', 'Sleep Clause Mod', 'Illusion Level Mod'],
|
||||
},
|
||||
{
|
||||
name: "[Gen 9] Computer-Generated Teams",
|
||||
desc: `Teams generated automatically based on heuristics (rules), with levels based on previous success/failure in battle. ` +
|
||||
`Not affiliated with Random Battles formats. Some sets will by nature be worse than others, but you can report egregiously bad sets ` +
|
||||
`with <a href="https://forms.gle/DYwQN5qGVegz3YU38">this form</a>.`,
|
||||
mod: 'gen9',
|
||||
team: 'computerGenerated',
|
||||
bestOfDefault: true,
|
||||
ruleset: ['Obtainable', 'Species Clause', 'HP Percentage Mod', 'Cancel Mod', 'Sleep Clause Mod', 'Illusion Level Mod'],
|
||||
},
|
||||
{
|
||||
name: "[Gen 9] Hackmons Cup",
|
||||
desc: `Randomized teams of level-balanced Pokémon with absolutely any ability, moves, and item.`,
|
||||
|
|
|
|||
|
|
@ -62,8 +62,6 @@ export const Aliases: import('../sim/dex').AliasesTable = {
|
|||
gen6hackmons: "[Gen 6] Pure Hackmons",
|
||||
cc1v1: "[Gen 9] Challenge Cup 1v1",
|
||||
cc2v2: "[Gen 9] Challenge Cup 2v2",
|
||||
cgt: "[Gen 9] Computer-Generated Teams",
|
||||
compgen: "[Gen 9] Computer-Generated Teams",
|
||||
hc: "[Gen 9] Hackmons Cup",
|
||||
bf: "[Gen 8] Battle Factory",
|
||||
bssf: "[Gen 9] BSS Factory",
|
||||
|
|
@ -90,8 +88,8 @@ export const Aliases: import('../sim/dex').AliasesTable = {
|
|||
gen6ag: "[Gen 6] Anything Goes",
|
||||
crossevo: "[Gen 9] Cross Evolution",
|
||||
mayhem: "[Gen 9] Random Battle Mayhem",
|
||||
omotm: "[Gen 9] Alphabet Cup",
|
||||
lcotm: "[Gen 9] Inheritance",
|
||||
omotm: "[Gen 9] Bad 'n Boosted",
|
||||
lcotm: "[Gen 9] Revelationmons",
|
||||
|
||||
// mega evos --- 1st ordered alphabetically by species, 2nd by alias
|
||||
megasnow: "Abomasnow-Mega",
|
||||
|
|
|
|||
|
|
@ -1,78 +0,0 @@
|
|||
// Data for computer-generated teams
|
||||
|
||||
export const MOVE_PAIRINGS: { [moveID: IDEntry]: IDEntry } = {
|
||||
rest: 'sleeptalk',
|
||||
sleeptalk: 'rest',
|
||||
};
|
||||
|
||||
// Bonuses to move ratings by ability
|
||||
export const ABILITY_MOVE_BONUSES: { [abilityID: IDEntry]: { [moveID: IDEntry]: number } } = {
|
||||
contrary: { terablast: 2 },
|
||||
drought: { sunnyday: 0.2, solarbeam: 2 },
|
||||
drizzle: { raindance: 0.2, solarbeam: 0.2, hurricane: 2 },
|
||||
};
|
||||
// Bonuses to move ratings by move type
|
||||
export const ABILITY_MOVE_TYPE_BONUSES: { [abilityID: IDEntry]: { [typeName: string]: number } } = {
|
||||
darkaura: { Dark: 1.33 },
|
||||
dragonsmaw: { Dragon: 1.5 },
|
||||
fairyaura: { Fairy: 1.33 },
|
||||
steelworker: { Steel: 1.5 },
|
||||
steelyspirit: { Steel: 1.5 },
|
||||
transistor: { Electric: 1.3 },
|
||||
|
||||
// -ate moves
|
||||
pixilate: { Normal: 1.5 * 1.2 },
|
||||
refrigerate: { Normal: 1.5 * 1.2 },
|
||||
aerilate: { Normal: 1.5 * 1.2 },
|
||||
normalize: { Normal: 1.2 },
|
||||
|
||||
// weather
|
||||
drizzle: { Water: 1.4, Fire: 0.6 },
|
||||
drought: { Fire: 1.4, Water: 0.6 },
|
||||
};
|
||||
// For moves whose quality isn't obvious from data
|
||||
// USE SPARINGLY!
|
||||
export const HARDCODED_MOVE_WEIGHTS: { [moveID: IDEntry]: number } = {
|
||||
// Fails unless user is asleep
|
||||
snore: 0,
|
||||
// Hard to use
|
||||
lastresort: 0.1, dreameater: 0.1,
|
||||
// Useless without Berry + sucks even then
|
||||
belch: 0.2,
|
||||
|
||||
// Power increases in conditions out of our control that may occur
|
||||
avalanche: 1.2,
|
||||
ficklebeam: 1.3,
|
||||
hex: 1.2,
|
||||
stompingtantrum: 1.2,
|
||||
temperflare: 1.2,
|
||||
|
||||
// Attacks that set hazards on hit
|
||||
// We REALLY like hazards
|
||||
stoneaxe: 16,
|
||||
ceaselessedge: 16,
|
||||
|
||||
// screens
|
||||
lightscreen: 3, reflect: 3, auroraveil: 3, // TODO: make sure AVeil always gets Snow?
|
||||
tailwind: 2,
|
||||
|
||||
// mess with the opponent
|
||||
taunt: 2, disable: 2, encore: 3,
|
||||
|
||||
// healing moves
|
||||
// TODO: should healing moves be more common on bulkier pokemon?
|
||||
// 25%
|
||||
junglehealing: 3, lifedew: 3,
|
||||
// 50%
|
||||
milkdrink: 5, moonlight: 5, morningsun: 5, recover: 5, roost: 5,
|
||||
shoreup: 5, slackoff: 5, softboiled: 5, synthesis: 5,
|
||||
// delayed/consequence
|
||||
rest: 3, // has sleeptalk potential
|
||||
wish: 2,
|
||||
|
||||
// requires terrain
|
||||
steelroller: 0.1,
|
||||
};
|
||||
|
||||
export const WEIGHT_BASED_MOVES = ['heatcrash', 'heavyslam', 'lowkick', 'grassknot'];
|
||||
export const TARGET_HP_BASED_MOVES = ['crushgrip', 'hardpress'];
|
||||
1098
data/cg-teams.ts
1098
data/cg-teams.ts
File diff suppressed because it is too large
Load Diff
|
|
@ -2696,7 +2696,7 @@ export const Moves: import('../../../sim/dex-moves').ModdedMoveDataTable = {
|
|||
basePower: 0,
|
||||
category: "Status",
|
||||
shortDesc: "Changes target to a Randbats set.",
|
||||
desc: "Z-Move requiring Irpatuzinium Z. Nearly always moves first. Permanently transforms the target into a randomized Pokemon that would be generated in one of the following formats: Gen 9 Random Battle, Gen 9 Hackmons Cup, Gen 9 Challenge Cup, or Computer-Generated Teams. In the vast majority of circumstances, this also prevents the target from acting this turn.",
|
||||
desc: "Z-Move requiring Irpatuzinium Z. Nearly always moves first. Permanently transforms the target into a randomized Pokemon that would be generated in one of the following formats: Gen 9 Random Battle, Gen 9 Hackmons Cup, or Gen 9 Challenge Cup. In the vast majority of circumstances, this also prevents the target from acting this turn.",
|
||||
name: "Bibbidi-Bobbidi-Rands",
|
||||
gen: 9,
|
||||
pp: 1,
|
||||
|
|
@ -2706,7 +2706,7 @@ export const Moves: import('../../../sim/dex-moves').ModdedMoveDataTable = {
|
|||
this.attrLastMove('[anim] Doom Desire');
|
||||
},
|
||||
onHit(target, source) {
|
||||
const formats = ['gen9randombattle', 'gen9hackmonscup', 'gen9challengecup1v1', 'gen9computergeneratedteams'];
|
||||
const formats = ['gen9randombattle', 'gen9hackmonscup', 'gen9challengecup1v1'];
|
||||
const randFormat = this.sample(formats);
|
||||
let msg;
|
||||
switch (randFormat) {
|
||||
|
|
@ -2719,9 +2719,6 @@ export const Moves: import('../../../sim/dex-moves').ModdedMoveDataTable = {
|
|||
case 'gen9challengecup1v1':
|
||||
msg = "The only difference between a Challenge Cup Pokémon and my in-game one is that the former actually surpassed lvl. 60, enjoy n.n";
|
||||
break;
|
||||
case 'gen9computergeneratedteams':
|
||||
msg = "We asked an AI to make a randbats set. YOU WON'T BELIEVE WHAT IT CAME UP WITH N.N";
|
||||
break;
|
||||
}
|
||||
let team = [] as PokemonSet[];
|
||||
const unModdedDex = Dex.mod('base');
|
||||
|
|
|
|||
|
|
@ -2016,11 +2016,6 @@ export const Rulesets: import('../sim/dex-formats').FormatDataTable = {
|
|||
desc: "Maximum team size (number of pokemon) that can be brought into Team Preview (or into the battle, in formats without Team Preview)",
|
||||
hasValue: 'positive-integer',
|
||||
// hardcoded in sim/team-validator
|
||||
onValidateRule(value) {
|
||||
if (this.format.id.endsWith('computergeneratedteams')) {
|
||||
throw new Error(`${this.format.name} does not support Max Team Size.`);
|
||||
}
|
||||
},
|
||||
},
|
||||
maxmovecount: {
|
||||
effectType: 'ValidatorRule',
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
CREATE TABLE IF NOT EXISTS gen9computergeneratedteams (
|
||||
species_id TEXT PRIMARY KEY,
|
||||
wins NUMBER NOT NULL,
|
||||
losses NUMBER NOT NULL,
|
||||
level NUMBER NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS gen9computergeneratedteams_species_id_level ON gen9computergeneratedteams(species_id, level);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS gen9_historical_levels (
|
||||
species_id TEXT NOT NULL,
|
||||
level NUMBER NOT NULL,
|
||||
timestamp NUMBER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS db_info (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL
|
||||
);
|
||||
|
||||
INSERT INTO db_info (key, value) VALUES ('version', '1') ON CONFLICT DO NOTHING;
|
||||
PRAGMA journal_mode=WAL;
|
||||
|
|
@ -1,163 +0,0 @@
|
|||
/**
|
||||
* Not a chat plugin.
|
||||
*
|
||||
* Handles updating the level database for [Gen 9] Computer-Generated Teams.
|
||||
*/
|
||||
|
||||
import { SQL, Utils } from "../../lib";
|
||||
import { getSpeciesName } from "./randombattles/winrates";
|
||||
import { cgtDatabase } from "../../data/cg-teams";
|
||||
|
||||
export let addPokemon: SQL.Statement | null = null;
|
||||
export let incrementWins: SQL.Statement | null = null;
|
||||
export let incrementLosses: SQL.Statement | null = null;
|
||||
export let dbSetupPromise: Promise<void> | null = null;
|
||||
|
||||
async function setupDatabase(database: SQL.DatabaseManager) {
|
||||
await database.runFile('./databases/schemas/battlestats.sql');
|
||||
addPokemon = await database.prepare(
|
||||
'INSERT OR IGNORE INTO gen9computergeneratedteams (species_id, wins, losses, level) VALUES (?, 0, 0, ?)'
|
||||
);
|
||||
incrementWins = await database.prepare(
|
||||
'UPDATE gen9computergeneratedteams SET wins = wins + 1 WHERE species_id = ?'
|
||||
);
|
||||
incrementLosses = await database.prepare(
|
||||
'UPDATE gen9computergeneratedteams SET losses = losses + 1 WHERE species_id = ?'
|
||||
);
|
||||
}
|
||||
|
||||
if (Config.usesqlite && Config.usesqliteleveling) {
|
||||
const database = SQL(module, {
|
||||
file: './databases/battlestats.db',
|
||||
});
|
||||
dbSetupPromise = setupDatabase(database);
|
||||
}
|
||||
|
||||
function getLevelSpeciesID(set: PokemonSet, format?: Format) {
|
||||
if (['Basculin', 'Greninja'].includes(set.name)) return toID(set.species);
|
||||
return toID(getSpeciesName(set, format || Dex.formats.get('gen9computergeneratedteams')));
|
||||
}
|
||||
|
||||
async function updateStats(battle: RoomBattle, winner: ID) {
|
||||
if (!incrementWins || !incrementLosses) await dbSetupPromise;
|
||||
if (toID(battle.format) !== 'gen9computergeneratedteams') return;
|
||||
// if the game is rated or part of a tournament hosted by a public room, it counts
|
||||
if (battle.rated <= 1 && battle.room.parent?.game) {
|
||||
let parent = battle.room.parent;
|
||||
if (parent.game!.gameid === 'bestof' && parent.parent?.game) parent = parent.parent;
|
||||
if (parent.game!.gameid !== 'tournament' || parent.settings.isPrivate) return;
|
||||
} else if (battle.rated < 1000) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const player of battle.players) {
|
||||
const team = await battle.getPlayerTeam(player);
|
||||
if (!team) return;
|
||||
const increment = (player.id === winner ? incrementWins : incrementLosses);
|
||||
|
||||
for (const set of team) {
|
||||
const statsSpecies = getLevelSpeciesID(set, Dex.formats.get(battle.format));
|
||||
await addPokemon?.run([statsSpecies, set.level || 100]);
|
||||
await increment?.run([statsSpecies]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const handlers: Chat.Handlers = {
|
||||
onBattleEnd(battle, winner) {
|
||||
if (!Config.usesqlite || !Config.usesqliteleveling) return;
|
||||
void updateStats(battle, winner);
|
||||
},
|
||||
};
|
||||
|
||||
export const commands: Chat.ChatCommands = {
|
||||
cgtwr: 'cgtwinrates',
|
||||
cgtwinrates(target, room, user) {
|
||||
return this.parse(`/j view-cgtwinrates-${target ? 'history--' + target : 'current'}`);
|
||||
},
|
||||
cgtwinrateshelp: [
|
||||
'/cgtwinrates OR /cgtwr - Get a list of the current win rate data for all Pokemon in [Gen 9] Computer Generated Teams.',
|
||||
],
|
||||
|
||||
// Add maintenance commands here
|
||||
};
|
||||
|
||||
interface MonCurrent { species_id: ID; wins: number; losses: number; level: number }
|
||||
interface MonHistory { level: number; species_id: ID; timestamp: number }
|
||||
|
||||
export const pages: Chat.PageTable = {
|
||||
async cgtwinrates(query, user) {
|
||||
if (!user.named) return Rooms.RETRY_AFTER_LOGIN;
|
||||
if (!cgtDatabase) {
|
||||
throw new Chat.ErrorMessage(`CGT win rates are not being tracked due to the server's SQL settings.`);
|
||||
}
|
||||
query = query.join('-').split('--');
|
||||
const mode = query.shift();
|
||||
if (mode === 'current') {
|
||||
let buf = `<div class="pad"><h2>Winrates for [Gen 9] Computer Generated Teams</h2>`;
|
||||
const sorter = toID(query.shift() || 'alphabetical');
|
||||
if (!['alphabetical', 'level'].includes(sorter)) {
|
||||
throw new Chat.ErrorMessage(`Invalid sorting method. Must be either 'alphabetical' or 'level'.`);
|
||||
}
|
||||
const otherSort = sorter === 'alphabetical' ? 'Level' : 'Alphabetical';
|
||||
buf += `<a class="button" target="replace" href="/view-cgtwinrates-current--${toID(otherSort)}">`;
|
||||
buf += `Sort by ${otherSort} descending</a>`;
|
||||
buf += `<hr />`;
|
||||
const statData: MonCurrent[] = await cgtDatabase.all(
|
||||
'SELECT species_id, wins, losses, level FROM gen9computergeneratedteams'
|
||||
);
|
||||
this.title = `[Winrates] [Gen 9] Computer Generated Teams`;
|
||||
let sortFn: (val: MonCurrent) => Utils.Comparable;
|
||||
|
||||
if (sorter === 'alphabetical') {
|
||||
sortFn = data => [data.species_id];
|
||||
} else {
|
||||
sortFn = data => [-data.level];
|
||||
}
|
||||
const mons = Utils.sortBy(statData, sortFn);
|
||||
buf += `<div class="ladder pad"><table><tr><th>Pokemon</th><th>Level</th><th>Wins</th><th>Losses</th>`;
|
||||
for (const mon of mons) {
|
||||
buf += `<tr><td>${Dex.species.get(mon.species_id).name}</td>`;
|
||||
buf += `<td>${mon.level}</td><td>${mon.wins}</td><td>${mon.losses}</td></tr>`;
|
||||
}
|
||||
buf += `</table></div></div>`;
|
||||
return buf;
|
||||
} else if (mode === 'history') {
|
||||
// Restricted because this is a potentially very slow command
|
||||
this.checkCan('modlog', null, Rooms.get('development')!); // stinky non-null assertion
|
||||
|
||||
let speciesID = query.shift();
|
||||
let buf;
|
||||
if (speciesID) {
|
||||
speciesID = getLevelSpeciesID({ species: speciesID || '' } as PokemonSet);
|
||||
const species = Dex.species.get(speciesID);
|
||||
if (!species.exists ||
|
||||
species.isNonstandard || species.isNonstandard === 'Unobtainable' ||
|
||||
species.nfe ||
|
||||
species.battleOnly && (!species.requiredItems?.length || species.name.endsWith('-Tera'))
|
||||
) {
|
||||
this.errorReply('Species has no data in [Gen 9] Computer Generated Teams');
|
||||
}
|
||||
buf = `<div class="pad"><h2>Level history for ${species.name} in [Gen 9] CGT</h2>`;
|
||||
} else {
|
||||
buf = `<div class="pad"><h2>Level history for [Gen 9] Computer Generated Teams</h2>`;
|
||||
}
|
||||
const history: MonHistory[] = await cgtDatabase.all(
|
||||
'SELECT level, species_id, timestamp FROM gen9_historical_levels'
|
||||
);
|
||||
this.title = `[History] [Gen 9] Computer Generated Teams`;
|
||||
|
||||
const MAX_LINES = 100;
|
||||
buf += `<div class="ladder pad"><table><tr><th>Pokemon</th><th>Level</th><th>Timestamp</th>`;
|
||||
for (let i = history.length - 1; history.length - i <= MAX_LINES; i--) {
|
||||
const entry = history[i];
|
||||
if (speciesID && entry.species_id !== speciesID) continue;
|
||||
buf += `<tr><td>${entry.species_id}</td><td>${entry.level}</td>`;
|
||||
const timestamp = new Date(entry.timestamp);
|
||||
buf += `<td>${timestamp.toLocaleDateString()}, ${timestamp.toLocaleTimeString()}</td></tr>`;
|
||||
}
|
||||
buf += `</table></div></div>`;
|
||||
return buf;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
@ -630,9 +630,7 @@ export const Teams = new class Teams {
|
|||
let mod = format.mod;
|
||||
if (format.mod === 'monkeyspaw') mod = 'gen9';
|
||||
const formatID = toID(format);
|
||||
if (formatID.includes('gen9computergeneratedteams')) {
|
||||
TeamGenerator = require(Dex.forFormat(format).dataDir + '/cg-teams').default;
|
||||
} else if (mod === 'gen9ssb') {
|
||||
if (mod === 'gen9ssb') {
|
||||
TeamGenerator = require(`../data/mods/gen9ssb/random-teams`).default;
|
||||
} else if (formatID.includes('gen9donotuserandombattle')) {
|
||||
TeamGenerator = require(`../data/random-battles/donotuse/teams`).default;
|
||||
|
|
|
|||
|
|
@ -1,61 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
const assert = require('assert').strict;
|
||||
const TeamGenerator = require('../../dist/data/cg-teams').default;
|
||||
|
||||
describe('[Gen 9] Computer-Generated Teams', () => {
|
||||
it.skip('should give all species 4 or fewer moves', () => {
|
||||
const generator = new TeamGenerator();
|
||||
const pool = generator.dex.species
|
||||
.all()
|
||||
.filter(s => s.exists && !(s.isNonstandard || s.isNonstandard === 'Unobtainable') && !s.nfe);
|
||||
for (const species of pool) {
|
||||
const set = generator.makeSet(species, { hazardSetters: {} });
|
||||
assert(set.moves.length <= 4, `Species ${species.name} has more than 4 moves (set=${JSON.stringify(set)})`);
|
||||
assert(new Set(set.moves).size === set.moves.length, `Species ${species.name} has duplicate moves (set=${JSON.stringify(set)})`);
|
||||
}
|
||||
});
|
||||
|
||||
// Skipped since it includes randomness; useful for debugging though
|
||||
it.skip('should have an accurate weighted picker', () => {
|
||||
const generator = new TeamGenerator();
|
||||
const numTrials = 100000;
|
||||
let error = 0;
|
||||
let trials = 0;
|
||||
|
||||
for (const choices of [
|
||||
[{ choice: 'a', weight: 1 }, { choice: 'b', weight: 2 }],
|
||||
[{ choice: 'a', weight: 1 }, { choice: 'b', weight: 1 }],
|
||||
[{ choice: 'a', weight: 30 }, { choice: 'b', weight: 2000 }, { choice: 'c', weight: 7 }],
|
||||
// a big test case with lots of different weight values
|
||||
[
|
||||
{ choice: 'a', weight: 1345 }, { choice: 'b', weight: 2013 }, { choice: 'c', weight: 3411 }, { choice: 'd', weight: 940 },
|
||||
{ choice: 'e', weight: 505 }, { choice: 'f', weight: 10148 }, { choice: 'g', weight: 7342 }, { choice: 'h', weight: 8403 },
|
||||
{ choice: 'i', weight: 9859 }, { choice: 'j', weight: 1042 }, { choice: 'k', weight: 1132 }, { choice: 'l', weight: 1200 },
|
||||
],
|
||||
]) {
|
||||
const results = {};
|
||||
for (let i = 0; i < numTrials; i++) {
|
||||
const res = generator.weightedRandomPick(choices.map(x => x.choice), c => choices.find(x => x.choice === c)?.weight || 0);
|
||||
// console.log(`"${res}"`);
|
||||
if (!results[res]) results[res] = 0;
|
||||
results[res]++;
|
||||
}
|
||||
|
||||
let totalWeight = 0;
|
||||
for (const choice of choices) {
|
||||
totalWeight += choice.weight;
|
||||
}
|
||||
|
||||
for (const [choice, count] of Object.entries(results)) {
|
||||
const c = choices.find(x => x.choice === choice);
|
||||
const expected = (c.weight / totalWeight) * numTrials;
|
||||
error += Math.abs(count - expected) / expected;
|
||||
trials++;
|
||||
}
|
||||
}
|
||||
|
||||
const percentError = (error / trials) * 100;
|
||||
assert(percentError < 3, `Weighted picker error is too high: ${percentError.toFixed(1)}%`);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user