In hindsight, I can’t believe anyone paid money1 for the first version of d20, which I released in September 2016. But they did, and those few sells I made inspired a much more useful version 2, which was released in January 2018.

By the end of 2018, I had been using d20 extensively and running into some problems I wanted to address. I started writing some brand new code in December of that year. Two years and 144 commits later, I’m happy to announce that d20 version 3 is now for sale on the Mac App Store.

Screenshot of d20, showing a roll formula in a text input with multiple results underneath. Showing presets pane expanded with custom buttons like "Long Bow".

You can check out the d20 website or Mac App Store page if you want to know more about the features. But I want to get into the decision making process, tech stack, and other tangents regarding the making of d20. Imagine this as the director’s commentary.

Electron: can’t live with it, can’t live without it

The biggest non-decision was whether the app would be powered by Electron, go fully native, or go fully web-based. I had already used Electron for the first two versions. But I felt guilty about it. Electron is basically Google Chrome and NodeJS smashed together, so it’s pretty big and resource intensive. Seeing as d20 has a simple interface, very limited scope in what it’s trying to accomplish, and is meant to be Mac-only, Electron was a really terrible option.

But I went with Electron again for the same reason every other web developer does—I know how to code for it without learning too much new stuff (e.g., Swift + Apple frameworks). Yes it’s way overkill, yes it’s absolutely ridiculous. But you either get d20 the Electron app, or you get nothing. That’s the great thing about Electron. It lowers the barrier to creating desktop applications and spawns tools and apps that otherwise would not exist. You’ll have to endure a little bit of ridicule from Hacker News and, if you’re like me, your inner-critic, but I say YOLO. Don’t let your dreams be dreams.

RANDOM.ORG

Since version 2, d20 has integrated with RANDOM.ORG to provide true random numbers rather than pseudorandom ones generated by JavaScript. This ended up being the catalyst that pushed me to finish version 3. RANDOM.ORG brought their API out of beta and began charging for it. My old API key was no longer working and so d20 was quietly falling back to using pseudorandom JavaScript RNG.

This was not cool, considering that true random numbers is one of d20’s unique selling points. I made the decision to move to the commercial API, which costs $12 per month. I don’t like incurring the extra costs each month, but I do make enough in sales to cover it and it is personally one of my favorite features of d20; so I gotta keep it in there.

Vue, Sass, Laravel Mix

I doubled down on using Vue. I had used it in version 2 but I was new to it at the time and I did a really poor job of code organization. I feel like the new version is much better. I’m a different programmer than I was when I first started d20 v3 so there are definitely things I would change if I were starting today (like using TypeScript). That’s just part of the consequences for taking two years to finish it.

CSS is pretty amazing these days, but I went with Sass out of complacency or momentum or whatever you want to call it. I processed all the styles and scripts with Laravel Mix which is a pretty great way to use webpack when you don’t feel like learning about webpack2.

Leaving behind localStorage

I have a bad habit of using localStorage, web storage feature, for everything. In d20 v2 I was saving all the presets in localStorage. But for v3 I moved to using a JSON file for saving presets and settings information. This is my first time using the file system in an Electron app so here’s hoping I did all of the Apple security stuff right.

The cool thing about this though is that it’s now much easier to back up your presets, move them to another machine, or share them with others. Just save the file. Boom. Easy. I will be able to rest easier knowing that I have reliable, persistent storage and that a user won’t wake up one day to find their data missing.

Design

These three images should give you an idea about the progression of UI design. Of course, I feel that the third version is far superior. I do think it’s funny how my color scheme seems to be trending pink-ward.

D20 version 1 promotional screenshot. Basically a grid of buttons witb a list of dice roll results running down the left side of the window.
d20 version 2 promotional screenshot. Dice buttons along the right side of the window. A large text input runs along the top with a list of dice roll results underneath.
Screenshot of d20, showing a rollformula in a text input with multiple results underneath. Captioned "An elegant, powerful dice roller."

I take the UI design seriously and the lack of nicely designed dice rollers on the Mac App Store was a big reason I made d20 originally. I’m pretty happy with how it turned out and I’m hoping to start getting sales information from Apple really soon to find out whether users agree.

Problems and solutions

I said earlier that I was beginning to run up against problems in v2. Many of those I have addressed in v3.

