Hello, loves!

This morning I’ll try to spike in a Section for the Scroller. I’ll waste 90 minutes. This afternoon, I’ll win! Tout le monde déteste l’IA.

We’ll need three Sections if we stick with Sections: the map, the scroller, and the mini-map. Each Section can only have one Camera, so I think we’re stuck with three. According to yesterday’s example code, the View creates a SectionManager and the sections. I’ll add that to our DungeonView as boilerplate, semi-copied from that example.

class DungeonView(arcade.View):
    def __init__(self, dungeon):
        if arcade.window_commands._window:
            super().__init__()
            self.cameras = Cameras(self, dungeon.max_x, dungeon.max_y, zoom=params.zoom_factor)
        self.dungeon = dungeon
        self.pub_sub = dungeon.pub_sub
        self.keys =  None
        self.keyed_sprites = None # updated below
        KeyedSpriteListMaker(dungeon).update(self)
        self.subscribe(self.dungeon, self.pub_sub)
        self.section_manager = SectionManager(self)
        self.section_manager.add_section(
            ScrollerPart(self.window.width/2, 0, self.window.width / 2, self.window.height, name="RightScroller")
        )

    def on_show_view(self) -> None:
        self.section_manager.enable()

    def on_hide_view(self) -> None:
        self.section_manager.disable()

The example has two instances of SectionPart, a section that draws and drags the box shown in yesterday’s article. Both sides of the example use an instance of the same SectionPart. Our Sections will need to be separate, as they all behave differently. Our fist one, ScrollerPart, needs the scroller drawing put into it. I’ll make a new file for it.

90 minutes in and no joy. As far as I can tell, the code is drawing. I’ve put explicit drawing in the ScrollerPart, and those items show up. But the Scroller text does not appear.

Ah. The messages are not getting accumulated. Something isn’t hooked up.

Break time.

Later That Day …

Well. This is embarrassing. I forgot that after the dungeon is drawn, I have to type two more D commands to start things running, one that places Dot, and one that places content. I was only typing one D. So no announcement was coming out. I spent a lot of time chasing my tail to figure out why. It’s displaying just fine, when I type enough D’s:

double-wide dungeon with announcements on right as intended

The dungeon position in the middle isn’t right, but we weren’t solving that problem, just the announcements.

I think that this has probably been working for most of the time I’ve been tail-chasing, except that I never typed enough D commands to make it display. Brill.

Here’s what’s changed. First in DungeonView:

class DungeonView(arcade.View):
    def __init__(self, dungeon):
        if arcade.window_commands._window:
            super().__init__()
            self.cameras = Cameras(self, dungeon.max_x, dungeon.max_y, zoom=params.zoom_factor)
        self.dungeon = dungeon
        self.pub_sub = dungeon.pub_sub
        self.keys =  None
        self.keyed_sprites = None # updated below
        KeyedSpriteListMaker(dungeon).update(self)
        self.subscribe(self.dungeon, self.pub_sub)
        self.section_manager = SectionManager(self)
        self.scroller_section = ScrollerPart(
            self.window.width/2, 0,
            self.window.width/2, self.window.height,
            name="RightScroller",
            prevent_dispatch=set(),
            prevent_dispatch_view=set())
        self.section_manager.add_section(
            self.scroller_section
        )

    def on_show_view(self) -> None:
        self.section_manager.enable()

    def on_hide_view(self) -> None:
        self.section_manager.disable()

OK there’s a cheerful reminder. At least some of my time spent was chasing the fact that I was not getting on_draw events at all, so those prevent_dispatch sets need to be provided. I think. Let’s remove them and see. prevent_dispatch_view is needed, the other is not. I’ll have to look up what they actually mean.

Moving right along, still in DungeonView:

class DungeonView:
    def on_draw(self):
        self.clear()
        player_cell = self.dungeon.player_cell
        if player_cell:
            self.cameras.scroll_dungeon_cam(self.dungeon.player_cell)
        with self.cameras.dungeon_cam.activate():
            self.keyed_sprites.draw()
            self.draw_adventurer()
            self.draw_flood()
        # with self.cameras.scroller_cam.activate():
        #     self.cameras.scroller.draw()

    def on_update(self, delta_time: float):
        self.scroller_section.update_scroller(self.dungeon)
        self.pub_sub.publish('on_update', 'view', delta_time=delta_time)

You can see that we removed the use of the scroller_cam, since the Section handles that function now.

The new class, ScrollerPart:

class ScrollerPart(arcade.Section):
    def __init__(self, left: int, bottom: int, width: int, height: int, **kwargs):
        super().__init__(left, bottom, width, height, **kwargs)
        self.scroller = Scroller(lines=4, base=(left + width/2,800))

    def on_draw(self) -> None:
        self.scroller.draw()

    def update_scroller(self, dungeon):
        def receive_announcement(msg):
            self.scroller.append(msg)
        dungeon.announce_via(receive_announcement)
        self.scroller.update()

The Scroller draws its text from the center, so as to make it centered in the view rather than left-justified. Looks better. So the base for the scroller needs to be half-way across the Section. A section does not clip, by the way. There is a way to convince pyglet to do it, according to my reading. Anyway, we set the scroller to draw centered in the Section.

The Scroller itself is not changed at all. Here it is, for completeness:

class Scroller:
    def __init__(self, lines=4, base=(512,800)):
        self.buffer = []
        self.base_x, self.base_y = base
        self.blank = BorderedText('', base_x=self.base_x, font_size=24)
        for i in range(lines):
            self.buffer.append(self.blank)
        self.lines = lines
        self.line_height = 36
        self.y_bump = 0

    def append(self, message: str) -> None:
        batch = BorderedText(message, font_size=24, base_x=self.base_x)
        self.buffer.append(batch)

    def draw(self):
        for line in range(self.lines):
            text = self.message(line)
            y_pos = self.y_position(line)
            text.draw(y_pos)

    def message(self, number: int) -> BorderedText:
        return self.buffer[number]

    def update(self):
        self.y_bump += 0.5
        if self.y_bump >= self.line_height:
            self.y_bump = 0
            self.buffer.pop(0)
            if len(self.buffer) < self.lines:
                self.buffer.append(self.blank)

    def y_position(self, line_number):
        return self.base_y - line_number * self.line_height + self.y_bump

It has a buffer of messages and it displays them higher and higher until it decides to pop the top one off. It is filled with blank messages, so it is actually always scrolling, just blank lines most of the time.

In the Cameras class, we now only create one Camera, since the Section takes care of the former scroller_camera. Nothing else changed there.

The only changes to ‘main’ were already there, making the window twice as wide as the dungeon, and making the dungeon a bit more narrow so as to fit the double-wide on my screen.

main.py
def main():
    layout = DungeonLayout(56, 56) # <===
    dungeon = Dungeon(layout)
    stepper = make_build_table(layout, dungeon)
    screen_width = 2*16*dungeon.max_x # <===
    screen_height = 16*dungeon.max_y
    arcade.Window(screen_width, screen_height, 'Caveat Emptor')
    ...

That’s all it took, and almost all of it was in place all the time while I was putting in prints and trying to figure out why nothing was coming out. Nothing was coming out because no one was making any announcements yet.

Summary

Sometimes the bear bites you. While burning my 90 minutes this morning, I felt that I was on the track of the problem and I was certainly learning things about how Sections work. But I’m sure that at least 45 minutes of the 90 would not have bee spent had I typed a couple more D commands.

I could be wrong. It is possible that I did type enough, once in a while, but that prints and attempted number fiddling broke things. But I don’t think so. I think I just flubbed. And I wasn’t very stressed or feeling badly: I felt I was on the track. Finally, seeing that 90 minutes had elapsed, I stopped as a matter of principle, because that much time in the weeds calls for a break.

And in a few minutes this afternoon, I realized that I had not typed enough D characters, got the text to come out, reset the Section boundaries back to where they belonged and it’s all good. I even think the code is nearly right.

We’ll call that a win. See you next time!