It turns out that having a relative die really kicks your side-project productivity squarely in the balls.
However, during my time off, the guy writing the tutorial I was using returned and finished it! Yay!
I’d been trying to do the start-up menu — and was getting bloody nowhere. I asked for some help on /r/roguelikedev, and it turns out that the developer had completely overhauled event handling in a way which would largely solve the problems I was having. He pointed me to the git repository showing the new code, but I was struggling to figure out all the places I’d have to change.
So I’m back on track, with a loading screen and everything.
I also got to check my savefile mechanism against the recommended version! The recommended version makes smaller files and seems a bit more efficient in general, but honestly, I prefer a lot of elements that I came up with.
Namely, my code for creating a backup of the autosave and restoring it if the save goes awry. I’m keeping that shit.
I also overhauled how monsters and treasure get generated so that it’s a bit easier to twiddle how often something does or doesn’t show up.
I’m back on this thing. I’m going back and forth about how deep I want to go into this once the tutorial is done. I’m a little put off by how difficult some “simple” things are, like creating basic menus or just communicating with the player in general. I’m wondering if I shouldn’t give the Unity engine a try for the product; the end result would wind up being more accessible, I’m sure.
Though I’m equally sure that has its own grab bag of headaches. Still. There are some assets for creating roguelikes in Unity that I could try out.
But for now, what the hell. The ASCII version of the project is moving forward again. May as well see it through to the end of the tutorial, at least.
Today, I hit an item on my to-do list: is my auto-save only saving after every player turn, or after every mouse action?
Turns out, the answer was “after every mouse action.” So that’s not, you know, great; I’m new to this whole game development thing, but I’m pretty sure saving your game forty times because the user dragged the mouse across the screen is considered sub-optimal design. So I worked to put that autosave command elsewhere.
The input handler seemed like a sensible spot; after I told the engine to handle the enemy turns, I also told it to save. Easy-peasy. However, this introduced me to the concept of “circular dependencies,” which Python finds quite objectionable. Basically, I had to dig into a lot of the weird stuff the tutorial put at the top of many files (like “from __future__ import annotations” and “if TYPE_CHECKING” and other such things) and understand why I needed them. Good news! I now understand why I needed them! Yay learning!
However, I stumbled across a very weird error when I chucked a fireball:
AttributeError: Can't pickle local object 'FireballDamageConsumable.get_action.<locals>.<lambda>'
Baaaah. The code I was using to save the file had beef with the nuances of how some of my stuff works — nuances I barely understood myself. So that looked no damn fun to debug.
But more troubling, I discovered that if I quit the game right after seeing that error, the auto-save file got corrupted and could not be reloaded. Having a game that corrupts its own auto-saves whenever something goes wrong sounds infuriating, so I had to get THAT shit sorted out, too.
Luckily, I had an easy way of generating those corrupted data files on hand.
I posted a question to Stack Overflow, but as one might reasonably expect on a Sunday afternoon, there were no Python nerds willing to lend me a hand. Ah, well.
I researched how to move files around in Python, then whipped-up some code that would move the old auto-save file to a backup and then restore the backup if something went wrong during the save. This code did not work. I spent about an hour tearing you my hair trying to figure out why. I had all these theories; was the filehandle still open? Was I running afoul of an asynchronous race condition? I investigated all that shit only to discover….
A typo. The backup code was looking for the wrong fuckmothering filename.
Once that moment of Software Development was behind me, I started digging into the reasons why the save was fucking-up. Still not sure I know the details why, but the solution turned out to be a package called “dill.” Yes, the code that serializes — “pickles” — the object for me works better when you enhance it with “dill.” Once I figured out how to do that, I was golden.
There’s an old truism in software: for every sufficiently advanced problem, there exists a solution that is simple, elegant, and wrong.
And that’s how my simple, elegant three-line save method turned into something closer to thirty lines, not including blankspace and comments.
I was planning on figuring out some menu stuff today. That’ll have to come next.
Latest development session involved getting into the saving & loading tutorial — from the outdated Version 1 of the tutorial. Version 2 still isn’t out.
But that’s okay. I’ve been looking forward to seeing if I can start writing my own code in this thing.
Step 1: Refactoring! The tutorial recommends setting up a “constants” file, for all the key things defining the game and what it looks like. (IE, how large is the map? What are the potential room sizes? Where is the message log located on the screen? Etc.) For this, I did copy-paste the tutorial’s file as my starting point, then set about adapting it to what my code actually looked like.
This proved a bit challenging, as the constants it defined were a bit all over the place. This drove me to do some refactoring, to make the code closer to my version of “tidy.” For instance, why was the code responsible for sending data to the message log defining the log’s x/y coordinates every time it was called? There’s a MessageLog object, after all; why not just tell that thing where it lives when you create it, and then not worry about it?
Eventually, I got the code to a point where the “main” file was the only one that cared about the “constants” file, and acted as the traffic cop responsible for sending that data to the parts of the game that needed it. Seems to work pretty well.
Also wound up having to do some debugging to figure out why my “fireball radius” feature (which I’m inordinately proud of) stopped working. Turned out I’d done something foolish to the order in which things got rendered. It was frustrating, but at the same time, I felt very confident when I managed to track-down and correct that problem.
It turns out that saving the game state to a file is piss easy, even easier than the tutorial suggested. The tutorial was telling me to save all these objects individually, but I quickly realized that the only thing I actually needed to save was the “Engine” object. Had a little trouble installing the Python package used to serialize the object, but the problem turned out to be that Python 3 comes with that package pre-installed. Love it when the solution turns out to be “You never actually had that problem in the first place, dumb-ass.”
So, for proof of concept, I had the game save itself after every action. (On the to-do list: confirm that the game is saving itself ONLY after the player acts, and isn’t saving itself every time the mouse twitches. That would be inefficient.) Then, when the game powers-up, it first checks to see if the save file exists; if it does, it loads it instead of creating a new dungeon.
It works! (After I figured out that the data serializer and the file manager have a difference of opinion on whether one should actually specify a “.dat” extension for the savefile.) It works SO well that, if you get killed, the next game will cheerfully reload the dungeon that has become your tomb, with you still laying there surrounded by the monsters that killed you. So, erm, maybe death should delete the save file.
It has the same problem if you win, though; clear the dungeon of monsters, and your next playthrough will load you into the corpse-filled site of your underground rampage, with nothing to do but wander and contemplate the ultimate futility of violence. So I probably do need to define some formal “You won!” condition just so I don’t have to manually delete the save files.
From there, the tutorial would like me to create a “Main” game screen, from which to ask the user if they would like to start a new game or continue an old one, and … goddamn, that’s actually going to take some effort. Looks like the old version of the tutorial actually made you write some dedicated UI code for stuff like that. The new tutorial lacks that feature. So, I think that’s my next task: centralize all the pop-up menu code into some convenient centralized package that I can use to communicate with the player.
Turns out that saving a game is dirt simple, but asking the player if they’d like to save the game is quite challenging. Roguelikes are fuckin’ weird.
Got some worrying personal information tonight. So, I buried myself in programming. Nothing like some scrolls of fireball to distract oneself from loved one suffering over a thousand miles away.
Today’s lesson: ranged attacks! Status effects! Crowd control!
In addition to Potions of Healing, the tutorial has now guided me to create a Scroll of Lightning Bolt, Scroll of Confusion, and Scroll of Fireball. Since magical shenanigans lie at the heart of the game I want to create, I was paying close attention.
Of course, if I’m adding arcane devastation to the mix, I felt like I had to beef-up the monsters a bit. Instead of randomly sticking between zero and two of them in every room, the engine now seeds the room with between zero and five of them. Only seems fair.
I was also not impressed with how the tutorial handled fireballs. To show the area of effect, it presented a simple square — even though the blast radius is very much a RADIUS. You know, a circle. So the beasties in the square may or may not have gotten tagged by the spell depending on how close to the corners they were.
Time to do some off-road coding.
center: Tuple[int, int],
bg_color: Tuple[int, int, int],
fg_color: Tuple[int, int, int],
) -> None:
Twiddle the foreground and background colors of all visible tiles within a circle
x, y = center
max_x = min(x + radius, self.width)
max_y = min(y + radius, self.height)
min_x = max(x - radius, 0)
min_y = max(y - radius, 0)
for check_x in range (min_x, max_x + 1, 1):
for check_y in range (min_y, max_y + 1, 1):
if (self.distance((x, y), (check_x, check_y)) <= radius
and (self.tiles['walkable'][check_x, check_y])
and (self.explored[check_x, check_y])):
console.tiles_rgb["bg"][check_x, check_y] = bg_color
console.tiles_rgb["fg"][check_x, check_y] = fg_color
def distance(self, start: Tuple[int, int], end: Tuple[int, int]) -> int:
Returns the distance between two grid points, rounded up.
Might someday be enhanced to care about whether or not those two points on the grid
can actually see each other; for now, just a Pythagorean Theorem wrapper.
x1, y1 = start
x2, y2 = end
return int(round(math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)))
Yup, that’s effectively the first proper Python code I’ve written myself. Results in something that looks like this:
Everything highlighed in red there is gonna get hit by a fireball. How do they feel about it?
They were not fans. Killed the four orcs outright, and definitely took a chunk out of both trolls.
In addition, I figured out how to update the mouseover text to show you the critter’s current health. The game needs more feedback in general, but that’s a first step towards showing you that what you did had an effect even if you foe didn’t drop.
But this iteration of the game marked another milestone. After I took that screenshot, I went back to the code for another round of fiddling. At that point, I should just escape out of the game and restart it … but I wanted to see if I could keep going. After blasting that room, I wanted to see if I could keep going, clear out the dungeon before I got overwhelmed.
And I did it. And I felt kinda like a badass.
This was the first time I had fun with this game as a GAME.
It is still, objectively, kind of a piece of shit. But it’s a piece of shit that has stretches were it’s damned interesting. This is kind of awesome.
Anyway. I’ve hit the end of this version of the tutorial. The tutorial is an update of an older version that just hasn’t gotten to all the chapters the author wanted to cover. So I can keep going, but I won’t be able to copy-paste like I used to. Now, I’m going to have to figure out a lot of the details on my own.
That feels manageable. That feels like a good exercise at this stage, really.
So, next step: figuring out how to save the game.
I wanna take a BIG ol’ detour into laying down an initial version of the magic system, one that doesn’t rely on one-shot items. But, no, saving and loading the game is super important, and I should probably figure out how that works sooner rather than later.
Basically, I can now communicate with the player AND give the player shit to pick up. Whoo-hoo!
I’m starting to get a little judgy about how the code is architected, though. I was curious to know if the toolset I was using had in built-in tools for pop-up interfaces, and the answer is … kinda?
I now have pop-up windows for both message history and for my inventory. I was expecting those to each be part of a new class of some sort, but nah, each of them is a bespoke entity crammed into the code which processes commands and figures out what to do next. That just seems sloppy to me; I would have expected those windows to be their own thing, somehow.
That HP bar WAS its own thing, but I know that I’m going to want a mana pool of some sort to power the player’s spellcasting. So I’ve already refactored that into a more generic component so I could get the mana-point bar in there. (Right now, it’s purely for show; I definitely haven’t implemented spells yet.) They’ll likely be joined by an XP bar at some point. For the final game, I don’t think I want to use an experience-point-based progression system, but for the early going, it’ll do.
I’m also a bit confused by the decision to make all items default to being “consumable.” I’m hoping that’ll get rolled back later in the tutorial.
So, yeah, I’m seeing a whole bunch of shit I think I’m going to refactor after I get to the end but before I get into my own version of the game hot and heavy.
But, still. This game is coming together, and that’s really cool.
I mean, don’t get me wrong, it’s a fucking TERRIBLE game. If I came across it during my bundle playthrough, I’d pimp-slap the bejesus out of it for completely wasting my goddamn time. Tactics are simplistic and obvious, there’s no mechanism for healing yourself or upgrading your abilities, there’s next to no on-screen feedback for what’s happening, the win state is boring, the lose state is even more boring … this game is shit.
But, nevertheless, after this tutorial, I can now roam the dungeon and punch orcs! And punch trolls! And they hit back! And can kill me, which only seems fair! Trolls hit harder.
The code that makes this stuff happen is, by and large, comprehensible to me. I don’t get every nuance. (And it doesn’t help that this edition of the tutorial begins with “Okay, this is how we’re going to totally reorganize the code, because this way is better.” The author is probably right, but still … grr. Would have appreciated if they’d go back and retrofit the existing tutorials. Eh, it’s free learning, I can only bitch so much.)
Even if I don’t get every nuance, though, I can see where stuff goes. When I think about “Okay, how am I going to tweak this to handle an action-point-based mechanism instead of strict turn-taking?”, I have a really good notion of what needs to happen where. It’s pretty dope. I’m probably wrong in several important respects, but I know where to start.
Even more fun, I got to do a little debugging!
The latest version of the code knocked-out the feature by which you could hit “Escape” and quit the game — but only if you were still alive. Once you were dead, you could escape-out just fine.
I thought it was due to some nuances I wasn’t understanding in terms of how the event handlers were firing, but naw, there was just a block of code that hadn’t been copied over, but should have been. So MY code is actually infinitesimally better than the canonical code at this stage of the tutorial!
AND I figured out how to dump objects to the console for debugging purposes, which is super valuable. Also inadvertently got a sniff of just how many events get fired every time the mouse twitches on-screen. Spoiler: several.
Just for giggles, I also tweaked the AI so that the monsters don’t need to see me to bum-rush me; in this version, they all have security cameras and walkie-talkies. Doesn’t take long before the entire dungeon descends upon me. This makes things pretty fuckin’ brutal.
This is really awesome. My little game may be terrible, but it exists, and it’s mine.
There are now trolls (“T”) and orcs (“o”) scattered throughout the dungeon. They really don’t do anything, they just whine to the console about how nice it would be if I coded action handlers for them, too. But, to be fair, I can’t do much to them, either. I can just walk up and kick them. Which is rude, but, pretty harmless, you know?
I’ve also been tinkering with the color palate to make it look more like what I might expect to see in a later product. Yeah, it’s pretty drab, but those shades of gray make it pretty clear what’s happening, at least to me. Color will come from the things you can interact with; I’m fine with the bits defining the environment being kinda dull.
When it comes to coding action handlers, I’m following along pretty well, much more so than I was during dungeon generation. Which is good, because I think that’s going to be one of my major points of deviation from the script when it comes time to start developing the game; I think I want an action-point-based mechanism instead of a strict my-turn/their-turn progression. I feel like it gives me more stuff to play with.
Once the game gets deep into development, I’ll need to implement two different “modes” of travel: combat, which is strictly regulated by the action point mechanic, and non-combat, where you can just wander around and do whatever. That could be tricky — but that’s also a detail that’s going to be WAY down the line.
Next tutorial implements combat, at which point this kinda becomes an actual game. Which is really exciting.
Also still poking through the Python tutorial. Today’s topic: lists! You can do many, many things with them. Most of them are pretty straighforward, but some of the options get really esoteric. Whoever wrote the tutorial has an interesting idea of what constitutes “readable,” but that’s true of any language’s partisans, really. Once you become “fluent,” some really wacky shit becomes perfectly intelligible and you lose sight of how anybody could possibly find it opaque.
Nobody who explores a dungeon should have a godlike view of the hellhole into which they have plunged. Restricting the POV to just what you can draw a line to was the focus of today’s tutorial.
It’s one of those things that’s astoundingly simple (and leans heavily on an existing bit of code) if you know what you’re doing, and hard as balls if you don’t. Thanks to the tutorial, I can pretend that I know what I’m doing! Am I gonna keep knowing what I’m doing when I move past the tutorial?
Maaaaaaaaaaybe? The relevant code is powered by a lot of array shenanigans that I can only barely follow along with. Looks like the “numpy” package is a big fuckin’ deal here.
At this point, I feel like if I can iterate out from what the tutorial is showing me, I have a fighting chance of figuring out out. If I have to do something array-powered that’s nothing like anything I’ve yet done, that’s gonna be a fight. But what the hell, that’s what the /r/roguelikedev community is for. I hope.
And it’s possible that this tutorial is gonna cover so much ground that most of what I want to do is just going to be some iterative extension of it. That’d be nice.
Important to note: this version of the tutorial isn’t complete. It’s an updated version of a pre-existing 13-part tutorial, and it only covers up to Part 9. The author meant to have the final four chapters finished by now, but is apparently running behind due to life or something. If I catch up to the end, I may have to branch out on my own and try adapting the lessons of the old tutorial to the new code. That could actually be a really worthwhile challenge.
Also started going through a Python tutorial. Nothing too exciting there, though it is helping provide me with a little formal context for stuff I’ve already figured out. Python has an awful lot of fiddly bits in its method parameter definitions, definitely hitting “Enough rope to hang yourself” territory. Looks like the tutorial is pretty good about documenting any esoteric/inobvious features it’s using, at least.
Still feeling good about this. Looking forward to defining the battle spells by which fools shall be smoked.
Know what’s more interesting than moving around an infinite void? MOVING AROUND A MOTHERFUCKING DUNGEON THAT’S WHAT!
Granted, when the dungeon is completely empty, it’s not THAT much more interesting. (I’m the red “@” sign. That yellow one? That’s Larry. He’s just chilling, don’t worry about Larry. He’s just happy he didn’t wind up embedded in the walls again.) But, still. There are places I can go, and places I CANNOT go! And I’m generating this shit at random, just like a proper roguelike!
Still following the script, though things are getting sophisticated enough that I occasionally had to debug when I miscopied something. (Already learned that Eclipse’s in-line syntax checker can be fooled a bit easier than I’d prefer and is prone to reporting false errors that look alarming but that don’t actually prevent the code from running. Grr.)
I should probably use my next coding session to spend some quality time with a Python tutorial. I’m more or less following along, but I think it’s time to get myself a bit better grounded in the basics of that language.
This is coming together fast. I imagine it will come together a whole lot LESS fast once I hit the end of the scripted tutorial and have to, you know, decide some shit for myself. Still. This is cool as hell.
Installed a bunch of shit, including Python 3 and C# runtime environment. Hardest part was getting the Python plugin working for Eclipse; I had some cruft that I needed to clear-out before things would update properly.
But, it’s running. I do love me some syntax highlighting.
Getting the environment set up properly is actually a pretty major mental hurdle for me. I’m a clumsy sysadmin, and I never know how long something is going to take. I tend to do a lot of flailing around in the process of getting all the pieces into place. And given that my brain is always looking for an excuse to quit on the grounds that it’s just too hard and who KNOWS how long everything is going to take, the infrastructure is a great place for shit to fall apart. Let the project fail before it even began.
But it has begun. Suck it, brain.
I followed the first page of the tutorial. It’s all copy-pasting, but I am trying to follow along. I largely get it; at the very least, it’s providing a solid example for me to work with, and I tend to learn best when I get to do. This tutorial does do a pretty solid job of explaining WHY I’m copying what I’m copying.
I’ll probably want to detour into a proper Python tutorial, though, just so I can get a proper introduction to how the language does things. Still, this should give me a pretty solid frame of reference.
I got an executable running! Created a blank field for an “@” symbol to just kinda hang out it. Right now that little guy is basically Janet chilling in her void.
I even made made him move around.
Someday, that “@” is going to be a wizard running around shooting fireballs and raising the dead and exploiting the lower classes and shit. Baby steps.
The tutorial ended by advising I put everything into source control. I’ve been doing software long enough to know that yeah, that’s a solid move.
I COULD have just set up a local repository, but fuckit, let’s stay optimistic. I’m putting this sucker on BitBucket, where future collaborators shall someday be able to access the project. There was a bit of confusion logging in, as BitBucket has been acquired by Atlassian, and I found my proper login credentials AFTER Atlassian made a new one for me. So, two accounts!
That’s gonna confuse the tits off of me someday, I can feel it.
I’m using git, which is also what I use at my day job. I’ve never been in love with git; I find it to be a little impenetrable, and every once in a while I need to hit-up someone smarter than me to unfuck whatever I just did. But what the hell, it’s just me on this project, so it’s not like there are going to be merge conflicts.
Took a little wrangling to get my existing code pushed to BitBucket, but it’s in there. I even managed to do it using Eclipse; here’s hoping I can keep right-clicking my way to source control victory.
At some point, I really need to organize my thoughts on The Fun Bits of the game. But for the time being, I’ll keep plugging through the tutorial.