I present you: Trade Runner — the post-apocalytic trade simulator:
This is an original game I made during weekends and sleepless nights over the span of a month, more or less. It ticks all the boxes of an MVP:
Here, you can play it right here:
For any serial-idea-haver ADHD kind of person, this, a finished project, is a dream come true. Yet, there’s something bugging me about this extended experiment…
The Idea
Let’s step back for a bit. Where did the idea for this game come from?
Like many recent human creative endeavors, the original idea for this game came from an experience in the 2020 global pandemic. I remember letting my anxious mind assault me with a pervasive dread and the latent feeling our society would slowly crumble into a lawless, apocalyptic hellscape. One in which I was about to bring a child.
To escape those thoughts, I latched to an old, somewhat abandoned hobby of mine: strategy/simulation gaming.
After getting faced wiped against the floor by the cannibals in that Kenshi scenario and spending way too much time in the wiki trying to understand Crusader Kings II , I started a new game of Mount & Blade: Warband.
For those of you who don’t know, Mount and Blade is a medieval game where you fight big battles and traverse the literal and political landscape of a fictional but plausible XII-century continent (think Lions of Al-Rassan or pretty much anything Gavriel Kay).
Here’s the twist, though: I wasn’t in the mood for combat. Mount and Blade has a fairly deep economic simulation system that accounts for geography, manufacturing, inflation, and war-related crisis. I found myself travelling from fiefs to villages to other fiefs across the map, trying to make money through the price arbitrage of essentially commodities.
And it was soooo relaxing. I felt so fulfilled by this simple, semi-pacifist — I’d still get jumped by looters in the woods — gameplay that I started looking for other strictly trade and travel games.
That’s when I stumbled upon Dopewars a modern implementation of the DOS classic Drugwars
Both of which scratched, maybe on a very basic, primal level, that itch of buying low in one place and selling high in another and they were super fun.
For a while, at least.
To me what they ended up lacking was variety and personality. Let’s face it, there’s only so much you can do when the premise is strictly tied to selling drugs. That’s why I commend space trading games for at least adding a level of risk to trading legal vs illegal products. Moreover, neither game had any more complex a economy than rolling die on each place you visit to see what prices you’d get in there for the items you bought somewhere else.
Still, Dopewars was the distilled form of an experience that was taking me away from thoughts I didn’t want to handle. It wasn’t until nearly an year later when I thought it would be cool to recreate Dopewars but with an actual dynamic economy like Mount & Blade
The Origins
Being a programmer, naturally, I began prototyping in Typescript a very basic, unplayable, multi-agent market simulation that simply rendered a ledger of transactions per turn as an HTML <table>
Note: I’m glossing over this because it’s not immediately relevant but let me just note down what were the challenges of this initial version:
At the end of a couple of months, I had a plausible simulation that was very exciting to see run on console and give me a result.
What I didn’t have was a way to make it a game.
The excitement of building a simulation was far more engaging than anything I could do with it, gameplay-wise. So after a couple of tries, I abandoned that project and lost some of the code on a computer BSOD situation.
I would attempt to get this simulation made into a game at least twice before giving up entirely.
Then as 2023 came to a close, I was obsessed with change, cycle renewal, the idea of a new year and a new me. That’s when I stumbled upon this video:
“Finishing projects?”, I thought, “Having a wrap-up plan?”
“This is exactly what I’m going to do!”
So I took a long hard look at my github repos and I found whatever I had salvaged from the latest attempt I decided to start from scratch with a much simpler scope and above all, a plan .
The Plan
I turned back to Dopewars as a main source of inspiration and decided their pricing trick was cleaver and, as long as a I had some sort of map for worldbuilding purposes, I didn’t need to worry about roaming it.
The game’s action would take place in a few menu screens.
I came up with a UI wireframe in draw.io
Just a few, simple screens and where they’d lead the palyer to.
In terms of looks, I knew I was going for retro/pixelated which can be charming and make assets blend even when they are from completely different sources.
I alsol wanted it dark.
I thought the original Drugwars premise of having to pay a debt was good enough to get the ball-rolling, narrative-wise. Also, when it comes to games and creative projects, I want to add original IPs to them, just in case I want to make other things within the same universe.
I came up with a post-apocalyptic world, stuck in the past. And before you scream “ Fallout !!!“ I’d like to remind you of how it took inspiration from Mad Max before it and that I’m going for something more 90s (which, ironically, was the actual decade during which the original Fallout games were conceived and produced).
Finally, to wear my inspirations on my sleeves, though I had other names in mind, I settled on the title Trade Runner.
The Implementation
FYI: this is the code-heavy technical section, it might for everyone, this will be your one and only warning.
I decided I was going to use technologies I knew and get things done as quickly as I could. So I picked Angular 17 and a nifty retro css package I had had contact with called nes.css
nes.css doesn’t have layout classes so I also brought in Tailwind for that.
This was my first time working with standalone components and I just wasn’t used to them so I defaulted to making a bunch of modules + a game folder for game-specific classes + configuration:
The silly thing is, my app.component.ts ended up looking like an app.module.ts
When I finally realized how that was defeating the purpose of standalone components, I was already in too deep.
I could also have entirely avoided the SharedModule since, well each shared component could have been a standalone and imported what it needed.
The basic UI was super straight-forward I mostly just added .nes-container and .is-dark class. I changed the color of some containers to set them apart from the rest.
From then on, it was a matter of trying to match my orignal wireframe
I rushed through the UI in a day or two, though small adjusments would be needed from time to time. Making the title screen and end screen was also easy and just a matter of checking variables to load the right ending and save an input value from a form.
I still didn’t have a map yet, neither did I know how to do it. I knew getting events from a canvas element was insanely hard — because I had tried that on one of my last attempts. In case you want to know, you need to get the pixel position of a click, offset it by the canvas position on the screen and check if that point is within the area of the drawn object you want to interact with. This is a fair amount of logic to keep track of and, honestly not something I wanted to get bogged down by.
So I looked for other things to do while I didn’t find a good lib to handle the canvas map. I decided to reimplemt the game classes:
Of these, Inventory and Item ended up being the most relevant.
Inventory mainly manages an array of Items though, since I wanted to work with item stacks (ie. Y quantity of X item), I had to add some logic into how we added and removed items
Not a huge deal, but I ran into bugs due to lack of attention during the first few iterations.
More on Items in a bit but I want to detour into talking about the Inventory component
This was the biggest headache during implementation for a while because of how I settled on passing down the inventory
See, as per line 13 we are sending it down as an input, so, intuitively, it would reflect any state the orignal input, sent from the parent component was in.
But remember earlier when I said how important it was to learn that javascript objects are passed by reference and not by value? Yeah, it turns I hadn’t quite learned my lesson.
Angular will only change that value if the reference to the value actually changes, which had I fully known at the time would have saved me hours trying to debug trades not automatically reflecting on the player’s or trader’s inventory. Honestly this is on me, since I could have just setup a subject to observe that subscribe within the component in what I consider is the preffered angular way.
Instead here’s what I did: if you are not in trade mode, I close the inventory.
Once the trade is complete, I get us out of trade mode and the Inventory disappears. Next time it appears, it will have the latest trader and player inventory. It’s hacky but it worked as a quick solution and it gives more space to the art on the background to shine.
Once that was done, I fixed all the trade features, changing amounts, amounts, displaying the offer in the middle of the screen, handling both the player doesn’t have enough money scenario and the trader doesn’t have enough money scenario with proper UI prompts.
Things were looking promising: I could trade and persist trades through a global game.service.ts and I could keep track of the current day to give the game a time limit. I think I was ready to tackle the map.
Like I said before, the native way to get events from canvas is not very convenient and led to inconsistent results and edge cases that I didn’t have the time to manually correct. Initially I thought I could use either phaser.js or pixi.js to get things going but I was so enamored by the examples over at http://fabricjs.com/ that i decided to go with that.
Honestly, it feels almost dishonest how simple the API for it was
And it worked straight away out of the box
Each of the black boxes was a city or trader that could be clicked and it would route me to /location with the right trader id.
Sometimes, adding tools makes things a lot easier.
By now you must be wondering how does it know what the cities look like and what their names are and what sorts of items they carry. I made the game configurable through a few json files.
They dictate what the traders and items will look and play like and trade at. This is what I’m loading in my game service and what sends to the map and the inventories.
This is also something else I learned too late: the game service was carrying too much game logic that should have been handled in the Game.ts class. The service should have just a few assessors to that object to keep it away from components. There are many reasons why I believe this, but the main one is, if I had done it that way, porting the game to React, Vue, text or any serious game engine would be just a matter of copying the game classes and the game configs (and rewriting the display logic around it).
The final feature I implemented was events. The game makes no sense unles there are actual arbitrage opportunities, i.e. the prices differ from location to location and from time to time. I came up with the idea of events to telegraph to the player potential trading opportunities:
This went through two iterations because of early over engineering. Initially I wanted to have event templates that could carry n-number of effects and make higher order consequences that would change multiple locations’ wealths and items’ prices and availability. So I split the configs into 2 templates, one with the events and one with the effects and the events template had an array of effects that it cause.
In practice, most events I came up with only caused one effect and the overhead to get the data from 2 different sources wasn’t worth the flexibility it afforded. I scratched that and rewrote it in the format above and events turned out more consistent.
As last minute addons I made a trigger warning — just in case — , configured social media meta tags, added all the public domain, pixelated art and added 1 music track that plays automatically with howl.js.
I created a firebase project and put it online to share with other people.
The regret
That’s it. At one point, I was just done. The project was as ready as I cared for it to be. Players could go from beggining to end of the game and get an ending based on whether they pay their debt or not. All the traders had mugshots, all the locations had images. It wasn’t bug-free, since some people reported a few things right after I sent it their way to test; but it was done.
I should feel proud and in many ways I do. This is a game that has been taking up space in my head for almost 4 years now and now it’s a thing, out there. It exists. It’s part of my body of work. Ready to take criticism, to be loved or, most likely, bashed. It’s a complete, finite package.
But now, finishing this writeup, I realise how bad the technical aspects of the project ended up being. This project rflecs very poorly on my software development skills because of how much I rushed to see it through. It pains me to have found solutions to things I struggled with for so long only too late to make them different.
This code is a shame. A personal one at it and I owe it to myself to be better in the future.
And fine, the title was a lie, I don’t actually regret it. I loved every single minute of it, up until the very end and I’m excited to have finished something as much as I believe it should. My ego is just hurt because I thought I could have made it so much better.
The future
I’ll let the feedback come in before I touch this project again. I need a breather from it to get perspective. I’m still going for the goal in the video from earlier and I have other projects I’d like to finish and hopefully release this year.
When I do come back to Trade Runner here’s what I expect to do:
In the meantime, feel free to play in the link above
Thanks for reading and if you did play, tell me, what did you think of it? What projects are you going to finish this year?
Update: This story was originally published on Guillermo Murúa’s personal Medium on March 2024. That Medium is no longer being updated but the game
Trade Runner
is being actively developed by Murua Media with a full release on steam and itch.io slated for 2025. We will be posting more about that and other games under development right here. In the meantime, catch up with us over at our Discord server:
https://discord.gg/6jTx7NRXkm