Hello, loves!

I feel that I’m in a bit of a hole and still digging. Let’s see how we might turn this around. We find ways to keep our oppressors off our backs.

I’m finding tests to be difficult to write, difficult to get right, somewhat fragile, and not very focused. That being the case, like any reasonably lazy person, I tend not to write them. Like any reasonably decent programmer, I often get away with it … until things change. Then I find that I need to run the whole game to be sure that things are working. That slows me down at best, and it’s dangerous, in that I don’t do it after every little change and defects can lurk long enough to make them difficult to find.

This is a vicious cycle. I don’t write tests, I add code, the code needs tests, it’s harder to test, I don’t write tests.

I do not like this situation. It’s slowing me down, and it makes me feel squidgy because I know a better way and I’m not doing the best I know.

What’s going on?

Let’s think about some of the forces making things worse, to see where we might be able to start reversing things.

  • Dungeon has upward of twenty methods, which is a lot.
  • DungeonView has more than twenty-five methods.
  • DungeonLayout has twenty-eight methods.
  • Testing many objects requires a complete dungeon setup or extensive faking.
  • The PubSub event-like design complicates test setup.
  • Content objects seem to be proliferating and their tests are tricky.

A class with twenty or thirty methods is almost certainly handling more than one idea, though there are exceptions. We can sometimes offload some of that capability into helper objects like yesterday’s Interactor method object. However, if the original class is not well tested, splitting out its capabilities is risky. (Vicious cycle.)

What shall we do right now?

Business as usual, but better. It is tempting to dig into those big classes and tease them apart. I could justify it here on the theory that my reader (hello, reader!) might want to see how I break apart classes that are too big. And, on some future day, I might just go there and do that. But I really think that it is best to improve code that we’re working on because we area dding capability to the product. Most programmers don’t have the luxury of working on the code that most interests them. Under the yoke of oppression that comes from earning a living, the best they can do is to improve the code that they must work on, barbecue their cruel masters demand features that impact that code.

What enhancements do we need?

I think that the main enhancement we need is likely doors. As things stand, the dungeon view shows wall structures between rooms:

map showing walls between rooms

As things stand, Dot can walk right through the walls into adjacent rooms. What we want is for most of the boundary between two rooms to be impassable, and then to put doors of various kinds somewhere on each border. Probably just one per border, possibly more than one.

There will be a few kinds of appearance, open, closed, barred, etc. Some doors will be invisible, some will require keys, and so on and so on.

We want doors. We can see that there will be a lot of changes necessary before we have them completely worked out, but it seems likely that a door might be represented by a content object that refuses entry onto its tile until its conditions are met.

If I were setting expectations, I would suggest that we can probably hack in a visual image of some door type things and rig them not to allow Dot to step on them, and therefore not through them to the other room. We can clearly rig a button or lever that would “open” the door, and we can almost certainly change its texture to an open configuration.

However, after that initial likely success, there is much work to be done. We’ll need to change the dungeon movement rules not to allow passage through walls, only through doors. We’ll need to figure out how to automatically choose reasonable places for doors, since we are auto-generating the whole dungeon and its shape is arbitrarily weird. And I think we’ll have some graphics issues, because while a door might look good against a north wall, it may not look so good on east or west, or even south.

So, I’d suggest, we’ll see progress, but should expect that fully working out doors may take some time.

As part of that effort, we’ll be working out door behavior, in particular how the door knows whether it is open or not. We have the basic mechanisms in place, but it will be desirable to have at most one basic Door content item, not an appearing one, a giving one, and so on.

Let’s do something

I think we should start by just drawing some doors somewhere, with a simple DrawableContent item. We’ll just create some in main.

map showing a few doors between rooms

To me, those look like possibilities to the north and south, and a bit odd east and west. We could imagine rotating the east-west ones so their rounded parts point outward. Let me see if I can hack that in.

map showing east and west doors pointing outwards

