Sisällysluettelo

Python esi­merk­ke­jä


Python esimerkkejä > Python esimerkkejä ()

 

Python esi­merk­ke­jä

Tu­tus­tum­me aluksi muu­ta­maan pieneen esi­merkki­oh­jel­maan. Pi­kai­sel­la vil­kai­sul­la oh­jel­moin­nin aloit­te­li­ja ei saane selvää, 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 paljon oh­jel­moin­nis­ta.

Esi­merk­ki: Fibonnacin luvut

Jos olet kiin­nos­tu­nut ma­te­ma­tii­kas­ta 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ä 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
 

Yleensä tieto­kone suo­rit­taa kir­joit­ta­ma­si oh­jel­man silmän­rä­päyk­ses­sä, mutta jos in­nos­tuit ko­kei­le­maan jonkin ison fibonnacin luvun las­ke­mis­ta, kyl­läs­tyit kenties odot­te­le­maan vas­taus­ta. Spyderin konsoli-ikkunan oi­keas­sa ylä­kul­mas­sa on pu­nai­nen neliö, josta oh­jel­man voi py­säyt­tää, mikäli se tuntuu jääneen "jumiin".

Yleensä ohjelma jää "jumiin" oh­jel­moin­ti­virheen takia, mutta nyt "ju­mit­tu­mi­nen" johtuu siitä, että fibonnacin luvun mää­rit­tä­mi­nen on moni­mut­kai­nen lasku­tehtävä. 35:tä suu­rem­mat fibonnacci luvut osoit­tau­tui­vat minun ko­neel­la­ni niin hi­taik­si laskea, etten jak­sa­nut odottaa tulosta.

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


Pieniä Python-kielisiä esi­merk­ke­jä

Ei kannata pe­läs­tyä, vaikka seu­raa­vat oh­jel­man­pätkät näyt­tä­vät vaikea­selkoisilta. Oh­jel­moin­tia tun­te­mat­to­mal­le ohjelma­koodi näyt­tä­nee täysin kä­sit­tä­mät­tö­mäl­tä. Myös oh­jel­moin­tia tun­te­van on usein vaikea saada selvää toisen kir­joit­ta­mas­ta oh­jel­mas­ta. Vasta har­jaan­tu­nut oh­jel­moi­ja, joka tuntee käy­tet­tä­vän oh­jel­moin­ti­kielen, voi lukea jonkun muun tekemää oh­jel­maa ilman suu­rem­pia vai­keuk­sia.

Python--ohjelmaa lu­kies­sa on hyvä tietää, että rivien si­sen­nys määrää, mihin ylem­pään ko­ko­nai­suu­teen rivi kuuluu. Aivan va­sem­mas­ta lai­das­ta alkavat rivit kuu­lu­vat oh­jel­man pää­tasolle. Yllä funktio fib(n) mää­ri­tel­tiin siis pää­tasolle, 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 kannata yrittää ym­mär­tää yhdellä vil­kai­sul­la. Lataa allaolevan oh­jel­man koodi kehitysympäristöön ja kokeile kuten yllä fibonnaccin lukuja laskevaa funktiota.

Mikäli et ole ins­tal­loi­nut pylab-modulia, laita kom­men­tik­si rivit, joissa vii­ta­taan pylabiin. Eli laita ky­seis­ten rivien alkuun #-merkki. Voit myös yrittää 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

Ohjelma kir­joit­taa kon­so­lil­le seu­raa­vaa, 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]
        

Oh­jel­man suo­ri­tuk­sen lisäksi F5:näppäimen pai­na­mi­nen lataa oh­jel­man python jär­jes­tel­män työ­tilaan niin, että voit kutsua kutsua 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ä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.

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 vaihto­ehtoja. Esi­merk­kien ko­men­not voi ko­pioi­da tar­vit­taes­sa omaan oh­jel­maan­sa.

Lopuksi piir­re­tään funk­tion

y = x 3

kuvaaja. 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 olioita

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 ostavat ja myyvät, mutta esi­merk­ki ei kui­ten­kaan kuvasta to­del­lis­ta kaupan­käyntiä. Siinä ei ole juuri muuta järkeä kuin, että siihen 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

Ohjelma kir­joit­taa kon­so­lil­le seu­raa­vaa, 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

        

