tl;dr check out the scoreboard

A little over a year ago, I released the first draft of my MTG pod scoring algorithm. I decided it was time to revisit the rankings and polish that a little bit. I thought a scoreboard would make the rankings easier to access. Backing the app with Google Sheets kept the scoreboard easy to share securely among friends, but let me update the rankings every time a game was added to the log. Before version 2, the scoring script required a manual run to update the rankings and it’s not clear exactly how they’re calculated or where they’re at historically, which isn’t great for a scoring algorithm where you want players to track their progress. This refactor presented an opportunity to update the schema to reflect some ideas mulled over in my previous post and to make the scores more accessible to the rest of the pod.

Algorithm Updates

Notable changes to the algorithm for this leaderboard include:

  • the ability to record table zaps and draws in the Google Sheet
  • there’s a hard cap of 6 players per game
  • players are rewarded based on the average skill of all the players

Reward Curves

In version 2, the winner’s rating delta is calculated from the average of the other players scores and their final position in the game.

game.RankAverage = totalScores / numPlayers

Their position in the rankings determines their reward amount. In the line below, the fourPlayers array contains an exponentially decaying reward curve for 4 players, and idx refers to the player’s place in the game. Once the reward amount is determined, the ratings delta is calculated from the rank average and the player’s current score.

ratingsDelta = elo.RatingDelta(playerScore, game.RankAverage, fourPlayers[idx])

Then the player’s score is updated according to that ratings delta.

scores[player] += ratingsDelta

The reward curves are based on the number of players in the game and exhibit exponentially decaying rewards.

// reward curves for different numbers of players in a game
var (
 twoPlayers   = []float64{1.0, 0}
 threePlayers = []float64{1.0, 0.5, 0}
 fourPlayers  = []float64{1.0, 0.5, 0.25, 0}
 fivePlayers  = []float64{1.0, 0.5, 0.25, 0.12, 0}
 sixPlayers   = []float64{1.0, 0.5, 0.25, 0.12, 0.05, 0}
)

The first version of this algorithm used a cascading approach to calculating the ratings deltas which resulted in similar but not exactly identical scores.

Google Sheets API

The entire thing is backed by Google Sheets. Using their API was surprisingly slick. 8/10 would use again. The best part is that it’s just a spreadsheet, so you’re getting CSV data as the default, which is amazing because then you can then start working with it however you want. API Key Authentication was really easy, too. The configuration handling around the API key ended up being a bigger problem to solve than switching to API key authentication in the first place.

Deployments

The server is hosted on a DigitalOcean VPS. The deploy script is an rsync script that copies over the project directory and then runs docker-compose commands to restart the service. It only relies on Google Sheets, and it only serves static assets, so it’s been a pretty reliable server. ./script/deploy.sh is all it takes to push an update to the server.

Source

As always, the full code is located at my github