That’s not too bad, and it wasn’t that hard to do as a one-off. But it makes the code messier and doesn’t work in any object except DrawableContent:

class DrawableContent(Content):
    def __init__(self, name, resource=None, scale=0.5, angle=0):
        self.name = name
        self.resources = [resource]
        self.scale = scale
        self.angle = angle

A new angle member, defaulted to zero so no existing code needed to change. Then in DungeonView:

    def create_content_lists(self):
        self.content_views = dict()
        self.content_sprite_list = arcade.SpriteList()
        for cell, content in self.dungeon.contents.items():
            for item in content:
                resources = item.resources
                scale = item.scale
                view = ContentView(item, resources, scale)
                try:
                    angle = item.angle
                    view.rotate(angle)
                except AttributeError:
                    pass
                view.sprite.position = cell.center_position(cell_size)
                self.content_views[item] = view
                self.content_sprite_list.append(view.sprite)

That try catch is a hack to ensure that we only try to rotate if we have a provided angle. I think this code could be moved to the ContentView, but for now we have this:

class ContentView:
    def rotate(self, angle):
        self.sprite.angle = angle

Angles, curiously enough, are clockwise. The code in main that installs the door images is just this:

    door = DrawableContent('door', my_resources + '/door1/1.png', 1)
    dungeon.place_content_at(Cell(32, 32), door)
    dungeon.place_content_at(Cell(32, 24), door)
    door = DrawableContent('door', my_resources + '/door1/1.png', 1, angle=270)
    dungeon.place_content_at(Cell(28, 28), door)
    door = DrawableContent('door', my_resources + '/door1/1.png', 1, angle=90)
    dungeon.place_content_at(Cell(36, 28), door)

This door texture has three states, the closed one shown here, an opening, and a fully open texture. In my existing art there are two other doors, a narrow portcullis kind of thing that has four states, and a single-panel door with three states, closed, opening, open.

This has all been pretty much a spike to learn some things and to show what a door might look like.

We’re about an hour and 45 in, so let’s reset the code and see what we’ve learned.

Summary

If this were a real game, I think we’d have the artists scrambling to figure out better door graphics, but as iconic representations of open and closed doors, for our purposes, I think we’re in good shape. There are going to be arrangements of borders where the doors may look odd, such as at the top of the cross-shaped room at the center of our pictures here. The portcullis or narrow door might be better in those locations, or we might just go ahead, because we’re here to work with the code, not to build the next World of Warcraft.

We’ve identified yet another aspect of content that probably needs to be supported throughout, namely the angle. And you can bet that someone is going to come up with an idea that requires us to change the angle of an object on the fly, not just once and for all as we did here.

For doors to make sense, they have to refuse entry when closed and allow it when open. That’s a thing that we only have partly worked out. The spike lever never allows entry, as intended. The spikes need to allow entry only when not cycling and down. So we need a general way to control cell entry, to apply to both doors and to spikes.

So we have a few “stories” that direct us to work with Content, and that’s a good thing, since we have one abstract class and five concrete classes in that hierarchy and there is code duplication and conceptual duplication that could use more than a little improvement.

We have a perfect situation, features that are desired, and code that needs improvement, supporting existing features and that needs to support the new ones. So we really have no sensible alternative other than refactoring the Content classes to better support what we’re doing.

We do have, of course, the tempting possibility of “just” copying and pasting to make a new DoorContent class, adding to the mess. Under enough pressure from the whip-cracking overseers, we might do that. But fortunately, we have been providing capability in a smooth enough flow that our overseers aren’t motivated to crack their whips.

I hope that you have a good enough relationship with your overseers to avoid the whip-cracking as well. If not, and you can’t find another situation in these trying times, I can only suggest trying to build a better relationship with them by moving toward smooth continuous delivery of visible capability.

Wow, if I’m not careful I’m going to depress myself, and I’m not even working for a living.

Cheer up: there is joy in doing good work and we can all improve how we work, generating little jolts of joy. I wish you well with it, and I’ll see you next time!