Hello, loves!

Let’s see about removing those skulls from the path. Currently we just remove the sprites. This is more tricky than it might be.

My concern is that when a content object gives itself to Dot, it si removed from the cell it was in and a publication even tells the view to remove the sprite. When one of our new temp objects removes itself, it publishes that event, but the Dungeon does not currently subscribe to it. And when it does, we need to be sure to remove the object and NOT fire the event again, which could be harmless or could be fatal.

Ah, I’m glad we decided to review this:

class Dungeon:
    def receive_content_from_cell(self, content, containing_cell):
        contents = self.contents_at(containing_cell)
        if content in contents:
            self.publish('remove_content', '', content=content)
            contents.remove(content)
        self.player_inventory.append(content)

I had read that if as for, and thought we were removing everything. We aren’t. OK, what are we publishing?

class ContentFactory:
    def temp(self, *, name, time, resource, scale):
        info = SimpleNamespace(elapsed=0, time=time)
        def update(self, *, pub_sub, delta_time):
            self.info.elapsed += delta_time
            if self.info.elapsed >= self.info.time:
                pub_sub.publish('remove_content', 'temp', content=self)
        update_sub = Subscription(event='on_update', caller_id='view', callback=update)
        subs = [update_sub]
        return Content(name=name, resources=[resource], scale=scale,
                       info=info, subs=subs)

OK, suppose we field that ‘remove_content’ in Dungeon. We’ll have to find the cell that the object is in. The only way available currently will be to search for it. I think we need a test for this.

    def test_remove(self):
        layout = DungeonLayout()
        dungeon = Dungeon(layout)
        pub_sub = dungeon.pub_sub
        dungeon.run()
        factory = ContentFactory()
        temp = factory.temp(name='temp', time=5, resource='', scale=0.5)
        dungeon.place_content_at(Cell(5,5), temp)
        temp.run(pub_sub)
        assert temp in dungeon.contents_at(Cell(5,5))
        pub_sub.publish('on_update', 'view', delta_time=6)
        assert temp not in dungeon.contents_at(Cell(5,5))

This was more tricky to set up than one might prefer. In particular, Dungeon defines the run-time PubSub, so I had to fetch that one. Otherwise, it was easy enough:

    def run(self):
        def callback(*, pub_sub, content):
            for cell, contents in self.contents.items():
                if content in contents:
                    contents.remove(content)
        self.pub_sub.subscribe('remove_content', '', callback)
        for content in self.contents.values():
            for item in content:
                item.run(self)

The test passes, and I’m confident that we’ll be removing the skulls now. But I’m not comfortable with this solution. We shouldn’t have to have two separate event handlers working in concert to remove one item and its sprite. Is there another way? Does the DungeonView have enough information to do this?

I think we have. There is the dictionary content_views from item to ContentView, and a ContentView has a cell and an item. Therefore we should be able to do the job from the dungeon view. Remove the subscription from Dungeon, let the test fail again.

Ages later, finally recognizing that I’m in a hole, I stop digging and reset back to this morning, because I didn’t save what’s above.

I’m not really tired, so let’s see if I can figure out a mystery. It seemed to me that when the skulls disappear, it was because their sprite was removed, and the skull itself was not. But as I was just working, it seemed that the sprite was not there. I need to understand better what is going on here.

Ah, of course:

class Dungeon:
    def show_path_to(self, item_name, dungeon_view):
        my_resources = '/Users/ron/Desktop/DungeonTiles/png/objects/'
        path = self.find_path_to(item_name)
        time_out =  5 # seconds
        for cell in path:
            item = ContentFactory().temp(name="skel", time=time_out, resource=my_resources + 'Skeleton1.png', scale=0.5)
            time_out += 1
            self.place_content_at(cell, item)
            item.run(self)
            dungeon_view.make_view_and_sprite(cell, item)

We make a view and sprite:

class DungeonView:
    def make_view_and_sprite(self, cell, item):
        resources = item.resources
        scale = item.scale
        view = ContentView(cell, item, resources, scale)
        view.sprite.position = cell.center_position(cell_size)
        self.content_views[item] = view
        self.content_views_by_room[cell.room].append(view)
        self.content_sprite_list.append(view.sprite)
        if self.is_lit(cell):
            view.sprite.visible = True

So at this point, content_views has our item. So when we get the event that we’re already dealing with, why can’t we just do this:

class DungeonView:
    def subscribe_to_remove_content(self, dungeon):
        def callback(*, pub_sub, content):
            self.view_do(content,
                lambda view: self.dungeon.remove_content_from_cell(view.item, view.cell))
            self.view_do(
                content,
                lambda view: view.remove_from_sprite_lists())
        dungeon.subscribe('remove_content', '', callback)

With the new method:

class Dungeon:
    def remove_content_from_cell(self, content, containing_cell):
        contents = self.contents_at(containing_cell)
        if content in contents:
            contents.remove(content)

That works on screen and via a print, I know it is removing the actual content. I do not know why I got in a hole earlier: it would have been wise to reset sooner.

Let’s see if we can improve that subscribe:

    def subscribe_to_remove_content(self, dungeon):
        def callback(*, pub_sub, content):
            self.view_do(content,
                lambda view: self.remove_item_via_view(view))
        dungeon.subscribe('remove_content', '', callback)

    def remove_item_via_view(self, content_view):
        self.dungeon.remove_content_from_cell(content_view.item, content_view.cell)
        content_view.remove_from_sprite_lists()

That, too, is working. We need a test like the one I reset. That’ll be for another day. I am definitely tired now and propose to commit and rest. Commit: temp object correctly removed, needs tests.

I had to do an odd thing to keep a test running:

class Dungeon:
    def receive_content_from_cell(self, content, containing_cell):
        contents = self.contents_at(containing_cell)
        if content in contents:
            self.publish('remove_content', '', content=content)
        if content in contents:
            # event may remove the content, done this way to support a test
            contents.remove(content)
        self.player_inventory.append(content)

Doing the obvious thing and just not doing the remove because the publish does it broke a test. Note made for next time.

Summary

Difficulty in doing this, even though the final code is pretty simple, suggests that our objects aren’t quite right. The ContentView has all the information we need to do the removal, but the views are in the DungeonView, while the actual items are in the Dungeon.

The Dungeon cannot see the View, but the view can see the dungeon. The view needs to remove the sprite, the dungeon needs to remove the content. It’s too complicated and we need to make it simpler and more clear. That will require a clearer head than I have on hand just now.

Maybe next time. See you then!