Get started with this tutorial series here!

The next function, `updateDirection`, is very simple although it is rather long. This is perhaps the only time where I’d recommend a copy-paste instead of writing the code for yourself!

1. ```void Map::updateDirection(TileType tileType) ```
2. ```{ ```
3. ```    for(int y = 0; y < this->height; ++y) ```
4. ```    { ```
5. ```        for(int x = 0; x < this->width; ++x) ```
6. ```        { ```
7. ```            int pos = y*this->width+x; ```
8. ` `
9. ```            if(this->tiles[pos].tileType != tileType) continue; ```
10. ` `
11. ```            bool adjacentTiles = {{0,0,0},{0,0,0},{0,0,0}}; ```
12. ` `
13. ```            /* Check for adjacent tiles of the same type */ ```
14. ```            if(x > 0 && y > 0) ```
15. ```                adjacentTiles = (this->tiles[(y-1)*this->width+(x-1)].tileType == tileType); ```
16. ```            if(y > 0) ```
17. ```                adjacentTiles = (this->tiles[(y-1)*this->width+(x  )].tileType == tileType); ```
18. ```            if(x < this->width-1 && y > 0) ```
19. ```                adjacentTiles = (this->tiles[(y-1)*this->width+(x+1)].tileType == tileType); ```
20. ```            if(x > 0) ```
21. ```                adjacentTiles = (this->tiles[(y  )*this->width+(x-1)].tileType == tileType); ```
22. ```            if(x < width-1) ```
23. ```                adjacentTiles = (this->tiles[(y  )*this->width+(x+1)].tileType == tileType); ```
24. ```            if(x > 0 && y < this->height-1) ```
25. ```                adjacentTiles = (this->tiles[(y+1)*this->width+(x-1)].tileType == tileType); ```
26. ```            if(y < this->height-1) ```
27. ```                adjacentTiles = (this->tiles[(y+1)*this->width+(x  )].tileType == tileType); ```
28. ```            if(x < this->width-1 && y < this->height-1) ```
29. ```                adjacentTiles = (this->tiles[(y+1)*this->width+(x+1)].tileType == tileType); ```
30. ` `
31. ```            /* Change the tile variant depending on the tile position */ ```
32. ```            if(adjacentTiles && adjacentTiles && adjacentTiles && adjacentTiles) ```
33. ```                this->tiles[pos].tileVariant = 2; ```
34. ```            else if(adjacentTiles && adjacentTiles && adjacentTiles) ```
35. ```                this->tiles[pos].tileVariant = 7; ```
36. ```            else if(adjacentTiles && adjacentTiles && adjacentTiles) ```
37. ```                this->tiles[pos].tileVariant = 8; ```
38. ```            else if(adjacentTiles && adjacentTiles && adjacentTiles) ```
39. ```                this->tiles[pos].tileVariant = 9; ```
40. ```            else if(adjacentTiles && adjacentTiles && adjacentTiles) ```
41. ```                this->tiles[pos].tileVariant = 10; ```
42. ```            else if(adjacentTiles && adjacentTiles) ```
43. ```                this->tiles[pos].tileVariant = 0; ```
44. ```            else if(adjacentTiles && adjacentTiles) ```
45. ```                this->tiles[pos].tileVariant = 1; ```
46. ```            else if(adjacentTiles && adjacentTiles) ```
47. ```                this->tiles[pos].tileVariant = 3; ```
48. ```            else if(adjacentTiles && adjacentTiles) ```
49. ```                this->tiles[pos].tileVariant = 4; ```
50. ```            else if(adjacentTiles && adjacentTiles) ```
51. ```                this->tiles[pos].tileVariant = 5; ```
52. ```            else if(adjacentTiles && adjacentTiles) ```
53. ```                this->tiles[pos].tileVariant = 6; ```
54. ```            else if(adjacentTiles) ```
55. ```                this->tiles[pos].tileVariant = 0; ```
56. ```            else if(adjacentTiles) ```
57. ```                this->tiles[pos].tileVariant = 0; ```
58. ```            else if(adjacentTiles)   ```
59. ```                this->tiles[pos].tileVariant = 1; ```
60. ```            else if(adjacentTiles) ```
61. ```                this->tiles[pos].tileVariant = 1; ```
62. ```        } ```
63. ```    } ```
64. ` `
65. ```    return; ```
66. `}`

