Sisällysluettelo

Python esi­merk­ke­jä


Heikin pohteita > Ohjelmointia, matematiikkaa, fysiikkaa … > Python esimerkkejä > Python esimerkkejä ()

Python esi­merk­ke­jä

Tu­tus­tum­me aluk­si muu­ta­maan pie­neen esi­merk­ki­oh­jel­maan. Pi­kai­sel­la vil­kai­sul­la oh­jel­moin­nin aloit­te­li­ja ei saane sel­vää, miten ne toi­mi­vat, mutta koh­tuul­li­sel­la pon­nis­te­lul­la ja ko­kei­lul­la ne opet­ta­vat pal­jon oh­jel­moin­nis­ta.

Esi­merk­ki: Fibonnacin luvut

Jos olet kiin­nos­tu­nut ma­te­ma­tii­kas­ta ja tie­dä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ä funk­tios­ta fib(n) saattaa tunnistaa fibonnacin luvun määritelmän, vaikka merkintätapa onkin eri kuin matematiikan kirjoissa.

Käyn­nis­tä 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.)

../xml/Programming/PythonOpetus/spyder_figs/spyder_fibo.png
 

Yleen­sä tieto­kone suo­rit­taa kir­joit­ta­ma­si oh­jel­man sil­män­rä­päyk­ses­sä, mutta jos in­nos­tuit ko­kei­le­maan jon­kin ison fibonnacin luvun las­ke­mis­ta, kyl­läs­tyit ken­ties odot­te­le­maan vas­taus­ta. Spyderin kon­so­li-ik­ku­nan oi­keas­sa ylä­kul­mas­sa on pu­nai­nen neliö, josta oh­jel­man voi py­säyt­tää, mi­kä­li se tun­tuu jää­neen "ju­miin".

Yleen­sä oh­jel­ma jää "ju­miin" oh­jel­moin­ti­vir­heen takia, mutta nyt "ju­mit­tu­mi­nen" joh­tuu siitä, että fibonnacin luvun mää­rit­tä­mi­nen on moni­mut­kai­nen lasku­teh­tä­vä. 35:tä suu­rem­mat fibonnacci luvut osoit­tau­tui­vat minun ko­neel­la­ni niin hi­taik­si las­kea, etten jak­sa­nut odot­taa tu­los­ta.

Hakukoneile "fibonnacci complexity" jos sinua jäi mie­ti­tyt­tä­mään, miksi noin pie­nen muu­ta­man rivin funk­tion arvon las­ke­mi­nen voi olla lii­kaa te­hok­kaal­le tieto­ko­neel­le.


Pie­niä Python-kielisiä esi­merk­ke­jä

Ei kan­na­ta pe­läs­tyä, vaik­ka seu­raa­vat oh­jel­man­pät­kät näyt­tä­vät vai­kea­sel­koisilta. Oh­jel­moin­tia tun­te­mat­to­mal­le oh­jel­ma­koodi näyt­tä­nee täy­sin kä­sit­tä­mät­tö­mäl­tä. Myös oh­jel­moin­tia tun­te­van on usein vai­kea saada sel­vää toi­sen kir­joit­ta­mas­ta oh­jel­mas­ta. Vasta har­jaan­tu­nut oh­jel­moi­ja, joka tun­tee käy­tet­tä­vän oh­jel­moin­ti­kie­len, voi lukea jon­kun muun te­ke­mää oh­jel­maa ilman suu­rem­pia vai­keuk­sia.

Python--ohjelmaa lu­kies­sa on hyvä tie­tää, että ri­vien si­sen­nys mää­rää, mihin ylem­pään ko­ko­nai­suu­teen rivi kuu­luu. Aivan va­sem­mas­ta lai­das­ta al­ka­vat rivit kuu­lu­vat oh­jel­man pää­ta­sol­le. Yllä funk­tio fib(n) mää­ri­tel­tiin siis pää­ta­sol­le, koska def fib(n): alkaa rivin alusta. Sitä seuraavat sisennetyt rivit kuuluvat funktion määrittelyyn — ne kertovat, mitä funktio tekee.

Seu­raa­vaa esi­merk­kiä ei kan­na­ta yrit­tää ym­mär­tää yh­del­lä vil­kai­sul­la. Lataa allaolevan oh­jel­man koodi kehitysympäristöön ja kokeile kuten yllä fibonnaccin lukuja laskevaa funktiota.

Mi­kä­li et ole ins­tal­loi­nut pylab-modulia, laita kom­men­tik­si rivit, jois­sa vii­ta­taan pylabiin. Eli laita ky­seis­ten ri­vien al­kuun #-merk­ki. Voit myös yrit­tää ins­tal­loi­da pylab-modulin ko­men­nol­la 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

        

