Inventory?
Hello, loves!
Let’s take a look at displaying Dot’s inventory in some reasonable way. Some spiking yields a bit of insight. Tout le monde déteste l’IA.
If this program were a game, there would be a display of the current values of Dot’s attributes, Equanimity, Savoir Faire, Opulence, Ostentation , or whatever attributes she has, and of the items in her inventory, potions, magical items, flowers, honeycombs, the usual sorts of things one lugs around a dungeon. Today, I’d like to explore displaying her inventory. Fortunately for us, all the Content instances that she might receive have at least one convenient display resource. My tentative plan is to create a new space in the display, off to the right because I think it’ll be easier, a narrow column in which the inventory items will be displayed.
In the fullness of time, we will probably give her a way to invoke, apply, or use an item, probably by clicking it with the mouse. Our first story will just be to get them to display. That will be challenging enough. I figure we’ll need one of those Section things that Arcade offers.
This morning will probably consist of spiking ideas. If things were to go very well we might commit some harmless code, but I don’t expect much beyond experimentation this morning.
Let’s see how we create the current window.
def main():
# random.seed(35)
# random.seed(234)
layout = DungeonLayout(56, 56)
dungeon = Dungeon(layout)
stepper = make_build_table(layout, dungeon)
screen_width = cell_size*dungeon.max_x
screen_height = cell_size*dungeon.max_y
arcade.Window(screen_width, screen_height, 'Caveat Emptor')
...
What would happen if we were to make the window width one cell larger?
arcade.Window(screen_width + cell_size, screen_height, 'Caveat Emptor')
I had hoped to find a black bar on the right. But it turns out that the map just fills the screen. My guess is that we’re using the window size somewhere important. Right:
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 = ScrollerSection(
0, 0,
self.window.width, self.window.height,
name="Scroller",
prevent_dispatch_view=set()
)
self.section_manager.add_section(
self.scroller_section
)
There’s one reference to window.width. It’s the scroller, so we could let it use the whole window. There must be more such references, probably in Cameras. No, nothing there. Oh, I think I know what’s happening: we don’t do any special clipping for the dungeon display, so it will always fill the screen. Let’s try a hack. In the screen-writing logic, we’ll just blank out that right column and see how things look.
Thrashing Ensues
I ran into a few issues. Most significant was that I wasn’t aware that the default anchor for an arcade rectangle is its center. In the end, I have this code in 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()
inventory_width = cell_size*zoom_factor
xm = self.window.width - inventory_width
r = arcade.rect.XYWH(xm,0, inventory_width, self.window.height, anchor=Vec2(0,0))
arcade.draw_rect_filled(rect=r, color=arcade.color.RED)
And get get this picture, which is interesting but not quite right:

The “not quite right” is that the map now shows half cells on each side. It is (no surprise, I suppose) centering in the window, not in the non-inventory part of the window.
Reflection
The red bar is presently using window-width as its base, offsetting a bit toward zero so as to make room. It’s about what I had in mind for the inventory, a one-cell column of icons.
The map is centered in the window, and we want it to be centered in its own part of the window, the full width minus whatever we may allocate for inventory and other info to come.
Ideally, I think, we’d make sections for the map and the inventory and whatever other paraphernalia we may someday devise, and would work within those. However, my limited experience with the Section idea leaves me with the belief that you can’t just reposition a section and have everything move. It’s not really very capable at all, if I’m right, just a convenient way to package up a subset of your drawing code.
We should almost certainly make an InventoryView to keep all the calculations in. Whether that has a Section involved or not, I’m not sure. We’ll look at the Section that we have, to review what that’s like.
In a program like this, in my experience, one really wants good separation between the hardware-like window sizes, pixels and such, and the layout of information inside those windows. In principle, one should be able to resize the windows and have everything just adjust. We do not have that. We don’t have anything remotely like that. Instead we have various ad hoc calculations based on cell size (which is pixels in my mind), zoom factor (a constant that determines how big things are on screen), screen width and height, and the number of cells high and wide that we want to display.
It will be possible, perhaps even fairly easy, to fix the centering issue, because it’s done in our Cameras object, right about here:
class Cameras:
def scroll_dungeon_cam(self, cell):
px, py = self.clamped_position(cell)
self.dungeon_cam.position = (px, py)
def clamped_position(self, cell):
px, py = cell.position
mx, my = self.max_position()
px = self.apply_margin(mx, px)
py = self.apply_margin(my, py)
return px, py
def max_position(self):
mx = self.max_x * cell_size
my = self.max_y * cell_size
return mx, my
def apply_margin(self, max_coord, coord):
margin = max_coord / (2 * self.dungeon_cam.zoom)
return min(max_coord - margin, max(coord, margin))
I’d bet that I could mash that code right now and make the picture look right. But that’s not the way to go.
A bit of reading tells me that there is more to cameras than I have been aware of. It seems that they have viewports and projectors and such inside them. Must see if I can find something to read.
Here are two pictures, first with the camera as it has been:

And this one with the camera viewport clipped to max_x and max_y:

The code for that is the viewport setting code here:
class Cameras:
def __init__(self, view, max_x, max_y, zoom):
self.view = view
self.max_x = max_x
self.max_y = max_y
w = max_x*cell_size
h = max_y*cell_size
viewport = arcade.rect.LRBT(0, w, 0, h)
self.dungeon_cam = arcade.Camera2D(viewport=viewport)
self.init_zoom(zoom)
That’s the only change, passing in that rectangle. The border looks correct on the left, through the right side has a gap before the red bar. I’m not sure which part is wrong there. But the learning point is that setting viewport correctly seems to be able to pan the view around. And I’m sure there’s more inside Camera that I need to look into.
Summary
A bit of experimentation gives us at least two somewhat good things. First, we could clearly just smash the inventory view over there on the right where the red bar is, with a bit of math and texture drawing. Second, and more promising, we have stumbled on the fact that the Camera2D is more capable than YT realized, and will probably repay some study.
So an interesting morning, and now I’m off to study Camera and perhaps read some fiction.
See you next time!