Python esimerkkejä > Python esimerkkejä ()
Tutustumme aluksi muutamaan pieneen esimerkkiohjelmaan. Pikaisella vilkaisulla ohjelmoinnin aloittelija ei saane selvää, miten ne toimivat, mutta kohtuullisella ponnistelulla ja kokeilulla ne opettavat paljon ohjelmoinnista.
Jos olet kiinnostunut matematiikasta ja tiedät, mitä ovat fibonnacin luvut sinua saattaa hetken säväyttää seuraava ohjelma. Ellet tunne fibonnacin lukuja, harjoitus tuntunee tyhmältä, mutta tee se silti.
# Määritellään funktio fib(n), jolla on yksi argumentti, n def fib(n): if n == 0: # Jos n on yhtäsuuri kuin nolla, return 0 # funktio saa arvon 0 elif n == 1: # muuten, jos n on yhtäsuuri kuin 1 return 1 # funktio saa arvon 1 else: # muissa tapauksissa funktio saa arvokseen return fib(n-1) + fib(n-2) # kahden edeltävän fibonnacin luvun summan # Pääohjelma print(fib(5))
Python-kielisestä funktiosta fib(n) saattaa tunnistaa fibonnacin luvun määritelmän, vaikka merkintätapa onkin eri kuin matematiikan kirjoissa.
Käynnistä spyder, kopio ylläoleva sen editointi-ikkunaan allaolevan kuvan mukaisesti ja tallenna tiedostoon fibonnacci.py. Suorita ohjelma painamalla F5. Samall F5-painallus tuo ohjelmamodulin funktion järjestelmän tietoon niin, että voit kutsua sitä myös konsolilta — kirjoittamalla esimerkiksi fib(12) kuvan mukaisesti spyder-ohjelmointiympäristön oikeassa alalaidassa olevan konsoli-ikkunan komentoriville. (Ilman F5-painallusta saat funktion konsolilta kutsuttavaksi kirjoittamalla konsolille from fibonnacci import fib.)
Yleensä tietokone suorittaa kirjoittamasi ohjelman silmänräpäyksessä, mutta jos innostuit kokeilemaan jonkin ison fibonnacin luvun laskemista, kyllästyit kenties odottelemaan vastausta. Spyderin konsoli-ikkunan oikeassa yläkulmassa on punainen neliö, josta ohjelman voi pysäyttää, mikäli se tuntuu jääneen "jumiin".
Yleensä ohjelma jää "jumiin" ohjelmointivirheen takia, mutta nyt "jumittuminen" johtuu siitä, että fibonnacin luvun määrittäminen on monimutkainen laskutehtävä. 35:tä suuremmat fibonnacci luvut osoittautuivat minun koneellani niin hitaiksi laskea, etten jaksanut odottaa tulosta.
Hakukoneile "fibonnacci complexity" jos sinua jäi mietityttämään, miksi noin pienen muutaman rivin funktion arvon laskeminen voi olla liikaa tehokkaalle tietokoneelle.
Ei kannata pelästyä, vaikka seuraavat ohjelmanpätkät näyttävät vaikeaselkoisilta. Ohjelmointia tuntemattomalle ohjelmakoodi näyttänee täysin käsittämättömältä. Myös ohjelmointia tuntevan on usein vaikea saada selvää toisen kirjoittamasta ohjelmasta. Vasta harjaantunut ohjelmoija, joka tuntee käytettävän ohjelmointikielen, voi lukea jonkun muun tekemää ohjelmaa ilman suurempia vaikeuksia.
Python--ohjelmaa lukiessa on hyvä tietää, että rivien sisennys määrää, mihin ylempään kokonaisuuteen rivi kuuluu. Aivan vasemmasta laidasta alkavat rivit kuuluvat ohjelman päätasolle. Yllä funktio fib(n) määriteltiin siis päätasolle, koska def fib(n): alkaa rivin alusta. Sitä seuraavat sisennetyt rivit kuuluvat funktion määrittelyyn — ne kertovat, mitä funktio tekee.
Seuraavaa esimerkkiä ei kannata yrittää ymmärtää yhdellä vilkaisulla. Lataa allaolevan ohjelman koodi kehitysympäristöön ja kokeile kuten yllä fibonnaccin lukuja laskevaa funktiota.
Mikäli et ole installoinut pylab-modulia, laita kommentiksi rivit, joissa viitataan pylabiin. Eli laita kyseisten rivien alkuun #-merkki. Voit myös yrittää installoida pylab-modulin komennolla pip install pylab tai jos käytät Python3:a komennollapip3 install pylab. Jos se aiheuttaa virheilmoituksia, jätä sikseen vähäksi aikaa. Emme tarvitse pylabia tästä eteenpäin.
# -*- coding: utf-8 -*- import pylab # lasketaan kokonaislukujen 1..k summa for-silmukan avulla # tulostetaan joka kierroksella välitulos # huom. Pythonin range(k) antaa luvut 0:sta (k-1):een. def summa(k): s = 0 for i in range(k+1): s = s+(i) print("i: ", i, "s: ", s) return s # lasketaan kokonaiskukujen 1..k rekursiivisella funktiolla. # Rekursiossa funktio kutsuu itseään, kunnes lopetusehto # (tässä tapauksessa k == 0) toteutuu. def summaR(k): print("summaR ", k) if k == 0: return 0 return k+summaR(k-1) # Esimerkkejä eri tavoista saada tulostettua jotain konsolille def mjonot(): s = "Tämä on merkkijono " m = 53 print(s) print("luku ja merkkijono ", m, str(m)) print("kaksi merkkijonoa yhdistetty + merkillä: ") print(s+str(m)) m = 120 n = 15 print(m+n) print(str(m)+str(n)) print(str(m)+' '+str(n)) # Esimerkki siitä, miten lukea konsolilta. # Input tuottaa merkkijonon, joten muutamme sen int()-funktiolla # kokonaisluvuksi def kysymys(): N = input("anna luku: ") print('luku on:', N, type(N)) S = summa(int(N)) print("lukujen 1..", N, " summa on: ", S) print("lukujen 1.."+str(N)+" summa on: "+str(S)) # piirretään funktion y = x**3 kuvaaja # Muodostetaan listat/taulukot xx ja yy toisiaan vastaavilla # x ja y arvoille. # loppu hoituu pylab-funktioilla # huom. Pythonin range(i,j) antaa luvut i:stä (j-1):een. def kuvaaja(): xx = [i for i in range(-10, 11)] yy = [i**3 for i in range(-10, 11)] print("xx:") print(xx) print("yy:") print(yy) pylab.plot(xx, yy) pylab.title('y = x**3') pylab.ylabel("x**3") pylab.xlabel("x") pylab.savefig('y_x3.png') pylab.show() # Huom. Merkkijoissa \n aiheuttaa rivinvaihdon print('\npääohjelma\n') print("summa(5):") s = summa(5) print(s) print("\nsummaR(5): ") s = summaR(5) print(s) print("\nmerkkijonot") mjonot() kysymys() print("\nkuvaaja:") kuvaaja() # voit kutsua funktiota konsolilta
Ohjelma kirjoittaa konsolille seuraavaa, kun painat F5:
pääohjelma summa(5): i: 0 s: 0 i: 1 s: 1 i: 2 s: 3 i: 3 s: 6 i: 4 s: 10 i: 5 s: 15 15 summaR(5): summaR 5 summaR 4 summaR 3 summaR 2 summaR 1 summaR 0 15 merkkijonot Tämä on merkkijono luku ja merkkijono 53 53 kaksi merkkijonoa yhdistetty + merkillä: Tämä on merkkijono 53 135 12015 120 15 anna luku: 4 luku on: 4 <class 'str'> i: 0 s: 0 i: 1 s: 1 i: 2 s: 3 i: 3 s: 6 i: 4 s: 10 lukujen 1.. 4 summa on: 10 lukujen 1..4 summa on: 10 kuvaaja: xx: [-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] yy: [-1000, -729, -512, -343, -216, -125, -64, -27, -8, -1, 0, 1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]
Ohjelman suorituksen lisäksi F5:näppäimen painaminen lataa ohjelman python järjestelmän työtilaan niin, että voit kutsua kutsua funktioita konsolilta. Esimerkiksi summa(3) laskee summan 0+1+2+3. Funktion koodin mukaisesti ensin asetetaan muuttujalle s arvo 0 ja sen jälkeen käydään foor-silmukassa läpi i:n arvot nollasta kolmeen ja joka kierroksella lisätään kukin i s:ään ja tulostetaan muuttujien arvot. Lopuksi funktio palauttaa s:n lopullisen arvon.
Ehkä ihmettelet for silmukassa käskyä range(k+1). Miksei range(k)? Kirjoita konsolin komentoriville range(3) niin saat listan [0,1,2]. Voit myös kirjoittaa range(1,3) niin saat listan [1,2]. Jostain syystä on nähty järkevästi, että range-käsky toimii noin.
Funtion summaR(k) näyttää, miten funktio voi kutsua itseään. Esimerkiksi lukujen yhdestä kolmeen summa on kolme plus lukujen yhdestä kahteen summa. Rekursiivisen funktion toimintaa voi olla vaikea hahmottaa, mutta yritä kuitenkin. Välitulostukset saattaavat auttaa.
Esimerkeissä tulostuksesta ja syötteiden lukemisesta konsolilta ei ole hirveästi ymmärrettävää. Ne esittelevät eri vaihtoehtoja. Esimerkkien komennot voi kopioida tarvittaessa omaan ohjelmaansa.
Lopuksi piirretään funktion
kuvaaja. Python-kielessä "x potenssiin kolme" merkitään x**3. Piirtämisessä käytetään pylab-nimisen modulin funktiota plot, jolle pitää antaa kaksi argumenttia — luettelo x:n arvoista ja luettelo niitä vastaavista y:n arvoista.
Seuraava — ei aivan yksinkertainen — esimerkki esittelee olio-ohjelmointia Pythonilla. Tämä opas olio-ohjelmointiin pythonilla kertoo lisää olio-ohjelmoinnista
Esimerkissä kauppiaat ostavat ja myyvät, mutta esimerkki ei kuitenkaan kuvasta todellista kaupankäyntiä. Siinä ei ole juuri muuta järkeä kuin, että siihen on upotettu olio-ohjelmoinnin perusteita.
# -*- coding: utf-8 -*- """ Created on Wed May 27 21:10:55 2015 @author: heikki """ # Määritellään, minkä tyyppisiä olioita ovat kauppiaat tässä ohjelmassa # Kullakin kauppiaalle on ominaisuudet varasto, kassa, tavaran yksikköhinta # ja nimi class kauppias(): def __init__(self, kpl, Eur, hinta, nimi): self.varasto = kpl self.kassa = Eur self.hinta = hinta self.nimi = nimi # Kauppiaaseen liittyy metodi report, joka kertoo kauppiaan liiketoiminnan # tilan def report(self): print(self.nimi, " Varasto:", self.varasto, "Kassa: ", self.kassa) # Kauppiaalla on metodit osta ja myy # kun myyn, varastoni pienenee ja kassaan tulee rahaa. # Lisäksi kerron ostajalle ostoksen hinnan def myy(self, kpl): self.varasto -= kpl self.kassa += kpl*self.hinta return self.hinta*kpl # Python-kielessä a = a+b ei ole yhtälö vaan tarkoittaa, että a:n uusi arvo on # a:n vanha arvo lisättynä b:llä # operaatio a += b on sama kuin a = a+b. Vastaavasti a -= b # Kun ostan myyjältä, tämä kertoo ostokseni hinnan # Kassani pienee ja varastoni kasvaa def osta(self, kpl, Myyja): maksu = Myyja.myy(kpl) self.kassa -= maksu self.varasto += kpl # Pääohjelma (ohjelman suoritus) alkaa tästä. # "Luodaan" kaksi kauppiasta, A ja B. (Kauppiaita voisi olla vaikka sata) # Kauppias A: Varastoa 100 kpl, kassassa 100 euroa, myy hintaan 2€/kpl A = kauppias(100, 100, 2, 'Jaska') # Kauppias B: Varastoa 50 kpl, kassassa 200 euroa, myy hintaan 1€/kpl B = kauppias(50, 200, 1, 'Kalle') # Esimerkki siitä, miten kutsutaan olion metodia print("\nAlkutilanteen raportoinnit") A.report() B.report() print("\nA ostaa B:ltä 20 kpl B:n tarjoamaan edulliseen hintaan") A.osta(20, B) A.report() B.report() print("\nB ostaa A:lta 10 kpl A:n pyytämään kalliimpaan hintaan") B.osta(10, A) A.report() B.report()
Ohjelma kirjoittaa konsolille seuraavaa, kun painat F5:
Alkutilanteen raportoinnit Jaska Varasto: 100 Kassa: 100 Kalle Varasto: 50 Kassa: 200 A ostaa B:ltä 20 kpl B:n tarjoamaan edulliseen hintaan Jaska Varasto: 120 Kassa: 80 Kalle Varasto: 30 Kassa: 220 B ostaa A:lta 10 kpl A:n pyytämään kalliimpaan hintaan Jaska Varasto: 110 Kassa: 100 Kalle Varasto: 40 Kassa: 200
Ohjelman toimintaa lienee vaikea ymmärtää, mutta yritä silti. Aloita kohdasta "pääohjelma alkaa tästä". Aluksi luodaan kaksi oliota A ja B, jotka ovat tyyppiä kauppias. Voit ajatella, että olio on määrämuotoinen arkistokortti — muistilappu, jollainen tehdään jokaisesta kauppiaasta. Ylempänä ohjelmakoodissa määrittelyn class kauppias: sisällä funktion _init_ määrittely kertoo, mitä "arkistokorttiin" kirjoitetaan: Varaston määrä; kassan suuruus; yksikköhinta, jolla kauppias myy tavaroitaan sekä kauppiaan nimi.
Käskyllä A.report() pyydämme kauppiasta A kertomaan yrityksensä tilanne. Funktio report() on määritelty luokan (class) kauppias sisällä. (Luokan sisällä määriteltyjä funktioita kutsutaan olio-ohjelmoinnissa metodeiksi.) Vastaavasti käskyllä B.report() pyydämme samat tiedot kauppiaalta B.
Ei liene helppo ymmärtää, miksi käytetään termejä class, luokka, metodi,– – ja siksi termeissä menee helposti sekaisin. Paras ottaa mallia yksinkertaisesta esimerkistä ja näin aluksi unohtaa termien alkuperän pähkäily.
Käskyllä A.osta(20,B) pyydämme A:ta ostamaan B:ltä 20 kappaletta. Metodit osta ja myy päivittävät varastojen ja kassojen tilanteet vastaamaan uutta tilannetta. Koeta selvittää, miten se tapahtuu.
Voit yrittää luoda vielä kolmannen kauppiaan ja lisätä ostotapahtumia. Jos onnistut, sinulla ei enää ole kovin paljoa opittavaa olio-ohjelmoinnin periaatteista.
Esimerkkejä funktioiden käsittelystä ja piirtelystä
Python esimerkkejä > Rekursio, säikeet, synkronointi (2016-4-8)
Tein vuosia sitten ohjelman, joka piirsi puun rekursiivisella funktiolla. Sen oksien väri ja paksuus riippui etäisyydestä tyvestä. Oksien kärkiin piirsin lehden tai kukan. Lisäämällä pikkuisen satunnaisuutta, puiden piirtymistä oli hauska katsella. Onnistuin jopa saamaan puut huojumaan. Jäin haaveilemaan maisemien luomisesta algoritmilla, jossa olisi sopivasti yhdistettynä säännönmukaisuutta ja satunnaisuutta. Vielä hienompaa, jos jaksaisi tehdä siitä kolmiulotteisen niin, että sitä voisi zoomailla ja käännellä. Niin pitkälle en päässyt, mutta onnistuinpa saamaan puut piirtymään vähän jouhevammin kuin silloin kauan sitten.
puu kasvaa
video varalle, jos yo. ei toimi
ja huojuu
video varalle, jos yo. ei toimi
Python esimerkkejä > Rekursio, säikeet, synkronointi > PuutPilvet.py (None)
Seuraava esimerkki esittelee, miten python-ohjelmointikielessä voi käyttää listoja, olioita ja rekursiivisia funktioita.
Ohjelma piirtää näytölle naivistisen maiseman ohjelmointikielen piirteiden havainnollistamiseksi. Esimerkiksi pilvet ja puun oksat ovat olioita. Puu piirretään rekursiivisella ohjelmalla.
Tue Apr 14 14:01:18 2020
Python esimerkkejä > Rekursio, säikeet, synkronointi > PuutPilvet.py > - (None)
import pygame import sys from math import sin, cos, pi import random
Musta = (0, 0, 0) # Mustassa ei ole mitään valoa Sin = (0, 0, 255) # vain sinistä Pun = (255, 0, 0) Vihr = (0, 255, 0) Maasto = (100, 120, 10) Kelt = (255, 255, 0) Valk = (255, 255, 255) # valkoisessa on kaikenväristä valoa Tausta = (160, 200, 255)
MaxHaara = 10 LCoeff = 0.95 DKulma = 0.3 DS = 0.25 Ccos = 4.0
def screenxy(x, y): return (int(SKAALA*x+CXW/2.0), int(CYW-SKAALA*y))
def puuVari(Nhaara): r = 20 + (80-20)*Nhaara/MaxHaara g = 240 - (240-20)*Nhaara/MaxHaara return (r, g, 0)
class oksa: def __init__(self, paikka, alapuolinen, kulma, pituus, Nhaara): self.paikka = paikka # Puun tyven sijainti self.kulma = kulma # Oksanpätkän kulma alapuolella olevaan self.pituus = pituus self.paksuus = 2.0 + Nhaara self.Nhaara = Nhaara # Monesko oksanpätkä latvasta lukien self.alapuolinen = alapuolinen # Alapuolinen oksanpätkä self.vasenhaara = None self.oikeahaara = None
def kasvata(self, Skulma): if self.Nhaara > 0 and Skulma < pi and Skulma > 0: # lisätään vielä ainakin yksi oksa kumpaankin suuntaan. # lasketaan lisättävälle oksanpätkälle pituus ... pituus = LCoeff*self.pituus + random.uniform(-DS, DS) + Ccos*(cos(Skulma) - 0.5) # ja asentokulma kulma = DKulma + random.uniform(-0.05, 0.05) # lisätään oksanpätkä ... self.vasenhaara = oksa(self.paikka, self, kulma, pituus, self.Nhaara-1) # ... ja jatketaan puun kasvatusta lisätystä oksanpätkästä ylöspäin self.vasenhaara.kasvata(Skulma+kulma) # kun vasenta haaraa on jatkettu latvaan asti, # ohjelman suoritus palaa tähän ja jatkaa oikeaa haaraa ylös. # Pituus ja kulma voisivat olla samat kuin vasemmallekin # lähtiessä, mutta vaihtelun vuoksi tehdään oikeasta haarasta # vähän erilainen pituus = LCoeff*self.pituus + random.uniform(-DS, DS) + Ccos*(cos(Skulma) - 0.5) kulma = - DKulma + random.uniform(-0.05, 0.05) self.oikeahaara = oksa(self.paikka, self, kulma, pituus, self.Nhaara-1) self.oikeahaara.kasvata(Skulma+kulma) else: # Lopuksi oksan päähän lisätään kukka self.vasenhaara = Kukka(self.pituus)
def huoju(self, huojahdus): self.kulma = self.kulma+huojahdus/self.paksuus*self.pituus/7.0 if self.vasenhaara is not None: self.vasenhaara.huoju(huojahdus) if self.oikeahaara is not None: self.oikeahaara.huoju(huojahdus)
def piirra_w(self, x0, y0, kulma0, Wait, screen): kulma = self.kulma+kulma0 if self.alapuolinen is None: (x0, y0) = self.paikka x = x0 + self.pituus*cos(kulma) y = y0 + self.pituus*sin(kulma) xy1 = screenxy(x, y) xy0 = screenxy(x0, y0) pygame.draw.line(screen, puuVari(self.Nhaara), xy0, xy1, int(self.paksuus)) if Wait: pygame.display.flip() if self.vasenhaara is not None: self.vasenhaara.piirra_w(x, y, kulma, Wait, screen) if self.oikeahaara is not None: self.oikeahaara.piirra_w(x, y, kulma, Wait, screen) class Kukka: def __init__(self, pituus): # Kukan koko riippuu alapuolisen oksan pituudesta # Voisi riippua oksan paksuudesta tai olla vakio self.size = pituus/20.0 self.colors = [Kelt, Sin, Pun, Kelt] # Piirretään kolme eriväristä ja -kokoista ympyrää päällekkäin. # Aloitetaan isoimmasta, ettei isompi peitä pienempäänsä. def piirra_w(self, x, y, kulma, Wait, screen): for i in range(3, 0, -1): xf, yf = screenxy(x-i*self.size, y+i*self.size) w = int(2*i*self.size*SKAALA) h = int(2*i*self.size*SKAALA) pygame.draw.ellipse(screen, self.colors[i], (xf, yf, w, h), 0) if Wait: pygame.display.flip() # Kukallakin pitää olla huoju-metodi, koska kutsuva ohjelma ei tiedä, # käsitteleekö se oksanpätkää vai kukkaa. def huoju(self, kulma): pass
class Pilvi: def __init__(self): xvasen = random.uniform(0.1, 0.9*CXW) ytop = random.uniform(0.1, 0.7*CYW) w = random.uniform(0.02*CXW, 0.2*CXW) h = max(0.2*w, random.uniform(0.02*CYW, 0.025*CYW)) self.coords = pygame.Rect(int(xvasen), int(ytop), int(w), int(h)) def liiku(self): # jos pilvi on ajautunut ulos peli-ikkunan oikeasta laidasta, # se siirretään peli-ikkunan vasempaan laitaan. if self.coords.left > CXW: self.coords.move_ip(-CXW-self.coords.width, random.randrange(-1, 2)) else: # move_ip siirtää pilveä satunnaisen hyppäyksen vasemmalle # ja lisäksi pikkuisen ylös tai alas max_speed = int(8-4*self.coords.y/CYW) self.coords.move_ip(random.randrange(0, max_speed), random.randrange(-1, 2)) # inflate_ip muuttaa pilven kokoa self.coords.inflate_ip(random.randrange(-1, 2), random.randrange(-1, 2)) # ei anneta pilvien kasvaa rajatta eikä kutistua olemattomiin # pidetään pilven korkeus reilusti pienempänä kuin leveys self.coords.width = max(int(0.02*CXW), min(int(0.2*CXW), self.coords.width)) self.coords.height = min(int(self.coords.width/5.0), max(int(0.02*CYW), self.coords.height)) def piirra(self, screen): pygame.draw.ellipse(screen, Valk, self.coords, 0) # Piirretään kaksi kukkulaa ja rajataan ne mustalla viivalla def maasto(screen): pygame.draw.ellipse(screen, Maasto, (0.4*CXW, 0.85*CYW, 0.9*CXW, CYW), 0) pygame.draw.ellipse(screen, Musta, (0.4*CXW, 0.85*CYW, 0.9*CXW, CYW), 2) pygame.draw.ellipse(screen, Maasto, (-0.1*CXW, 0.8*CYW, 0.8*CXW, 0.8*CYW), 0) pygame.draw.ellipse(screen, Musta, (-0.1*CXW, 0.8*CYW, 0.8*CXW, 0.8*CYW), 2) # Tarkistetaan, haluaako käyttäjä pysäyttää ohjelman. # Tämä ei ole aivan tyylikäs tapa pysäyttää ohjelma. # Ongelmana on reagoida keskeytyspyyntöön monikertaisen for-silmukan # sisältä. # En ole vielä opetellut try - exception rakenteen käyttöä. def lopetus(): for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() sys.exit() if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE: pygame.quit() sys.exit() # # # # # # # # # # # # # # # # # # # # # # # # # # # # Pääohjelma alkaa tästä # # # # # # # # # # # # # # # # # # # # # # # # # # #
def main(): global CXW, CYW, XW, YW, SKAALA pygame.display.init() # Selvitetään näytön koko pikseleinä D_Info = pygame.display.Info() CXW = D_Info.current_w CYW = D_Info.current_h YW = 100.0 # Maiseman korkeus. XW = CXW/CYW*YW # Maiseman leveys SKAALA = CYW/YW screen = pygame.display.set_mode((CXW, CYW)) pygame.display.set_caption("puut") pygame.init() random.seed() screen.fill(Tausta) # Piirretään kukkulat maasto(screen) pygame.display.flip() # Luodaan sadan pilven lista pilvet = [] for i in range(100): pilvet.append(Pilvi()) # Piirretään pilvet yksi kerrallaan for pilvi in pilvet: pilvi.piirra(screen) pygame.display.flip() pygame.time.wait(50) # Luodaan "metsä" eli lista puista puut = [] puut.append(oksa((-60, 15), None, pi/2.0+0.1, 5.0, MaxHaara-2)) puut.append(oksa((50, 10), None, pi/2.0-0.1, 7.0, MaxHaara-1)) puut.append(oksa((-10, 4), None, pi/2.0, 9.0, MaxHaara)) # Kasvatetaan puut ja piirretään ne. # Wait = True eli päivitetään näyttö jokaisen # oksan piirtämisen jälkeen. for puu in puut: puu.kasvata(pi/2.0) puu.piirra_w(0, 2, 0.0, True, screen) pygame.time.wait(500) s = 0.0075 while True: # käännytään keskeltä ääriasentoon ja takaisin sadalla s:n kokoisella # huojahduksella # Sitten sama toiseen ääriasentoon for j in range(2): for k in range(100): screen.fill(Tausta) for pilvi in pilvet: pilvi.liiku() pilvi.piirra(screen) maasto(screen) for puu in puut: puu.huoju(s) puu.piirra_w(0, 2, 0.0, False, screen) pygame.display.flip() lopetus() pygame.time.wait(10) s = -s # Käännytään ääriasennosta takaisin s = -s # Lähdetään keskeltä toiseen suuntaan kuin viimeksi pygame.quit() if __name__ == "__main__": main()
Selityksiä ylläolevaan
Allaolevat import käskyt tuovat tämän ohjelman käyttöön valmiita moduleita, joiden sisältämista funktioista saa tietoa mm. webin python-oppaista. Grafiikka on toteutettu pygame-modulin avulla lähinnä siksi, että satun tuntemaan sen ennestään.
import pygame import sys from math import sin, cos, pi import random
Seuraavilla riveillä on globaaleja vakioita, joita voi käyttää missä hyvänsä ohjelman osassa.
Pygame-modulissa värit esitetään ilmoittamalla luvulla väliltä 0..255 punaisen, vihreän ja sinisen värin osuus.
Musta = (0, 0, 0) # Mustassa ei ole mitään valoa Sin = (0, 0, 255) # vain sinistä Pun = (255, 0, 0) Vihr = (0, 255, 0) Maasto = (100, 120, 10) Kelt = (255, 255, 0) Valk = (255, 255, 255) # valkoisessa on kaikenväristä valoa Tausta = (160, 200, 255)
Seuraavien parametrien merkitys selviää myöhemmin.
Tyylikkässä ohjelmassa olisi graafinen käyttöliittymä, joka selittäisi parametrien merkityksen ja antaisi mahdollisuuden muuttaa niiden oletusarvoja. Ehkä joskus opettelen tekemään käyttöliittymän tämäntapaisiin ohjelmiin.
MaxHaara = 10 LCoeff = 0.95 DKulma = 0.3 DS = 0.25 Ccos = 4.0
Funktio screenxy(x, y) laskee, missä kohtaa näytöllä on pelimaailman piste (x, y)
def screenxy(x, y): return (int(SKAALA*x+CXW/2.0), int(CYW-SKAALA*y))
Puiden runkojen väri vaihtuu tyven tummanruskeasta latvaoksien vaaleanvihreään. Seuraava funktio laskee puun rungon värin. Ohjelmassa puu kasvaa vaiheittain kuin vuosikasvu kerrallaan. Nhaara kertoo, monesko vuosikasvu latvasta lukien on värjättävänä. MaxHaara on vuosikasvujen enimmäismäärä.
def puuVari(Nhaara): r = 20 + (80-20)*Nhaara/MaxHaara g = 240 - (240-20)*Nhaara/MaxHaara return (r, g, 0)
Puu koostuu oksiksi kutsutuista pätkistä. Oksan ominaisuudet ovat pituus, asennon kertova kulma, paksuus ja tieto siitä, monesko pätkä se on latvasta lukien. Oksalla on myös tieto siitä, minkä alapuolisen oksanpätkän varassa se on ja mitkä oksanpätkät lähtevät siitä ylöspäin oikealle ja vasemmalle.
class oksa: def __init__(self, paikka, alapuolinen, kulma, pituus, Nhaara): self.paikka = paikka # Puun tyven sijainti self.kulma = kulma # Oksanpätkän kulma alapuolella olevaan self.pituus = pituus self.paksuus = 2.0 + Nhaara self.Nhaara = Nhaara # Monesko oksanpätkä latvasta lukien self.alapuolinen = alapuolinen # Alapuolinen oksanpätkä self.vasenhaara = None self.oikeahaara = None
Seuraava funtio lisää puuhun tyvestä lähtien oksanpätkän kerrallaan kunnes päästään latvaan asti tai kunnes oksa kääntyy maata kohti. Jatketaan siis puun kasvattamista niin kauan kuin Nhaara > 0 ja ja kulma horisonttiin nähden - Skulma - on pienempi kuin 180 astetta ja suurempi kuin 0. Tätä funktiota vähän muutettuna voisi kutsua suoraan ylläolevassa __init__ funktiossa: self.vasenhaara = kasvata(self, ...
Oksanpätkän asentokulma on 0, kun oksa osoittaa vaakasuoraan oikealle ja asentokulma on 180, kun oksa osoittaa suoraan vasemmalle. Pythonissa kulmat ilmoitetaan oletusarvoisesti radiaaneina, eli 180 astetta on pii radiaania.
Uuden oksanpätkän pituus voisi olla sama kuin sen alapuolella olevankin, mutta huvin vuoksi lisäsin puun kasvulla taipumuksen, että yläpuolinen on hieman alapuolista oksaa lyhyempi (LCoeff < 1.0). Lisäksi oksa kasvaa sitä paremmin, mitä pystysuorempaan se on kasvamassa. (Termi Ccos*(cos(Skulma) - 0.5)) Lisäsin oksan pituuskasvuun myös hieman satunnaisuutta (random.uniform(-DS, DS)).
def kasvata(self, Skulma): if self.Nhaara > 0 and Skulma < pi and Skulma > 0: # lisätään vielä ainakin yksi oksa kumpaankin suuntaan. # lasketaan lisättävälle oksanpätkälle pituus ... pituus = LCoeff*self.pituus + random.uniform(-DS, DS) + Ccos*(cos(Skulma) - 0.5) # ja asentokulma kulma = DKulma + random.uniform(-0.05, 0.05) # lisätään oksanpätkä ... self.vasenhaara = oksa(self.paikka, self, kulma, pituus, self.Nhaara-1) # ... ja jatketaan puun kasvatusta lisätystä oksanpätkästä ylöspäin self.vasenhaara.kasvata(Skulma+kulma) # kun vasenta haaraa on jatkettu latvaan asti, # ohjelman suoritus palaa tähän ja jatkaa oikeaa haaraa ylös. # Pituus ja kulma voisivat olla samat kuin vasemmallekin # lähtiessä, mutta vaihtelun vuoksi tehdään oikeasta haarasta # vähän erilainen pituus = LCoeff*self.pituus + random.uniform(-DS, DS) + Ccos*(cos(Skulma) - 0.5) kulma = - DKulma + random.uniform(-0.05, 0.05) self.oikeahaara = oksa(self.paikka, self, kulma, pituus, self.Nhaara-1) self.oikeahaara.kasvata(Skulma+kulma) else: # Lopuksi oksan päähän lisätään kukka self.vasenhaara = Kukka(self.pituus)
huoju kääntää jokaista oksanpätkää parametristä huojahdus ja oksan paksuudesta ja pituudesta riippuvan kulman verran Huomaa, että meidän ei tarvitse tarkistaa, onko yläpuolinen haara oksanpätkä vai latvakukka. Riittää, että olioluokalle Kukka on sillekin määritelty metodi huoju.
def huoju(self, huojahdus): self.kulma = self.kulma+huojahdus/self.paksuus*self.pituus/7.0 if self.vasenhaara is not None: self.vasenhaara.huoju(huojahdus) if self.oikeahaara is not None: self.oikeahaara.huoju(huojahdus)
Piirretään puu rekursiivisesti tyvestä lähtien oksanpätkä kerrallaan. Tyveä piirrettäessä oksanpätkän alapään paikka (x0, y0) ) on tietysti puun tyven paikka, muussa tapauksessa funtion argumenttina saatava alapuolisen oksan yläpään paikka.
Oksanpätkän kulma horisonttiin nähden on sen suhteellinen kulma alapuoliseen oksaan nähden + argumenttina saatava kulma0, joka on alapuolisen oksan kulma horisonttiin nähden
Oksanpätkän yläpään paikka (x, y) saadaan trigonometrian avulla. Miten, sen ymmärtää, kun piirtää kuvan.
screenxy(x, y) laskee maiseman pistettä tt vastaavan pisteen pygamen peli-ikkunassa.
Wait määrää, päivitetäänkö peli-ikkuna näytöllä jokaisen oksanpätkän piirtämisen jälkeen.
def piirra_w(self, x0, y0, kulma0, Wait, screen): kulma = self.kulma+kulma0 if self.alapuolinen is None: (x0, y0) = self.paikka x = x0 + self.pituus*cos(kulma) y = y0 + self.pituus*sin(kulma) xy1 = screenxy(x, y) xy0 = screenxy(x0, y0) pygame.draw.line(screen, puuVari(self.Nhaara), xy0, xy1, int(self.paksuus)) if Wait: pygame.display.flip() if self.vasenhaara is not None: self.vasenhaara.piirra_w(x, y, kulma, Wait, screen) if self.oikeahaara is not None: self.oikeahaara.piirra_w(x, y, kulma, Wait, screen) class Kukka: def __init__(self, pituus): # Kukan koko riippuu alapuolisen oksan pituudesta # Voisi riippua oksan paksuudesta tai olla vakio self.size = pituus/20.0 self.colors = [Kelt, Sin, Pun, Kelt] # Piirretään kolme eriväristä ja -kokoista ympyrää päällekkäin. # Aloitetaan isoimmasta, ettei isompi peitä pienempäänsä. def piirra_w(self, x, y, kulma, Wait, screen): for i in range(3, 0, -1): xf, yf = screenxy(x-i*self.size, y+i*self.size) w = int(2*i*self.size*SKAALA) h = int(2*i*self.size*SKAALA) pygame.draw.ellipse(screen, self.colors[i], (xf, yf, w, h), 0) if Wait: pygame.display.flip() # Kukallakin pitää olla huoju-metodi, koska kutsuva ohjelma ei tiedä, # käsitteleekö se oksanpätkää vai kukkaa. def huoju(self, kulma): pass
Pilvet ovat suorakaiteen sisään piirrettäviä ellipsejä. Käytän kokeeksi pygame-modulin olioluokkaa Rect, jonka siirtelyn ja koon muuntelun pitäisi olla helppoa ja ohjelman suorituksen kannalta tehokasta. (xvasen, ytop) on suorakaiteen vasen yläkulma ja (w, h) sen leveys ja korkeus.
Käytän pilviä piirtäessä suoraan peli-ikkunan koordinaatistoa, vaikka olisi ollut tyylikkäämpää käyttää samaa maiseman koordinaatistoa kuin puita piirtäessä.
Pilvet liikkuvat vasemmalta oikealle.
class Pilvi: def __init__(self): xvasen = random.uniform(0.1, 0.9*CXW) ytop = random.uniform(0.1, 0.7*CYW) w = random.uniform(0.02*CXW, 0.2*CXW) h = max(0.2*w, random.uniform(0.02*CYW, 0.025*CYW)) self.coords = pygame.Rect(int(xvasen), int(ytop), int(w), int(h)) def liiku(self): # jos pilvi on ajautunut ulos peli-ikkunan oikeasta laidasta, # se siirretään peli-ikkunan vasempaan laitaan. if self.coords.left > CXW: self.coords.move_ip(-CXW-self.coords.width, random.randrange(-1, 2)) else: # move_ip siirtää pilveä satunnaisen hyppäyksen vasemmalle # ja lisäksi pikkuisen ylös tai alas max_speed = int(8-4*self.coords.y/CYW) self.coords.move_ip(random.randrange(0, max_speed), random.randrange(-1, 2)) # inflate_ip muuttaa pilven kokoa self.coords.inflate_ip(random.randrange(-1, 2), random.randrange(-1, 2)) # ei anneta pilvien kasvaa rajatta eikä kutistua olemattomiin # pidetään pilven korkeus reilusti pienempänä kuin leveys self.coords.width = max(int(0.02*CXW), min(int(0.2*CXW), self.coords.width)) self.coords.height = min(int(self.coords.width/5.0), max(int(0.02*CYW), self.coords.height)) def piirra(self, screen): pygame.draw.ellipse(screen, Valk, self.coords, 0) # Piirretään kaksi kukkulaa ja rajataan ne mustalla viivalla def maasto(screen): pygame.draw.ellipse(screen, Maasto, (0.4*CXW, 0.85*CYW, 0.9*CXW, CYW), 0) pygame.draw.ellipse(screen, Musta, (0.4*CXW, 0.85*CYW, 0.9*CXW, CYW), 2) pygame.draw.ellipse(screen, Maasto, (-0.1*CXW, 0.8*CYW, 0.8*CXW, 0.8*CYW), 0) pygame.draw.ellipse(screen, Musta, (-0.1*CXW, 0.8*CYW, 0.8*CXW, 0.8*CYW), 2) # Tarkistetaan, haluaako käyttäjä pysäyttää ohjelman. # Tämä ei ole aivan tyylikäs tapa pysäyttää ohjelma. # Ongelmana on reagoida keskeytyspyyntöön monikertaisen for-silmukan # sisältä. # En ole vielä opetellut try - exception rakenteen käyttöä. def lopetus(): for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() sys.exit() if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE: pygame.quit() sys.exit() # # # # # # # # # # # # # # # # # # # # # # # # # # # # Pääohjelma alkaa tästä # # # # # # # # # # # # # # # # # # # # # # # # # # #
Yleensä peleissä tai kuvia piirrellessä pelimaailman mittayksiköt kannattaa erottaa näytön mittatyksiköistä, koska ohjelman pitää toimia järkevästi eri kokoisilla näytöillä. Voin esimerkiksi sijoittaa puun pisteeseen (-30, 5) riippumatta siitä, montako pikseliä peliruuden koko on näytöllä.
Tässä ohjelmassa pelimaailman korkeus on kiinnitetty sadaksi yksiköksi ja leveys sovitetaan niin, että pelimaailman ja peli-ikkunan kuvasuhde on sama.
Seuraavassa on määritelty peli-ikkunan ja näytön kokoon liittyvät parametrit globaaleiksi muuttujiksi, jotta niitä voi käyttää missä hyvänsä ohjelman kohdassa. Globaaleja muuttujia on houkutteleva käyttää erityisesti ohjelmaa kehittäessä. Turvallisempaa, mutta työläämpää, on välittää funktioille kaikki tarvittava tieto kutsuparametrien kautta.
def main(): global CXW, CYW, XW, YW, SKAALA pygame.display.init() # Selvitetään näytön koko pikseleinä D_Info = pygame.display.Info() CXW = D_Info.current_w CYW = D_Info.current_h YW = 100.0 # Maiseman korkeus. XW = CXW/CYW*YW # Maiseman leveys SKAALA = CYW/YW screen = pygame.display.set_mode((CXW, CYW)) pygame.display.set_caption("puut") pygame.init() random.seed() screen.fill(Tausta) # Piirretään kukkulat maasto(screen) pygame.display.flip() # Luodaan sadan pilven lista pilvet = [] for i in range(100): pilvet.append(Pilvi()) # Piirretään pilvet yksi kerrallaan for pilvi in pilvet: pilvi.piirra(screen) pygame.display.flip() pygame.time.wait(50) # Luodaan "metsä" eli lista puista puut = [] puut.append(oksa((-60, 15), None, pi/2.0+0.1, 5.0, MaxHaara-2)) puut.append(oksa((50, 10), None, pi/2.0-0.1, 7.0, MaxHaara-1)) puut.append(oksa((-10, 4), None, pi/2.0, 9.0, MaxHaara)) # Kasvatetaan puut ja piirretään ne. # Wait = True eli päivitetään näyttö jokaisen # oksan piirtämisen jälkeen. for puu in puut: puu.kasvata(pi/2.0) puu.piirra_w(0, 2, 0.0, True, screen) pygame.time.wait(500) s = 0.0075 while True: # käännytään keskeltä ääriasentoon ja takaisin sadalla s:n kokoisella # huojahduksella # Sitten sama toiseen ääriasentoon for j in range(2): for k in range(100): screen.fill(Tausta) for pilvi in pilvet: pilvi.liiku() pilvi.piirra(screen) maasto(screen) for puu in puut: puu.huoju(s) puu.piirra_w(0, 2, 0.0, False, screen) pygame.display.flip() lopetus() pygame.time.wait(10) s = -s # Käännytään ääriasennosta takaisin s = -s # Lähdetään keskeltä toiseen suuntaan kuin viimeksi pygame.quit() if __name__ == "__main__": main()
Lataa tästä ohjelman tämän hetkinen versio: PuutPilvet_0.py
Python esimerkkejä > Rekursio, säikeet, synkronointi > synkronointi.py (None)
Tue Apr 14 14:02:30 2020
Python esimerkkejä > Rekursio, säikeet, synkronointi > synkronointi.py > Synkronointi (None)
# Jokaisen pilven säikeessä on silmukka, jossa siirretään pilveä ja # piirretään se while not loppu: # siirretään pilveä self.siirra() # ja sitten piirretään pilvi uuteen paikkaan # Ennen kuin aletaan piirtää, anotaan lupaa kirjoittaa näytölle portti.acquire() # odotetaan, kunnes näyttöä päivittävä ohjelma antaa luvan portti.wait() # piirretään self.piirra() # vapautetaan varaus portti.release() # Pääohjelman säikeessä on silmukka, joka päivittää näytön määrävälein while not loppu: # Odotetaan, että portti vapautuu ja estetään sen jälkeen # muita ohittamasta porttia portti.acquire() pygame.display.flip() # Päivitetään näyttö screen.fill(Tausta) # Tyhjennetään "kangas" portti.notify_all() # Ilmoitetaan muille säikeille, että portti vapautuu portti.release() # # Odotellaan hetki, että muut säikeet ehtivät piirtää kankaalle jotain # uutta. DT on näytön päivitysväli time.sleep(DT)
Selityksiä ylläolevaan
Tässä ohjelmaversiossa kukin puu ja pilvi toimii eri säikessään. Ohjelmoijan on hyvä ajatella, että säikeitä suoritetaan rinnakkain, koska kunkin säikeen suoritus etenee omaa tahtiaan -- asynkronisesti. Multiytimisessä prosessorissa säikeet voisivat edetä aidosti rinnakkain, mutta aidosti rinnakkaisen ohjelman tekeminen vaatii vielä lisätemppuja.
Jos säikeiden halutaan ottavan toistensa suoritusvaiheen huomioon, ne täytyy laittaa välittämään toisilleen viestejä. Seuraavassa esitetään yksi tapa synkronoida säikeiden suoritus.
Jokaisen puun ja pilven siirtelyyn ja piirtämiseen tarvittava ohjelma käynnistetään omaksi säikeekseen. Ne piirtävät yhteiselle "kankaalle". Pääohjelmä siirtää määrävälein "kankaan" sisällön näyttömuistiin ja pyyhkii kankaan tyhjäksi. Ellei piirtämisiä ja näytön päivitystä synkroinoida, näytölle tulee mitä sattuu.
# Jokaisen pilven säikeessä on silmukka, jossa siirretään pilveä ja # piirretään se while not loppu: # siirretään pilveä self.siirra() # ja sitten piirretään pilvi uuteen paikkaan # Ennen kuin aletaan piirtää, anotaan lupaa kirjoittaa näytölle portti.acquire() # odotetaan, kunnes näyttöä päivittävä ohjelma antaa luvan portti.wait() # piirretään self.piirra() # vapautetaan varaus portti.release() # Pääohjelman säikeessä on silmukka, joka päivittää näytön määrävälein while not loppu: # Odotetaan, että portti vapautuu ja estetään sen jälkeen # muita ohittamasta porttia portti.acquire() pygame.display.flip() # Päivitetään näyttö screen.fill(Tausta) # Tyhjennetään "kangas" portti.notify_all() # Ilmoitetaan muille säikeille, että portti vapautuu portti.release() # # Odotellaan hetki, että muut säikeet ehtivät piirtää kankaalle jotain # uutta. DT on näytön päivitysväli time.sleep(DT)
Lataa tästä ohjelman tämän hetkinen versio: synkronointi_0.py
Python esimerkkejä > Rekursio, säikeet, synkronointi > PuutPilvetSaikeet.py (None)
Tämä on säikeistetty versio edellisestä ohjelmasta. Ohjelmassa on listoja, olioita, rekursiivisia funktioita ja säikeitä. Ohjelma piirtää näytölle naivistisen maiseman ohjelmointikielen piirteiden havainnollistamiseksi. Esimerkiksi pilvet ja puut ovat omissa säikeissään suoritettavia olioita. Puut luodaan ja piirretään rekursiivisella ohjelmalla.
Tue Apr 14 14:01:49 2020
Python esimerkkejä > Rekursio, säikeet, synkronointi > PuutPilvetSaikeet.py > Säikeiden synkronointia (None)
import threading import time import pygame import sys from math import sin, cos, pi import random
Musta = (0, 0, 0) # Mustassa ei ole mitään valoa Sin = (0, 0, 255) # vain sinistä Pun = (255, 0, 0) Vihr = (0, 255, 0) Maasto = (100, 120, 10) # Kukkuloiden väri Kelt = (255, 255, 0) Valk = (255, 255, 255) # valkoisessa on kaikenväristä valoa Tausta = (160, 200, 255) # Taivas
DT = 0.05 # Näytön päivitysväli sekunteina MaxHaara = 8 # Tyvestä kunkin haaran latvaan on 8 oksanpätkää LCoeff = 0.9 # Oksan pituuteen vaikuttava kerroin DKulma = 0.3 # Kulma, jossa yläpuolinen kulma kääntyy alemmasta DS = 0.5 # Selviää myöhemmin ;-) Ccos = 2.0 # Selviää myöhemmin ;-)
def taustaxy(x, y): return (int(SKAALA*x+CXW/2.0), int(CYW-SKAALA*y))
# Oksanpätkän väri riippuu siitä, kuinka korkealla tyvestä se on def puuVari(Nhaara): r = 20 + (80-20)*Nhaara/MaxHaara g = 240 - (240-20)*Nhaara/MaxHaara return (r, g, 0) # Oksanpätkä class Oksa(): def __init__(self, tyvi, kulma, pituus, Nhaara): self.kulma = kulma # Kulma suhteessa alapuoliseen oksaan self.pituus = pituus self.paksuus = 2 + 1.5*Nhaara # oksat ohenevat latvaa kohti self.Nhaara = Nhaara # Nhaara kertoo, montaka askelta on latvaan self.tyvi = tyvi # alapuolinen oksa self.vasenhaara = None # yläpuolinen oksa vasemmalle self.oikeahaara = None # Lisätään puuhun rekursiivisesti oksa kerrallaan def kasvataPuu(self, Skulma): # Lisätään oksanpätkä, jos Nhaara ei vielä ole 0 # eikä oksa "roiku" alaspäin # (Skulma on oksanpätkän absoluuttinen kulma horisonttiin nähden) if self.Nhaara > 0 and Skulma < pi and Skulma > 0: # Ylemmillä oksilla on "taipumus" olla alempaa lyhyempiä, # mutta satunnaisuus saattaa muuttaa tilanteen. pituus = LCoeff*self.pituus + random.uniform(-DS, DS) # lisätään vielä termi, joka pidentää ylöspäin osoittavia # oksia ja lyhentää sivulle kasvavia + Ccos*(cos(Skulma) - 0.5) # Yläpuolinen oksan suunta poikkeaa alapuolisesta # Dkulman verran lisättynä pienellä satunnaisella termillä kulma = DKulma*(4.0+self.Nhaara)/(1.0*MaxHaara) + random.uniform(-0.1, 0.1) # Lisätään oksanpätkä nykyisen yläpuolelle self.vasenhaara = Oksa(self, kulma, pituus, self.Nhaara-1) # ja kasvatetaan puuta siitä ylöspäin self.vasenhaara.kasvataPuu(Skulma+kulma) # Sama oikealle haaralle pituus = LCoeff*self.pituus + random.uniform(-DS, DS) + Ccos*(cos(Skulma) - 0.5) kulma = - DKulma*(4.0+self.Nhaara)/(1.0*MaxHaara) + random.uniform(-0.1, 0.1) self.oikeahaara = Oksa(self, kulma, pituus, self.Nhaara-1) self.oikeahaara.kasvataPuu(Skulma+kulma) else: # Kun on päästy latvaan, piirretään vielä kukka self.vasenhaara = Kukka(self) # self.oikeahaara = Kukka(self) # Puuta voi huojuttaa muuttamalla kunkin oksanpätkän kulmaa # Muutetaan kulmaa kääntäen verrannollisesti sen paksuuteen ja # ja suoraan verrannollisesti sen pituuteen. def huojahda(self, huojahdus): self.kulma = self.kulma+huojahdus/self.paksuus*self.pituus if self.vasenhaara is not None: self.vasenhaara.huojahda(huojahdus) if self.oikeahaara is not None: self.oikeahaara.huojahda(huojahdus) # Piirretään oksa kerrallaan def piirra_w(self, paikka, kulma0): # portin avulla tahdistetaan puiden piirtäminen näytön päivittävän # säikeen kanssa portti.acquire() portti.wait() # Loppu on trigonometriaa ;-) kulma = self.kulma+kulma0 (x0, y0) = paikka x = x0 + self.pituus*cos(kulma) y = y0 + self.pituus*sin(kulma) xy1 = taustaxy(x, y) xy0 = taustaxy(x0, y0) # piirtämisten tahdistamisen helpottamiseksi käytetään kahta eri # kangasta, edusta ja tausta pygame.draw.line(edusta, puuVari(self.Nhaara), xy0, xy1, int(self.paksuus)) portti.release() if self.vasenhaara is not None: self.vasenhaara.piirra_w((x, y), kulma) if self.oikeahaara is not None: self.oikeahaara.piirra_w((x, y), kulma) # Piirretään kerralla koko puu def piirra(self, paikka, kulma0): kulma = self.kulma+kulma0 (x0, y0) = paikka x = x0 + self.pituus*cos(kulma) y = y0 + self.pituus*sin(kulma) xy1 = taustaxy(x, y) xy0 = taustaxy(x0, y0) pygame.draw.line(edusta, puuVari(self.Nhaara), xy0, xy1, int(self.paksuus)) if self.vasenhaara is not None: self.vasenhaara.piirra((x, y), kulma) if self.oikeahaara is not None: self.oikeahaara.piirra((x, y), kulma) class Kukka: def __init__(self, varsi): self.size = varsi.pituus/20.0 self.varsi = varsi self.colors = [Kelt, Sin, Pun, Kelt] def piirra(self, paikka, kulma): (x, y) = paikka for i in range(3, 0, -1): xf, yf = taustaxy(x-i*self.size, y+i*self.size) w = int(2*i*self.size*SKAALA) h = int(2*i*self.size*SKAALA) pygame.draw.ellipse(edusta, self.colors[i], (xf, yf, w, h), 0) def piirra_w(self, paikka, kulma): portti.acquire() portti.wait() (x, y) = paikka for i in range(3, 0, -1): xf, yf = taustaxy(x-i*self.size, y+i*self.size) w = int(2*i*self.size*SKAALA) h = int(2*i*self.size*SKAALA) pygame.draw.ellipse(edusta, self.colors[i], (xf, yf, w, h), 0) portti.release() def huojahda(self, kulma): pass # Jokainen olio puu käynnistetään omaksi säikeekseen class Puu(threading.Thread): def __init__(self, paikka, kulma, pituus, Nhaara): threading.Thread.__init__(self) self.paikka = paikka self.tyvi = Oksa(self, 0.0, pituus, Nhaara) self.kulma = kulma self.pituus = pituus self.paksuus = 2.0 + Nhaara def run(self): global FILL, DT self.tyvi.kasvataPuu(pi/2.0) self.tyvi.piirra_w(self.paikka, self.kulma) FILL = True DT = 0.05 s = 0.002 while not loppu: for j in range(2): for k in range(50): self.tyvi.huojahda(s) portti.acquire() portti.wait() self.tyvi.piirra(self.paikka, self.kulma) portti.release() s = -s s = -s class Pilvi(threading.Thread): def __init__(self): threading.Thread.__init__(self) self.x = random.uniform(0.1, 0.9*CXW) self.y = random.uniform(0.1, 0.6*CYW) self.w = random.uniform(0.02*CXW, 0.2*CXW) self.h = max(0.3*self.w, random.uniform(0.02*CYW, 0.025*CYW)) self.sin = 0 def run(self): while not loppu: if self.x > CXW: self.x -= (CXW + self.w) else: max_speed = int(8-4*self.y/CYW) self.x += random.uniform(0, max_speed) self.y += random.uniform(-2, 2) self.w += random.uniform(-2, 2) self.h += random.uniform(-2, 2) self.w = max(int(0.02*CXW), min(int(0.2*CXW), self.w)) self.h = min(int(self.w/3.0), max(int(0.02*CYW), self.h)) portti.acquire() portti.wait() self.sin = max(0, min(50, self.sin + random.randint(-5, 5))) Color = (255 - self.sin, 255 - self.sin, 255, 64) iy = 5 ix = int(iy*self.w/self.h) dx = self.w/ix/2.0 dy = self.h/iy/2.0 R0 = max(3, self.h/iy/2.0) istp = int(ix/2) for j in range(-2, 3): for i in range(-istp+abs(j), istp-abs(j)+1): x = self.x + i*dx + random.uniform(-3, 3) y = self.y + j*dy + random.uniform(-3, 3) R = R0 + random.uniform(-3, 3) pygame.draw.circle(tausta, Color, (int(x), int(y)), int(R), 0) portti.release() class PuutPilvet(threading.Thread): def __init__(self): threading.Thread.__init__(self) self.puut = [] self.pilvet = [] def run(self): global FILL, DT DT = 0.01 self.puut.append(Puu((40, 10), pi/2.0-0.1, 8.0, MaxHaara)) self.puut.append(Puu((-40, 15), pi/2.0+0.1, 7.0, MaxHaara-1)) for puu in self.puut: puu.setDaemon(True) puu.start() time.sleep(4.0) for i in range(25): self.pilvet.append(Pilvi()) for pilvi in self.pilvet: pilvi.setDaemon(True) pilvi.start() time.sleep(2.0) def maasto(): pygame.draw.ellipse(tausta, Maasto, (0.4*CXW, 0.85*CYW, 0.9*CXW, CYW), 0) pygame.draw.ellipse(tausta, Musta, (0.4*CXW, 0.85*CYW, 0.9*CXW, CYW), 2) pygame.draw.ellipse(tausta, Maasto, (-0.1*CXW, 0.8*CYW, 0.8*CXW, 0.8*CYW), 0) pygame.draw.ellipse(tausta, Musta, (-0.1*CXW, 0.8*CYW, 0.8*CXW, 0.8*CYW), 2) def lopetus(): for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() sys.exit() if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE: pygame.quit() sys.exit() def naytolle(): portti.acquire() maasto() screen.blit(tausta, (0, 0)) screen.blit(edusta, (0, 0)) pygame.display.flip() tausta.fill(Tausta) if FILL: edusta.fill(Musta) portti.notify_all() portti.release() def main(): global FILL global CXW, CYW, XW, YW, SKAALA global loppu global tausta, edusta, screen global portti
pygame.display.init() D_Info = pygame.display.Info() CXW = D_Info.current_w CYW = D_Info.current_h FILL = False YW = 100.0 # Maiseman korkeus. XW = CXW/CYW*YW # Maiseman leveys SKAALA = CYW/YW screen = pygame.display.set_mode((CXW, CYW)) edusta = pygame.Surface(screen.get_size()) tausta = screen.convert_alpha() # tausta.set_alpha(128) # edusta.set_alpha(128) edusta.set_colorkey((0, 0, 0)) tausta.fill(Tausta) portti = threading.Condition() portti.acquire() loppu = False pygame.display.set_caption("puut") pygame.init() random.seed() naytolle() portti.release() puutpilvet = PuutPilvet() puutpilvet.setDaemon(True) puutpilvet.start() loppu = False while not loppu: naytolle() lopetus() time.sleep(DT) pygame.quit() if __name__ == "__main__": main()
Selityksiä ylläolevaan
import threading import time import pygame import sys from math import sin, cos, pi import random
Yleinen tapa esittää värit on ilmoittaa luvulla 0..255 punaisen, vihreän ja sinisen värin osuus.
Musta = (0, 0, 0) # Mustassa ei ole mitään valoa Sin = (0, 0, 255) # vain sinistä Pun = (255, 0, 0) Vihr = (0, 255, 0) Maasto = (100, 120, 10) # Kukkuloiden väri Kelt = (255, 255, 0) Valk = (255, 255, 255) # valkoisessa on kaikenväristä valoa Tausta = (160, 200, 255) # Taivas
Erinäisiä globaaleja vakioita. Tyylikkäässä ohjelmassa olisi graafinen käyttöliittymä näiden muuttamista varten.
DT = 0.05 # Näytön päivitysväli sekunteina MaxHaara = 8 # Tyvestä kunkin haaran latvaan on 8 oksanpätkää LCoeff = 0.9 # Oksan pituuteen vaikuttava kerroin DKulma = 0.3 # Kulma, jossa yläpuolinen kulma kääntyy alemmasta DS = 0.5 # Selviää myöhemmin ;-) Ccos = 2.0 # Selviää myöhemmin ;-)
Yleensä peleissä tai kuvia piirrellessä pelimaailman mittayksiköt kannattaa erottaa näytön mittatyksiköistä, koska ohjelman pitää toimia järkevästi eri kokoisilla näytöillä. Voin esimerkiksi sijoittaa puun pisteeseen (-30, 5) riippumatta siitä, montako pikseliä peliruuden koko on näytöllä.
Funktio taustaxy(x, y) laskee, missä kohtaa näytöllä on pelimaailman piste (x, y)
def taustaxy(x, y): return (int(SKAALA*x+CXW/2.0), int(CYW-SKAALA*y))
Oksanpätkän määrittelyä.
# Oksanpätkän väri riippuu siitä, kuinka korkealla tyvestä se on def puuVari(Nhaara): r = 20 + (80-20)*Nhaara/MaxHaara g = 240 - (240-20)*Nhaara/MaxHaara return (r, g, 0) # Oksanpätkä class Oksa(): def __init__(self, tyvi, kulma, pituus, Nhaara): self.kulma = kulma # Kulma suhteessa alapuoliseen oksaan self.pituus = pituus self.paksuus = 2 + 1.5*Nhaara # oksat ohenevat latvaa kohti self.Nhaara = Nhaara # Nhaara kertoo, montaka askelta on latvaan self.tyvi = tyvi # alapuolinen oksa self.vasenhaara = None # yläpuolinen oksa vasemmalle self.oikeahaara = None # Lisätään puuhun rekursiivisesti oksa kerrallaan def kasvataPuu(self, Skulma): # Lisätään oksanpätkä, jos Nhaara ei vielä ole 0 # eikä oksa "roiku" alaspäin # (Skulma on oksanpätkän absoluuttinen kulma horisonttiin nähden) if self.Nhaara > 0 and Skulma < pi and Skulma > 0: # Ylemmillä oksilla on "taipumus" olla alempaa lyhyempiä, # mutta satunnaisuus saattaa muuttaa tilanteen. pituus = LCoeff*self.pituus + random.uniform(-DS, DS) # lisätään vielä termi, joka pidentää ylöspäin osoittavia # oksia ja lyhentää sivulle kasvavia + Ccos*(cos(Skulma) - 0.5) # Yläpuolinen oksan suunta poikkeaa alapuolisesta # Dkulman verran lisättynä pienellä satunnaisella termillä kulma = DKulma*(4.0+self.Nhaara)/(1.0*MaxHaara) + random.uniform(-0.1, 0.1) # Lisätään oksanpätkä nykyisen yläpuolelle self.vasenhaara = Oksa(self, kulma, pituus, self.Nhaara-1) # ja kasvatetaan puuta siitä ylöspäin self.vasenhaara.kasvataPuu(Skulma+kulma) # Sama oikealle haaralle pituus = LCoeff*self.pituus + random.uniform(-DS, DS) + Ccos*(cos(Skulma) - 0.5) kulma = - DKulma*(4.0+self.Nhaara)/(1.0*MaxHaara) + random.uniform(-0.1, 0.1) self.oikeahaara = Oksa(self, kulma, pituus, self.Nhaara-1) self.oikeahaara.kasvataPuu(Skulma+kulma) else: # Kun on päästy latvaan, piirretään vielä kukka self.vasenhaara = Kukka(self) # self.oikeahaara = Kukka(self) # Puuta voi huojuttaa muuttamalla kunkin oksanpätkän kulmaa # Muutetaan kulmaa kääntäen verrannollisesti sen paksuuteen ja # ja suoraan verrannollisesti sen pituuteen. def huojahda(self, huojahdus): self.kulma = self.kulma+huojahdus/self.paksuus*self.pituus if self.vasenhaara is not None: self.vasenhaara.huojahda(huojahdus) if self.oikeahaara is not None: self.oikeahaara.huojahda(huojahdus) # Piirretään oksa kerrallaan def piirra_w(self, paikka, kulma0): # portin avulla tahdistetaan puiden piirtäminen näytön päivittävän # säikeen kanssa portti.acquire() portti.wait() # Loppu on trigonometriaa ;-) kulma = self.kulma+kulma0 (x0, y0) = paikka x = x0 + self.pituus*cos(kulma) y = y0 + self.pituus*sin(kulma) xy1 = taustaxy(x, y) xy0 = taustaxy(x0, y0) # piirtämisten tahdistamisen helpottamiseksi käytetään kahta eri # kangasta, edusta ja tausta pygame.draw.line(edusta, puuVari(self.Nhaara), xy0, xy1, int(self.paksuus)) portti.release() if self.vasenhaara is not None: self.vasenhaara.piirra_w((x, y), kulma) if self.oikeahaara is not None: self.oikeahaara.piirra_w((x, y), kulma) # Piirretään kerralla koko puu def piirra(self, paikka, kulma0): kulma = self.kulma+kulma0 (x0, y0) = paikka x = x0 + self.pituus*cos(kulma) y = y0 + self.pituus*sin(kulma) xy1 = taustaxy(x, y) xy0 = taustaxy(x0, y0) pygame.draw.line(edusta, puuVari(self.Nhaara), xy0, xy1, int(self.paksuus)) if self.vasenhaara is not None: self.vasenhaara.piirra((x, y), kulma) if self.oikeahaara is not None: self.oikeahaara.piirra((x, y), kulma) class Kukka: def __init__(self, varsi): self.size = varsi.pituus/20.0 self.varsi = varsi self.colors = [Kelt, Sin, Pun, Kelt] def piirra(self, paikka, kulma): (x, y) = paikka for i in range(3, 0, -1): xf, yf = taustaxy(x-i*self.size, y+i*self.size) w = int(2*i*self.size*SKAALA) h = int(2*i*self.size*SKAALA) pygame.draw.ellipse(edusta, self.colors[i], (xf, yf, w, h), 0) def piirra_w(self, paikka, kulma): portti.acquire() portti.wait() (x, y) = paikka for i in range(3, 0, -1): xf, yf = taustaxy(x-i*self.size, y+i*self.size) w = int(2*i*self.size*SKAALA) h = int(2*i*self.size*SKAALA) pygame.draw.ellipse(edusta, self.colors[i], (xf, yf, w, h), 0) portti.release() def huojahda(self, kulma): pass # Jokainen olio puu käynnistetään omaksi säikeekseen class Puu(threading.Thread): def __init__(self, paikka, kulma, pituus, Nhaara): threading.Thread.__init__(self) self.paikka = paikka self.tyvi = Oksa(self, 0.0, pituus, Nhaara) self.kulma = kulma self.pituus = pituus self.paksuus = 2.0 + Nhaara def run(self): global FILL, DT self.tyvi.kasvataPuu(pi/2.0) self.tyvi.piirra_w(self.paikka, self.kulma) FILL = True DT = 0.05 s = 0.002 while not loppu: for j in range(2): for k in range(50): self.tyvi.huojahda(s) portti.acquire() portti.wait() self.tyvi.piirra(self.paikka, self.kulma) portti.release() s = -s s = -s class Pilvi(threading.Thread): def __init__(self): threading.Thread.__init__(self) self.x = random.uniform(0.1, 0.9*CXW) self.y = random.uniform(0.1, 0.6*CYW) self.w = random.uniform(0.02*CXW, 0.2*CXW) self.h = max(0.3*self.w, random.uniform(0.02*CYW, 0.025*CYW)) self.sin = 0 def run(self): while not loppu: if self.x > CXW: self.x -= (CXW + self.w) else: max_speed = int(8-4*self.y/CYW) self.x += random.uniform(0, max_speed) self.y += random.uniform(-2, 2) self.w += random.uniform(-2, 2) self.h += random.uniform(-2, 2) self.w = max(int(0.02*CXW), min(int(0.2*CXW), self.w)) self.h = min(int(self.w/3.0), max(int(0.02*CYW), self.h)) portti.acquire() portti.wait() self.sin = max(0, min(50, self.sin + random.randint(-5, 5))) Color = (255 - self.sin, 255 - self.sin, 255, 64) iy = 5 ix = int(iy*self.w/self.h) dx = self.w/ix/2.0 dy = self.h/iy/2.0 R0 = max(3, self.h/iy/2.0) istp = int(ix/2) for j in range(-2, 3): for i in range(-istp+abs(j), istp-abs(j)+1): x = self.x + i*dx + random.uniform(-3, 3) y = self.y + j*dy + random.uniform(-3, 3) R = R0 + random.uniform(-3, 3) pygame.draw.circle(tausta, Color, (int(x), int(y)), int(R), 0) portti.release() class PuutPilvet(threading.Thread): def __init__(self): threading.Thread.__init__(self) self.puut = [] self.pilvet = [] def run(self): global FILL, DT DT = 0.01 self.puut.append(Puu((40, 10), pi/2.0-0.1, 8.0, MaxHaara)) self.puut.append(Puu((-40, 15), pi/2.0+0.1, 7.0, MaxHaara-1)) for puu in self.puut: puu.setDaemon(True) puu.start() time.sleep(4.0) for i in range(25): self.pilvet.append(Pilvi()) for pilvi in self.pilvet: pilvi.setDaemon(True) pilvi.start() time.sleep(2.0) def maasto(): pygame.draw.ellipse(tausta, Maasto, (0.4*CXW, 0.85*CYW, 0.9*CXW, CYW), 0) pygame.draw.ellipse(tausta, Musta, (0.4*CXW, 0.85*CYW, 0.9*CXW, CYW), 2) pygame.draw.ellipse(tausta, Maasto, (-0.1*CXW, 0.8*CYW, 0.8*CXW, 0.8*CYW), 0) pygame.draw.ellipse(tausta, Musta, (-0.1*CXW, 0.8*CYW, 0.8*CXW, 0.8*CYW), 2) def lopetus(): for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() sys.exit() if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE: pygame.quit() sys.exit() def naytolle(): portti.acquire() maasto() screen.blit(tausta, (0, 0)) screen.blit(edusta, (0, 0)) pygame.display.flip() tausta.fill(Tausta) if FILL: edusta.fill(Musta) portti.notify_all() portti.release() def main(): global FILL global CXW, CYW, XW, YW, SKAALA global loppu global tausta, edusta, screen global portti
Yleensä peleissä tai kuvia piirrellessä pelimaailman mittayksiköt kannattaa erottaa näytön mittatyksiköistä, koska ohjelman pitää toimia järkevästi eri kokoisilla näytöillä. Voin esimerkiksi sijoittaa puun pisteeseen (-30, 5) riippumatta siitä, montako pikseliä peliruuden koko on näytöllä.
Tässä ohjelmassa pelimaailman korkeus on kiinnitetty sadaksi yksiköksi ja leveys sovitetaan niin, että pelimaailman ja peli-ikkunan kuvasuhde pysyy samana.
pygame.display.init() D_Info = pygame.display.Info() CXW = D_Info.current_w CYW = D_Info.current_h FILL = False YW = 100.0 # Maiseman korkeus. XW = CXW/CYW*YW # Maiseman leveys SKAALA = CYW/YW screen = pygame.display.set_mode((CXW, CYW)) edusta = pygame.Surface(screen.get_size()) tausta = screen.convert_alpha() # tausta.set_alpha(128) # edusta.set_alpha(128) edusta.set_colorkey((0, 0, 0)) tausta.fill(Tausta) portti = threading.Condition() portti.acquire() loppu = False pygame.display.set_caption("puut") pygame.init() random.seed() naytolle() portti.release() puutpilvet = PuutPilvet() puutpilvet.setDaemon(True) puutpilvet.start() loppu = False while not loppu: naytolle() lopetus() time.sleep(DT) pygame.quit() if __name__ == "__main__": main()
Lataa tästä ohjelman tämän hetkinen versio: PuutPilvetSaikeet_0.py