Oli's old stuff

Tinkering with retro and electronics

Dec 21, 2022 - 9 minute read - retro gamedev amiga

Cosmic Conquest

We all remember certain games from our youth. They could be great games, terrible games or just games that spark a particular memory or emotion when you think back to them. Everyone’s list is different, it’s a very personal thing.

One such game for me is Cosmic Conquest, originally released as public domain for the Amiga in 1988 by Carl Edman.

Cosmic Conquest - Amiga Version

Cosmic Conquest is a turn based strategy game that pits 4 players against each other fight it out for control over the local space sector of 16 stars.

I don’t recall how I acquired my copy but I do remember playing it. Or rather, I remember the feeling I got from playing it. Hours and hours spent slowly expanding your empire out until you bump up against another player (or in my case, the AI). It was my first exposure to such a game and it’s stayed with me ever since.

I’ve recently built an RC2014 kit and have been messing with the Interak emulator so have been exposed to green-screen 80 column displays a lot. I think it was this that triggered my memory of Cosmic Conquest and me being me it sat buzzing around in my head until I had to do something about it.

So I went searching and found the original PD distribution of Cosmic Conquest on the Amiga in ADF format. I fired up WinUAE to give it a go and was instantly struck that there were a lot more files on the disk than just the original game.

Cosmic Conquest - PD Disk

Those files with the .c extension are the original C source files of the game! I don’t recall if I knew this at the time, or even if the version I had contained these files but fast-forward 30 years to 2022 and current me was very interested.

Inception of the Port

I grabbed the HxC Floppy Emulator and extracted the contents of the ADF to a folder on Windows. I then cracked open vscode and started exploring the code.

To my surprise I discovered that the game is Intuition-based and uses very few functions from the OS other than changing colors and rendering text. I instantly thought “hm, I wonder how hard this would be to get working on PC?”.

So I grabbed dos-like by Mattias Gustavsson and set about putting the basic plumbing together. I chose dos-like because it’s very simple to get something up and running, plus I’m fond of the CRT effect that’s built in.

The main things required to port this to dos-like (or rather, any framework) were:

  • app bootstrap
  • 640x200 framebuffer (80 col x 25 rows)
  • 16 colour palette
  • character input (ASCII characters)
  • mouse input (movement and buttons)
  • text-based output

Shimming Intuition

In order to get going fast, I decided to put a shim in for the Amiga Intuition code rather than rip out and rewrite it. The main functions to replace were:

I wrote versions of these functions that do the equivalent actions using the dos-like library functions and largely find/replaced my way through removing a few extra unused parameters (which I could have left, but it doesn’t hurt to rip them out).

Before long, I had the title screen rendering.

Cosmic Conquest - Ported Title Screen

To render the text I chose to bring in help from Damien Guard’s brilliant pixel font library ZX Origins. I tried a couple of fonts, but very quickly landed on ZX Eurostile as the one that I felt looked the nicest but also is reasonably close to the original’s ruby.font from the Amiga.

As I was using the 640x200 screen in pixel mode and not text-mode, I quickly implemented a routine to blit the bit-packed ZX Eurostile font into the dos-like framebuffer, picking the right pen colors from the IntuiText structure that the game uses.

The next step was replacing the input loop with equivalent dos-like functions. The original has 2 input functions cget and cinput; so the changes to those were pretty localized. I could replace them with the equivalent readkeys() or keypressed() functions from dos-like.

The final step was to quickly hook in the equivalent mouse events using the mousex(), mousey() and keypressed() functions in dos-like.

After about 4 or 5 hours work, I had a fully playable game. In fact, I found myself playing it for several hours afterwards. I had a couple of crashes down to uninitialized variables, but they were quick to patch up and keep going/

Cosmic Conquest - Port

The original code

Being from 1988, the original code is written in one of the earliest styles of C. Fairly expected here was the old “declare a variable up front” and assign later syntax (as was the way).

Quite unexpected to me was to encounter the function style; declarations are as you’d see them today, but the definition uses a very different syntax.

Rather than:

    int do_thing(int pla, int sta);

    int do_thing(int pla, int sta) {
        // do the thing
        return 0;
    }

You’d see:

    int do_thing(int pla, int sta);

    do_thing(pla, sta) int pla, int sta;
    {
        // do the thing
    }

