Hello, loves!

I was going to say ‘work’, but this isn’t work, it’s fun. I continue to be pleased.

OK, let’s make a list of things we want to do:

  1. Implement Appearing. I think we might need to adjust the messaging.
  2. Look into generally improving messaging to be more explicit.
  3. Do we want explicit different controls like Lever and Button, or should they somehow be converged?
  4. Implement Animated content.
  5. General cleanup of the new scheme.

The first two are related. I set up a recursive set of messages the other day, listening to a control and issuing messages. The current convention is that the control and controlled object have the same name and the controlled guy listens for his own name in a state change. But then if he changes state under his own name, bad things happen.

I think what I’d like is a message to tell the view to update us, and different messages to connect control to controlled. As we’ll see when we do the Animated control, there is a mapping provided between what the control says, often no more than 0, 1, 2, 3, … and what the Animated control does, such as run, stop in the up position, stop in the down position, and run, respectively.

OK. Let’s look at the lever. I think we’ll sort the messaging now before it gets any more weird.

class ContentFactory:
    def lever(self, *, name):
        def lever(self, interactor):
            self.state = (self.state+1)%len(self.resources)
            interactor.publish('state_number', self.name, content=self, state=self.state)
            return False
        resource1 = '/Users/ron/Desktop/DungeonTiles/png/objects/lever/1.png'
        resource2 = '/Users/ron/Desktop/DungeonTiles/png/objects/lever/2.png'
        resource3 = '/Users/ron/Desktop/DungeonTiles/png/objects/lever/3.png'
        resource4 = '/Users/ron/Desktop/DungeonTiles/png/objects/lever/4.png'
        resources = [resource1, resource2, resource3, resource4]
        scale = 0.75
        return CombinedContent(name=name,
                  resources=resources,
                  scale=scale,
                  interaction=lever)

Let’s have the new rule be that we publish('control', self.name, state=self.state) for the controlled buy to listen to, and state_number as it is, for the view to listen to. We may change the latter event name later. For now we’ll just use the state number for the control, though maybe we’ll want something better. I’ll just add the other message:

        def lever(self, interactor):
            self.state = (self.state+1)%len(self.resources)
            interactor.publish('control', self.name, state=self.state)
            interactor.publish('state_number', self.name, content=self, state=self.state)
            return False

And in the existing AppearingContent, field the new message:

class AppearingContent(Content):
    def run(self, dungeon):
        dungeon.publish('state_number', '', content=self, state=False)
        self.visible = False
        def callback(pub_sub, state):
            pub_sub.publish('announce', 'appearing', message=f'{self.name} has appeared!')
            pub_sub.publish('state_number', '', content=self, state=True)
            self.visible = True
        dungeon.subscribe_once('control', self.name, callback)

Works as intended, once I actually test by bumping the lever. I haven’t used the new code for the button yet, so when I stepped on it expecting it to work, it did not.

Let’s add the button factory method now.

    def button(self, *, name):
        def button(self, interactor):
            self.state = (self.state+1)%len(self.resources)
            interactor.publish('control', self.name, state=self.state)
            interactor.publish('state_number', self.name, content=self, state=self.state)
            return False
        resource1 = '/Users/ron/Desktop/DungeonTiles/png/objects/floor button/Button (1).png'
        resource2 = '/Users/ron/Desktop/DungeonTiles/png/objects/floor button/Button (2).png'
        resources = [resource1, resource2]
        scale = 0.75
        return CombinedContent(name=name,
                  resources=resources,
                  scale=scale,
                  interaction=button)

And change main. Oops, button’s interaction needs to return True: we step on a button.

After that change, all is good. Commit: lever and button both working with new control message.