As an overview, `updateDirection` iterates over every `Tile`. It then builds an array of all the adjacent tiles, setting each element to `true` if the `Tile` is of the same type as the centre `Tile`, and `false` otherwise. Finally the `adjacentTiles` array is checked to see the configuration of its `true`/`false` values, and the `tileVariant` of the `Tile` is set accordingly. The order of the checks here is important, as we are only checking for `true` elements and not `false`; some combinations exist that would override others.

For example, a crossroads would appear as a corner tile if you checked for the corner tile first. You could make this more programmer friendly by defining `const` values for each direction combination, but it’s just as simple to refer to this image (the highlighted edges are adjacent to a tile of the same type) We now go from long and boring to short and interesting, with our `depthfirstsearch` function. If you’ve used such an algorithm before you can skip over this bit, but at least look at the code! If not, it’s time to explain a ‘proper’ algorithm. As we discussed a while back, we want our industrial zones to dig material from the ground and then ship it to commercial zones where it can be sold. They can’t just send the material to any zone though, they would need to be connected using roads (or another zone). To do this we need to check if there is a path that only goes through adjacent zones or roads and that takes us from the industrial zone at the start to some other commercial zone.

We use something called a depth-first search to do this, which starts at a `Tile` and checks if we can go through it or not. If we can, it branches off to every adjacent `Tile`, checking again, before branching off, then checking again… it then stops when we find the `Tile` we can stop at. This is fine, but it isn’t very efficient; we will have to check if every industrial zone is connected to every commercial zone! That’s going to be extremely slow, and what’s more we have to do it every new game day. One way of improving this would be to simply use a more efficient pathfinding algorithm, such as A*. That doesn’t fix our problem though, we’ve still got far too many pairs of zones to check, especially in a large city.

Instead what we do is split the `Map` into regions (using the `regions` array from before). Each `Tile`will be labelled depending on what region it is in, where two `Tile`s are in the same region if there is a path (through zones or roads) between them. If we find all of those paths, we can just check which region each `Tile` is in instead of trying to find a path between them each time. (We don’t care what the path is after all, only that one exists!) What’s more, we only have to update the regions when those paths change; if a `Tile` is created or destroyed. That’s exactly what `depthfirstsearch` and `findConnectingRegions` do, they split the `Map` into those regions. 1. ```void Map::depthfirstsearch(std::vector<TileType>& whitelist, ```
2. ```    sf::Vector2i pos, int label, int regionType=0) ```
3. ```{ ```
4. ```    if(pos.x < 0 || pos.x >= this->width) return; ```
5. ```    if(pos.y < 0 || pos.y >= this->height) return; ```
6. ```    if(this->tiles[pos.y*this->width+pos.x].regions[regionType] != 0) return; ```
7. ```    bool found = false; ```
8. ```    for(auto type : whitelist) ```
9. ```    { ```
10. ```        if(type == this->tiles[pos.y*this->width+pos.x].tileType) ```
11. ```        { ```
12. ```            found = true; ```
13. ```            break; ```
14. ```        } ```
15. ```    } ```
16. ```    if(!found) return; ```
17. ` `
18. ```    this->tiles[pos.y*this->width+pos.x].regions[regionType] = label; ```
19. ` `
20. ```    depthfirstsearch(whitelist, pos + sf::Vector2i(-1,  0), label, regionType); ```
21. ```    depthfirstsearch(whitelist, pos + sf::Vector2i(0 ,  1), label, regionType); ```
22. ```    depthfirstsearch(whitelist, pos + sf::Vector2i(1 ,  0), label, regionType); ```
23. ```    depthfirstsearch(whitelist, pos + sf::Vector2i(0 , -1), label, regionType); ```
24. ` `
25. ```    return; ```
26. `}`

Let’s examine how this function works. First we check to see if the supplied position is out of bounds of the `Map`. If it is, we `return`. We then check to see if the `Tile` has already received a region and hence has already been visited by the function. If it has, we `return`, as we don’t want to go over the same `Tile` twice. If we did the function would never finish! We then check to see if the `Tile`‘s `tileType` is present in `whitelist`. If it isn’t, once again we `return`, otherwise we assign the `Tile` a region and call `depthfirstsearch` again 4 times, once for each adjacent tile.

Such a function that calls itself is called recursive, and so this is a recursive implementation of the depth-first search algorithm; there is also an iterative version, which uses `for` loops, but I find this one is much easier to understand! If you’ve been paying attention though, you’ll have noticed that `depthfirstsearch` is a `private` function! We will use `findConnectingRegions` to actually start the search.

1. ```void Map::findConnectedRegions(std::vector<TileType> whitelist, int regionType=0) ```
2. ```{ ```
3. ```    int regions = 1; ```
4. ` `
5. ```    for(auto& tile : this->tiles) tile.regions[regionType] = 0; ```
6. ` `
7. ```    for(int y = 0; y < this->height; ++y) ```
8. ```    { ```
9. ```        for(int x = 0; x < this->width; ++x) ```
10. ```        { ```
11. ```            bool found = false; ```
12. ```            for(auto type : whitelist) ```
13. ```            { ```
14. ```                if(type == this->tiles[y*this->width+x].tileType) ```
15. ```                { ```
16. ```                    found = true; ```
17. ```                    break; ```
18. ```                } ```
19. ```            } ```
20. ```            if(this->tiles[y*this->width+x].regions[regionType] == 0 && found) ```
21. ```            { ```
22. ```                depthfirstsearch(whitelist, sf::Vector2i(x, y), regions++, regionType); ```
23. ```            } ```
24. ```        } ```
25. ```    } ```
26. ```    this->numRegions[regionType] = regions; ```
27. `}`

Upon calling the function, we clear each `Tile`‘s region to 0, and then we iterate over every `Tile`. Once again we check to see if the `tileType` is in the `whitelist`, and if it is and the `Tile` has not yet been assigned a region we call `depthfirstsearch` on that tile. Since `depthfirstsearch` only continues through `whitelist`ed `Tile`s, every call of `depthfirstsearch` will be for a new region! Therefore we just increment the `regions` variable after every call, and each isolated block of tiles will be assigned a different region.

All that’s left is to try it out! Create a new `Map` in `GameStateEditor`, then either `load` it or fill it with random tiles using a `for` loop inside the constructor. Add a `map.draw` call in `draw` after we draw the background (note it’s `map.draw(window, dt)` not `window.draw(map)`), then compile and run! Hopefully you should see a lovely, animated world. Now that we’ve actually got some interesting things on the screen, this program is starting to look like a game! It’s still completely non-interactive though, so let’s change that by adding the ability to pan (move around) and zoom the camera. For this we will use a state variable (not a `GameState`, just a variable that will keep track of what the player is doing) called `actionState`. First then, let’s add this and some other variables to `GameStateEditor`.

1. ```#include <SFML/System.hpp> ```
2. ` `
3. ```#include "game_state.hpp" ```
4. ```#include "map.hpp" ```
5. ` `
6. ```enum class ActionState { NONE, PANNING }; ```
7. ` `
8. ```class GameStateEditor : public GameState ```
9. ```{ ```
10. ```    private: ```
11. ` `
12. ```    ActionState actionState; ```
13. ` `
14. ```    sf::View gameView; ```
15. ```    sf::View guiView; ```
16. ` `
17. ```    Map map; ```
18. ` `
19. ```    sf::Vector2i panningAnchor; ```
20. `    float zoomLevel;`

We’ve used an `enum class` definition again to create the `ActionState` type; if `actionState == ActionState::PANNING` then the player is panning the camera, otherwise they are not. We don’t need an entry for zooming, as zooming is not a continuous process and will only happen upon each turn of the mouse wheel. We then have the `panningAnchor` variable which will keep track of where we started panning.

Upon pressing the middle mouse button, `panningAnchor` will record the mouse position. Then as the mouse moves away from the `panningAnchor` and the middle mouse button is still held down, the world will move too. `zoomLevel` records how far zoomed in we are, and is increased and decreased as the player scrolls the mouse wheel forwards and backwards. We will double and halve `zoomLevel` in order to keep the world at a nice scale factor (computers love powers of 2). First let’s initialize some variable inside of the `GameStateEditor` constructor.

1. ```GameStateEditor::GameStateEditor(Game* game) ```
2. ```{ ```
3. ```    this->game = game; ```
4. ```    sf::Vector2f pos = sf::Vector2f(this->game->window.getSize()); ```
5. ```    this->guiView.setSize(pos); ```
6. ```    this->gameView.setSize(pos); ```
7. ```    pos *= 0.5f; ```
8. ```    this->guiView.setCenter(pos); ```
9. ```    this->gameView.setCenter(pos); ```
10. ` `
11. ```    map = Map("city_map.dat", 64, 64, game->tileAtlas); ```
12. ` `
13. ```    this->zoomLevel = 1.0f; ```
14. ` `
15. ```    /* Centre the camera on the map */ ```
16. ```    sf::Vector2f centre(this->map.width, this->map.height*0.5); ```
17. ```    centre *= float(this->map.tileSize); ```
18. ```    gameView.setCenter(centre); ```
19. ` `
20. ```    this->actionState = ActionState::NONE; ```
21. `}`

The new parts start below the `map` assignment; we initialize `zoomLevel`, set the `actionState`, and whilst we’re here we also centre the camera on the `Map`. Forgive the British/American mix, I can’t seem to default to “center”… We also need to update the `draw` function so that is uses the correct views. So far they’ve been the same and it hasn’t mattered, but now that we are moving `gameView`around and zooming it in and out we need to make the distinction.

1. ```void GameStateEditor::draw(const float dt) ```
2. ```{ ```
3. ```    this->game->window.clear(sf::Color::Black); ```
4. ` `
5. ```    this->game->window.setView(this->guiView); ```
6. ```    this->game->window.draw(this->game->background); ```
7. ` `
8. ```    this->game->window.setView(this->gameView); ```
9. ```    map.draw(this->game->window, dt); ```
10. ` `
11. ```    return; ```
12. `}`

We want the background to always be drawn in the same place, so we draw it on `guiView`, but the world is part of the game and so should be drawn to `gameView`. If you compile the code now you should see a nicely centred `Map` being displayed in front of `background`, which should expand as you resize the window whilst the `Map` stays in the same (relative) place. Now we can add the actual panning and zooming code. This code should be placed as events inside of the `handleInput` function. The new events are

