Part 2

The generic Entity, the render functions, and the map

Now that we can move our little '@' symbol around, we need to give it something to move around in. But before that, let's stop for a moment and think about the player object itself.

Right now, we just represent the player with the '@' symbol, and its x and y coordinates. Shouldn't we tie those things together in an object, along with some other data and functions that pertain to it?

Let's create a generic class to represent not just the player, but just about everything in our game world. Enemies, items, and whatever other foreign entities we can dream of will be part of this class, which we'll call Entity.

Create a new file, and call it entity.py. In that file, put the following class:

This is pretty self explanatory. The Entity class holds the x and y coordinates, along with the character (the '@' symbol in the player's case) and the color (white for the player by default). We also have a method called move, which will allow the entity to be moved around by a given x and y.

Let's put our fancy new class into action! Modify the first part of engine.py to look like this:

Now we need to alter the way that the entities are drawn to the screen. If you run the code right now, only
the player gets drawn. Let's write some functions to draw not only the player, but any entity currently in
our entities list.

Create a new file called render_functions.py. This will hold our functions for drawing and
clearing from the screen. Put the following code in that file.

The render_all function is what we'll call from our game loop to draw entities and, shortly,
the map. For now, it takes the console (con), a list of entities, and the screen width/height as parameters,
and calls the draw_entity function on each. It then blits the changes to the screen.

draw_entity is what does the actual drawing. The code should look very similar to what's in our
game loop right now, except it's using the entity's variables (x, y, char, and color) to do the drawing.
This makes it flexible enough to, theoretically, draw any entity we pass to it.

clear_all is what we'll use to clear all the entities after drawing them to the screen. It's
just a loop that calls another function.

clear_entity just does what our previous line did, and clears the entity from the screen (so
that when it moves, it doesn't leave a trail behind).

Now that we've gotten a few functions to assist drawing the entities, let's put them to use. Make the
following modifications to the section where we drew the player (in engine.py).

If you run the project now, you should see your '@' symbol, along with a yellow one representing our NPC.
It doesn't do anything, yet, but now we have a method for drawing more than one character to the screen.

It's time to shift gears a bit and get our map in place. The map will consist of a 2d array of Tile objects.
Tiles will have a few properties that define whether we can move through them, or see through them.

We'd better start by defining the size of our map. Add these variables right below where you defined the
screen width and height:

...
screen_height = 50
map_width = 80
map_height = 45

Simple enough. Now we need a place for our Tile class, along with a few other map classes, to live. I prefer to keep similar classes in the same folder, so create a new Python package (that's a directory with a file called __init__.py in it, __init__.py is empty in this case) called map_objects. In there, create a file called tile.py and put the following code in it.

class Tile:
"""
A tile on a map. It may or may not be blocked, and may or may not block sight.
"""
def __init__(self, blocked, block_sight=None):
self.blocked = blocked
# By default, if a tile is blocked, it also blocks sight
if block_sight is None:
block_sight = blocked
self.block_sight = block_sight

Nothing too complicated here. The Tile class holds whether or not the tile is blocked (if it's
blocked, you can't move through it), and whether or not it blocks sight (for our FOV algorithm later).
Notice that you don't have to pass block_sight every time; it's assumed to be the same as
blocked. By keeping the two separate, we can have a tile that can be seen-through,
but not crossed (a lava pit maybe?), or vice versa (a dark room perhaps).

Now that we have the tile class, we need some sort of container to hold our tiles. Let's call this class
GameMap, which will hold the 2d array of tiles and some methods for setting up and interacting
with it. Create a file (in the map_objects folder) and call it game_map, and put the following
in it:

We're passing in the width and height of the map (which we've defined in our engine), and initializing a 2d
array of Tiles, set to non-blocking by default. We're setting a few tiles as blocked, just for demonstration
purposes. I've kept the code setting the tiles up out of the __init__ function, for two reasons.
One, it may get called outside the initialization, and two, because I prefer keeping __init__
functions as simple as possible.

Go back to engine.py, where we'll make a few changes so that our map get initialized and then
drawn to the screen.

Firstly, we need to define what colors to draw for blocked and non-blocked tiles. Let's set up a dictionary
that holds the colors we'll be using for now (it will expand as this tutorial goes on).

Now that our map object is ready to go, let's pass it to render_all so that we can draw it.
We'll also pass the colors dictionary, because render_all will need to know what
colors to draw the various parts of the map. Note that the order in which you pass these arguments doesn't
matter, it just has to match in the function definition and when you call it.

* Note: You could shorted the is_blocked function, by simply writingreturn self.tiles[x][y].blocked. But we'll be modifying this function to do more checks
soon, hence we're taking the more explicit route.

Run the project again, and you will get stuck on the walls.

That's going to do it for this tutorial. It may not look like much, but we've set ourselves up to create
some real looking dungeons in the next tutorial.