One of the major ones is what I’m calling “multi-roll.” The easiest way to describe this is that it lets you roll attack and damage at the same time. Technically, what it is doing is letting you separate different formulas with a comma but roll them at the same time. This means that you can also save them together, which means that now you can have a weapon as a preset button and clicking will roll the attack and the damage simultaneously, with each having its own result entry.

Another big problem was that v2 presets were simplistic—just a list of buttons that each can roll one combo. If you have multiple characters, good luck rummaging through all your presets to find the ones you need.

I’m tackling this in v3 by introducing the concept of “preset groups,” which let you create different lists of presets (e.g., one per character). This plus multi-roll solve the biggest annoyances I had with v2.

Submitting to the Mac App Store is hard

Seriously, that was harder than building the app. There are a lot of articles recommending electron-forge but there’s not much help out there about what happens when something doesn’t work.

I ended up using its parts directly: electron-packager, electron-osx-sign, and electron-osx-flat. This topic deserves its own article probably but in summary:

  • You need a “Mac App Distribution” certificate from Apple.
  • You need a “provisioning profile” from Apple.
  • You need the entitlements files—I just grab these off of the Electron site, but if the app needs any special entitlements, you have to add those. For example: com.apple.security.network.client if your app needs to hit the web for stuff.
  • You need to package the app with electron-packager, which requires you have the aforementioned Apple cert installed onto your keychain.
  • You need to sign the app with electron-osx-sign.
  • You need to turn the .app into an installer with electron-osx-flat.
  • You need to send the resulting installer to Apple using their app called Transporter.

Here are the three npm scripts I used to package, sign, and prep the installer for distribution on the Mac App Store:

{
  "scripts": {
    "package": "electron-packager . d20 --out=out --platform=mas --arch=x64 --appBundleId=com.example.app_name --icon=./images/d20_icon --no-deref-symlinks --overwrite",
    "sign:mas": "electron-osx-sign out/d20-mas-x64/d20.app --entitlements=entitlements/parent.plist --entitlements-inherit=entitlements/child.plist --entitlements-loginhelper=entitlements/loginhelper.plist",
    "installer": "electron-osx-flat out/d20-mas-x64/d20.app --platform=mas"
  }
}

These resources helped me out a lot:

Web vs app stores

To be honest, I would have loved to just deploy d20 as a web app. But unfortunately it’s hard to get traffic to a web app versus getting the built-in audience an app store provides. It’s one thing to charge for a Mac app, but try to charge for a website that rolls dice and suddenly it seems ridiculous. I don’t know if there’s an app-store-like website for distributing web apps (itch.io maybe?) but I doubt a web version would have had the same success (10-20 copies sold per month) that the Mac App Store provides with $0 of marketing effort.

It’s a technical challenge building an Electron version and a web version from the same codebase, but providing some kind of web version is on my radar. As much as I would love to go cross-platform and distribute Windows and Linux versions, I don’t have a good way to test those platforms. And my specific setup for working on my Mac (which I need thanks to my disability) isn’t conducive to OS or device switching. But at least providing a web version would open it up to more platforms.

By the numbers

In conclusion, here are some random stats/numbers for your amusement.

  • 3.5 years on the Mac App Store
  • 203 commits across all versions
  • d1,000,000: the largest die d20 v3 can roll
  • 100: number of random integers d20 requests from RANDOM.ORG when you roll a die
  • $3.99: the price of d20 v1
  • 04/2020: highest grossing month ($200)
  • $3.41k in lifetime total sales (before Apple’s cut)

  1. Seriously, version 1 was so bad. The only way to roll dice was by clicking (or using keyboard shortcuts) even though it had what looked like a text input where you could type out a dice roll. But no, you could not type in it, it was a read-only thing. In addition to having way too many modifier buttons, I managed to forget all about the d100, forcing players instead to roll two d10s. You can see for yourself how bad it was because it lives on as a free web app
  2. I use it on all my projects now. I’m really impressed by how sophisticated of a setup you can make while still keeping configuration files nice and easy to understand. Mix is super powerful and, despite the name, doesn’t at all rely on Laravel, the PHP framework. I’m actually using Mix on my other project, A Fine Start, to generate builds for web in addition to Chrome and Firefox extensions.