Implement the Drossel and Schwabl definition of the forest-fire model.
It is basically a 2D cellular automaton where each cell can be in three distinct states: empty, tree and burning, and evolves according to the following rules:
- A burning cell turns into an empty cell,
- A tree will burn if at least one heighbor is burning,
- A tree ignites with probability
feven if no neighbor is burning,
- An empty space fills with a tree with probability
Neighborhood is the Moore neighborhood; boundary conditions are so that on the boundary the cells are always empty ("fixed" boundary condition).
At the beginning, populate the lattice with empty and tree cells according to a specific probability (e.g. a cell has the probability 0.5 to be a tree). Then, let the system evolve.
We solve this task graphically with help of the
canvas.l library. In the end, it will look like this:
- "cell has tree" probability: 50 %
- Ignition probability
f: 0.01 %
- "Empty space fills with tree" probability
p: 1 %
- Number of cells: 480x360 = 172800
simul.l library file
In cellular automata, the Moore neighborhood is defined on a two-dimensional square lattice and is composed of a central cell and the eight cells that surround it.
We can use the
simul.l library for this purpose, which defines the functions
north in the namespace
Let's load the libraries and functions:
(load "@lib/http.l" "@lib/xhtml.l" "@lib/form.l" "@lib/canvas.l" "@lib/simul.l") (import simul~grid simul~west simul~east simul~south simul~north)
First of all, we define the grid size
drawCanvas and a function called
(setq *CanvasDX 480 *CanvasDY 360) (de drawCanvas (Id Dly) ] (de forest () (and (app) *Port% (redirect (baseHRef) *SesId *Url)) (action (html 0 "Forest Fire" "@lib.css" NIL (form NIL ] (de go() (server 8080 "!forest"))
Now we can define the grid
*FireGrid by using the
grid function from
simul.l. We can return a random
NIL value by calling
(for Col (setq *FireGrid (grid *CanvasDX *CanvasDY)) (for This Col (=: tree (rand T)) ) )
This approach uses the convenient fact that the canvas "ID" can actually be an object. Let's pass the object
<canvas> and re-draw it every 40 milliseconds:
We define that the
*FireGrid object has three attributes:
tree, which is
Tif the tree is intact,
burn, which is
Tif the tree is burning,
next, which shows the status in the next iteration.
Now we define the
drawCanvas function. In the first step, we iterate over every cell in
*FireGrid and set the
next attribute according to the following rules:
- The tree is burning:
- The tree is intact:
burn, if a neighbor tree is burning.
burnwith a probability of 0.1 % (ignition)
- The cell object s
treewith a probability of 1 %, otherwise
(for Col *FireGrid (for This Col (=: next (cond ((: burn) NIL) ((: tree) (if (or (find # Neighbor burning? '((Dir) (get (Dir This) 'burn)) (quote west east south north ((X) (south (west X))) ((X) (north (west X))) ((X) (south (east X))) ((X) (north (east X))) ) ) (=0 (rand 0 9999)) ) 'burn 'tree ) ) (T (and (=0 (rand 0 99)) 'tree)) ) ) ) )
Then we iterate over
*FireGrid again, this time in order to set each cell to the value stored in
(for Col *FireGrid (for This Col (if (: next) (put This @ T) (=: burn) (=: tree) ) ) )
Drawing the canvas
Now that the new state of
*FireGrid is defined, let's draw it:
- For each
treecell, we draw a green dot.
- For each
burncell, we draw a red dot.
We do this with the
ctx.fillRect function. This is more efficient than defining each pixel in a loop.
csDrawDots two times: First for green pixels of size 1 (in x and y direction) to represent the trees, and then red pixels of size 3 to represent the fire.
(make (csClearRect 0 0 *CanvasDX *CanvasDY) (csFillStyle "green") (csDrawDots 1 1 (make (for (X . Col) (for (Y . This) Col (and (: tree) (link X Y)) ) ) ) ) (csFillStyle "red") (csDrawDots 3 3 (make (for (X . Col) (for (Y . This) Col (and (: burn) (link X Y)) ) ) ) ) )
You can find the source code of the finished example here.