Sisällysluettelo

Python esimerkkejä


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

 

Python esimerkkejä

Tutustumme aluksi muutamaan pieneen esimerkkiohjelmaan. Pikaisella vilkaisulla ohjelmoinnin aloittelija ei saane selvää, miten ne toimivat, mutta kohtuullisella ponnistelulla ja kokeilulla ne opettavat paljon ohjelmoinnista.

Esimerkki: Fibonnacin luvut

Jos olet kiinnostunut matematiikasta ja tiedät, mitä ovat fibonnacin luvut sinua saattaa hetken säväyttää seuraava ohjelma. Ellet tunne fibonnacin lukuja, harjoitus tuntunee tyhmältä, mutta tee se silti.

            # Määritellään funktio fib(n), jolla on yksi argumentti, n
            def fib(n):
            if n == 0:    # Jos n on yhtäsuuri kuin nolla,
            return 0  # funktio saa arvon 0
            elif n == 1:  # muuten, jos n on yhtäsuuri kuin 1
            return 1  # funktio saa arvon 1
            else:         # muissa tapauksissa funktio saa arvokseen
            return fib(n-1) + fib(n-2)  # kahden edeltävän fibonnacin luvun summan

            # Pääohjelma
            print(fib(5))
        

Python-kielisestä funktiosta fib(n) saattaa tunnistaa fibonnacin luvun määritelmän, vaikka merkintätapa onkin eri kuin matematiikan kirjoissa.

Käynnistä spyder, kopio ylläoleva sen editointi-ikkunaan allaolevan kuvan mukaisesti ja tallenna tiedostoon fibonnacci.py. Suorita ohjelma painamalla F5. Samall F5-painallus tuo ohjelmamodulin funktion järjestelmän tietoon niin, että voit kutsua sitä myös konsolilta — kirjoittamalla esimerkiksi fib(12) kuvan mukaisesti spyder-ohjelmointiympäristön oikeassa alalaidassa olevan konsoli-ikkunan komentoriville. (Ilman F5-painallusta saat funktion konsolilta kutsuttavaksi kirjoittamalla konsolille from fibonnacci import fib.)

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

Yleensä tietokone suorittaa kirjoittamasi ohjelman silmänräpäyksessä, mutta jos innostuit kokeilemaan jonkin ison fibonnacin luvun laskemista, kyllästyit kenties odottelemaan vastausta. Spyderin konsoli-ikkunan oikeassa yläkulmassa on punainen neliö, josta ohjelman voi pysäyttää, mikäli se tuntuu jääneen "jumiin".

Yleensä ohjelma jää "jumiin" ohjelmointivirheen takia, mutta nyt "jumittuminen" johtuu siitä, että fibonnacin luvun määrittäminen on monimutkainen laskutehtävä. 35:tä suuremmat fibonnacci luvut osoittautuivat minun koneellani niin hitaiksi laskea, etten jaksanut odottaa tulosta.

Hakukoneile "fibonnacci complexity" jos sinua jäi mietityttämään, miksi noin pienen muutaman rivin funktion arvon laskeminen voi olla liikaa tehokkaalle tietokoneelle.


Pieniä Python-kielisiä esimerkkejä

Ei kannata pelästyä, vaikka seuraavat ohjelmanpätkät näyttävät vaikeaselkoisilta. Ohjelmointia tuntemattomalle ohjelmakoodi näyttänee täysin käsittämättömältä. Myös ohjelmointia tuntevan on usein vaikea saada selvää toisen kirjoittamasta ohjelmasta. Vasta harjaantunut ohjelmoija, joka tuntee käytettävän ohjelmointikielen, voi lukea jonkun muun tekemää ohjelmaa ilman suurempia vaikeuksia.

Python--ohjelmaa lukiessa on hyvä tietää, että rivien sisennys määrää, mihin ylempään kokonaisuuteen rivi kuuluu. Aivan vasemmasta laidasta alkavat rivit kuuluvat ohjelman päätasolle. Yllä funktio fib(n) määriteltiin siis päätasolle, koska def fib(n): alkaa rivin alusta. Sitä seuraavat sisennetyt rivit kuuluvat funktion määrittelyyn — ne kertovat, mitä funktio tekee.

Seuraavaa esimerkkiä ei kannata yrittää ymmärtää yhdellä vilkaisulla. Lataa allaolevan ohjelman koodi kehitysympäristöön ja kokeile kuten yllä fibonnaccin lukuja laskevaa funktiota.

Mikäli et ole installoinut pylab-modulia, laita kommentiksi rivit, joissa viitataan pylabiin. Eli laita kyseisten rivien alkuun #-merkki. Voit myös yrittää installoida pylab-modulin komennolla pip install pylab tai jos käytät Python3:a komennollapip3 install pylab. Jos se aiheuttaa virheilmoituksia, jätä sikseen vähäksi aikaa. Emme tarvitse pylabia tästä eteenpäin.

            # -*- coding: utf-8 -*-

            import pylab

            # lasketaan kokonaislukujen 1..k summa for-silmukan avulla
            # tulostetaan joka kierroksella välitulos
            # huom. Pythonin range(k) antaa luvut 0:sta (k-1):een.

            def summa(k):
            s = 0
            for i in range(k+1):
            s = s+(i)
            print("i: ", i, "s: ", s)
            return s


            # lasketaan kokonaiskukujen 1..k rekursiivisella funktiolla.
            # Rekursiossa funktio kutsuu itseään, kunnes lopetusehto
            # (tässä tapauksessa k == 0) toteutuu.
            def summaR(k):
            print("summaR ", k)
            if k == 0:
            return 0
            return k+summaR(k-1)


            # Esimerkkejä eri tavoista saada tulostettua jotain konsolille
            def mjonot():
            s = "Tämä on merkkijono "
            m = 53
            print(s)
            print("luku ja merkkijono ", m, str(m))
            print("kaksi merkkijonoa yhdistetty + merkillä: ")
            print(s+str(m))
            m = 120
            n = 15
            print(m+n)
            print(str(m)+str(n))
            print(str(m)+' '+str(n))


            # Esimerkki siitä, miten lukea konsolilta.
            # Input tuottaa merkkijonon, joten muutamme sen int()-funktiolla
            # kokonaisluvuksi
            def kysymys():
            N = input("anna luku: ")
            print('luku on:', N, type(N))
            S = summa(int(N))
            print("lukujen 1..", N, " summa on: ", S)
            print("lukujen 1.."+str(N)+" summa on: "+str(S))


            # piirretään funktion y = x**3 kuvaaja
            # Muodostetaan listat/taulukot xx ja yy toisiaan vastaavilla
            # x ja y arvoille.
            # loppu hoituu pylab-funktioilla
            # huom. Pythonin range(i,j) antaa luvut i:stä (j-1):een.

            def kuvaaja():
            xx = [i for i in range(-10, 11)]
            yy = [i**3 for i in range(-10, 11)]

            print("xx:")
            print(xx)
            print("yy:")
            print(yy)

            pylab.plot(xx, yy)
            pylab.title('y = x**3')
            pylab.ylabel("x**3")
            pylab.xlabel("x")
            pylab.savefig('y_x3.png')
            pylab.show()

            # Huom. Merkkijoissa \n aiheuttaa rivinvaihdon
            print('\npääohjelma\n')

            print("summa(5):")
            s = summa(5)
            print(s)
            print("\nsummaR(5): ")
            s = summaR(5)
            print(s)

            print("\nmerkkijonot")
            mjonot()

            kysymys()

            print("\nkuvaaja:")
            kuvaaja()

            # voit kutsua funktiota konsolilta

        

Ylläolevan ohjelman koodi

