Skip to content

Instantly share code, notes, and snippets.

@double-beep
Last active December 30, 2022 09:13
Show Gist options
  • Save double-beep/848bdcc15ad1d1d348c0231422d1c2ff to your computer and use it in GitHub Desktop.
Save double-beep/848bdcc15ad1d1d348c0231422d1c2ff to your computer and use it in GitHub Desktop.
Scraps GM applications & GM norms and finds the people who earnt at least two in less than x days
// convert [[id1], [id2], [id3], ...] to { id1: [], id2: [], id3: [] }
// each empty array is filled later with norm information
const objectify = array => array.reduce((a, v) => ({ ...a, [v]: []}), {});
const players = {}; // {id: name}
function sortByDiff(entries) {
return entries
.filter(([, value]) => value.length > 1) // players with >1 GM norm
.map(([id, info]) => {
// convert YYYY-MM-DD to millis
const times = info.map(entry => new Date(entry.endDate).getTime());
const differences = times
.sort((a, b) => a - b) // sort by the most recent norm
.reduce((acc, val, index) => {
// all but the last element:
if (index === times.length - 1) return acc;
// find the absolute value of the difference
// between two dates
const difference = Math.abs(val - times[index + 1]);
acc.push(difference / (60 * 60 * 24 * 1000));
return acc;
}, []);
return [id, differences];
})
// each player may have earnt 3+ GM norms
// sort by the min difference of each player
.sort(([, a], [, b]) => Math.min(...a) - Math.min(...b));
}
async function getNormInformation(parsed, tdN, record = '') {
// an HTML table containing the tournaments a player has participated & earnt a GM norm
// ?record2=<id> -> for players who aren't GMs yet (Norms tab)
// ?record=<id> -> for players who have applied for GM (Applications tab)
const getLink = id => `https://ratings.fide.com/a_titles.php?record${record}=${id}`;
for (const id of Object.keys(parsed)) {
const link = getLink(id);
const normCall = await fetch(link);
const table = await normCall.text();
const domParser = new DOMParser();
const html = domParser.parseFromString(table, 'text/html');
const children = html.body.querySelector('tbody')?.children;
if (!children) continue; // e.g. "No records found"
[...children]
// may contain a tr with a single column (Application file: ....)
.filter(child => child.childElementCount > 4)
.forEach(tr => {
const tds = [...tr.children];
// NOTE: - date & event name is in the 2nd (index = 1)/5th (index = 4) column
// respectively if the player isn't a GM
// - otherwise 1st (index = 0)/4th (index = 3)
// only keep the date the tournament ended
const endDate = tds[tdN].innerText.substring(10, 20); // YYYY-MM-DD
const event = tds[tdN + 3].innerText;
// exclude duplicate entries
// yes, sometimes the same tournament can be shown twice
// although the player has earnt 1 GM norm from it
const isDuplicate = parsed[id].some(info => info.event === event);
if (isDuplicate) return;
// event is not needed/used, exists mostly for debugging purposes
parsed[id].push({ event, endDate });
});
}
}
async function part1() {
const call = await fetch('https://ratings.fide.com/a_titles.php?country=latest');
const result = await call.json();
// of the values provided, only keep the player's id
const ids = result.data.map(([, id, name]) => {
players[id] = name;
return id;
});
const parsed = objectify(ids);
await getNormInformation(parsed, 1, 2);
return parsed;
}
async function part2() {
let data = [];
// pb = 70: 2023 1st FIDE Council
// update for new FIDE PB/Council meetings
for (let i = 1; i <= 70; i++) {
const url = `https://ratings.fide.com/a_titles.php?pb=${i}`;
const call = await fetch(url);
const result = await call.json();
const gms = result.data
// grandmasters only
.filter(array => array.at(-1) === 'Grandmaster')
// keep the ids only
.map(([, id,,, name]) => {
players[id] = name;
return id;
});
data.push(...gms);
}
data = objectify(data);
await getNormInformation(data, 0);
return data;
}
function announceResults(result) {
// find index to stop
// stop at 4 days
const stopIndex = result.findIndex(([, times]) => times.every(time => time > 4));
result.forEach(([id, times], index) => {
if (index > stopIndex) return;
console.log('Player', players[id], '->', times);
});
}
(async function() {
const p1 = Object.entries(await part1());
const p2 = Object.entries(await part2());
const result = sortByDiff(p1.concat(p2));
announceResults(result);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment