Consolidation
I see opportunities for consolidation. Can we take advantage of them? Let’s see. We change something unexpected. Is it too clever? Nahhh!
The Button content object now supports two different textures, one showing it up, and one with it down. The Dungeon sends interact_with_player When Dot steps into the Button’s cell. The Button publishes two messages, its name + ‘button_pressed’, and ‘state_number’. The code in the Button’s ContentView listens for the ‘state_number’ event and sets the displayed texture to the corresponding texture in the Sprite.
Meanwhile, the AppearingContent object subscribes to name + ‘button_pressed’, and when it sees that message, it publishes ‘item_visibility’.
Meanwhile, DungeonView currently subscribes to three publications: ‘remove_item’, ‘item_visibility’, and ‘state_number’. It handles ‘remove_item’ itself, and passes the other two publications on to the view corresponding to the content item that published the message.
Meanwhile, the ContentView handles set_visibility by setting the visible flag in its Sprite, and handles state_number N by setting the Sprite’s texture to the Nth one in its texture list.
One opportunity would be to configure appearing content with a blank or clear texture as number zero, and the “normal” display as number one. Then use ‘state_number’ instead of ‘item_visibility’. That would remove the ‘item_visibility’ publication entirely, leaving the publication space a bit simpler. It would remove the set_visbility method from ContentView, making it simpler as well.
If I had a clear texture, which can be arranged, I think we could do that right now. But wait, there’s more!
The Button, as I mentioned, also publishes ‘state_number’. So if we could arrange for the AppearingContent item to listen for that, we could remove the ‘button_pressed’ publication. But there is an issue with that: currently the ‘button_pressed’ is published with the name of the sender object prepended. There is an unwritten agreement between the Button and the AppearingContent about that name:
dungeon.place_content_at(Cell(33, 28), Button('a secret key'))
appearing = AppearingContent('a secret key', my_resources + 'Key (1).png')
dungeon.place_content_at(Cell(36, 28), appearing)
dungeon.place_content_at(Cell(35, 29), Button('a brilliant torch'))
appearing = AppearingContent('a brilliant torch', my_resources + 'torch/1.png', 1)
dungeon.place_content_at(Cell(36, 29), appearing)
The Button agrees to prepend ‘a secret key’ when it publishes and the AppearingContent subscribes to ‘a secret key’, and similarly for ‘a brilliant torch’.
Those publications are occurring between two (or more) Content items, and the views never see them. The DungeonView does all the subscribing, and when it sees a publication from a Content item, its callback typically finds the corresponding ContentView and calls a corresponding method on the view. The current cases are these:
def subscribe_to_remove(self, dungeon):
def callback(event, item):
try:
view = self.content_views.pop(item)
view.sprite.remove_from_sprite_lists()
except KeyError:
pass
dungeon.subscribe('remove_item', callback)
def subscribe_to_visibility(self, dungeon):
def callback(event, item, a_boolean):
try:
view = self.content_views.get(item)
view.set_visibility(a_boolean)
except KeyError:
pass
dungeon.subscribe('item_visibility', callback)
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)
Those are all subscribed at the Dungeon level and they all find the corresponding view and so something to it, calling a method in two cases and removing its sprite from the sprite lists.
It would be nice to get rid of that duplication. I think we could do it with a bit more trickery, if an embedded callback function isn’t already confusing enough. You know what? We’re on a clean commit, let’s improve this code while we’re here.
Thing one will be to defer the removal from sprite lists down to the view:
def subscribe_to_remove(self, dungeon):
def callback(event, item):
try:
view = self.content_views.pop(item)
view.remove_from_sprite_lists()
except KeyError:
pass
dungeon.subscribe('remove_item', callback)
class ContentView:
def remove_from_sprite_lists(self):
self.sprite.remove_from_sprite_lists()
I’ll test that. Works, no surprise. Commit: Remove sprite removal feature envy. Sorry, Demeter.
Now let’s have a little helper method. I’ll just write it, then use it.
class DungeonView:
def corresponding_view(self, content_item) -> ContentView|None:
try:
return self.content_views[content_item]
except KeyError:
return None
def subscribe_to_remove(self, dungeon):
def callback(event, item):
view = self.corresponding_view(item)
if view:
view.remove_from_sprite_lists()
dungeon.subscribe('remove_item', callback)
Test. Works as advertised. Commit: added corresponding_view to eliminate some try excepts.
Use it in the other two callbacks:
def subscribe_to_visibility(self, dungeon):
def callback(event, item, a_boolean):
view = self.corresponding_view(item)
if view:
view.set_visibility(a_boolean)
dungeon.subscribe('item_visibility', callback)
def subscribe_to_state(self, dungeon):
def callback(event, item, state_number):
view = self.corresponding_view(item)
if view:
view.set_state(state_number)
dungeon.subscribe('state_number', callback)
That’s better. I rather wish we didn’t have to fetch the view and then check it. I still see a lot of duplication as well.
Commit this. Using corresponding_view throughout.
I wonder if we can do better. What if we could make this work … no, I just don’t quite see a way.
One more try … Ah!
def subscribe_to_remove(self, dungeon):
def callback(event, item):
self.view_do(item, lambda view: view.remove_from_sprite_lists())
dungeon.subscribe('remove_item', callback)
def subscribe_to_visibility(self, dungeon):
def callback(event, item, a_boolean):
self.view_do(item, lambda view:view.set_visibility(a_boolean))
dungeon.subscribe('item_visibility', callback)
def subscribe_to_state(self, dungeon):
def callback(event, item, state_number):
self.view_do(item, lambda view: view.set_state(state_number))
dungeon.subscribe('state_number', callback)
def view_do(self, content_item, action):
try:
view = self.content_views[content_item]
action(view)
except KeyError:
return
That works fine. We use view_do (not voodoo) to call our lambda. Commit: change to view_do
There’s still a lot of similarity there, but one quick attempt to remove it didn’t help much. I think we could do better in Kotlin, maybe even in Smalltalk but we’re stuck with Python.
A thought comes to mind, something about using keyword arguments in the PubSub. As things stand the PubSub code itself accepts any arguments that the publisher provides and passes them willy-nilly to the subscribers. We’ll save that idea for sometime when we need it. What we have here is, I think, about as compact as we can get it, and I would freely grant that it’s cryptic enough to cause a second or third look when next we pass this way.
Is it too clever. I’m saying “Nahhh, it’s fine”. We’ll see what we think as time goes on.
Reflection
This is all very nice, and I’m glad we stopped by here, but we were looking at consolidating some of the messages. I’ll create a suitable blank texture when next I’m out of things to do with my iPad, and we’ll then see about consolidating Button and AppearingContent’s separate visibility code into just one scheme, ‘state-number’. We could do that now with some other weird texture, but there’s no rush.
The larger issue is inter-content communication, button to appearing content, switches to spike control, levers opening lava chutes, the usual dungeon stuff. I think there should be some standard scheme, ideally supported by some common mechanism and probably with PubSub support.
Maybe an event, instead of just being a string, is some simple Event object with a string and a modifier, amounting to a name that can be shared between publishers and listeners. And maybe, in PubSub, you subscribe to an Event and only get the message when the string and modifier both match. (There might be a way to subscribe to all such events, no matter the modifier. Sort of a wild card.)
We could do a simple version of that now, and if we’re going to have an Event object used in PubSub, probably sooner is better than later. We can always accommodate the current form and a new form, and refactor over time, etc etc same as always there are no large progress-stalling refactorings, we know, Ron. But sooner is better, it gives us experience sooner and when the idea is decent, might even save a little hassle later. Just because we can refactor over time doesn’t mean that we want to.
Lots to think about. We’ll see what we do when we do it. For now, we have an improvement where we didn’t expect one, and we need to go create a blank texture.
And have a nice iced chai. I hope you’ll enjoy a little treat as well. See you next time!