I think I’ll try making the Button show up and down states. Sounds easy enough to spike something.

I’ll check the API. There’s an add texture, I think, and some way to switch by index. OK, there is append_texture, and cur_texture_index, and set_texture. I think the first and last are what we want. My first question is whether instead of providing the texture on creation we can append even the first one. I’d bet the answer is yes. To find out, change this:

class ContentView:
    error_texture = ':resources:images/items/star.png'
    def __init__(self, content_item, resource, scale=0.5):
        self.item = content_item
        try:
            texture = arcade.load_texture(resource)
        except (FileNotFoundError, AttributeError):
            texture = arcade.load_texture(self.error_texture)
        self.sprite = Sprite(texture)
        self.sprite.scale = scale_texture(self.sprite.texture, scale)

To this:

class ContentView:
    error_texture = ':resources:images/items/star.png'
    def __init__(self, content_item, resource, scale=0.5):
        self.item = content_item
        try:
            texture = arcade.load_texture(resource)
        except (FileNotFoundError, AttributeError):
            texture = arcade.load_texture(self.error_texture)
        self.sprite = Sprite()
        self.sprite.append_texture(texture)
        self.sprite.set_texture(0)
        self.sprite.scale = scale_texture(self.sprite.texture, scale)

Without the set_texture, you get an error texture:

Map showing vivid pink/black flags for all textures

Charming but not the look we’re going for.

Since that works, I’ll commit it: moving toward texture lists for content.

I’m planning to change the protocol to always provide a list of textures instead of just one. I think we’ll change the method we call to resources, and iterate it in the ContentView. Everyone just encloses their provided resource in a list, except for Button:

class Button(Content):
    def __init__(self, name):
        resource1 = '/Users/ron/Desktop/DungeonTiles/png/objects/floor button/Button (1).png'
        resource2 = '/Users/ron/Desktop/DungeonTiles/png/objects/floor button/Button (2).png'
        self.name = name
        self.scale = 1
        self.resources = [resource1, resource2]

And we change 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)
                view.sprite.position = cell.center_position(cell_size)
                self.content_views[item] = view
                self.content_sprite_list.append(view.sprite)

And ContentView:

class ContentView:
    error_texture = ':resources:images/items/star.png'
    def __init__(self, content_item, resources, scale=0.5):
        self.item = content_item
        self.sprite = Sprite()
        for resource in resources:
            try:
                texture = arcade.load_texture(resource)
            except (FileNotFoundError, AttributeError):
                texture = arcade.load_texture(self.error_texture)
            self.sprite.append_texture(texture)
        self.sprite.set_texture(0)
        self.sprite.scale = scale_texture(self.sprite.texture, scale)

That draws correctly.

Now I think we’ll have DungeonView receive the messages about state, so as not to subscribe every content item.

class DungeonView:
    def subscribe_to_state(self, dungeon):
        def callback(event, item, state_number):
            try:
                view = self.content_views.get(item)
                view.set_state(state_number)
            except KeyError:
                pass
        dungeon.subscribe('state_number', callback)

And:

class ContentView:
    def set_state(self, state_number):
        self.sprite.set_texture(state_number)

And, finally, in button, a hack for now:

class Button:
    def interact_with_player(self, dungeon):
        dungeon.publish(self.name + ' button_pressed')
        dungeon.publish('state_number', self, 1)

That just changes state once, to the down position, but it works:

Map showing lower floor button depressed, upper not depressed

A careful eye will see that the lower button is now depressed, while the upper one is still not in touch with the situation and is not depressed.

Let’s commit this: button depresses when stepped on and then reflect a bit.

Reflection

Clearly the button should cycle 0/1/0/1. Its messages should reflect state. There’s no reason that the AppearingContent object couldn’t subscribe to the state_number message, checking for state 1. It seems possible that we could have a MultiStateContent object that inferred how many states it has from the number of images it has.

Presently the Button prepends its name to the button pressed message. Since the state_change publish passes the actual content object, the subscriber could probably get the name from that and check it. That would, however, wind up sending the message to more objects.

Possibly the PubSub could include a name option and only forward messages from the given name. Or a list of names to forward.

I believe that we’d do well to avoid prepending names to the messages. It just seems kind of like a hack, and is likely to be even more error prone than whatever else we might do. If all the messages we publish were required to include the publishing object as the first parameter, the DungeonView would automatically limit the message passing to the view instance for that object.

However, it would not surprise me if we were to come up with messages for everyone. To do that today, we have at least two options:

  1. We could define the Dungeon callback such that it looped over all sub-views;
  2. We could have all the sub-views subscribe.

The former seems better, as the latter would clog the PubSub queue for no good purpose.

Net net net, I think we’ll move in the direction of multi-state content, such as a two-state floor button or a four-state floor lever (we have those images). As we do for AppearingContent, I think individual Content items will subscribe directly, and — I’m guessing here — will interrogate the name of the sender to see if they want to respond, rather than prepend names onto messages. And I’ll at least look at standardizing on requiring the publisher object in every publication, and checking names in PubSub.

Trying to make PubSub do that will be an interesting exercise on its own.

Finishing Up

Let’s improve Button to increment its state and send suitable messages.

class Button(Content):
    def __init__(self, name):
        resource1 = '/Users/ron/Desktop/DungeonTiles/png/objects/floor button/Button (1).png'
        resource2 = '/Users/ron/Desktop/DungeonTiles/png/objects/floor button/Button (2).png'
        self.name = name
        self.scale = 0.75
        self.resources = [resource1, resource2]
        self.state = 0

    def interact_with_player(self, dungeon):
        dungeon.publish(self.name + ' button_pressed')
        self.state += 1
        if self.state >= len(self.resources):
            self.state = 0
        dungeon.publish('state_number', self, self.state)

    def run(self, dungeon):
        self.state = 0

Now the floor buttons go up and down. I think we might do well to improve the visibility a bit, as the down state is hard to notice. Or maybe it should be. The button itself is quite visibly different from the standard flooring. I did make it a bit smaller, as it looks better in border cells this way.

Summary

We’ve made some nice progress, with a button that presses down and pops up. We have some ideas for multi-state objects and for improving the message conventions a bit.

I do wish these visual things were easier to test automatically but they are so simple I can’t see much in the way of improving my speed with better tests. I think I made zero mistakes this afternoon, better than most LLMs.

A pleasant afternoon’s fiddling. See you next time!