Plat's Works |
|
Plat's Works |
|
|
|
|
|
|
|
|
Wolves of Mercia, a Tabletop Simulator port
Introduction
In this article, I will discuss a project I did for hire – a card game to port to Tabletop Simulator. My goal is to discuss the often-overlooked and underexplained challenges of systems design, and to show how tools available to a programmer can make up programs that end up actually working. The clarity and transparency of card game rules is an excellent opportunity to bridge the gap between writing code that “executes a few commands” and eventually getting to “a program that performs a complex task”.
Wolves of Mercia is a card game for groups of 5 to 10 players. Players are dealt a pair of cards, one Public (also called Day card, or Villager card) and one Secret (also called Night card), from two separate decks, and each determines what they can or have to do during the two phases of the game – day and night. These day-night rounds continue up to 5 times until one player (or a team, in some cases) wins. The victory condition is written on the Secret cards, and their Day cards give them a special action or ability to hopefully aid them in this goal. During the game, the players will use these actions and abilities to achieve help themselves or harm opponents while pretending they’re helping others, and while also trying to not make themselves the biggest enemy.
While it’s pretty sensible to compare the game to the more famous Mafia psychological/party game, it plays a little differently due to everyone having their own victory conditions and significantly looser teamwork incentives – it’s a lot less of a Mafia’s “villagers versus mafiosos” and much more of a “many free agents, some of which just want everyone else dead”. This leads to a slightly different game feel, as the player priorities shift from “how do I not die” to “how do I win” as the game progresses. Additionally, the game has several other mechanics to keep it interesting even when a player dies – something that Mafia is often criticized for failing to do.
Here’s a few screenshots of the game before and after setup.
The Offer and Promise
I was approached by the game’s designer and was asked to digitize this game for Tabletop Simulator, a multiplayer 3D sandbox for board and card games. I was also to program the game – write scripts in Lua – instructions and mechanisms for Tabletop Simulator to manage the game state itself, to make some of the interactions during game setup or play easier. It’s a straightforward process in theory, but the card game’s unique rules sometimes threw a curveball, and I had to figure out how to solve that. In this article, I’d like to walk the reader through my process, the aforementioned curveballs, and how I handled them. Digitizing boardgame rules and putting them into code and algorithms is a fairly unique activity, as game mechanics can be very imaginative, and sometimes a sentence or two in a rulebook can turn into tens of steps of a computer algorithm. A bunch of cardboard cutouts on another sheet of painted cardboard easily serve as the equivalent of computer memory to a table of players, and elements of artwork like arrows, rectangles denoting groups, or shapes denoting slots can convey incredible depths of complexity of interactions or sequences. In a computer, though, all of that has to be organized in a completely different way, and a computer will never make the “Oh, I get it now” leaps of understand that a human player would.
Consequentially, when it comes to teaching a computer to run a card game, a lot of wonderful design techniques easily understood by players fall apart completely, and have to be broken down to the smallest units understood by a computer, and then built back up to perform the exact same actions. There are no quick or convenient tools to teach a computer about “slots” on a cardboard with artwork, nor the arrows within, or natural language text on a card. The slots must be simplified down to a point or an area where a specific game piece may or may not be present, or perhaps it’s one of several pieces. The aforementioned arrows have to be adapted into either links between such slots, or steps in an algorithm, or perhaps they aren’t useful altogether to the boardgame when it’s being run by code. In a way, the game must be taken apart to its smallest moving parts and rebuilt back up.
Several years later, as I write this article, I look back on the project fondly. I do want to point out to all aspiring programmers that while I was working on the project, I felt anger, I felt frustration, and often I felt confused and unconfident. Sometimes I had to step away for several days, I wanted to give up, I wished to have never agreed to this project. It can be annoying and it can be unpleasant, but a lot of engineering, both real-world and software, can be viewed as a process of fixing problems until you run out of problems to fix. Problems to be broken down into smaller problems, fixed one at a time. On my side, I had a uniquely convenient circumstance – the project being a boardgame with a rulebook that already was printed and sold, meaning I wouldn’t have any surprises due to rules changing mid-project. It didn’t save me from not fully planning through every possibility, though, and I still had to redo some systems to account for some rules discoveries during the project – I simply didn’t read and internalize the game rules completely, and couldn’t plan for every tiny situation in advance because of this. Sometimes programming projects can feel improvisational. They must be finished in a finite time. We can’t know everything, and we can’t always plan for everything either. And yet, a few weeks of work can save us a few days of planning, as the saying goes. Point is, it’s great to plan, but you can’t always plan for everything, and in many cases you just didn’t. In this project, I honestly would’ve strongly benefitted from making a flowchart for every possibility, but that could’ve doubled or tripled the project length. And sometimes the best way to find a good design is to make it wrong first, and feel every sharp corner and “that-could’ve-been-done-better”. When it comes to programming, no amount of plans, diagrams, checklists or notes will get a computer to do anything – only code will. One must choose how much they’re going to plan, and how much they’re going to actually do – because only doing gets results, but planning makes doing much, much easier.
The Game and its Predictable Challenges
Wolves of Mercia is a social deduction game, with public and hidden roles. As with most programming tasks, it helps to break down the project into smaller bits, and also separate these bits. This particular game’s process nicely breaks down into two categories of “things that happen always” and “things that happen sometimes”.
Let’s start with things that always happen – in every play session, every time. Every player is dealt two cards describing their Day and Night abilities, things they can do during one half of a game round. Every single card in these two decks has a number in the corner that denotes its initiative – the order in which players get to perform their action. During Day, players will act in order of increasing initiative on their Day card. During the Night, in the physical game everyone closes their eyes, and a player will count up from 1 to 12 (the highest Night card initiative) with 10-15 seconds pauses, prompting the players with that initiative number on their Secret card to wake up – open their eyes and do an action. Some roles have multiple people with the same number, and thus they waking up together, hopefully acknowledging each other as allies (or with some roles, as rivals) through eye contact. Night Actions involve moving around tokens or markers, and swapping or flipping player Day cards facedown to signify they’ve been killed by Night roles that can kill. The following Day, killed players may choose to become a Phantom, yet another role that could grant them special abilities or victory conditions. The game rules enforce a special procedure for building the Secret deck that’s dealt out to player – ensuring some roles, such as the Werewolf or a specific amount of Cultists depending on player count, is always present.
These mechanics would be sensible to implement first, as they’re the core and the skeleton – everything else in the game happens within, around, or from them. There are also some opportunities to improve on the ideas behind the game that aren’t possible in physical play. As any boardgame setup is a sequence of rigid instructions without player decisions, it makes sense to fully delegate this task to the computer – Tabletop Simulator’s scripting engine in this case. Uniquely to Wolves of Mercia and games with similar social deduction mechanics, it’s also possible to keep track of Secret roles in the game, and wake them up directly instead of the count-up method by a dedicated human moderator recommended for physical play – speeding up and streamlining play significantly. Some other physical card games have already done that, in fact, via a companion app for mobile devices that can even narrate the games. Once a system for recognizing the player turn order during Night is in place, it wouldn’t be too hard to also extend it to the Day phase – tracking player roles, calculating the turn order and displaying it.
The specific role deck is straightforward to implement, as the rulebook pretty much outlines the entire process – you must include these roles (Werewolf, Cultists depending on player count), maybe include these ones (Lovers), and then add more at random until the deck has enough cards in it, again depending on player count.
Day and Night phases was implemented by having special spots next to players where the appropriate role card is placed. Each card is then named, and a name-value collection was made – looking up a card’s name will return its initiative number. The players are then sorted by their role’s initiative. For the Day cards, all that remains is to display the order (and set up Tabletop Simulator turn order, to have the game help out the players). For Night cards, it’s the same idea, but a bit more involved – the players must be woken up, a timer has to be started allowing them to act, and then they must be blindfolded again. Finally, there must be routines to start the Night (blindfold everyone), and to end the Night (unblindfold everyone).
Next up, there are things that may or may not happen in a given session, but with enough playtime, all of them will. And all of them must be implemented for the game to be considered fully functional. Some characters require special tokens to be made available to them, or require special setup changes. This is a surprisingly complex situation for programming, as these can involve both shared and bespoke behaviours. Half of the “special care required” cast only needs things to be given to specific players during game set up. Some Night roles can only exist if some other specific Night roles are already present (Lonely Heart only makes sense in games with Lovers, and Hierophant relies on Cultists being in the game – although this is a double special case that cancels itself out as Cultists are always present), and a few roles have a continuous effect which does things as the game runs.
A convenient fact is that in discussing “there are so many moving parts to special roles”, we already broke them down into different groups that can be tackled separately – as I said before, half of them just need special things given to players during set up. And so, the previously implemented systems for things that always happen it’s straightforward to add some things that branch out with a few extra steps – some before set up, some during, some after.
To give roles their extra things, all that’s needed is several extra tables, tying together a named role (using the previously-made system for checking a role in front of the player) to a table denoting a list of object references it needs, and where it needs to go. Note that even though it’s a very modest “object and placement”, it still ends up being a format where the stored data has to conform to a specific standard layout, so it deserves some thought put into it – I went with a two-item list (Lua table, in this case, because Lua only has tables) with the object first, and placement later. “Take this object and place it here” felt like a sensible arrangement when that’s all it needs to do.
Boardgames are unique to program because while the individual interactions are simple, they’re also very specific – and common programming advice is to try to write code that’s general. Twisting boardgame rules into general tasks that can be written in code that’s easy to manage, and then keeping all the cases for specifics organized is part of the job. Every token could require being moved to a specific place, and that makes for a simple and straightforward program. However, often there are dozens of interactions like these, and without careful organization it’s very easy to end up with a confusing, hard-to-follow mess.
The Unpredicted (I didn’t plan enough) Challenges
It’s rarely possible to plan for everything, especially when implementing a complex boardgame by yourself. It’s one of the reasons why writing clear, expandable and maintainable code is so important.
In this project, two characters who blindsided me and lead to a whole bunch of painful reworks are the Widow and Courtesan. Their requirements go beyond the simple “deal a card, maybe with some extra stuff given”, and thus require the special care – I had to modify already existing systems to make these two work.
The Widow starts with an extra Secret card that can be swapped to during play. Since every card uses a dedicated spot for it (as an interface for the scripts to know where to look for stuff, and for players to know where to put stuff so it’ll be known), the extra card had to be dealt specially to Widow. This, luckily, was straightforward using the existing system for dealing extra stuff. I was also very happy to realize that, in the game rules, the extra Secret card is basically completely out of play unless brought in by Widow’s ability – so I didn’t have to include the extra Secret mat in the rest of the program, and I didn’t have to add any special cases for it being this special Widow card. I wasn’t so lucky with the other aspect of this rule – the extra Secret card meant that the code putting together the deck of Secrets had to run in a special mode with one extra card. This additionally meant that it had to run after Day cards are dealt – not a big deal if accounted for, but could require a whole extra layer of code to schedule these two things to happen one after another, with data being recorded and transferred between them. And all of this headache from a statement that’s basically “buy 1 get 1 free”. I’ll leave it to the reader to make a judgement on whether this is an illustration for efficiency of spoken language, the lack of computer capability for interpretation, or the complexities of going from one to the other.
The Courtesan is allowed to wake up during any Night round, to spy on the dealings of whatever Secret role is up to no good. The obvious first special case is with the code responsible for running the Night – it now needs to know if there’s a Courtesan, and if there is, to read her desired player-inputted Night initiative number, and make sure to wake her up at that point. With some code to do exactly those things, it’s pretty simple to insert her into the table that’s composed for the Night, and that’s enough for it to work. Of course, this involved special code for the Night wakeup order program to specifically know if a Courtesan is present, and then to parse the number. The second part of having this card work is the user interface – a player has to be able to let the systems know when they’d like to wake up, on whom they’d like to spy. There were many options I considered – typing in chat commands, renaming the card or typing into its description. All of these options felt like they wouldn’t feel as clean or polished as having a small circle, matching the boardgame’s artstyle, to type in the number. Unfortunately, this required wrangling with Tabletop Simulator’s XML UI system, which is fairly inflexible and annoying to work with. The concept idea was ready in minutes – it’s an image with a text box on top. To get it looking, feeling and working well was an extra hour of tweaking the widths, heights, positions and font options. One unique thing about the Courtesan is that at least a portion of its code must be on the card itself, as that’s how Tabletop Simulator manages scripts, and specifically custom UI elements. This meant that the Courtesan also needs to have code to support that UI, and specifically a way for the main script to retrieve the inputted number. I implemented this with a simple getter script, although Tabletop Simulator’s UI inputs are weird and require extra care to properly work. You can’t retrieve the text typed into textboxes with the API at will, so instead you have to rely on a function that’s called every time a UI changes, and then process it with your script – since in my case it was just a number, it was a simple case of casting it to int, making sure it’s within a sensible range, and storing it as a variable. A small amount of extra work, but worth mentioning for completeness.
The Visual Flair and Usability Touches
Of course, all of these aforementioned mechanisms help a lot in making the game start quickly and run smoothly, but that makes it easy for a user to take them for granted. In some ways, this is the highest praise systems like these can earn – when you’re working behind the scenes, usually people mention you only when things go wrong. But for me, there were some relatively easy-to-reach visual touches I could add to strengthen the game’s theme, and alleviate some of the pain points inherent to its design.
Let’s start with the problem – during the night phase, everyone has to be quiet and only gets a small fraction of a night’s time to perform their Secret role’s ability. This is boring. However, there are multiple steps I’ve taken to improve upon this. The first is to allow players during the night to voluntarily end their night phase with a press of a button. Some abilities require only to look at other players, and some require moving stuff around – there’s a disparity how much time every Secret role requires, so why waste time? Adding a button that players can press to end their round solved this issue. In addition, a nice design opportunity showed up – I could make the night timer and the button the same thing! So I made a circle with a small notch, made it rotate, and that was both the timer and the button.
The lighting has a huge impact on how any scene looks and feels, so it also was a good place to get some cheap flair. Thankfully Tabletop Simulator comes with a pretty easy programming interface for its in-game lighting, and I was able to come up with nice values for Day and Night lighting (to reflect what’s going on in the game), and then write them down as a few constants in the script. With a simple linear interpolation between the values of the two and some helper functions to invoke the transitions, it was ready. Even today, few games utilize a day-night cycle, and even fewer games on Tabletop Simulator feature anything happening in response to it, so the light dimming for the night still excites and surprises first-time players.
Conclusions
There’s a lot more to programming than just writing code, and it’s not enough to do coding and algorithm exercises to go from an idea to working software that does something useful. There are whole layers of activities that deal with translating a project description into more sub-projects that can be actually put to code, and these are often and regrettably easy to miss both when teaching yourself to program, or when consuming a course or a lecture.
There’s a good that “do a project” is such common advice for engaging with programming, as the whole process must run through to the end for an idea to turn into working software, and only the last steps leave you with something to show for all the work that you’ve done. This process can and will produce a lot of different materials that aren’t code, and often intangible – thoughts, plans, understanding, insights.
Supplementary Material
The game described in this article can be accessed at https://steamcommunity.com/sharedfiles/filedetails/?id=2157849988.
A copy of Tabletop Simulator is required to play.
Lua code
board.lua, responsible for setting up and running the game
global.lua, responsible for viewing in-game reference material
time.lua, assists board.lua with visual lighting effects and managing round-tracking token