Małe projekty w Pythonie: pożar lasu

Niniejszy artykuł pochodzi z książki pt. „Wielka księga małych projektów w Pythonie. 81 łatwych praktycznych programów" Al Sweigart (Helion 2022).
Pożar lasu
Ta symulacja pokazuje las, w którym drzewa ciągle rosną, a potem płoną. W każdym kroku symulacji istnieje 1 procent prawdopodobieństwa, że na pustym polu urośnie drzewo, i 1 procent, że drzewo zostanie trafione przez błyskawicę i spłonie.
Ogień rozprzestrzeni się na sąsiednie drzewa, więc w przypadku gęstego lasu istnieje większe prawdopodobieństwo, że wybuchnie większy pożar, niż w przypadku lasu, gdzie drzewa rosną w większych odstępach od siebie.
Inspiracją tej symulacji był program Nicky’ego Case’a, opublikowany na stronie https://ncase.me/simulating/ model/.
Rysunek: Symulacja pożaru lasu, w której zielone litery A to drzewa, a czerwone litery W to płomienie
Jak to działa?
Ta symulacja jest przykładem emergencji — interakcji między prostymi częściami systemu, w wyniku której tworzą się bardziej złożone wzorce. Na pustych miejscach rosną drzewa, pioruny powodują, że drzewa zaczynają płonąć i ogień zamienia drzewa z powrotem w puste miejsce, po czym rozprzestrzenia się na sąsiednie drzewa.
Przez dostosowanie prędkości wzrostu i częstotliwości uderzeń pioruna możesz sprawić, by las pokazywał inne zjawisko. Na przykład małe prawdopodobieństwo uderzenia pioruna i wysokie tempo wzrostu drzew powodują duże, ciągłe pożary lasu, gdyż drzewa znajdują się blisko siebie, a na miejscu spalonych drzew szybko pojawiają się nowe.
Niskie tempo wzrostu i wysokie prawdopodobieństwo uderzenia pioruna skutkują kilkoma małymi pożarami, które szybko gasną, ponieważ w pobliżu nie ma innych drzew. Nie programujemy takiego zachowania, to wynika naturalnie ze stworzonego przez nas systemu.
001: """Symulacja pożaru lasu, autor: Al Sweigart, [email protected]
002: Symulacja pożaru rozprzestrzeniającego się w lesie. Naciśnij Ctrl+C, by zatrzymać program.
003: Zainspirowana programem Nicky'ego Case'a ze strony http://ncase.me/simulating/model/.
004: Kod pobrany ze strony https://ftp.helion.pl/przyklady/wiksma.zip.
005: Etykiety: krótki, bext, symulacja"""
006:
007: import random, sys, time
008:
009: try:
010: import bext
011: except ImportError:
012: print('Ten program wymaga modułu bext,')
013: print('który możesz zainstalować według instrukcji ze strony')
014: print('https://pypi.org/project/Bext/.')
015: sys.exit()
016:
017: # Deklaracja stałych:
018: WIDTH = 79
019: HEIGHT = 22
020:
021: TREE = 'A'
022: FIRE = 'W'
023: EMPTY = ' '
024:
025: # (!) Spróbuj zmienić te ustawienia na dowolną wartość między 0.0 a 1.0:
026: INITIAL_TREE_DENSITY = 0.20 # Liczba drzew na początku programu.
027: GROW_CHANCE = 0.01 # Szansa na wyrośnięcie drzewa w pustym miejscu.
028: FIRE_CHANCE = 0.01 # Szansa, że drzewo zostanie uderzone przez piorun i spłonie.
029:
030: # (!) Spróbuj ustawić długość pauzy na 1.0 lub 0.0:
031: PAUSE_LENGTH = 0.5
032:
033:
034: def main():
035: forest = createNewForest()
036: bext.clear()
037:
038: while True: # Główna pętla programu.
039: displayForest(forest)
040:
041: # Uruchom pojedynczy krok symulacji:
042: nextForest = {'width': forest['width'],
043: 'height': forest['height']}
044:
045: for x in range(forest['width']):
046: for y in range(forest['height']):
047: if (x, y) in nextForest:
048: # Jeśli ustawiłeś już nextForest[(x, y)]
049: # w poprzednim obiegu pętli, to nic nie rób:
050: continue
051:
052: if ((forest[(x, y)] == EMPTY)
053: and (random.random() <= GROW_CHANCE)):
054: # Zasadź drzewo w pustym miejscu:
055: nextForest[(x, y)] = TREE
056: elif ((forest[(x, y)] == TREE)
057: and (random.random() <= FIRE_CHANCE)):
058: # Piorun podpala drzewo:
059: nextForest[(x, y)] = FIRE
060: elif forest[(x, y)] == FIRE:
061: # To drzewo cały czas płonie.
062: # Przejdź przez wszystkie sąsiednie miejsca:
063: for ix in range(-1, 2):
064: for iy in range(-1, 2):
065: # Pożar rozprzestrzenia się na sąsiednie drzewa:
066: if forest.get((x + ix, y + iy)) == TREE:
067: nextForest[(x + ix, y + iy)] = FIRE
068: # Drzewo spłonęło, więc je usuń:
069: nextForest[(x, y)] = EMPTY
070: else:
071: # Po prostu skopiuj istniejący obiekt:
072: nextForest[(x, y)] = forest[(x, y)]
073: forest = nextForest
074:
075: time.sleep(PAUSE_LENGTH)
076:
077:
078: def createNewForest():
079: """Zwraca słownik dla nowego lasu."""
080: forest = {'width': WIDTH, 'height': HEIGHT}
081: for x in range(WIDTH):
082: for y in range(HEIGHT):
083: if (random.random() * 100) <= INITIAL_TREE_DENSITY:
084: forest[(x, y)] = TREE # Zacznij od drzewa.
085: else:
086: forest[(x, y)] = EMPTY # Zacznij od pustego miejsca.
087: return forest
088:
089:
090: def displayForest(forest):
091: """Wyświetl las na ekranie."""
092: bext.goto(0, 0)
093: for y in range(forest['height']):
094: for x in range(forest['width']):
095: if forest[(x, y)] == TREE:
096: bext.fg('green')
097: print(TREE, end='')
098: elif forest[(x, y)] == FIRE:
099: bext.fg('red')
100: print(FIRE, end='')
101: elif forest[(x, y)] == EMPTY:
102: print(EMPTY, end='')
103: print()
104: bext.fg('reset') # Użyj domyślnego koloru czcionki.
105: print('Szansa na drzewo: {}% '.format(GROW_CHANCE * 100), end='')
106: print('Szansa na piorun: {}% '.format(FIRE_CHANCE * 100), end='')
107: print('Naciśnij Ctrl+C, by zatrzymać program.')
108:
109:
110: # Jeśli program został uruchomiony (a nie zaimportowany), rozpocznij grę:
111: if __name__ == '__main__':
112: try:
113: main()
114: except KeyboardInterrupt:
115: sys.exit() # Po naciśnięciu Ctrl+C zakończ program.
Po przepisaniu kodu źródłowego i uruchomieniu go kilka razy spróbuj poeksperymentować. Komentarze opatrzone znakiem (!) są sugestiami zmian, jakie możesz wprowadzić.
Możesz również samodzielnie postarać się wykonać następujące zadania:
- Dodaj losowo tworzone jeziora i rzeki, które działają jak blokady dla ognia.
- Dodaj prawdopodobieństwo, że drzewo zapłonie od sąsiedniego.
- Dodaj różnego rodzaju drzewa z różną predyspozycją do zapalania się.
- Dodaj różne stadia palenia się drzew, tak by symulacja pożaru miała kilka kroków, zanim drzewo spłonie.
Odkrywanie programu
Postaraj się znaleźć odpowiedzi na poniżej podane pytania. Spróbuj wprowadzić zmiany w kodzie i uruchomić ponownie program, by zobaczyć, jaki efekt to przyniosło.
- Co się stanie, gdy zamienisz
bext.fg('green')
w linii 96. nabext.fg('random')
? - Co się stanie, gdy zamienisz
EMPTY = ' '
w linii 23. naEMPTY = '.'
? - Co się stanie, gdy zamienisz
forest.get((x + ix, y + iy)) == TREE
w linii 66. naforest.get((x + ix, y + iy)) == EMPTY
? - Co się stanie, gdy zamienisz
nextForest[(x, y)] = EMPTY
w linii 69. nanextForest[(x, y)] = FIRE
? - Co się stanie, gdy zamienisz
forest[(x, y)] = EMPTY
w linii 86. naforest[(x, y)] = TREE
?
Artykuł stanowi fragment książki pt. „Wielka księga małych projektów w Pythonie. 81 łatwych praktycznych programów” Ala Sweigarta (Helion 2022)