Retrocomputing
So I should probably have a blog post that I can point to about this whole retrocomputing project that I've been up to the past year and a half.
I wrote a game on an MS-DOS 286 PC, using only tools I built myself or tools that were available during the era where they were still selling 286 PCs. It's called Neut Tower and you can play it on your MS-DOS PC, in DOSBox, or in your browser. As part of this project, I implemented a Forth system, and built most of my game and its tools using it.
My motivation at the start of the project was this: I was enjoying using my 286. I liked the single-tasking workflow; there were no distractions. I was downloading games and apps and it was fun! So I figured I'd take the next step and write a little game or something.
When I was a teenager, I had a 286, and I tried to learn low-level programming on it because my options were “low-level programming” and “BASIC”, and I had hit my limit with BASIC. Assembly might as well have been Martian to me, but I got a book about C, and I got a book about game programming, and I sort of got some stuff working. But mostly the stuff I tried to do myself from scratch, or port from other sources, didn't work, and I didn't know why. Eventually I also got access to a 486, and then a Pentium, and the internet, and djgpp and Allegro, and suddenly I had an embarrassment of nice graphics and sound libraries and tooling, segment:offset addressing didn't matter, and I never had to worry about trying to understand how Mode X worked ever again.
Twentyish years later, I wanted to learn all the stuff that never quite clicked for me. I wanted to dig into how everything worked, to make sense of the tutorials that once baffled me. I wanted to really understand it all. So I started writing little prototypes, and pretty soon, yeah, I had a cool EGA graphics engine, with two way scrolling of a tilemap and 16x16 sprites drawn on top, running at a decent speed on actual hardware. Everything fell into place one tiny experiment at a time.
With the hardware programming side of things, I learned that my teenage understanding hadn't really been all that far off the mark – my problems weren't so much that I didn't understand the tutorials and resources that were available to me, it was more that I was simply bad at debugging my beginner code, and didn't have the tools or the patience to fix it. With 20 years of professional programming experience under my belt, and a wealth of resources on the internet that explained how things worked in depth, this was no longer an issue.
Then I started to write a game loop in C, and didn't really like it. I knew in the back of my head that, for what I wanted to do, I really wanted some kind of scripting language. And I remembered Forth existed.
In my 20s, obsessed with both the world of programming languages and the world of embedded systems, it was inevitable that I would learn about Forth – it's a particularly uncommon blend of small and powerful, that could run directly on hardware, that people who loved it really loved. I'd tried seriously to learn it but couldn't really wrap my head around it – the weird postfix syntax, the confusing levels of meta. Why could I not use IF statements at the REPL? How was I supposed to remember all these finicky rules? I filed it away as “interesting, but not for me.”
This project was the perfect opportunity to revisit that evaluation. Forth fit the bill exactly – it was a tool that could be built quickly, using minimal resources, and made to do what I wanted, AND I already had a hazy half-remembered foundation from decades ago. I dove headfirst into it.
Relearning Forth was an altogether different experience. It turned out that once I built one myself, I understood it completely. The design of Forth is to write as little code as you possibly can, to make the computer do only as much work as it needs to. When I had to write it all myself, I had to decide – is it worth building this language feature, or can I do without it? Usually I could do without it. Usually there was a tinier way to do it. The code that I had to write wasn't really all that much uglier or worse for it, once I got used to the constraints. And I had proven designs I could pilfer; there are lots of existing open-source Forth implementations to get inspiration from. There are guides for building Forth systems. Doing Forth is not learning an existing language set in stone, it is building a language to solve your problem, and sharing ideas about useful building blocks. Chuck Moore, the inventor of Forth, hated its standardization; thought the goal of portability was absurd, thought everyone should change it as they needed, to fit their problem. He is still trying out new ideas, rebuilding, simplifying, making a system uniquely his own.
So why do I think all this is important enough to write about?
When I was a kid, I had this persistent idea in my head, that computing was a skill I could work at, get better at, and that doing so would allow me to accomplish things that were impossible for me without it. “Once I got good enough”, I could make a computer game, by myself. I could draw the graphics, I could write the code, I could make the music, I could design it all. I could make it and I could put it out into the world and it would be mine, start to finish. Every time I learned something new about computers, got some new piece of software, I gained abilities. I could do things I couldn't do before. My vision of computer literacy is that everyone has this experience, that everyone can learn the skills they want, is provided with the tools they need, to make their imagination real. I have never really let go of this idea.
I'm still trying to find ways to make it true, still trying to explore the different ways that computing can be empowering. Retrocomputing is one avenue for that – people in the past had a lot of good ideas that didn't catch on. And while emulators are wonderful, running them inside a modern computing system makes it harder to experience what using an old computing system really felt like.
When I show people my setup, they are often curious about the qualitative difference between old tools and modern tools; it must be so much harder, right? And... for me, it's really not! I write bugs at about the same rate; I fix them at about the same rate. There are many things I can't do because of resource constraints, but that keeps the scope manageable and makes for an interesting challenge to find cool stuff I can do. The biggest thing I miss is having a second editor that I can use to look at & edit code while my game is running — I have often resorted to taking a photo of some code with my phone so I can read it while I have the game up.
And I gain really valuable things from the constraints. The biggest thing is that there's no alt-tab away from the work – it's so much easier to focus without a web browser instantly at my fingertips. (I'm procrastinating at work writing this right now!) The resource constraints mean I have to focus ruthlessly on solving the problems I have, not the problems I imagine I'll have – there's no perfect, elegant, general solution if I think hard enough, there's only adding things and cleaning up what I've got, one small piece at a time. And I can take workflow seriously as one of those problems! When I'm fed up with the tools that are available for DOS on a 286 (and this happened multiple times!), I make my own that work the way I want, and I'm able to integrate them seamlessly into my engine. I'm able to intentionally craft my environment to be comfortable. I'm no artist, but multiple people have complimented my art – partly, the secret is that 16x16 sprites and tiles can only look so good with a fixed ugly 16-colour palette, so I'm able to focus on broad colour and style choices. But really, if you put me into my ugly, limited pixel editor that's two pages of code but instantly shows me what my sprite looks like in my game, I will mess around until I'm happy. Put me in front of Photoshop with 16 million colours and I will go crazy from decision fatigue; I'll avoid making more art, and I'll get myself stuck.
So for me, the tradeoffs are incredibly worth it. I've spent decades trying to make games as a hobby; I've put out reams of junk – failed prototypes, bad joke games, quick jam games, failed engines, half-finished tools. I've tried every way of making games that I can think of; coding engines from scratch, using Unity, Godot, Love2D, Klik & Play, Game Maker, Twine, Construct, Adventure Game Studio, pygame, Allegro. Some approaches I've had more success with than others, but I've not ever been as happy with anything I've made as I am with Neut Tower. Not as a retrocomputing exercise — as a game.
Neut Tower is done, for now, and I am taking a break from it. (Perhaps someday I will return to it to create the next two episodes.) I'm quickly finding myself using all of these lessons and starting to build some tools for myself in Linux. I don't quite know what they'll turn into yet, but I'm looking forward to finding out, one small piece at a time.