Oh­jel­man toi­min­taa lienee vaikea ym­mär­tää, mutta yritä silti. Aloita koh­das­ta "pää­ohjelma alkaa tästä". Aluksi luodaan kaksi oliota A ja B, jotka ovat tyyppiä kaup­pias. Voit aja­tel­la, että olio on määrä­muo­toi­nen arkisto­kortti — muisti­lappu, jol­lai­nen tehdään jo­kai­ses­ta kaup­piaas­ta. Ylem­pä­nä ohjelma­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 helppo ym­mär­tää, miksi käy­te­tään termejä class, luokka, metodi,– – ja siksi ter­meis­sä menee hel­pos­ti se­kai­sin. Paras ottaa mallia yksin­ker­tai­ses­ta esi­mer­kis­tä ja näin aluksi unohtaa termien 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 yrittää luoda vielä kol­man­nen kaup­piaan ja lisätä osto­ta­pah­tu­mia. Jos on­nis­tut, sinulla ei enää ole kovin paljoa 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ä


Python esimerkkejä > Rekursio, säikeet, synkronointi (2016-4-8)

 

Rekursio, säikeet, synk­ro­noin­ti

Tein vuosia sitten oh­jel­man, joka piirsi puun re­kur­sii­vi­sel­la funk­tiol­la. Sen oksien väri ja paksuus riippui etäi­syy­des­tä tyvestä. Oksien kärkiin piirsin lehden tai kukan. Li­sää­mäl­lä pik­kui­sen sa­tun­nai­suut­ta, puiden piir­ty­mis­tä oli hauska kat­sel­la. On­nis­tuin jopa saamaan 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äännö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äässyt, mutta on­nis­tuin­pa saamaan puut piir­ty­mään vähän jou­he­vam­min 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)

 

PuutPilvet.py

Seu­raa­va esi­merk­ki esit­te­lee, miten python-ohjelmointikielessä voi käyttää listoja, olioita ja re­kur­sii­vi­sia funk­tioi­ta.

Ohjelma piirtää näy­töl­le nai­vis­ti­sen mai­se­man oh­jel­moin­ti­kielen piir­tei­den ha­vain­nol­lis­ta­mi­sek­si. Esi­mer­kik­si pilvet ja puun oksat ovat olioita. Puu piir­re­tään re­kur­sii­vi­sel­la oh­jel­mal­la.

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


Se­li­tyk­siä ylläolevaan

Allaolevat import käskyt tuovat tämän oh­jel­man käyt­töön val­mii­ta moduleita, joiden sisältämista funk­tiois­ta saa tietoa mm. webin python-oppaista. Gra­fiik­ka on to­teu­tet­tu pygame-modulin avulla lähinnä 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äyttää missä hyvänsä oh­jel­man osassa.

Pygame-modulissa värit esi­te­tään il­moit­ta­mal­la luvulla väliltä 0..255 pu­nai­sen, vihreän ja sinisen värin osuus.

Musta = (0, 0, 0)  #  Mus­tas­sa 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)  #  val­koi­ses­sa on kaiken­väristä valoa
Tausta = (160, 200, 255)



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

Tyylikkässä oh­jel­mas­sa olisi graa­fi­nen käyttö­liit­ty­mä, joka se­lit­täi­si pa­ra­met­rien mer­ki­tyk­sen ja antaisi mah­dol­li­suu­den muuttaa niiden oletus­arvoja. Ehkä joskus opet­te­len te­ke­mään käyttö­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



Funktio screenxy(x, y) laskee, missä kohtaa 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))



Puiden run­ko­jen väri vaihtuu tyven tumman­rus­keas­ta latva­oksien vaalean­vih­reään. Seu­raa­va funktio laskee puun rungon värin. Oh­jel­mas­sa puu kasvaa vai­heit­tain kuin vuosi­kasvu ker­ral­laan. Nhaara kertoo, monesko vuosi­kasvu lat­vas­ta lukien 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 koostuu oksiksi kut­su­tuis­ta pät­kis­tä. Oksan omi­nai­suu­det ovat pituus, asennon kertova kulma, paksuus ja tieto siitä, monesko pätkä se on lat­vas­ta lukien. Oksalla on myös tieto siitä, minkä ala­puo­li­sen oksan­pätkän varassa se on ja mitkä oksan­pätkä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ätkän kulma ala­puo­lel­la olevaan
        self.pituus = pituus
        self.paksuus = 2.0 + Nhaara
        self.Nhaara = Nhaara  #  Monesko oksan­pätkä lat­vas­ta lukien
        self.alapuolinen = alapuolinen  #  Ala­puo­li­nen oksan­pätkä
        self.vasenhaara = None
        self.oikeahaara = None


