💾 Archived View for gemini.ctrl-c.club › ~nttp › writing › roguelike-coding-advice.gmi captured on 2024-02-05 at 10:30:23. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2021-12-03)
-=-=-=-=-=-=-
Roguelikes are hard to make, and it doesn't get any easier with experience. In time, I learned to keep it fun at least, and even that takes effort.
My favorite languages are dynamic, like Python and Javascript. They're very powerful, letting me express lots of complex data and behaviors in just a few lines of code and, thanks to garbage collection, not worry where it all goes. That makes them doubly tempting when working on a complex kind of game.
And it's a trap. Looking at things that way is a recipe for making a big mess that will be harder to untangle later.
To wit: in dynamic languages, it's all too easy to toss a bunch of keys and values into a dictionary and call it a day. Don't do it, because it will come back to bite you. Take the time to define classes. No, not for the added type checking, but to *express intent*: "this is supposed to be a missile".
On a related note, for the longest time I've been using character literals to denote tiles. It just makes sense and it's kind of elegant. But integer constants have, you know, names. Plus, they can be used to index into an array of colors, for example. Bonus points if your language of choice has enumerations, like those in the C family. Funny how my favorites keep adding other crap, but leave out highly useful constructs like these for "simplicity".
Data types are a vocabulary you define to explain your game, to the computer and other programmers alike. And one thing we do in meatspace is giving names to often-used concepts. For example, the way I handle level generation and visibility involves working with pairs of 2D coordinates, like this:
struct Bounds { int x1, y1, x2, y2; };
It took me a shamefully long amount of time to realize that should be a named type rather than messing around with tuples, or other improvisations.
That said, don't go overboard. It's tempting to represent your game world as a doubly linked list of levels, each with a list of items and monsters on them, all keeping references to each other because they need to, you know? But if your levels are all the same size, all you need is a 3D array of tiles. Items and creatures can make do with just x / y / z integer coordinates then.
Roguelikes are complicated enough in the first place. Save your energy for adding ranged combat or whatever.
Keeping it simple does another thing, too. As I was saying, garbage collection is a useful feature that can spare you many headaches. But it can also conceal bugs. Recently, while porting an old roguelike from Python to Genie, it turned out right at the end that an object wasn't deleted when it should have been, while another had the opposite problem. Just in case you were wondering how memory leaks can happen in a garbage-collected language. Lesson learned: weak references are your friend. And by the way: I fixed it by changing the code so that the world is created once then reset with every new game instead of being remade from scratch all the time.
Last but not least: if you check out the article section on RogueBasin (always a good idea), you'll be told that difficult aspects include: timekeeping, missiles, magic, inventory, user interface... pretty much everything in other words. And it's true! But what gave me the most trouble by far was to make sure that a decent mix of items and monsters spawns on each level, such that the game remains challenging yet winnable. It's partly a matter of game design (you need enough of everything to choose from), but also one of programming, as it requires a good way to represent probability ranges, and also to make use of them.
Couldn't say how to do it though, because I never found a *good* way.
So much for the titular advice. But keep in mind that _Rogue_, a game 40 years old as of this month, is still considered one of the best (among countless clones). And it counts as a coffeebreak game nowadays.
Last modified: 21 October 2020