Ylläolevan oh­jel­man koodi

Oh­jel­ma kir­joit­taa kon­so­lil­le seu­raa­vaa, kun pai­nat 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]
        

Oh­jel­man suo­ri­tuk­sen li­säk­si F5:näppäimen pai­na­mi­nen lataa oh­jel­man python jär­jes­tel­män työ­ti­laan niin, että voit kut­sua kut­sua funk­tioi­ta kon­so­lil­ta. Esi­mer­kik­si 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ä ih­met­te­let for sil­mu­kas­sa käs­kyä 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.

Esi­mer­keis­sä tu­los­tuk­ses­ta ja syöt­tei­den lu­ke­mi­ses­ta kon­so­lil­ta ei ole hir­veäs­ti ym­mär­ret­tä­vää. Ne esit­te­le­vät eri vaih­to­eh­to­ja. Esi­merk­kien ko­men­not voi ko­pioi­da tar­vit­taes­sa omaan oh­jel­maan­sa.

Lo­puk­si piir­re­tään funk­tion

y = x 3

ku­vaa­ja. Python-kielessä "x po­tens­siin kolme" mer­ki­tää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.

../xml/Programming/PythonOpetus/y_x3.png
 

Pythonin olioi­ta

Seu­raa­va — ei aivan yksin­ker­tai­nen — esi­merk­ki esit­te­lee olio-oh­jel­moin­tia Pythonilla. Tämä opas olio-oh­jel­moin­tiin pythonilla kertoo lisää olio-ohjelmoinnista

Esi­mer­kis­sä kaup­piaat os­ta­vat ja myy­vät, mutta esi­merk­ki ei kui­ten­kaan ku­vas­ta to­del­lis­ta kau­pan­käyn­tiä. Siinä ei ole juuri muuta jär­keä kuin, että sii­hen on upo­tet­tu olio-oh­jel­moin­nin pe­rus­tei­ta.

            # -*- 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()

        

Ylläolevan oh­jel­man koodi

Oh­jel­ma kir­joit­taa kon­so­lil­le seu­raa­vaa, kun pai­nat 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

        

Oh­jel­man toi­min­taa lie­nee vai­kea ym­mär­tää, mutta yritä silti. Aloi­ta koh­das­ta "pää­oh­jel­ma alkaa tästä". Aluk­si luo­daan kaksi olio­ta A ja B, jotka ovat tyyp­piä kaup­pias. Voit aja­tel­la, että olio on määrä­muo­toi­nen ar­kis­to­kort­ti — muis­ti­lappu, jol­lai­nen teh­dään jo­kai­ses­ta kaup­piaas­ta. Ylem­pä­nä oh­jel­ma­koo­dis­sa mää­rit­te­lyn class kaup­pias: 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äs­kyl­lä 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 help­po ym­mär­tää, miksi käy­te­tään ter­me­jä class, luok­ka, me­to­di,– – ja siksi ter­meis­sä menee hel­pos­ti se­kai­sin. Paras ottaa mal­lia yksin­ker­tai­ses­ta esi­mer­kis­tä ja näin aluk­si unoh­taa ter­mien alku­perän päh­käi­ly.

Käs­kyl­lä 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 yrit­tää luoda vielä kol­man­nen kaup­piaan ja li­sä­tä osto­ta­pah­tu­mia. Jos on­nis­tut, si­nul­la ei enää ole kovin pal­joa opit­ta­vaa olio-oh­jel­moin­nin peri­aat­teis­ta.


Esi­merk­ke­jä funk­tioi­den kä­sit­te­lys­tä ja piir­te­lys­tä


Heikin pohteita > Ohjelmointia, matematiikkaa, fysiikkaa … > Python esimerkkejä > Rekursio, säikeet, synkronointi (2016-4-8)

Rekursio, säi­keet, synk­ro­noin­ti

Tein vuo­sia sit­ten oh­jel­man, joka piir­si puun re­kur­sii­vi­sel­la funk­tiol­la. Sen ok­sien väri ja pak­suus riip­pui etäi­syy­des­tä ty­ves­tä. Ok­sien kär­kiin piir­sin leh­den tai kukan. Li­sää­mäl­lä pik­kui­sen sa­tun­nai­suut­ta, pui­den piir­ty­mis­tä oli haus­ka kat­sel­la. On­nis­tuin jopa saa­maan puut huo­ju­maan. Jäin haa­vei­le­maan mai­se­mien luo­mi­ses­ta al­go­rit­mil­la, jossa olisi so­pi­vas­ti yh­dis­tet­ty­nä sään­nön­mu­kai­suut­ta ja sa­tun­nai­suut­ta. Vielä hie­nom­paa, jos jak­sai­si tehdä siitä kolmi­ulot­tei­sen niin, että sitä voisi zoo­mail­la ja kään­nel­lä. Niin pit­käl­le en pääs­syt, mutta on­nis­tuin­pa saa­maan puut piir­ty­mään vähän jou­he­vam­min kuin sil­loin kauan sit­ten.

puu kas­vaa

video varalle, jos yo. ei toimi

ja huo­juu

video varalle, jos yo. ei toimi


Heikin pohteita > Ohjelmointia, matematiikkaa, fysiikkaa … > Python esimerkkejä > Rekursio, säikeet, synkronointi > PuutPilvet.py ()

PuutPilvet.py

Seu­raa­va esi­merk­ki esit­te­lee, miten python-ohjelmointikielessä voi käyt­tää lis­to­ja, olioi­ta ja re­kur­sii­vi­sia funk­tioi­ta.

Oh­jel­ma piir­tää näy­töl­le nai­vis­ti­sen mai­se­man oh­jel­moin­ti­kie­len piir­tei­den ha­vain­nol­lis­ta­mi­sek­si. Esi­mer­kik­si pil­vet ja puun oksat ovat olioi­ta. Puu piir­re­tään re­kur­sii­vi­sel­la oh­jel­mal­la.

Tue Apr 14 14:01:18 2020


Heikin pohteita > Ohjelmointia, matematiikkaa, fysiikkaa … > Python esimerkkejä > Rekursio, säikeet, synkronointi > PuutPilvet.py > - ()

-

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()


Se­li­tyk­siä ylläolevaan

Allaolevat import käs­kyt tuo­vat tämän oh­jel­man käyt­töön val­mii­ta moduleita, joi­den sisältämista funk­tiois­ta saa tie­toa mm. webin python-oppaista. Gra­fiik­ka on to­teu­tet­tu pygame-modulin avul­la lä­hin­nä siksi, että satun tun­te­maan sen en­nes­tään.

import pygame
import sys
from math import sin, cos, pi
import random



Seu­raa­vil­la ri­veil­lä on glo­baa­le­ja va­kioi­ta, joita voi käyt­tää missä hy­vän­sä oh­jel­man osas­sa.

Pygame-modulissa värit esi­te­tään il­moit­ta­mal­la lu­vul­la vä­lil­tä 0..255 pu­nai­sen, vih­reän ja si­ni­sen värin osuus.

Musta = (0, 0, 0)  #  Mus­tas­sa ei ole mi­tään valoa
Sin = (0, 0, 255)  #  vain si­nis­tä
Pun = (255, 0, 0)
Vihr = (0, 255, 0)
Maasto = (100, 120, 10)
Kelt = (255, 255, 0)
Valk = (255, 255, 255)  #  val­koi­ses­sa on kai­ken­vä­ris­tä valoa
Tausta = (160, 200, 255)



Seu­raa­vien pa­ra­met­rien mer­ki­tys sel­viää myö­hem­min.

Tyylikkässä oh­jel­mas­sa olisi graa­fi­nen käyt­tö­liit­ty­mä, joka se­lit­täi­si pa­ra­met­rien mer­ki­tyk­sen ja an­tai­si mah­dol­li­suu­den muut­taa nii­den ole­tus­ar­vo­ja. Ehkä jos­kus opet­te­len te­ke­mään käyt­tö­liit­ty­män tämän­ta­pai­siin oh­jel­miin.

MaxHaara = 10
LCoeff = 0.95
DKulma = 0.3
DS = 0.25
Ccos = 4.0



Funk­tio screenxy(x, y) las­kee, missä koh­taa näy­töl­lä on peli­maailman piste (x, y)



def screenxy(x, y):
    return (int(SKAALA*x+CXW/2.0), int(CYW-SKAALA*y))



Pui­den run­ko­jen väri vaih­tuu tyven tum­man­rus­keas­ta latva­ok­sien vaa­lean­vih­reään. Seu­raa­va funk­tio las­kee puun run­gon värin. Oh­jel­mas­sa puu kas­vaa vai­heit­tain kuin vuosi­kasvu ker­ral­laan. Nhaara ker­too, mo­nes­ko vuosi­kasvu lat­vas­ta lu­kien on vär­jät­tä­vä­nä. MaxHaara on vuosi­kas­vu­jen enim­mäis­määrä.



def puuVari(Nhaara):
    r = 20 + (80-20)*Nhaara/MaxHaara
    g = 240 - (240-20)*Nhaara/MaxHaara
    return (r, g, 0)



Puu koos­tuu ok­sik­si kut­su­tuis­ta pät­kis­tä. Oksan omi­nai­suu­det ovat pi­tuus, asen­non ker­to­va kulma, pak­suus ja tieto siitä, mo­nes­ko pätkä se on lat­vas­ta lu­kien. Ok­sal­la on myös tieto siitä, minkä ala­puo­li­sen oksan­pät­kän va­ras­sa se on ja mitkä oksan­pät­kät läh­te­vät siitä ylös­päin oi­keal­le ja va­sem­mal­le.



class oksa:
    def __init__(self, paikka, alapuolinen, kulma, pituus, Nhaara):
        self.paikka = paikka  #  Puun tyven si­jain­ti
        self.kulma = kulma  #  Oksan­pät­kän kulma ala­puo­lel­la ole­vaan
        self.pituus = pituus
        self.paksuus = 2.0 + Nhaara
        self.Nhaara = Nhaara  #  Mo­nes­ko oksan­pätkä lat­vas­ta lu­kien
        self.alapuolinen = alapuolinen  #  Ala­puo­li­nen oksan­pätkä
        self.vasenhaara = None
        self.oikeahaara = None


Seu­raa­va funtio lisää puu­hun ty­ves­tä läh­tien oksan­pät­kän ker­ral­laan kun­nes pääs­tään lat­vaan asti tai kun­nes oksa kään­tyy maata kohti. Jat­ke­taan siis puun kas­vat­ta­mis­ta niin kauan kuin Nhaara > 0 ja ja kulma ho­ri­sont­tiin näh­den - Skulma - on pie­nem­pi kuin 180 as­tet­ta ja suu­rem­pi kuin 0. Tätä funk­tio­ta vähän muu­tet­tu­na voisi kut­sua suo­raan ylläolevassa __init__ funktiossa: self.vasenhaara = kas­va­ta(self, ...

Oksan­pät­kän asen­to­kulma on 0, kun oksa osoit­taa vaaka­suo­raan oi­keal­le ja asen­to­kulma on 180, kun oksa osoit­taa suo­raan va­sem­mal­le. Pythonissa kul­mat il­moi­te­taan ole­tus­ar­voi­ses­ti ra­diaa­nei­na, eli 180 as­tet­ta on pii ra­diaa­nia.

Uuden oksan­pät­kän pi­tuus voisi olla sama kuin sen ala­puo­lel­la ole­van­kin, mutta huvin vuok­si li­sä­sin puun kas­vul­la tai­pu­muk­sen, että ylä­puo­li­nen on hie­man ala­puo­lis­ta oksaa ly­hyem­pi (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:
            #  li­sä­tään vielä ai­na­kin yksi oksa kum­paan­kin suun­taan.
            #  las­ke­taan li­sät­tä­väl­le oksan­pät­käl­le pi­tuus ...
            pituus = LCoeff*self.pituus + random.uniform(-DS, DS)
            + Ccos*(cos(Skulma) - 0.5)
            #  ja asen­to­kulma
            kulma = DKulma + random.uniform(-0.05, 0.05)
            #  li­sä­tään oksan­pätkä ...
            self.vasenhaara = oksa(self.paikka, self, kulma,
                                   pituus, self.Nhaara-1)
            #  ... ja jat­ke­taan puun kas­va­tus­ta li­sä­tys­tä oksan­pät­käs­tä ylös­päin
            self.vasenhaara.kasvata(Skulma+kulma)

            #  kun va­sen­ta haa­raa on jat­ket­tu lat­vaan asti,
            #  oh­jel­man suo­ri­tus palaa tähän ja jat­kaa oi­keaa haa­raa ylös.
            #  Pi­tuus ja kulma voi­si­vat olla samat kuin va­sem­mal­le­kin
            #  läh­ties­sä, mutta vaih­te­lun vuok­si teh­dään oi­keas­ta haa­ras­ta
            #  vähän eri­lai­nen
            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:
            #  Lo­puk­si oksan pää­hän li­sä­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)



Piir­re­tään puu re­kur­sii­vi­ses­ti ty­ves­tä läh­tien oksan­pätkä ker­ral­laan. Tyveä piir­ret­täes­sä oksan­pät­kän ala­pään paik­ka (x0, y0) ) on tietysti puun tyven paikka, muussa tapauksessa funtion argumenttina saatava alapuolisen oksan yläpään paikka.

Oksan­pät­kän kulma ho­ri­sont­tiin näh­den on sen suh­teel­li­nen kulma ala­puo­li­seen ok­saan näh­den + ar­gu­ment­ti­na saa­ta­va kulma0, joka on alapuolisen oksan kulma horisonttiin nähden

Oksan­pät­kän ylä­pään paik­ka (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 riip­puu ala­puo­li­sen oksan pi­tuu­des­ta
        #  Voisi riip­pua oksan pak­suu­des­ta tai olla vakio
        self.size = pituus/20.0
        self.colors = [Kelt, Sin, Pun, Kelt]

    #  Piir­re­tään kolme eri­vä­ris­tä ja -ko­kois­ta ym­py­rää pääl­lek­käin.
    #  Aloi­te­taan isoim­mas­ta, ettei isom­pi peitä pie­nem­pään­sä.
    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()

    #  Ku­kal­la­kin pitää olla huoju-metodi, koska kut­su­va oh­jel­ma ei tiedä,
    #  kä­sit­te­lee­kö se oksan­pät­kää vai kuk­kaa.
    def huoju(self, kulma):
        pass



Pil­vet ovat suora­kai­teen si­sään piir­ret­tä­viä el­lip­se­jä. Käy­tän ko­keek­si pygame-modulin olio­luok­kaa 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äy­tän pil­viä piir­täes­sä suo­raan peli-ik­ku­nan koor­di­naa­tis­toa, vaik­ka olisi ollut tyy­lik­kääm­pää käyt­tää samaa mai­se­man koor­di­naa­tis­toa kuin puita piir­täes­sä.

Pil­vet liik­ku­vat va­sem­mal­ta oi­keal­le.



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 ajau­tu­nut ulos peli-ik­ku­nan oi­keas­ta lai­das­ta,
        #  se siir­re­tään peli-ik­ku­nan va­sem­paan lai­taan.
        if self.coords.left > CXW:
            self.coords.move_ip(-CXW-self.coords.width,
                                random.randrange(-1, 2))
        else:
            #  move_ip siir­tää pil­veä sa­tun­nai­sen hyp­päyk­sen va­sem­mal­le
            #  ja li­säk­si pik­kui­sen 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 muut­taa pil­ven kokoa
            self.coords.inflate_ip(random.randrange(-1, 2),
                                   random.randrange(-1, 2))
            #  ei an­ne­ta pil­vien kas­vaa ra­jat­ta eikä ku­tis­tua ole­mat­to­miin
            #  pi­de­tään pil­ven kor­keus rei­lus­ti pie­nem­pä­nä kuin le­veys
            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)

 #  Piir­re­tään kaksi kuk­ku­laa ja ra­ja­taan ne mus­tal­la vii­val­la
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)


 #  Tar­kis­te­taan, ha­luaa­ko käyt­tä­jä py­säyt­tää oh­jel­man.
 #  Tämä ei ole aivan tyy­li­käs tapa py­säyt­tää oh­jel­ma.
 #  On­gel­ma­na on rea­goi­da kes­key­tys­pyyn­töön moni­ker­tai­sen for-silmukan
 #  si­säl­tä.
 #  En ole vielä ope­tel­lut try - exception ra­ken­teen käyt­töä.
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ää­oh­jel­ma alkaa tästä
 #  # # # # # # # # # # # # # # # # # # # # # # # # # #



Yleen­sä pe­leis­sä tai kuvia piir­rel­les­sä peli­maailman mitta­yk­si­köt kan­nat­taa erot­taa näy­tön mittatyksiköistä, koska oh­jel­man pitää toi­mia jär­ke­väs­ti eri ko­koi­sil­la näy­töil­lä. Voin esi­mer­kik­si si­joit­taa puun pis­tee­seen (-30, 5) riippumatta siitä, montako pikseliä peliruuden koko on näytöllä.

Tässä oh­jel­mas­sa peli­maailman kor­keus on kiin­ni­tet­ty sa­dak­si yk­si­kök­si ja le­veys so­vi­te­taan niin, että peli­maailman ja peli-ik­ku­nan kuva­suhde on sama.

Seu­raa­vas­sa on mää­ri­tel­ty peli-ik­ku­nan ja näy­tön ko­koon liit­ty­vät pa­ra­met­rit glo­baa­leik­si muut­tu­jik­si, jotta niitä voi käyt­tää missä hy­vän­sä oh­jel­man koh­das­sa. Glo­baa­le­ja muut­tu­jia on hou­kut­te­le­va käyt­tää eri­tyi­ses­ti oh­jel­maa ke­hit­täes­sä. Tur­val­li­sem­paa, mutta työ­lääm­pää, on vä­lit­tää funk­tioil­le kaik­ki tar­vit­ta­va tieto kutsu­pa­ra­met­rien kaut­ta.



def main():
    global CXW, CYW, XW, YW, SKAALA

    pygame.display.init()

    #  Sel­vi­te­tään näy­tön koko pik­se­lei­nä
    D_Info = pygame.display.Info()
    CXW = D_Info.current_w
    CYW = D_Info.current_h

    YW = 100.0  #  Mai­se­man kor­keus.
    XW = CXW/CYW*YW  #  Mai­se­man le­veys
    SKAALA = CYW/YW

    screen = pygame.display.set_mode((CXW, CYW))
    pygame.display.set_caption("puut")
    pygame.init()

    random.seed()

    screen.fill(Tausta)

    #  Piir­re­tään kuk­ku­lat
    maasto(screen)
    pygame.display.flip()

    #  Luo­daan sadan pil­ven lista
    pilvet = []
    for i in range(100):
        pilvet.append(Pilvi())

    #  Piir­re­tään pil­vet yksi ker­ral­laan
    for pilvi in pilvet:
        pilvi.piirra(screen)
        pygame.display.flip()
        pygame.time.wait(50)

    #  Luo­daan "metsä" eli lista puis­ta
    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))

    #  Kas­va­te­taan puut ja piir­re­tään ne.
    #  Wait = True eli päi­vi­te­tään näyt­tö jo­kai­sen
    #  oksan piir­tä­mi­sen jäl­keen.
    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ään­ny­tään kes­kel­tä ääri­asen­toon ja ta­kai­sin sa­dal­la s:n ko­koi­sel­la
        #  huo­jah­duk­sel­la
        #  Sit­ten sama toi­seen ääri­asen­toon
        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ään­ny­tään ääri­asen­nos­ta ta­kai­sin
        s = -s  #  Läh­de­tään kes­kel­tä toi­seen suun­taan kuin vii­mek­si

    pygame.quit()

if __name__ == "__main__":

    main()



Lataa tästä oh­jel­man tämän het­ki­nen ver­sio: PuutPilvet_0.py


Heikin pohteita > Ohjelmointia, matematiikkaa, fysiikkaa … > Python esimerkkejä > Rekursio, säikeet, synkronointi > synkronointi.py ()

synkronointi.py

Eri säi­keis­sä toi­mi­vien oh­jel­mien synk­ro­noin­ti

Tue Apr 14 14:02:30 2020


Heikin pohteita > Ohjelmointia, matematiikkaa, fysiikkaa … > Python esimerkkejä > Rekursio, säikeet, synkronointi > synkronointi.py > Synkronointi ()

Synk­ro­noin­ti

# 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)


Se­li­tyk­siä ylläolevaan

Tässä oh­jel­ma­ver­sios­sa kukin puu ja pilvi toi­mii eri säikessään. Oh­jel­moi­jan on hyvä aja­tel­la, että säi­kei­tä suo­ri­te­taan rin­nak­kain, koska kun­kin säi­keen suo­ri­tus ete­nee omaa tah­tiaan -- asynk­ro­ni­ses­ti. Multi­yti­mi­ses­sä pro­ses­so­ris­sa säi­keet voi­si­vat edetä ai­dos­ti rin­nak­kain, mutta ai­dos­ti rin­nak­kai­sen oh­jel­man te­ke­mi­nen vaa­tii vielä lisä­temp­pu­ja.

Jos säi­kei­den ha­lu­taan ot­ta­van tois­ten­sa suo­ri­tus­vai­heen huo­mioon, ne täy­tyy lait­taa vä­lit­tä­mään toi­sil­leen vies­te­jä. Seu­raa­vas­sa esi­te­tään yksi tapa synk­ro­noi­da säi­kei­den suo­ri­tus.

Jo­kai­sen puun ja pil­ven siir­te­lyyn ja piir­tä­mi­seen tar­vit­ta­va oh­jel­ma käyn­nis­te­tään omak­si säi­keek­seen. Ne piir­tä­vät yh­tei­sel­le "kan­kaal­le". Pääohjelmä siir­tää määrä­vä­lein "kan­kaan" si­säl­lön näyt­tö­muis­tiin ja pyyh­kii kan­kaan tyh­jäk­si. Ellei piir­tä­mi­siä ja näy­tön päi­vi­tys­tä synkroinoida, näy­töl­le tulee mitä sat­tuu.


 #  Jo­kai­sen pil­ven säi­kees­sä on sil­muk­ka, jossa siir­re­tään pil­veä ja
 #  piir­re­tään se
while not loppu:
    #  siir­re­tään pil­veä
    self.siirra()
    #  ja sit­ten piir­re­tään pilvi uu­teen paik­kaan
    #  Ennen kuin ale­taan piir­tää, ano­taan lupaa kir­joit­taa näy­töl­le
    portti.acquire()
    #  odo­te­taan, kun­nes näyt­töä päi­vit­tä­vä oh­jel­ma antaa luvan
    portti.wait()
    #  piir­re­tään
    self.piirra()
    #  va­pau­te­taan va­raus
    portti.release()


 #  Pää­oh­jel­man säi­kees­sä on sil­muk­ka, joka päi­vit­tää näy­tön määrä­vä­lein
