Hello, loves!

In testing the inventory display spike, I hit some bumps in the road. Let’s see what we can do. Tout le monde déteste l’IA.

Frequent readers may recall that the dungeon contains a bee, and that the bee wants a flower, and that if Dot has the flower, the bee is grateful and gives Dot a delicious honeycomb, which will presumably be useful later. Well, it turns out that while the bee has the honeycomb gift, the honeycomb does not have a sprite, and bad things happened. I have a hack that makes it work, but it’s more egregious than we can tolerate.

Another issue that I’d like to understand is the way that perfectly reasonable sprites seemed to need their scale reset to display in inventory. As I write that, I get an idea about what might be going on. In normal play, the screen zoom factor is 64, because the nominal size of a floor tile is 16 pixels. So that would make the nominal size of a sprite that displays inside a tile quite small. Since the inventory panel is currently scaled at 1 … it may be perfectly reasonable that the sprites are tiny until we bump them up. I bet that’s what’s going on. Mystery reduced, though I’m not sure whether just banging the scale up is a good idea: we might want to zoom the window instead.

Anyway, we can belay the scale issue and look at giving gifts a sprite. I have an explanation for what’s going on there as well, though we’ll want to review the code for more clarity. Basically, when we are getting things going, the DungeonView is created, and it creates the KeyedSpriteList, which is created from all the content in the dungeon, both allocating floor tiles and content items like flowers, keys and jewels. All of the sprites are kept in an Arcade SpriteList, and they can be found, when the game needs them, via the KeyedSpriteList, which can find a sprite based on the cell it’s in (a floor sprite) or by the Content item that controls the sprite, namely the flower, key, or whatever kind of Content it is.

This process will create a sprite for anything in the dungeon, and store it in one of two SpriteList instances, the floor list of the content list. Those lists are drawn by the KeyedSpriteList on every draw cycle.

The reason that the honeycomb gift has no sprite is that it was not in the content of any cell at the time the above work was done: it was somewhere deep inside the QuestGiverDenizen.

Here’s the egregious hack I used to get it to work in the inventory spike we’re working on:

class KeyedSpriteListMaker:
    def create_content_lists(self, dungeon, keyed_sprites):
        for cell, content in dungeon.layout.contents.items():
            for item in content:
                keyed_sprites.make_visible_content_sprite(item, cell)
                if item.name == "Buzz":
                    try:
                        info = item.info
                        denizen = info.denizen
                        item = denizen.knowledge.gift
                        sprite = ContentSpriteMaker(item.resources, item.scale / 16).sprite
                        sprite.visible = False
                        keyed_sprites.add_content(item, None, sprite)
                        print('added')
                    finally:
                        pass

We don’t really need the try in this case, since we’re checking the name ‘Buzz’, but originally I was trying to get the code to work whether the content has info or not, whether it is a denizen or not, whether it has a gift or not. I wonder if that code will work without the if.

I have no idea why I had finally there, not except. No excuse: I was hacking to make it go. In this form it works:

    def create_content_lists(self, dungeon, keyed_sprites):
        for cell, content in dungeon.layout.contents.items():
            for item in content:
                keyed_sprites.make_visible_content_sprite(item, cell)
                try:
                    info = item.info
                    denizen = info.denizen
                    item = denizen.knowledge.gift
                    sprite = ContentSpriteMaker(item.resources, item.scale / 16).sprite
                    sprite.visible = False
                    keyed_sprites.add_content(item, None, sprite)
                    print('added')
                except AttributeError:
                    pass

We could inline to make that less unreasonable. But what would be better? What if every Content instance could return a collection of other Content instances for which it wants sprites made and recorded?

Let’s apply Wishful Thinking to see if we can make that happen. We’re just spiking anyway. I am going to commit a save point however.

    def create_content_lists(self, dungeon, keyed_sprites):
        for cell, content in dungeon.layout.contents.items():
            for item in content:
                keyed_sprites.make_visible_content_sprite(item, cell)
                for contained_item in item.contained_items():
                    sprite = ContentSpriteMaker(contained_item.resources, contained_item.scale / 16).sprite
                    sprite.visible = False
                    keyed_sprites.add_content(contained_item, None, sprite)

