Steam Board Game Notification Bot in Discord

I’ve been playing a board game online with some friends. Since we’re spread across the world, we all take our turns at different times. The board game runs through Steam, and Steam’s turn notifications are unreliable and easy to miss. So the easiest way to see if it’s your turn is to load up the game. However, that can be frustrating to take the time to load it up only to see it’s not your turn yet. There must be an easier way to quickly and easily see whos turn it is.

That’s where my friends (mainly jcsung) and I came up with a discord bot that will check whos turn it is. Any time it’s a new persons turn it posts a notification to a discord channel. If it’s your turn you can be @ tagged. That way it takes minimal effort to see whose turn it is, and you get a persistent notification when it’s your turn.

I refrain from naming the game or releasing the code here. I’m not sure whether my use of the game’s API is condoned, and I don’t want it to get shut down.

Method

Technologies used:

Steam on the Raspberry Pi

We setup a raspberry pi with its own running Steam client and account that owns the board game. It needs to be a separate account that owns the board game, rather than an instance of one of our own accounts, so that it doesn’t interfere when the player launches the game on their own machine. We tried running the bot as the notorious Steam test game SpaceWar (480), but that didn’t work, so we needed to buy the actual game for the account. We were also lucky that in the board game’s API, any user can access the data for a game being played, and not just the players in the game.

The first step our bot does is authenticate with the locally running Steam client and get an access token, using SteamworksPy.

steamworks = STEAMWORKS()
steamworks.initialize()
session_ticket = steamworks.Users.GetAuthSessionTicket()

Game API

We used Fiddler to capture the API traffic between the game and its servers to understand what API calls were needed to get game info. The http responses are in JSON, and with some experimentation we got what the important info is, specifically: the authentication protocol, whose turn it is, whether the game has started, whether the game has completed, and how much time is left in the turn.

We use the session ticket from Steam to authenticate with the game’s servers using the http api. After authenticating, it sends a request to the server every 5 minutes to get the game state, and parses whose turn it is from the response. When it detects a new person’s turn it posts to the discord channel, using nextcord. Players can also register to be @ tagged when it’s their turn.

Results

For myself and others, this bot has made playing a more enjoyable experience. I don’t need to load the game to see whose turn it is and I get a notification through discord when it’s my turn.

This bot was developed while a game that was taking especially long was in progress, and part of the annoyance with the game’s speed lead to this bot. The bot was planned, built, debugged and deployed all while that game was continuing to be played.

Event Date Turn number
Game Started 2021-11-16 0
Bot fully deployed 2022-01-14 106
Game Over 2022-02-10 170
Before deployment 1.8 turns/day
After deployment 2.4 turns/day

The table above is the timeline of the bot’s deployment during the game. Turns are counted as an individual player action, either in regular turn order, or an out-of-turn action by a player that’s triggered by another player’s action.

The timeline of the game was accelerated by about a third with the use of the bot. Moreover, it saves the one or two minutes of loading the game each day to see whose turn it is, often only to discover that it’s not yet your turn. Now we only need to briefly check for a discord notification.

The bot seems to be successful from a qualitative improvement of the gameplay experience and from quantitative time savings and acceleration of gameplay.

Choosing the Optimal Turn Order for Board Games

For those of us that play board games with friends online, especially games that take multiple days, waiting for friends to make their turn can stir some impatience. While waiting for my turn in a game against friends across 3 countries and 4 time zones I realized our turn order was less than ideal given our schedules. This presented a great opportunity to work on my React skills and make our games run faster. Thus I created a board game scheduler to choose the optimal turn order for board games that minimizes wait time based on everyone’s schedules and time zones.

Theory

board_game_schedule

The image above is a hypothetical schedule. Each number represents the hour of that person’s local time with UTC time at the top. Green squares represent when that person is available to play. It’s quickly apparent that if the turn order is top down then two rounds can be played per day, but if it’s bottom up then there is less than one round per day. The goal is to find the most turns per day possible.

I turned to Graph Theory. Each playable hour (green square) is a vertex in a graph, and each edge points to the next player’s playable hour. The weight of each edge is how many hours away the next player’s turn is. The rules are:

  • A green–or available–square points to the square below it with an edge weight of 0.
  • An unavailable square points to the square to the right of it with an edge weight of one.
  • When an edge points beyond the bottom or right of the grid, it wraps back to the top or left.
  • From the above, each vertex can only point to one other vertex, but each vertex can have bewteen zero and two vertices pointing to it.
  • All vertices are adjacent to their connecting vertices.

We can try different permutations of player orders, construct the graphs, and pick the graph and turn order that provides the optimal solution. It’s tempting to phrase the graph as a shortest path problem, but that unfortunately doesn’t work. What we want to do is find the cycle with the maximum rounds per total weighted path. That means the cost function is dependent not just on weights, but also the number of vertices visited. However, there are some other tricks we can use.

Because each vertex only points to one other vertex and all vertices are adjacent to their connecting vertices, it creates some useful features for solving this:

  • Multiple closed and separate cycles (of the same length) can exist in one graph.
  • However, any closed cycle is equal to the optimal solution for the graph.

The first point is simple: you can have two possibilities of timing as long as they have the same path length. For example, in the image below there are two cycles for a turn order running from top to bottom. One starting at 11 and one at 19 in Japan. The second feature in the list is key to solving the problem: any closed cycle is an optimal cycle. If there was a more and less frequent cycle in the same graph, the more frequent would intercept with the less frequent (because all vertices are adjacent to their connecting vertices), and thus the less frequent one would become part of the more frequent one.

schedule_2_closed_cycle

The solution is then to traverse the map until a cycle is found, because any cycle is an optimal cycle. Once a cycle is found, recurs back to the meeting point and subtract the edge weights (hours) and turns to get the amount of hours and turns in the cycle. With that you can get the optimized rounds per day.

The traversal of the map will need to be completed for each permutation of player order. We can start with the first player on the schedule without permutation because turn order is what matters, not who starts. Unfortunately, this permutation of players gives a run time of (n-1)!. If too many players are added, the algorithm becomes prohibitively slow. For now this is working well enough for my friends and I, but possible future solutions might be to reuse or not recreate the map traversal for each permutation.