The problem#
I had been playing a lot of the social-deduction game The Resistance (and the version with more roles The Resistance: Avalon) and running into the problem that many players had trouble remembering exactly what had happened in previous rounds. Between the fact that there can be several votes throughout a game of the The Resistance and the game can sometimes take up to an hour, it can be hard to remember how the votes went a couple rounds back, especially for teams that were rejected.
The solution#
I created a web app implementation of The Resistance: Avalon (source) using Django.
(Unsurprisingly, a quick web search finds that multiple other people have made their own online implementations of The Resistance: Avalon. I haven't looked at any of them except to note that most seem to require a login.)
To play, each player needs to be able to access the web app with their own device (intended for a smart phone, but anything with a web browser will work). Optionally, one or more devices can be logged into the "observer" view displaying all of the public information, to limit how much time players are actually looking at their phones while playing the game.
If enabled on the settings screen when creating a game, the public information may include a table of all votes taken so far in the game. There is also an option of whether votes are public or only vote totals are reported during the game. Either way, once the game is over, the full history table will be displayed showing how everyone voted during the game.
Since there isn't much to the game itself (without the app, I've played it with just a deck of cards), you do not use a physical copy of the game when using the app. This also means that it can be used to play the game online if you have a separate way to doing a voice chat, although it was originally intended for in-person play.
The details#
Original implementation#
I found that someone else had made a web app implementation of The Resistance: Avalon and originally started a fork of their app (the original GitHub project I forked no longer exists), adding the history table and making various minor fixes.
But I decided I disliked the design enough that I would rather rewrite the app from scratch. It was implemented using Meteor, which is a JavaScript framework focusing around magically keeping data displayed up-to-date, resulting in an opaque JavaScript-heavy web app with a dependency on MongoDB for what is a very simple application. In contrast, my rewrite uses JavaScript but doesn't require it: you can play using Lynx if you're willing to manually refresh the page to check for new information.
I did base the layout/styles on that original app, so the basic appearance is the same, and I did not come up with that part myself.
Django implementation#
I wrote my app in Django, using a more traditional SQL database to store the state. A simple turn-based game like The Resistance is not much more than a CRUD app with a small amount of business logic on top.
Each phase of the game has a different template for
its UI. The template displays the information the current player knows
about what is going on in the game and defines an HTML form for the
actions that can be taken by the current player at the current phase.
The shared in_game.html
includes the information
that's always available like the player's role (hidden unless clicked on
to avoid accidental reveals) and which past missions have passed/failed.
Making JavaScript optional#
The JavaScript comes in with the template game.html
which
queries the server every five seconds for the current game state to find
out if there's updates that should be displayed. The game state is a
JSON object representing what should be shown to the current player. By
default, if the game state has changed, the page refreshes to get a fresh
page from the server, but templates can implement handleNewStatus()
to instead update the display without refreshing if possible. For
example, in the role phase where every player is
supposed to review what role they have and mark when they have done
so, handleNewStatus()
updates the display of which players are ready
without refreshing the page.
If using a browser without JavaScript or if something goes wrong, there's a "refresh" button, which is the same as just refreshing the page, having the server rebuild the HTML of what to display to the user from scratch.
Why every five seconds?#
You'll note this is a less sophisticated version of the update mechanism
I discussed last week which uses long polling
instead of constantly querying the server. I wrote this project long
before that one, and never updated it to use long polling which is
slightly more complicated to implement. I settled on the five second
interval as a reasonable trade-off between responsiveness and wasting
resources, but for a new project I would use long polling or some other
server-push strategy like WebSockets, now that I have the example code
to copy from fear-tracker
.
Identifying players#
My app does not have any kind of login system. To join a game, you just
need the six letter code identifying the room (or the QR code that
includes that code). You select a name to be identified by and the game
gives you a URL containing a random secret_id
. As anyone with that
secret_id
can see your secret role, that should be kept secret.
But I also don't want technical difficulties to ruin a game, as if any player leaves the game, it can't be completed. So there's a recovery mechanism: if a player hasn't connected to the server recently, they're considered offline and a new device can connect as that player by joining the game and giving the name of that player. The timeout is set to ten seconds, giving the player two tries at the (automatic) every-five-second check-in before treating the player as having gone offline.
Obviously, this isn't very secure, but it's for running friendly games, so it doesn't have to be. The goal is to ensure it's not easy to accidentally log in as another player, not to make it difficult to do so on purpose.
Random player order#
Normally when playing The Resistance, the order of who is the leader for each mission progresses in seating order. As there's gameplay reasons to have a preference for or against selecting people based on their seating order relative to you, shuffling the order makes the game more interesting. But in practice, when playing in person we never stood up and selected different seats between games. But with the app, it just selects a random player order and keeps track of it for you.
Observer mode#
As the desired social experience of playing a tabletop game is generally to avoid everyone staring at their phones, I wanted to design the app so that as much as possible, players would only look at their phone when needing to take an action in the game. One idea I had, but never implemented, was using vibration to notify players when they actually had something to do, although, for the most part, everyone is voting at once, so notifying individual users of when they specifically need to take an action doesn't seem like it would help much.
The workaround is the observer mode, which is intended to be put up on a large display like a television that's visible to all of the players. As that displays the game status, people wouldn't be looking at their phones for that information. Hopefully that would make using the app feel more social.
Play experience#
By the time I completed the app, my friend group had started getting tired of the game, so it never actually saw much use. I only ever used the app with full history and showing everyone's votes (not just counts) once, and it both played much faster and the resistance team found it way too easy to identify the spies. Hopefully it would work better with the option to show only the vote counts enabled, but with the full information in a table in front of us, the game turned into a logical deduction, not social deduction, game.
Comments
Have something to add? Post a comment by sending an email to comments@aweirdimagination.net. You may use Markdown for formatting.
There are no comments yet.