Hello, loves!

Shall we add more capability, or consolidate? I think we’ll add. Callooh! Callay!! Tout le monde déteste l’IA.

Tuesday AM: Reflection (Instant Replay)

I believe that the operational status of the code is in a good place:

  1. The screen is 2x as wide as is needed for game display.
  2. The Scroller is in a Section, and correctly positioned in the right-hand pane.
  3. When the game begins to run, the large-scale map is in the right hand pane, not yet in a Section.
  4. During play, the right hand pane is properly clipped: no dungeon bits overflow to the left pane.
  5. Scrolling keeps Dot centered in the right hand pane, unless scrolling would go beyond the game boundaries, in which case scrolling stops and Dot can walk to the edge.

The quality of the code is not so good. In particular, the drawing of the large-scale dungeon includes some very ad-hoc decisions, including but not limited to those magic -112 values shown above.

I have reached a point with the camera bounds that I’m familiar with in other developments: I’m at or near the point where I can pretty quickly make the library do what I want, but not yet to the point where I could explain to someone else how it works or why I’m doing what I’m doing. I would much prefer to understand well enough to really explain what I’ve done.

The question before me is how to proceed … and whether to start a new article. Let’s do start a new one: I’ll sum up here.

Let’s Go Forward

The code we have is not good, but it isn’t terrible. We would all like to see the mini-map appear before we do much refactoring: the mini-map is the payoff for this effort, and so far all we can see is a blank space where it might go.

A rough plan:
I think we’ll put the mini-map in a Section. They’re not terribly useful, but they do keep each section’s code isolated, which is generally a good idea: otherwise it would all be in one View, unless we built helper subclasses, which is pretty much what a Section is.

I think we’ll spike it in, starting with a trivial display in the section and then we’ll pull in or write the actual display code, which should be a lot like the existing dungeon display code. I expect it’ll be simpler, and in particular, I propose that we might display where Dot is, but we won’t show any contents. When we’re done, we’ll only show parts of the map hat Do has visited, but right now we have everything illuminated anyway.

Here’s where we create our existing Section:

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)
        if arcade.window_commands._window:
            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_view=set()
            )
            self.section_manager.add_section(
                self.scroller_section
            )

I think we should rename ScrollerPart to ScrollerSection. I borrowed the Part idea from the arcade example and our practice is to name subclasses with a prefix on the name of the base class.

With that done, I’ll just use Wishful Thinking to type in the new Section info:

            self.mini_map_section = MiniMapSection(
                0, 0, self.window.width/2, self.window.height,
                name="MiniMap",
                prevent_dispatch_view=set()
            )
            self.section_manager.add_section(
                self.mini_map_section
            )

PyCharm declines to run because there is no such class. Let’s provide one.

class MiniMapSection(arcade.Section):
    def __init__(self, left: int, bottom: int, width: int, height: int, **kwargs):
        super().__init__(left, bottom, width, height, **kwargs)
        self.left = left
        self.bottom = bottom
        self.width = width
        self.height = height

    def on_draw(self) -> None:
        arcade.draw_line(self.left, self.bottom, self.width, self.height, arcade.color.GREEN, 2)
        arcade.draw_line(self.left, self.height, self.width, self.bottom, arcade.color.GREEN, 2)

This should draw a big green X on the left side of the screen, and after I disable the scissoring done during drawing the zoomed-in side, it does:

game screen with big green x where the mini map will go.

Scared me when the big x didn’t appear until I realized that the scissoring that prevents the map from overflowing from the right side was still in effect.

It seems to me that we need access to the Dungeon in our Section, so that we can draw all the things. No, better to have access to the View, because we want the sprite lists. So:

class DungeonView:
    ...
            self.mini_map_section = MiniMapSection(
                self,
                0, 0, self.window.width/2, self.window.height,
                name="MiniMap",
                prevent_dispatch_view=set()
            )
            self.section_manager.add_section(
                self.mini_map_section
            )

class MiniMapSection(arcade.Section):
    def __init__(self, dungeon_view, left: int, bottom: int, width: int, height: int, **kwargs):
        super().__init__(left, bottom, width, height, **kwargs)
        self.dungeon_view = dungeon_view
        self.left = left
        self.bottom = bottom
        self.width = width
        self.height = height

    def on_draw(self) -> None:
        self.dungeon_view.keyed_sprites.draw()
        player_cell = self.dungeon_view.dungeon.player_cell
        if not player_cell: return
        cx, cy = player_cell.center_position(cell_size)
        arcade.draw_circle_filled(cx, cy, cell_size // 4, arcade.color.RED)

And when we run:

game screen showing large and mini maps with player shown on each.

Callooh! Callay!! That went perfectly. We have mini-map. It’s showing the contents. If we stick with our plan of not showing content on the minimap, we’ll need to separate content out into its own sprite list. I think we can probably adjust our KeyedSpriteList object to deal with that fairly easily.

Commit: minimap working, includes content.

Reflective Summary

I’d be a fool to make even one more change. This is a perfect moment to wrap up and make a delicious iced chai and enjoy the glow. In the code above we see the best argument that I can see for the Section: it lets us keep a single display idea separate from other display ideas, and that’s a good thing. There is some duplication in there. I wonder though …

OK, one change … we can call back to the view to draw Dot. And we don’t need those left right things any more:


class MiniMapSection(arcade.Section):
    def __init__(self, dungeon_view, left: int, bottom: int, width: int, height: int, **kwargs):
        super().__init__(left, bottom, width, height, **kwargs)
        self.dungeon_view = dungeon_view

    def on_draw(self) -> None:
        self.dungeon_view.keyed_sprites.draw()
        self.dungeon_view.draw_adventurer()

Now that, I definitely like that. Mini-map in six lines of code. Commit.

Drinks are on me, gang. See you next time!