We’re down to only AppearingContent and Animated not using the new scheme. Appearing already has a test that works, I believe:

    def test_appearing(self):
        name = 'a thing'
        info = SimpleNamespace(visible=False)
        def interaction(self, interactor):
            if self.info.visible:
                interactor.receive_content(self)
                interactor.publish('announce', 'xx', message=f'You have received {self.name}!')
            return True
        def appear(self, pub_sub, content, state):
            pub_sub.publish('announce', 'xx', message=f'{name} has appeared!')
            pub_sub.publish('state_number', 'x', content=self, state=state)
            self.info.visible = True
        sub = Subscription(event='state_number',caller_id=name,callback=appear,one_shot=True)
        item = CombinedContent(name=name, resources=['a', 'b'], scale=0.5, interaction=interaction, info=info, subs=[sub] )
        item.run(self)
        item.interact_with_player(self)
        assert self.message is None
        assert item.info.visible is False
        self.pub_sub.publish('state_number', name, content=item, state=1)
        assert self.message == 'a thing has appeared!'
        assert item.info.visible is True
        item.interact_with_player(self)
        assert self.message == 'You have received a thing!'

We just need to scarf up the building part of that and turn it into another factory method. First, let’s change the test to use control to drive the thing.

    def test_appearing(self):
        name = 'a thing'
        info = SimpleNamespace(visible=False)
        def interaction(self, interactor):
            if self.info.visible:
                interactor.receive_content(self)
                interactor.publish('announce', 'xx', message=f'You have received {self.name}!')
            return True
        def appear(self, pub_sub, state):
            pub_sub.publish('announce', 'xx', message=f'{name} has appeared!')
            pub_sub.publish('state_number', 'x', content=self, state=state)
            self.info.visible = True
        sub = Subscription(event='control',caller_id=name,callback=appear,one_shot=True)
        item = CombinedContent(name=name, resources=['a', 'b'], scale=0.5, interaction=interaction, info=info, subs=[sub] )

Passing. Make factory method:

    def appearing(self, *, name, resource, scale):
        blank = '/Users/ron/Desktop/DungeonTiles/png/objects/1x1.png'
        resources = [blank, resource]
        self.name = name
        info = SimpleNamespace(visible=False)
        def interaction(self, interactor):
            if self.info.visible:
                interactor.receive_content(self)
                interactor.publish('announce', 'xx', message=f'You have received {self.name}!')
            return True
        def appear(self, pub_sub, state):
            pub_sub.publish('announce', 'xx', message=f'{name} has appeared!')
            pub_sub.publish('state_number', 'x', content=self, state=state)
            self.info.visible = True
        sub = Subscription(event='control',caller_id=name,callback=appear,one_shot=True)
        return CombinedContent(name=name, resources=resources, scale=scale, interaction=interaction, info=info, subs=[sub] )

Try that in main. I think I could use the test but I am impatient.

    dungeon.place_content_at(Cell(33, 28), factory.button(name='a secret key'))
    appearing = factory.appearing(name='a secret key', resource=my_resources + 'Key (1).png', scale=0.5)
    dungeon.place_content_at(Cell(36, 28), appearing)
    dungeon.place_content_at(Cell(35, 29), factory.button(name='a brilliant torch'))
    appearing = factory.appearing(name='a brilliant torch', resource=my_resources + 'torch/1.png', scale=1)
    dungeon.place_content_at(Cell(36, 29), appearing)

All works as intended. I think the only old Content now in prod is the Animated, which we have not yet implemented in the new scheme. It has been a pleasant interlude. Let’s sum up.

Summary

The new scheme is in actual use and even interacts properly with the old Content items. (I did update those as needed to deal with the new ‘control’ event.) One more factory method for Animated, and we’ll have them all handled. Animated is slightly more intricate than the others, but since we already have it in the old style we’ll likely find it easy enough to do.

There are some rough edges beginning to show. No surprise with a new implementation: we’ll be smoothing things for a bit after it’s all in place. I’ve noticed:

  • There is duplication in the two control objects, the lever and the button.
  • I’m not sure whether I like the resource= vs resources= distinction in the objects that have just one texture vs those with more.
  • We still need some more sensible resource management scheme than passing naked file names all around.
  • It would be nice to have a way for the publish calls to know what parameters are required, but so far all I’ve thought of is to proliferate methods like publish_control and publish_state, which could then have keyword arguments, and that might be OK.
  • It would be better to separate the interaction behavior from the True/False return indicating whether Dot can step on the cell.
  • The appearing object unconditionally appears upon receiving ‘control’. It’s a one-shot subscription. Might be better to check control state? But we have no unsubscribe …

Doubtless there’s more. There’s always more. But it’s working as intended and I remain pleased.

See you next time!