Ohjelma kirjoittaa konsolille seuraavaa, kun painat F5:

            pääohjelma

            summa(5):
            i:  0 s:  0
            i:  1 s:  1
            i:  2 s:  3
            i:  3 s:  6
            i:  4 s:  10
            i:  5 s:  15
            15

            summaR(5):
            summaR  5
            summaR  4
            summaR  3
            summaR  2
            summaR  1
            summaR  0
            15

            merkkijonot
            Tämä on merkkijono
            luku ja merkkijono  53 53
            kaksi merkkijonoa yhdistetty + merkillä:
            Tämä on merkkijono 53
            135
            12015
            120 15

            anna luku: 4
            luku on: 4 <class 'str'>
            i:  0 s:  0
            i:  1 s:  1
            i:  2 s:  3
            i:  3 s:  6
            i:  4 s:  10
            lukujen 1.. 4  summa on:  10
            lukujen 1..4 summa on: 10

            kuvaaja:
            xx:
            [-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
            yy:
            [-1000, -729, -512, -343, -216, -125, -64, -27, -8, -1, 0, 1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]
        

Ohjelman suorituksen lisäksi F5:näppäimen painaminen lataa ohjelman python järjestelmän työtilaan niin, että voit kutsua kutsua funktioita konsolilta. Esimerkiksi summa(3) laskee summan 0+1+2+3. Funktion koodin mukaisesti ensin asetetaan muuttujalle s arvo 0 ja sen jälkeen käydään foor-silmukassa läpi i:n arvot nollasta kolmeen ja joka kierroksella lisätään kukin i s:ään ja tulostetaan muuttujien arvot. Lopuksi funktio palauttaa s:n lopullisen arvon.

Ehkä ihmettelet for silmukassa käskyä range(k+1). Miksei range(k)? Kirjoita konsolin komentoriville range(3) niin saat listan [0,1,2]. Voit myös kirjoittaa range(1,3) niin saat listan [1,2]. Jostain syystä on nähty järkevästi, että range-käsky toimii noin.

Funtion summaR(k) näyttää, miten funktio voi kutsua itseään. Esimerkiksi lukujen yhdestä kolmeen summa on kolme plus lukujen yhdestä kahteen summa. Rekursiivisen funktion toimintaa voi olla vaikea hahmottaa, mutta yritä kuitenkin. Välitulostukset saattaavat auttaa.

Esimerkeissä tulostuksesta ja syötteiden lukemisesta konsolilta ei ole hirveästi ymmärrettävää. Ne esittelevät eri vaihtoehtoja. Esimerkkien komennot voi kopioida tarvittaessa omaan ohjelmaansa.

Lopuksi piirretään funktion

y = x 3

kuvaaja. Python-kielessä "x potenssiin kolme" merkitään x**3. Piirtämisessä käytetään pylab-nimisen modulin funktiota plot, jolle pitää antaa kaksi argumenttia — luettelo x:n arvoista ja luettelo niitä vastaavista y:n arvoista.

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

Pythonin olioita

Seuraava — ei aivan yksinkertainen — esimerkki esittelee olio-ohjelmointia Pythonilla. Tämä opas olio-ohjelmointiin pythonilla kertoo lisää olio-ohjelmoinnista

Esimerkissä kauppiaat ostavat ja myyvät, mutta esimerkki ei kuitenkaan kuvasta todellista kaupankäyntiä. Siinä ei ole juuri muuta järkeä kuin, että siihen on upotettu olio-ohjelmoinnin perusteita.

            # -*- coding: utf-8 -*-
            """
            Created on Wed May 27 21:10:55 2015

            @author: heikki
            """

            # Määritellään, minkä tyyppisiä olioita ovat kauppiaat tässä ohjelmassa
            # Kullakin kauppiaalle on ominaisuudet varasto, kassa, tavaran yksikköhinta
            # ja nimi
            class kauppias():
            def __init__(self, kpl, Eur, hinta, nimi):
            self.varasto = kpl
            self.kassa = Eur
            self.hinta = hinta
            self.nimi = nimi

            # Kauppiaaseen liittyy metodi report, joka kertoo kauppiaan liiketoiminnan
            # tilan
            def report(self):
            print(self.nimi, " Varasto:",
            self.varasto, "Kassa: ", self.kassa)

            # Kauppiaalla on metodit osta ja myy
            # kun myyn, varastoni pienenee ja kassaan tulee rahaa.
            # Lisäksi kerron ostajalle ostoksen hinnan
            def myy(self, kpl):
            self.varasto -= kpl
            self.kassa += kpl*self.hinta
            return self.hinta*kpl

            # Python-kielessä a = a+b ei ole yhtälö vaan tarkoittaa, että a:n uusi arvo on
            # a:n vanha arvo lisättynä b:llä
            # operaatio a += b on sama kuin a = a+b. Vastaavasti a -= b

            # Kun ostan myyjältä, tämä kertoo ostokseni hinnan
            # Kassani pienee ja varastoni kasvaa
            def osta(self, kpl, Myyja):
            maksu = Myyja.myy(kpl)
            self.kassa -= maksu
            self.varasto += kpl


            # Pääohjelma (ohjelman suoritus) alkaa tästä.
            # "Luodaan" kaksi kauppiasta, A ja B. (Kauppiaita voisi olla vaikka sata)
            # Kauppias A: Varastoa 100 kpl, kassassa 100 euroa, myy hintaan 2€/kpl
            A = kauppias(100, 100, 2, 'Jaska')

            # Kauppias B: Varastoa 50 kpl, kassassa 200 euroa, myy hintaan 1€/kpl
            B = kauppias(50, 200, 1, 'Kalle')

            # Esimerkki siitä, miten kutsutaan olion metodia
            print("\nAlkutilanteen raportoinnit")
            A.report()
            B.report()

            print("\nA ostaa B:ltä 20 kpl B:n tarjoamaan edulliseen hintaan")
            A.osta(20, B)
            A.report()
            B.report()

            print("\nB ostaa A:lta 10 kpl A:n pyytämään kalliimpaan hintaan")
            B.osta(10, A)
            A.report()
            B.report()

        

Ylläolevan ohjelman koodi

Ohjelma kirjoittaa konsolille seuraavaa, kun painat F5:

            Alkutilanteen raportoinnit
            Jaska  Varasto: 100 Kassa:  100
            Kalle  Varasto: 50 Kassa:  200

            A ostaa B:ltä 20 kpl B:n tarjoamaan edulliseen hintaan
            Jaska  Varasto: 120 Kassa:  80
            Kalle  Varasto: 30 Kassa:  220

            B ostaa A:lta 10 kpl A:n pyytämään kalliimpaan hintaan
            Jaska  Varasto: 110 Kassa:  100
            Kalle  Varasto: 40 Kassa:  200

        

Ohjelman toimintaa lienee vaikea ymmärtää, mutta yritä silti. Aloita kohdasta "pääohjelma alkaa tästä". Aluksi luodaan kaksi oliota A ja B, jotka ovat tyyppiä kauppias. Voit ajatella, että olio on määrämuotoinen arkistokortti — muistilappu, jollainen tehdään jokaisesta kauppiaasta. Ylempänä ohjelmakoodissa määrittelyn class kauppias: sisällä funktion _init_ määrittely kertoo, mitä "arkistokorttiin" kirjoitetaan: Varaston määrä; kassan suuruus; yksikköhinta, jolla kauppias myy tavaroitaan sekä kauppiaan nimi.

Käskyllä A.report() pyydämme kauppiasta A kertomaan yrityksensä tilanne. Funktio report() on määritelty luokan (class) kauppias sisällä. (Luokan sisällä määriteltyjä funktioita kutsutaan olio-ohjelmoinnissa metodeiksi.) Vastaavasti käskyllä B.report() pyydämme samat tiedot kauppiaalta B.

Ei liene helppo ymmärtää, miksi käytetään termejä class, luokka, metodi,– – ja siksi termeissä menee helposti sekaisin. Paras ottaa mallia yksinkertaisesta esimerkistä ja näin aluksi unohtaa termien alkuperän pähkäily.

Käskyllä A.osta(20,B) pyydämme A:ta ostamaan B:ltä 20 kappaletta. Metodit osta ja myy päivittävät varastojen ja kassojen tilanteet vastaamaan uutta tilannetta. Koeta selvittää, miten se tapahtuu.

Voit yrittää luoda vielä kolmannen kauppiaan ja lisätä ostotapahtumia. Jos onnistut, sinulla ei enää ole kovin paljoa opittavaa olio-ohjelmoinnin periaatteista.


Esimerkkejä funktioiden käsittelystä ja piirtelystä


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

 

Rekursio, säikeet, synkronointi

Tein vuosia sitten ohjelman, joka piirsi puun rekursiivisella funktiolla. Sen oksien väri ja paksuus riippui etäisyydestä tyvestä. Oksien kärkiin piirsin lehden tai kukan. Lisäämällä pikkuisen satunnaisuutta, puiden piirtymistä oli hauska katsella. Onnistuin jopa saamaan puut huojumaan. Jäin haaveilemaan maisemien luomisesta algoritmilla, jossa olisi sopivasti yhdistettynä säännönmukaisuutta ja satunnaisuutta. Vielä hienompaa, jos jaksaisi tehdä siitä kolmiulotteisen niin, että sitä voisi zoomailla ja käännellä. Niin pitkälle en päässyt, mutta onnistuinpa saamaan puut piirtymään vähän jouhevammin kuin silloin kauan sitten.

puu kasvaa

video varalle, jos yo. ei toimi

ja huojuu

video varalle, jos yo. ei toimi


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

 

PuutPilvet.py

Seuraava esimerkki esittelee, miten python-ohjelmointikielessä voi käyttää listoja, olioita ja rekursiivisia funktioita.

Ohjelma piirtää näytölle naivistisen maiseman ohjelmointikielen piirteiden havainnollistamiseksi. Esimerkiksi pilvet ja puun oksat ovat olioita. Puu piirretään rekursiivisella ohjelmalla.

Tue Apr 14 14:01:18 2020


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

 

-

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


Musta = (0, 0, 0)  # Mustassa ei ole mitään valoa
Sin = (0, 0, 255)  # vain sinistä
Pun = (255, 0, 0)
Vihr = (0, 255, 0)
Maasto = (100, 120, 10)
Kelt = (255, 255, 0)
Valk = (255, 255, 255)  # valkoisessa on kaikenväristä valoa
Tausta = (160, 200, 255)


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


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


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


class oksa:
    def __init__(self, paikka, alapuolinen, kulma, pituus, Nhaara):
        self.paikka = paikka  # Puun tyven sijainti
        self.kulma = kulma  # Oksanpätkän kulma alapuolella olevaan
        self.pituus = pituus
        self.paksuus = 2.0 + Nhaara
        self.Nhaara = Nhaara  # Monesko oksanpätkä latvasta lukien
        self.alapuolinen = alapuolinen  # Alapuolinen oksanpätkä
        self.vasenhaara = None
        self.oikeahaara = None


    def kasvata(self, Skulma):
        if self.Nhaara > 0 and Skulma < pi and Skulma > 0:
            # lisätään vielä ainakin yksi oksa kumpaankin suuntaan.
            # lasketaan lisättävälle oksanpätkälle pituus ...
            pituus = LCoeff*self.pituus + random.uniform(-DS, DS)
            + Ccos*(cos(Skulma) - 0.5)
            # ja asentokulma
            kulma = DKulma + random.uniform(-0.05, 0.05)
            # lisätään oksanpätkä ...
            self.vasenhaara = oksa(self.paikka, self, kulma,
                                   pituus, self.Nhaara-1)
            # ... ja jatketaan puun kasvatusta lisätystä oksanpätkästä ylöspäin
            self.vasenhaara.kasvata(Skulma+kulma)
            # kun vasenta haaraa on jatkettu latvaan asti,
            # ohjelman suoritus palaa tähän ja jatkaa oikeaa haaraa ylös.
            # Pituus ja kulma voisivat olla samat kuin vasemmallekin
            # lähtiessä, mutta vaihtelun vuoksi tehdään oikeasta haarasta
            # vähän erilainen
            pituus = LCoeff*self.pituus + random.uniform(-DS, DS)
            + Ccos*(cos(Skulma) - 0.5)
            kulma = - DKulma + random.uniform(-0.05, 0.05)
            self.oikeahaara = oksa(self.paikka, self, kulma,
                                   pituus, self.Nhaara-1)
            self.oikeahaara.kasvata(Skulma+kulma)
        else:
            # Lopuksi oksan päähän lisätään kukka
            self.vasenhaara = Kukka(self.pituus)


    def huoju(self, huojahdus):
        self.kulma = self.kulma+huojahdus/self.paksuus*self.pituus/7.0
        if self.vasenhaara is not None:
            self.vasenhaara.huoju(huojahdus)
        if self.oikeahaara is not None:
            self.oikeahaara.huoju(huojahdus)


    def piirra_w(self, x0, y0, kulma0, Wait, screen):
        kulma = self.kulma+kulma0
        if self.alapuolinen is None:
            (x0, y0) = self.paikka
        x = x0 + self.pituus*cos(kulma)
        y = y0 + self.pituus*sin(kulma)
        xy1 = screenxy(x, y)
        xy0 = screenxy(x0, y0)
        pygame.draw.line(screen, puuVari(self.Nhaara),
                         xy0, xy1, int(self.paksuus))
        if Wait:
            pygame.display.flip()
        if self.vasenhaara is not None:
            self.vasenhaara.piirra_w(x, y, kulma, Wait, screen)
        if self.oikeahaara is not None:
            self.oikeahaara.piirra_w(x, y, kulma, Wait, screen)
class Kukka:
    def __init__(self, pituus):
        # Kukan koko riippuu alapuolisen oksan pituudesta
        # Voisi riippua oksan paksuudesta tai olla vakio
        self.size = pituus/20.0
        self.colors = [Kelt, Sin, Pun, Kelt]
    # Piirretään kolme eriväristä ja -kokoista ympyrää päällekkäin.
    # Aloitetaan isoimmasta, ettei isompi peitä pienempäänsä.
    def piirra_w(self, x, y, kulma, Wait, screen):
        for i in range(3, 0, -1):
            xf, yf = screenxy(x-i*self.size, y+i*self.size)
            w = int(2*i*self.size*SKAALA)
            h = int(2*i*self.size*SKAALA)
            pygame.draw.ellipse(screen, self.colors[i], (xf, yf, w, h), 0)
        if Wait:
            pygame.display.flip()
    # Kukallakin pitää olla huoju-metodi, koska kutsuva ohjelma ei tiedä,
    # käsitteleekö se oksanpätkää vai kukkaa.
    def huoju(self, kulma):
        pass


class Pilvi:
    def __init__(self):
        xvasen = random.uniform(0.1, 0.9*CXW)
        ytop = random.uniform(0.1, 0.7*CYW)
        w = random.uniform(0.02*CXW, 0.2*CXW)
        h = max(0.2*w, random.uniform(0.02*CYW, 0.025*CYW))
        self.coords = pygame.Rect(int(xvasen), int(ytop), int(w), int(h))
    def liiku(self):
        # jos pilvi on ajautunut ulos peli-ikkunan oikeasta laidasta,
        # se siirretään peli-ikkunan vasempaan laitaan.
        if self.coords.left > CXW:
            self.coords.move_ip(-CXW-self.coords.width,
                                random.randrange(-1, 2))
        else:
            # move_ip siirtää pilveä satunnaisen hyppäyksen vasemmalle
            # ja lisäksi pikkuisen ylös tai alas
            max_speed = int(8-4*self.coords.y/CYW)
            self.coords.move_ip(random.randrange(0, max_speed),
                                random.randrange(-1, 2))
            # inflate_ip muuttaa pilven kokoa
            self.coords.inflate_ip(random.randrange(-1, 2),
                                   random.randrange(-1, 2))
            # ei anneta pilvien kasvaa rajatta eikä kutistua olemattomiin
            # pidetään pilven korkeus reilusti pienempänä kuin leveys
            self.coords.width = max(int(0.02*CXW),
                                    min(int(0.2*CXW), self.coords.width))
            self.coords.height = min(int(self.coords.width/5.0),
                                     max(int(0.02*CYW), self.coords.height))
    def piirra(self, screen):
        pygame.draw.ellipse(screen, Valk, self.coords, 0)
# Piirretään kaksi kukkulaa ja rajataan ne mustalla viivalla
def maasto(screen):
    pygame.draw.ellipse(screen, Maasto,
                        (0.4*CXW, 0.85*CYW, 0.9*CXW, CYW), 0)
    pygame.draw.ellipse(screen, Musta,
                        (0.4*CXW, 0.85*CYW, 0.9*CXW, CYW), 2)
    pygame.draw.ellipse(screen, Maasto,
                        (-0.1*CXW, 0.8*CYW, 0.8*CXW, 0.8*CYW), 0)
    pygame.draw.ellipse(screen, Musta,
                        (-0.1*CXW, 0.8*CYW, 0.8*CXW, 0.8*CYW), 2)
# Tarkistetaan, haluaako käyttäjä pysäyttää ohjelman.
# Tämä ei ole aivan tyylikäs tapa pysäyttää ohjelma.
# Ongelmana on reagoida keskeytyspyyntöön monikertaisen for-silmukan
# sisältä.
# En ole vielä opetellut try - exception rakenteen käyttöä.
def lopetus():
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
        if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
            pygame.quit()
            sys.exit()
# # # # # # # # # # # # # # # # # # # # # # # # # # #
# Pääohjelma alkaa tästä
# # # # # # # # # # # # # # # # # # # # # # # # # # #


def main():
    global CXW, CYW, XW, YW, SKAALA
    pygame.display.init()
    # Selvitetään näytön koko pikseleinä
    D_Info = pygame.display.Info()
    CXW = D_Info.current_w
    CYW = D_Info.current_h
    YW = 100.0  # Maiseman korkeus.
    XW = CXW/CYW*YW  # Maiseman leveys
    SKAALA = CYW/YW
    screen = pygame.display.set_mode((CXW, CYW))
    pygame.display.set_caption("puut")
    pygame.init()
    random.seed()
    screen.fill(Tausta)
    # Piirretään kukkulat
    maasto(screen)
    pygame.display.flip()
    # Luodaan sadan pilven lista
    pilvet = []
    for i in range(100):
        pilvet.append(Pilvi())
    # Piirretään pilvet yksi kerrallaan
    for pilvi in pilvet:
        pilvi.piirra(screen)
        pygame.display.flip()
        pygame.time.wait(50)
    # Luodaan "metsä" eli lista puista
    puut = []
    puut.append(oksa((-60, 15), None, pi/2.0+0.1, 5.0, MaxHaara-2))
    puut.append(oksa((50, 10), None, pi/2.0-0.1, 7.0, MaxHaara-1))
    puut.append(oksa((-10, 4), None, pi/2.0, 9.0, MaxHaara))
    # Kasvatetaan puut ja piirretään ne.
    # Wait = True eli päivitetään näyttö jokaisen
    # oksan piirtämisen jälkeen.
    for puu in puut:
        puu.kasvata(pi/2.0)
        puu.piirra_w(0, 2, 0.0, True, screen)
    pygame.time.wait(500)
    s = 0.0075
    while True:
        # käännytään keskeltä ääriasentoon ja takaisin sadalla s:n kokoisella
        # huojahduksella
        # Sitten sama toiseen ääriasentoon
        for j in range(2):
            for k in range(100):
                screen.fill(Tausta)
                for pilvi in pilvet:
                    pilvi.liiku()
                    pilvi.piirra(screen)
                maasto(screen)
                for puu in puut:
                    puu.huoju(s)
                    puu.piirra_w(0, 2, 0.0, False, screen)
                pygame.display.flip()
                lopetus()
                pygame.time.wait(10)
            s = -s  # Käännytään ääriasennosta takaisin
        s = -s  # Lähdetään keskeltä toiseen suuntaan kuin viimeksi
    pygame.quit()
if __name__ == "__main__":
    main()


Selityksiä ylläolevaan

Allaolevat import käskyt tuovat tämän ohjelman käyttöön valmiita moduleita, joiden sisältämista funktioista saa tietoa mm. webin python-oppaista. Grafiikka on toteutettu pygame-modulin avulla lähinnä siksi, että satun tuntemaan sen ennestään.

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



Seuraavilla riveillä on globaaleja vakioita, joita voi käyttää missä hyvänsä ohjelman osassa.

Pygame-modulissa värit esitetään ilmoittamalla luvulla väliltä 0..255 punaisen, vihreän ja sinisen värin osuus.

Musta = (0, 0, 0)  #  Mustassa ei ole mitään valoa
Sin = (0, 0, 255)  #  vain sinistä
Pun = (255, 0, 0)
Vihr = (0, 255, 0)
Maasto = (100, 120, 10)
Kelt = (255, 255, 0)
Valk = (255, 255, 255)  #  valkoisessa on kaikenväristä valoa
Tausta = (160, 200, 255)



Seuraavien parametrien merkitys selviää myöhemmin.

Tyylikkässä ohjelmassa olisi graafinen käyttöliittymä, joka selittäisi parametrien merkityksen ja antaisi mahdollisuuden muuttaa niiden oletusarvoja. Ehkä joskus opettelen tekemään käyttöliittymän tämäntapaisiin ohjelmiin.

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



Funktio screenxy(x, y) laskee, missä kohtaa näytöllä on pelimaailman piste (x, y)



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



Puiden runkojen väri vaihtuu tyven tummanruskeasta latvaoksien vaaleanvihreään. Seuraava funktio laskee puun rungon värin. Ohjelmassa puu kasvaa vaiheittain kuin vuosikasvu kerrallaan. Nhaara kertoo, monesko vuosikasvu latvasta lukien on värjättävänä. MaxHaara on vuosikasvujen enimmäismäärä.



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



Puu koostuu oksiksi kutsutuista pätkistä. Oksan ominaisuudet ovat pituus, asennon kertova kulma, paksuus ja tieto siitä, monesko pätkä se on latvasta lukien. Oksalla on myös tieto siitä, minkä alapuolisen oksanpätkän varassa se on ja mitkä oksanpätkät lähtevät siitä ylöspäin oikealle ja vasemmalle.



class oksa:
    def __init__(self, paikka, alapuolinen, kulma, pituus, Nhaara):
        self.paikka = paikka  #  Puun tyven sijainti
        self.kulma = kulma  #  Oksanpätkän kulma alapuolella olevaan
        self.pituus = pituus
        self.paksuus = 2.0 + Nhaara
        self.Nhaara = Nhaara  #  Monesko oksanpätkä latvasta lukien
        self.alapuolinen = alapuolinen  #  Alapuolinen oksanpätkä
        self.vasenhaara = None
        self.oikeahaara = None


Seuraava funtio lisää puuhun tyvestä lähtien oksanpätkän kerrallaan kunnes päästään latvaan asti tai kunnes oksa kääntyy maata kohti. Jatketaan siis puun kasvattamista niin kauan kuin Nhaara > 0 ja ja kulma horisonttiin nähden - Skulma - on pienempi kuin 180 astetta ja suurempi kuin 0. Tätä funktiota vähän muutettuna voisi kutsua suoraan ylläolevassa __init__ funktiossa: self.vasenhaara = kasvata(self, ...

Oksanpätkän asentokulma on 0, kun oksa osoittaa vaakasuoraan oikealle ja asentokulma on 180, kun oksa osoittaa suoraan vasemmalle. Pythonissa kulmat ilmoitetaan oletusarvoisesti radiaaneina, eli 180 astetta on pii radiaania.

Uuden oksanpätkän pituus voisi olla sama kuin sen alapuolella olevankin, mutta huvin vuoksi lisäsin puun kasvulla taipumuksen, että yläpuolinen on hieman alapuolista oksaa lyhyempi (LCoeff < 1.0). Lisäksi oksa kasvaa sitä paremmin, mitä pystysuorempaan se on kasvamassa. (Termi Ccos*(cos(Skulma) - 0.5)) Lisäsin oksan pituuskasvuun myös hieman satunnaisuutta (random.uniform(-DS, DS)).

    def kasvata(self, Skulma):
        if self.Nhaara > 0 and Skulma < pi and Skulma > 0:
            #  lisätään vielä ainakin yksi oksa kumpaankin suuntaan.
            #  lasketaan lisättävälle oksanpätkälle pituus ...
            pituus = LCoeff*self.pituus + random.uniform(-DS, DS)
            + Ccos*(cos(Skulma) - 0.5)
            #  ja asentokulma
            kulma = DKulma + random.uniform(-0.05, 0.05)
            #  lisätään oksanpätkä ...
            self.vasenhaara = oksa(self.paikka, self, kulma,
                                   pituus, self.Nhaara-1)
            #  ... ja jatketaan puun kasvatusta lisätystä oksanpätkästä ylöspäin
            self.vasenhaara.kasvata(Skulma+kulma)

            #  kun vasenta haaraa on jatkettu latvaan asti,
            #  ohjelman suoritus palaa tähän ja jatkaa oikeaa haaraa ylös.
            #  Pituus ja kulma voisivat olla samat kuin vasemmallekin
            #  lähtiessä, mutta vaihtelun vuoksi tehdään oikeasta haarasta
            #  vähän erilainen
            pituus = LCoeff*self.pituus + random.uniform(-DS, DS)
            + Ccos*(cos(Skulma) - 0.5)
            kulma = - DKulma + random.uniform(-0.05, 0.05)

            self.oikeahaara = oksa(self.paikka, self, kulma,
                                   pituus, self.Nhaara-1)
            self.oikeahaara.kasvata(Skulma+kulma)

        else:
            #  Lopuksi oksan päähän lisätään kukka
            self.vasenhaara = Kukka(self.pituus)



huoju kääntää jokaista oksanpätkää parametristä huojahdus ja oksan paksuudesta ja pituudesta riippuvan kulman verran Huomaa, että meidän ei tarvitse tarkistaa, onko yläpuolinen haara oksanpätkä vai latvakukka. Riittää, että olioluokalle Kukka on sillekin määritelty metodi huoju.


    def huoju(self, huojahdus):
        self.kulma = self.kulma+huojahdus/self.paksuus*self.pituus/7.0
        if self.vasenhaara is not None:
            self.vasenhaara.huoju(huojahdus)
        if self.oikeahaara is not None:
            self.oikeahaara.huoju(huojahdus)



Piirretään puu rekursiivisesti tyvestä lähtien oksanpätkä kerrallaan. Tyveä piirrettäessä oksanpätkän alapään paikka (x0, y0) ) on tietysti puun tyven paikka, muussa tapauksessa funtion argumenttina saatava alapuolisen oksan yläpään paikka.

Oksanpätkän kulma horisonttiin nähden on sen suhteellinen kulma alapuoliseen oksaan nähden + argumenttina saatava kulma0, joka on alapuolisen oksan kulma horisonttiin nähden

Oksanpätkän yläpään paikka (x, y) saadaan trigonometrian avulla. Miten, sen ymmärtää, kun piirtää kuvan.

screenxy(x, y) laskee maiseman pistettä tt vastaavan pisteen pygamen peli-ikkunassa.

Wait määrää, päivitetäänkö peli-ikkuna näytöllä jokaisen oksanpätkän piirtämisen jälkeen.


    def piirra_w(self, x0, y0, kulma0, Wait, screen):
        kulma = self.kulma+kulma0
        if self.alapuolinen is None:
            (x0, y0) = self.paikka
        x = x0 + self.pituus*cos(kulma)
        y = y0 + self.pituus*sin(kulma)
        xy1 = screenxy(x, y)
        xy0 = screenxy(x0, y0)
        pygame.draw.line(screen, puuVari(self.Nhaara),
                         xy0, xy1, int(self.paksuus))
        if Wait:
            pygame.display.flip()
        if self.vasenhaara is not None:
            self.vasenhaara.piirra_w(x, y, kulma, Wait, screen)
        if self.oikeahaara is not None:
            self.oikeahaara.piirra_w(x, y, kulma, Wait, screen)


class Kukka:
    def __init__(self, pituus):
        #  Kukan koko riippuu alapuolisen oksan pituudesta
        #  Voisi riippua oksan paksuudesta tai olla vakio
        self.size = pituus/20.0
        self.colors = [Kelt, Sin, Pun, Kelt]

    #  Piirretään kolme eriväristä ja -kokoista ympyrää päällekkäin.
    #  Aloitetaan isoimmasta, ettei isompi peitä pienempäänsä.
    def piirra_w(self, x, y, kulma, Wait, screen):
        for i in range(3, 0, -1):
            xf, yf = screenxy(x-i*self.size, y+i*self.size)
            w = int(2*i*self.size*SKAALA)
            h = int(2*i*self.size*SKAALA)
            pygame.draw.ellipse(screen, self.colors[i], (xf, yf, w, h), 0)

        if Wait:
            pygame.display.flip()

    #  Kukallakin pitää olla huoju-metodi, koska kutsuva ohjelma ei tiedä,
    #  käsitteleekö se oksanpätkää vai kukkaa.
    def huoju(self, kulma):
        pass



Pilvet ovat suorakaiteen sisään piirrettäviä ellipsejä. Käytän kokeeksi pygame-modulin olioluokkaa Rect, jonka siirtelyn ja koon muuntelun pitäisi olla helppoa ja ohjelman suorituksen kannalta tehokasta. (xvasen, ytop) on suorakaiteen vasen yläkulma ja (w, h) sen leveys ja korkeus.

Käytän pilviä piirtäessä suoraan peli-ikkunan koordinaatistoa, vaikka olisi ollut tyylikkäämpää käyttää samaa maiseman koordinaatistoa kuin puita piirtäessä.

Pilvet liikkuvat vasemmalta oikealle.



class Pilvi:
    def __init__(self):
        xvasen = random.uniform(0.1, 0.9*CXW)
        ytop = random.uniform(0.1, 0.7*CYW)
        w = random.uniform(0.02*CXW, 0.2*CXW)
        h = max(0.2*w, random.uniform(0.02*CYW, 0.025*CYW))
        self.coords = pygame.Rect(int(xvasen), int(ytop), int(w), int(h))

    def liiku(self):
        #  jos pilvi on ajautunut ulos peli-ikkunan oikeasta laidasta,
        #  se siirretään peli-ikkunan vasempaan laitaan.
        if self.coords.left > CXW:
            self.coords.move_ip(-CXW-self.coords.width,
                                random.randrange(-1, 2))
        else:
            #  move_ip siirtää pilveä satunnaisen hyppäyksen vasemmalle
            #  ja lisäksi pikkuisen ylös tai alas
            max_speed = int(8-4*self.coords.y/CYW)
            self.coords.move_ip(random.randrange(0, max_speed),
                                random.randrange(-1, 2))
            #  inflate_ip muuttaa pilven kokoa
            self.coords.inflate_ip(random.randrange(-1, 2),
                                   random.randrange(-1, 2))
            #  ei anneta pilvien kasvaa rajatta eikä kutistua olemattomiin
            #  pidetään pilven korkeus reilusti pienempänä kuin leveys
            self.coords.width = max(int(0.02*CXW),
                                    min(int(0.2*CXW), self.coords.width))
            self.coords.height = min(int(self.coords.width/5.0),
                                     max(int(0.02*CYW), self.coords.height))

    def piirra(self, screen):
        pygame.draw.ellipse(screen, Valk, self.coords, 0)

 #  Piirretään kaksi kukkulaa ja rajataan ne mustalla viivalla
def maasto(screen):
    pygame.draw.ellipse(screen, Maasto,
                        (0.4*CXW, 0.85*CYW, 0.9*CXW, CYW), 0)
    pygame.draw.ellipse(screen, Musta,
                        (0.4*CXW, 0.85*CYW, 0.9*CXW, CYW), 2)
    pygame.draw.ellipse(screen, Maasto,
                        (-0.1*CXW, 0.8*CYW, 0.8*CXW, 0.8*CYW), 0)
    pygame.draw.ellipse(screen, Musta,
                        (-0.1*CXW, 0.8*CYW, 0.8*CXW, 0.8*CYW), 2)


 #  Tarkistetaan, haluaako käyttäjä pysäyttää ohjelman.
 #  Tämä ei ole aivan tyylikäs tapa pysäyttää ohjelma.
 #  Ongelmana on reagoida keskeytyspyyntöön monikertaisen for-silmukan
 #  sisältä.
 #  En ole vielä opetellut try - exception rakenteen käyttöä.
def lopetus():
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
        if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
            pygame.quit()
            sys.exit()

 #  # # # # # # # # # # # # # # # # # # # # # # # # # #
 #  Pääohjelma alkaa tästä
 #  # # # # # # # # # # # # # # # # # # # # # # # # # #



Yleensä peleissä tai kuvia piirrellessä pelimaailman mittayksiköt kannattaa erottaa näytön mittatyksiköistä, koska ohjelman pitää toimia järkevästi eri kokoisilla näytöillä. Voin esimerkiksi sijoittaa puun pisteeseen (-30, 5) riippumatta siitä, montako pikseliä peliruuden koko on näytöllä.

Tässä ohjelmassa pelimaailman korkeus on kiinnitetty sadaksi yksiköksi ja leveys sovitetaan niin, että pelimaailman ja peli-ikkunan kuvasuhde on sama.

Seuraavassa on määritelty peli-ikkunan ja näytön kokoon liittyvät parametrit globaaleiksi muuttujiksi, jotta niitä voi käyttää missä hyvänsä ohjelman kohdassa. Globaaleja muuttujia on houkutteleva käyttää erityisesti ohjelmaa kehittäessä. Turvallisempaa, mutta työläämpää, on välittää funktioille kaikki tarvittava tieto kutsuparametrien kautta.



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

    pygame.display.init()

    #  Selvitetään näytön koko pikseleinä
    D_Info = pygame.display.Info()
    CXW = D_Info.current_w
    CYW = D_Info.current_h

    YW = 100.0  #  Maiseman korkeus.
    XW = CXW/CYW*YW  #  Maiseman leveys
    SKAALA = CYW/YW

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

    random.seed()

    screen.fill(Tausta)

    #  Piirretään kukkulat
    maasto(screen)
    pygame.display.flip()

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

    #  Piirretään pilvet yksi kerrallaan
    for pilvi in pilvet:
        pilvi.piirra(screen)
        pygame.display.flip()
        pygame.time.wait(50)

    #  Luodaan "metsä" eli lista puista
    puut = []
    puut.append(oksa((-60, 15), None, pi/2.0+0.1, 5.0, MaxHaara-2))
    puut.append(oksa((50, 10), None, pi/2.0-0.1, 7.0, MaxHaara-1))
    puut.append(oksa((-10, 4), None, pi/2.0, 9.0, MaxHaara))

    #  Kasvatetaan puut ja piirretään ne.
    #  Wait = True eli päivitetään näyttö jokaisen
    #  oksan piirtämisen jälkeen.
    for puu in puut:
        puu.kasvata(pi/2.0)
        puu.piirra_w(0, 2, 0.0, True, screen)

    pygame.time.wait(500)

    s = 0.0075

    while True:
        #  käännytään keskeltä ääriasentoon ja takaisin sadalla s:n kokoisella
        #  huojahduksella
        #  Sitten sama toiseen ääriasentoon
        for j in range(2):
            for k in range(100):
                screen.fill(Tausta)
                for pilvi in pilvet:
                    pilvi.liiku()
                    pilvi.piirra(screen)
                maasto(screen)
                for puu in puut:
                    puu.huoju(s)
                    puu.piirra_w(0, 2, 0.0, False, screen)
                pygame.display.flip()
                lopetus()
                pygame.time.wait(10)
            s = -s  #  Käännytään ääriasennosta takaisin
        s = -s  #  Lähdetään keskeltä toiseen suuntaan kuin viimeksi

    pygame.quit()

if __name__ == "__main__":

    main()



Lataa tästä ohjelman tämän hetkinen versio: PuutPilvet_0.py


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

 

synkronointi.py

Eri säikeissä toimivien ohjelmien synkronointi

Tue Apr 14 14:02:30 2020


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

 

Synkronointi

# Jokaisen pilven säikeessä on silmukka, jossa siirretään pilveä ja
# piirretään se
while not loppu:
    # siirretään pilveä
    self.siirra()
    # ja sitten piirretään pilvi uuteen paikkaan
    # Ennen kuin aletaan piirtää, anotaan lupaa kirjoittaa näytölle
    portti.acquire()
    # odotetaan, kunnes näyttöä päivittävä ohjelma antaa luvan
    portti.wait()
    # piirretään
    self.piirra()
    # vapautetaan varaus
    portti.release()
# Pääohjelman säikeessä on silmukka, joka päivittää näytön määrävälein
while not loppu:
    # Odotetaan, että portti vapautuu ja estetään sen jälkeen
    # muita ohittamasta porttia
    portti.acquire()
    pygame.display.flip()  # Päivitetään näyttö
    screen.fill(Tausta)  # Tyhjennetään "kangas"
    portti.notify_all()  # Ilmoitetaan muille säikeille, että portti vapautuu
    portti.release()  #
    # Odotellaan hetki, että muut säikeet ehtivät piirtää kankaalle jotain
    # uutta. DT on näytön päivitysväli
    time.sleep(DT)


Selityksiä ylläolevaan

Tässä ohjelmaversiossa kukin puu ja pilvi toimii eri säikessään. Ohjelmoijan on hyvä ajatella, että säikeitä suoritetaan rinnakkain, koska kunkin säikeen suoritus etenee omaa tahtiaan -- asynkronisesti. Multiytimisessä prosessorissa säikeet voisivat edetä aidosti rinnakkain, mutta aidosti rinnakkaisen ohjelman tekeminen vaatii vielä lisätemppuja.

Jos säikeiden halutaan ottavan toistensa suoritusvaiheen huomioon, ne täytyy laittaa välittämään toisilleen viestejä. Seuraavassa esitetään yksi tapa synkronoida säikeiden suoritus.

Jokaisen puun ja pilven siirtelyyn ja piirtämiseen tarvittava ohjelma käynnistetään omaksi säikeekseen. Ne piirtävät yhteiselle "kankaalle". Pääohjelmä siirtää määrävälein "kankaan" sisällön näyttömuistiin ja pyyhkii kankaan tyhjäksi. Ellei piirtämisiä ja näytön päivitystä synkroinoida, näytölle tulee mitä sattuu.


 #  Jokaisen pilven säikeessä on silmukka, jossa siirretään pilveä ja
 #  piirretään se
while not loppu:
    #  siirretään pilveä
    self.siirra()
    #  ja sitten piirretään pilvi uuteen paikkaan
    #  Ennen kuin aletaan piirtää, anotaan lupaa kirjoittaa näytölle
    portti.acquire()
    #  odotetaan, kunnes näyttöä päivittävä ohjelma antaa luvan
    portti.wait()
    #  piirretään
    self.piirra()
    #  vapautetaan varaus
    portti.release()


 #  Pääohjelman säikeessä on silmukka, joka päivittää näytön määrävälein
while not loppu:
    #  Odotetaan, että portti vapautuu ja estetään sen jälkeen
    #  muita ohittamasta porttia
    portti.acquire()
    pygame.display.flip()  #  Päivitetään näyttö
    screen.fill(Tausta)  #  Tyhjennetään "kangas"
    portti.notify_all()  #  Ilmoitetaan muille säikeille, että portti vapautuu
    portti.release()  #
    #  Odotellaan hetki, että muut säikeet ehtivät piirtää kankaalle jotain
    #  uutta. DT on näytön päivitysväli
    time.sleep(DT)



Lataa tästä ohjelman tämän hetkinen versio: synkronointi_0.py


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

 

PuutPilvetSaikeet.py

Tämä on säikeistetty versio edellisestä ohjelmasta. Ohjelmassa on listoja, olioita, rekursiivisia funktioita ja säikeitä. Ohjelma piirtää näytölle naivistisen maiseman ohjelmointikielen piirteiden havainnollistamiseksi. Esimerkiksi pilvet ja puut ovat omissa säikeissään suoritettavia olioita. Puut luodaan ja piirretään rekursiivisella ohjelmalla.

Tue Apr 14 14:01:49 2020


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

 

Säikeiden synkronointia

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


Musta = (0, 0, 0)  # Mustassa ei ole mitään valoa
Sin = (0, 0, 255)  # vain sinistä
Pun = (255, 0, 0)
Vihr = (0, 255, 0)
Maasto = (100, 120, 10)  # Kukkuloiden väri
Kelt = (255, 255, 0)
Valk = (255, 255, 255)  # valkoisessa on kaikenväristä valoa
Tausta = (160, 200, 255)  # Taivas


DT = 0.05  # Näytön päivitysväli sekunteina
MaxHaara = 8  # Tyvestä kunkin haaran latvaan on 8 oksanpätkää
LCoeff = 0.9  # Oksan pituuteen vaikuttava kerroin
DKulma = 0.3  # Kulma, jossa yläpuolinen kulma kääntyy alemmasta
DS = 0.5  # Selviää myöhemmin ;-)
Ccos = 2.0    # Selviää myöhemmin ;-)


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


# Oksanpätkän väri riippuu siitä, kuinka korkealla tyvestä se on
def puuVari(Nhaara):
    r = 20 + (80-20)*Nhaara/MaxHaara
    g = 240 - (240-20)*Nhaara/MaxHaara
    return (r, g, 0)
# Oksanpätkä
class Oksa():
    def __init__(self, tyvi, kulma, pituus, Nhaara):
        self.kulma = kulma  # Kulma suhteessa alapuoliseen oksaan
        self.pituus = pituus
        self.paksuus = 2 + 1.5*Nhaara  # oksat ohenevat latvaa kohti
        self.Nhaara = Nhaara  # Nhaara kertoo, montaka askelta on latvaan
        self.tyvi = tyvi  # alapuolinen oksa
        self.vasenhaara = None  # yläpuolinen oksa vasemmalle
        self.oikeahaara = None
    # Lisätään puuhun rekursiivisesti oksa kerrallaan
    def kasvataPuu(self, Skulma):
        # Lisätään oksanpätkä, jos Nhaara ei vielä ole 0
        # eikä oksa "roiku" alaspäin
        # (Skulma on oksanpätkän absoluuttinen kulma horisonttiin nähden)
        if self.Nhaara > 0 and Skulma < pi and Skulma > 0:
            # Ylemmillä oksilla on "taipumus" olla alempaa lyhyempiä,
            # mutta satunnaisuus saattaa muuttaa tilanteen.
            pituus = LCoeff*self.pituus + random.uniform(-DS, DS)
            # lisätään vielä termi, joka pidentää ylöspäin osoittavia
            # oksia ja lyhentää sivulle kasvavia
            + Ccos*(cos(Skulma) - 0.5)
            # Yläpuolinen oksan suunta poikkeaa alapuolisesta
            # Dkulman verran lisättynä pienellä satunnaisella termillä
            kulma = DKulma*(4.0+self.Nhaara)/(1.0*MaxHaara)
            + random.uniform(-0.1, 0.1)
            # Lisätään oksanpätkä nykyisen yläpuolelle
            self.vasenhaara = Oksa(self, kulma, pituus, self.Nhaara-1)
            # ja kasvatetaan puuta siitä ylöspäin
            self.vasenhaara.kasvataPuu(Skulma+kulma)
            # Sama oikealle haaralle
            pituus = LCoeff*self.pituus + random.uniform(-DS, DS)
            + Ccos*(cos(Skulma) - 0.5)
            kulma = - DKulma*(4.0+self.Nhaara)/(1.0*MaxHaara)
            + random.uniform(-0.1, 0.1)
            self.oikeahaara = Oksa(self, kulma, pituus, self.Nhaara-1)
            self.oikeahaara.kasvataPuu(Skulma+kulma)
        else:
            # Kun on päästy latvaan, piirretään vielä kukka
            self.vasenhaara = Kukka(self)
            # self.oikeahaara = Kukka(self)
    # Puuta voi huojuttaa muuttamalla kunkin oksanpätkän kulmaa
    # Muutetaan kulmaa kääntäen verrannollisesti sen paksuuteen ja
    # ja suoraan verrannollisesti sen pituuteen.
    def huojahda(self, huojahdus):
        self.kulma = self.kulma+huojahdus/self.paksuus*self.pituus
        if self.vasenhaara is not None:
            self.vasenhaara.huojahda(huojahdus)
        if self.oikeahaara is not None:
            self.oikeahaara.huojahda(huojahdus)
    # Piirretään oksa kerrallaan
    def piirra_w(self, paikka, kulma0):
        # portin avulla tahdistetaan puiden piirtäminen näytön päivittävän
        # säikeen kanssa
        portti.acquire()
        portti.wait()
        # Loppu on trigonometriaa ;-)
        kulma = self.kulma+kulma0
        (x0, y0) = paikka
        x = x0 + self.pituus*cos(kulma)
        y = y0 + self.pituus*sin(kulma)
        xy1 = taustaxy(x, y)
        xy0 = taustaxy(x0, y0)
        # piirtämisten tahdistamisen helpottamiseksi käytetään kahta eri
        # kangasta, edusta ja tausta
        pygame.draw.line(edusta, puuVari(self.Nhaara),
                         xy0, xy1, int(self.paksuus))
        portti.release()
        if self.vasenhaara is not None:
            self.vasenhaara.piirra_w((x, y), kulma)
        if self.oikeahaara is not None:
            self.oikeahaara.piirra_w((x, y), kulma)
    # Piirretään  kerralla koko puu
    def piirra(self, paikka, kulma0):
        kulma = self.kulma+kulma0
        (x0, y0) = paikka
        x = x0 + self.pituus*cos(kulma)
        y = y0 + self.pituus*sin(kulma)
        xy1 = taustaxy(x, y)
        xy0 = taustaxy(x0, y0)
        pygame.draw.line(edusta, puuVari(self.Nhaara),
                         xy0, xy1, int(self.paksuus))
        if self.vasenhaara is not None:
            self.vasenhaara.piirra((x, y), kulma)
        if self.oikeahaara is not None:
            self.oikeahaara.piirra((x, y), kulma)
class Kukka:
    def __init__(self, varsi):
        self.size = varsi.pituus/20.0
        self.varsi = varsi
        self.colors = [Kelt, Sin, Pun, Kelt]
    def piirra(self, paikka, kulma):
        (x, y) = paikka
        for i in range(3, 0, -1):
            xf, yf = taustaxy(x-i*self.size, y+i*self.size)
            w = int(2*i*self.size*SKAALA)
            h = int(2*i*self.size*SKAALA)
            pygame.draw.ellipse(edusta, self.colors[i], (xf, yf, w, h), 0)
    def piirra_w(self, paikka, kulma):
        portti.acquire()
        portti.wait()
        (x, y) = paikka
        for i in range(3, 0, -1):
            xf, yf = taustaxy(x-i*self.size, y+i*self.size)
            w = int(2*i*self.size*SKAALA)
            h = int(2*i*self.size*SKAALA)
            pygame.draw.ellipse(edusta, self.colors[i], (xf, yf, w, h), 0)
        portti.release()
    def huojahda(self, kulma):
        pass
# Jokainen olio puu käynnistetään omaksi säikeekseen
class Puu(threading.Thread):
    def __init__(self, paikka, kulma, pituus, Nhaara):
        threading.Thread.__init__(self)
        self.paikka = paikka
        self.tyvi = Oksa(self, 0.0, pituus, Nhaara)
        self.kulma = kulma
        self.pituus = pituus
        self.paksuus = 2.0 + Nhaara
    def run(self):
        global FILL, DT
        self.tyvi.kasvataPuu(pi/2.0)
        self.tyvi.piirra_w(self.paikka, self.kulma)
        FILL = True
        DT = 0.05
        s = 0.002
        while not loppu:
            for j in range(2):
                for k in range(50):
                    self.tyvi.huojahda(s)
                    portti.acquire()
                    portti.wait()
                    self.tyvi.piirra(self.paikka, self.kulma)
                    portti.release()
                s = -s
            s = -s
class Pilvi(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.x = random.uniform(0.1, 0.9*CXW)
        self.y = random.uniform(0.1, 0.6*CYW)
        self.w = random.uniform(0.02*CXW, 0.2*CXW)
        self.h = max(0.3*self.w, random.uniform(0.02*CYW, 0.025*CYW))
        self.sin = 0
    def run(self):
        while not loppu:
            if self.x > CXW:
                self.x -= (CXW + self.w)
            else:
                max_speed = int(8-4*self.y/CYW)
                self.x += random.uniform(0, max_speed)
                self.y += random.uniform(-2, 2)
                self.w += random.uniform(-2, 2)
                self.h += random.uniform(-2, 2)
                self.w = max(int(0.02*CXW), min(int(0.2*CXW), self.w))
                self.h = min(int(self.w/3.0), max(int(0.02*CYW), self.h))
            portti.acquire()
            portti.wait()
            self.sin = max(0, min(50, self.sin + random.randint(-5, 5)))
            Color = (255 - self.sin, 255 - self.sin, 255, 64)
            iy = 5
            ix = int(iy*self.w/self.h)
            dx = self.w/ix/2.0
            dy = self.h/iy/2.0
            R0 = max(3, self.h/iy/2.0)
            istp = int(ix/2)
            for j in range(-2, 3):
                for i in range(-istp+abs(j), istp-abs(j)+1):
                    x = self.x + i*dx + random.uniform(-3, 3)
                    y = self.y + j*dy + random.uniform(-3, 3)
                    R = R0 + random.uniform(-3, 3)
                    pygame.draw.circle(tausta, Color,
                                       (int(x), int(y)), int(R), 0)
            portti.release()
class PuutPilvet(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.puut = []
        self.pilvet = []
    def run(self):
        global FILL, DT
        DT = 0.01
        self.puut.append(Puu((40, 10), pi/2.0-0.1, 8.0, MaxHaara))
        self.puut.append(Puu((-40, 15), pi/2.0+0.1, 7.0, MaxHaara-1))
        for puu in self.puut:
            puu.setDaemon(True)
            puu.start()
            time.sleep(4.0)
        for i in range(25):
            self.pilvet.append(Pilvi())
        for pilvi in self.pilvet:
            pilvi.setDaemon(True)
            pilvi.start()
            time.sleep(2.0)
def maasto():
    pygame.draw.ellipse(tausta, Maasto,
                        (0.4*CXW, 0.85*CYW, 0.9*CXW, CYW), 0)
    pygame.draw.ellipse(tausta, Musta,
                        (0.4*CXW, 0.85*CYW, 0.9*CXW, CYW), 2)
    pygame.draw.ellipse(tausta, Maasto,
                        (-0.1*CXW, 0.8*CYW, 0.8*CXW, 0.8*CYW), 0)
    pygame.draw.ellipse(tausta, Musta,
                        (-0.1*CXW, 0.8*CYW, 0.8*CXW, 0.8*CYW), 2)
def lopetus():
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
        if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
            pygame.quit()
            sys.exit()
def naytolle():
    portti.acquire()
    maasto()
    screen.blit(tausta, (0, 0))
    screen.blit(edusta, (0, 0))
    pygame.display.flip()
    tausta.fill(Tausta)
    if FILL:
        edusta.fill(Musta)
    portti.notify_all()
    portti.release()
def main():
    global FILL
    global CXW, CYW, XW, YW, SKAALA
    global loppu
    global tausta, edusta, screen
    global portti


    pygame.display.init()
    D_Info = pygame.display.Info()
    CXW = D_Info.current_w
    CYW = D_Info.current_h
    FILL = False
    YW = 100.0  # Maiseman korkeus.
    XW = CXW/CYW*YW  # Maiseman leveys
    SKAALA = CYW/YW
    screen = pygame.display.set_mode((CXW, CYW))
    edusta = pygame.Surface(screen.get_size())
    tausta = screen.convert_alpha()
    # tausta.set_alpha(128)
    # edusta.set_alpha(128)
    edusta.set_colorkey((0, 0, 0))
    tausta.fill(Tausta)
    portti = threading.Condition()
    portti.acquire()
    loppu = False
    pygame.display.set_caption("puut")
    pygame.init()
    random.seed()
    naytolle()
    portti.release()
    puutpilvet = PuutPilvet()
    puutpilvet.setDaemon(True)
    puutpilvet.start()
    loppu = False
    while not loppu:
        naytolle()
        lopetus()
        time.sleep(DT)
    pygame.quit()
if __name__ == "__main__":
    main()


Selityksiä ylläolevaan

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



Yleinen tapa esittää värit on ilmoittaa luvulla 0..255 punaisen, vihreän ja sinisen värin osuus.

Musta = (0, 0, 0)  #  Mustassa ei ole mitään valoa
Sin = (0, 0, 255)  #  vain sinistä
Pun = (255, 0, 0)
Vihr = (0, 255, 0)
Maasto = (100, 120, 10)  #  Kukkuloiden väri
Kelt = (255, 255, 0)
Valk = (255, 255, 255)  #  valkoisessa on kaikenväristä valoa
Tausta = (160, 200, 255)  #  Taivas



Erinäisiä globaaleja vakioita. Tyylikkäässä ohjelmassa olisi graafinen käyttöliittymä näiden muuttamista varten.


DT = 0.05  #  Näytön päivitysväli sekunteina
MaxHaara = 8  #  Tyvestä kunkin haaran latvaan on 8 oksanpätkää
LCoeff = 0.9  #  Oksan pituuteen vaikuttava kerroin
DKulma = 0.3  #  Kulma, jossa yläpuolinen kulma kääntyy alemmasta
DS = 0.5  #  Selviää myöhemmin ;-)
Ccos = 2.0    #  Selviää myöhemmin ;-)



Yleensä peleissä tai kuvia piirrellessä pelimaailman mittayksiköt kannattaa erottaa näytön mittatyksiköistä, koska ohjelman pitää toimia järkevästi eri kokoisilla näytöillä. Voin esimerkiksi sijoittaa puun pisteeseen (-30, 5) riippumatta siitä, montako pikseliä peliruuden koko on näytöllä.

Funktio taustaxy(x, y) laskee, missä kohtaa näytöllä on pelimaailman piste (x, y)



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



Oksanpätkän määrittelyä.

 #  Oksanpätkän väri riippuu siitä, kuinka korkealla tyvestä se on
def puuVari(Nhaara):
    r = 20 + (80-20)*Nhaara/MaxHaara
    g = 240 - (240-20)*Nhaara/MaxHaara
    return (r, g, 0)


 #  Oksanpätkä
class Oksa():
    def __init__(self, tyvi, kulma, pituus, Nhaara):
        self.kulma = kulma  #  Kulma suhteessa alapuoliseen oksaan
        self.pituus = pituus
        self.paksuus = 2 + 1.5*Nhaara  #  oksat ohenevat latvaa kohti
        self.Nhaara = Nhaara  #  Nhaara kertoo, montaka askelta on latvaan
        self.tyvi = tyvi  #  alapuolinen oksa
        self.vasenhaara = None  #  yläpuolinen oksa vasemmalle
        self.oikeahaara = None

    #  Lisätään puuhun rekursiivisesti oksa kerrallaan
    def kasvataPuu(self, Skulma):
        #  Lisätään oksanpätkä, jos Nhaara ei vielä ole 0
        #  eikä oksa "roiku" alaspäin
        #  (Skulma on oksanpätkän absoluuttinen kulma horisonttiin nähden)
        if self.Nhaara > 0 and Skulma < pi and Skulma > 0:
            #  Ylemmillä oksilla on "taipumus" olla alempaa lyhyempiä,
            #  mutta satunnaisuus saattaa muuttaa tilanteen.
            pituus = LCoeff*self.pituus + random.uniform(-DS, DS)
            #  lisätään vielä termi, joka pidentää ylöspäin osoittavia
            #  oksia ja lyhentää sivulle kasvavia
            + Ccos*(cos(Skulma) - 0.5)
            #  Yläpuolinen oksan suunta poikkeaa alapuolisesta
            #  Dkulman verran lisättynä pienellä satunnaisella termillä
            kulma = DKulma*(4.0+self.Nhaara)/(1.0*MaxHaara)
            + random.uniform(-0.1, 0.1)
            #  Lisätään oksanpätkä nykyisen yläpuolelle
            self.vasenhaara = Oksa(self, kulma, pituus, self.Nhaara-1)
            #  ja kasvatetaan puuta siitä ylöspäin
            self.vasenhaara.kasvataPuu(Skulma+kulma)

            #  Sama oikealle haaralle
            pituus = LCoeff*self.pituus + random.uniform(-DS, DS)
            + Ccos*(cos(Skulma) - 0.5)
            kulma = - DKulma*(4.0+self.Nhaara)/(1.0*MaxHaara)
            + random.uniform(-0.1, 0.1)
            self.oikeahaara = Oksa(self, kulma, pituus, self.Nhaara-1)
            self.oikeahaara.kasvataPuu(Skulma+kulma)
        else:
            #  Kun on päästy latvaan, piirretään vielä kukka
            self.vasenhaara = Kukka(self)
            #  self.oikeahaara = Kukka(self)

    #  Puuta voi huojuttaa muuttamalla kunkin oksanpätkän kulmaa
    #  Muutetaan kulmaa kääntäen verrannollisesti sen paksuuteen ja
    #  ja suoraan verrannollisesti sen pituuteen.
    def huojahda(self, huojahdus):
        self.kulma = self.kulma+huojahdus/self.paksuus*self.pituus
        if self.vasenhaara is not None:
            self.vasenhaara.huojahda(huojahdus)
        if self.oikeahaara is not None:
            self.oikeahaara.huojahda(huojahdus)

    #  Piirretään oksa kerrallaan
    def piirra_w(self, paikka, kulma0):
        #  portin avulla tahdistetaan puiden piirtäminen näytön päivittävän
        #  säikeen kanssa
        portti.acquire()
        portti.wait()
        #  Loppu on trigonometriaa ;-)
        kulma = self.kulma+kulma0
        (x0, y0) = paikka
        x = x0 + self.pituus*cos(kulma)
        y = y0 + self.pituus*sin(kulma)
        xy1 = taustaxy(x, y)
        xy0 = taustaxy(x0, y0)
        #  piirtämisten tahdistamisen helpottamiseksi käytetään kahta eri
        #  kangasta, edusta ja tausta
        pygame.draw.line(edusta, puuVari(self.Nhaara),
                         xy0, xy1, int(self.paksuus))
        portti.release()

        if self.vasenhaara is not None:
            self.vasenhaara.piirra_w((x, y), kulma)
        if self.oikeahaara is not None:
            self.oikeahaara.piirra_w((x, y), kulma)

    #  Piirretään  kerralla koko puu
    def piirra(self, paikka, kulma0):
        kulma = self.kulma+kulma0
        (x0, y0) = paikka
        x = x0 + self.pituus*cos(kulma)
        y = y0 + self.pituus*sin(kulma)
        xy1 = taustaxy(x, y)
        xy0 = taustaxy(x0, y0)
        pygame.draw.line(edusta, puuVari(self.Nhaara),
                         xy0, xy1, int(self.paksuus))
        if self.vasenhaara is not None:
            self.vasenhaara.piirra((x, y), kulma)
        if self.oikeahaara is not None:
            self.oikeahaara.piirra((x, y), kulma)


class Kukka:
    def __init__(self, varsi):
        self.size = varsi.pituus/20.0
        self.varsi = varsi
        self.colors = [Kelt, Sin, Pun, Kelt]

    def piirra(self, paikka, kulma):
        (x, y) = paikka
        for i in range(3, 0, -1):
            xf, yf = taustaxy(x-i*self.size, y+i*self.size)
            w = int(2*i*self.size*SKAALA)
            h = int(2*i*self.size*SKAALA)
            pygame.draw.ellipse(edusta, self.colors[i], (xf, yf, w, h), 0)

    def piirra_w(self, paikka, kulma):
        portti.acquire()
        portti.wait()
        (x, y) = paikka
        for i in range(3, 0, -1):
            xf, yf = taustaxy(x-i*self.size, y+i*self.size)
            w = int(2*i*self.size*SKAALA)
            h = int(2*i*self.size*SKAALA)
            pygame.draw.ellipse(edusta, self.colors[i], (xf, yf, w, h), 0)
        portti.release()

    def huojahda(self, kulma):
        pass

 #  Jokainen olio puu käynnistetään omaksi säikeekseen
class Puu(threading.Thread):
    def __init__(self, paikka, kulma, pituus, Nhaara):
        threading.Thread.__init__(self)
        self.paikka = paikka
        self.tyvi = Oksa(self, 0.0, pituus, Nhaara)
        self.kulma = kulma
        self.pituus = pituus
        self.paksuus = 2.0 + Nhaara

    def run(self):
        global FILL, DT
        self.tyvi.kasvataPuu(pi/2.0)
        self.tyvi.piirra_w(self.paikka, self.kulma)
        FILL = True
        DT = 0.05

        s = 0.002
        while not loppu:
            for j in range(2):
                for k in range(50):
                    self.tyvi.huojahda(s)
                    portti.acquire()
                    portti.wait()
                    self.tyvi.piirra(self.paikka, self.kulma)
                    portti.release()
                s = -s
            s = -s


class Pilvi(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.x = random.uniform(0.1, 0.9*CXW)
        self.y = random.uniform(0.1, 0.6*CYW)
        self.w = random.uniform(0.02*CXW, 0.2*CXW)
        self.h = max(0.3*self.w, random.uniform(0.02*CYW, 0.025*CYW))
        self.sin = 0

    def run(self):
        while not loppu:
            if self.x > CXW:
                self.x -= (CXW + self.w)
            else:
                max_speed = int(8-4*self.y/CYW)
                self.x += random.uniform(0, max_speed)
                self.y += random.uniform(-2, 2)
                self.w += random.uniform(-2, 2)
                self.h += random.uniform(-2, 2)
                self.w = max(int(0.02*CXW), min(int(0.2*CXW), self.w))
                self.h = min(int(self.w/3.0), max(int(0.02*CYW), self.h))

            portti.acquire()
            portti.wait()
            self.sin = max(0, min(50, self.sin + random.randint(-5, 5)))
            Color = (255 - self.sin, 255 - self.sin, 255, 64)
            iy = 5
            ix = int(iy*self.w/self.h)
            dx = self.w/ix/2.0
            dy = self.h/iy/2.0
            R0 = max(3, self.h/iy/2.0)
            istp = int(ix/2)
            for j in range(-2, 3):
                for i in range(-istp+abs(j), istp-abs(j)+1):
                    x = self.x + i*dx + random.uniform(-3, 3)
                    y = self.y + j*dy + random.uniform(-3, 3)
                    R = R0 + random.uniform(-3, 3)
                    pygame.draw.circle(tausta, Color,
                                       (int(x), int(y)), int(R), 0)
            portti.release()


class PuutPilvet(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.puut = []
        self.pilvet = []

    def run(self):
        global FILL, DT
        DT = 0.01
        self.puut.append(Puu((40, 10), pi/2.0-0.1, 8.0, MaxHaara))
        self.puut.append(Puu((-40, 15), pi/2.0+0.1, 7.0, MaxHaara-1))

        for puu in self.puut:
            puu.setDaemon(True)
            puu.start()
            time.sleep(4.0)

        for i in range(25):
            self.pilvet.append(Pilvi())

        for pilvi in self.pilvet:
            pilvi.setDaemon(True)
            pilvi.start()
            time.sleep(2.0)


def maasto():
    pygame.draw.ellipse(tausta, Maasto,
                        (0.4*CXW, 0.85*CYW, 0.9*CXW, CYW), 0)
    pygame.draw.ellipse(tausta, Musta,
                        (0.4*CXW, 0.85*CYW, 0.9*CXW, CYW), 2)
    pygame.draw.ellipse(tausta, Maasto,
                        (-0.1*CXW, 0.8*CYW, 0.8*CXW, 0.8*CYW), 0)
    pygame.draw.ellipse(tausta, Musta,
                        (-0.1*CXW, 0.8*CYW, 0.8*CXW, 0.8*CYW), 2)


def lopetus():
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
        if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
            pygame.quit()
            sys.exit()


def naytolle():
    portti.acquire()
    maasto()
    screen.blit(tausta, (0, 0))
    screen.blit(edusta, (0, 0))
    pygame.display.flip()
    tausta.fill(Tausta)
    if FILL:
        edusta.fill(Musta)
    portti.notify_all()
    portti.release()


def main():
    global FILL
    global CXW, CYW, XW, YW, SKAALA
    global loppu
    global tausta, edusta, screen
    global portti



Yleensä peleissä tai kuvia piirrellessä pelimaailman mittayksiköt kannattaa erottaa näytön mittatyksiköistä, koska ohjelman pitää toimia järkevästi eri kokoisilla näytöillä. Voin esimerkiksi sijoittaa puun pisteeseen (-30, 5) riippumatta siitä, montako pikseliä peliruuden koko on näytöllä.

Tässä ohjelmassa pelimaailman korkeus on kiinnitetty sadaksi yksiköksi ja leveys sovitetaan niin, että pelimaailman ja peli-ikkunan kuvasuhde pysyy samana.

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

    FILL = False

    YW = 100.0  #  Maiseman korkeus.
    XW = CXW/CYW*YW  #  Maiseman leveys
    SKAALA = CYW/YW

    screen = pygame.display.set_mode((CXW, CYW))
    edusta = pygame.Surface(screen.get_size())
    tausta = screen.convert_alpha()
    #  tausta.set_alpha(128)
    #  edusta.set_alpha(128)
    edusta.set_colorkey((0, 0, 0))
    tausta.fill(Tausta)

    portti = threading.Condition()
    portti.acquire()

    loppu = False

    pygame.display.set_caption("puut")
    pygame.init()
    random.seed()

    naytolle()
    portti.release()

    puutpilvet = PuutPilvet()
    puutpilvet.setDaemon(True)
    puutpilvet.start()

    loppu = False
    while not loppu:
        naytolle()
        lopetus()
        time.sleep(DT)

    pygame.quit()

if __name__ == "__main__":

    main()



Lataa tästä ohjelman tämän hetkinen versio: PuutPilvetSaikeet_0.py