Advertisement
Guest User

Geoguessr Advanced Stats V5

a guest
Nov 23rd, 2024
712
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
JavaScript 30.49 KB | Source Code | 0 0
  1. // ==UserScript==
  2. // @name         Geoguessr Advanced Stats V5
  3. // @namespace    http://tampermonkey.net/
  4. // @version      5.0
  5. // @description  Fetches Geoguessr API data to get advanced stats for duel games and displays them on the profile page.
  6. // @author       You
  7. // @match        https://www.geoguessr.com/*/me/profile
  8. // @match        https://www.geoguessr.com/me/profile
  9. // @icon         https://www.geoguessr.com/_next/static/media/favicon.bffdd9d3.png
  10.  
  11. // @grant        GM_xmlhttpRequest
  12. // @grant        GM_addStyle
  13. // @require      https://code.jquery.com/jquery-3.6.0.min.js
  14. // @require      https://unpkg.com/[email protected]/dist/axios.min.js
  15. // @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/progressbar.min.js
  16. // @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.min.js
  17. // @connect      https://game-server.geoguessr.com/
  18. // ==/UserScript==
  19.  
  20. (function() {
  21.     'use strict';
  22.  
  23.     // Constants
  24.     const MAX_SCORE = 5000;
  25.     const API_BASE_URL = 'https://game-server.geoguessr.com/api/duels';
  26.  
  27.     // Variables
  28.     let playerId = '';
  29.  
  30.     // Inject custom styles
  31.     function addCustomStyles() {
  32.         GM_addStyle(`
  33.             table { width:96%; border-collapse: collapse; color:black;}
  34.             th { border: 1px solid #ddd; padding: 8px; text-align: left; background-color: #f2f2f2; cursor: pointer; }
  35.             td { border: 1px solid #ddd; padding: 8px; text-align: left; background-color: #ffffff; }
  36.             .chart-container { margin: auto; width: 80%; height: 400px; }
  37.         `);
  38.     }
  39.  
  40.     // Utility functions for color interpolation
  41.     function interpolateColor(color1, color2, factor) {
  42.         factor = Math.min(1, Math.max(0, factor));
  43.         const color1Parts = parseColor(color1);
  44.         const color2Parts = parseColor(color2);
  45.  
  46.         return color1Parts.map((part, index) =>
  47.             Math.round(part + (factor * (color2Parts[index] - part)))
  48.         ).reduce((acc, val) => acc + ` ${val}`, 'rgb(').concat(')');
  49.     }
  50.  
  51.     function parseColor(hex) {
  52.         return [
  53.             parseInt(hex.slice(1, 3), 16),
  54.             parseInt(hex.slice(3, 5), 16),
  55.             parseInt(hex.slice(5, 7), 16)
  56.         ];
  57.     }
  58.  
  59.     function getColor(value, min, max) {
  60.         const mid = (max + min) / 2;
  61.         return value < mid ?
  62.             interpolateColor("#cd2941", "#ffffff", (value - min) / (mid - min))
  63.             : interpolateColor("#ffffff", "#29cdb5", (value - mid) / (max - mid));
  64.     }
  65.  
  66.     // Calculate global stats from countryData
  67.     function calculateGlobalStats(countryData) {
  68.         let totalScore = 0;
  69.         let totalAccuracy = 0;
  70.         let totalRounds = 0;
  71.         let totalWins = 0;
  72.  
  73.         Object.values(countryData).forEach(data => {
  74.             totalScore += data.totalScore;
  75.             totalAccuracy += data.totalAccuracy;
  76.             totalRounds += data.totalRounds;
  77.             totalWins += data.wins;
  78.         });
  79.  
  80.         const totalAverageScore = Math.floor(totalScore / totalRounds);
  81.         const overallAccuracy = (totalAccuracy / totalRounds).toFixed(2);
  82.         const totalWinRate = ((totalWins / totalRounds) * 100).toFixed(2);
  83.  
  84.         return { totalAverageScore, overallAccuracy, totalWinRate };
  85.     }
  86.  
  87.     // Render circular gauges for global stats
  88.     function renderGauges(container, globalStats) {
  89.         if (!container) {
  90.             console.error("Container element not found.");
  91.             return;
  92.         }
  93.         const overallStatsWrapper = document.createElement('div');
  94.         overallStatsWrapper.style.marginBottom = '40px';
  95.  
  96.         const titleElement = document.createElement('h2');
  97.         titleElement.textContent = "Overall Stats";
  98.         titleElement.style.color = "#ffa43d";
  99.         titleElement.style.textAlign = 'center';
  100.         titleElement.style.marginBottom = '15px';
  101.  
  102.         overallStatsWrapper.appendChild(titleElement);
  103.  
  104.         const statsWrapper = document.createElement('div');
  105.         statsWrapper.style.display = 'flex';
  106.         statsWrapper.style.justifyContent = 'space-around';
  107.         statsWrapper.style.marginBottom = '20px';
  108.  
  109.         const gaugesData = [
  110.             { label: 'Average Score', value: globalStats.totalAverageScore / MAX_SCORE, actualValue: globalStats.totalAverageScore, maxValue: MAX_SCORE },
  111.             { label: 'Accuracy', value: globalStats.overallAccuracy / 100, actualValue: globalStats.overallAccuracy , maxValue : 100},
  112.             { label: 'Win-Rate', value: globalStats.totalWinRate / 100, actualValue: globalStats.totalWinRate, maxValue : 100 }
  113.         ];
  114.  
  115.         gaugesData.forEach(data => {
  116.             const gaugeWrapper = document.createElement('div');
  117.             gaugeWrapper.style.textAlign = 'center';
  118.  
  119.             if (typeof ProgressBar === 'undefined' || typeof ProgressBar.Circle === 'undefined') {
  120.                 console.error("ProgressBar.js library not found.");
  121.                 return;
  122.             }
  123.             const bar = new ProgressBar.Circle(gaugeWrapper, {
  124.                 trailColor: 'hsla(0,0%,100%,.6)',
  125.                 trailWidth: 15,
  126.                 strokeWidth: 15,
  127.                 text: {
  128.                     value: '',
  129.                     style: {
  130.                         color: '#FFF',
  131.                         position: 'absolute',
  132.                         top: '45%',
  133.                         left: '50%',
  134.                         padding: 0,
  135.                         margin: 0,
  136.                         transform: 'translate(-50%, -50%)',
  137.                     }
  138.                 },
  139.             });
  140.  
  141.             bar.text.style.fontSize = '2rem';
  142.             bar.path.setAttribute('stroke', getColor(data.actualValue, 0, data.maxValue));
  143.             bar.animate(data.value);
  144.             bar.setText(`${data.actualValue}${data.maxValue == 100 ? '%' : ''}`);
  145.  
  146.             const label = document.createElement('div');
  147.             label.style.marginTop = '10px';
  148.             label.style.color = '#ffa43d';
  149.             label.textContent = data.label;
  150.  
  151.             gaugeWrapper.appendChild(label);
  152.             statsWrapper.appendChild(gaugeWrapper);
  153.         });
  154.  
  155.         overallStatsWrapper.appendChild(statsWrapper);
  156.  
  157.         if (overallStatsWrapper.children.length > 0) {
  158.             container.append(overallStatsWrapper);
  159.         }
  160.     }
  161.  
  162.     // Geoguessr API interactions
  163.     async function fetchDuels(session, gameIds) {
  164.         console.log(`Try to fetch ${gameIds.length} games`);
  165.         const results = await Promise.all(gameIds.map(async (gameId) => {
  166.             try {
  167.                 const response = await session.get(`${API_BASE_URL}/${gameId}`);
  168.                 return response.data;
  169.             } catch (error) {
  170.                 console.error(`Failed to fetch data for game ID ${gameId}:`, error);
  171.                 return null;
  172.             }
  173.         }));
  174.         console.log(`Fetched ${results.filter(Boolean).length} games`);
  175.         return results.filter(Boolean);
  176.     }
  177.  
  178.     async function getGameIds(session) {
  179.         const gameIds = [];
  180.         let paginationToken = null;
  181.  
  182.         try {
  183.             while (true) {
  184.                 const response = await session.get('https://www.geoguessr.com/api/v4/feed/private', { params: { paginationToken } });
  185.                 const data = response.data;
  186.                 paginationToken = data.paginationToken;
  187.  
  188.                 if (!playerId && data.entries.length > 0) {
  189.                     playerId = data.entries[0].user.id;
  190.                 }
  191.  
  192.                 data.entries.forEach(entry => {
  193.                     try {
  194.                         extractGameIds(entry, gameIds);
  195.                     } catch (error) {
  196.                         console.error(error);
  197.                     }
  198.                 });
  199.  
  200.                 if (!paginationToken) break;
  201.             }
  202.         } catch (error) {
  203.             console.error("An error occurred while fetching game IDs:", error);
  204.         }
  205.  
  206.         return gameIds;
  207.     }
  208.  
  209.     function extractGameIds(entry, gameIds) {
  210.         const payloadJson = JSON.parse(entry.payload);
  211.         const time = entry.time
  212.         const payloadArray = Array.isArray(payloadJson) ? payloadJson : [payloadJson];
  213.         payloadArray.forEach(payload => {
  214.             // Test if partyId is undefined
  215.             if (payload.gameMode === 'Duels' && payload.partyId === undefined) {
  216.                 gameIds.push({ id: payload.gameId, time: time });
  217.             } else if (payload.payload && payload.payload.gameMode === 'Duels' && payload.payload.partyId === undefined) {
  218.                 gameIds.push({ id: payload.payload.gameId, time: time });
  219.             }
  220.         });
  221.     }
  222.  
  223.     // Table rendering logic
  224.     async function renderTable(session, gameIds) {
  225.         try {
  226.             const dataList = await fetchDuels(session, gameIds.map(game => game.id));
  227.             const weeklyStats = calculateWeeklyStats(dataList, gameIds);
  228.             const countryData = processGameData(dataList);
  229.             const globalStats = calculateGlobalStats(countryData);
  230.  
  231.             const tableData = transformCountryData(countryData);
  232.             const container = document.querySelectorAll("[class^='container_content']")[document.querySelectorAll("[class^='container_content']").length - 1];
  233.             console.log("Global Stats:", globalStats);
  234.             renderGauges(container, globalStats);
  235.             renderHistogram(container, weeklyStats);
  236.             createCountryTable(container, tableData);
  237.         } catch (error) {
  238.             console.error("Error fetching or processing data:", error);
  239.         } finally {
  240.             console.timeEnd('loading_games');
  241.         }
  242.     }
  243.  
  244.     function calculateWeeklyStats(dataList, gameTimeData) {
  245.         const weeklyStats = {};
  246.  
  247.         dataList.forEach((data, index) => {
  248.             const gameTime = new Date(gameTimeData[index].time);
  249.             const weekYear = `${gameTime.getFullYear()}-W${Math.ceil((((gameTime - new Date(gameTime.getFullYear(),0,1)) / 86400000) + gameTime.getDay() + 1)/7)}`;
  250.             if (!weeklyStats[weekYear]) {
  251.                 weeklyStats[weekYear] = { totalAccuracy: 0, totalRounds: 0 };
  252.             }
  253.             const player = data.teams.flatMap(team => team.players).find(player => player.playerId === playerId);
  254.             if (!player) return;
  255.  
  256.             data.rounds.forEach(round => {
  257.                 const playerGuess = player.guesses.find(g => g.roundNumber === round.roundNumber);
  258.                 if (playerGuess) {
  259.                     weeklyStats[weekYear].totalAccuracy += (playerGuess.score * 100) / MAX_SCORE;
  260.                     weeklyStats[weekYear].totalRounds++;
  261.                 }
  262.             });
  263.         });
  264.  
  265.         return Object.keys(weeklyStats).map(week => ({
  266.             week,
  267.             accuracy: (weeklyStats[week].totalAccuracy / weeklyStats[week].totalRounds).toFixed(2)
  268.         }));
  269.     }
  270.  
  271.     function processGameData(dataList) {
  272.         const countryData = {};
  273.  
  274.         dataList.forEach(data => {
  275.             const player = data.teams.flatMap(team => team.players).find(player => player.playerId === playerId);
  276.             if (!player) return;
  277.  
  278.             data.rounds.forEach(round => aggregateRoundData(countryData, round, player, data.teams));
  279.         });
  280.  
  281.         return countryData;
  282.     }
  283.  
  284.     function aggregateRoundData(countryData, round, player, teams) {
  285.         const countryCode = round.panorama.countryCode;
  286.         const playerGuess = player.guesses.find(g => g.roundNumber === round.roundNumber);
  287.  
  288.         if (playerGuess) {
  289.             if (!countryData[countryCode]) {
  290.                 countryData[countryCode] = { totalRounds: 0, totalScore: 0, totalPointDiff: 0, wins: 0, totalAccuracy: 0 };
  291.             }
  292.             let sumPointDiff = 0;
  293.  
  294.             countryData[countryCode].totalRounds++;
  295.             countryData[countryCode].totalScore += playerGuess.score;
  296.             countryData[countryCode].totalAccuracy += (playerGuess.score * 100) / MAX_SCORE;
  297.  
  298.             teams.forEach(team => {
  299.                 team.players.forEach(opponent => {
  300.                     if (opponent.playerId !== playerId) {
  301.                         const opponentGuess = opponent.guesses.find(g => g.roundNumber === round.roundNumber);
  302.                         if (opponentGuess) {
  303.                             let pointDiff = playerGuess.score - opponentGuess.score;
  304.                             sumPointDiff += pointDiff;
  305.                             if (playerGuess.score > opponentGuess.score) {
  306.                                 countryData[countryCode].wins++;
  307.                             }
  308.                         }
  309.                     }
  310.                 });
  311.             });
  312.  
  313.             countryData[countryCode].totalPointDiff += sumPointDiff;
  314.         }
  315.     }
  316.  
  317.     function transformCountryData(countryData) {
  318.         return Object.keys(countryData).map(countryCode => {
  319.             const totalRounds = countryData[countryCode].totalRounds;
  320.             return {
  321.                 country: countryCode,
  322.                 totalRounds: totalRounds,
  323.                 averageScore: (countryData[countryCode].totalScore / totalRounds).toFixed(2),
  324.                 winRate: ((countryData[countryCode].wins / totalRounds) * 100).toFixed(2),
  325.                 totalPointDiff: countryData[countryCode].totalPointDiff,
  326.                 averagePointDiff: (countryData[countryCode].totalPointDiff / totalRounds).toFixed(2),
  327.                 countryAccuracy: (countryData[countryCode].totalAccuracy / totalRounds).toFixed(2)
  328.             };
  329.         });
  330.     }
  331.  
  332.     function createCountryTable(container, tableData) {
  333.         const countryNames = getCountryNames();
  334.         const overallCountryStatsWrapper = document.createElement('div');
  335.         const titleElement = document.createElement('h2');
  336.         titleElement.textContent = "Country Stats";
  337.         titleElement.style.color = "#ffa43d";
  338.         titleElement.style.textAlign = 'center';
  339.         titleElement.style.marginBottom = '15px';
  340.  
  341.         overallCountryStatsWrapper.appendChild(titleElement);
  342.  
  343.         const table = document.createElement('table');
  344.         const thead = document.createElement('thead');
  345.         const headers = ['Country', 'Total Rounds', 'Average Score', 'Win Rate (%)', 'Total Point Diff', 'Average Point Diff', 'Country Accuracy (%)'];
  346.         const headerRow = document.createElement('tr');
  347.  
  348.         headers.forEach((header, index) => {
  349.             const th = document.createElement('th');
  350.             th.textContent = header;
  351.             th.addEventListener('click', () => sortTable(table, index));
  352.             headerRow.appendChild(th);
  353.         });
  354.  
  355.         thead.appendChild(headerRow);
  356.         table.appendChild(thead);
  357.  
  358.         const tbody = document.createElement('tbody');
  359.         tableData.forEach(row => {
  360.             const countryNameWithFlag = countryNames[row.country] || row.country;
  361.             const urlFriendlyName = countryNameWithFlag
  362.                 .replace(/[\u{1F1E6}-\u{1F1FF}]/gu, '')
  363.                 .normalize('NFD').replace(/[\u0300-\u036f]/g, '')
  364.                 .trim().toLowerCase().replace(/ /g, '-');
  365.             const countryLink = `<a href="https://www.plonkit.net/${urlFriendlyName}" target="_blank">${countryNameWithFlag}</a>`;
  366.  
  367.             const tr = document.createElement('tr');
  368.             tr.innerHTML = `
  369.                 <td>${countryLink}</td>
  370.                 <td>${row.totalRounds}</td>
  371.                 <td style="background-color:${getColor(row.averageScore, 0, MAX_SCORE)};">${row.averageScore}</td>
  372.                 <td style="background-color:${getColor(row.winRate, 0, 100)};">${row.winRate}</td>
  373.                 <td style="background-color:${getColor(row.totalPointDiff, -MAX_SCORE, MAX_SCORE)};">${row.totalPointDiff}</td>
  374.                 <td style="background-color:${getColor(row.averagePointDiff, -1000, 1000)};">${row.averagePointDiff}</td>
  375.                 <td style="background-color:${getColor(row.countryAccuracy, 0, 100)};">${row.countryAccuracy}</td>
  376.             `;
  377.             tbody.appendChild(tr);
  378.         });
  379.         table.appendChild(tbody);
  380.  
  381.         overallCountryStatsWrapper.appendChild(table);
  382.         container.appendChild(overallCountryStatsWrapper);
  383.     }
  384.  
  385.     function sortTable(table, columnIndex) {
  386.         const tbody = table.querySelector('tbody');
  387.         const rows = Array.from(tbody.querySelectorAll('tr'));
  388.         const isAsc = table.getAttribute('data-sort-direction') === 'asc';
  389.  
  390.         table.setAttribute('data-sort-direction', isAsc ? 'desc' : 'asc');
  391.  
  392.         rows.sort((a, b) => {
  393.             const aText = a.querySelectorAll('td')[columnIndex].textContent.trim();
  394.             const bText = b.querySelectorAll('td')[columnIndex].textContent.trim();
  395.             return !isNaN(aText) && !isNaN(bText) ?
  396.                 (isAsc ? aText - bText : bText - aText)
  397.                 : (isAsc ? aText.localeCompare(bText) : bText.localeCompare(aText));
  398.         });
  399.  
  400.         rows.forEach(row => tbody.appendChild(row));
  401.     }
  402.  
  403.     function getCountryNames() {
  404.         // Return a mapping object for country codes to country names
  405.         return {
  406.             'ad': 'Andorra ๐Ÿ‡ฆ๐Ÿ‡ฉ',
  407.             'ae': 'United Arab Emirates ๐Ÿ‡ฆ๐Ÿ‡ช',
  408.             'af': 'Afghanistan ๐Ÿ‡ฆ๐Ÿ‡ซ',
  409.             'ag': 'Antigua and Barbuda ๐Ÿ‡ฆ๐Ÿ‡ฌ',
  410.             'ai': 'Anguilla ๐Ÿ‡ฆ๐Ÿ‡ฎ',
  411.             'al': 'Albania ๐Ÿ‡ฆ๐Ÿ‡ฑ',
  412.             'am': 'Armenia ๐Ÿ‡ฆ๐Ÿ‡ฒ',
  413.             'ao': 'Angola ๐Ÿ‡ฆ๐Ÿ‡ด',
  414.             'aq': 'Antarctica ๐Ÿ‡ฆ๐Ÿ‡ถ',
  415.             'ar': 'Argentina ๐Ÿ‡ฆ๐Ÿ‡ท',
  416.             'as': 'American Samoa ๐Ÿ‡ฆ๐Ÿ‡ธ',
  417.             'at': 'Austria ๐Ÿ‡ฆ๐Ÿ‡น',
  418.             'au': 'Australia ๐Ÿ‡ฆ๐Ÿ‡บ',
  419.             'aw': 'Aruba ๐Ÿ‡ฆ๐Ÿ‡ผ',
  420.             'ax': 'ร…land Islands ๐Ÿ‡ฆ๐Ÿ‡ฝ',
  421.             'az': 'Azerbaijan ๐Ÿ‡ฆ๐Ÿ‡ฟ',
  422.             'ba': 'Bosnia and Herzegovina ๐Ÿ‡ง๐Ÿ‡ฆ',
  423.             'bb': 'Barbados ๐Ÿ‡ง๐Ÿ‡ง',
  424.             'bd': 'Bangladesh ๐Ÿ‡ง๐Ÿ‡ฉ',
  425.             'be': 'Belgium ๐Ÿ‡ง๐Ÿ‡ช',
  426.             'bf': 'Burkina Faso ๐Ÿ‡ง๐Ÿ‡ซ',
  427.             'bg': 'Bulgaria ๐Ÿ‡ง๐Ÿ‡ฌ',
  428.             'bh': 'Bahrain ๐Ÿ‡ง๐Ÿ‡ญ',
  429.             'bi': 'Burundi ๐Ÿ‡ง๐Ÿ‡ฎ',
  430.             'bj': 'Benin ๐Ÿ‡ง๐Ÿ‡ฏ',
  431.             'bl': 'Saint Barthรฉlemy ๐Ÿ‡ง๐Ÿ‡ฑ',
  432.             'bm': 'Bermuda ๐Ÿ‡ง๐Ÿ‡ฒ',
  433.             'bn': 'Brunei Darussalam ๐Ÿ‡ง๐Ÿ‡ณ',
  434.             'bo': 'Bolivia ๐Ÿ‡ง๐Ÿ‡ด',
  435.             'bq': 'Bonaire, Sint Eustatius and Saba ๐Ÿ‡ง๐Ÿ‡ถ',
  436.             'br': 'Brazil ๐Ÿ‡ง๐Ÿ‡ท',
  437.             'bs': 'Bahamas ๐Ÿ‡ง๐Ÿ‡ธ',
  438.             'bt': 'Bhutan ๐Ÿ‡ง๐Ÿ‡น',
  439.             'bv': 'Bouvet Island ๐Ÿ‡ง๐Ÿ‡ป',
  440.             'bw': 'Botswana ๐Ÿ‡ง๐Ÿ‡ผ',
  441.             'by': 'Belarus ๐Ÿ‡ง๐Ÿ‡พ',
  442.             'bz': 'Belize ๐Ÿ‡ง๐Ÿ‡ฟ',
  443.             'ca': 'Canada ๐Ÿ‡จ๐Ÿ‡ฆ',
  444.             'cc': 'Cocos (Keeling) Islands ๐Ÿ‡จ๐Ÿ‡จ',
  445.             'cd': 'Congo (Democratic Republic of the) ๐Ÿ‡จ๐Ÿ‡ฉ',
  446.             'cf': 'Central African Republic ๐Ÿ‡จ๐Ÿ‡ซ',
  447.             'cg': 'Congo ๐Ÿ‡จ๐Ÿ‡ฌ',
  448.             'ch': 'Switzerland ๐Ÿ‡จ๐Ÿ‡ญ',
  449.             'ci': 'Cรดte d\'Ivoire ๐Ÿ‡จ๐Ÿ‡ฎ',
  450.             'ck': 'Cook Islands ๐Ÿ‡จ๐Ÿ‡ฐ',
  451.             'cl': 'Chile ๐Ÿ‡จ๐Ÿ‡ฑ',
  452.             'cm': 'Cameroon ๐Ÿ‡จ๐Ÿ‡ฒ',
  453.             'cn': 'China ๐Ÿ‡จ๐Ÿ‡ณ',
  454.             'co': 'Colombia ๐Ÿ‡จ๐Ÿ‡ด',
  455.             'cr': 'Costa Rica ๐Ÿ‡จ๐Ÿ‡ท',
  456.             'cu': 'Cuba ๐Ÿ‡จ๐Ÿ‡บ',
  457.             'cv': 'Cabo Verde ๐Ÿ‡จ๐Ÿ‡ป',
  458.             'cw': 'Curaรงao ๐Ÿ‡จ๐Ÿ‡ผ',
  459.             'cx': 'Christmas Island ๐Ÿ‡จ๐Ÿ‡ฝ',
  460.             'cy': 'Cyprus ๐Ÿ‡จ๐Ÿ‡พ',
  461.             'cz': 'Czechia ๐Ÿ‡จ๐Ÿ‡ฟ',
  462.             'de': 'Germany ๐Ÿ‡ฉ๐Ÿ‡ช',
  463.             'dj': 'Djibouti ๐Ÿ‡ฉ๐Ÿ‡ฏ',
  464.             'dk': 'Denmark ๐Ÿ‡ฉ๐Ÿ‡ฐ',
  465.             'dm': 'Dominica ๐Ÿ‡ฉ๐Ÿ‡ฒ',
  466.             'do': 'Dominican Republic ๐Ÿ‡ฉ๐Ÿ‡ด',
  467.             'dz': 'Algeria ๐Ÿ‡ฉ๐Ÿ‡ฟ',
  468.             'ec': 'Ecuador ๐Ÿ‡ช๐Ÿ‡จ',
  469.             'ee': 'Estonia ๐Ÿ‡ช๐Ÿ‡ช',
  470.             'eg': 'Egypt ๐Ÿ‡ช๐Ÿ‡ฌ',
  471.             'eh': 'Western Sahara ๐Ÿ‡ช๐Ÿ‡ญ',
  472.             'er': 'Eritrea ๐Ÿ‡ช๐Ÿ‡ท',
  473.             'es': 'Spain ๐Ÿ‡ช๐Ÿ‡ธ',
  474.             'et': 'Ethiopia ๐Ÿ‡ช๐Ÿ‡น',
  475.             'fi': 'Finland ๐Ÿ‡ซ๐Ÿ‡ฎ',
  476.             'fj': 'Fiji ๐Ÿ‡ซ๐Ÿ‡ฏ',
  477.             'fk': 'Falkland Islands (Malvinas) ๐Ÿ‡ซ๐Ÿ‡ฐ',
  478.             'fm': 'Micronesia (Federated States of) ๐Ÿ‡ซ๐Ÿ‡ฒ',
  479.             'fo': 'Faroe Islands ๐Ÿ‡ซ๐Ÿ‡ด',
  480.             'fr': 'France ๐Ÿ‡ซ๐Ÿ‡ท',
  481.             'ga': 'Gabon ๐Ÿ‡ฌ๐Ÿ‡ฆ',
  482.             'gb': 'United Kingdom ๐Ÿ‡ฌ๐Ÿ‡ง',
  483.             'gd': 'Grenada ๐Ÿ‡ฌ๐Ÿ‡ฉ',
  484.             'ge': 'Georgia ๐Ÿ‡ฌ๐Ÿ‡ช',
  485.             'gf': 'French Guiana ๐Ÿ‡ฌ๐Ÿ‡ซ',
  486.             'gg': 'Guernsey ๐Ÿ‡ฌ๐Ÿ‡ฌ',
  487.             'gh': 'Ghana ๐Ÿ‡ฌ๐Ÿ‡ญ',
  488.             'gi': 'Gibraltar ๐Ÿ‡ฌ๐Ÿ‡ฎ',
  489.             'gl': 'Greenland ๐Ÿ‡ฌ๐Ÿ‡ฑ',
  490.             'gm': 'Gambia ๐Ÿ‡ฌ๐Ÿ‡ฒ',
  491.             'gn': 'Guinea ๐Ÿ‡ฌ๐Ÿ‡ณ',
  492.             'gp': 'Guadeloupe ๐Ÿ‡ฌ๐Ÿ‡ต',
  493.             'gq': 'Equatorial Guinea ๐Ÿ‡ฌ๐Ÿ‡ถ',
  494.             'gr': 'Greece ๐Ÿ‡ฌ๐Ÿ‡ท',
  495.             'gs': 'South Georgia and the South Sandwich Islands ๐Ÿ‡ฌ๐Ÿ‡ธ',
  496.             'gt': 'Guatemala ๐Ÿ‡ฌ๐Ÿ‡น',
  497.             'gu': 'Guam ๐Ÿ‡ฌ๐Ÿ‡บ',
  498.             'gw': 'Guinea-Bissau ๐Ÿ‡ฌ๐Ÿ‡ผ',
  499.             'gy': 'Guyana ๐Ÿ‡ฌ๐Ÿ‡พ',
  500.             'hk': 'Hong Kong ๐Ÿ‡ญ๐Ÿ‡ฐ',
  501.             'hm': 'Heard Island and McDonald Islands ๐Ÿ‡ญ๐Ÿ‡ฒ',
  502.             'hn': 'Honduras ๐Ÿ‡ญ๐Ÿ‡ณ',
  503.             'hr': 'Croatia ๐Ÿ‡ญ๐Ÿ‡ท',
  504.             'ht': 'Haiti ๐Ÿ‡ญ๐Ÿ‡น',
  505.             'hu': 'Hungary ๐Ÿ‡ญ๐Ÿ‡บ',
  506.             'id': 'Indonesia ๐Ÿ‡ฎ๐Ÿ‡ฉ',
  507.             'ie': 'Ireland ๐Ÿ‡ฎ๐Ÿ‡ช',
  508.             'il': 'Israel ๐Ÿ‡ฎ๐Ÿ‡ฑ',
  509.             'im': 'Isle of Man ๐Ÿ‡ฎ๐Ÿ‡ฒ',
  510.             'in': 'India ๐Ÿ‡ฎ๐Ÿ‡ณ',
  511.             'io': 'British Indian Ocean Territory ๐Ÿ‡ฎ๐Ÿ‡ด',
  512.             'iq': 'Iraq ๐Ÿ‡ฎ๐Ÿ‡ถ',
  513.             'ir': 'Iran ๐Ÿ‡ฎ๐Ÿ‡ท',
  514.             'is': 'Iceland ๐Ÿ‡ฎ๐Ÿ‡ธ',
  515.             'it': 'Italy ๐Ÿ‡ฎ๐Ÿ‡น',
  516.             'je': 'Jersey ๐Ÿ‡ฏ๐Ÿ‡ช',
  517.             'jm': 'Jamaica ๐Ÿ‡ฏ๐Ÿ‡ฒ',
  518.             'jo': 'Jordan ๐Ÿ‡ฏ๐Ÿ‡ด',
  519.             'jp': 'Japan ๐Ÿ‡ฏ๐Ÿ‡ต',
  520.             'ke': 'Kenya ๐Ÿ‡ฐ๐Ÿ‡ช',
  521.             'kg': 'Kyrgyzstan ๐Ÿ‡ฐ๐Ÿ‡ฌ',
  522.             'kh': 'Cambodia ๐Ÿ‡ฐ๐Ÿ‡ญ',
  523.             'ki': 'Kiribati ๐Ÿ‡ฐ๐Ÿ‡ฎ',
  524.             'km': 'Comoros ๐Ÿ‡ฐ๐Ÿ‡ฒ',
  525.             'kn': 'Saint Kitts and Nevis ๐Ÿ‡ฐ๐Ÿ‡ณ',
  526.             'kp': 'North Korea ๐Ÿ‡ฐ๐Ÿ‡ต',
  527.             'kr': 'South Korea ๐Ÿ‡ฐ๐Ÿ‡ท',
  528.             'kw': 'Kuwait ๐Ÿ‡ฐ๐Ÿ‡ผ',
  529.             'ky': 'Cayman Islands ๐Ÿ‡ฐ๐Ÿ‡พ',
  530.             'kz': 'Kazakhstan ๐Ÿ‡ฐ๐Ÿ‡ฟ',
  531.             'la': 'Laos ๐Ÿ‡ฑ๐Ÿ‡ฆ',
  532.             'lb': 'Lebanon ๐Ÿ‡ฑ๐Ÿ‡ง',
  533.             'lc': 'Saint Lucia ๐Ÿ‡ฑ๐Ÿ‡จ',
  534.             'li': 'Liechtenstein ๐Ÿ‡ฑ๐Ÿ‡ฎ',
  535.             'lk': 'Sri Lanka ๐Ÿ‡ฑ๐Ÿ‡ฐ',
  536.             'lr': 'Liberia ๐Ÿ‡ฑ๐Ÿ‡ท',
  537.             'ls': 'Lesotho ๐Ÿ‡ฑ๐Ÿ‡ธ',
  538.             'lt': 'Lithuania ๐Ÿ‡ฑ๐Ÿ‡น',
  539.             'lu': 'Luxembourg ๐Ÿ‡ฑ๐Ÿ‡บ',
  540.             'lv': 'Latvia ๐Ÿ‡ฑ๐Ÿ‡ป',
  541.             'ly': 'Libya ๐Ÿ‡ฑ๐Ÿ‡พ',
  542.             'ma': 'Morocco ๐Ÿ‡ฒ๐Ÿ‡ฆ',
  543.             'mc': 'Monaco ๐Ÿ‡ฒ๐Ÿ‡จ',
  544.             'md': 'Moldova ๐Ÿ‡ฒ๐Ÿ‡ฉ',
  545.             'me': 'Montenegro ๐Ÿ‡ฒ๐Ÿ‡ช',
  546.             'mf': 'Saint Martin ๐Ÿ‡ฒ๐Ÿ‡ซ',
  547.             'mg': 'Madagascar ๐Ÿ‡ฒ๐Ÿ‡ฌ',
  548.             'mh': 'Marshall Islands ๐Ÿ‡ฒ๐Ÿ‡ญ',
  549.             'mk': 'North Macedonia ๐Ÿ‡ฒ๐Ÿ‡ฐ',
  550.             'ml': 'Mali ๐Ÿ‡ฒ๐Ÿ‡ฑ',
  551.             'mm': 'Myanmar ๐Ÿ‡ฒ๐Ÿ‡ฒ',
  552.             'mn': 'Mongolia ๐Ÿ‡ฒ๐Ÿ‡ณ',
  553.             'mo': 'Macao ๐Ÿ‡ฒ๐Ÿ‡ด',
  554.             'mp': 'Northern Mariana Islands ๐Ÿ‡ฒ๐Ÿ‡ต',
  555.             'mq': 'Martinique ๐Ÿ‡ฒ๐Ÿ‡ถ',
  556.             'mr': 'Mauritania ๐Ÿ‡ฒ๐Ÿ‡ท',
  557.             'ms': 'Montserrat ๐Ÿ‡ฒ๐Ÿ‡ธ',
  558.             'mt': 'Malta ๐Ÿ‡ฒ๐Ÿ‡น',
  559.             'mu': 'Mauritius ๐Ÿ‡ฒ๐Ÿ‡บ',
  560.             'mv': 'Maldives ๐Ÿ‡ฒ๐Ÿ‡ป',
  561.             'mw': 'Malawi ๐Ÿ‡ฒ๐Ÿ‡ผ',
  562.             'mx': 'Mexico ๐Ÿ‡ฒ๐Ÿ‡ฝ',
  563.             'my': 'Malaysia ๐Ÿ‡ฒ๐Ÿ‡พ',
  564.             'mz': 'Mozambique ๐Ÿ‡ฒ๐Ÿ‡ฟ',
  565.             'na': 'Namibia ๐Ÿ‡ณ๐Ÿ‡ฆ',
  566.             'nc': 'New Caledonia ๐Ÿ‡ณ๐Ÿ‡จ',
  567.             'ne': 'Niger ๐Ÿ‡ณ๐Ÿ‡ช',
  568.             'nf': 'Norfolk Island ๐Ÿ‡ณ๐Ÿ‡ซ',
  569.             'ng': 'Nigeria ๐Ÿ‡ณ๐Ÿ‡ฌ',
  570.             'ni': 'Nicaragua ๐Ÿ‡ณ๐Ÿ‡ฎ',
  571.             'nl': 'Netherlands ๐Ÿ‡ณ๐Ÿ‡ฑ',
  572.             'no': 'Norway ๐Ÿ‡ณ๐Ÿ‡ด',
  573.             'np': 'Nepal ๐Ÿ‡ณ๐Ÿ‡ต',
  574.             'nr': 'Nauru ๐Ÿ‡ณ๐Ÿ‡ท',
  575.             'nu': 'Niue ๐Ÿ‡ณ๐Ÿ‡บ',
  576.             'nz': 'New Zealand ๐Ÿ‡ณ๐Ÿ‡ฟ',
  577.             'om': 'Oman ๐Ÿ‡ด๐Ÿ‡ฒ',
  578.             'pa': 'Panama ๐Ÿ‡ต๐Ÿ‡ฆ',
  579.             'pe': 'Peru ๐Ÿ‡ต๐Ÿ‡ช',
  580.             'pf': 'French Polynesia ๐Ÿ‡ต๐Ÿ‡ซ',
  581.             'pg': 'Papua New Guinea ๐Ÿ‡ต๐Ÿ‡ฌ',
  582.             'ph': 'Philippines ๐Ÿ‡ต๐Ÿ‡ญ',
  583.             'pk': 'Pakistan ๐Ÿ‡ต๐Ÿ‡ฐ',
  584.             'pl': 'Poland ๐Ÿ‡ต๐Ÿ‡ฑ',
  585.             'pm': 'Saint Pierre and Miquelon ๐Ÿ‡ต๐Ÿ‡ฒ',
  586.             'pn': 'Pitcairn ๐Ÿ‡ต๐Ÿ‡ณ',
  587.             'pr': 'Puerto Rico ๐Ÿ‡ต๐Ÿ‡ท',
  588.             'ps': 'Palestine ๐Ÿ‡ต๐Ÿ‡ธ',
  589.             'pt': 'Portugal ๐Ÿ‡ต๐Ÿ‡น',
  590.             'pw': 'Palau ๐Ÿ‡ต๐Ÿ‡ผ',
  591.             'py': 'Paraguay ๐Ÿ‡ต๐Ÿ‡พ',
  592.             'qa': 'Qatar ๐Ÿ‡ถ๐Ÿ‡ฆ',
  593.             're': 'Rรฉunion ๐Ÿ‡ท๐Ÿ‡ช',
  594.             'ro': 'Romania ๐Ÿ‡ท๐Ÿ‡ด',
  595.             'rs': 'Serbia ๐Ÿ‡ท๐Ÿ‡ธ',
  596.             'ru': 'Russia ๐Ÿ‡ท๐Ÿ‡บ',
  597.             'rw': 'Rwanda ๐Ÿ‡ท๐Ÿ‡ผ',
  598.             'sa': 'Saudi Arabia ๐Ÿ‡ธ๐Ÿ‡ฆ',
  599.             'sb': 'Solomon Islands ๐Ÿ‡ธ๐Ÿ‡ง',
  600.             'sc': 'Seychelles ๐Ÿ‡ธ๐Ÿ‡จ',
  601.             'sd': 'Sudan ๐Ÿ‡ธ๐Ÿ‡ฉ',
  602.             'se': 'Sweden ๐Ÿ‡ธ๐Ÿ‡ช',
  603.             'sg': 'Singapore ๐Ÿ‡ธ๐Ÿ‡ฌ',
  604.             'sh': 'Saint Helena ๐Ÿ‡ธ๐Ÿ‡ญ',
  605.             'si': 'Slovenia ๐Ÿ‡ธ๐Ÿ‡ฎ',
  606.             'sj': 'Svalbard and Jan Mayen ๐Ÿ‡ธ๐Ÿ‡ฏ',
  607.             'sk': 'Slovakia ๐Ÿ‡ธ๐Ÿ‡ฐ',
  608.             'sl': 'Sierra Leone ๐Ÿ‡ธ๐Ÿ‡ฑ',
  609.             'sm': 'San Marino ๐Ÿ‡ธ๐Ÿ‡ฒ',
  610.             'sn': 'Senegal ๐Ÿ‡ธ๐Ÿ‡ณ',
  611.             'so': 'Somalia ๐Ÿ‡ธ๐Ÿ‡ด',
  612.             'sr': 'Suriname ๐Ÿ‡ธ๐Ÿ‡ท',
  613.             'ss': 'South Sudan ๐Ÿ‡ธ๐Ÿ‡ธ',
  614.             'st': 'Sao Tome and Principe ๐Ÿ‡ธ๐Ÿ‡น',
  615.             'sv': 'El Salvador ๐Ÿ‡ธ๐Ÿ‡ป',
  616.             'sx': 'Sint Maarten ๐Ÿ‡ธ๐Ÿ‡ฝ',
  617.             'sy': 'Syria ๐Ÿ‡ธ๐Ÿ‡พ',
  618.             'sz': 'Eswatini ๐Ÿ‡ธ๐Ÿ‡ฟ',
  619.             'tc': 'Turks and Caicos Islands ๐Ÿ‡น๐Ÿ‡จ',
  620.             'td': 'Chad ๐Ÿ‡น๐Ÿ‡ฉ',
  621.             'tf': 'French Southern Territories ๐Ÿ‡น๐Ÿ‡ซ',
  622.             'tg': 'Togo ๐Ÿ‡น๐Ÿ‡ฌ',
  623.             'th': 'Thailand ๐Ÿ‡น๐Ÿ‡ญ',
  624.             'tj': 'Tajikistan ๐Ÿ‡น๐Ÿ‡ฏ',
  625.             'tk': 'Tokelau ๐Ÿ‡น๐Ÿ‡ฐ',
  626.             'tl': 'Timor-Leste ๐Ÿ‡น๐Ÿ‡ฑ',
  627.             'tm': 'Turkmenistan ๐Ÿ‡น๐Ÿ‡ฒ',
  628.             'tn': 'Tunisia ๐Ÿ‡น๐Ÿ‡ณ',
  629.             'to': 'Tonga ๐Ÿ‡น๐Ÿ‡ด',
  630.             'tr': 'Turkey ๐Ÿ‡น๐Ÿ‡ท',
  631.             'tt': 'Trinidad and Tobago ๐Ÿ‡น๐Ÿ‡น',
  632.             'tv': 'Tuvalu ๐Ÿ‡น๐Ÿ‡ป',
  633.             'tw': 'Taiwan ๐Ÿ‡น๐Ÿ‡ผ',
  634.             'tz': 'Tanzania ๐Ÿ‡น๐Ÿ‡ฟ',
  635.             'ua': 'Ukraine ๐Ÿ‡บ๐Ÿ‡ฆ',
  636.             'ug': 'Uganda ๐Ÿ‡บ๐Ÿ‡ฌ',
  637.             'um': 'United States Minor Outlying Islands ๐Ÿ‡บ๐Ÿ‡ฒ',
  638.             'us': 'United States ๐Ÿ‡บ๐Ÿ‡ธ',
  639.             'uy': 'Uruguay ๐Ÿ‡บ๐Ÿ‡พ',
  640.             'uz': 'Uzbekistan ๐Ÿ‡บ๐Ÿ‡ฟ',
  641.             'va': 'Vatican City ๐Ÿ‡ป๐Ÿ‡ฆ',
  642.             'vc': 'Saint Vincent and the Grenadines ๐Ÿ‡ป๐Ÿ‡จ',
  643.             've': 'Venezuela ๐Ÿ‡ป๐Ÿ‡ช',
  644.             'vg': 'British Virgin Islands ๐Ÿ‡ป๐Ÿ‡ฌ',
  645.             'vi': 'U.S. Virgin Islands ๐Ÿ‡ป๐Ÿ‡ฎ',
  646.             'vn': 'Vietnam ๐Ÿ‡ป๐Ÿ‡ณ',
  647.             'vu': 'Vanuatu ๐Ÿ‡ป๐Ÿ‡บ',
  648.             'wf': 'Wallis and Futuna ๐Ÿ‡ผ๐Ÿ‡ซ',
  649.             'ws': 'Samoa ๐Ÿ‡ผ๐Ÿ‡ธ',
  650.             'xk': 'Kosovo ๐Ÿ‡ฝ๐Ÿ‡ฐ',
  651.             'ye': 'Yemen ๐Ÿ‡พ๐Ÿ‡ช',
  652.             'yt': 'Mayotte ๐Ÿ‡พ๐Ÿ‡น',
  653.             'za': 'South Africa ๐Ÿ‡ฟ๐Ÿ‡ฆ',
  654.             'zm': 'Zambia ๐Ÿ‡ฟ๐Ÿ‡ฒ',
  655.             'zw': 'Zimbabwe ๐Ÿ‡ฟ๐Ÿ‡ผ',
  656.         };
  657.     }
  658.  
  659.     // Function to render a histogram showing weekly accuracy
  660.     function renderHistogram(container, weeklyStats) {
  661.         // reverse the array to show the most recent week last
  662.         weeklyStats.reverse();
  663.         const histogramWrapper = document.createElement('div');
  664.         const titleElement = document.createElement('h2');
  665.         titleElement.textContent = "Weekly Accuracy";
  666.         titleElement.style.color = "#ffa43d";
  667.         titleElement.style.textAlign = 'center';
  668.         titleElement.style.marginBottom = '15px';
  669.  
  670.         histogramWrapper.appendChild(titleElement);
  671.  
  672.         const chartCanvas = document.createElement('canvas');
  673.         histogramWrapper.appendChild(chartCanvas);
  674.         histogramWrapper.classList.add('chart-container');
  675.         histogramWrapper.style.marginBottom = '60px';
  676.  
  677.         container.appendChild(histogramWrapper);
  678.  
  679.         const ctx = chartCanvas.getContext('2d');
  680.         const chartData = {
  681.             labels: weeklyStats.map(stat => stat.week),
  682.             datasets: [{
  683.                 label: 'Accuracy (%)',
  684.                 data: weeklyStats.map(stat => stat.accuracy),
  685.                 backgroundColor: 'rgba(41, 205, 181, 0.5)',
  686.                 borderColor: '#29cdb5',
  687.                 borderWidth: 1,
  688.                 fill: true,
  689.                 tension: 0.1
  690.             }]
  691.         };
  692.  
  693.         new Chart(ctx, {
  694.             type: 'line',
  695.             data: chartData,
  696.             options: {
  697.                 responsive: true,
  698.                 maintainAspectRatio: false,
  699.                 plugins: {
  700.                     legend: {
  701.                         position: 'top',
  702.                         labels: {
  703.                             color: '#ffffff',
  704.                         }
  705.                     },
  706.                     title: {
  707.                         display: true,
  708.                         text: 'Weekly Accuracy',
  709.                         color: '#ffa43d'
  710.                     }
  711.                 },
  712.                 scales: {
  713.                     x: {
  714.                         title: {
  715.                             display: true,
  716.                             text: 'Week',
  717.                             color: '#ffffff'
  718.                         },
  719.                         ticks: {
  720.                             color: '#ffffff'
  721.                         }
  722.                     },
  723.                     y: {
  724.                         beginAtZero: true,
  725.                         max: 100,
  726.                         title: {
  727.                             display: true,
  728.                             text: 'Accuracy (%)',
  729.                             color: '#ffffff'
  730.                         },
  731.                         ticks: {
  732.                             color: '#ffffff'
  733.                         }
  734.                     }
  735.                 }
  736.             }
  737.         });
  738.     }
  739.  
  740.     // Initialization
  741.     function init() {
  742.         console.time('loading_games');
  743.         addCustomStyles();
  744.  
  745.         const session = axios.create({
  746.             withCredentials: true
  747.         });
  748.         // gameIds.splice(0,100) to get only your last 100 games. Change the number to get more or less games
  749.         getGameIds(session).then(gameIds => renderTable(session, gameIds.splice(0,100)));
  750.     }
  751.  
  752.     $(document).ready(init);
  753.  
  754. })();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement