34. Level 3 - Forest Fires#

In this topic, you will demonstrate your ability to use Python to perform: tests, while and for loops, list manipulations and 2d list.

To do this, you will create a simple automaton simulating the growth of a forest subject to wildfires.

In a few words, an automaton is a grid made up of cells. Each cell can take on different values. A cell will evolve over time based on the value it contains and the values in neighboring cells.

Here are the rules we will establish for the evolution of a cell:

  • if a cell is empty, a tree will grow with a probability of 3 out of 1000;

  • if a cell contains a tree,

    • it will burn with a probability of 1 out of 10,000;

    • it will definitely burn if it is adjacent to a cell that is burning;

  • if a cell is burning, it will become empty again.

The rest of the topic is organized in such a way that you can develop elementary functions, which can be tested independently. These functions, when combined, will form a complete program.

34.1. Creating the World#

34.1.1. Initialization#

Define two global variables WIDTH and HEIGHT that will be constants and represent the dimensions of our world. Initially, we will set them to small values for our tests.

Create a function init_world that generates and returns a list of lists where each cell contains an empty string.

WIDTH = 5
HEIGHT = 3

def init_world():
    # TO COMPLETE

world = init_world()
world[1][1] = 'T' 
print(world)

By completing the code above, you should get the following output:

[[' ', ' ', ' ', ' ', ' '], [' ', 'T', ' ', ' ', ' '], [' ', ' ', ' ', ' ', ' ']]

34.1.2. Displaying the world#

Add to our program a function intended to display the world in a rudimentary form. Later, we will improve this display.

Write a function print_world(world) that takes as a parameter a list of lists representing an automaton and displays it in the terminal, adding borders.

def print_world(world):
    # TO COMPLETE

world = init_world()
world[1][1] = 'T'
print_world(world)

You can copy/paste special characters : “╔”, “╗”, “╚”, “╝”, “═”, “║”.

╔═════╗
║     ║
║ T   ║
║     ║
╚═════╝

34.1.3. Planting trees#

For our simulation to work correctly, it is necessary to initialize it by planting a few trees at the start.

Write a function plant(world, nb_trees) that takes a world as a parameter and randomly adds nb_trees trees to it.

Note:

  • Your function should not return anything. It modifies the world passed as a parameter.

  • You should only add a tree if the cell is empty.

from random import randint
def plant(world, nb_trees):
    # TO COMPLETE

world = init_world()
plant(world, 7)
print_world(world)

34.2. Evolution#

We now move on to the evolution of the cells that make up the world. To make our work easier, we will first create two utility functions.

34.2.1. Inside the world#

Create a function is_in_world(l, c) that takes a coordinate as a parameter and returns True if it is within the world, meaning the coordinates (l, c) are between (0, 0) and (HEIGHT-1, WIDTH-1).

def is_in_world(l, c):
    # TO COMPLETE

print(is_in_world(0, 0)) # True
print(is_in_world(0, WIDTH)) # False
print(is_in_world(HEIGHT, 1)) # False
print(is_in_world(HEIGHT-1, 1)) # True

34.2.2. Neighboring Fire#

Create a function close_to_fire(world, l, c) that returns True if the cell at coordinates (l, c) is on fire or is neighboring a cell on fire in the world. A cell is on fire if it contains the value "F".

Hint: For this function, it is necessary to pay attention to the edges of our world. A cell in the center of the world has eight neighbors, but for cells at the boundary, there may be fewer neighbors. A cell in a corner, for example, only has 3 neighbors.

We recommend using a generic approach for all cells while utilizing the is_in_world function. Here is the pseudo-code for this approach.

Close_to_fire(world, l, c):
    for i from -1 (included) to 2 (excluded):
        for j from -1 (included) to 2 (excluded):
            if (l+i, c+j) is in the world:
                if cell (l+i, c+j) is in fire:
                    return True
    return False

Among the possible bugs, be careful with negative index values, which refer to list numbering starting from the end.

Test your function, for example, with the following code.

def close_to_fire(world, l, c):
    # TO COMPLETE

world = init_world()
world[1][1] = 'F'
print_world(world)
print(close_to_fire(world, 0, 0)) # True
print(close_to_fire(world, 1, 3)) # False

34.2.3. Cell Evolution#

Write a function cell_evolution(world, l, c) that takes as parameters a world and the coordinates l, c of a cell and returns the value of the cell at the next step of the simulation.

def cell_evolution(world, l, c):
    # TO COMPLETE

print(cell_evolution(world, 1, 1)) # Expect ' '
print(cell_evolution(world, 0, 1)) # Expect 'F'

34.2.4. World Evolution#

Write a function world_evolution(world) that takes a world as a parameter and returns the world at the next step.

def world_evolution(world):
    # TO COMPLETE


world = init_world()
world[1][1] = 'F'
world[0][1] = 'T'
print_world(world)
world = world_evolution(world)
print_world(world)
world = world_evolution(world)
print_world(world)

Expected output:

╔═════╗
║ T   ║
║ F   ║
║     ║
╚═════╝
╔═════╗
║ F   ║
║     ║
║     ║
╚═════╝
╔═════╗
║     ║
║     ║
║     ║
╚═════╝

34.3. The World Moves#

Now use the function below to observe your world evolve. Now that the program is complete and the tests have passed, we recommend changing the dimensions of the world.

import time

WIDTH = 50
HIEGHT = 30
def main():
    delta = 0.1  # time between each step
    duration = 60  # duration of the simulation
    steps = int(duration / delta)
    world = init_world()
    plant(world, 10)
    print(print_world(world))
    for _ in range(steps):
        time.sleep(0.1)
        world = world_evolution(world)
        print_world(world)


main()

34.4. Advanced Features#

Congratulations, you have reached the end of the guided part of this topic. You are now free to complete the project with any extension that you may imagine. This is an opportunity to show that you are now confident and can propose and develop new ideas independently.

Here are a few ideas:

  • There is a special ASCII character in Python that defines the color of the text displayed on a terminal. You could use it to change the color of the cell that is burning different possible colors and adapt the display with more relevant ASCII symbols.

  • You could add cells of different types and specific rules.