We have a thin treasure/inventory capability wired in. Let’s review our notes and pick something to add. We add visible treasures and console announcements with just a little code.

A few articles back I remarked that we need a message to appear, wherever messages appear, indicating what has been given when a content item gives something to Dot. There is also the need for an inventory display, and of course the item needs an image to be displayed in the dungeon and in some kind of inventory display.

We can begin to sketch design and implementation ideas from that much:

For the message, we can have an announce method on whatever object is given to the interact_with_player method. That object is currently the Dungeon. We can have announce print to the console, as our first move. We’ll want some kind of on-screen text display, of course. One simple possibility would be a text pane in the game display. Another might be to display the text superimposed on the dungeon display. For extra credit we might scroll messages up and off the screen. There’s an effect like that in my iPad Dungeon game, and I rather like it.

So that suggests that each ContentItem might have a string description to be displayed. Maybe it’ll just be the noun phrase saying what the treasure is, and the announce method or methods could add in the common elements.

For the graphics, we presently draw a polygon for each item, just for something to display:

class ContentItem:
    def __init__(self, name, shape=None):
        self.name = name
        if shape:
            self.shape = shape
        else:
            self.shape = [(0.5,0.66), (0.66, 0.33),(0.33, 0.33)]

    def draw(self, cx, cy, size):
        sized = [self.size_point(pair, cx, cy, size)  for pair in self.shape]
        arcade.draw_polygon_outline(sized, arcade.color.GREEN)

We’ll want to replace that with a graphical texture or sprite or whatever Arcade calls it. Additionally, for efficiency, we might want to accumulate all the content items into a sprite list, for efficiency.

I think there’s learning to be had in each of these, but I’m inclined to start with the graphical bit, because it’ll be a nice little jolt of fun.

I’ll find an example of drawing an image in the plentiful Arcade examples. Just one moment … after just a little copy pasta:

class ContentItem:
    def __init__(self, name, shape=None):
        self.name = name
        if shape:
            self.shape = arcade.load_texture(':resources:images/items/gemRed.png')
        else:
            self.shape = arcade.load_texture(':resources:images/items/gemBlue.png')

    def draw(self, cx, cy, size):
        texture = self.shape
        scale = size/128
        arcade.draw_texture_rect(
            texture,
            arcade.XYWH(cx, cy, texture.width, texture.height).scale(scale)
        )

I’ve hacked the shape such that it defaults to a blue gem, and if a shape is provided, it uses a “red” gem, which we will shortly see isn’t as red as it might be. I just picked the scale to make the initial picture OK. We’ll probably want to do something more clever there, and elsewhere, and everywhere.

I also placed the two treasures in different cells, like this:

main.py
    ...
    dungeon.place_player_at(target)
    item_1 = ContentItem("treasure")
    shape = [(0.33, 0.66), (0.66, 0.66), (0.5, 0.33)]
    item_2 = ContentItem("more treasure", shape)
    dungeon.place_content_at(Cell(29, 28), item_1)
    dungeon.place_content_at(Cell(29, 29), item_2)
    window = arcade.Window(screen_width, screen_height, 'Caveat Emptor')
    view = DungeonView(dungeon)
    view.setup()
    window.show_view(view)
    arcade.run()

With that simple patch we get this most satisfying first result:

map showing two gems and Dot

Reflection

This is, of course, just a thin wire supporting what we’ll ultimately need. We’ll probably have treasure images at different scales, since we’ll be scrounging for them. So we’ll need some way to pass in the desired scaling rather than default it.

In a real game, we would have some kind of asset management tool in our Making App, where we can specify all the data we need to make things look right. We may or may not do something like that here: offhand it doesn’t sound interesting to me, but we’ll see.

There are surely other things we’ll want. For example, if a single cell contained two gems, only one would show up, because they’d be right on top of each other. Sometimes we’ll want that: picture an open chest with a gem on top, signifying that it was in the chest. Mostly we probably won’t want things stacking up. If we do, we may want control over which one is on top.

It goes on and on. We could worry about that now, but we will not. We will burn those bridges when we get to them, meanwhile trying to keep our code simple, with capabilities standing in the right place, ready to be enhanced. Sometimes we’ll get it wrong and need a bit more refactoring. We’ll see. I predict that our chances are much better than they’d be if we were tossed into the dungeon.

Let’s provide the ability to specify the treasure. We’ll change ContentItem to receive the resource identifier and load it.

I futz around a bit more than I should have and wind up here:

class ContentItem:
    error_texture = ':resources:images/items/star.png'
    def __init__(self, name, resource=None):
        self.name = name
        try:
            self.shape = arcade.load_texture(resource)
        except:
            self.name = 'invalid resource ' + name
            self.shape = arcade.load_texture(self.error_texture)

If we pass in a resource that we cannot load, we’ll display a star and the message will have invalid resource prepended. Maybe we’ll think of something better. In main, I’ve done this:

    path=':resources:images/items/'
    item_1 = ContentItem("mysterious key", path+'keyRed.png')
    item_2 = ContentItem("sapphire", path+'gemBlue.png')
    dungeon.place_content_at(Cell(29, 28), item_1)
    dungeon.place_content_at(Cell(29, 29), item_2)
    window = arcade.Window(screen_width, screen_height, 'Caveat Emptor')
    view = DungeonView(dungeon)
    view.setup()
    window.show_view(view)
    arcade.run()

With this result:

map showing a gem, a key, and Dot

Let’s go a bit further. I’ll put an invalid item into main, and we’ll have the giving logic print a message to the console.

main.py
    ...
    item_1 = ContentItem("mysterious key", path+'keyRed.png')
    item_2 = ContentItem("sapphire", path+'gemBlue.png')
    item_3 = ContentItem("sapphire", path+'garbled.png')
    dungeon.place_content_at(Cell(29, 28), item_1)
    dungeon.place_content_at(Cell(29, 29), item_2)
    dungeon.place_content_at(Cell(29, 30), item_3)

And …

class Dungeon:
    def announce_received(self, item_name):
        print(f'You have found: {item_name}')

And when we run the program, we get this display:

map showing a gem, a key, a star, and Dot

The star represents the erroneous object and when we walk over the three items the console says:

You have found: mysterious key
You have found: sapphire
You have found: invalid resource sapphire

Perfect in every way! Let’s sum up.

Summary

Again, we have strung a few very thin lines across the chasm we need to cross, treasure images and messages. And yet, those lines are, I’d argue, rather well positioned and ready to be enhanced.

The resource and name is provided by the user. If a missing resource is specified, we display an error image. It’s a star right now, and we might want some other display, or even a default working treasure. We are in a position to change that as needed.

A missing resource also changes the name of the desired item to include ‘invalid resource’, our proposed error message. Again, we can change that when we get a better idea.

The ContentItem knows to give itself to the player, and to announce that it is doing so. That seems to me to be in the right place, but if it isn’t, we’ll presumably want to have the gift received (currently dungeon) do it, so we can just move the logic over there.

With very few changes, our game is a bit more game-like. By my lights, this is the way. Small steps, thin logic, made stronger as needed.

See you next time!