Hacker News new | past | comments | ask | show | jobs | submit login
Re-implementing an old DOS game in C++ 17 (2019) (lethalguitar.wordpress.com)
198 points by mariuz on April 15, 2022 | hide | past | favorite | 25 comments



> I also did a lot of testing with the original game, running it in DOSBox and on original hardware (various 386 and 486 machines which I got from eBay). I built test levels for focused observation of specific enemies and game mechanics, recorded video captures of these using DOSBox, and stepped through the recordings frame by frame to verify my findings from reading assembly code. Once I had implemented an enemy or game mechanic, I would also typically record footage from my version, and compare it to the original frame by frame to verify the accuracy of my implementation.

The developer put in a tremendous amount of work!

The wonderful thing about programming, is how the product of your intense focus can be shared with so many people. There are people who have built super detailed model railroads but you will never see them unless you visit their basement. With software, just about anybody can build/run/play this game that has been so lovingly and painstakingly recreated.


> as usual, the best way to architect a piece of software becomes most evident once you’re done writing said piece of software

So much, this - I've occasionally gotten the opportunity to rewrite a piece of software and it is always a much better design the 2nd time.


"Build one to throw away" [1] - Fred Brooks

Jim Coplien on how one of Dave Thomas' companies do software [2]:

"They build a prototype, then they throw it away. They learn from the prototype. Then they build the product, and throw it away. And then they build the deliverable; and ship release 1, release 2, and release 3--and throw it away. Every third release, they are throwing things away. Do you think they have any problems with technical debt?"

[1] https://en.m.wikipedia.org/wiki/Fred_Brooks

[2] https://www.youtube.com/watch?v=gPP7Bleg214&t=2339s

[3] https://en.m.wikipedia.org/wiki/David_A._Thomas_(software_de...


To add to lore, https://www.folklore.org/StoryView.py?story=Negative_2000_Li...

After he talked about throwing away code, he started hammering on the idea of understanding your domain. I don't think you can program without rich domain knowledge, if you are, then the software you write is very narrowly focused.

My idea of programming has shifted quite dramatically over the last couple months towards almost a hyperfocus on the domain, the data representation of that domain and the hierarchy of abstractions that we use to represent the ideas in the domain and the physicality in the data (and some extent the algorithms).

A computer program is a great way to let you know that you still have parts of the domain to understand.

We should be working in systems that are finely tuned TO the domain, so that we can play with concepts as easy as declaring self evident facts, so that we can constantly be throwing it away.


I experience this so often that I try to intentionally write software the first time with as little architecture as possible, so that there's less to strip out when the "correct" architecture becomes clear. Just leave everything as a big bag of functions and mostly-unstructured values passed on the stack, until it's working. Then figure out what abstractions might help make the code more expressive.


This approach has made my work much more relaxed and reduced stress a lot. The beauty of just first making it work somehow before going into design is that you can get rid of a good chunk of the unknown unknowns beforehand and you have a known working version you can always revert back to. It's less likely that you suddenly hit a brick wall when you think you are about to finish.


This is also my approach to organizing my room: first I put everything on the floor. Once I can see it all at once, I see out how things relate to one another.

I find it harder to see code all at once though (some IDEs have an "outline" of all variables and functions, but my current editor does not), and I wish there were more ergonomic ways to rearrange the order of things in code.


Funny enough, a big bag of functions and stack passed values is an accurate representation of assemble - how a computer might actually work. We often forget we're programming stack based machines


Generally agree, but I fall prey to the Second System Effect sometimes, tho. I rewrite my "organically grown into spaghetti code over some time" original program, aiming for a proper design, and end up with some overengineered mess that's hard to maintain and burns CPU and/or memory in layers of abstractions. The 3rd time then is the charm for me, more often than I'd like to admit - avoiding the pitfalls of original program and having learned the real requirements/needed features and details from it, and avoiding the overengineering of the 2nd attempt reminding me to KISS.


I think it was Joe Armstrong that said, "I don't really understand a program until I rewrite it five or six times."


"Writing is nature's way of letting you know how sloppy your thinking is." [1] Dick Guindon

