# Part 4 - Field of View

We have a dungeon now, and we can move about it freely. But are we really exploring the dungeon if we can just see it all from the beginning?

Most roguelikes (not all!) only let you see within a certain range of your character, and ours will be no different. We need to implement a way to calculate the “Field of View” for our adventurer, and fortunately, libtcod makes that easy!

We’ll need to define a few variables before we get started. Add these in the same section as our screen and map variables:

``````   ...
max_rooms = 30

+   fov_algorithm = 0
+   fov_light_walls = True
+   fov_radius = 10

colors = {
...
``````
```    ...
max_rooms = 30

fov_algorithm = 0
fov_light_walls = True

colors = {
...```

‘0’ is just the default algorithm that libtcod uses; it has more, and I encourage you to experiment with them later. `fov_light_walls` just tells us whether or not to ’light up’ the walls we see; you can change it if you don’t like the way it looks. `fov_radius` is somewhat obvious, it tells us how far we can actually see.

We also need to update the `colors` dictionary, because now we need two more colors for the ’light’ versions of both walls and floors. Walls and floors in our fov will be ’lit’, distinguishing them from the ones outside what we can see.

``````    colors = {
'dark_wall': libtcod.Color(0, 0, 100),
'dark_ground': libtcod.Color(50, 50, 150),
'light_wall': libtcod.Color(130, 110, 50),
'light_ground': libtcod.Color(200, 180, 50)
}``````

* Don’t forget to add the comma after the ‘dark_ground’ entry; Python will throw an error without it!

If you don’t like these colors, feel free to change them to your liking.

The thing about field of view is that it doesn’t need to be computed every turn. In fact, it would be quite a waste to do so! We really only need change it when the player moves. Attacking, using an item, or just standing still for a turn doesn’t alter FOV. We can handle this by having a boolean variable, which we’ll call `fov_recompute`, which tells us if we need to recompute. We can define it somewhere above our game loop (I put mine right after the map initialization).

``````   ...
game_map.make_map(max_rooms, room_min_size, room_max_size, map_width, map_height, player)

+   fov_recompute = True

key = libtcod.Key()
...
``````
```    ...
game_map.make_map(max_rooms, room_min_size, room_max_size, map_width, map_height, player)

fov_recompute = True

key = libtcod.Key()
...```

It’s `True` by default, because we have to compute it right when the game starts.

Now let’s initialize our field of view, which we’ll store in a variable called `fov_map`. `fov_map` will need to not only be initialized, but recomputed when the player moves. Let’s keep these functions out of `engine.py`, and instead, put them in a new file, called `fov_functions.py`. In that file, put the following:

``````import tcod as libtcod

def initialize_fov(game_map):
fov_map = libtcod.map_new(game_map.width, game_map.height)

for y in range(game_map.height):
for x in range(game_map.width):
libtcod.map_set_properties(fov_map, x, y, not game_map.tiles[x][y].block_sight,
not game_map.tiles[x][y].blocked)

return fov_map``````

Call this function in `engine.py` and store the result in `fov_map`.

``````   ...
fov_recompute = True

+   fov_map = initialize_fov(game_map)

key = libtcod.Key()
...
``````
```    ...
fov_recompute = True

fov_map = initialize_fov(game_map)

key = libtcod.Key()
...```

Don’t forget the import for the function.

``````...
from entity import Entity
+from fov_functions import initialize_fov
from input_handlers import handle_keys
...
``````
```...
from entity import Entity
from fov_functions import initialize_fov
from input_handlers import handle_keys
...```

While we’re at it, let’s modify the section where we move the player to set `fov_recompute` to True.

``````               ...
player.move(dx, dy)

+               fov_recompute = True
``````
```                ...
player.move(dx, dy)

fov_recompute = True```

But where does the recompute actually happen? For that, let’s add a new function to `fov_functions.py` to do the recomputing. The recompute function will modify the `fov_map` variable based on where the player is, what the radius for lighting is, whether or not to light the walls, and what algorithm we’re using.

That’s a lot of variables, but consider this: in your game, you’ll probably pick one FOV algorithm and stick with it. Also, whether or not you light the walls probably won’t change during the course of the game. So why not create our function with default arguments? That way, we can pass the `light_walls` and `algorithm` variables if we want to, but if not, a default is chosen. That looks like this:

``````def initialize_fov(game_map):
...

+def recompute_fov(fov_map, x, y, radius, light_walls=True, algorithm=0):
+   libtcod.map_compute_fov(fov_map, x, y, radius, light_walls, algorithm)
``````
```def initialize_fov(game_map):
...

def recompute_fov(fov_map, x, y, radius, light_walls=True, algorithm=0):
libtcod.map_compute_fov(fov_map, x, y, radius, light_walls, algorithm)```

So when we call the function, we have to give fov_map, x, y, and radius, but we don’t necessarily have to pass in light_walls or algorithm. In my `engine.py` file, I’ll pass them in anyway, but you don’t have to if you don’t want to (you can also change the defaults I gave above to whatever you prefer).

Whatever you decide, put your fov recomputation in `engine.py` like so:

``````       ...
libtcod.sys_check_for_event(libtcod.EVENT_KEY_PRESS, key, mouse)

+       if fov_recompute:
+           recompute_fov(fov_map, player.x, player.y, fov_radius, fov_light_walls, fov_algorithm)

render_all(con, entities, game_map, screen_width, screen_height, colors)
...
``````
```        ...
libtcod.sys_check_for_event(libtcod.EVENT_KEY_PRESS, key, mouse)

if fov_recompute:
recompute_fov(fov_map, player.x, player.y, fov_radius, fov_light_walls, fov_algorithm)

render_all(con, entities, game_map, screen_width, screen_height, colors)
...```

… And, of course, we have to import that function:

``````...
from entity import Entity
-from fov_functions import initialize_fov
+from fov_functions import initialize_fov, recompute_fov
from input_handlers import handle_keys
...
``````
```...
from entity import Entity
from fov_functions import initialize_fov, recompute_fov
from input_handlers import handle_keys
...```

Now, after the player successfully moves, the field of view will be set to recalculate, but it won’t if we do something else.

With our field of view calculated, we need to actually display it (if you run the code now, you won’t notice any visible change). Open up `render_functions.py` and modify the `render_all` function like this:

``````def render_all(con, entities, game_map, screen_width, screen_height, colors):
+def render_all(con, entities, game_map, fov_map, fov_recompute, screen_width, screen_height, colors):
-   for y in range(game_map.height):
+   if fov_recompute:
-       for x in range(game_map.width):
+       for y in range(game_map.height):
-           wall = game_map.tiles[x][y].block_sight
-
-           if wall:
-               libtcod.console_set_char_background(con, x, y, colors.get('dark_wall'), libtcod.BKGND_SET)
-           else:
-               libtcod.console_set_char_background(con, x, y, colors.get('dark_ground'), libtcod.BKGND_SET)
+           for x in range(game_map.width):
+               visible = libtcod.map_is_in_fov(fov_map, x, y)
+               wall = game_map.tiles[x][y].block_sight

+               if visible:
+                   if wall:
+                       libtcod.console_set_char_background(con, x, y, colors.get('light_wall'), libtcod.BKGND_SET)
+                   else:
+                       libtcod.console_set_char_background(con, x, y, colors.get('light_ground'), libtcod.BKGND_SET)
+               else:
+                   if wall:
+                       libtcod.console_set_char_background(con, x, y, colors.get('dark_wall'), libtcod.BKGND_SET)
+                   else:
+                       libtcod.console_set_char_background(con, x, y, colors.get('dark_ground'), libtcod.BKGND_SET)

# Draw all entities in the list
for entity in entities:
draw_entity(con, entity)

libtcod.console_blit(con, 0, 0, screen_width, screen_height, 0, 0, 0)
``````
```def render_all(con, entities, game_map, screen_width, screen_height, colors):
def render_all(con, entities, game_map, fov_map, fov_recompute, screen_width, screen_height, colors):
if fov_recompute:
for y in range(game_map.height):
for x in range(game_map.width):
visible = libtcod.map_is_in_fov(fov_map, x, y)
wall = game_map.tiles[x][y].block_sight

if visible:
if wall:
libtcod.console_set_char_background(con, x, y, colors.get('light_wall'), libtcod.BKGND_SET)
else:
libtcod.console_set_char_background(con, x, y, colors.get('light_ground'), libtcod.BKGND_SET)
else:
if wall:
libtcod.console_set_char_background(con, x, y, colors.get('dark_wall'), libtcod.BKGND_SET)
else:
libtcod.console_set_char_background(con, x, y, colors.get('dark_ground'), libtcod.BKGND_SET)

# Draw all entities in the list
for entity in entities:
draw_entity(con, entity)

libtcod.console_blit(con, 0, 0, screen_width, screen_height, 0, 0, 0)```

* Note: Blue denotes lines that are exactly the same as before, expect for their indentation. The if statements for `fov_recompute` and `visible` force certain lines to be indented farther than they were before. Remember, this is Python, indentation matters!

Now our `render_all` function will display tiles differently, depending on if they’re in our field of view or not. If a tile falls in the `fov_map`, we draw it with the ’light’ colors, and if not, we draw the ‘dark’ version.

The definition of `render_all` has changed, so be sure to update it in `engine.py`. While we’re at it, let’s set `fov_recompute` to `False` after we call `render_all`.

``````       ...
-       render_all(con, entities, game_map, screen_width, screen_height, colors)
+       render_all(con, entities, game_map, fov_map, fov_recompute, screen_width, screen_height, colors)
+
+       fov_recompute = False
``````
```        ...
render_all(con, entities, game_map, screen_width, screen_height, colors)
render_all(con, entities, game_map, fov_map, fov_recompute, screen_width, screen_height, colors)

fov_recompute = False```

Run the project now. The player’s field of view is now visible! But, despite being able to “see” the FOV, it still doesn’t really do anything. We can still see the entire map, along with our NPC. Luckily, the changes we have to make to fix this are fairly minimal.

Let’s start with our NPC. We should just be able to modify our `draw_entity` function to account for the field of view, which would solve our problem.

``````def draw_entity(con, entity):
+def draw_entity(con, entity, fov_map):
-   libtcod.console_set_default_foreground(con, entity.color)
-   libtcod.console_put_char(con, entity.x, entity.y, entity.char, libtcod.BKGND_NONE)
+   if libtcod.map_is_in_fov(fov_map, entity.x, entity.y):
+       libtcod.console_set_default_foreground(con, entity.color)
+       libtcod.console_put_char(con, entity.x, entity.y, entity.char, libtcod.BKGND_NONE)
``````
```def draw_entity(con, entity):
def draw_entity(con, entity, fov_map):
if libtcod.map_is_in_fov(fov_map, entity.x, entity.y):
libtcod.console_set_default_foreground(con, entity.color)
libtcod.console_put_char(con, entity.x, entity.y, entity.char, libtcod.BKGND_NONE)```

* Again, the blue means the line is the same as before, except the indentation has changed.

Also be sure to update the part where we call the function:

``````   for entity in entities:
-       draw_entity(con, entity)
+       draw_entity(con, entity, fov_map)
``````
```    for entity in entities:
draw_entity(con, entity)
draw_entity(con, entity, fov_map)```

Run the project again, and you won’t see the NPC unless it’s in your field of view.

Now for the map. In traditional roguelikes, your character can only see whats inside its field of view, but it will “remember” areas that were explored previously. We can accomplish this effect by adding a variable called `explored` to our `Tile` class. Modify the `__init__` function in `Tile` to include this new variable:

``````       ...
self.block_sight = block_sight

+       self.explored = False
``````
```        ...
self.block_sight = block_sight

self.explored = False```

This new variable needs to be taken into account in our `render_all` function. Let’s do that now. We’ll only draw the tiles outside of our field of view if we’ve explored them previously. Also, any tiles that are in our field of view, we’ll mark as ’explored’.

``````               ...
visible = libtcod.map_is_in_fov(fov_map, x, y)
wall = game_map.tiles[x][y].block_sight

if visible:
if wall:
libtcod.console_set_char_background(con, x, y, colors.get('light_wall'), libtcod.BKGND_SET)
else:
libtcod.console_set_char_background(con, x, y, colors.get('light_ground'), libtcod.BKGND_SET)

+                   game_map.tiles[x][y].explored = True
-               else:
+               elif game_map.tiles[x][y].explored:
if wall:
libtcod.console_set_char_background(con, x, y, colors.get('dark_wall'), libtcod.BKGND_SET)
else:
libtcod.console_set_char_background(con, x, y, colors.get('dark_ground'), libtcod.BKGND_SET)
...
``````
```                ...
visible = libtcod.map_is_in_fov(fov_map, x, y)
wall = game_map.tiles[x][y].block_sight

if visible:
if wall:
libtcod.console_set_char_background(con, x, y, colors.get('light_wall'), libtcod.BKGND_SET)
else:
libtcod.console_set_char_background(con, x, y, colors.get('light_ground'), libtcod.BKGND_SET)

game_map.tiles[x][y].explored = True
else:
elif game_map.tiles[x][y].explored:
if wall:
libtcod.console_set_char_background(con, x, y, colors.get('dark_wall'), libtcod.BKGND_SET)
else:
libtcod.console_set_char_background(con, x, y, colors.get('dark_ground'), libtcod.BKGND_SET)
...```

We now have a real, explorable dungeon! True, there may not be much in there right now, but this was a major step to a working game. In the next few parts, we’ll fill the dungeon with some evil(?) monsters to punch.

If you want to see the code so far in its entirety, click here.

Click here to move on to the next part of this tutorial.