Developing a Retro-Roguelike Game for Multiple Platforms in C

2 days ago 1

Creating a game that runs smoothly across different vintage and modern computers is a complex and ambitious challenge. Can I achieve it?

Let me tell you the story so far; the process, obstacles, and solutions involved in making a roguelike dungeon crawler playable on systems like the Commodore 64, Commodore PET, and even more constrained machines.

Watch on YouTube

Why Build Games for Multiple Platforms?

Many enthusiasts collect old computers just for their nostalgic value. However, having these hardware collections provide playable experiences affords fresh ways to enjoy and preserve their unique quirks and capabilities.

My goal was to develop a game that can run on, if not all, then at least across numerous systems, utilising each machine’s features while managing constraints like memory, graphics, and input methods. 

First Approach: Using TRSE to Compile for Multiple Target Systems

The journey began with TRSE, a development environment supporting a huge number of vintage computer systems. My plan was to develop a text-based roguelike that could run on various hardware by compiling code for each target. 

Problems

  • TRSE is broadly based on Pascal, unfamiliar territory for me, especially when I take long breaks from the environment.
  • Many older computers had incomplete or inconsistent text support in TRSE libraries as most people in the community generally develop for each systems graphics hardware.
  • Differences in hardware standards like ASCII character mapping caused compatibility headaches, especially on pre-1980s machines. 

Lessons Learned

  • Multiplatform development on vintage hardware is additionally complicated compared to today because system standards and capabilities vary widely, even from the same company, using the same processor.
  • Support libraries are not always fully fleshed out in ways that match your vision, or might need adaptation for different systems.
  • TRSE is an amazing and pretty recent voluntary project so support for certain machines needs to come from the community.

Transitioning to C for Greater Opportunity

Realising my limitations from choosing TRSE, I shifted towards programming in C. Since C compilers exist for a broad array of hardware, from calculators to mainframes, my hope was to create a universal codebase. 

Anticipated benefits of C:

  • Wide availability of cross-compiler support.
  • I had experience writing portable code adaptable to multiple hardware environments.
  • Features for control over low-level hardware interactions when required. 

Reality Check

  • Despite C’s portability, a fair amount of conditional code must be used to accommodate different system architectures.
  • Variations in compilers (e.g., CC65, Z88DK) and variable adherence to standards (C89, C99) introduce added complexity.

Managing Platform Differences Through Conditional Compilation

One of the key techniques I used was conditional compilation. This process involves including or excluding parts of the code depending on the target system. 

Example:

#ifdef C64 // Specific code for Commodore 64 #endif #ifdef PET // Specific code for Commodore PET #endif

However, this approach becomes demanding because:

  • Different compilers have varying syntax and features.
  • Each system’s memory and graphics capabilities differ.
  • Code can become bloated and hard to maintain. 

My focus shifted from balancing the effort needed to support many platforms versus focusing on one.

The Challenge of Hardware Limitations

Supporting multiple machines means accounting for:

  • Memory constraints: e.g., I currently have a minimum 48K RAM limit, though I would love to address this for my beloved Vic 20 by utilising RAM upgrade cartridge support.
  • Graphics and character sets: Some systems have custom character graphics, others don’t.
  • Input controls: Many vintage machines used keyboards, not all had native joystick support.
  • Loading mechanisms: From tapes to disks, resource management varies.

All this complexity led to a strategy of focusing on systems with similar capabilities , mainly those with 64K RAM or more, such as:

  • Commodore 64, PET, Plus+4, Atari 800, BBC Micro
  • CP/M-based systems such as IMSAI and Altair
  • Linux, Mac, Windows (via terminal and a graphical version using Raylib) 

Developing the Core Game: Levels, Map Generation, and Graphics

Hand-Crafted Dungeons to Random Map Generation

A big win was creating a procedurally generated map to make each gameplay experience unique and to save me having the added task of hand drawing maps in a level editor. The main downside is the ram this process takes up versus loading a complete map on level change. Fortunately, the delay for generating the map is not as long as I feared so it is not a very noticeable pause in action.

This map generator produces random but walkable layouts, and based on guard-rails in the logic, adds the baddies and collectibles throughout the level.

That meant at this point the game included:

  • Basic player movement
  • Collectible items (power-ups, health)
  • Enemies with their own movement and attack profiles, like rats and goblins
  • Simple combat and item interactions

Transition to User Defined Character Graphics

Initially, as is traditional in the roguelike dungeon genre, the game was text-based, but even so there are variations between machines and even versions of the same machine. For example, the PET can have 40 or 80 text columns.

Adding graphics involves:

  • Defining the user defined graphics in a copy of the C64 default.
  • Loading the definitions into memory banks.
  • Changing character sets to the loaded custom user-defined characters.
  • Translating the usual character/object/bad-guy character code for the substitutes

Change in Strategy: Focusing on a Single Platform First

Given the complexity, I have shifted now towards prioritising the Commodore 64 as the main target platform because:

  • Probably still the most popular target retro computer in 2025.
  • It has great graphics and sound support.
  • Easier to cross-develop and test.
  • Core code can be deployed to other systems later.

Once the game becomes polished on the C64, I can adapt it to other systems with different capabilities, reducing the risk of distracting rabbit holes, code bloating, and hard to find bugs. 

Practical Tips for Multi-Platform Development

  1. Start small and focus: Develop a functional version for one platform before expanding.
  2. Use conditional compilation wisely: Limit branches by focusing on only the most common features.
  3. Prioritise core gameplay mechanics: Enable obstacles, basic movement and interaction first.
  4. Design flexible resources: For example, load graphics and data from external files when possible.
  5. Use emulators with CLI parameters for testing: They need to not only display how your game would perform on target systems but also not require lots of clicks and waiting before you see results.
  6. Research hardware quirks: Knowledge of each system’s specs helps optimise code even when cross-platform libraries and compilers exist.

Bottom Line: Balancing ambition with pragmatism

Creating games that work across many historical and modern systems involves managing a fine balance. While aiming for broad compatibility is appealing, the technical realities of diverse hardware makes it more realistic to carve out a manageable scope, starting with a single, well-supported platform like the Commodore 64 or the PET.

Focus on delivering a fun experience, learn platform-specific nuances, and then gradually adapt or port to other systems. By keeping an eye out of control code complexity, and incrementally developing, it IS possible to create engaging retro-styled games that honour the spirit of vintage computing while using modern programming tools!

As always, if you would like to know more, just hit me up on YouTube or the socials

Read Entire Article