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

${name} Top 100

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