Seu­raa­va funtio lisää puuhun tyvestä lähtien oksan­pätkän ker­ral­laan kunnes pääs­tään latvaan asti tai kunnes oksa kääntyy 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ähden - Skulma - on pie­nem­pi kuin 180 astetta ja suu­rem­pi kuin 0. Tätä funk­tio­ta vähän muu­tet­tu­na voisi kutsua suoraan ylläolevassa __init__ funktiossa: self.vasenhaara = kasvata(self, ...

Oksan­pätkän asento­kulma on 0, kun oksa osoit­taa vaaka­suoraan oi­keal­le ja asento­kulma on 180, kun oksa osoit­taa suoraan va­sem­mal­le. Pythonissa kulmat il­moi­te­taan oletus­ar­voi­ses­ti ra­diaa­nei­na, eli 180 astetta on pii ra­diaa­nia.

Uuden oksan­pätkän pituus voisi olla sama kuin sen ala­puo­lel­la ole­van­kin, mutta huvin vuoksi lisäsin puun kas­vul­la tai­pu­muk­sen, että ylä­puo­li­nen on hieman 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ä ainakin yksi oksa kum­paan­kin suun­taan.
            #  las­ke­taan li­sät­tä­väl­le oksan­pät­käl­le pituus ...
            pituus = LCoeff*self.pituus + random.uniform(-DS, DS)
            + Ccos*(cos(Skulma) - 0.5)
            #  ja asento­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 vasenta haaraa on jat­ket­tu latvaan asti,
            #  oh­jel­man suo­ri­tus palaa tähän ja jatkaa oikeaa haaraa ylös.
            #  Pituus ja kulma voi­si­vat olla samat kuin va­sem­mal­le­kin
            #  läh­ties­sä, mutta vaih­te­lun vuoksi tehdää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:
            #  Lopuksi 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 tyvestä lähtien oksan­pätkä ker­ral­laan. Tyveä piir­ret­täes­sä oksan­pätkän alapään paikka (x0, y0) ) on tietysti puun tyven paikka, muussa tapauksessa funtion argumenttina saatava alapuolisen oksan yläpään paikka.

Oksan­pätkän kulma ho­ri­sont­tiin nähden on sen suh­teel­li­nen kulma ala­puo­li­seen oksaan nähden + ar­gu­ment­ti­na saatava kulma0, joka on alapuolisen oksan kulma horisonttiin nähden

Oksan­pä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 ala­puo­li­sen oksan pi­tuu­des­ta
        #  Voisi riippua 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äristä ja -ko­kois­ta ympyrää pääl­lek­käin.
    #  Aloi­te­taan isoim­mas­ta, ettei isompi 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 kutsuva ohjelma ei tiedä,
    #  kä­sit­te­lee­kö se oksan­pätkää vai kukkaa.
    def huoju(self, kulma):
        pass



Pilvet ovat suora­kaiteen sisään piir­ret­tä­viä el­lip­se­jä. Käytän ko­keek­si pygame-modulin olio­luokkaa 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ä piir­täes­sä suoraan peli-ikkunan koor­di­naa­tis­toa, vaikka olisi ollut tyy­lik­kääm­pää käyttää samaa mai­se­man koor­di­naa­tis­toa kuin puita piir­täes­sä.

Pilvet 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-ikkunan oi­keas­ta lai­das­ta,
        #  se siir­re­tään peli-ikkunan va­sem­paan laitaan.
        if self.coords.left > CXW:
            self.coords.move_ip(-CXW-self.coords.width,
                                random.randrange(-1, 2))
        else:
            #  move_ip siirtää pilveä sa­tun­nai­sen hyp­päyk­sen va­sem­mal­le
            #  ja lisäksi 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 muuttaa pilven kokoa
            self.coords.inflate_ip(random.randrange(-1, 2),
                                   random.randrange(-1, 2))
            #  ei anneta pilvien kasvaa rajatta eikä ku­tis­tua ole­mat­to­miin
            #  pi­de­tään pilven korkeus rei­lus­ti pie­nem­pä­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)

 #  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ää ohjelma.
 #  On­gel­ma­na on rea­goi­da kes­key­tys­pyyn­töön moni­ker­tai­sen for-silmukan
 #  sisältä.
 #  En ole vielä ope­tel­lut try - exception ra­ken­teen 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ä pe­leis­sä tai kuvia piir­rel­les­sä peli­maailman mitta­yksiköt kan­nat­taa erottaa näytön mittatyksiköistä, koska oh­jel­man pitää toimia 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 korkeus on kiin­ni­tet­ty sadaksi yk­si­kök­si ja leveys so­vi­te­taan niin, että peli­maailman ja peli-ikkunan kuva­suhde on sama.

Seu­raa­vas­sa on mää­ri­tel­ty peli-ikkunan ja näytön kokoon liit­ty­vät pa­ra­met­rit glo­baa­leik­si muut­tu­jik­si, jotta niitä voi käyttää missä hyvänsä oh­jel­man koh­das­sa. Glo­baa­le­ja muut­tu­jia on hou­kut­te­le­va käyttää 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 kaikki tar­vit­ta­va tieto kutsu­pa­ra­met­rien kautta.



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

    pygame.display.init()

    #  Sel­vi­te­tään näytö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 korkeus.
    XW = CXW/CYW*YW  #  Mai­se­man leveys
    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()

    #  Luodaan sadan pilven lista
    pilvet = []
    for i in range(100):
        pilvet.append(Pilvi())

    #  Piir­re­tään pilvet yksi ker­ral­laan
    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))

    #  Kas­va­te­taan puut ja piir­re­tään ne.
    #  Wait = True eli päi­vi­te­tään näyttö jo­kai­sen
    #  oksan piir­tä­mi­sen 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ään­ny­tään kes­kel­tä ääri­asen­toon ja ta­kai­sin sadalla s:n ko­koi­sel­la
        #  huo­jah­duk­sel­la
        #  Sitten sama toiseen ää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ä toiseen suun­taan kuin vii­mek­si

    pygame.quit()

if __name__ == "__main__":

    main()



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


Python esimerkkejä > Rekursio, säikeet, synkronointi > synkronointi.py (None)

 

synkronointi.py

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

Tue Apr 14 14:02:30 2020


Python esimerkkejä > Rekursio, säikeet, synkronointi > synkronointi.py > Synkronointi (None)

 

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ä ohjelma­ver­sios­sa kukin puu ja pilvi toimii 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 kunkin säikeen suo­ri­tus etenee omaa tah­tiaan -- asynk­ro­ni­ses­ti. Multi­yti­mi­ses­sä pro­ses­so­ris­sa säikeet voi­si­vat edetä aidosti rin­nak­kain, mutta aidosti rin­nak­kai­sen oh­jel­man te­ke­mi­nen vaatii vielä lisä­temp­pu­ja.

Jos säi­kei­den ha­lu­taan ottavan tois­ten­sa suo­ri­tus­vaiheen huo­mioon, ne täytyy laittaa 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 pilven siir­te­lyyn ja piir­tä­mi­seen tar­vit­ta­va ohjelma käyn­nis­te­tään omaksi säi­keek­seen. Ne piir­tä­vät yh­tei­sel­le "kan­kaal­le". Pääohjelmä siirtää määrä­välein "kankaan" si­säl­lön näyttö­muis­tiin ja pyyhkii kankaan tyh­jäk­si. Ellei piir­tä­mi­siä ja näytön päi­vi­tys­tä synkroinoida, näy­töl­le tulee mitä sattuu.


 #  Jo­kai­sen pilven säi­kees­sä on sil­muk­ka, jossa siir­re­tään pilveä ja
 #  piir­re­tään se
while not loppu:
    #  siir­re­tään pilveä
    self.siirra()
    #  ja sitten piir­re­tään pilvi uuteen paik­kaan
    #  Ennen kuin aletaan piirtää, anotaan lupaa kir­joit­taa näy­töl­le
    portti.acquire()
    #  odo­te­taan, kunnes näyttöä päi­vit­tä­vä ohjelma antaa luvan
    portti.wait()
    #  piir­re­tään
    self.piirra()
    #  va­pau­te­taan varaus
    portti.release()


 #  Pää­oh­jel­man säi­kees­sä on sil­muk­ka, joka päi­vit­tää näytön määrä­välein
while not loppu:
    #  Odo­te­taan, että portti va­pau­tuu ja es­te­tään sen jälkeen
    #  muita ohit­ta­mas­ta porttia
    portti.acquire()
    pygame.display.flip()  #  Päi­vi­te­tään näyttö
    screen.fill(Tausta)  #  Tyh­jen­ne­tään "kangas"
    portti.notify_all()  #  Il­moi­te­taan muille säi­keil­le, että portti va­pau­tuu
    portti.release()  #
    #  Odo­tel­laan hetki, että muut säikeet ehtivät piirtää kan­kaal­le jotain
    #  uutta. DT on näytön päi­vi­tys­väli
    time.sleep(DT)



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


Python esimerkkejä > Rekursio, säikeet, synkronointi > PuutPilvetSaikeet.py (None)

 

PuutPilvetSaikeet.py

Tämä on säikeistetty versio edel­li­ses­tä oh­jel­mas­ta. Oh­jel­mas­sa on listoja, olioita, re­kur­sii­vi­sia funk­tioi­ta ja säi­kei­tä. Ohjelma piirtää näy­töl­le nai­vis­ti­sen mai­se­man oh­jel­moin­ti­kielen piir­tei­den ha­vain­nol­lis­ta­mi­sek­si. Esi­mer­kik­si pilvet ja puut ovat omissa säi­keis­sään suo­ri­tet­ta­via olioita. Puut luodaan ja piir­re­tään re­kur­sii­vi­sel­la oh­jel­mal­la.

Tue Apr 14 14:01:49 2020


Python esimerkkejä > Rekursio, säikeet, synkronointi > PuutPilvetSaikeet.py > Säikeiden synkronointia (None)

 

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



Yleinen tapa esittää värit on il­moit­taa luvulla 0..255 pu­nai­sen, vihreän ja sinisen värin osuus.

Musta = (0, 0, 0)  #  Mus­tas­sa ei ole mitään valoa
Sin = (0, 0, 255)  #  vain sinistä
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 kaiken­väristä valoa
Tausta = (160, 200, 255)  #  Taivas



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äyttö­liit­ty­mä näiden muut­ta­mis­ta varten.


DT = 0.05  #  Näytön päi­vi­tys­väli se­kun­tei­na
MaxHaara = 8  #  Tyvestä kunkin haaran latvaan on 8 oksan­pätkää
LCoeff = 0.9  #  Oksan pi­tuu­teen vai­kut­ta­va kerroin
DKulma = 0.3  #  Kulma, jossa ylä­puo­li­nen kulma kääntyy alem­mas­ta
DS = 0.5  #  Selviää myö­hem­min ;-)
Ccos = 2.0    #  Selviää myö­hem­min ;-)



Yleensä pe­leis­sä tai kuvia piir­rel­les­sä peli­maailman mitta­yksiköt kan­nat­taa erottaa näytön mittatyksiköistä, koska oh­jel­man pitää toimia 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ä.

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



Oksan­pätkän mää­rit­te­lyä.

 #  Oksan­pätkän väri riippuu siitä, kuinka kor­keal­la tyvestä 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 oksaan
        self.pituus = pituus
        self.paksuus = 2 + 1.5*Nhaara  #  oksat ohe­ne­vat latvaa kohti
        self.Nhaara = Nhaara  #  Nhaara kertoo, montaka askelta on latvaan
        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 puuhun 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ätkän ab­so­luut­ti­nen kulma ho­ri­sont­tiin nähden)
        if self.Nhaara > 0 and Skulma < pi and Skulma > 0:
            #  Ylem­mil­lä oksilla on "tai­pu­mus" olla alempaa ly­hyem­piä,
            #  mutta sa­tun­nai­suus saattaa muuttaa 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ää sivulle kas­va­via
            + Ccos*(cos(Skulma) - 0.5)
            #  Ylä­puo­li­nen oksan suunta poik­keaa ala­puo­li­ses­ta
            #  Dkulman verran 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äästy latvaan, piir­re­tään vielä kukka
            self.vasenhaara = Kukka(self)
            #  self.oikeahaara = Kukka(self)

    #  Puuta voi huo­jut­taa muut­ta­mal­la kunkin oksan­pätkän kulmaa
    #  Muu­te­taan kulmaa kään­täen ver­ran­nol­li­ses­ti sen pak­suu­teen ja
    #  ja suoraan 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):
        #  portin avulla tah­dis­te­taan puiden piir­tä­mi­nen näytön päi­vit­tä­vän
        #  säikeen kanssa
        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, 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)

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



Yleensä pe­leis­sä tai kuvia piir­rel­les­sä peli­maailman mitta­yksiköt kan­nat­taa erottaa näytön mittatyksiköistä, koska oh­jel­man pitää toimia 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ä, montako pik­se­liä peliruuden koko on näy­töl­lä.

Tässä oh­jel­mas­sa peli­maailman korkeus on kiin­ni­tet­ty sadaksi yk­si­kök­si ja leveys so­vi­te­taan niin, että peli­maailman ja peli-ikkunan kuva­suhde 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  #  Mai­se­man korkeus.
    XW = CXW/CYW*YW  #  Mai­se­man 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ä oh­jel­man tämän het­ki­nen versio: PuutPilvetSaikeet_0.py