while not loppu:
    #  Odo­te­taan, että port­ti va­pau­tuu ja es­te­tään sen jäl­keen
    #  muita ohit­ta­mas­ta port­tia
    portti.acquire()
    pygame.display.flip()  #  Päi­vi­te­tään näyt­tö
    screen.fill(Tausta)  #  Tyh­jen­ne­tään "kan­gas"
    portti.notify_all()  #  Il­moi­te­taan muil­le säi­keil­le, että port­ti va­pau­tuu
    portti.release()  #
    #  Odo­tel­laan hetki, että muut säi­keet eh­ti­vät piir­tää kan­kaal­le jo­tain
    #  uutta. DT on näy­tön päi­vi­tys­väli
    time.sleep(DT)



Lataa tästä oh­jel­man tämän het­ki­nen ver­sio: synk­ro­noin­ti_0.py


Heikin pohteita > Ohjelmointia, matematiikkaa, fysiikkaa … > Python esimerkkejä > Rekursio, säikeet, synkronointi > PuutPilvetSaikeet.py ()

PuutPilvetSaikeet.py

Tämä on säikeistetty ver­sio edel­li­ses­tä oh­jel­mas­ta. Oh­jel­mas­sa on lis­to­ja, olioi­ta, re­kur­sii­vi­sia funk­tioi­ta ja säi­kei­tä. Oh­jel­ma piir­tää näy­töl­le nai­vis­ti­sen mai­se­man oh­jel­moin­ti­kie­len piir­tei­den ha­vain­nol­lis­ta­mi­sek­si. Esi­mer­kik­si pil­vet ja puut ovat omis­sa säi­keis­sään suo­ri­tet­ta­via olioi­ta. Puut luo­daan ja piir­re­tään re­kur­sii­vi­sel­la oh­jel­mal­la.

Tue Apr 14 14:01:49 2020


Heikin pohteita > Ohjelmointia, matematiikkaa, fysiikkaa … > Python esimerkkejä > Rekursio, säikeet, synkronointi > PuutPilvetSaikeet.py > Säikeiden synkronointia ()

Säi­kei­den synk­ro­noin­tia

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()


Se­li­tyk­siä ylläolevaan

import threading
import time
import pygame
import sys
from math import sin, cos, pi
import random



Ylei­nen tapa esit­tää värit on il­moit­taa lu­vul­la 0..255 pu­nai­sen, vih­reän ja si­ni­sen värin osuus.

Musta = (0, 0, 0)  #  Mus­tas­sa ei ole mi­tään valoa
Sin = (0, 0, 255)  #  vain si­nis­tä
Pun = (255, 0, 0)
Vihr = (0, 255, 0)
Maasto = (100, 120, 10)  #  Kuk­ku­loi­den väri
Kelt = (255, 255, 0)
Valk = (255, 255, 255)  #  val­koi­ses­sa on kai­ken­vä­ris­tä valoa
Tausta = (160, 200, 255)  #  Tai­vas



Eri­näi­siä glo­baa­le­ja va­kioi­ta. Tyy­lik­kääs­sä oh­jel­mas­sa olisi graa­fi­nen käyt­tö­liit­ty­mä näi­den muut­ta­mis­ta var­ten.


DT = 0.05  #  Näy­tön päi­vi­tys­väli se­kun­tei­na
MaxHaara = 8  #  Ty­ves­tä kun­kin haa­ran lat­vaan on 8 oksan­pät­kää
LCoeff = 0.9  #  Oksan pi­tuu­teen vai­kut­ta­va ker­roin
DKulma = 0.3  #  Kulma, jossa ylä­puo­li­nen kulma kään­tyy alem­mas­ta
DS = 0.5  #  Sel­viää myö­hem­min ;-)
Ccos = 2.0    #  Sel­viää myö­hem­min ;-)



Yleen­sä pe­leis­sä tai kuvia piir­rel­les­sä peli­maailman mitta­yk­si­köt kan­nat­taa erot­taa näy­tön mittatyksiköistä, koska oh­jel­man pitää toi­mia jär­ke­väs­ti eri ko­koi­sil­la näy­töil­lä. Voin esi­mer­kik­si si­joit­taa puun pis­tee­seen (-30, 5) riippumatta siitä, montako pikseliä peliruuden koko on näytöllä.

Funk­tio 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))



Oksan­pät­kän mää­rit­te­lyä.

 #  Oksan­pät­kän väri riip­puu siitä, kuin­ka kor­keal­la ty­ves­tä se on
def puuVari(Nhaara):
    r = 20 + (80-20)*Nhaara/MaxHaara
    g = 240 - (240-20)*Nhaara/MaxHaara
    return (r, g, 0)


 #  Oksan­pätkä
class Oksa():
    def __init__(self, tyvi, kulma, pituus, Nhaara):
        self.kulma = kulma  #  Kulma suh­tees­sa ala­puo­li­seen ok­saan
        self.pituus = pituus
        self.paksuus = 2 + 1.5*Nhaara  #  oksat ohe­ne­vat lat­vaa kohti
        self.Nhaara = Nhaara  #  Nhaara ker­too, montaka as­kel­ta on lat­vaan
        self.tyvi = tyvi  #  ala­puo­li­nen oksa
        self.vasenhaara = None  #  ylä­puo­li­nen oksa va­sem­mal­le
        self.oikeahaara = None

    #  Li­sä­tään puu­hun re­kur­sii­vi­ses­ti oksa ker­ral­laan
    def kasvataPuu(self, Skulma):
        #  Li­sä­tään oksan­pätkä, jos Nhaara ei vielä ole 0
        #  eikä oksa "roiku" alas­päin
        #  (Skulma on oksan­pät­kän ab­so­luut­ti­nen kulma ho­ri­sont­tiin näh­den)
        if self.Nhaara > 0 and Skulma < pi and Skulma > 0:
            #  Ylem­mil­lä ok­sil­la on "tai­pu­mus" olla alem­paa ly­hyem­piä,
            #  mutta sa­tun­nai­suus saat­taa muut­taa ti­lan­teen.
            pituus = LCoeff*self.pituus + random.uniform(-DS, DS)
            #  li­sä­tään vielä termi, joka pi­den­tää ylös­päin osoit­ta­via
            #  oksia ja ly­hen­tää si­vul­le kas­va­via
            + Ccos*(cos(Skulma) - 0.5)
            #  Ylä­puo­li­nen oksan suun­ta poik­keaa ala­puo­li­ses­ta
            #  Dkulman ver­ran li­sät­ty­nä pie­nel­lä sa­tun­nai­sel­la ter­mil­lä
            kulma = DKulma*(4.0+self.Nhaara)/(1.0*MaxHaara)
            + random.uniform(-0.1, 0.1)
            #  Li­sä­tään oksan­pätkä ny­kyi­sen ylä­puo­lel­le
            self.vasenhaara = Oksa(self, kulma, pituus, self.Nhaara-1)
            #  ja kas­va­te­taan puuta siitä ylös­päin
            self.vasenhaara.kasvataPuu(Skulma+kulma)

            #  Sama oi­keal­le haa­ral­le
            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ääs­ty lat­vaan, piir­re­tään vielä kukka
            self.vasenhaara = Kukka(self)
            #  self.oikeahaara = Kukka(self)

    #  Puuta voi huo­jut­taa muut­ta­mal­la kun­kin oksan­pät­kän kul­maa
    #  Muu­te­taan kul­maa kään­täen ver­ran­nol­li­ses­ti sen pak­suu­teen ja
    #  ja suo­raan ver­ran­nol­li­ses­ti sen pi­tuu­teen.
    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)

    #  Piir­re­tään oksa ker­ral­laan
    def piirra_w(self, paikka, kulma0):
        #  por­tin avul­la tah­dis­te­taan pui­den piir­tä­mi­nen näy­tön päi­vit­tä­vän
        #  säi­keen kans­sa
        portti.acquire()
        portti.wait()
        #  Loppu on tri­go­no­met­riaa ;-)
        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)
        #  piir­tä­mis­ten tah­dis­ta­mi­sen hel­pot­ta­mi­sek­si käy­te­tään kahta eri
        #  kan­gas­ta, edus­ta ja taus­ta
        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)

    #  Piir­re­tään  ker­ral­la 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

 #  Jo­kai­nen olio puu käyn­nis­te­tään omak­si säi­keek­seen
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



Yleen­sä pe­leis­sä tai kuvia piir­rel­les­sä peli­maailman mitta­yk­si­köt kan­nat­taa erot­taa näy­tön mittatyksiköistä, koska oh­jel­man pitää toi­mia jär­ke­väs­ti eri ko­koi­sil­la näy­töil­lä. Voin esi­mer­kik­si si­joit­taa puun pis­tee­seen (-30, 5) riip­pu­mat­ta siitä, mon­ta­ko pik­se­liä peliruuden koko on näy­töl­lä.

Tässä oh­jel­mas­sa peli­maailman kor­keus on kiin­ni­tet­ty sa­dak­si yk­si­kök­si ja le­veys so­vi­te­taan niin, että peli­maailman ja peli-ik­ku­nan kuva­suhde pysyy sa­ma­na.

    pygame.display.init()
    D_Info = pygame.display.Info()
    CXW = D_Info.current_w
    CYW = D_Info.current_h

    FILL = False

    YW = 100.0  #  Mai­se­man kor­keus.
    XW = CXW/CYW*YW  #  Mai­se­man le­veys
    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ä oh­jel­man tämän het­ki­nen ver­sio: PuutPilvetSaikeet_0.py