Partially to understand better how those modern fancy JavaScript frameworks work and partially to be hopefully useful to other players, I wrote a JavaScript proof of concept that allows you to see some basic statistics of any player.
It works as follows: open the dev tools in your browser (usually this is done by pressing F12
). Go to the console and copy and past all the code below. At the top of the page, a small form should show up where you can enter any username and see some basic info: how many times they chose each faction, what their average score is with that faction and how many times they won with that faction. I only consider finished non-cancelled Gaia Project games where nobody dropped out.
If you've removed everything, run statistics();
again in the console. If you ever refresh the page, you need to copy and paste the full code again.
function statistics() {
const factions = ['terrans', 'lantids', 'nevlas', 'itars', 'firaks', 'bescods', 'taklons', 'ambas', 'xenos', 'gleens', 'geodens', 'baltaks', 'ivits', 'hadsch-hallas']
let userId;
let username;
let showGlobalStats = false;
function getRequest(url, callback, args) {
fetch(url).then(response => response.json())
.then(data => callback(data, args));
}
function loadGameData(userData) {
userId = userData._id;
if(!userId) return;
getRequest('https://www.boardgamers.space/api/user/' + userId + '/games/count/closed', paginateThroughGames)
}
function paginateThroughGames(numberOfGames) {
if(showGlobalStats) {
getRequest('https://www.boardgamers.space/api/game/closed?count=100&skip=0', loadNextGames, {existingData: [], currentSkip: 0, totalGames: numberOfGames});
} else {
getRequest('https://www.boardgamers.space/api/game/closed?user=' + userId + '&count=100&skip=0', loadNextGames, {existingData: [], currentSkip: 0, totalGames: numberOfGames});
}
}
function loadNextGames(newData, {existingData, currentSkip, totalGames}) {
const mergedData = existingData.concat(newData);
if(currentSkip + 100 >= totalGames) {
filterAndSplitPlayerCount(mergedData);
} else {
newSkip = currentSkip + 100;
if(showGlobalStats) {
getRequest('https://www.boardgamers.space/api/game/closed?count=100&skip=' + newSkip, loadNextGames, {existingData: mergedData, currentSkip: newSkip, totalGames: totalGames});
} else {
getRequest('https://www.boardgamers.space/api/game/closed?user=' + userId + '&count=100&skip=' + newSkip, loadNextGames, {existingData: mergedData, currentSkip: newSkip, totalGames: totalGames});
}
}
}
function filterAndSplitPlayerCount(gameData) {
const gaiaCompletedOnlyData = filterGameData(gameData);
if(showGlobalStats) {
analyseGameData(gaiaCompletedOnlyData, 'All players - all')
} else {
analyseGameData(gaiaCompletedOnlyData, username + ' - all')
}
for(let playerCount=2; playerCount <= 4; playerCount++) {
if(showGlobalStats) {
analyseGameData(gaiaCompletedOnlyData.filter(game => game.players.length === playerCount), 'All players - ' + playerCount + ' players');
} else {
analyseGameData(gaiaCompletedOnlyData.filter(game => game.players.length === playerCount), username + ' - ' + playerCount + ' players');
}
}
}
function analyseGameData(gameData, title) {
const gaiaCompletedOnlyData = gameData;
const factionDataToDisplay = {};
const countChosen = 'Times chosen';
const averageScore = 'Average score';
const wins = 'Wins';
const winPercent = 'Win %';
const total = 'total';
for(const faction of factions) {
factionDataToDisplay[faction] = {};
factionDataToDisplay[faction][countChosen] = 0;
factionDataToDisplay[faction][averageScore] = 0;
factionDataToDisplay[faction][wins] = null;
factionDataToDisplay[faction][winPercent] = null;
}
factionDataToDisplay[total] = {};
factionDataToDisplay[total][countChosen] = 0;
factionDataToDisplay[total][averageScore] = null;
factionDataToDisplay[total][wins] = null;
factionDataToDisplay[total][winPercent] = null;
for(const game of gaiaCompletedOnlyData) {
for (const player of game.players) {
if(showGlobalStats) {
factionDataToDisplay[player.faction][countChosen] += 1;
factionDataToDisplay[player.faction][averageScore] += player.score;
factionDataToDisplay[player.faction][wins] += didPlayerWin(game, player.score);
factionDataToDisplay[total][countChosen] += 1;
factionDataToDisplay[total][averageScore] += player.score;
} else if(player._id === userId) {
factionDataToDisplay[player.faction][countChosen] += 1;
factionDataToDisplay[player.faction][averageScore] += player.score;
factionDataToDisplay[player.faction][wins] += didPlayerWin(game, player.score);
factionDataToDisplay[total][countChosen] += 1;
factionDataToDisplay[total][averageScore] += player.score;
factionDataToDisplay[total][wins] += didPlayerWin(game, player.score);
}
}
}
for(const faction of Object.keys(factionDataToDisplay)) {
if(factionDataToDisplay[faction][countChosen] > 0) {
factionDataToDisplay[faction][averageScore] /= factionDataToDisplay[faction][countChosen];
factionDataToDisplay[faction][averageScore] = factionDataToDisplay[faction][averageScore].toFixed(1);
factionDataToDisplay[faction][winPercent] = (factionDataToDisplay[faction][wins] / factionDataToDisplay[faction][countChosen] * 100).toFixed(1) + '%';
} else {
factionDataToDisplay[faction][averageScore] = null;
}
}
if(showGlobalStats) factionDataToDisplay[total][winPercent] = null;
const dataToDisplay = {title: title, data: factionDataToDisplay};
displayData(dataToDisplay);
}
function filterGameData(gameData) {
const gaiaGames = gameData.filter(game => game.game.name.toUpperCase().trim() === 'gaia-project'.toUpperCase());
const gaiaGamesNonCancelled = gaiaGames.filter(game => !game.cancelled);
const gaiaGamesNonDroppedOrCancelled = gaiaGamesNonCancelled.filter(game => !someoneDropped(game));
return gaiaGamesNonDroppedOrCancelled;
}
function someoneDropped(game) {
for(const player of game.players) {
if(player.dropped) return true;
}
return false;
}
function didPlayerWin(game, currentPlayerScore) {
for(const player of game.players) {
if(player.score > currentPlayerScore) return false;
}
return true;
}
function displayData(data) {
const table = document.createElement('TABLE');
const rowFactions = document.createElement('TR');
rowFactions.appendChild(document.createElement('TH'));
for(const faction of Object.keys(data.data)) {
const headCell = document.createElement('TH');
headCell.innerHTML = faction;
rowFactions.appendChild(headCell);
}
table.appendChild(rowFactions);
const rowProperties = Object.keys(data.data[factions[0]]);
for(const rowProperty of rowProperties) {
const rowElement = document.createElement('TR');
const rowNameElement = document.createElement('TD');
rowNameElement.innerHTML = rowProperty;
rowElement.appendChild(rowNameElement);
for(const faction of Object.keys(data.data)) {
const cellValueElement = document.createElement('TD');
cellValueElement.innerHTML = data.data[faction][rowProperty];
rowElement.appendChild(cellValueElement);
}
table.appendChild(rowElement);
}
// styling:
table.classList.add('statistics-table');
if(document.getElementById('stat-loading-indicator')) document.getElementById('stat-loading-indicator').remove();
document.getElementById('my-statistics').insertAdjacentHTML('beforeend', '<h3>' + data.title + '</h3>');
document.getElementById('my-statistics').insertAdjacentElement('beforeend', table);
}
function getStatisticsForUser() {
showGlobalStats = false;
username = document.getElementById('stat-input-username').value;
getRequest('https://www.boardgamers.space/api/user/infoByName/' + username, loadGameData);
}
statistics.getStatisticsForUser = getStatisticsForUser;
function removeAll() {
document.getElementById('my-statistics').remove();
document.getElementById('customTableStyle').remove();
}
statistics.removeAll = removeAll;
function seeAllStats() {
showGlobalStats = true;
document.getElementById('my-statistics').insertAdjacentHTML('afterbegin', '<h3 id="stat-loading-indicator">Loading...</h3>')
getRequest('https://www.boardgamers.space/api/game/stats', (data) => paginateThroughGames(data.finished));
}
statistics.seeAllStats = seeAllStats;
// styling element:
const customTableStyle = document.createElement('STYLE');
customTableStyle.setAttribute('id', 'customTableStyle');
customTableStyle.innerHTML =
`
.statistics-table {
text-align: center;
margin-bottom: 10px;
transform: scale(0.8);
transform-origin: left;
}
.statistics-table tr {
line-height: 2;
}
.statistics-table tr:nth-child(odd) {
background-color: #DCDCDC;
}
.statistics-table th {
padding-left: 12px;
padding-right: 12px;
}
`
document.getElementsByTagName('head')[0].appendChild(customTableStyle);
document.getElementById("navbar").insertAdjacentHTML('beforebegin', '<div id="my-statistics"> <form onsubmit="statistics.getStatisticsForUser(); return false"><input type="text" placeholder="username" id="stat-input-username"><input type="submit" value="See statistics!"></form> <button onclick="statistics.seeAllStats()">Golbal statistics</button> <button onclick="statistics.removeAll()">Remove all</button> </div?');
}
statistics();
I'd like to hear suggestions on how to improve this, but note that I do this in my free time so it might take a while before I pick it up (if I want to in the first place). Of course, feel free to edit it yourself to make it do whatever you want.
@coyotte508: is there a reference for the API available? For example, I currently fetch all games of a player with 'https://www.boardgamers.space/api/user/' + userId + '/games/closed?count=999999&skip=0'
and I don't know if the value of 999999
is respected. When leaving count
out, it only returns 20 games. Is there a way to always get all the games of a player?
Edit 1 (2020-07-20): the script should now also work for users who have played more than 100 games.
Edit 2 (2020-07-21): the tables now also show win percentages.
Edit 3 (2020-07-21): a table for each player count, total stats on each table
Edit 4 (2020-07-21): global statistics! It takes a while to load, so don't spam the Global statistics button, so we don't overload the API.
Edit 5 (2020-09-23): fix broken script by filtering out games that were scheduled but not started, due to a lack of players, by using the cancelled
field
Edit 6 (2020-09-23): change requested endpoints to avoid the same bug as in Edit 5
when looking up individual players