"To think, you have to write. If you're thinking without writing, chances are you're fooling yourself. You're only pretending to think." [1] Leslie Lamport

[1] https://www.youtube.com/watch?v=-4Yp3j_jk8Q&t=179s


Be sure to read author's response(s) to comments below the post. Some insights about why C++17 doesn't help with reducing the size of the codebase:

> My version (...) has to convert the data into formats usable by modern video and audio APIs (...) in an endian-independent way, and adds quite a few correctness checks (...) instead of triggering undefined behavior

> the original can basically do a memcpy to show something on screen, I need 1000 lines of OpenGL code…

> I don’t have global variables, but this in turn means more boilerplate to inject dependencies into objects on construction etc.,

> I try to be fairly type-safe, which sometimes means needing more code, additional data transformations etc.


As someone who is trying to develop a PlayStation 1 game using modern C++ (GCC still has MIPS 1 support so why not) I can almost feel the author's pain, even though I'm running into different issues. Getting today's abstraction layers to run on nearly 30 year old hardware is not that different from adding them to a game that was developed back when memory protection was not a thing.

Here are some of the issues I've run into so far:

- Having no access to the STL (which would be too bloated for the PS1) makes many C++ features unusable. The ones that do not rely on the STL, such as virtual methods for I/O drivers or constexpr for compile-time string hashing, are still useful though.

- The PS1 is, very roughly speaking, a slow CPU with a bunch of fast ASICs bolted on. Most of those operate asynchronously but require relatively high-bandwidth streams of data (display lists, textures, geometry, audio...) that has to be generated by the CPU in real time or provided by another peripheral such as the CD drive. Feeding multiple ASICs running in parallel is an exercise in buffering and synchronization.

- As if that wasn't enough, most peripherals have their own proprietary data formats: the GPU uses a nonstandard 16-bit RGB color format where the top bit is used to enable alpha blending, the audio mixer only supports a custom ADPCM encoding and so on. I had to roll my own tools to convert assets into these formats ahead of time.

- Coroutines or threads are usually not an option since context switching burns a significant amount of cycles. A lot of "magic" has to happen within interrupt handlers, but those must also be fast since IRQs occurring before the handler returns are occasionally dropped.

- The PS1 has a BIOS that provides APIs for accessing files on the CD and memory cards, but they are so limited and bugged that I was pretty much forced to reimplement a full filesystem stack, from the SPI driver that handles communication with controllers and memory cards all the way up to the VFS layer. And writing asynchronous filesystem drivers without even using coroutines is... not fun.


> makes many C++ features unusable.

Few. Because:

1. There are independent libraries implementing facilities from the STL for embedded settings.

2. The ones without independent implementations are very often ones you shouldn't be using in the first place, because they're slow in general or for your specific testcase (e.g. std::unordered_map come to mind - abstractly, very useful structures; but the STL interface+implementation is junk, performance-wise).

3. You have EASTL if you _really_ need it.

4. The STL is mostly data-format-neutral in the sense of not supporting any data format itself :-P


I LOVED Duke Nukem. Then the world went FPS and ... I stopped playing games.

This looks like a really cool project.


In defense of the genre, the world had plenty of great and popular side-scrollers throughout the next two decades. Rare and Nintendo made a bunch of universally-beloved ones, for example.


This is an impressive project as it's not just a reimplementation but is back compatible.



I really wish abandonware would have its source code automatically released after a certain period, kind of like a patent expiring.

Mostly I want to win the lottery so I can sponsor someone to code Sid Meier's Alpha Centauri in a modern vector/HD UI and with 64bit memory space.


> Mostly I want to win the lottery so I can sponsor someone to code Sid Meier's Alpha Centauri in a modern vector/HD UI and with 64bit memory space.

Perhaps the FreeCiv guys would be up to the task if the price were right.


Still want to migrate turbo pascal bridge … guess this old to new intent never end. Just read the pi calc using 4004, which is the other way round.


Really cool - fun to see your notes and a break down of the work over time that was committed to the project!


do Commander Keen next please






Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: