{ "version": 3, "sources": ["../../server/ladders-local.ts"], "sourcesContent": ["/**\n * Ladder library\n * Pokemon Showdown - http://pokemonshowdown.com/\n *\n * This file handles ladders for all servers other than\n * play.pokemonshowdown.com.\n *\n * Specifically, this is the file that handles calculating and keeping\n * track of players' Elo ratings for all formats.\n *\n * Matchmaking is currently still implemented in rooms.ts.\n *\n * @license MIT\n */\n\nimport { FS, Utils } from '../lib';\n\n// ladderCaches = {formatid: ladder OR Promise(ladder)}\n// Use Ladders(formatid).ladder to guarantee a Promise(ladder).\n// ladder is basically a 2D array representing the corresponding ladder.tsv\n// with userid in front\n/** [userid, elo, username, w, l, t, lastUpdate */\ntype LadderRow = [string, number, string, number, number, number, string];\n/** formatid: ladder */\ntype LadderCache = Map>;\n\nconst ladderCaches: LadderCache = new Map();\n\nexport class LadderStore {\n\tformatid: string;\n\tladder: LadderRow[] | null;\n\tladderPromise: Promise | null;\n\tsaving: boolean;\n\tstatic readonly formatsListPrefix = '|,LL';\n\tstatic readonly ladderCaches = ladderCaches;\n\n\tconstructor(formatid: string) {\n\t\tthis.formatid = formatid;\n\t\tthis.ladder = null;\n\t\tthis.ladderPromise = null;\n\t\tthis.saving = false;\n\t}\n\n\tgetLadder() {\n\t\tif (!this.ladderPromise) this.ladderPromise = this.load();\n\t\treturn this.ladderPromise;\n\t}\n\n\t/**\n\t * Internal function, returns a Promise for a ladder\n\t */\n\tasync load() {\n\t\t// ladderCaches[formatid]\n\t\tconst cachedLadder = ladderCaches.get(this.formatid);\n\t\tif (cachedLadder) {\n\t\t\tif ((cachedLadder as Promise).then) {\n\t\t\t\tconst ladder = await cachedLadder;\n\t\t\t\treturn (this.ladder = ladder);\n\t\t\t}\n\t\t\treturn (this.ladder = cachedLadder as LadderRow[]);\n\t\t}\n\t\ttry {\n\t\t\tconst data = await FS('config/ladders/' + this.formatid + '.tsv').readIfExists();\n\t\t\tconst ladder: LadderRow[] = [];\n\t\t\tfor (const dataLine of data.split('\\n').slice(1)) {\n\t\t\t\tconst line = dataLine.trim();\n\t\t\t\tif (!line) continue;\n\t\t\t\tconst row = line.split('\\t');\n\t\t\t\tladder.push([toID(row[1]), Number(row[0]), row[1], Number(row[2]), Number(row[3]), Number(row[4]), row[5]]);\n\t\t\t}\n\t\t\t// console.log('Ladders(' + this.formatid + ') loaded tsv: ' + JSON.stringify(this.ladder));\n\t\t\tladderCaches.set(this.formatid, (this.ladder = ladder));\n\t\t\treturn this.ladder;\n\t\t} catch {\n\t\t\t// console.log('Ladders(' + this.formatid + ') err loading tsv: ' + JSON.stringify(this.ladder));\n\t\t}\n\t\tladderCaches.set(this.formatid, (this.ladder = []));\n\t\treturn this.ladder;\n\t}\n\n\t/**\n\t * Saves the ladder in config/ladders/[formatid].tsv\n\t *\n\t * Called automatically by updateRating, so you don't need to manually\n\t * call this.\n\t */\n\tasync save() {\n\t\tif (this.saving) return;\n\t\tthis.saving = true;\n\t\tconst ladder = await this.getLadder();\n\t\tif (!ladder.length) {\n\t\t\tthis.saving = false;\n\t\t\treturn;\n\t\t}\n\t\tconst stream = FS(`config/ladders/${this.formatid}.tsv`).createWriteStream();\n\t\tvoid stream.write('Elo\\tUsername\\tW\\tL\\tT\\tLast update\\r\\n');\n\t\tfor (const row of ladder) {\n\t\t\tvoid stream.write(row.slice(1).join('\\t') + '\\r\\n');\n\t\t}\n\t\tvoid stream.writeEnd();\n\t\tthis.saving = false;\n\t}\n\n\t/**\n\t * Gets the index of a user in the ladder array.\n\t *\n\t * If createIfNeeded is true, the user will be created and added to\n\t * the ladder array if it doesn't already exist.\n\t */\n\tindexOfUser(username: string, createIfNeeded = false) {\n\t\tif (!this.ladder) throw new Error(`Must be called with ladder loaded`);\n\t\tconst userid = toID(username);\n\t\tfor (const [i, user] of this.ladder.entries()) {\n\t\t\tif (user[0] === userid) return i;\n\t\t}\n\t\tif (createIfNeeded) {\n\t\t\tconst index = this.ladder.length;\n\t\t\tthis.ladder.push([userid, 1000, username, 0, 0, 0, '']);\n\t\t\treturn index;\n\t\t}\n\t\treturn -1;\n\t}\n\n\t/**\n\t * Returns [formatid, html], where html is an the HTML source of a\n\t * ladder toplist, to be displayed directly in the ladder tab of the\n\t * client.\n\t */\n\tasync getTop(prefix?: string) {\n\t\tconst formatid = this.formatid;\n\t\tconst name = Dex.formats.get(formatid).name;\n\t\tconst ladder = await this.getLadder();\n\t\tlet buf = `

${name} Top 100

`;\n\t\tbuf += ``;\n\t\tbuf += ``;\n\t\tfor (const [i, row] of ladder.entries()) {\n\t\t\tif (prefix && !row[0].startsWith(prefix)) continue;\n\t\t\tbuf += ``;\n\t\t}\n\t\treturn [formatid, buf];\n\t}\n\n\t/**\n\t * Returns a Promise for the Elo rating of a user\n\t */\n\tasync getRating(userid: string) {\n\t\tconst formatid = this.formatid;\n\t\tconst user = Users.getExact(userid);\n\t\tif (user?.mmrCache[formatid]) {\n\t\t\treturn user.mmrCache[formatid];\n\t\t}\n\t\tconst ladder = await this.getLadder();\n\t\tconst index = this.indexOfUser(userid);\n\t\tlet rating = 1000;\n\t\tif (index >= 0) {\n\t\t\trating = ladder[index][1];\n\t\t}\n\t\tif (user && user.id === userid) {\n\t\t\tuser.mmrCache[formatid] = rating;\n\t\t}\n\t\treturn rating;\n\t}\n\n\t/**\n\t * Internal method. Update the Elo rating of a user.\n\t */\n\tupdateRow(row: LadderRow, score: number, foeElo: number) {\n\t\tlet elo = row[1];\n\n\t\telo = this.calculateElo(elo, score, foeElo);\n\n\t\trow[1] = elo;\n\t\tif (score > 0.6) {\n\t\t\trow[3]++; // win\n\t\t} else if (score < 0.4) {\n\t\t\trow[4]++; // loss\n\t\t} else {\n\t\t\trow[5]++; // tie\n\t\t}\n\t\trow[6] = `${new Date()}`;\n\t}\n\n\t/**\n\t * Update the Elo rating for two players after a battle, and display\n\t * the results in the passed room.\n\t */\n\tasync updateRating(p1name: string, p2name: string, p1score: number, room: AnyObject) {\n\t\tif (Ladders.disabled) {\n\t\t\troom.addRaw(`Ratings not updated. The ladders are currently disabled.`).update();\n\t\t\treturn [p1score, null, null];\n\t\t}\n\n\t\tconst formatid = this.formatid;\n\t\tlet p2score = 1 - p1score;\n\t\tif (p1score < 0) {\n\t\t\tp1score = 0;\n\t\t\tp2score = 0;\n\t\t}\n\t\tconst ladder = await this.getLadder();\n\n\t\tlet p1newElo;\n\t\tlet p2newElo;\n\t\ttry {\n\t\t\tconst p1index = this.indexOfUser(p1name, true);\n\t\t\tconst p1elo = ladder[p1index][1];\n\n\t\t\tlet p2index = this.indexOfUser(p2name, true);\n\t\t\tconst p2elo = ladder[p2index][1];\n\n\t\t\tthis.updateRow(ladder[p1index], p1score, p2elo);\n\t\t\tthis.updateRow(ladder[p2index], p2score, p1elo);\n\n\t\t\tp1newElo = ladder[p1index][1];\n\t\t\tp2newElo = ladder[p2index][1];\n\n\t\t\t// console.log('L: ' + ladder.map(r => ''+Math.round(r[1])+' '+r[2]).join('\\n'));\n\n\t\t\t// move p1 to its new location\n\t\t\tlet newIndex = p1index;\n\t\t\twhile (newIndex > 0 && ladder[newIndex - 1][1] <= p1newElo) newIndex--;\n\t\t\twhile (newIndex === p1index || (ladder[newIndex] && ladder[newIndex][1] > p1newElo)) newIndex++;\n\t\t\t// console.log('ni='+newIndex+', p1i='+p1index);\n\t\t\tif (newIndex !== p1index && newIndex !== p1index + 1) {\n\t\t\t\tconst row = ladder.splice(p1index, 1)[0];\n\t\t\t\t// adjust for removed row\n\t\t\t\tif (newIndex > p1index) newIndex--;\n\t\t\t\tif (p2index > p1index) p2index--;\n\n\t\t\t\tladder.splice(newIndex, 0, row);\n\t\t\t\t// adjust for inserted row\n\t\t\t\tif (p2index >= newIndex) p2index++;\n\t\t\t}\n\n\t\t\t// move p2\n\t\t\tnewIndex = p2index;\n\t\t\twhile (newIndex > 0 && ladder[newIndex - 1][1] <= p2newElo) newIndex--;\n\t\t\twhile (newIndex === p2index || (ladder[newIndex] && ladder[newIndex][1] > p2newElo)) newIndex++;\n\t\t\t// console.log('ni='+newIndex+', p2i='+p2index);\n\t\t\tif (newIndex !== p2index && newIndex !== p2index + 1) {\n\t\t\t\tconst row = ladder.splice(p2index, 1)[0];\n\t\t\t\t// adjust for removed row\n\t\t\t\tif (newIndex > p2index) newIndex--;\n\n\t\t\t\tladder.splice(newIndex, 0, row);\n\t\t\t}\n\n\t\t\tconst p1 = Users.getExact(p1name);\n\t\t\tif (p1) p1.mmrCache[formatid] = +p1newElo;\n\t\t\tconst p2 = Users.getExact(p2name);\n\t\t\tif (p2) p2.mmrCache[formatid] = +p2newElo;\n\t\t\tvoid this.save();\n\n\t\t\tif (!room.battle) {\n\t\t\t\tMonitor.warn(`room expired before ladder update was received`);\n\t\t\t\treturn [p1score, null, null];\n\t\t\t}\n\n\t\t\tlet reasons = `${Math.round(p1newElo) - Math.round(p1elo)} for ${p1score > 0.9 ? 'winning' : (p1score < 0.1 ? 'losing' : 'tying')}`;\n\t\t\tif (!reasons.startsWith('-')) reasons = '+' + reasons;\n\t\t\troom.addRaw(\n\t\t\t\tUtils.html`${p1name}'s rating: ${Math.round(p1elo)} → ${Math.round(p1newElo)}
(${reasons})`\n\t\t\t);\n\n\t\t\treasons = `${Math.round(p2newElo) - Math.round(p2elo)} for ${p2score > 0.9 ? 'winning' : (p2score < 0.1 ? 'losing' : 'tying')}`;\n\t\t\tif (!reasons.startsWith('-')) reasons = '+' + reasons;\n\t\t\troom.addRaw(\n\t\t\t\tUtils.html`${p2name}'s rating: ${Math.round(p2elo)} → ${Math.round(p2newElo)}
(${reasons})`\n\t\t\t);\n\n\t\t\troom.update();\n\t\t} catch (e: any) {\n\t\t\tif (!room.battle) return [p1score, null, null];\n\t\t\troom.addRaw(`There was an error calculating rating changes:`);\n\t\t\troom.add(e.stack);\n\t\t\troom.update();\n\t\t}\n\n\t\treturn [p1score, p1newElo, p2newElo];\n\t}\n\n\t/**\n\t * Returns a promise for a with all ratings for the current format.\n\t */\n\tasync visualize(username: string) {\n\t\tconst ladder = await this.getLadder();\n\n\t\tconst index = this.indexOfUser(username, false);\n\n\t\tif (index < 0) return '';\n\n\t\tconst ratings = ladder[index];\n\n\t\tconst output = ``;\n\t\treturn `${output}`;\n\t}\n\n\t/**\n\t * Calculates Elo based on a match result\n\t */\n\tcalculateElo(oldElo: number, score: number, foeElo: number): number {\n\t\t// The K factor determines how much your Elo changes when you win or\n\t\t// lose games. Larger K means more change.\n\t\t// In the \"original\" Elo, K is constant, but it's common for K to\n\t\t// get smaller as your rating goes up\n\t\tlet K = 50;\n\n\t\t// dynamic K-scaling (optional)\n\t\tif (oldElo < 1200) {\n\t\t\tif (score < 0.5) {\n\t\t\t\tK = 10 + (oldElo - 1000) * 40 / 200;\n\t\t\t} else if (score > 0.5) {\n\t\t\t\tK = 90 - (oldElo - 1000) * 40 / 200;\n\t\t\t}\n\t\t} else if (oldElo > 1350 && oldElo <= 1600) {\n\t\t\tK = 40;\n\t\t} else {\n\t\t\tK = 32;\n\t\t}\n\n\t\t// main Elo formula\n\t\tconst E = 1 / (1 + 10 ** ((foeElo - oldElo) / 400));\n\n\t\tconst newElo = oldElo + K * (score - E);\n\n\t\treturn Math.max(newElo, 1000);\n\t}\n\n\t/**\n\t * Returns a Promise for an array of strings of s for ladder ratings of the user\n\t */\n\tstatic visualizeAll(username: string) {\n\t\tconst ratings = [];\n\t\tfor (const format of Dex.formats.all()) {\n\t\t\tif (format.searchShow) {\n\t\t\t\tratings.push(new LadderStore(format.id).visualize(username));\n\t\t\t}\n\t\t}\n\t\treturn Promise.all(ratings);\n\t}\n}\n"], "mappings": "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", "names": [] }
` + ['', 'Username', 'Elo', 'W', 'L', 'T'].join(``) + `
` + [\n\t\t\t\ti + 1, row[2], `${Math.round(row[1])}`, row[3], row[4], row[5],\n\t\t\t].join(``) + `
${this.formatid}${Math.round(ratings[1])}${ratings[3]}${ratings[4]}${ratings[3] + ratings[4]}