Software & Apps

Cowleyfornia Studios – Why am I writing a commercial C game in 2025

For the last few years we have been working on a train management game, Iron Roads, which we released in Early Access today. Somewhat atypically for a game released in 2025, Iron Roads is written in pure C, not C++, pure C99. As a choice it has its ups and downs, which I would like to share in this post.

The TLDR why I chose C is that I want portability and simplicity, but most importantly I want to be clear about what my code is doing. I want to know where it allocates memory, where performance problems are likely to arise, and I’m willing to pay a big price to work in a language that can’t hide details.

The requirements, ie the game

Screenshot of the iron roads

Obviously any technical decision should be a function of the game being written.

Iron roads is a 2D train simulation game. Our development focus is the game that comes from optimizing a complex network of tracks with many trains. In addition, we always wanted this to be a game that is portable to all platforms, and accessible to many players. Technically, this means:

  • Graphics codes can be simple. Unlike a 3D game there is no need for linear algebra, or operator overloading which would be useful in that case.
  • The simulation code is not that simple. The pathfinding calculations are complex, constantly running, and I know I will spend a lot of time optimizing the engine to support more trains.
  • It should be portable across multiple platforms. An unfortunate quirk of games development is that the SDKs for some of the platforms you’re targeting are more than just an NDA. This has the unfortunate effect that most programming languages ​​that are popular choices on desktop platforms, are not ported

Other languages ​​I tried

In a technical sense Iron Roads followed a winding path to its final form, beginning with a series of partial prototypes. In total there are four of three languages: Go, Haskell and Rust.

Haskell is the most interesting of these experiments, and is worth its own blog post. Personally, I hope it does because it’s a lot of fun to work on. Unfortunately the requirements of a high frequency game loop seem too antithetical to idiomatic Haskell code to work for me. Soon every function I wrote seemed to be on top of the IO monad, and it stopped feeling like Haskell code at all.

Go and Rust are more viable options, and it’s not hard to see myself writing Iron Roads in either of them. Unfortunately they share with Haskell the difficulty of porting the language’s runtime to a console, and no ports seem to be publicly available. As mentioned above, NDAs mean that very few languages ​​have first-class support for console platforms, and Go, Rust and Haskell are not among those that do.

There are cases where a language is transferred to a powerful individual. To gain access you need to convince that individual that you signed the NDA as well. You usually get access, but the upstream project probably doesn’t, so there’s no guarantee that breaking changes won’t surface at any time, leaving the NDAd fork.

C/C++ is provided by all platform owners, so they’re safe choices, and C# has enough corporate momentum behind it to more or less guarantee a port, but beyond that the the only languages ​​I feel confident I can confidently use are those that embed C, like Lua, or those that compile it, like Nim (aka the prototype I regret not writing!).

Lua

I have to clear that saying I wrote Iron Roads in C is a lie. Iron Roads is written in C and Lua.

Lua, for those unfamiliar with it, is a scripting language designed to be easily embedded in other applications, and is often used in games for that purpose. I wrote the low-level code that runs each video or simulation tick in C (approximately 40k sloc), but a lot of the higher-level game logic, including all the level-specific code, is in Lua (approx 8k sloc).

This structure seems almost an inevitable consequence for a game written in C. Greenspun’s tenth rule says that any sufficiently complex C program contains a partial implementation of Common Lisp, but in the gamedev world it is more accurate to say that any sufficiently complex C/C+ game + contains Lua, and Iron Roads is no exception.

Why not C++?

An immediate question for someone who works in C is why aren’t they working in C++?

Since I always knew I would write “content” in Lua, my priority in C code was to create an efficient and reliable engine for routing trains, marking their positions, and interpreting the everything. The routing engine in particular is a trivial piece of logic, and from the beginning I was concerned about its efficiency.

When profiling previous games I wrote in C++ to improve their performance I often found STL content as the root of a performance issue, it was only necessary to rewrite those parts to code in an optimized pure C equivalent.

There’s nothing wrong with containers, or their implementation, but they hide the dynamic memory allocation and de-allocation they do for you. This is their greatest feature, however it can be easy to write code that seems like it should be efficient, but it isn’t. I personally found working in C to help with this problem!

I am also tired of the compilation speed of C++. I have never worked with a C++ codebase that compiles easily.

When writing a game, especially when covering the roles of designer and programmer, a lot of my time is spent in a loop of playing the game, finding something to change, changing it, and playing it again . I strongly believe that speeding up this iteration loop improves my process enough to have a noticeable impact on the quality of the game I write.

Generalizations are by no means accurate, but I find that idiomatic C++ tends to take longer to compile than the equivalent idiomatic C code. This is a big vote in favor of C for me for Iron Roads.

Finally, literal structures in C are unreliable. I have no idea why they didn’t do it in C++.

Achievements

  • Quick Compilation Iron Roads builds quickly, so I can return quickly, and this way C helps my workflow a lot.
  • Optimization is easy In general I found the production of the code I wrote to be “no wonder”. There are very few “invisible” effects in C, so it’s easy to understand what a performance or memory penalty might be, and avoid it.
  • Minimal porting issues Not that C is completely cross-platform. It clearly isn’t, and the many shims in the codebase prove that. However, I have never been to any platform where this is impossible on port C, which is good

The real achievement though, is that for me the choice of C is part of a wider pattern of choosing the most straightforward technology possible to build Iron Roads. Maybe I just can’t be trusted to write C++, but it’s good for the readability and performance of the codebase, and good for keeping me focused on the game I’m writing rather than the technology I’m using to write it.

Failures

  • serializations Compared to serialization in almost any modern language, the serialising state of C (or C++) is very poor. There are no reflection capabilities in C, and less capabilities in C++, so you have to manually specify each and every field when serializing or deserializing game state.
  • Boilerplate This seems almost too obvious to teach, but strings, arrays, dictionaries, etc. in C require a lot of boilerplate. This strings problem is a bit annoying when the game is only in English, but it becomes worse when we localize the game in Chinese.
  • Runtime issues Valgrind and asan are amazing, and it’s hard to imagine the game being written without them, but they remove the runtime issues that other languages ​​flag at compile time.

CONCLUSION

I’m honestly not sold on writing games in C, and I don’t think I’ll repeat the process. I’m happy with the result codewise, and designwise, but the process of getting there was very difficult.

I don’t see any point in going back to C++, but I’m very interested in experimenting with modern, higher-level languages ​​that compile to C. I think the first prototype of my next game will be written in Nim!

2025-01-20 16:50:00

Related Articles

Leave a Reply

Your email address will not be published. Required fields are marked *

Back to top button