I am not used to this syntax; for a while I wondered if it was still valid C. Compilers will generally take it and throw out warnings for implicit int return values at level 3 and finally complain that the syntax is archaic at warning level 4.

As the Amiga wa a 16-bit machine all of the int values were actually 16 bit word and long being used for the 32-bit words. So there’s a few places in code that specify numbers as 123L which causes a modern compiler to grumble when being assigned to an int.

One of the most egregious things in the original code is the use of direct modification of string literals. The game does this a lot because it takes a padded template string and pokes in the various characters it wants before finally displaying it.

    char* str = "%000 %000";
    str[0] = 'A';
    somefunc_that_modifies_str(pla, str);

In MSVC this is “fine”, but in clang/gcc it causes a segfault. It turns out that this is undefined behaviour in the C standard, so to stand any chance of this running on anything other than Windows I had to reluctantly change it.

I took the fast and dirty way; I changed the definition of IntuiText from:

    struct IntuiText {
        ...
        char* IText;
        ...
    };

to…

    struct IntuiText {
        ...
        char IText[128];
        ...
    };

And then fixed all the compiler errors by strcpy the original assignment into this new buffer. There were about 100 places to fix, but it wasn’t too bad in the end. With that done I was finally able to get a version working on Linux… well, sort of.

The final fix I had to put in place was to handle the different values for return. Some sections of the game detected the return key fine, others simply would not. The original game uses \n and \r interchangably, so I picked \n as my value for return and always made sure the game checked for that. This was a bit of fun, as sometimes it used \n and others it used 0x0d, but I got there in the end.

Port Issues

There’s a couple of things that don’t work properly in the port.

The first is the mouse cursor graphic; I don’t use the game’s original cursor. Fixing this should be simple, but it’s not because dos-like doesn’t expose what I need. I can’t reach into dos-like and say “hey, use this graphic” for the cursor because it’s hidden in the guts of the framework. It’s possible, but just not exposed.

The second is the mouse cursor (again); it doesn’t display in full screen mode. This is a choice in dos-like to hide the cursor in fullscreen. It’d be nice to be able to control this myself - but would require a fork and modifications to the engine.

The original code’s savegame system uses a direct write of the game struct data to disk; whilst I’ve preserved this in the ported version it does mean that the original savegames are incompatible with the port. It also means that different platforms may not support save games due to how the compiler chooses to pack and organize the data in memory. Obviously today reading and writing savegame data like this is really not a good thing to do; so it’s something I’d like to go back and clean up if I have time.

The final annoyance is that the game doesn’t create the folder for your savegames so saving them out will fail due to the folder not being there. It’s probably quick to add, but I haven’t done it.

The release

I decided to release the port to itch.io and put the source code on github.

Very few people (if any) will actually play it, but I figured that on the off chance someone had the same memories I did, then they have the opportunity to relive them.

Porting the game to Linux took twice as long as the original port to Windows. This was largely due to finding the string literal issue, dealing with the newline problem (which only surfaced on Linux) and generally not having a dev environment for Linux itself outside of WSL2. I would love to get a Mac build going, but I simply don’t have the hardware to dev or test it.

The test of time?

I have been enjoying playing the ported version. It took me right back to my room in the 1990’s and evoked the same feelings I had for the game back then. Even the simple ASCII graphics are enough to create an interesting and immersive experience; I feel like I don’t need any additional graphics - my imagination can pick up the slack just the same as it did back in my youth.

It amuses me that only one alien race is actually implemented. The game detects klingon, romulan, alien and beserker as ai from the name you enter for the player, but only has logic for the klingon. The rest simply enter a no-op loop, with the exception of the beserker who declare war on everyone and then no-op.

There’s aspects of the game that haven’t held up well; the UI/UX could do with some tweaks (ability to cancel a command, anyone?) and man that three-step prompt to save game/continue/play after every turn really grinds your gears after a few dozen goes. A simple notification system when a tech item unlocks would be nice too.

The research system is linear and doesn’t really give many options. Being able to scrap obsolete ships would also be a nice feature. I found that all my early game stuff goes out of date really quickly and just sits there in a task force, occupying a valuable slot.

Hey, maybe I should roll an SE edition with some of these QoL tweaks built in?

I’m also very interested in getting a version running on the Interak using the z88dk cross assembler.

Enough of this patter, I have Klingons to kill.