Besides assembling a retinue of armed and optionally disagreeable men, another D&D tradition is obtaining a castle, keep, or other stronghold. Some variants (especially Adventurer, Conqueror, King) go a step further, and assume that the new home base comes with feudal obligations and large territories to manage. Obtaining an interesting home base has long been something I have wanted to do in a game. Rules for these, however, tend to delve into almost minute detail.
I’ll begin with a seeming digression – one of the things that I find fun and helpful in Fate is the “Fate fractal.” Officially called the Bronze rule, the idea is this: Fate has a small set of very simple components for a character – aspects, skills or approaches, stress, and consequences. These components represent all the mechanics of the game, and therefore anything that needs mechanical support is necessarily able to be modeled as a character (or part of one).
Thieves’ Guild? Obviously it has aspects. Will the guild be acting against characters or the interests of another guild? Now it has skills to model its ability to act. Will someone else be acting against them in turn? Stress and consequences.
Is the building on fire? Sure, there’s an aspect for that, but if the fire is so deadly that it “attacks” the characters, it has a skill for that, too. If the fire can be put out, it might also have a stress track.
This approach makes it both quick and easy to give something the mechanics it needs, when it needs them.
The past couple of days, I have started actually playing around with programming in Rust.
If you want to play along at home, here is how you can get set up.
The language updates periodically, and there are several features that currently live on the nightly branch. The tool offered to manage this is rustup, which is your “toolchain manager”. This means it’s responsible for pulling down different versions of your core dev stuff – compiler, package manager, and the rest.
Since I work in a .NET shop, I’m using the new Visual Studio Code, which has a rust extension. It provides syntax highlighting and some basic code completion, but it’s far, far short of what I get with Visual Studio and C#. This is one of the areas that’s expected to improve this year, but there’s plenty of room to improve.
Doing the work
Since this is just playing around, I decided to redo a small utility that I’ve already written in C#. It scans a directory for all executables, then outputs the path (relative to the starting directory) and its MD5 checksum either to the console or to a CSV file, depending on command line arguments. The C# application is quick-and-dirty, but I did try a few optimization tricks – arrays of structs, splitting the work across multiple workers to compute hashes in parallel.
For my Rust version, I decided to start simple and do a single-threaded application. Rust is not “batteries included”, so I had to pull in almost everything I’d use a standard library for elsewhere – the MD5 hasher, the glob library to search directories recursively, a library for handling timing how the application runs, and even command line argument parsing all come from “crates”, which are third-party (or in some cases first-party-but-still-in-trial-phase) libraries that are pulled in via the package manager.
The pain points
Contrary to popular wisdom, I have not had to struggle much with the borrow checker – the set of rules applied by the compiler to guarantee that I’m not going to share the same reference out several times and have one part of the code start tripping up another. For a project as linear as this one right now, that doesn’t surprise me. Adding parallelism later on will no doubt make it more interesting.
My problem has been dealing with types. First off, it’s good that Rust’s documentation is as good as it is (and that the crates are all open source), because it has made me keenly aware of how much I rely on my accumulated knowledge of the .NET framework as well as quick and accurate code completion. Most of my time has been spent groping around in the dark trying to figure out what type or function I even need.
The other hurdle I hit is, literally, types. For example: I wish to abstract over “write to console” and “write to file”. In C#, this is easy because I can just get a Stream from either the console or a file, and then write to it. Base classes and interfaces form a hierarchy that lets me abstract over the details. Rust doesn’t let me get away with that, and here’s why.
First, Rust doesn’t have object inheritance, so there’s no type hierarchy in the sense that I am used to. Second, instead of interfaces, it has a similar concept called Traits (probably closer to C++ Concepts, but I’m saying this as someone who has barely done C++). The trick is that while there is a useful Trait to abstract behavior (in this case std::io::Write), I can’t go about just saying that I have some instance of std::io::Write. That’s because I’m trying to hold onto some concrete type that Writes, and the console (std::io::Stdout) and a file (std::fs::File) are two different concrete types. I can’t have some space on the stack (or can I? More in a later paragraph!) and have it be one of the two, don’t care which, just pretend that it Writes. The quick way to fix this is to turn them into a Trait Object – stick the real thing on the heap (Box in Rust parlance, new or any kind of smart pointer in C++, reference objects in .NET), and keep the pointer type as the trait. Once I have a Box<std::io::Write>, I can put anything that implements the trait behind it, and through magic auto-dereferencing, pretend I have the original object at hand.
The other trick, which dummy me only thought of driving home, is to use rust’s enum. Rust enums are much smarter than the enums you’re used to, which are numbers given fancy names. Rust enums are also called “algebraic data types” or “sum types” and the reason is that each variant of an enum is not just a named alternative, but it can have its own data-carrying type. For example, Rust doesn’t allow null pointers – the way you signal that you may or may not have some object of type T is to use the enum Option<T>, where you either have Some(T) or None. There are no thrown exceptions, so operations that can fail are modeled as the enum Result<T, E> and the action was either Ok(T) or Err(E), where both the data type T and the error type E are defined in the operation itself.
What I could have done was define my own Output enum, and given it types like Console(std::io::Stdout) and CsvFile(std::fs::File). For added trickiness, I could then implement the Write trait on my new type, and just forward everything along to the thing I actually have. Bam, one more pointer indirection done away with.
As of this afternoon I have the basic program behavior done. The next thing will be trying to parallelize hashing files, and then seeing how I can do performance-wise against my .NET version.
Preview material for Pillars of Eternity 2 got me thinking about multi-class design in RPGs.
I picked up Pillars of Eternity on sale for cough totally legitimate research purposes. For some reason, the game just isn’t grabbing me.
Pillars is very reminiscent of Baldur’s Gate, with several things cleaned up from baseline D&D (ish, anyway) rules, and several things made more fiddly to reward system mastery. Unfortunately for me, one thing I don’t particularly enjoy is games rewarding system mastery for very fiddly systems. The other problem is that while the game has a lot of original lore, it hasn’t managed to convey it well enough to pull me in.
I’m through Act I so far. The opening tutorial is blessedly shorter than I remember running around Candlekeep in BG1 being, and includes an actual (though small) dungeon that’s far less annoying that Irenicus’ opening dungeon from BGII. There’s also a nice small dungeon in the first town, though a bit of it fell flat for me. There’s a door puzzle that I couldn’t find enough clues to solve, but since I also picked up the key that opened it anyway, I don’t quite understand the point of the puzzle.
One thing I notice is that every map feels very, very small. My memories of BGI are so old as to be unreliable (I haven’t played it since at high school), but I’ve started BGII a couple times in the past many years, and I know that the opening dungeon is sprawling – merely escaping it took about as much play as getting through Act I, with more compelling immediacy to the story’s stakes.
As for the totally legitimate research purposes…