Hello, loves!

Today, we’ll decide if this is a Spike to be tossed, or a good enough base from which to proceed. Tout le monde déteste l’IA.

Based on my recollection from yesterday afternoon, the new Section is close enough to right to go ahead with it. We can always reset further back if things go very wrong. I’ll review the diffs one more time, only calling out here anything I see that votes not to commit.

Cameras still has some debug code in it, but it will be useful. Retain. DungeonView has legitimate code to create the SectionManager and stop using the second camera, plus the change to update the scroller in the new Section. Retain. Main has the new dungeon size and screen size settings. Retain. Scroller has a renamed local variable. Retain. ScrollerPart is the new Section, looks good. Retain. Commit: scrolling now done in a section.

As things stand now, the Scroller is where we want it. However, when we are building the map it displays on the left side of the screen, and then when we place Dot, it centers the view in the full screen (as it has always done). We want it centered on the right-hand side for the full view, zoomed in, and on the left side, zoomed out to fill the side. If I ever managed to get it to display on the right in zoomed form, I’ve forgotten.

Tooling

I have spent too much time typing D a dozen times to get another view of the dungeon drawn where I don’t want it to be. Let’s enhance our “Making App” a bit, by improving the BuildStepper to have a way of just zipping through things.

class BuildStepper:
    def finish(self, view=None):
        while self._more_to_do():
            self._do_one_step(view)

And in main:

    view = DungeonView(dungeon)
    # for single stepping comment out next TWO lines
    stepper.finish(view)
    dungeon.run()
    # for single stepping comment out previous TWO lines
    view.run(stepper)

Commit that. Now the game creates the dungeon and starts running. No more typing D required.

I’ve been hacking too much, trying to get things to work, so improving the tools a bit is a good idea. We have three tests failing: I’ve been ignoring that because of the changes we’ve been making. Let’s calm down and fix or skip them like a sensible person might do.

test_room_illuminate can’t run because we create the SectionManager and sections even if we don’t have a window.

Putting that code under an if fixes all the tests. Nice. Commit that.

OK, we are on an even footing. What I want to accomplish next is to get the game to draw, in zoomed form, over on the right hand side of the window. I think we’ll try to do that before we make a Section, since the Section appears not to provide anything special other than,, perhaps, clipping, which will be useful.

As things stand now, Dot appears with her tile exactly centered, which would be correct on a window whose width was half the current width, I think.

It is taking quite a bit of time to build the dungeon, literally seconds. I comment out everything in the stepper except for the main rooms and the finishing up bits. Now I get the starting rooms in mid screen.

I’d like to have it centered in the right hand half of the screen. Let’s draw a marker where we want it.

dungeon map with two nested rooms centered in wide screen with green x centered in right half

Back to Bashing

I’ll try to be more rational about this, but frankly, I do not understand the whole margin / grips thing on the cameras, and I have not found a good explanation of what it’s all about.

I think a trivial change to accomplish what we want would be to shift the entire sprite list over. Having thought of it, I wonder if we can position a whole list. Yes. This code does position the dungeon map where we want it:

    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.move(448/4,0) # <===
            self.keyed_sprites.draw()
            self.keyed_sprites.move(-448/4,0) # <===
            self.draw_adventurer()
            self.draw_flood()

We’ll belay that but keep it in mind if all else fails. Surely there is a way to move the entire view.

I tried something in the Cameras and got a nearly-good result:

    def scroll_dungeon_cam(self, cell):
        cx, cy = cell.xy
        self.dungeon_cam.position = (-7*16 + cx * cell_size, cy * cell_size)
        self.dungeon_cam.position = (
            arcade.camera.grips.constrain_xy(
                self.dungeon_cam.view_data, self.dungeon_camera_bounds
            ))

The change is that -7*16, which is 7 cells at size 16. Hm. Dot’s location is x=32. The dungeon size is 56. 56-32 is 14. I suspect the “right” number” is -14*8, or 56-cx*8. Why 8? We want to go half way, so we use half the cell size?

The picture is close to right. The view doesn’t clip, and it doesn’t align correctly at the edges. I feel ambivalent about this change. It’s simple enough and should be simple enough to adjust to scroll correctly. I think the section can clip, though that will have to be checked. Possibly if we had this in a section we could use the section boundaries so that we didn’t have so many magic numbers.

For now, I need a break. Back soon …

Back …

I think the next thing I’ll try will be to limit drawing to the right side of the screen. Apparently you do that with explicit screen coordinates. I’m starting to wonder if these Section deals are useful for much.

Scissoring, which I would have called “clipping” is readily done:

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():
            ctx = self.window.ctx
            ctx.scissor = (
                self.window.width/2, 0, self.window.width/2, self.window.height
            )
            self.keyed_sprites.draw()
            self.draw_adventurer()
            self.draw_flood()

With the setting of ctx.scissor, the big map is draw only inside the right side of the window. Note that I don’t even have a Section defined for it: it’s just using the existing camera.

The scrolling isn’t quite right. What is intended, and used to work, was that Dot would stay centered on the screen until a tile on the edge came into view, and then instead of scrolling, we would move Dot toward the wall. That avoids scrolling a lot of black into the window. I think the issue is in the margin-related code. Let’s commit what we have and then have a look.

I have found a hack that gets scrolling to work:

class Cameras:
    def __init__(self, view, max_x, max_y, zoom):
        self.max_x = max_x
        self.max_y = max_y
        self.scroller = Scroller(lines=4, base=(512,800))
        self.dungeon_cam = arcade.Camera2D()
        self.dungeon_cam.zoom = zoom
        width_margin = self.compute_margin(max_x, zoom)
        height_margin = self.compute_margin(max_y, zoom)
        self.dungeon_camera_bounds = (
            self.margin_rectangle(view.width/2, view.height, width_margin, height_margin))

    def compute_margin(self, cell_count, zoom):
        cells_visible = cell_count // zoom
        desired_margin = cells_visible // 2
        pixel_margin = desired_margin * cell_size
        return pixel_margin

    def margin_rectangle(self, view_width, view_height, width_margin, height_margin):
        rect = arcade.LRBT(-112+width_margin, -112+view_width - width_margin, height_margin, view_height - height_margin)
        return rect

I even somewhat understand that 112. 112 is 7 times 16. The dungeon is 56x56 cells. Zoom is 4, so we get 56/4, or 14 cells in the view. We never want to drag anything in from the edge, so the margin is 7 cells or 112 pixels. This is not the right place for that adjustment, almost certainly: it was most expeditious for bashing.

I think that with a clear head, if I can find out anywhere around here, we can sort out a sensible scheme for setting up the window and margin to work. I’m going to commit this, since it is working, if not lovely.

Tuesday AM: Reflection

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.

Summary

Over a series of at least five articles, I have managed to hack experiment my way to a display that is, so far, what I want to see. Along the way but no longer in the code, I also had a proper display of the mini-map on the left side of the screen.

THe code is mediocre in detail but organized adequately. Decisions await the next article. See you there!