1. ```case sf::Event::MouseMoved: ```
2. ```{ ```
3. ```    /* Pan the camera */ ```
4. ```    if(this->actionState == ActionState::PANNING) ```
5. ```    { ```
6. ```        sf::Vector2f pos = sf::Vector2f(sf::Mouse::getPosition(this->game->window) - this->panningAnchor); ```
7. ```        gameView.move(-1.0f * pos * this->zoomLevel); ```
8. ```        panningAnchor = sf::Mouse::getPosition(this->game->window); ```
9. ```    } ```
10. ```    break; ```
11. ```} ```
12. ```case sf::Event::MouseButtonPressed: ```
13. ```{ ```
14. ```    /* Start panning */ ```
15. ```    if(event.mouseButton.button == sf::Mouse::Middle) ```
16. ```    { ```
17. ```        if(this->actionState != ActionState::PANNING) ```
18. ```        { ```
19. ```            this->actionState = ActionState::PANNING; ```
20. ```            this->panningAnchor = sf::Mouse::getPosition(this->game->window); ```
21. ```        } ```
22. ```    } ```
23. ```    break; ```
24. ```} ```
25. ```case sf::Event::MouseButtonReleased: ```
26. ```{ ```
27. ```    /* Stop panning */ ```
28. ```    if(event.mouseButton.button == sf::Mouse::Middle) ```
29. ```    { ```
30. ```        this->actionState = ActionState::NONE; ```
31. ```    } ```
32. ```    break; ```
33. ```} ```
34. ```/* Zoom the view */ ```
35. ```case sf::Event::MouseWheelMoved: ```
36. ```{ ```
37. ```    if(event.mouseWheel.delta < 0) ```
38. ```    { ```
39. ```        gameView.zoom(2.0f); ```
40. ```        zoomLevel *= 2.0f; ```
41. ```    } ```
42. ```    else ```
43. ```    { ```
44. ```        gameView.zoom(0.5f); ```
45. ```        zoomLevel *= 0.5f; ```
46. ```    } ```
47. ```    break; ```
48. `}`

When the middle mouse button is pressed and the player is not already panning the camera (this is why we created `actionState`) we set the `panningAnchor` to the position of the mouse. This is a screen position, and has nothing to do with the views we created. We also set `actionState` so that the program knows that the player is panning.

When the middle mouse button is released, we set the `actionState` to `ActionState::NONE` so that the player is not panning anymore. If the mouse moves whilst the player is panning then we get the new position of the mouse and subtract the old position (the `panningAnchor`) from it. Since both positions are coordinates, we can interpet this as calculating the (mathematical) vector from the anchor to the mouse. We then move the `gameView` in the direction that vector points.

To get a nice pan, we want the `Map` to move exactly in sync with the mouse, so whatever pixel was underneath the mouse when the panning started will remain beneath the mouse throughout the pan. To achieve this we first reverse the direction of motion; if you stop to think about it, moving a camera to the right is the same as moving everything else to the left, but we want the view to follow the mouse like a sheet of paper or a physical map, and so we reverse this by multiply by -1. At a 1:1 screen to `gameView` scale ratio (when `zoomLevel` is 1) the view will follow the cursor perfectly. But if `zoomLevel` is 2 we have a 1:2 ratio and so we have to multiply however much the mouse has moved by the `zoomLevel` in order to get the ratio to 2:2 (which is the same as 1:1) and make everything move in sync.

Finally, when the mouse wheel is scrolled up (negative `delta`) we zoom the view by a factor of 2 and if the wheel is scrolled down we zoom the view by a factor of 0.5. Much simpler! Although try compiling and running the program, zooming in, and then resizing the window. See that the zoom level resets? Well it doesn’t actually, `zoomLevel` remains the same and it’s only the view that changes. This is obviously bad as `zoomLevel` stops being in sync with the actual zoom! If you try panning again you’ll see how bad this is. We could fix this by just setting `zoomLevel = 1.0f` when the player resizes the view, but it’s better to match the view with the `zoomLevel` instead of the other way around (it prevents suddening zoom reset, which looks weird). The `zoom` call is the new bit!

1. ```/* Resize the window */ ```
2. ```case sf::Event::Resized: ```
3. ```{ ```
4. ```    gameView.setSize(event.size.width, event.size.height); ```
5. `    gameView.zoom(zoomLevel);`

In the next tutorial we will add the ability to select tiles ready for bulldozing or building.

Source code for this section

Author: Daniel Mansfield