Levende rutenett#
Hvordan i all verden kan noe så komplekst som liv oppstå ut fra forholdsvis enkle regler og lover fra kjemi og fysikk?
Hvorfor i all verden snakker jeg om dette i et kapittel for faget Informasjonsteknologi 2?
Alt vil bli klart, kjære leser, når vi tar et dypdykk i Conway’s Game of Life…
Game of Life 🦠#
Game of Life er et sett med regler for et rutenett som ble utviklet av John Horton Conway på 70-tallet. Hver rute i rutenettet anses som en celle, og hver celle kan være enten levende eller død. Reglene er som følger:
Naboer |
Oppdatering |
---|---|
Færre enn to levende naboer |
Dør |
To eller tre levende naboer |
Fortsetter å leve |
Flere enn tre levende naboer |
Dør |
Død, men med akkurat tre levende naboer |
Kommer til live |
Oppgave: Lag Game of Life 🦠
Lag et objektorientert Game of Life med grensesnitt i PyGame.
Bruk klassen Simulasjon
for å holde styr på hele rutenettet/spillet og Celle
for å holde styr på hver individuelle celle.
Du kan bestemme hvordan du håndterer kantene. En måte er ved å bestemme at alle cellene utenfor kanten er døde. En annen måte er ved å gjøre wrap-around, hvor naboene til cellene på høyre kant blir cellene på venstre kant osv…
Extra: Utvid programmet til å starte med at alle cellene er døde. Ta mouse-input fra brukeren som registrerer på hvilken celle det klikkes. Klikker man på en celle så dør den eller kommer til live.
Løsningsforslag: Game of Life 🦠
Her er et enkelt løsningsforslag for Game of Life med utvidelsen med tegning.
import pygame, random
"""
En simulasjon med reglene fra Game of Life.
- Bruk mellomrom for å pause/starte simulasjonen og klikk rutene for å tegne.
- Bruk knappen "r" for å restarte simulasjonen.
Det finnes mange mulige forbedringer, men dettr er bare et forslag. Slå deg løs!
"""
class Simulasjon:
def __init__(self):
# Setter opp pygame
pygame.init()
self.clock = pygame.time.Clock()
self.running = True
# Setter opp skjermen
self.bredde, self.høyde = 30, 30 # (n x n) rutenett
self.skalering = 20 # Hvor store rutene skal være
self.skjerm = pygame.display.set_mode((self.bredde * self.skalering, self.høyde * self.skalering))
self.skjerm.fill("white")
# Setter opp rader med celler og tegner de
self.rader = [[Celle(x, y, simulasjon = self) for x in range(self.bredde)] for y in range(self.høyde)]
for rad in self.rader:
for celle in rad:
celle.tegn()
# Starter med pause
self.pause = True
pygame.display.set_caption("Game of Life (Pause)")
def main_loop(self):
self.update()
self.render()
def update(self):
# Holder styr på hvilke celler som skal tegnes etter update.
self.skal_tegnes = []
# Avslutter spillet
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
# Knapper
if event.type == pygame.KEYDOWN:
# Mellomrom bytter på pause-modus
if event.key == pygame.K_SPACE:
if self.pause:
self.pause = False
pygame.display.set_caption("Game of Life")
else:
self.pause = True
pygame.display.set_caption("Game of Life (Pause)")
# Knappen "r" starter simulasjonen om igjen
if event.key == pygame.K_r:
self.__init__()
# Oppdaterer musepekeren
if event.type == pygame.MOUSEBUTTONDOWN:
mouse_pos = pygame.mouse.get_pos()
# Henter koordinater i rutenettet
x, y = mouse_pos[0] // self.skalering, mouse_pos[1] // self.skalering
# Henter og oppdaterer cellen
celle = self.rader[y][x]
if not celle.levende:
celle.levende = True
self.skal_tegnes.append(celle)
# Oppdaterer celler
if not self.pause:
for rad in self.rader:
for celle in rad:
# Henter antall naboer
antall_naboer = celle.hent_antall_naboer()
# Levende celle
if celle.levende:
# Dør (for få naboer)
if antall_naboer < 2:
celle.levende = False
self.skal_tegnes.append(celle)
# Dør (for mange naboer)
elif antall_naboer > 3:
celle.levende = False
self.skal_tegnes.append(celle)
# Død celle
else:
# Kommer til live
if antall_naboer == 3:
celle.levende = True
self.skal_tegnes.append(celle)
# Tikker klokken videre
self.clock.tick(60)
def render(self):
# Tegner celler som skal oppdateres
for celle in self.skal_tegnes:
celle.tegn()
self.skal_tegnes = [] # Tømmer listen etter alle er tegnet
# Tegner skjermen på skjermen
pygame.display.flip()
class Celle:
def __init__(self, x, y, simulasjon):
self.x = x
self.y = y
self.simulasjon = simulasjon
self.levende = False
def hent_antall_naboer(self):
"""
På denne måten kan vi sjekke alle naboene
[-1,-1] [0,-1] [1,-1]
[-1, 0] [0, 0] [1, 0]
[-1, 1] [0, 1] [1, 1]
"""
antall_naboer = 0
for retning in [[-1, -1], [0, -1], [1, -1], [-1, 0], [1, 0], [-1, 1], [0, 1], [1, 1]]:
# Ignorerer kantene ved å sjekke bare de som er innenfor simulasjonen
if self.x + retning[0] >= 0 and self.x + retning[0] < self.simulasjon.bredde:
if self.y + retning[1] >= 0 and self.y + retning[1] < self.simulasjon.høyde:
nabo = self.simulasjon.rader[self.y + retning[1]][self.x + retning[0]]
if nabo.levende:
antall_naboer += 1
return antall_naboer
def tegn(self):
if self.levende:
pygame.draw.rect(self.simulasjon.skjerm, (0, 0, 0), (self.x * self.simulasjon.skalering, self.y * self.simulasjon.skalering, self.simulasjon.skalering, self.simulasjon.skalering), border_radius=2)
else:
pygame.draw.rect(self.simulasjon.skjerm, (255, 255, 255), (self.x * self.simulasjon.skalering, self.y * self.simulasjon.skalering, self.simulasjon.skalering, self.simulasjon.skalering), border_radius=2)
simulasjon = Simulasjon()
while simulasjon.running:
simulasjon.main_loop()
Det er virkelig fascinerende hvordan det oppstår så mange kule «livsformer» og mønstre i så enkle regler. Jeg anbefaler å lese litt videre på Wikipedia (lenke) om du er interessert.
Simulasjoner hvor man bestemmer noen enkle regler for naboer til celler kalles for Cellular Automata på engelsk. I oppgavene under får du enda flere av disse å leke deg med.
Oppgave: Langton’s Ant 🐜
Langton’s Ant er en annen kjent Cellular Automaton.
Reglene er som følger:
En maur starter i et rutenett med en posisjon og en retning.
Hvis mauren er på en hvit rute, snur den
90
grader med klokken, bytter fargen på ruten den står på og beveger seg ett skritt frem.Hvis mauren er på en sort rute, snur den
90
grader mot klokken, bytter fargen på ruten den står på og beveger seg ett skritt frem.
Lag Langton’s Ant. Du må bruke objektorientert programmering med klasser og objekter. Hvordan du håndterer kanten på rutenettet er opp til deg selv.
Utforsk: Plasser flere maur på skjermen på en gang eller utforsk andre regler for mauren.
Løsningsforslag
import pygame, random
"""
En simulasjon av Langton's Ant.
Noen mulige utvidelser:
- Tegn mauren.
- Lag flere maur på en gang.
- Bytt på reglene.
- Utforsk med farger.
"""
class LangtonsMaur:
def __init__(self):
pygame.init()
self.clock = pygame.time.Clock()
self.running = True
self.size, self.dim = 20, (50, 50)
self.screen = pygame.display.set_mode((self.size * self.dim[0], self.size* self.dim[1]))
self.rutenett = [[0 for x in range(self.dim[0])] for y in range(self.dim[1])]
self.screen.fill("white")
self.maur = [Maur(random.randint(0, self.dim[0]), random.randint(0, self.dim[1]), (0, 1), langtonsmaur=self)]
def main_loop(self):
self.handle_events()
self.update()
self.render()
def handle_events(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
def update(self):
for maur in self.maur:
maur.update(self.screen)
self.clock.tick()
def render(self):
# Bytt farge der mauren står
"""for row in range(len(self.rutenett)):
for col in range(len(self.rutenett[0])):
if self.rutenett[row][col] == 1:
pygame.draw.rect(self.screen, (0, 0, 0), ((col * self.size, row * self.size), (self.size, self.size)))
else:
pygame.draw.rect(self.screen, (255, 255, 255), ((col * self.size, row * self.size), (self.size, self.size)))"""
pygame.display.flip()
class Maur:
def __init__(self, x : int, y : int, retning : tuple, langtonsmaur : LangtonsMaur):
self.x = x
self.y = y
self.retning = retning
self.langtonsmaur = langtonsmaur
self.rutenett = langtonsmaur.rutenett
self.retninger = [(0, 1), (1, 0), (0, -1), (-1, 0)]
def roter(self, med_klokken : bool):
# Rotér med klokken. Flytt fremover i retningslisten.
if med_klokken:
i = self.retninger.index(self.retning)
if i + 1 > len(self.retninger) - 1:
i = 0
else:
i += 1
# Rotér mot klokken. Bakover i retningslisten.
else:
i = self.retninger.index(self.retning)
if i - 1 < 0:
i = len(self.retninger) - 1
else:
i -= 1
self.retning = self.retninger[i]
def gå(self):
self.x = (self.x + self.retning[0]) % self.langtonsmaur.dim[0]
self.y = (self.y + self.retning[1]) % self.langtonsmaur.dim[1]
def update(self, display : pygame.Surface):
size = self.langtonsmaur.size
# Står på hvit
if self.rutenett[self.y][self.x] == 0:
self.rutenett[self.y][self.x] = 1
pygame.draw.rect(display, (0, 0, 0), ((self.x * size, self.y* size), (size, size)))
self.roter(med_klokken = True)
self.gå()
# Står på sort
else:
self.rutenett[self.y][self.x] = 0
pygame.draw.rect(display, (255, 255, 255), ((self.x * size, self.y * size), (size, size)))
self.roter(med_klokken = False)
self.gå()
spill = LangtonsMaur()
while spill.running:
spill.main_loop()
Oppgave: Skogbrann 🌲🔥 (Eksamen Informasjonsteknologi 2 V25)
Til eksamen V25 ble denne formuleringen gitt for en simulasjon av skogbranner.
Ved oppstart består simuleringen av et tomt rutenett.
Hver celle i rutenettet kan bli et tre, og sannsynligheten for at en tom celle blir et tre, kan ved oppstart f.eks. være 0,3 %.
Etter som det vokser fram trær i skogen, kan et lynnedslag slå ned i et tre og starte en skogbrann. Sannsynligheten for at et tre blir truffet av lyn, kan f.eks. settes til 0,03 %.
Om lynet slår ned i et tre, begynner det å brenne, og skogbrannen sprer seg etter følgende regler:
Ved lynnedslag er det et tre som begynner å brenne.
Ved neste trinn i simuleringen har treet lynet slo ned i, brent opp og blitt en blank celle. Trærne som sto ved siden av treet der lynet slo ned, brenner.
Simuleringen fortsetter på samme vis ved at alle trær som står ved et tre som nettopp brant opp, nå brenner.
Skogen vokser ikke mens skogbrannen pågår. Det betyr at mens det pågår en brann, representerer hvert trinn i simuleringen veldig mye mindre tid enn mens skogen vokser.
Lag denne simulasjonen visuelt med PyGame. Bruk hensiktsmessig objektorientert programmering.
Løsningsforslag
"""
Programmet bruker pakken PyGame. Installer ved å skrive
>pip install pygame
i terminalvinduet.
"""
import pygame, random
class Spill:
def __init__(self):
pygame.init()
self.clock = pygame.time.Clock()
self.running = True
self.bredde, self.høyde = 50, 50
self.skalering = 10
self.screen = pygame.display.set_mode((self.bredde * self.skalering, self.høyde * self.skalering))
self.ruter = [[Rute(x, y) for x in range(self.bredde)] for y in range(self.høyde)]
self.skogbrann = False # Holder styr på om det er skogbrann eller ikke
def main_loop(self):
self.update()
self.render()
def update(self):
# Avslutter spillet
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
# Skogen vokser
if not self.skogbrann:
# Lag trær
for x in range(self.bredde):
for y in range(self.høyde):
rute = self.ruter[y][x]
if not rute.tre:
if random.randint(1, 1000) <= 3:
rute.tre = True
# Start skogbrann
if random.randint(1, 1000) <= 3: # 0.03% sjanse for skogbrann per tid
self.skogbrann = True
ruter_med_trær = []
for x in range(self.bredde):
for y in range(self.høyde):
rute = self.ruter[y][x]
if rute.tre:
ruter_med_trær.append(rute)
if len(ruter_med_trær) > 0:
random.choice(ruter_med_trær).brenner = True
# Skogen brenner
else:
# Liste som holder styr på de som skal begynne å brenne
branner = []
pågående_brann = False
for x in range(self.bredde):
for y in range(self.høyde):
rute = self.ruter[y][x]
if rute.brenner:
pågående_brann = True
rute.tre = False
rute.brenner = False
# Naboer
for retning in [(0, 1), (1, 1), (1, 0), (1, -1), (0, -1), (-1, -1), (-1, 0), (-1, 1)]:
if rute.x + retning[0] < self.bredde and rute.x > 0:
if rute.y + retning[1] < self.høyde and rute.y > 0:
nabo = self.ruter[rute.y + retning[1]][rute.x + retning[0]]
if nabo.tre:
branner.append(nabo)
# Start branner i nabotrær
for rute in branner:
rute.brenner = True
# Stopper skogbrannen hvis ingenting brenner
if not pågående_brann:
self.skogbrann = False
# Tikker klokken videre
self.clock.tick(30)
def render(self):
self.screen.fill("white")
# Tegner trær
for x in range(self.bredde):
for y in range(self.høyde):
rute = self.ruter[y][x]
if rute.tre:
pygame.draw.rect(self.screen, (0, 100, 0), ((rute.x * self.skalering, rute.y * self.skalering), (self.skalering, self.skalering)))
elif rute.brenner:
pygame.draw.rect(self.screen, (200, 100, 100), ((rute.x * self.skalering, rute.y * self.skalering), (self.skalering, self.skalering)))
pygame.display.flip()
class Rute:
def __init__(self, x, y):
self.x = x
self.y = y
self.tre = False
self.brenner = False
spill = Spill()
while spill.running:
spill.main_loop()