Just one problem, Content does not understand contained_items. But it could

Content is quite simple:

class Content:
    def __init__(self, *, name, resources, scale,
                 interaction=lambda self, interactor: True,
                 info=None,
                 subs=None):
        if subs is None:
            subs = []
        self.name = name
        self.resources = resources
        self.scale = scale
        self.state = 0
        self.info = info
        self.subs = subs
        self.interact_with_player = types.MethodType(interaction, self)
        self.dungeon = None

    def run(self, pub_sub):
        for sub in self.subs:
            sub.callback = types.MethodType(sub.callback, self)
        pub_sub.subscribe_all(self.subs)

So we need a contained_items method. We could put the equivalent of the try block in here. In fact, let’s do that, because it’ll make things work and it’s better to work from code that’s working.

In fact I didn’t do the try block. Instead I did this:

class Content:
    def contained_items(self):
        if self.info is not None \
            and 'denizen' in vars(self.info) \
            and 'gift' in vars(self.info.denizen.knowledge):
                return [self.info.denizen.knowledge.gift]
        else:
            return []

This is … well … this actually works, and all the nastiness is inside Content, which is at least somewhat reasonable. Here’s a picture of the honeycomb in inventory. You’ll notice that Buzz does not yet take the flower, so it is still in inventory as well.

map with inventory panel showing honeycomb and flower

I honestly think this spike is reasonable enough to commit. We’ll want to move the code to a method or to a section, and so on, but it’s really doing just what we need for now. So commit: inventory spike working well enough.

Just a few lines of code, but that’s the right size for one programming session, after which a break is always appropriate. Let’s sum up.

Summary

We have a credible first cut at inventory display, enough to show to people and get feedback. Internally, it’s still just barely acceptable, and some might object to retaining it. Our practice here in the dungeon is to save experimental code when it is roughly right, by which I mean something like “most of the pieces of the code are in the right place if not in the right shape”.

You and your team might be more strict than that. I might argue that if you are any less strict, the ice is pretty thin where you’re skating, especially if you don’t clean things up quickly, which we’ll be doing here over the next few sessions.

Issues right now include:

  • We should probably look at KeyedSpriteListMaker to see if there are improvements to be made, though my assessment right now is “not bad”.

  • The contained_items code in Content is pretty nasty, checking for the existence of objects and keys before ultimately finding something and pulling it out of the inner inner guts of the Content item. It is, however, fully contained inside Content, which is the sort of thing one should do with nasty code. Perhaps we should add an instance variable to the Content, contained_items, and fill it in during the creation of objects with, um, contained items. That would add a slot to the Content but there aren’t all that many to worry about.

  • The scaling issue mentioned above needs exploration if not some kind of revision to the code. It’s possible that we should change the scaling of the inventory pane so that we don’t have to fiddle the sprite scale. If we ever deploy an inventory item into the dungeon, we’d have to scale it back down, as things stand. Possible but kind of wrong.

  • There’s probably work to be done on the general behavior of the inventory panel, such as dealing with multiple copies of the same kind of item, and the possibility of more items than will fit in one column. We could fit about a dozen items as things are now, and there isn’t much room to crowd them. I’d guess we could get 14, possibly 15 in there at the current scale. Of course we could scale them down, etc. Point is, there’s lots to improve there.

Looking further out, and given that the whole point of this is to write code and improve it, I think there’s an interesting problem in allocation of items into the dungeon, with an eye to placing keys on the right side of doors and the like. My tentative thoughts there are to allocate a subset of the rooms, populate them with keys and similar solution items, place the doors or other obstacles, then allocate another tranche of rooms, rinse repeat.

Lots of hand-waving there. We don’t even have doors or other obstacles.

We need more Denizens, gift-giving and otherwise. It’d be fun to figure out some puzzles.

What will we do? We’ll do whatever I find interesting, or, perhaps, if a reader asks a question or suggests something, we might work on that.

See you next time!