Ohjelmointia, matematiikkaa, fysiikkaa …

Miten opettaa ohjelmointia — ja fyssaa ja matikkaa siinä sivussa.

Harjoitus: proseduraalisen ohjelmoinnin perusrakenteet Scratchilla
Harjoitus: proseduraalisen ohjelmoinnin perusrakenteet Scratchilla

Seuraavan harjoituksen tarkoitus on tutustuttaa proseduraalisen ohjelmoinnin perusajatukseen. Samalla voi tutustua ohjelmoinnin alkuopetusta varten kehitettyyn Scratch-ohjelmointiympäristöön.

Maaliskuu 2015

Python hyttysjahdissa ja muita pelejä
Python hyttysjahdissa ja muita pelejä

Seuraavassa ohjelmoidaan vaihe vaiheelta Python-kielisiä peliohjelmia. Harjoitusten tekemistä helpottaa edelläesitetyn scratch-harjoituksen tekeminen.

Nämä harjoitukset tekemällä ei välttämättä opi kaikkia ohjelmoinnin saloja, mutta harjoituksen ohjelmissa hyödynnetään useimpia Python ohjelmoinnin perusrakenteita niin, että harjoitusten tekemisen myötä voi tutustua syvemmin Python-ohjelmointiin.

Harjoitukset vaativat jonkin verran matemaattista ajattelua, mutta eivät edellytä matematiikan eikä fysiikan esitietoja, mutta niin haluttaessa pelejä voidaan hyödyntää Newtonin mekaniikan ja siihen liittyvien matematiikan osa-alueiden opetuksessa. Pelien avulla voi myös tutustua differentiaaliyhtälöiden numeeriseen ratkaisemiseen.

Selitykset ja ohjeet eivät vielä riittäne aloittelijalle, joten vika ei ole sinussa, jos harjoitukset tuntuvat liian vaikeilta.

Maaliskuu 2015

HyttysJahti.py
HyttysJahti.py

Rakennetaan vaihe vaiheelta videopeli, jossa jahdataan hyttysiä. Hyttyslätkää ohjataan näppäimillä z,x, nuoli-ylös ja nuoli-alas. Ohjelma pysäytetään joko esc-näppäimellä tai sulkemalla peli-ikkuna.

Esittelyvideo pelistä

Sun Mar 11 09:58:51 2018

morssari.py
morssari.py

Tässä pelissä tehtävänä on osua vanhan ajan mörssärillä esteen takana olevaan maaliin.

— Mörssäriä suunnataan ylös ja alas nuolinäppäimillä.

— Numeronäppäimistön + ja - -merkkeillä säädetään ajopanoksen kokoa

— Numeronäppäimistön näppäimillä 1, 2, ja 3 voit valita tykinkuulan tiheyden.

— z- ja x- näppäimillä voit siirtää mörssäriä.

Mörssäri on ladattu, kun sen piippu muuttuu punaiseksi. Se laukaistaan painamalla välilyöntiä.

F1-näppaimen vaikutuksen voit selvittää itse.

Esittelyvideo pelistä.

Sun Mar 11 09:58:51 2018

Voimia ja liikettä
Voimia ja liikettä

Mörssärin kuulaan vaikuttivat Maan vetovoima ja ilmanvastus. Seuraavan pelin avaruusalusta liikuttelevat Maan ja Kuun vetovoiman sekä rakettimoottorit. Siksi on hyvä kerrata, miten kappaleiden liikkeitä lasketaan.

Kesäkuu 2015

KuuAlus.py
KuuAlus.py

Tehtävänä on ohjata avaruusalus Maata kiertävältä radalta Kuuta kiertävälle radalle.

Alusta ohjataan nuolinäppäimillä sekä z- ja x-näppäimillä. x kiihdyttää alusta isolla raketilla, z jarruttaa isolla raketilla. Nuolet vasemmalle ja oikealle hidastavat ja kiihdyttävät pienemmällä voimalla. Ylä- ja alanuolilla rakettia voi ohjata sivusuunnassa liikerataan nähden. Jos tekee kiihdytykset ja hidastukset oikein, sivusuuntaista ohjausta ei tarvitse.
F2 tuo näkyviin ohjaamista helpottavia suureita.
Numeronäppäimistön 0, 1 ja enter vaikuttavat pelin nopeuteen.
Numeronäppäimistön + ja - zoomaavat näyttöä
m- ja k- näppäimet siirtävät näytön keskipisteeseen joko Maan tai Kuun F1 jättää aluksen radan näkyviin.

Perehdy ohjelman toimintaa tarkemmin lukemalla koodia.

Avaruuslento ei ole helppoa. Seuraavat peukalosäännöt saattavat auttaa.

Lento Kuun kiertoradalle pikkuisen räpistellen

Sun Mar 11 09:58:51 2018

PuutPilvet.py
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.

Sun Mar 11 10:05:35 2018

synkronointi.py
synkronointi.py Eri säikeissä toimivien ohjelmien synkronointi

Sun Mar 11 10:05:35 2018

PuutPilvetSaikeet.py
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.

Sun Mar 11 10:05:35 2018

Lisäharjoituksia
Lisäharjoituksia

Innostunut harrastaja — ehkä itsekin innostun — voi täydentää pelejä ja tehdä uusia.

Miten ylläolevat dokumentit tuotettiin
Miten ylläolevat dokumentit tuotettiin

Ohjelman vaiheittaisesta kehittelystä ei voi tehdä yllä olevan kaltaisia dokumentteja käsityönä. Virheiltä ei voisi välttyä. Kerran dokumentoitua ohjelmaa ei liioin voisi parannella eikä täydentää, koska dokumenttien korjaaminen vastaamaan muutoksia olisi aivan liian työlästä ja virhealtista.

Onneksi pythonilla oli melko helppo automatisoida vaiheittaisen kehittelyn dokumentti.

Toukokuu 2015

Python2html.py
Python2html.py

Tällä ohjelmalla on tuotettu tämän sivuston python-ohjelmien esittelyt.

Sun Mar 11 09:58:51 2018

Peikkometsän hiilitase
Peikkometsän hiilitase

Me - peikkokoulun Python-kerho - teimme pelin, joka auttaa ymmärtämään kraatterimme metsien hakkuun vaikutusta kraatterimme ilmastoon.

Helmikuu 2018

Siltanosturi — virtuaalinen mekaaninen lelu
Siltanosturi — virtuaalinen mekaaninen lelu

Siltanosturi ei ehkä ole pelinä erityisen jännittävä, mutta sopinee esimerkiksi tutkivasta oppimisesta.

Kesäkuu 2015

Prolog ohjelmointikieli
Prolog ohjelmointikieli

Esitän pari esimerkkiä prolog-ohjelmointikielestä. Niiden perusteella et ehkä opi prolog-ohjelmointia enkä ehkä edes onnistu selittämään tyhjentävästi, miten esimerkkien ohjelmat toimivat. Tavoitteeni on kuitenkin antaa hyvä yleisvaikutelma siitä, millainen ohjelmointikieli prolog on ja antaa muutama esimerkki siitä, millaisia ongelmia sillä on kätevä ratkaista.

Helmikuu 2016

Reititys
Reititys

Esimerkki reitin hakemisesta pisteestä A pisteeseen B.

Helmikuu 2016

Polyrytmistä rummutusta Sonic Pi:llä
Polyrytmistä rummutusta Sonic Pi:llä

Miten tuottaa ohjelmallisesti jotain soiton tapaista muutamalla yksinkertaisella säännöllä ja ripauksella satunnaisuutta?

Helmikuu 2016

Ohjelmoinnista ja ohjelmoinnin oppimisesta
Ohjelmoinnista ja ohjelmoinnin oppimisesta

Ohjelmoinnissa on monenlaista opittavaa:

— On sisäistettävä algoritminen ajattelu

— On opittava ohjelmointiympäristöjen 'nappulatekniikka'

— On opittava käytettävän ohjelmointikielen syntaksi eli kielioppi.

— On opittava tekemään laadukasta koodia, joka on ymmärrettävää ja helposti muuteltavissa ja siten uudelleenkäytettävissä.




( Maaliskuu 2015 )

Harjoitus: proseduraalisen ohjelmoinnin perusrakenteet Scratchilla

../xml/Programming/Scratch/scratchKuvat/EsittelyKuva.png

Kuva 1.

Seuraavan harjoituksen tarkoitus on tutustuttaa proseduraalisen ohjelmoinnin perusajatukseen. Samalla voi tutustua ohjelmoinnin alkuopetusta varten kehitettyyn Scratch-ohjelmointiympäristöön.


Proseduraalisen ohjelmoinnin ja oli-ohjelmoinnin periaatteet ovat pysyneet samoina ainakin viisikymmentä vuotta. Monet yleiset ohjelmointikielet kuten C++, Java, Python, perustuvat proseduraalisen ohjelmoinnin ja olio-ohjelmoinnin periaatteisiin.

Joihinkin tarkoituksiin sopii proseduraalista ohjelmointia paremmin esimerkiksi funktionaalinen ohjelmointi tai logiikkapohjainen ohjelmointi , mutta hyödyllisintä on aloittaa ohjelmoinnin opiskelu perehtymällä proseduraaliseen ohjelmointiin ja olio-ohjelmointiin.

Tämän harjoituksen tarkoitus on tutustuttaa proseduraalisen ohjelmoinnin keskeisiin asioihin, kuten kontrollirakenteisiin ja muuttujiin. Teemme ohjelman — algoritmin, joka piirtää kuvan 1 kaltaisia geometrisia kuvioita. Moinen kuvio tuskin sinänsä säväyttää ketään videopelien maailmaan tottunutta, mutta teemmekin piirto-ohjelman, koska scratch-kielisen piirto-ohjelman käskyjen vaikutus havainnollistuu selkeästi ohjelmoijalle. Geometristen kuvioiden piirtelyn myötä pääsee samalla harjoittelemaan geometrista ajattelua. Luvun lopussa on esimerkki olio-ohjelmoinnista Scratchilla.

Käynnistä scratch ohjelmointiympäristö tästä ja valitse kokeile sitä (tai try it out, jos selaimesi ei suosi suomen kieltä). Sinun pitäisi päästä vaiheen 1 mukaiselle aloitussivulle.

../xml/Programming/Scratch/scratchKuvat/alku.jpg

Vaihe 1: Aloitussivu. Mikäli aloitussivu on englanniksi, voit vaihtaa kielen suomeksi klikkaamalla mustan nuolen osoittamaa maapallonkuvaa.

../xml/Programming/Scratch/scratchKuvat/ekaLiike.jpg

Vaihe 2: Scratch-ohjelmia tehdään vetämällä keskipalkista ohjelmalohkoja oikealle työtilaan. Valitse aluksi mustalla ympyröidyltä alueelta tapahtumat ja vedä sieltä työtilaan lohko kun klikataan+vihreän lipun kuva. Hae muut kuvan lohkot valikoista kynä ja liike ja "naksauta" ne paikoilleen. Kirjoita valkeisiin soikioihin järkevät luvut. Komento liiku 100 askelta siirtää oikealla ylhäällä olevassa laatikossa näkyvää kissaa sata "askelta". Huomaa, että "hiekkalaatikon" koko on muutama sata "kissanaskelta" suuntaansa.

Ohjelmasi on valmis. Klikkaa kissan "hiekkalaatikon" ylälaidassa olevaa vihreää lippua. Jos kaikki meni hyvin, kissa siirtyi ja piirsi viivan.

Hyvä työtä säästävä tapa on tallentaa ohjelma uudella nimellä ennen kuin alkaa tehdä isoja muutoksia. Klikkaa yläpalkista tiedosto — lataa tietokoneellesi. Anna tiedoston nimeksi vaikka "piirtely-v1". Seuraavalla kerralla tallenna ohjelma nimellä "piirtely-v2" jne. Jos jossain vaiheessa satut sotkemaan ohjelmasi tosi pahasti, voit ladata edellisen version eikä sinun tarvitse aloittaa kaikkea alusta. Pienet virheet voi korjata yläpalkin komennolla muokkaa — peruuta.

../xml/Programming/Scratch/scratchKuvat/ekaLiikejaReset.png

Vaihe 3: Tee kuvan oikean laidan mukainen "siivousohjelma", joka palauttaa kissan ja "hiekkalaatikon" alkutilaan. Vaikka kissasi karkaisi näytön ulkopuolelle, voit kutsua sen takaisin painamalla välilyöntiä. Samalla vanhat piirrokset pyyhkiytyvät pois.

(Minun mielestäni Scratchissä pitäisi olla valmiina reset-komento, joka tekisi ylläolevan.)

../xml/Programming/Scratch/scratchKuvat/Kolmion_osat.png

Vaihe 4: Hae kuvan ohjelmalohkot valikosta ja kokoa niistä ohjelma, joka piirtää kolmion. Oikeaa kääntymiskulmaa voi olla vaikea keksiä. Voit nousta seisomaan ja kävellä kolmion muotoisen polun. Montako astetta käännyit kaikkiaan?

../xml/Programming/Scratch/scratchKuvat/OsatRepeatKolmio.png

Vaihe 5: Kolmiota piirtävässä ohjelmassa toistuu sama komentosarja kolme kertaa. Kokoa kuvan lohkoja käyttäen ohjelma, joka piirtää kolmion. Sujauta toistettavat komennot toista-lohkon haarukkaan.

../xml/Programming/Scratch/scratchKuvat/sanoKaannos.png

Vaihe 6: Kissa piirtää kuvan niin nopeasti, ettei sen liikkeitä ehdi seurata. Laita kissa pysähtymään ja sanomaan "käännös" kolmion joka kulmassa, sujauttamalla sano-käsky sopivaan kohtaan ohjelmaa.

../xml/Programming/Scratch/scratchKuvat/teeMuuttuja.png

Vaihe 7: Entä jos haluaisimme piirtää neliön tai mikä hyvänsä monikulmion? Helppo juttu. Toistetaan komennot useamman kerran ja käännytään kussakin kulmassa vähän vähemmän. Tätä varten tarvitsemme muuttujan, joka kertoo, montako kulmaa haluamme kuvioomme.

Valitse tieto-valikosta komento tee muuttuja ja anna sille nimeksi vaikka KulmienLkm. Laiskempi ehkä antaa muuttujalle nimeksi n tai lkm. Näin pienessä ohjelmassa tuskin unohtuu, mitä varten muuttuja on luotu.

../xml/Programming/Scratch/scratchKuvat/KulmienLkm.png

Vaihe 8: Lisää ohjelmaasi oheiset lohkot niin, että se piirtää minkä hyvänsä monikulmion. Kulmien lukumäärälle pitää asettaa haluttu arvo ennen toista-lohkoa. Toistoja tehdään KulmienLkm kertaa. Vihreä pallukka suorittaa jakolaskun. Sillä voi laskea kääntymiskulman, joten sijoita se lohkoon käänny ja sijoita jakolaskuun sopivat arvot ja muuttujat.

Kokeile, toimiiko ohjelmasi. Hienoa, jos toimii. Läheskään aina ohjelma ei toimi ekalla yrityksellä ja virheen etsiminen voi olla rasittavaa. Ohjelmointi on kiehtovaa ja koukuttavaa, mutta joskus myös raskasta.

../xml/Programming/Scratch/scratchKuvat/Piiri.png

Vaihe 9: Jos piirsit ohjelmallasi esimerkiksi 13-kulmion, se saattoi venyä kuvaruudun ulkopuolelle. Muuta ohjelmaasi niin, että monikulmion piiri on kulmien lukumäärästä riippumatta sama.

Tee muuttuja piiri ja laita sille ohjelman alussa, ennen toista silmukkaa joku järkevä arvo. Senjälkeen aseta sivun pituudeksi piiri/KulmienLkm eli liiku kerrallaan piiri/KulmienLkm. Kuvassa näet tarvittavat lohkot.

../xml/Programming/Scratch/scratchKuvat/kysyNkulmat.png

Vaihe 10: Entäpä jos kissa kysyisi, minkälaisen monikulmion haluamme piirtää?

Sijoita kysymys ohjelman alkuun ennen kuin tarvitset tietoa kulmien lukumäärästä. Sen jälkeen loksauta vastaus sinne, mihin olet tähän asti kirjoittanut käsin muuttujien lukumäärän.

../xml/Programming/Scratch/scratchKuvat/Laskuri.png

Vaihe 11: Harjoitellaan lisää muuttujien käyttöä. Tee muuttuja Laskuri , jolla seuraamme, montako sivua kissa on jo piirtänyt.

Aseta Laskurin arvoksi 0 ohjelman alussa ja lisää sen arvoa yhdella aina kun on tullut piirrettyä lisää yksi sivu. Muuta komento sano käännä niin, että kissa sanoo, montako sivua se on jo piirtänyt. Eli korvaa "käännä!" muuttujalla Laskuri.

../xml/Programming/Scratch/scratchKuvat/pikkuKolmio.png

Vaihe 12: Kokoa kuvan mukainen lohko ja sujauta se monikulmio-ohjelmasi toista-lohkon sisään aiempien komentojen lisäksi. Koeta, mitä tapahtuu.

../xml/Programming/Scratch/scratchKuvat/pikkuKolmiot.png

Vaihe 13: Minulle kävi näin. Jos laitoit lohkon eri paikkaan kuin minä, sait varmaankin vähän toisenlaisen kuvion.

../xml/Programming/Scratch/scratchKuvat/Lopetus.png
Vaihe 14: Lisää oheiset komennot ohjelmaasi niin, että kuvion piirrettyään kissa siirtyy sen sisään kuten kuvassa 1.

Vaiheissa 12 ja 13 piirsit monikulmion nurkkaan pienen kolmion. Muuta ohjelmaa niin, että joka kulmaan piirtyy iso monikulmio pienoiskoossa niin, että kunkin sivun pituus on viidesosa ison kuvion sivun pituudesta eli pienemmän kuvion piiri on viidesosa isomman kuvion piiristä. Voit joko luoda uuden muuttuja vaikka nimellä piiri2 ja käyttää sitä sisemmässä toista-haarukassa tai käyttää kahta sisäkkäistä jakolaskua. (Tämä tehtävä saattaa vaatia pikkuisen miettimistä ja kokeilua.)

Scracthilla voi tehdä paljon muutakin, monen mielestä ehkä hauskempaakin kuin monikulmioiden piirtely, joten kannattaa tutustua scratchin aloitussivun oikean laidan tutoriaaleihin. Voit vaikka tehdä ohjelman, jossa kissat tanssivat, mikäli geometria alkaa tympiä. Voit myös vilkaista lisäharjoituksia , jotka esittelevät muutamia ohjelmoinnin perusrakenteita.


Kysymyksiä? Vastauksia? Kommentteja?


Kiitokset

Helsingin yliopiston aineenopettajakoulutuksessa valmistelimme ryhmätyönä ohjelmoinnin oppitunnin. En tiedä, vastaako ylläoleva ensinkään ryhmän muiden jäsenten ajatuksia, mutta joka tapauksessa ylläoleva perustuu ryhmätyössä oppimaani.



( Maaliskuu 2015 )

Python hyttysjahdissa ja muita pelejä

Seuraavassa ohjelmoidaan vaihe vaiheelta Python-kielisiä peliohjelmia. Harjoitusten tekemistä helpottaa edelläesitetyn scratch-harjoituksen tekeminen.

Nämä harjoitukset tekemällä ei välttämättä opi kaikkia ohjelmoinnin saloja, mutta harjoituksen ohjelmissa hyödynnetään useimpia Python ohjelmoinnin perusrakenteita niin, että harjoitusten tekemisen myötä voi tutustua syvemmin Python-ohjelmointiin.

Harjoitukset vaativat jonkin verran matemaattista ajattelua, mutta eivät edellytä matematiikan eikä fysiikan esitietoja, mutta niin haluttaessa pelejä voidaan hyödyntää Newtonin mekaniikan ja siihen liittyvien matematiikan osa-alueiden opetuksessa. Pelien avulla voi myös tutustua differentiaaliyhtälöiden numeeriseen ratkaisemiseen.

Selitykset ja ohjeet eivät vielä riittäne aloittelijalle, joten vika ei ole sinussa, jos harjoitukset tuntuvat liian vaikeilta.


Harjoitusten tekemiseen tarvitset python ohjelmointiympäristön kuten spyder/spyder3 tai IDLE sekä Pygame -pelimodulin. OpenSuse -linux järjestelmääni sain nämä installoitua ruksaamalla spyder3 ja pygame Yast- ohjelmistohallinnosta. Mikäli pygame-modulin installointi ei onnistu suoraan järjestelmäsi pakettien hallinnan avulla yritä antamalla komentoriviltä komento pip3 install pygame. pip3 on python ohjelmistoihin kuuluva ohjelma, jolla voi installoida python-ohjelmistomoduleja.

Pythonista on kaksi ei aivan täysin yhteensopivaa versiota python2 (esim. versio python 2.7) ja python3 (esim. versio python 3.4). Toisinaan käytetään nimiä spyder ja spyder3 sekä pip ja pip3 kertomaan, kumman pythonin kanssa kehitysympäristö ja lisämodulien installointiohjelma onvat yhteensopivia.

Windowsiin on tarjolla useita python-kehitysympäristöjä, joista WinPythonia voi kokeilla jopa ilman admin-oikeuksia. WinPythoniin saa pygame modulin täältä klikkaamalla sopivaa versiota, esimerkiksi pygame‑1.9.2a0‑cp34‑none‑win_amd64.whl

WinPython asennetaan käynnistämällä ladattu winpython exe-tiedosto. Sen jälkeen pygame moduli installoidaan käynnistämällä syntyneestä winpython hakemistosta WinPython Control Panel. Samasta hakemistosta löytyy myös Spyder Käynnistä se ja ala koodata tai avaa joku jatkossa esiteltävistä ohjelmista.

Installointia Maciin — minulle vieraiseen järjestelmään — en ole päässyt kokeilemaan, mutta senkään ei pitäisi olla kovin vaikeaa.

Ohjelmoinnin opiskelu alkeista lähtien

Yleisin opetuksessa perinteisesti käytetty ensimmäinen ohjelma lienee Hello world- ohjelma, joka tulostaa näytölle "Heippa". Ohjelmointia voi opetella aloittamalla alkeista ja siirtymällä uuteen asiaan vasta kun on perusteellisesti sisäistänyt edellisen. Toimiva tapa oppia, mutta monen mielestä hidas ja tylsä.

"Tylsä" tapa opiskella ohjelmointia ei välttämättä ole huono tai tehoton. Tällä sivustolla ehdotan "tyvestä-puuhun" menetelmän sijasta jotain vallan muuta, silti suosittelen tutustumaan esimerkiksi Pythonin kotisivuilla olevaan dokumentaatioon ja tutoriaaleihin ja tarjolla oleviin verkkokursseihin.

Voit kokeilla esimerkiksi codecademyn python kurssia, BeginnersGuide for Non Programmers tai tätä tutoriaalia . Näistä kannattaa hakea apua myös seuraavien ohjelmien ymmärtäämiseen. Hakukoneilla löydät ehkä itsellesi vielä paremmin sopivaa oppimateriaalia.

Kirja making games selittää seikkaperäisesti, miten tehdä pelejä pygamesilla.

Valmiin ohjelman muokkaaminen ja laajentaminen

Ohjelmointia voi opetella tutustumalla johonkin olemassa olevaan mielenkiintoiseen ohjelmaan ja yrittämällä muokata sitä. Yleensä muokkaamiseen riittää ymmärtää ohjelmaa vain osittain, joten melko vähällä perehtymisellä voi saada paljon aikaan. Ohjelmaa myös alkaa ymmärtää paremmin, kun näkee sen toimivan. Esimerkiksi olio-ohjelmointiin voi yrittää tutustua lukemalla kirjoista ja sitten kokeilemalla, mutta uskoakseni oppimista auttaa nähdä muutama "olio" vipeltämässä ruudulla ennen teoriaan perehtymistä.

Seuraavassa tutustutaan yksinkertaisen peliohjelman runkoon. Se ei tee juuri mitään, mutta pienellä muokkauksella saamme ohjelman tynkämme tekemään monen laista hauskaa. Ilman valmista mallia joutuisimme opettelemaan paljon uutta Python-ohjelmoinnista ja keksimään uudestaan muiden jo keksimää. Erityisesti alkuun pääseminen on ohjelmoinnissa vaikeaa, koska ei tiedä, mitä pitäisi opetella ja selvittää.

Aletaan tutkia ensimmäisen peliohjelmamme tynkää. Seuraavassa esitellään vaihe vaiheelta peliohjelman kehittely. Kunkin vaiheen lopussa on vaihetta vastaava versio ohjelmasta. Lataa vaiheen 0 lopusta koodi spyderiin tai muuhun käyttämääsi kehitysympäristöön.

Spyder-ympäristön yläpalkissa on toiminto source → fix indendation, joka auttaa saamaan sisennykset kohdalleen. Komento source → run static code analysis (F8) auttaa löytämään sisennys- ja muita -virheitä ohjelmasta. Tab ja shift+tab auttavat sisennyksessä. Run (F5) käynnistää ohjelman. IDLE-ympäristössä on vastaavat toiminnot.

Ohjelmoinnissa on tyypillistä, että kaikki ei onnistu ensimmäisellä yrityksellä. Ikävä kyllä ongelmien selvittely on ikävää puuhaa ja lipsahtaa helposti hakuammunnaksi.

Seuraavassa on paljon koodia ja sama koodi moneen kertaan vähän muutettuna ja täydennettynä. Pelkästään koodia silmäilemällä et opi paljoa.

Seuraavassa ohjelmaa täydennetään ja muokataan vaihe vaiheelta. Kussakin vaiheessa näytetään aikaisempi koodi mustalla ja aikaisempaan versioon lisätty koodi vihreällä ja mahdollinen poistettava koodi punaisella. Sinisellä on joitakin rivejä, joissa on muuteltavia parametrejä. Ensin näytetään kunkin vaiheen ohjelmakoodi kokonaan ja sen jälkeen erikseen joitakin pätkiä, joita selitetään tarkemmin. Kunkin vaiheen ohjelma on ladattavissa suoraan tältä sivulta. Näissä harjoituksissa ei välttämättä tarvitse kirjoittaa koodia vaan riittää kokeilla eri versioita valmiista ohjelmasta. Yritä ymmärtää, miten ohjelma toimii tai miten joku yksittäinen toiminto tai efekti on toteutettu. Kannattaa myös yrittää muutella ohjelmaa aina kun herää pienikin ajatus jostain, mitä voisi kokeilla.


( )

"Hyttysjahti" oppitunnin kulku


Kyllästyttyäsi hyttysjahtiin siirry mörssäripeliin. Siinä voit tutustua, miten matematiikkaa ja fysiikan tietoja voi käyttää pelissä hyväksi. Jos inhoat matikkaa ja fyssaa, voit silti tehdä harjoituksen. Matikka ja fyssa on koodissa valmiina, joten sinun ei ole pakko yrittää ymmärtää, miten trigonometriaa tai Newtonin lakia on käytetty peliä koodatessa. Näet joka tapauksessa, miten fysiikan lait toimivat.

Mörssäripelin jälkeen on tarjolla peli, jossa pitää ohjata avaruusalus Maata kiertävältä radalta Kuuta kiertävälle radalle. Sen avulla voi oppia vähän lisää fysiikkaa. Tehtävä ei ole aivan helppo, mutta pelaaminen tutustuttaa taivaanmekaniikan perusteisiin. Jos tehtävä alkaa tuntua liian helpolta, pienennä Kuun massaa. Nyt se on monta kertaa todellista suurempi.


(Sun Mar 11 09:58:51 2018 )

HyttysJahti.py

Rakennetaan vaihe vaiheelta videopeli, jossa jahdataan hyttysiä. Hyttyslätkää ohjataan näppäimillä z,x, nuoli-ylös ja nuoli-alas. Ohjelma pysäytetään joko esc-näppäimellä tai sulkemalla peli-ikkuna.

Esittelyvideo pelistä



()

Vaihe 0: Peliohjelman perusrakenne

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

# Tässä pelissä läiskitään ympäriinsä lenteleviä hyttysiä

import pygame
import random
from math import sqrt

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

# Peli-ikkunan koko pikseleinä. Pikseli on piste näytöllä.
Wx = 800
Wy = 600
Wxy = (Wx, Wy)  # peli-ikkunan korkeus ja leveys

Nayttotaajuus = 24  # Voit kokeilla eri näyttötaajuuksia

Dt = 1.0/Nayttotaajuus

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

pygame.display.init()
# Selvitetään peli-ikkunan koko näytöllä pikseleinä. Pikseli on piste näytöllä.
D_Info = pygame.display.Info()
Wx = D_Info.current_w
Wy = int(0.9*D_Info.current_h)
Wxy = (Wx, Wy)  # peli-ikkunan korkeus ja leveys

screen = pygame.display.set_mode((Wx, Wy))
pygame.display.set_caption("Hyttysjahti")
pygame.init()
clock = pygame.time.Clock()
random.seed()

#
# Toistetaan allaolevaa while-silmukkaa, kunnes käyttäjä sulkee peli-ikkunan
# tai painaa ESC-iä
#
loppu = False
while not loppu:

    clock.tick(Nayttotaajuus)

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            loppu = True
        if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
            loppu = True

    screen.fill(Tausta)

    # näytetään kaikki yllä 'piirretty' näytöllä
    pygame.display.flip()
    #
    # while silmukka päättyy tähän
#
# Ohjelman loppu
pygame.quit()


Selityksiä ylläolevaan

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

Ohjelman ensimmäisellä rivillä määrittely -*- coding: utf-8 -*- kertoo, missä muodossa kirjaimet ja muut merkit talletetaan tietokoneen muistiin. Siitä ei tarvitse tietää tämän enempää.

 #  Tässä pelissä läiskitään ympäriinsä lenteleviä hyttysiä

Ylläoleva on komentti eli selitys ohjelman lukijalle. Kommentit eivät vaikuta millään lailla ohjelman suoritukseen. Kommentti merkitään rivin alussa olevalla # -merkillä. Kommentit voidaan merkitä myös kolmella lainausmerkillä, mutta suositeltavampaa on käyttää # -merkkiä.

import pygame
import random
from math import sqrt


Import-käskyllä otetaan käyttöön muiden ohjelmoimia valmiita toimintoja:

  • Import pygame tuo ohjelmamme käyttöön hyödyllisiä peliohjelmointiin liittyviä funktioita, kuten pygame.display.caption("Pelin nimi").
  • Import random tuo ohjelmamme käyttöön satunnaisluvut, joilla saamme hyttyset lennähtelemään minne sattuu.
  • from math import sqrt tuo käyttöömme funktion neliöjuuren laskemiseen. Sitä tarvitsemme mm. soveltaessamme Pythagoraan lausetta hyttysen ja lätkän välisen etäisyyden laskemiseen.
Musta = (0, 0, 0)  #  Mustassa ei ole mitään valoa
Sin = (0, 0, 255)  #  vain sinistä
Pun = (255, 0, 0)
Vihr = (0, 255, 0)
Valk = (255, 255, 255)  #  valkoisessa on kaikenväristä valoa
Tausta = (160, 200, 255)


Ohjelmoinnissa värit voidaan esittää sinisen, vihreän ja punaisen valon yhdistelminä -- RGB-koodina -- väri = (punaisen määrä, vihreän määrä, sinisen määrä). Kunkin perusvärin määrä valojen sekoituksessa ilmoitetaan kokonaisluvulla väliltä 0..255. Mustassa ei ole mitään valoa ja valkoisessa on kaiken väristä valoa. (128, 128, 128) lienee keskiharmaa. Värit voisi ohjelmassa esittää toisinkin, mutta RGB--koodia käyttämällä voi tehdä hauskoja väriefektejä.

Sulkumerkkien avulla voidaan Pythonissa koota kätevästi monta asiaa yhteen -- yhdeksi muuttujaksi. Jos innostut enemmän Python-ohjelmoinnista, googlaa jossain vaiheessa "python tuple". Musta, Sin, Pun, Vihr, Valk ovat muuttujien nimiä, jotka voi itse valita.

 #  Peli-ikkunan koko pikseleinä. Pikseli on piste näytöllä.
Wx = 800
Wy = 600
Wxy = (Wx, Wy)  #  peli-ikkunan korkeus ja leveys


Ylläoleva kertoo, että peli-ikkunan leveys on 800 pikseliä ja korkeus 600 pikseliä. Näitä voit vapaasti muuttaa.

Pygame peli-ikkunan koordinaatisto on jostain syystä erilainen kuin matematiikassa normaalisti käytetty. Piste (0,0) on vasemmassa yläkulmassa ja piste (Wx,Wy) oikeassa alakulmassa. x siis kasvaa ihan järkevästi oikealle mentäessä, mutta y kasvaa alaspäin mentäessä.

Nayttotaajuus = 24  #  Voit kokeilla eri näyttötaajuuksia

Dt = 1.0/Nayttotaajuus


Peli-ikkuna piirretään uudelleen -- päivitetään -- näyttötaajuus kertaa sekunnissa eli päivitysten väli on Dt sekuntia. Tavallisissa elokuvissa kuva vaihtuu 24 kertaa sekunnissa. Kunhan saamme ohjelman toimimaan, voit kokeilla, mitä näyttötaajuuden laskeminen esimerkiksi kymmeneen vaikuttaa. Millä taajuudella ihminen erottaa kuvat erillisiksi niin, että peli alkaa nykiä?

Pelin olioille pitää laskea uusi paikka ajan Dt välein, eli pitää laskea, paljonko pelin oliot liikkuvat ajassa Dt.

Jotkin Pythonin funktiot --- kuten pygamen piirtofuktiot --- vaativat argumenteikseen kokonaislukuja, koska kokonaisluvut ja reaaliluvut tallennetaan tietokoneen muistiin eri tavalla. Reaaliluvusta saa kokonaisluvun int-funtiolla seuraavasti: Komento x = 4/3 antaa x:lle arvon 1.33333... ja i = int(x) tai komento i = int(4/3) antaa i:lle arvon 1

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


Alustuksia, jotka kopioin saamastani malliohjelmasta. Luodaan peli-ikkuna screen ja annetaan sille nimi "Hyttysjahti".

Ilman malliohjelmaa minulta olisi mennyt ties miten pitkään ymmärtää, mitä tällaisia komentoja tarvitaan pelin alustamiseksi.

pygame.display.init()
 #  Selvitetään peli-ikkunan koko näytöllä pikseleinä. Pikseli on piste näytöllä.
D_Info = pygame.display.Info()
Wx = D_Info.current_w
Wy = int(0.9*D_Info.current_h)
Wxy = (Wx, Wy)  #  peli-ikkunan korkeus ja leveys


Ylläoleva kertoo, että peli-ikkunan leveys on Wx pikseliä ja korkeus Wy pikseliä.

Pygame peli-ikkunan koordinaatisto on jostain syystä erilainen kuin matematiikassa normaalisti käytetty. Piste (0,0) on vasemmassa yläkulmassa ja piste (Wx,Wy) oikeassa alakulmassa. x siis kasvaa ihan järkevästi oikealle mentäessä, mutta y kasvaa alaspäin mentäessä.

screen = pygame.display.set_mode((Wx, Wy))
pygame.display.set_caption("Hyttysjahti")
pygame.init()
clock = pygame.time.Clock()
random.seed()


 # 
 #  Toistetaan allaolevaa while-silmukkaa, kunnes käyttäjä sulkee peli-ikkunan
 #  tai painaa ESC-iä
 # 
loppu = False
while not loppu:


Alla komento clock.tick(Nayttotaajuus) oleva varmistaa, että tätä while-silmukkaa toistetaan Nayttotaajuus kertaa sekunnissa. Ohjelma jää odottamaan tähän, kunnes aikaa on kulunut tarpeeksi. Peliohjelman odotellessa tietokoneesi pääsee suorittamaan muita ohjelmia.

    clock.tick(Nayttotaajuus)


Jos käyttäjä on sulkenut peli-ikkunan tai painanut ESC-näppäintä, asetetaan loppu todeksi niin, että tämä jää viimeiseksi while-silmukan toistoksi

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            loppu = True
        if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
            loppu = True


Jokaisella while-silmukan toistolla täytetään peli-ikkuna taustavärillä eli ikkuna pyyhkiytyy tyhjäksi

    screen.fill(Tausta)



    #  näytetään kaikki yllä 'piirretty' näytöllä
    pygame.display.flip()
    # 
    #  while silmukka päättyy tähän
 # 
 #  Ohjelman loppu
pygame.quit()


Tämä ohjelma ei vielä tee mitään, mutta näytölle olisi pitänyt ilmestyä Hyttysjahti niminen ikkuna.

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



()

Vaihe 1: Luodaan lätkä hyttysten pyydystämiseen

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

# Tässä pelissä läiskitään ympäriinsä lenteleviä hyttysiä

import pygame
import random
from math import sqrt

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

# Peli-ikkunan koko pikseleinä. Pikseli on piste näytöllä.
Wx = 800
Wy = 600
Wxy = (Wx, Wy)  # peli-ikkunan korkeus ja leveys

Nayttotaajuus = 24  # Voit kokeilla eri näyttötaajuuksia

Dt = 1.0/Nayttotaajuus

# Peliin liittyviä muuttujia.
VLatka = 200.0  # Lätkän nopeus

# __init__(self, x, y, koko) on metodi, jolla lätkä luodaan

class Cl_Latka:
    def __init__(self, x, y, koko):
        self.paikka = (x, y)
        self.koko = koko
        self.color = Vihr
        self.liikkuu = False

    def piirra(self):
        (x, y) = self.paikka
        iPaikka = (int(x), int(y))
        pygame.draw.circle(screen, self.color, iPaikka, self.koko, 0)
        pygame.draw.circle(screen, Pun, iPaikka, int(self.koko/2), 0)

    def liiku(self, vxvy):
        (x, y) = self.paikka
        (vx, vy) = vxvy
        (x, y) = (x + vx*Dt, y + vy*Dt)
        # Lisäämällä tähän väliin muutaman if lauseen, voit korjata ohjelmaa
        #  niin, että Lätkä ei karkaa peli-ikkunan reunan ulkopuolelle
        # jos x on suurempi kuin niin Wx x.stä kannattaa vähentää Wx jne...
        self.paikka = (x, y)
        self.liikkuu = True

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

pygame.display.init()
# Selvitetään peli-ikkunan koko näytöllä pikseleinä. Pikseli on piste näytöllä.
D_Info = pygame.display.Info()
Wx = D_Info.current_w
Wy = int(0.9*D_Info.current_h)
Wxy = (Wx, Wy)  # peli-ikkunan korkeus ja leveys

screen = pygame.display.set_mode((Wx, Wy))
pygame.display.set_caption("Hyttysjahti")
pygame.init()
clock = pygame.time.Clock()
random.seed()

# Luodaan 'olio' tyyppiä Cl_latka
Latka = Cl_Latka(Wx-50, Wy-50, 15)

#
# Toistetaan allaolevaa while-silmukkaa, kunnes käyttäjä sulkee peli-ikkunan
# tai painaa ESC-iä
#
loppu = False
while not loppu:

    clock.tick(Nayttotaajuus)

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            loppu = True
        if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
            loppu = True

    screen.fill(Tausta)

    Latka.liikkuu = False
#   Tarkistetaan, onko joku tai jotkin lätkää ohjaavista näppäimistä
# painettuna. Liikutetaan lätkää vastaavasti.
    pressed = pygame.key.get_pressed()
    if pressed[pygame.K_UP]:
        Latka.liiku((0.0, -VLatka))  # ylös
    if pressed[pygame.K_DOWN]:
        Latka.liiku((0.0, VLatka))  # alas
    if pressed[pygame.K_z]:
        Latka.liiku((-VLatka, 0.0))  # vasemmalle
    if pressed[pygame.K_x]:
        Latka.liiku((VLatka, 0.0))  # oikealle
    # Piirretään lätkä uudelle paikalleen
    Latka.piirra()

    # näytetään kaikki yllä 'piirretty' näytöllä
    pygame.display.flip()
    #
    # while silmukka päättyy tähän
#
# Ohjelman loppu
pygame.quit()


Selityksiä ylläolevaan

import pygame
import random
from math import sqrt


Nayttotaajuus = 24  #  Voit kokeilla eri näyttötaajuuksia

 #  Peliin liittyviä muuttujia.
VLatka = 200.0  #  Lätkän nopeus

 #  __init__(self, x, y, koko) on metodi, jolla lätkä luodaan



class Cl_Latka:
    def __init__(self, x, y, koko):
        self.paikka = (x, y)
        self.koko = koko
        self.color = Vihr
        self.liikkuu = False


Määritellään, minkälainen 'olio' on lätkä, jolla huiskimme hyttysiä.

Lätkällä on paikka (x, y) peli-ikkunassa, koko ja väri. Vielä olioon lätkä kuuluu tieto siitä, liikkuuko se, koska ohjelmassa on ehto, että vain liikkuva lätkä tappaa hyttysen. Ei riitä, että hyttynen osuu lätkään.

Jotkin Pythonin funktiot --- kuten pygamen piirtofuktiot --- vaativat argumenteikseen kokonaislukuja, koska kokonaisluvut ja reaaliluvut tallennetaan tietokoneen muistiin eri tavalla. Reaaliluvusta saa kokonaisluvun int-funtiolla seuraavasti: Komento x = 4/3 antaa x:lle arvon 1.33333... ja i = int(x) tai komento i = int(4/3) antaa i:lle arvon 1

    def piirra(self):
        (x, y) = self.paikka
        iPaikka = (int(x), int(y))
        pygame.draw.circle(screen, self.color, iPaikka, self.koko, 0)
        pygame.draw.circle(screen, Pun, iPaikka, int(self.koko/2), 0)


Lätkä täytyy piirtää uudelleen ajan Dt-välein. Tämä tehdään olioon lätkä kuuluvalla metodilla piirra(), joka piirtää lätkän värillä ympyrän ja sen päälle pienemmän punaisen ympyrän. pygame.draw.circle on ohjelman alussa importoidun pygame modulin funktio, joka on dokumentoitu pygame web-sivulla

    def liiku(self, vxvy):
        (x, y) = self.paikka
        (vx, vy) = vxvy
        (x, y) = (x + vx*Dt, y + vy*Dt)
        #  Lisäämällä tähän väliin muutaman if lauseen, voit korjata ohjelmaa
        #   niin, että Lätkä ei karkaa peli-ikkunan reunan ulkopuolelle
        #  jos x on suurempi kuin niin Wx x.stä kannattaa vähentää Wx jne...
        self.paikka = (x, y)
        self.liikkuu = True


Metodi liikux(vx) laskee lätkälle uuden paikan, jos nuoli ylös tai alas tai z tai x on painettuna.

Alempana -- pelin pääohjelmassa -- tarkistetaan, mitä näppäimiä pelaaja pitää pohjassa tai on painanut.

vx on lätkän nopeus, eli ajassa Dt lätkä kulkee matkan vx*Dt

Huom: Ohjelmoinnissa a = a + 5 ei ole yhtälö, vaan sijoituslause, joka tarkoittaa, että a:lle annetaan uusi arvo, joka on a:n vanha arvo + 5.

Lopuksi päivitetään vielä tieto, että lätkä liikkuu.

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


. . .
 #  Luodaan 'olio' tyyppiä Cl_latka
Latka = Cl_Latka(Wx-50, Wy-50, 15)


 # 
 #  Toistetaan allaolevaa while-silmukkaa, kunnes käyttäjä sulkee peli-ikkunan
 #  tai painaa ESC-iä
 # 
loppu = False
while not loppu:


    Latka.liikkuu = False

 #    Tarkistetaan, onko joku tai jotkin lätkää ohjaavista näppäimistä
 #  painettuna. Liikutetaan lätkää vastaavasti.
    pressed = pygame.key.get_pressed()
    if pressed[pygame.K_UP]:
        Latka.liiku((0.0, -VLatka))  #  ylös
    if pressed[pygame.K_DOWN]:
        Latka.liiku((0.0, VLatka))  #  alas
    if pressed[pygame.K_z]:
        Latka.liiku((-VLatka, 0.0))  #  vasemmalle
    if pressed[pygame.K_x]:
        Latka.liiku((VLatka, 0.0))  #  oikealle

    #  Piirretään lätkä uudelle paikalleen
    Latka.piirra()

    #  näytetään kaikki yllä 'piirretty' näytöllä
    pygame.display.flip()
    # 
    #  while silmukka päättyy tähän
 # 
 #  Ohjelman loppu
pygame.quit()


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



()

Vaihe 2: Luodaan hyttynen

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

# Tässä pelissä läiskitään ympäriinsä lenteleviä hyttysiä

import pygame
import random
from math import sqrt

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

# Peli-ikkunan koko pikseleinä. Pikseli on piste näytöllä.
Wx = 800
Wy = 600
Wxy = (Wx, Wy)  # peli-ikkunan korkeus ja leveys

Nayttotaajuus = 24  # Voit kokeilla eri näyttötaajuuksia

Dt = 1.0/Nayttotaajuus

# Peliin liittyviä muuttujia.
VLatka = 200.0  # Lätkän nopeus

Vmax = 250.0
MaxKiihtyvyys = 400.0

# __init__(self, x, y, koko) on metodi, jolla lätkä luodaan

class Cl_Latka:
    def __init__(self, x, y, koko):
        self.paikka = (x, y)
        self.koko = koko
        self.color = Vihr
        self.liikkuu = False

    def piirra(self):
        (x, y) = self.paikka
        iPaikka = (int(x), int(y))
        pygame.draw.circle(screen, self.color, iPaikka, self.koko, 0)
        pygame.draw.circle(screen, Pun, iPaikka, int(self.koko/2), 0)

    def liiku(self, vxvy):
        (x, y) = self.paikka
        (vx, vy) = vxvy
        (x, y) = (x + vx*Dt, y + vy*Dt)
        # Lisäämällä tähän väliin muutaman if lauseen, voit korjata ohjelmaa
        #  niin, että Lätkä ei karkaa peli-ikkunan reunan ulkopuolelle
        # jos x on suurempi kuin niin Wx x.stä kannattaa vähentää Wx jne...
        self.paikka = (x, y)
        self.liikkuu = True

class Cl_Hyttynen:
    def __init__(self, x, y):
        self.paikka = (x, y)

        self.nopeus = (70.0, 50.0)  # kokeile tähän eri arvoja

        self.koko = 10
        self.color = (random.randint(0, 255),
                      random.randint(0, 255),
                      random.randint(0, 255))
        self.elossa = True

    def piirra(self):
        iPaikka = (int(self.paikka[0]), int(self.paikka[1]))
        iEllxy = (iPaikka[0], iPaikka[1], 4*self.koko, self.koko)
        if self.elossa:
            pygame.draw.circle(screen, self.color, iPaikka, self.koko, 0)
        else:
            pygame.draw.ellipse(screen, self.color, iEllxy, 0)

    def liiku_suoraan(self):
        (x, y) = self.paikka
        (vx, vy) = self.nopeus
        (x, y) = (x + vx*Dt, y + vy*Dt)
        # poista seuraavista kommenttimerkki,
        # niin saat hyttysen pysymään ruudulla
#        if (x > Wx and vx > 0) or (x < 0 and vx < 0):
#            vx = -vx
#        elif (y > Wy and vy > 0) or (y < 0 and vy < 0):
#            vy = -vy
        self.paikka = (x, y)
        self.nopeus = (vx, vy)

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

pygame.display.init()
# Selvitetään peli-ikkunan koko näytöllä pikseleinä. Pikseli on piste näytöllä.
D_Info = pygame.display.Info()
Wx = D_Info.current_w
Wy = int(0.9*D_Info.current_h)
Wxy = (Wx, Wy)  # peli-ikkunan korkeus ja leveys

screen = pygame.display.set_mode((Wx, Wy))
pygame.display.set_caption("Hyttysjahti")
pygame.init()
clock = pygame.time.Clock()
random.seed()

# Luodaan 'olio' tyyppiä Cl_latka
Latka = Cl_Latka(Wx-50, Wy-50, 15)

# Luodaan yksi hyttynen
hyttynen = Cl_Hyttynen(50, 50)

#
# Toistetaan allaolevaa while-silmukkaa, kunnes käyttäjä sulkee peli-ikkunan
# tai painaa ESC-iä
#
loppu = False
while not loppu:

    clock.tick(Nayttotaajuus)

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            loppu = True
        if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
            loppu = True

    screen.fill(Tausta)

    hyttynen.liiku_suoraan()

    hyttynen.piirra()

    Latka.liikkuu = False
#   Tarkistetaan, onko joku tai jotkin lätkää ohjaavista näppäimistä
# painettuna. Liikutetaan lätkää vastaavasti.
    pressed = pygame.key.get_pressed()
    if pressed[pygame.K_UP]:
        Latka.liiku((0.0, -VLatka))  # ylös
    if pressed[pygame.K_DOWN]:
        Latka.liiku((0.0, VLatka))  # alas
    if pressed[pygame.K_z]:
        Latka.liiku((-VLatka, 0.0))  # vasemmalle
    if pressed[pygame.K_x]:
        Latka.liiku((VLatka, 0.0))  # oikealle
    # Piirretään lätkä uudelle paikalleen
    Latka.piirra()

    # näytetään kaikki yllä 'piirretty' näytöllä
    pygame.display.flip()
    #
    # while silmukka päättyy tähän
#
# Ohjelman loppu
pygame.quit()


Selityksiä ylläolevaan

import pygame
import random
from math import sqrt


Nayttotaajuus = 24  #  Voit kokeilla eri näyttötaajuuksia

 #  Peliin liittyviä muuttujia.
VLatka = 200.0  #  Lätkän nopeus

Vmax = 250.0
MaxKiihtyvyys = 400.0

class Cl_Hyttynen:
    def __init__(self, x, y):
        self.paikka = (x, y)

        self.nopeus = (70.0, 50.0)  #  kokeile tähän eri arvoja

        self.koko = 10
        self.color = (random.randint(0, 255),
                      random.randint(0, 255),
                      random.randint(0, 255))
        self.elossa = True


Määritellään olioluokka Cl_Hyttynen, joka kertoo, millaisia olioita hyttyset ovat. Oliomäärittelyn sisällä määriteltyjä funktiota kutsutaan metodeiksi. Metodi __init__ luo olion, eli se kertoo, millaisia oliot ovat 'syntyessään': Jokainen luokkaan Otokka kuuluva olio luodaan -- 'syntyy' -- johonkin paikkaan koordinaatistossa, sillä on jokin nopeus ja se on jonkin kokoinen ja se on elossa syntyessään. Väriltään olio on mitä sattuu, koska läiskäisemme siihen satunnaisen määrän sinistä, punaista ja vihreää. random.randint(0, 255) on jokin kokonaisluku väliltä 0..255

Määrittelyssä käytetty self viittaa olioon itseensä. Sen tarkan merkityksen ja tarkoituksen ymmärtäminen on vaikeaa, mutta älä takerru siihen nyt. Olio-ohjelmoinnin kunnollinen ymmärtäminen on tärkeää melkein kaikessa ohjelmoinnissa, mutta tästä harjoituksesta selviämme seuraamalla valmista esimerkkiä, joten kaikkea ei tarvitse yrittää ymmärtää perin pohjin. Esimerkin myötä saanet aavistuksen olio-ohjelmoinnista niin, että teorian omaksuminen on tarvittaessa helpompaa.

Kuten lätkälle, hyttysellekin pitää määritellä metodi, joka piirtää hyttysen peli-ikkunaan. Elävä hyttynen esitetään ympyränä, kuollut litteänä ellipsinä.

pygame-modulista löytyy metodi draw.circle(screen, x, y, sade, reunan_leveys), joka piirtää ympyrän näytölle, eikä meidän tarvitse vaivata päätämme sillä, miten se sen tekee.

Käytämme laskuissa reaalilukuja, mutta draw.circle-funktio haluaa paikan koordinaatit kokonaislukuina. Siksi käytämme funktiota int(x), joka pyöristää reaaliluvun x lähinnä vastaavaksi kokonaisluvuksi. Tällainen käytännössä tarvittava pikkusälä tekee periaatteessa selkeästäkin algoritmista vaikeaselkoista.

    def piirra(self):
        iPaikka = (int(self.paikka[0]), int(self.paikka[1]))
        iEllxy = (iPaikka[0], iPaikka[1], 4*self.koko, self.koko)
        if self.elossa:
            pygame.draw.circle(screen, self.color, iPaikka, self.koko, 0)
        else:
            pygame.draw.ellipse(screen, self.color, iEllxy, 0)


Kuten lätkälle, hyttysellekin pitää laskea uusi paikka ajan Dt välein. Seuraava alkeellinen metodi liikuttaa hyttystä suoraa viivaa pitkin

    def liiku_suoraan(self):
        (x, y) = self.paikka
        (vx, vy) = self.nopeus
        (x, y) = (x + vx*Dt, y + vy*Dt)
        #  poista seuraavista kommenttimerkki,
        #  niin saat hyttysen pysymään ruudulla
 #         if (x > Wx and vx > 0) or (x < 0 and vx < 0):
 #             vx = -vx
 #         elif (y > Wy and vy > 0) or (y < 0 and vy < 0):
 #             vy = -vy
        self.paikka = (x, y)
        self.nopeus = (vx, vy)


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


. . .
 #  Luodaan 'olio' tyyppiä Cl_latka
Latka = Cl_Latka(Wx-50, Wy-50, 15)


 #  Luodaan yksi hyttynen
hyttynen = Cl_Hyttynen(50, 50)

 # 
 #  Toistetaan allaolevaa while-silmukkaa, kunnes käyttäjä sulkee peli-ikkunan
 #  tai painaa ESC-iä
 # 
loppu = False
while not loppu:


    hyttynen.liiku_suoraan()

    hyttynen.piirra()

    #  näytetään kaikki yllä 'piirretty' näytöllä
    pygame.display.flip()
    # 
    #  while silmukka päättyy tähän
 # 
 #  Ohjelman loppu
pygame.quit()


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



()

Vaihe 3: Laitetaan hyttynen lentelemään ympäriinsä

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

# Tässä pelissä läiskitään ympäriinsä lenteleviä hyttysiä

import pygame
import random
from math import sqrt

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

# Peli-ikkunan koko pikseleinä. Pikseli on piste näytöllä.
Wx = 800
Wy = 600
Wxy = (Wx, Wy)  # peli-ikkunan korkeus ja leveys

Nayttotaajuus = 24  # Voit kokeilla eri näyttötaajuuksia

Dt = 1.0/Nayttotaajuus

# Peliin liittyviä muuttujia.
VLatka = 200.0  # Lätkän nopeus

Vmax = 250.0
MaxKiihtyvyys = 400.0

# __init__(self, x, y, koko) on metodi, jolla lätkä luodaan

class Cl_Latka:
    def __init__(self, x, y, koko):
        self.paikka = (x, y)
        self.koko = koko
        self.color = Vihr
        self.liikkuu = False

    def piirra(self):
        (x, y) = self.paikka
        iPaikka = (int(x), int(y))
        pygame.draw.circle(screen, self.color, iPaikka, self.koko, 0)
        pygame.draw.circle(screen, Pun, iPaikka, int(self.koko/2), 0)

    def liiku(self, vxvy):
        (x, y) = self.paikka
        (vx, vy) = vxvy
        (x, y) = (x + vx*Dt, y + vy*Dt)
        # Lisäämällä tähän väliin muutaman if lauseen, voit korjata ohjelmaa
        #  niin, että Lätkä ei karkaa peli-ikkunan reunan ulkopuolelle
        # jos x on suurempi kuin niin Wx x.stä kannattaa vähentää Wx jne...
        self.paikka = (x, y)
        self.liikkuu = True

class Cl_Hyttynen:
    def __init__(self, x, y):
        self.paikka = (x, y)

        self.nopeus = (70.0, 50.0)  # kokeile tähän eri arvoja

        self.koko = 10
        self.color = (random.randint(0, 255),
                      random.randint(0, 255),
                      random.randint(0, 255))
        self.elossa = True

    def piirra(self):
        iPaikka = (int(self.paikka[0]), int(self.paikka[1]))
        iEllxy = (iPaikka[0], iPaikka[1], 4*self.koko, self.koko)
        if self.elossa:
            pygame.draw.circle(screen, self.color, iPaikka, self.koko, 0)
        else:
            pygame.draw.ellipse(screen, self.color, iEllxy, 0)

    def liiku_suoraan(self):
        (x, y) = self.paikka
        (vx, vy) = self.nopeus
        (x, y) = (x + vx*Dt, y + vy*Dt)
        # poista seuraavista kommenttimerkki,
        # niin saat hyttysen pysymään ruudulla
#        if (x > Wx and vx > 0) or (x < 0 and vx < 0):
#            vx = -vx
#        elif (y > Wy and vy > 0) or (y < 0 and vy < 0):
#            vy = -vy
        self.paikka = (x, y)
        self.nopeus = (vx, vy)

    def liiku(self):

        self.nopeus = lenna(self.paikka, self.nopeus)

# Siirrytään eteenpäin nopeus kertaa aikaväli
        self.paikka = dfdt(self.paikka, self.nopeus)

def lenna(paikka, nopeus):
    kiihtyvyys = (random.uniform(-MaxKiihtyvyys, MaxKiihtyvyys),
                  random.uniform(-MaxKiihtyvyys, MaxKiihtyvyys))
    nopeus = reunat(paikka, dfdt(nopeus, kiihtyvyys))
    return nopeus
# matka = nopeus*aika
def dfdt(xy, dxdy):
    (x, y) = xy
    (dx, dy) = dxdy
    return (x + dx*Dt, y + dy*Dt)

def reunat(xy, vxvy):
    (vx, vy) = vxvy
    (x, y) = xy
    # ei päästetä hyttystä reunojen ulkopuolelle
    if (x > Wx and vx > 0) or (x < 0 and vx < 0):
        vx = -vx
    elif (y > Wy and vy > 0) or (y < 0 and vy < 0):
        vy = -vy
    # pysäytetään hyttynen, mikäli se lentää tolkuttoman lujaa
    elif sqrt(vx**2 + vy**2) > Vmax:
        vx, vy = 0.0, 0.0
    return (vx, vy)

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

pygame.display.init()
# Selvitetään peli-ikkunan koko näytöllä pikseleinä. Pikseli on piste näytöllä.
D_Info = pygame.display.Info()
Wx = D_Info.current_w
Wy = int(0.9*D_Info.current_h)
Wxy = (Wx, Wy)  # peli-ikkunan korkeus ja leveys

screen = pygame.display.set_mode((Wx, Wy))
pygame.display.set_caption("Hyttysjahti")
pygame.init()
clock = pygame.time.Clock()
random.seed()

# Luodaan 'olio' tyyppiä Cl_latka
Latka = Cl_Latka(Wx-50, Wy-50, 15)

# Luodaan yksi hyttynen
hyttynen = Cl_Hyttynen(50, 50)

#
# Toistetaan allaolevaa while-silmukkaa, kunnes käyttäjä sulkee peli-ikkunan
# tai painaa ESC-iä
#
loppu = False
while not loppu:

    clock.tick(Nayttotaajuus)

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            loppu = True
        if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
            loppu = True

    screen.fill(Tausta)

    hyttynen.liiku_suoraan()

    hyttynen.liiku()

    hyttynen.piirra()

    Latka.liikkuu = False
#   Tarkistetaan, onko joku tai jotkin lätkää ohjaavista näppäimistä
# painettuna. Liikutetaan lätkää vastaavasti.
    pressed = pygame.key.get_pressed()
    if pressed[pygame.K_UP]:
        Latka.liiku((0.0, -VLatka))  # ylös
    if pressed[pygame.K_DOWN]:
        Latka.liiku((0.0, VLatka))  # alas
    if pressed[pygame.K_z]:
        Latka.liiku((-VLatka, 0.0))  # vasemmalle
    if pressed[pygame.K_x]:
        Latka.liiku((VLatka, 0.0))  # oikealle
    # Piirretään lätkä uudelle paikalleen
    Latka.piirra()

    # näytetään kaikki yllä 'piirretty' näytöllä
    pygame.display.flip()
    #
    # while silmukka päättyy tähän
#
# Ohjelman loppu
pygame.quit()


Selityksiä ylläolevaan

import pygame
import random
from math import sqrt


Nayttotaajuus = 24  #  Voit kokeilla eri näyttötaajuuksia

    def liiku_suoraan(self):
        (x, y) = self.paikka
        (vx, vy) = self.nopeus
        (x, y) = (x + vx*Dt, y + vy*Dt)
        #  poista seuraavista kommenttimerkki,
        #  niin saat hyttysen pysymään ruudulla
 #         if (x > Wx and vx > 0) or (x < 0 and vx < 0):
 #             vx = -vx
 #         elif (y > Wy and vy > 0) or (y < 0 and vy < 0):
 #             vy = -vy
        self.paikka = (x, y)
        self.nopeus = (vx, vy)


Laitetaan hyttynen lentelemään ympäriinsä

    def liiku(self):

        self.nopeus = lenna(self.paikka, self.nopeus)

 #  Siirrytään eteenpäin nopeus kertaa aikaväli
        self.paikka = dfdt(self.paikka, self.nopeus)



lennä(paikka, nopeus) on funktio, joka

  • muuttaa hyttysen nopeutta satunnaisella määrällä
  • kääntää hyttysen, jos se on peli-ikkunan reunalla ja menossa ulospäin
  • pysäyttää hyttysen, jos se lentää liian lujaa ;-)

Nopeuden muutos on keskimäärin nolla, mutta satunnaisluvut ovat oikukkaita ja nopeus saattaa kasvaa pitkäksi aikaa niin suureksi, ettei hyttyseen voi mitenkään osua. Siksi pysäytys.



def lenna(paikka, nopeus):
    kiihtyvyys = (random.uniform(-MaxKiihtyvyys, MaxKiihtyvyys),
                  random.uniform(-MaxKiihtyvyys, MaxKiihtyvyys))
    nopeus = reunat(paikka, dfdt(nopeus, kiihtyvyys))
    return nopeus

 #  matka = nopeus*aika


def dfdt(xy, dxdy):
    (x, y) = xy
    (dx, dy) = dxdy
    return (x + dx*Dt, y + dy*Dt)




def reunat(xy, vxvy):
    (vx, vy) = vxvy
    (x, y) = xy
    #  ei päästetä hyttystä reunojen ulkopuolelle
    if (x > Wx and vx > 0) or (x < 0 and vx < 0):
        vx = -vx
    elif (y > Wy and vy > 0) or (y < 0 and vy < 0):
        vy = -vy
    #  pysäytetään hyttynen, mikäli se lentää tolkuttoman lujaa
    elif sqrt(vx**2 + vy**2) > Vmax:
        vx, vy = 0.0, 0.0
    return (vx, vy)


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


. . .
 #  Luodaan 'olio' tyyppiä Cl_latka
Latka = Cl_Latka(Wx-50, Wy-50, 15)


 #  Luodaan yksi hyttynen
hyttynen = Cl_Hyttynen(50, 50)

 # 
 #  Toistetaan allaolevaa while-silmukkaa, kunnes käyttäjä sulkee peli-ikkunan
 #  tai painaa ESC-iä
 # 
loppu = False
while not loppu:


    hyttynen.liiku_suoraan()

    hyttynen.liiku()

    hyttynen.piirra()

    #  näytetään kaikki yllä 'piirretty' näytöllä
    pygame.display.flip()
    # 
    #  while silmukka päättyy tähän
 # 
 #  Ohjelman loppu
pygame.quit()


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



()

Vaihe 4: Ohjelmoidaan hyttyneen reagoimaan lätkän osumaan

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

# Tässä pelissä läiskitään ympäriinsä lenteleviä hyttysiä

import pygame
import random
from math import sqrt

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

# Peli-ikkunan koko pikseleinä. Pikseli on piste näytöllä.
Wx = 800
Wy = 600
Wxy = (Wx, Wy)  # peli-ikkunan korkeus ja leveys

Nayttotaajuus = 24  # Voit kokeilla eri näyttötaajuuksia

Dt = 1.0/Nayttotaajuus

# Peliin liittyviä muuttujia.
VLatka = 200.0  # Lätkän nopeus

Vmax = 250.0
MaxKiihtyvyys = 400.0

# __init__(self, x, y, koko) on metodi, jolla lätkä luodaan

class Cl_Latka:
    def __init__(self, x, y, koko):
        self.paikka = (x, y)
        self.koko = koko
        self.color = Vihr
        self.liikkuu = False

    def piirra(self):
        (x, y) = self.paikka
        iPaikka = (int(x), int(y))
        pygame.draw.circle(screen, self.color, iPaikka, self.koko, 0)
        pygame.draw.circle(screen, Pun, iPaikka, int(self.koko/2), 0)

    def liiku(self, vxvy):
        (x, y) = self.paikka
        (vx, vy) = vxvy
        (x, y) = (x + vx*Dt, y + vy*Dt)
        # Lisäämällä tähän väliin muutaman if lauseen, voit korjata ohjelmaa
        #  niin, että Lätkä ei karkaa peli-ikkunan reunan ulkopuolelle
        # jos x on suurempi kuin niin Wx x.stä kannattaa vähentää Wx jne...
        self.paikka = (x, y)
        self.liikkuu = True

class Cl_Hyttynen:
    def __init__(self, x, y):
        self.paikka = (x, y)

        self.nopeus = (70.0, 50.0)  # kokeile tähän eri arvoja

        self.koko = 10
        self.color = (random.randint(0, 255),
                      random.randint(0, 255),
                      random.randint(0, 255))
        self.elossa = True

    def piirra(self):
        iPaikka = (int(self.paikka[0]), int(self.paikka[1]))
        iEllxy = (iPaikka[0], iPaikka[1], 4*self.koko, self.koko)
        if self.elossa:
            pygame.draw.circle(screen, self.color, iPaikka, self.koko, 0)
        else:
            pygame.draw.ellipse(screen, self.color, iEllxy, 0)

    def liiku(self):

        self.isku()  # Onko lätkä osunut?
        if self.elossa:
            self.nopeus = lenna(self.paikka, self.nopeus)

        else:
            self.nopeus = self.putoa()

# Siirrytään eteenpäin nopeus kertaa aikaväli
        self.paikka = dfdt(self.paikka, self.nopeus)

#   Jos Latka on lähellä tätä hyttystä sekä x- että y-suunnassa
#   lätkä osuu, hyttynen kuolee -- litistyy ja muuttuu punaiseksi.
    def isku(self):
        if (etaisyys(self.paikka, Latka.paikka) < Latka.koko and
            Latka.liikkuu):
            self.elossa = False
            self.color = Pun

    def putoa(self):
        vx = random.uniform(-10, 10)
        if self.paikka[1] < Wy-self.koko:
            vy = random.uniform(20, 70)
        else:
            vx = 0
            vy = 0
        return (vx, vy)

def lenna(paikka, nopeus):
    kiihtyvyys = (random.uniform(-MaxKiihtyvyys, MaxKiihtyvyys),
                  random.uniform(-MaxKiihtyvyys, MaxKiihtyvyys))
    nopeus = reunat(paikka, dfdt(nopeus, kiihtyvyys))
    return nopeus
# matka = nopeus*aika
def dfdt(xy, dxdy):
    (x, y) = xy
    (dx, dy) = dxdy
    return (x + dx*Dt, y + dy*Dt)

def reunat(xy, vxvy):
    (vx, vy) = vxvy
    (x, y) = xy
    # ei päästetä hyttystä reunojen ulkopuolelle
    if (x > Wx and vx > 0) or (x < 0 and vx < 0):
        vx = -vx
    elif (y > Wy and vy > 0) or (y < 0 and vy < 0):
        vy = -vy
    # pysäytetään hyttynen, mikäli se lentää tolkuttoman lujaa
    elif sqrt(vx**2 + vy**2) > Vmax:
        vx, vy = 0.0, 0.0
    return (vx, vy)

# kahden pisteen p0 ja p1 välinen etäisyys xy-tasossa
def etaisyys(p0, p1):
    (x0, y0) = p0
    (x1, y1) = p1
    return sqrt((x1-x0)**2 + (y1-y0)**2)
# def ei_hyttysia(parvi):
#   for a in parvi:
#       if a.elossa:
#           return False
#   return True

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

pygame.display.init()
# Selvitetään peli-ikkunan koko näytöllä pikseleinä. Pikseli on piste näytöllä.
D_Info = pygame.display.Info()
Wx = D_Info.current_w
Wy = int(0.9*D_Info.current_h)
Wxy = (Wx, Wy)  # peli-ikkunan korkeus ja leveys

screen = pygame.display.set_mode((Wx, Wy))
pygame.display.set_caption("Hyttysjahti")
pygame.init()
clock = pygame.time.Clock()
random.seed()

# Luodaan 'olio' tyyppiä Cl_latka
Latka = Cl_Latka(Wx-50, Wy-50, 15)

# Luodaan yksi hyttynen
hyttynen = Cl_Hyttynen(50, 50)

#
# Toistetaan allaolevaa while-silmukkaa, kunnes käyttäjä sulkee peli-ikkunan
# tai painaa ESC-iä
#
loppu = False
while not loppu:

    clock.tick(Nayttotaajuus)

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            loppu = True
        if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
            loppu = True

    screen.fill(Tausta)

    hyttynen.liiku()

    hyttynen.piirra()

    Latka.liikkuu = False
#   Tarkistetaan, onko joku tai jotkin lätkää ohjaavista näppäimistä
# painettuna. Liikutetaan lätkää vastaavasti.
    pressed = pygame.key.get_pressed()
    if pressed[pygame.K_UP]:
        Latka.liiku((0.0, -VLatka))  # ylös
    if pressed[pygame.K_DOWN]:
        Latka.liiku((0.0, VLatka))  # alas
    if pressed[pygame.K_z]:
        Latka.liiku((-VLatka, 0.0))  # vasemmalle
    if pressed[pygame.K_x]:
        Latka.liiku((VLatka, 0.0))  # oikealle
    # Piirretään lätkä uudelle paikalleen
    Latka.piirra()

    # näytetään kaikki yllä 'piirretty' näytöllä
    pygame.display.flip()
    #
    # while silmukka päättyy tähän
#
# Ohjelman loppu
pygame.quit()


Selityksiä ylläolevaan

import pygame
import random
from math import sqrt


Nayttotaajuus = 24  #  Voit kokeilla eri näyttötaajuuksia

        self.isku()  #  Onko lätkä osunut?
        if self.elossa:
            self.nopeus = lenna(self.paikka, self.nopeus)

        else:
            self.nopeus = self.putoa()

 #    Jos Latka on lähellä tätä hyttystä sekä x- että y-suunnassa
 #    lätkä osuu, hyttynen kuolee -- litistyy ja muuttuu punaiseksi.
    def isku(self):
        if (etaisyys(self.paikka, Latka.paikka) < Latka.koko and
            Latka.liikkuu):
            self.elossa = False
            self.color = Pun

Kuolleena putoavan hyttysen liike: Nopeus x-suuntaan on pieni satunnaisluku, eli hyttynen leijailee alas lähes pystysuoraan. Ellei hyttynen jo ole maassa, sillä on pieni satunnainen nopeus alaspäin (muista, että y kasvaa epäloogisesti alaspäin). Jos hyttynen on jo maassa, y-suuntainen nopeus asetetaan nollaksi.

    def putoa(self):
        vx = random.uniform(-10, 10)
        if self.paikka[1] < Wy-self.koko:
            vy = random.uniform(20, 70)
        else:
            vx = 0
            vy = 0
        return (vx, vy)

 #  kahden pisteen p0 ja p1 välinen etäisyys xy-tasossa


def etaisyys(p0, p1):
    (x0, y0) = p0
    (x1, y1) = p1
    return sqrt((x1-x0)**2 + (y1-y0)**2)

 #  def ei_hyttysia(parvi):
 #    for a in parvi:
 #        if a.elossa:
 #            return False
 #    return True


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


. . .
 #  Luodaan 'olio' tyyppiä Cl_latka
Latka = Cl_Latka(Wx-50, Wy-50, 15)


 #  Luodaan yksi hyttynen
hyttynen = Cl_Hyttynen(50, 50)

 # 
 #  Toistetaan allaolevaa while-silmukkaa, kunnes käyttäjä sulkee peli-ikkunan
 #  tai painaa ESC-iä
 # 
loppu = False
while not loppu:


    hyttynen.liiku()

    hyttynen.piirra()

    #  näytetään kaikki yllä 'piirretty' näytöllä
    pygame.display.flip()
    # 
    #  while silmukka päättyy tähän
 # 
 #  Ohjelman loppu
pygame.quit()


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



()

Vaihe 5: Luodaan parvi hyttysiä

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

# Tässä pelissä läiskitään ympäriinsä lenteleviä hyttysiä

import pygame
import random
from math import sqrt

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

# Peli-ikkunan koko pikseleinä. Pikseli on piste näytöllä.
Wx = 800
Wy = 600
Wxy = (Wx, Wy)  # peli-ikkunan korkeus ja leveys

Nayttotaajuus = 24  # Voit kokeilla eri näyttötaajuuksia

Dt = 1.0/Nayttotaajuus

# Peliin liittyviä muuttujia.
VLatka = 200.0  # Lätkän nopeus

Vmax = 250.0
MaxKiihtyvyys = 400.0

N_hyttyset = 100

# __init__(self, x, y, koko) on metodi, jolla lätkä luodaan

class Cl_Latka:
    def __init__(self, x, y, koko):
        self.paikka = (x, y)
        self.koko = koko
        self.color = Vihr
        self.liikkuu = False

    def piirra(self):
        (x, y) = self.paikka
        iPaikka = (int(x), int(y))
        pygame.draw.circle(screen, self.color, iPaikka, self.koko, 0)
        pygame.draw.circle(screen, Pun, iPaikka, int(self.koko/2), 0)

    def liiku(self, vxvy):
        (x, y) = self.paikka
        (vx, vy) = vxvy
        (x, y) = (x + vx*Dt, y + vy*Dt)
        # Lisäämällä tähän väliin muutaman if lauseen, voit korjata ohjelmaa
        #  niin, että Lätkä ei karkaa peli-ikkunan reunan ulkopuolelle
        # jos x on suurempi kuin niin Wx x.stä kannattaa vähentää Wx jne...
        self.paikka = (x, y)
        self.liikkuu = True

class Cl_Hyttynen:
    def __init__(self, x, y):
        self.paikka = (x, y)

        self.nopeus = (70.0, 50.0)  # kokeile tähän eri arvoja

        self.koko = 10
        self.color = (random.randint(0, 255),
                      random.randint(0, 255),
                      random.randint(0, 255))
        self.elossa = True

    def piirra(self):
        iPaikka = (int(self.paikka[0]), int(self.paikka[1]))
        iEllxy = (iPaikka[0], iPaikka[1], 4*self.koko, self.koko)
        if self.elossa:
            pygame.draw.circle(screen, self.color, iPaikka, self.koko, 0)
        else:
            pygame.draw.ellipse(screen, self.color, iEllxy, 0)

    def liiku(self):

        self.isku()  # Onko lätkä osunut?
        if self.elossa:
            self.nopeus = lenna(self.paikka, self.nopeus)

        else:
            self.nopeus = self.putoa()

# Siirrytään eteenpäin nopeus kertaa aikaväli
        self.paikka = dfdt(self.paikka, self.nopeus)

#   Jos Latka on lähellä tätä hyttystä sekä x- että y-suunnassa
#   lätkä osuu, hyttynen kuolee -- litistyy ja muuttuu punaiseksi.
    def isku(self):
        if (etaisyys(self.paikka, Latka.paikka) < Latka.koko and
            Latka.liikkuu):
            self.elossa = False
            self.color = Pun

    def putoa(self):
        vx = random.uniform(-10, 10)
        if self.paikka[1] < Wy-self.koko:
            vy = random.uniform(20, 70)
        else:
            vx = 0
            vy = 0
        return (vx, vy)

def lenna(paikka, nopeus):
    kiihtyvyys = (random.uniform(-MaxKiihtyvyys, MaxKiihtyvyys),
                  random.uniform(-MaxKiihtyvyys, MaxKiihtyvyys))
    nopeus = reunat(paikka, dfdt(nopeus, kiihtyvyys))
    return nopeus
# matka = nopeus*aika
def dfdt(xy, dxdy):
    (x, y) = xy
    (dx, dy) = dxdy
    return (x + dx*Dt, y + dy*Dt)

def reunat(xy, vxvy):
    (vx, vy) = vxvy
    (x, y) = xy
    # ei päästetä hyttystä reunojen ulkopuolelle
    if (x > Wx and vx > 0) or (x < 0 and vx < 0):
        vx = -vx
    elif (y > Wy and vy > 0) or (y < 0 and vy < 0):
        vy = -vy
    # pysäytetään hyttynen, mikäli se lentää tolkuttoman lujaa
    elif sqrt(vx**2 + vy**2) > Vmax:
        vx, vy = 0.0, 0.0
    return (vx, vy)

# kahden pisteen p0 ja p1 välinen etäisyys xy-tasossa
def etaisyys(p0, p1):
    (x0, y0) = p0
    (x1, y1) = p1
    return sqrt((x1-x0)**2 + (y1-y0)**2)
# def ei_hyttysia(parvi):
#   for a in parvi:
#       if a.elossa:
#           return False
#   return True

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

pygame.display.init()
# Selvitetään peli-ikkunan koko näytöllä pikseleinä. Pikseli on piste näytöllä.
D_Info = pygame.display.Info()
Wx = D_Info.current_w
Wy = int(0.9*D_Info.current_h)
Wxy = (Wx, Wy)  # peli-ikkunan korkeus ja leveys

screen = pygame.display.set_mode((Wx, Wy))
pygame.display.set_caption("Hyttysjahti")
pygame.init()
clock = pygame.time.Clock()
random.seed()

# Luodaan 'olio' tyyppiä Cl_latka
Latka = Cl_Latka(Wx-50, Wy-50, 15)

# Luodaan yksi hyttynen
hyttynen = Cl_Hyttynen(50, 50)

hyttyset = list(range(N_hyttyset))
for i in range(N_hyttyset):
    hyttyset[i] = Cl_Hyttynen(50, 50)

#
# Toistetaan allaolevaa while-silmukkaa, kunnes käyttäjä sulkee peli-ikkunan
# tai painaa ESC-iä
#
loppu = False
while not loppu:

    clock.tick(Nayttotaajuus)

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            loppu = True
        if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
            loppu = True

    screen.fill(Tausta)

    hyttynen.liiku()

    hyttynen.piirra()

#  Lasketaan hyttysille uudet paikat ja piiretään ne
    for i in range(N_hyttyset):
        hyttyset[i].liiku()
        hyttyset[i].piirra()

    Latka.liikkuu = False
#   Tarkistetaan, onko joku tai jotkin lätkää ohjaavista näppäimistä
# painettuna. Liikutetaan lätkää vastaavasti.
    pressed = pygame.key.get_pressed()
    if pressed[pygame.K_UP]:
        Latka.liiku((0.0, -VLatka))  # ylös
    if pressed[pygame.K_DOWN]:
        Latka.liiku((0.0, VLatka))  # alas
    if pressed[pygame.K_z]:
        Latka.liiku((-VLatka, 0.0))  # vasemmalle
    if pressed[pygame.K_x]:
        Latka.liiku((VLatka, 0.0))  # oikealle
    # Piirretään lätkä uudelle paikalleen
    Latka.piirra()

    # näytetään kaikki yllä 'piirretty' näytöllä
    pygame.display.flip()
    #
    # while silmukka päättyy tähän
#
# Ohjelman loppu
pygame.quit()


Selityksiä ylläolevaan

import pygame
import random
from math import sqrt


Nayttotaajuus = 24  #  Voit kokeilla eri näyttötaajuuksia

 #  Peliin liittyviä muuttujia.
VLatka = 200.0  #  Lätkän nopeus

Vmax = 250.0
MaxKiihtyvyys = 400.0

N_hyttyset = 100

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


. . .
 #  Luodaan 'olio' tyyppiä Cl_latka
Latka = Cl_Latka(Wx-50, Wy-50, 15)


 #  Luodaan yksi hyttynen
hyttynen = Cl_Hyttynen(50, 50)

Luodaan parvi hyttysiä. Hyttysparvi esitetään listana:
hyttyset = [hyttynen0, hyttynen1, hyttynen2, ...]
hyttyset[0] = hyttynen0
hyttyset[1] = hyttynen1

Esitämme siis hyttysparven listana.

../xml/Programming/PythonOpetus/spyder_figs/lista_esim.png

Voit tutkia listoja iconsole komentotulkissa. Lista on luettelo hakasulkeiden sisällä. Jos parvi on lista niin parvi[0] viittaa listan ensimmäiseen alkioon. (Joissain ohjelmointikielissä indeksointi alkaa nollasta, toisissa yhdestä. )

hyttyset = list(range(N_hyttyset))
for i in range(N_hyttyset):
    hyttyset[i] = Cl_Hyttynen(50, 50)


 # 
 #  Toistetaan allaolevaa while-silmukkaa, kunnes käyttäjä sulkee peli-ikkunan
 #  tai painaa ESC-iä
 # 
loppu = False
while not loppu:


    hyttynen.liiku()

    hyttynen.piirra()

 #   Lasketaan hyttysille uudet paikat ja piiretään ne
    for i in range(N_hyttyset):
        hyttyset[i].liiku()
        hyttyset[i].piirra()


    #  näytetään kaikki yllä 'piirretty' näytöllä
    pygame.display.flip()
    # 
    #  while silmukka päättyy tähän
 # 
 #  Ohjelman loppu
pygame.quit()


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




(Sun Mar 11 09:58:51 2018 )

morssari.py

Tässä pelissä tehtävänä on osua vanhan ajan mörssärillä esteen takana olevaan maaliin.

— Mörssäriä suunnataan ylös ja alas nuolinäppäimillä.

— Numeronäppäimistön + ja - -merkkeillä säädetään ajopanoksen kokoa

— Numeronäppäimistön näppäimillä 1, 2, ja 3 voit valita tykinkuulan tiheyden.

— z- ja x- näppäimillä voit siirtää mörssäriä.

Mörssäri on ladattu, kun sen piippu muuttuu punaiseksi. Se laukaistaan painamalla välilyöntiä.

F1-näppaimen vaikutuksen voit selvittää itse.

Esittelyvideo pelistä.



()

v0: Ohjelman runko + mörssäri + "vuori"

# -*- coding: utf-8 -*-
import pygame
import random
from math import pi, cos, sin, sqrt
Musta = (0, 0, 0)  # Mustassa ei ole mitään valoa
Sin = (0, 0, 255)  # vain sinistä
Pun = (255, 0, 0)
Vihr = (0, 255, 0)
Kelt = (255, 255, 0)
Valk = (255, 255, 255)  # valkoisessa on kaikenväristä valoa
Tausta = (160, 200, 255)
MaastoVari = (95, 95, 20)

# Peli-ikkuna päivitetään Nayttotaajuus kertaa sekunnissa
Nayttotaajuus = 48

# Tällä tykin säätö saadaan jouhevaksi kaikilla näyttötaajuuksilla
saatoaskel = 48.0/Nayttotaajuus

# Nopeutus = 1.0 ->  oikea "filmin" nopeus
Nopeutus = 5

g_maa = 9.81  # putoamiskiihtyvyys
RKuula = 0.1  # kuulan säde metreinä

# Lasketaan, mihin tietty pelimaailman piste kuuluu peli-ikkunassa
def xy_ruudulla(xmaasto, ymaasto):
    xpikseli = int(xmaasto*scale)
    ypikseli = int(Wy-ymaasto*scale)
    return (xpikseli, ypikseli)

def xy_skaalaus(w, h):
    xpikseli = int(w*scale)
    ypikseli = int(h*scale)
    return (xpikseli, ypikseli)

def r_xy(kulma, r):
    return (r*cos(kulma), r*sin(kulma))

def kuulanMassa(tiheys):
    VKuula = 4/3*pi*RKuula**3
    return tiheys*VKuula

def kiihtyvyys0():
    ax = 0.0
    ay = -g_maa
    return (ax, ay)

# Vuori ja "tuuliviiri"
class Cl_vuori:
    def __init__(self):
        self.x1 = random.uniform(0.3*Wxmaasto, 0.6*Wxmaasto)
        self.leveys = 0.1*Wxmaasto
        self.h = random.uniform(0.0, 0.6*Wymaasto)
    def piirra(self):
        # Vuori
        (a, b) = xy_ruudulla(self.x1, self.h)
        (w, h) = xy_skaalaus(self.leveys, self.h)
        pygame.draw.rect(screen, MaastoVari, (a, b, w, h), 0)

# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
# Morssari
# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
class Cl_morssari:
    def __init__(self):
        self.x = 0.05*Wxmaasto
        self.y = 0.01*Wymaasto
        (w, h) = xy_skaalaus(0.05*Wxmaasto, 0.03*Wymaasto)
        self.w = w
        self.h = h
        self.ruuti = 1.0
        self.KuulanTiheys = 5000.0
        self.kuulanVari = Sin
        self.latausaika = Nayttotaajuus
        self.kulma = pi/7.0  # piipun asento
        self.p1x = 0.0  # piipun pään x-koordinaatti
        self.p1y = 0.0  # piipun pään y-koordinaatti
        self.vaunu = pygame.Rect(0, 0, w, h)

    def piirra(self):
        (x, y) = xy_ruudulla(self.x, self.y)
        # piirretään mörssärin piippu
        (sx, sy) = r_xy(self.kulma, 200.0)
        self.p1x = self.x + sx
        self.p1y = self.y + sy
        if self.latausaika > 0:
            self.latausaika = self.latausaika - 1
            vari = Musta
        else:
            vari = Pun
        pygame.draw.line(screen, vari, (x, y),
                         xy_ruudulla(self.p1x, self.p1y), 10)
        # Ajopanosten ruutimäärän osoitin
        pygame.draw.line(screen, Sin, (10, Wy),
                         (10, Wy-int(self.ruuti/12.0*Wy)), 8)
        # vaunu
        self.vaunu.center = (x-self.w/4, y)
        pygame.draw.ellipse(screen, Musta, self.vaunu, 0)

    # Vaunun liikuttelua z ja x näppäimillä.
    # Vaunulla ei pääse vuoren läpi ;-)
    def liiku_x(self, dx):
        self.x = self.x + dx
        if self.x > vuori.x1:
            self.x = 0.0

# num+ ja num- näppäimillä kutsutaan tätä metodia
    def add_ruuti(self, m):
        self.ruuti = min(11.0, max(1.0, self.ruuti + 0.5*m))

# num-1, num-2 ja num-3 näppäimillä valitaan kuulan tyyppi
    def valitseKuula(self, tyyppi):
        if tyyppi ==  'puu':
            self.KuulanTiheys = 1000.0
            self.kuulanVari = Kelt
        elif tyyppi ==  'rauta':
            self.KuulanTiheys = 5000.0
            self.kuulanVari = Sin
        elif tyyppi ==  'lyijy':
            self.KuulanTiheys = 12000.0
            self.kuulanVari = Musta

# Käännetään piippua nuolinäppäimillä
    def asento(self, dkulma):
        self.kulma = self.kulma + dkulma

# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
# Pääohjelma alkaa tästä
# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
print("alku")
pygame.init()

pygame.display.init()
# Selvitetään peli-ikkunan koko näytöllä pikseleinä
D_Info = pygame.display.Info()
Wx = D_Info.current_w
Wy = int(0.9*D_Info.current_h)
# pelimaailman leveys "metreinä"
Wxmaasto = 5000.0
scale = Wx/Wxmaasto
# Korkeus pelimaailmassa
Wymaasto = Wy/scale
screen = pygame.display.set_mode((Wx, Wy))
pygame.display.set_caption("Morssari")
clock = pygame.time.Clock()
random.seed()
screen.fill(Tausta)
# Luodaan mörssäri ja vuori
vuori = Cl_vuori()
morssari = Cl_morssari()
kuulat = []

loppu = False
MuistaVanhat = False
while not loppu:
    clock.tick(Nayttotaajuus)
    if not MuistaVanhat:
        screen.fill(Tausta)
    for event in pygame.event.get():
        # Lopetetaan peli sulkemalla ikkuna tai painamalla ESC
        if event.type ==  pygame.QUIT:
            loppu = True
        if event.type ==  pygame.KEYDOWN:
            if event.key ==  pygame.K_ESCAPE:
                loppu = True
            # Pyyhitäänkö peli-ikkuna puhtaaksi joka kierroksella vai ei
            if event.key ==  pygame.K_F1:
                MuistaVanhat = not MuistaVanhat
            # säädellään ajopanoksen ruutimäärää
            if event.key ==  pygame.K_KP_PLUS:
                morssari.add_ruuti(1)
            if event.key ==  pygame.K_KP_MINUS:
                morssari.add_ruuti(-1)
            # valitaan kuulan tyyppi
            if event.key ==  pygame.K_KP1:
                morssari.valitseKuula('puu')
            if event.key ==  pygame.K_KP2:
                morssari.valitseKuula('rauta')
            if event.key ==  pygame.K_KP3:
                morssari.valitseKuula('lyijy')
    # Suunnataan ja siirrellään mörssäriä
    pressed = pygame.key.get_pressed()
    if pressed[pygame.K_UP]:
        morssari.asento(0.005*saatoaskel)
    if pressed[pygame.K_DOWN]:
        morssari.asento(-0.005*saatoaskel)
    if pressed[pygame.K_x]:
        morssari.liiku_x(saatoaskel)
    if pressed[pygame.K_z]:
        morssari.liiku_x(-saatoaskel)

    # Piirretään kaikki oliot peli-ikkunaan ja lasketaan kuulille ja
    # sirpaleille uudet paikat peli-ikkunassa
    morssari.piirra()
    vuori.piirra()

    pygame.display.flip()
    # while silmukka päättyy tähän
pygame.quit()


Selityksiä ylläolevaan

Jotkin Pythonin funktiot — kuten pygamen piirtofuktiot — vaativat argumenteikseen kokonaislukuja, koska kokonaisluvut ja reaaliluvut tallennetaan tietokoneen muistiin eri tavalla. Reaaliluvusta saa kokonaisluvun int-funtiolla seuraavasti: Komento x = 4/3 antaa x:lle arvon 1.33333... ja i = int(x) tai komento i = int(4/3) antaa i:lle arvon 1

 #  Peli-ikkuna päivitetään Nayttotaajuus kertaa sekunnissa
Nayttotaajuus = 48


 #  Tällä tykin säätö saadaan jouhevaksi kaikilla näyttötaajuuksilla
saatoaskel = 48.0/Nayttotaajuus


 #  Nopeutus = 1.0 ->  oikea "filmin" nopeus
Nopeutus = 5

Parametrilla nopeutus kuula saadaan lentämään todellista nopeutta nopeammin, mikäli peli alkaa tuntua liian hitaalta.

g_maa = 9.81  #  putoamiskiihtyvyys
RKuula = 0.1  #  kuulan säde metreinä




 #  Lasketaan, mihin tietty pelimaailman piste kuuluu peli-ikkunassa
def xy_ruudulla(xmaasto, ymaasto):
    xpikseli = int(xmaasto*scale)
    ypikseli = int(Wy-ymaasto*scale)
    return (xpikseli, ypikseli)


pygame-peli-ikkunassa origo on vasemmassa yläkulmassa ja y kasvaa alaspäin. Ylläoleva funktio skaalaa maaston peli-ikkunaan ja 'kääntää' koordinaatiston niin, että pelimaailmassa origo on vasemmassa alakulmassa ja y kasvaa ylöspäin siirryttäessä.

Jos p_maasto = (x_maasto, y_maasto) on piste pelimaailman maastossa niin p_ikkuna = xy_ruudulla(p_maasto) on sitä vastaava piste peli-ikkunassa.

Voit kokeilla funkiota kirjoittamalla konsolille esimerkiksi xy_ruudulla(0,0)

Sen miettiminen, mitä tuo tarkoittaa ja miten muunnos lasketaan, on hyvä geometrian tehtävä.

funktio int(z) muuttaa z:n kokonaisluvuksi, koska pygamen piirto-funktioille 'kelpaavat' vain kokonaislukuargumentit.

def xy_skaalaus(w, h):
    xpikseli = int(w*scale)
    ypikseli = int(h*scale)
    return (xpikseli, ypikseli)



def r_xy(kulma, r):
    return (r*cos(kulma), r*sin(kulma))

Funktio r_xy(kulma, r) laskee pisteen x- ja y- koordinaatit, kun tiedetään, missä suunnassa (kulma) ja kuinka kaukana origosta (etäisyys = r) piste on. Kuvan piirtäminen auttanee ymmärtämään.

def kuulanMassa(tiheys):
    VKuula = 4/3*pi*RKuula**3
    return tiheys*VKuula


Hauskan pelin olisi voinut toteuttaa miettimättä kuulan mittoja ja massaa, mutta varaudumme siihen, että voimme kokeilla pelissä kuulia, joilla on eri tiheys ja siksi eri massa. Sama ajopanoksen ruutimäärä antaa (tässä pelissä) raskaammalle kuulalle pienemmän lähtönopeuden, mutta ilmanvastus kuluttaa raskaamman kuulan liike-energiaa suhteessa vähemmän, joten raskas kuula lentää pidemälle. Tämän tarkka ymmärtäminen vaatii perehtymistä fysiikkaan.

def kiihtyvyys0():
    ax = 0.0
    ay = -g_maa
    return (ax, ay)


Kiihtyvyys = kuulaan vaikuttavien voimien aiheuttama muutos kuulan nopeudelle. Otamme aluksi huomioon vain maan vetovoiman aiheuttaman putoamiskiihtyvyyden, mutta lisäämme myöhemmin ilmanvastuksen vaikutuksen.

 #  Vuori ja "tuuliviiri"
class Cl_vuori:
    def __init__(self):
        self.x1 = random.uniform(0.3*Wxmaasto, 0.6*Wxmaasto)
        self.leveys = 0.1*Wxmaasto
        self.h = random.uniform(0.0, 0.6*Wymaasto)

    def piirra(self):
        #  Vuori
        (a, b) = xy_ruudulla(self.x1, self.h)
        (w, h) = xy_skaalaus(self.leveys, self.h)
        pygame.draw.rect(screen, MaastoVari, (a, b, w, h), 0)

Piirretään laatikko kuvaamaan vuorta tms. estettä. Piirretään vuoren huipulle tuuliviiri.

Peli-ohjelmassa ei riitä saada piirrettyä jotain, vaan kappaleita pitää voida siirrellä ja pitää voida laskea, törmäävätkö ne toisiinsa. Mitä monimutkaisempia muodot ovat, sitä hankalampaa laskeminen on. Siksi vuori on pelkkä laatikko tässä pelissä



 #  = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
 #  Morssari
 #  = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
class Cl_morssari:
    def __init__(self):
        self.x = 0.05*Wxmaasto
        self.y = 0.01*Wymaasto
        (w, h) = xy_skaalaus(0.05*Wxmaasto, 0.03*Wymaasto)
        self.w = w
        self.h = h
        self.ruuti = 1.0
        self.KuulanTiheys = 5000.0
        self.kuulanVari = Sin
        self.latausaika = Nayttotaajuus
        self.kulma = pi/7.0  #  piipun asento
        self.p1x = 0.0  #  piipun pään x-koordinaatti
        self.p1y = 0.0  #  piipun pään y-koordinaatti
        self.vaunu = pygame.Rect(0, 0, w, h)

Osan yllämääritellyistä mörssärin ominaisuuksista otamme käyttöön vasta myöhemmissä vaiheissa

    def piirra(self):
        (x, y) = xy_ruudulla(self.x, self.y)
        #  piirretään mörssärin piippu
        (sx, sy) = r_xy(self.kulma, 200.0)
        self.p1x = self.x + sx
        self.p1y = self.y + sy
        if self.latausaika > 0:
            self.latausaika = self.latausaika - 1
            vari = Musta
        else:
            vari = Pun
        pygame.draw.line(screen, vari, (x, y),
                         xy_ruudulla(self.p1x, self.p1y), 10)
        #  Ajopanosten ruutimäärän osoitin
        pygame.draw.line(screen, Sin, (10, Wy),
                         (10, Wy-int(self.ruuti/12.0*Wy)), 8)
        #  vaunu
        self.vaunu.center = (x-self.w/4, y)
        pygame.draw.ellipse(screen, Musta, self.vaunu, 0)


muuttujien x,y, w ja h arvot on määritetty kokeilemalla niin, että mörssäri näyttää sopivan kokoiselta maisemassa


    #  Vaunun liikuttelua z ja x näppäimillä.
    #  Vaunulla ei pääse vuoren läpi ;-)
    def liiku_x(self, dx):
        self.x = self.x + dx
        if self.x > vuori.x1:
            self.x = 0.0



 #  num+ ja num- näppäimillä kutsutaan tätä metodia
    def add_ruuti(self, m):
        self.ruuti = min(11.0, max(1.0, self.ruuti + 0.5*m))

funktioiden min ja max avulla rajataan "ruutimäärä" välille 1..10. Kyseessä ei ole mikään oikea ruutimäärä. Ruutipussien lukumäärä voisi olla parempi nimi.

 #  num-1, num-2 ja num-3 näppäimillä valitaan kuulan tyyppi
    def valitseKuula(self, tyyppi):
        if tyyppi ==  'puu':
            self.KuulanTiheys = 1000.0
            self.kuulanVari = Kelt
        elif tyyppi ==  'rauta':
            self.KuulanTiheys = 5000.0
            self.kuulanVari = Sin
        elif tyyppi ==  'lyijy':
            self.KuulanTiheys = 12000.0
            self.kuulanVari = Musta

Fysiikasta kiinnostuneille:

Kuulan paino vaikuttaa siihen, kuinka suuren lähtönopeuden tietty ruutimäärä antaa kuulalle. Kuulan tiheys vaikuttaa siihen, kuinka suuren osuuden ilmanvastus syö liike-energiasta. (Ilmanvastuksen vaikutus lisätään myöhemmin.)

Tiheydet eivät tarkkaan ottaen ole puun, raudan eikä lyijyn tiheyksiä,mutta näillä arvoilla tuli sopivasti tiheyden vaihtelua.

 #  Käännetään piippua nuolinäppäimillä
    def asento(self, dkulma):
        self.kulma = self.kulma + dkulma



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

print("alku")
pygame.init()


    #  Piirretään kaikki oliot peli-ikkunaan ja lasketaan kuulille ja
    #  sirpaleille uudet paikat peli-ikkunassa
    morssari.piirra()
    vuori.piirra()


    pygame.display.flip()
    #  while silmukka päättyy tähän

pygame.quit()


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



()

v1: Kuula, mörssärin laukaiseminen ja kuulan lento

# -*- coding: utf-8 -*-
import pygame
import random
from math import pi, cos, sin, sqrt
Musta = (0, 0, 0)  # Mustassa ei ole mitään valoa
Sin = (0, 0, 255)  # vain sinistä
Pun = (255, 0, 0)
Vihr = (0, 255, 0)
Kelt = (255, 255, 0)
Valk = (255, 255, 255)  # valkoisessa on kaikenväristä valoa
Tausta = (160, 200, 255)
MaastoVari = (95, 95, 20)

# Peli-ikkuna päivitetään Nayttotaajuus kertaa sekunnissa
Nayttotaajuus = 48

# Tällä tykin säätö saadaan jouhevaksi kaikilla näyttötaajuuksilla
saatoaskel = 48.0/Nayttotaajuus

# Nopeutus = 1.0 ->  oikea "filmin" nopeus
Nopeutus = 5

g_maa = 9.81  # putoamiskiihtyvyys
RKuula = 0.1  # kuulan säde metreinä

# Lasketaan, mihin tietty pelimaailman piste kuuluu peli-ikkunassa
def xy_ruudulla(xmaasto, ymaasto):
    xpikseli = int(xmaasto*scale)
    ypikseli = int(Wy-ymaasto*scale)
    return (xpikseli, ypikseli)

def xy_skaalaus(w, h):
    xpikseli = int(w*scale)
    ypikseli = int(h*scale)
    return (xpikseli, ypikseli)

def r_xy(kulma, r):
    return (r*cos(kulma), r*sin(kulma))

def kuulanMassa(tiheys):
    VKuula = 4/3*pi*RKuula**3
    return tiheys*VKuula

def kiihtyvyys0():
    ax = 0.0
    ay = -g_maa
    return (ax, ay)

# Siirtymä ja nopeuden muutos ajassa dt
def integ_euler(x, y, vx, vy, A, M):
    dt = 1.0/Nayttotaajuus
    for i in range(Nopeutus):

        (ax, ay) = kiihtyvyys0()

        (x, y, vx, vy) = (x + vx*dt, y + vy*dt, vx + ax*dt, vy + ay*dt)
    return (x, y, vx, vy)

# Vuori ja "tuuliviiri"
class Cl_vuori:
    def __init__(self):
        self.x1 = random.uniform(0.3*Wxmaasto, 0.6*Wxmaasto)
        self.leveys = 0.1*Wxmaasto
        self.h = random.uniform(0.0, 0.6*Wymaasto)
    def piirra(self):
        # Vuori
        (a, b) = xy_ruudulla(self.x1, self.h)
        (w, h) = xy_skaalaus(self.leveys, self.h)
        pygame.draw.rect(screen, MaastoVari, (a, b, w, h), 0)

# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
# Morssari
# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
class Cl_morssari:
    def __init__(self):
        self.x = 0.05*Wxmaasto
        self.y = 0.01*Wymaasto
        (w, h) = xy_skaalaus(0.05*Wxmaasto, 0.03*Wymaasto)
        self.w = w
        self.h = h
        self.ruuti = 1.0
        self.KuulanTiheys = 5000.0
        self.kuulanVari = Sin
        self.latausaika = Nayttotaajuus
        self.kulma = pi/7.0  # piipun asento
        self.p1x = 0.0  # piipun pään x-koordinaatti
        self.p1y = 0.0  # piipun pään y-koordinaatti
        self.vaunu = pygame.Rect(0, 0, w, h)

    def piirra(self):
        (x, y) = xy_ruudulla(self.x, self.y)
        # piirretään mörssärin piippu
        (sx, sy) = r_xy(self.kulma, 200.0)
        self.p1x = self.x + sx
        self.p1y = self.y + sy
        if self.latausaika > 0:
            self.latausaika = self.latausaika - 1
            vari = Musta
        else:
            vari = Pun
        pygame.draw.line(screen, vari, (x, y),
                         xy_ruudulla(self.p1x, self.p1y), 10)
        # Ajopanosten ruutimäärän osoitin
        pygame.draw.line(screen, Sin, (10, Wy),
                         (10, Wy-int(self.ruuti/12.0*Wy)), 8)
        # vaunu
        self.vaunu.center = (x-self.w/4, y)
        pygame.draw.ellipse(screen, Musta, self.vaunu, 0)

    # Vaunun liikuttelua z ja x näppäimillä.
    # Vaunulla ei pääse vuoren läpi ;-)
    def liiku_x(self, dx):
        self.x = self.x + dx
        if self.x > vuori.x1:
            self.x = 0.0

# num+ ja num- näppäimillä kutsutaan tätä metodia
    def add_ruuti(self, m):
        self.ruuti = min(11.0, max(1.0, self.ruuti + 0.5*m))

# num-1, num-2 ja num-3 näppäimillä valitaan kuulan tyyppi
    def valitseKuula(self, tyyppi):
        if tyyppi ==  'puu':
            self.KuulanTiheys = 1000.0
            self.kuulanVari = Kelt
        elif tyyppi ==  'rauta':
            self.KuulanTiheys = 5000.0
            self.kuulanVari = Sin
        elif tyyppi ==  'lyijy':
            self.KuulanTiheys = 12000.0
            self.kuulanVari = Musta

# Käännetään piippua nuolinäppäimillä
    def asento(self, dkulma):
        self.kulma = self.kulma + dkulma

# laukaistaan mörssäri välilyöntinäppäimellä
    def ammu(self):

        self.latausaika = random.randint(Nayttotaajuus/2, 2*Nayttotaajuus)
        M = kuulanMassa(self.KuulanTiheys)
        (vx, vy) = self.kuulan_alkunopeus(M)
        return Cl_ammus(self.p1x, self.p1y, vx, vy, M, self.kuulanVari)

    def kuulan_alkunopeus(self, M):
        Cv0 = 600.0
        v0 = Cv0*sqrt(self.ruuti/M)
        print("ruuti: {:2.1f}".format(self.ruuti),
              "kulma: {:2.1f}".format(self.kulma*180.0/pi),
              "massa: {:4.1f}".format(M),
              "nopeus: {:4.0f}".format(v0))
        return (v0*cos(self.kulma),
                v0*sin(self.kulma))

# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
# Ammus
# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
class Cl_ammus:
    def __init__(self, x, y, vx, vy, M, vari):
        self.x = x
        self.y = y
        self.vx = vx
        self.vy = vy
        self.M = M  # kg/m**3
        self.A = pi*RKuula**2
        self.vari = vari

    def piirra0(self):
        pygame.draw.circle(screen, self.vari,
                           xy_ruudulla(self.x, self.y), 5, 0)

    def liiku0(self):
        (self.x, self.y, self.vx, self.vy) = integ_euler(
            self.x, self.y, self.vx, self.vy, self.A, self.M)

# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
# Pääohjelma alkaa tästä
# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
print("alku")
pygame.init()

pygame.display.init()
# Selvitetään peli-ikkunan koko näytöllä pikseleinä
D_Info = pygame.display.Info()
Wx = D_Info.current_w
Wy = int(0.9*D_Info.current_h)
# pelimaailman leveys "metreinä"
Wxmaasto = 5000.0
scale = Wx/Wxmaasto
# Korkeus pelimaailmassa
Wymaasto = Wy/scale
screen = pygame.display.set_mode((Wx, Wy))
pygame.display.set_caption("Morssari")
clock = pygame.time.Clock()
random.seed()
screen.fill(Tausta)
# Luodaan mörssäri ja vuori
vuori = Cl_vuori()
morssari = Cl_morssari()
kuulat = []

loppu = False
MuistaVanhat = False
while not loppu:
    clock.tick(Nayttotaajuus)
    if not MuistaVanhat:
        screen.fill(Tausta)
    for event in pygame.event.get():
        # Lopetetaan peli sulkemalla ikkuna tai painamalla ESC
        if event.type ==  pygame.QUIT:
            loppu = True
        if event.type ==  pygame.KEYDOWN:
            if event.key ==  pygame.K_ESCAPE:
                loppu = True
            # Pyyhitäänkö peli-ikkuna puhtaaksi joka kierroksella vai ei
            if event.key ==  pygame.K_F1:
                MuistaVanhat = not MuistaVanhat
            # säädellään ajopanoksen ruutimäärää
            if event.key ==  pygame.K_KP_PLUS:
                morssari.add_ruuti(1)
            if event.key ==  pygame.K_KP_MINUS:
                morssari.add_ruuti(-1)
            # valitaan kuulan tyyppi
            if event.key ==  pygame.K_KP1:
                morssari.valitseKuula('puu')
            if event.key ==  pygame.K_KP2:
                morssari.valitseKuula('rauta')
            if event.key ==  pygame.K_KP3:
                morssari.valitseKuula('lyijy')
    # Suunnataan ja siirrellään mörssäriä
    pressed = pygame.key.get_pressed()
    if pressed[pygame.K_UP]:
        morssari.asento(0.005*saatoaskel)
    if pressed[pygame.K_DOWN]:
        morssari.asento(-0.005*saatoaskel)
    if pressed[pygame.K_x]:
        morssari.liiku_x(saatoaskel)
    if pressed[pygame.K_z]:
        morssari.liiku_x(-saatoaskel)

    # Laukaistaan mörssäri
    if pressed[pygame.K_SPACE] and morssari.latausaika ==  0:
        kuulat.append(morssari.ammu())

    # Piirretään kaikki oliot peli-ikkunaan ja lasketaan kuulille ja
    # sirpaleille uudet paikat peli-ikkunassa
    morssari.piirra()
    vuori.piirra()

#    if not kuulat =  []:
#        for kuula in kuulat:

#            kuula.liiku0()
#            kuula.piirra0()

    if not kuulat ==  []:
        for k, kuula in enumerate(kuulat):

            kuula.liiku0()
            kuula.piirra0()

    pygame.display.flip()
    # while silmukka päättyy tähän
pygame.quit()


Selityksiä ylläolevaan

Jotkin Pythonin funktiot — kuten pygamen piirtofuktiot — vaativat argumenteikseen kokonaislukuja, koska kokonaisluvut ja reaaliluvut tallennetaan tietokoneen muistiin eri tavalla. Reaaliluvusta saa kokonaisluvun int-funtiolla seuraavasti: Komento x = 4/3 antaa x:lle arvon 1.33333... ja i = int(x) tai komento i = int(4/3) antaa i:lle arvon 1

 #  Siirtymä ja nopeuden muutos ajassa dt
def integ_euler(x, y, vx, vy, A, M):
    dt = 1.0/Nayttotaajuus
    for i in range(Nopeutus):

        (ax, ay) = kiihtyvyys0()

        (x, y, vx, vy) = (x + vx*dt, y + vy*dt, vx + ax*dt, vy + ay*dt)
    return (x, y, vx, vy)


../xml/Programming/PythonOpetus/integrointi.png

Kuvan uudelleen piirtämisten väli on dt = 1.0/Nayttotaajuus sekuntia. Pelin olioille pitää siis laskea uusi paikka ajan dt välein, eli pitää laskea, paljonko pelin oliot liikkuvat ajassa dt. (Kuvassa dt on asetettu keinotekoisesti hyvin pitkäksi.) Koska matka = nopeus*aika, uusi paikka voidaan laskea seuraavasti:

x(t+dt) =x(t)+ vxdt y(t+dt) =y(t)+ vydt

Huomaa, että python-ohjelmassa x = x + dx ei ole yhtälö vaan tarkoittaa, että x:lle annetaan uusi arvo joka on x:n vanha arvo lisättynä dx:llä.

Yllä siirtymä lasketaan Nopeutus kertaa, minkä takia kaikki liikkuu Nopeutus kertaa oikeaa nopeutta nopeammin. Nopeutus on tarpeen, koska pelimme kuula noudattaa fysiikan lakeja ja niiden mukaan mörssärin kuula lentää kohteeseensa niin hitaasti, että hätäisempi pelaaja kyllästyy.

Laitoin funktion nimeksi integ_euler, koska oikeastaan kyseessä on differentiaaliyhtälön ratkaisemisesta Eulerin menetelmällä.

 #  laukaistaan mörssäri välilyöntinäppäimellä
    def ammu(self):

        self.latausaika = random.randint(Nayttotaajuus/2, 2*Nayttotaajuus)
        M = kuulanMassa(self.KuulanTiheys)
        (vx, vy) = self.kuulan_alkunopeus(M)
        return Cl_ammus(self.p1x, self.p1y, vx, vy, M, self.kuulanVari)


Asetetaan latausajaksi näyttötaajuudesta riippuva satunnaisluku. Latausaikaa vähennetään yhdellä piirrä- methodissa jokaisella näyttöruuden päivityksellä, joten latausajan laskuri nollaantuu ajassa Nayttotaajuus/2 — 2*Nayttotaajuus.

Latausaika ja sen satunnaisuus tekee mielestäni ampumisesta vähän haastavampaa.


    def kuulan_alkunopeus(self, M):
        Cv0 = 600.0
        v0 = Cv0*sqrt(self.ruuti/M)
        print("ruuti: {:2.1f}".format(self.ruuti),
              "kulma: {:2.1f}".format(self.kulma*180.0/pi),
              "massa: {:4.1f}".format(M),
              "nopeus: {:4.0f}".format(v0))
        return (v0*cos(self.kulma),
                v0*sin(self.kulma))


print-komento tulostaa konsolille tietoja laukauksesta. print(self.ruuti, self.kulma*180.0/pi, M, v0) toimisi ihan hyvin. (voit kokeilla) Monimutkaisella format-komennolla voidaan määrätty, monenko desimaalin tarkuudella luvut tulostetaan niin, että niitä on helpompi lukea.

Tämä selitys kuulan alkunopeuden laskemisesta ei ehkä kerro hirveän tarkasti todellisuudesta eikä kiinnostane kuin tekniikasta tosissaan kiinnostuneita.

Nopeus, millä kuula lähtee putkesta, riippuu ruutimäärästä. Nopeuden x- ja y- komponentit riippuvat putken asennosta

Oletin, että ajopanoksen palaminen aiheuttaa piipussa ruudin määrään verrannollisen keskimääräisen paineen ja siis keskimääräisen voiman mörssärin s metriä pitkässä piipussa etenevään kuulaan. Todellisuudessa paine yleensä nousee nollasta huippuarvoonsa ennen kuin kuula ehtii piipun puoliväliin ja alkaa sitten laskea.

Joka tapauksessa ruudin tekemä työ on yhtä kuin kuulan saama liike-energia:

Fruuti s = 1/2 M v2


 #  = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
 #  Ammus
 #  = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
class Cl_ammus:
    def __init__(self, x, y, vx, vy, M, vari):
        self.x = x
        self.y = y
        self.vx = vx
        self.vy = vy
        self.M = M  #  kg/m**3
        self.A = pi*RKuula**2
        self.vari = vari

    def piirra0(self):
        pygame.draw.circle(screen, self.vari,
                           xy_ruudulla(self.x, self.y), 5, 0)


    def liiku0(self):
        (self.x, self.y, self.vx, self.vy) = integ_euler(
            self.x, self.y, self.vx, self.vy, self.A, self.M)


    #  Laukaistaan mörssäri
    if pressed[pygame.K_SPACE] and morssari.latausaika ==  0:
        kuulat.append(morssari.ammu())


 #     if not kuulat =  []:
 #         for kuula in kuulat:

 #             kuula.liiku0()
 #             kuula.piirra0()

    if not kuulat ==  []:
        for k, kuula in enumerate(kuulat):

            kuula.liiku0()
            kuula.piirra0()

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



()

v2: Lisätään maali, tuuli ja tuuliviiri.

# -*- coding: utf-8 -*-
import pygame
import random
from math import pi, cos, sin, sqrt
Musta = (0, 0, 0)  # Mustassa ei ole mitään valoa
Sin = (0, 0, 255)  # vain sinistä
Pun = (255, 0, 0)
Vihr = (0, 255, 0)
Kelt = (255, 255, 0)
Valk = (255, 255, 255)  # valkoisessa on kaikenväristä valoa
Tausta = (160, 200, 255)
MaastoVari = (95, 95, 20)

# Peli-ikkuna päivitetään Nayttotaajuus kertaa sekunnissa
Nayttotaajuus = 48

# Tällä tykin säätö saadaan jouhevaksi kaikilla näyttötaajuuksilla
saatoaskel = 48.0/Nayttotaajuus

# Nopeutus = 1.0 ->  oikea "filmin" nopeus
Nopeutus = 5

Tuuli = random.uniform(-50.0, 50.0)
# Tuuli = -50.0

g_maa = 9.81  # putoamiskiihtyvyys
RKuula = 0.1  # kuulan säde metreinä

# Lasketaan, mihin tietty pelimaailman piste kuuluu peli-ikkunassa
def xy_ruudulla(xmaasto, ymaasto):
    xpikseli = int(xmaasto*scale)
    ypikseli = int(Wy-ymaasto*scale)
    return (xpikseli, ypikseli)

def xy_skaalaus(w, h):
    xpikseli = int(w*scale)
    ypikseli = int(h*scale)
    return (xpikseli, ypikseli)

def r_xy(kulma, r):
    return (r*cos(kulma), r*sin(kulma))

def kuulanMassa(tiheys):
    VKuula = 4/3*pi*RKuula**3
    return tiheys*VKuula

def kiihtyvyys0():
    ax = 0.0
    ay = -g_maa
    return (ax, ay)

# Siirtymä ja nopeuden muutos ajassa dt
def integ_euler(x, y, vx, vy, A, M):
    dt = 1.0/Nayttotaajuus
    for i in range(Nopeutus):

        (ax, ay) = kiihtyvyys0()

        (x, y, vx, vy) = (x + vx*dt, y + vy*dt, vx + ax*dt, vy + ay*dt)
    return (x, y, vx, vy)

# Vuori ja "tuuliviiri"
class Cl_vuori:
    def __init__(self):
        self.x1 = random.uniform(0.3*Wxmaasto, 0.6*Wxmaasto)
        self.leveys = 0.1*Wxmaasto
        self.h = random.uniform(0.0, 0.6*Wymaasto)
    def piirra(self):
        # Vuori
        (a, b) = xy_ruudulla(self.x1, self.h)
        (w, h) = xy_skaalaus(self.leveys, self.h)
        pygame.draw.rect(screen, MaastoVari, (a, b, w, h), 0)

        # Tuuliviirin masto
        tuulix = Tuuli/50.0*0.05*Wx
        mastonKorkeus = 0.05*Wy
        pygame.draw.line(screen, Musta, (a+w/2, b),
                         (a+w/2, b-mastonKorkeus), 4)
        # Tuuliviiri
        pygame.draw.line(screen, Sin, (a+w/2, b-mastonKorkeus),
                         (a+w/2+tuulix, b-mastonKorkeus), 12)

    # Tarkistetaan, onko kuula, jonka koordinaatit ovat x ja y osunut vuoreen
    def osuma(self, x, y):
        return y < self.h and self.x1 < x and x < self.x1 + self.leveys

# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
# Maali
# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
class Cl_maali:
    def __init__(self, xmin):
        self.x = random.uniform(xmin, 0.95*Wxmaasto)
        self.y = 0.0
        self.leveys = 0.05*Wxmaasto
        self.korkeus = 0.03*Wymaasto
        (x, y) = xy_ruudulla(self.x, self.korkeus)
        (w, h) = xy_skaalaus(self.leveys, self.korkeus)
        self.vaunu = pygame.Rect(x, y, w, h)
        self.vari = (225, 255, 0)
        self.ehja = True

    def piirra(self):
        if self.ehja:
            pygame.draw.ellipse(screen, self.vari, self.vaunu, 0)
    def osuma(self, x, y):
        if (self.ehja and y < self.korkeus and
          self.x < x < self.x + self.leveys):
            self.ehja = False

            return True
        else:
            return False

# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
# Morssari
# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
class Cl_morssari:
    def __init__(self):
        self.x = 0.05*Wxmaasto
        self.y = 0.01*Wymaasto
        (w, h) = xy_skaalaus(0.05*Wxmaasto, 0.03*Wymaasto)
        self.w = w
        self.h = h
        self.ruuti = 1.0
        self.KuulanTiheys = 5000.0
        self.kuulanVari = Sin
        self.latausaika = Nayttotaajuus
        self.kulma = pi/7.0  # piipun asento
        self.p1x = 0.0  # piipun pään x-koordinaatti
        self.p1y = 0.0  # piipun pään y-koordinaatti
        self.vaunu = pygame.Rect(0, 0, w, h)

    def piirra(self):
        (x, y) = xy_ruudulla(self.x, self.y)
        # piirretään mörssärin piippu
        (sx, sy) = r_xy(self.kulma, 200.0)
        self.p1x = self.x + sx
        self.p1y = self.y + sy
        if self.latausaika > 0:
            self.latausaika = self.latausaika - 1
            vari = Musta
        else:
            vari = Pun
        pygame.draw.line(screen, vari, (x, y),
                         xy_ruudulla(self.p1x, self.p1y), 10)
        # Ajopanosten ruutimäärän osoitin
        pygame.draw.line(screen, Sin, (10, Wy),
                         (10, Wy-int(self.ruuti/12.0*Wy)), 8)
        # vaunu
        self.vaunu.center = (x-self.w/4, y)
        pygame.draw.ellipse(screen, Musta, self.vaunu, 0)

    # Vaunun liikuttelua z ja x näppäimillä.
    # Vaunulla ei pääse vuoren läpi ;-)
    def liiku_x(self, dx):
        self.x = self.x + dx
        if self.x > vuori.x1:
            self.x = 0.0

# num+ ja num- näppäimillä kutsutaan tätä metodia
    def add_ruuti(self, m):
        self.ruuti = min(11.0, max(1.0, self.ruuti + 0.5*m))

# num-1, num-2 ja num-3 näppäimillä valitaan kuulan tyyppi
    def valitseKuula(self, tyyppi):
        if tyyppi ==  'puu':
            self.KuulanTiheys = 1000.0
            self.kuulanVari = Kelt
        elif tyyppi ==  'rauta':
            self.KuulanTiheys = 5000.0
            self.kuulanVari = Sin
        elif tyyppi ==  'lyijy':
            self.KuulanTiheys = 12000.0
            self.kuulanVari = Musta

# Käännetään piippua nuolinäppäimillä
    def asento(self, dkulma):
        self.kulma = self.kulma + dkulma

# laukaistaan mörssäri välilyöntinäppäimellä
    def ammu(self):

        self.latausaika = random.randint(Nayttotaajuus/2, 2*Nayttotaajuus)
        M = kuulanMassa(self.KuulanTiheys)
        (vx, vy) = self.kuulan_alkunopeus(M)
        return Cl_ammus(self.p1x, self.p1y, vx, vy, M, self.kuulanVari)

    def kuulan_alkunopeus(self, M):
        Cv0 = 600.0
        v0 = Cv0*sqrt(self.ruuti/M)
        print("ruuti: {:2.1f}".format(self.ruuti),
              "kulma: {:2.1f}".format(self.kulma*180.0/pi),
              "massa: {:4.1f}".format(M),
              "nopeus: {:4.0f}".format(v0))
        return (v0*cos(self.kulma),
                v0*sin(self.kulma))

# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
# Ammus
# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
class Cl_ammus:
    def __init__(self, x, y, vx, vy, M, vari):
        self.x = x
        self.y = y
        self.vx = vx
        self.vy = vy
        self.M = M  # kg/m**3
        self.A = pi*RKuula**2
        self.vari = vari

    def piirra0(self):
        pygame.draw.circle(screen, self.vari,
                           xy_ruudulla(self.x, self.y), 5, 0)

    def liiku0(self):
        (self.x, self.y, self.vx, self.vy) = integ_euler(
            self.x, self.y, self.vx, self.vy, self.A, self.M)

# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
# Pääohjelma alkaa tästä
# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
print("alku")
pygame.init()

pygame.display.init()
# Selvitetään peli-ikkunan koko näytöllä pikseleinä
D_Info = pygame.display.Info()
Wx = D_Info.current_w
Wy = int(0.9*D_Info.current_h)
# pelimaailman leveys "metreinä"
Wxmaasto = 5000.0
scale = Wx/Wxmaasto
# Korkeus pelimaailmassa
Wymaasto = Wy/scale
screen = pygame.display.set_mode((Wx, Wy))
pygame.display.set_caption("Morssari")
clock = pygame.time.Clock()
random.seed()
screen.fill(Tausta)
# Luodaan mörssäri ja vuori
vuori = Cl_vuori()
morssari = Cl_morssari()
kuulat = []

maali = Cl_maali(vuori.x1+2.0*vuori.leveys)

loppu = False
MuistaVanhat = False
while not loppu:
    clock.tick(Nayttotaajuus)
    if not MuistaVanhat:
        screen.fill(Tausta)
    for event in pygame.event.get():
        # Lopetetaan peli sulkemalla ikkuna tai painamalla ESC
        if event.type ==  pygame.QUIT:
            loppu = True
        if event.type ==  pygame.KEYDOWN:
            if event.key ==  pygame.K_ESCAPE:
                loppu = True
            # Pyyhitäänkö peli-ikkuna puhtaaksi joka kierroksella vai ei
            if event.key ==  pygame.K_F1:
                MuistaVanhat = not MuistaVanhat
            # säädellään ajopanoksen ruutimäärää
            if event.key ==  pygame.K_KP_PLUS:
                morssari.add_ruuti(1)
            if event.key ==  pygame.K_KP_MINUS:
                morssari.add_ruuti(-1)
            # valitaan kuulan tyyppi
            if event.key ==  pygame.K_KP1:
                morssari.valitseKuula('puu')
            if event.key ==  pygame.K_KP2:
                morssari.valitseKuula('rauta')
            if event.key ==  pygame.K_KP3:
                morssari.valitseKuula('lyijy')
    # Suunnataan ja siirrellään mörssäriä
    pressed = pygame.key.get_pressed()
    if pressed[pygame.K_UP]:
        morssari.asento(0.005*saatoaskel)
    if pressed[pygame.K_DOWN]:
        morssari.asento(-0.005*saatoaskel)
    if pressed[pygame.K_x]:
        morssari.liiku_x(saatoaskel)
    if pressed[pygame.K_z]:
        morssari.liiku_x(-saatoaskel)

    # Laukaistaan mörssäri
    if pressed[pygame.K_SPACE] and morssari.latausaika ==  0:
        kuulat.append(morssari.ammu())

    # Piirretään kaikki oliot peli-ikkunaan ja lasketaan kuulille ja
    # sirpaleille uudet paikat peli-ikkunassa
    morssari.piirra()
    vuori.piirra()

    if maali.ehja:
        maali.piirra()

#    if not kuulat =  []:
#        for kuula in kuulat:

#            kuula.liiku0()
#            kuula.piirra0()

    if not kuulat ==  []:
        for k, kuula in enumerate(kuulat):

            kuula.liiku0()
            kuula.piirra0()

    pygame.display.flip()
    # while silmukka päättyy tähän
pygame.quit()


Selityksiä ylläolevaan

Tuuli = random.uniform(-50.0, 50.0)
 #  Tuuli = -50.0


Tuuli vaikuttaa tuuliviiriin. Myöhemmässä vaiheessa se vaikuttaa myös kuulan lentoon.
        #  Tuuliviirin masto
        tuulix = Tuuli/50.0*0.05*Wx
        mastonKorkeus = 0.05*Wy
        pygame.draw.line(screen, Musta, (a+w/2, b),
                         (a+w/2, b-mastonKorkeus), 4)
        #  Tuuliviiri
        pygame.draw.line(screen, Sin, (a+w/2, b-mastonKorkeus),
                         (a+w/2+tuulix, b-mastonKorkeus), 12)

    #  Tarkistetaan, onko kuula, jonka koordinaatit ovat x ja y osunut vuoreen

    def osuma(self, x, y):
        return y < self.h and self.x1 < x and x < self.x1 + self.leveys


Hyvä geometrian tehtävä on tehdä tyylikkäämpi vuori piirtämällä monikulmio ja määrittää ylläolevaan funtioon ehto, joka kertoo, onko kuula osunut vuoreen.

This draws a triangle using the polygon command

pygame.draw.polygon(screen, BLACK, [[100, 100], [0, 200], [200, 200]], 5)

 #  = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
 #  Maali
 #  = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
class Cl_maali:
    def __init__(self, xmin):
        self.x = random.uniform(xmin, 0.95*Wxmaasto)
        self.y = 0.0
        self.leveys = 0.05*Wxmaasto
        self.korkeus = 0.03*Wymaasto
        (x, y) = xy_ruudulla(self.x, self.korkeus)
        (w, h) = xy_skaalaus(self.leveys, self.korkeus)
        self.vaunu = pygame.Rect(x, y, w, h)
        self.vari = (225, 255, 0)
        self.ehja = True


Maali sijoitetaan mörssäristä katsoen vuoren taakse satunnaiseen paikkaan.

    def piirra(self):
        if self.ehja:
            pygame.draw.ellipse(screen, self.vari, self.vaunu, 0)

    def osuma(self, x, y):
        if (self.ehja and y < self.korkeus and
          self.x < x < self.x + self.leveys):
            self.ehja = False

            return True
        else:
            return False


Tarkistetaan, onko kuula, jonka koordinaatit ovat x ja y osunut maaliin. Jokainen kuula kutsuu tätä metodia jokaisella pääohjelman while-silmukan kierroksella. Jos kuula on osunut, maali ei enää ole ehjä.

Osuman voisi tarkistaa myös pygamen funktiolla self.vaunu.collidepoint(x,y), kunhan x ja y ovat näyttöruudun koordinaatteja, eivät kuulan koordinaatteja pelimaailmassa.

    def piirra0(self):
        pygame.draw.circle(screen, self.vari,
                           xy_ruudulla(self.x, self.y), 5, 0)


    def liiku0(self):
        (self.x, self.y, self.vx, self.vy) = integ_euler(
            self.x, self.y, self.vx, self.vy, self.A, self.M)


maali = Cl_maali(vuori.x1+2.0*vuori.leveys)


    if maali.ehja:
        maali.piirra()


 #             kuula.liiku0()
 #             kuula.piirra0()

            kuula.liiku0()
            kuula.piirra0()

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



()

v3: Pysäytetään kuula sen osuessa johonkin.

# -*- coding: utf-8 -*-
import pygame
import random
from math import pi, cos, sin, sqrt
Musta = (0, 0, 0)  # Mustassa ei ole mitään valoa
Sin = (0, 0, 255)  # vain sinistä
Pun = (255, 0, 0)
Vihr = (0, 255, 0)
Kelt = (255, 255, 0)
Valk = (255, 255, 255)  # valkoisessa on kaikenväristä valoa
Tausta = (160, 200, 255)
MaastoVari = (95, 95, 20)

# Peli-ikkuna päivitetään Nayttotaajuus kertaa sekunnissa
Nayttotaajuus = 48

# Tällä tykin säätö saadaan jouhevaksi kaikilla näyttötaajuuksilla
saatoaskel = 48.0/Nayttotaajuus

# Nopeutus = 1.0 ->  oikea "filmin" nopeus
Nopeutus = 5

Tuuli = random.uniform(-50.0, 50.0)
# Tuuli = -50.0

g_maa = 9.81  # putoamiskiihtyvyys
RKuula = 0.1  # kuulan säde metreinä

# Lasketaan, mihin tietty pelimaailman piste kuuluu peli-ikkunassa
def xy_ruudulla(xmaasto, ymaasto):
    xpikseli = int(xmaasto*scale)
    ypikseli = int(Wy-ymaasto*scale)
    return (xpikseli, ypikseli)

def xy_skaalaus(w, h):
    xpikseli = int(w*scale)
    ypikseli = int(h*scale)
    return (xpikseli, ypikseli)

def r_xy(kulma, r):
    return (r*cos(kulma), r*sin(kulma))

def kuulanMassa(tiheys):
    VKuula = 4/3*pi*RKuula**3
    return tiheys*VKuula

def kiihtyvyys0():
    ax = 0.0
    ay = -g_maa
    return (ax, ay)

# Siirtymä ja nopeuden muutos ajassa dt
def integ_euler(x, y, vx, vy, A, M):
    dt = 1.0/Nayttotaajuus
    for i in range(Nopeutus):

        (ax, ay) = kiihtyvyys0()

        (x, y, vx, vy) = (x + vx*dt, y + vy*dt, vx + ax*dt, vy + ay*dt)
    return (x, y, vx, vy)

# Vuori ja "tuuliviiri"
class Cl_vuori:
    def __init__(self):
        self.x1 = random.uniform(0.3*Wxmaasto, 0.6*Wxmaasto)
        self.leveys = 0.1*Wxmaasto
        self.h = random.uniform(0.0, 0.6*Wymaasto)
    def piirra(self):
        # Vuori
        (a, b) = xy_ruudulla(self.x1, self.h)
        (w, h) = xy_skaalaus(self.leveys, self.h)
        pygame.draw.rect(screen, MaastoVari, (a, b, w, h), 0)

        # Tuuliviirin masto
        tuulix = Tuuli/50.0*0.05*Wx
        mastonKorkeus = 0.05*Wy
        pygame.draw.line(screen, Musta, (a+w/2, b),
                         (a+w/2, b-mastonKorkeus), 4)
        # Tuuliviiri
        pygame.draw.line(screen, Sin, (a+w/2, b-mastonKorkeus),
                         (a+w/2+tuulix, b-mastonKorkeus), 12)

    # Tarkistetaan, onko kuula, jonka koordinaatit ovat x ja y osunut vuoreen
    def osuma(self, x, y):
        return y < self.h and self.x1 < x and x < self.x1 + self.leveys

# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
# Maali
# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
class Cl_maali:
    def __init__(self, xmin):
        self.x = random.uniform(xmin, 0.95*Wxmaasto)
        self.y = 0.0
        self.leveys = 0.05*Wxmaasto
        self.korkeus = 0.03*Wymaasto
        (x, y) = xy_ruudulla(self.x, self.korkeus)
        (w, h) = xy_skaalaus(self.leveys, self.korkeus)
        self.vaunu = pygame.Rect(x, y, w, h)
        self.vari = (225, 255, 0)
        self.ehja = True

    def piirra(self):
        if self.ehja:
            pygame.draw.ellipse(screen, self.vari, self.vaunu, 0)
    def osuma(self, x, y):
        if (self.ehja and y < self.korkeus and
          self.x < x < self.x + self.leveys):
            self.ehja = False

            return True
        else:
            return False

# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
# Morssari
# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
class Cl_morssari:
    def __init__(self):
        self.x = 0.05*Wxmaasto
        self.y = 0.01*Wymaasto
        (w, h) = xy_skaalaus(0.05*Wxmaasto, 0.03*Wymaasto)
        self.w = w
        self.h = h
        self.ruuti = 1.0
        self.KuulanTiheys = 5000.0
        self.kuulanVari = Sin
        self.latausaika = Nayttotaajuus
        self.kulma = pi/7.0  # piipun asento
        self.p1x = 0.0  # piipun pään x-koordinaatti
        self.p1y = 0.0  # piipun pään y-koordinaatti
        self.vaunu = pygame.Rect(0, 0, w, h)

    def piirra(self):
        (x, y) = xy_ruudulla(self.x, self.y)
        # piirretään mörssärin piippu
        (sx, sy) = r_xy(self.kulma, 200.0)
        self.p1x = self.x + sx
        self.p1y = self.y + sy
        if self.latausaika > 0:
            self.latausaika = self.latausaika - 1
            vari = Musta
        else:
            vari = Pun
        pygame.draw.line(screen, vari, (x, y),
                         xy_ruudulla(self.p1x, self.p1y), 10)
        # Ajopanosten ruutimäärän osoitin
        pygame.draw.line(screen, Sin, (10, Wy),
                         (10, Wy-int(self.ruuti/12.0*Wy)), 8)
        # vaunu
        self.vaunu.center = (x-self.w/4, y)
        pygame.draw.ellipse(screen, Musta, self.vaunu, 0)

    # Vaunun liikuttelua z ja x näppäimillä.
    # Vaunulla ei pääse vuoren läpi ;-)
    def liiku_x(self, dx):
        self.x = self.x + dx
        if self.x > vuori.x1:
            self.x = 0.0

# num+ ja num- näppäimillä kutsutaan tätä metodia
    def add_ruuti(self, m):
        self.ruuti = min(11.0, max(1.0, self.ruuti + 0.5*m))

# num-1, num-2 ja num-3 näppäimillä valitaan kuulan tyyppi
    def valitseKuula(self, tyyppi):
        if tyyppi ==  'puu':
            self.KuulanTiheys = 1000.0
            self.kuulanVari = Kelt
        elif tyyppi ==  'rauta':
            self.KuulanTiheys = 5000.0
            self.kuulanVari = Sin
        elif tyyppi ==  'lyijy':
            self.KuulanTiheys = 12000.0
            self.kuulanVari = Musta

# Käännetään piippua nuolinäppäimillä
    def asento(self, dkulma):
        self.kulma = self.kulma + dkulma

# laukaistaan mörssäri välilyöntinäppäimellä
    def ammu(self):

        self.latausaika = random.randint(Nayttotaajuus/2, 2*Nayttotaajuus)
        M = kuulanMassa(self.KuulanTiheys)
        (vx, vy) = self.kuulan_alkunopeus(M)
        return Cl_ammus(self.p1x, self.p1y, vx, vy, M, self.kuulanVari)

    def kuulan_alkunopeus(self, M):
        Cv0 = 600.0
        v0 = Cv0*sqrt(self.ruuti/M)
        print("ruuti: {:2.1f}".format(self.ruuti),
              "kulma: {:2.1f}".format(self.kulma*180.0/pi),
              "massa: {:4.1f}".format(M),
              "nopeus: {:4.0f}".format(v0))
        return (v0*cos(self.kulma),
                v0*sin(self.kulma))

# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
# Ammus
# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
class Cl_ammus:
    def __init__(self, x, y, vx, vy, M, vari):
        self.x = x
        self.y = y
        self.vx = vx
        self.vy = vy
        self.M = M  # kg/m**3
        self.A = pi*RKuula**2
        self.vari = vari

        self.rajahdys = False
        self.rajahdysvaihe = 0
        self.rajahtanyt = 0

    def piirra0(self):
        pygame.draw.circle(screen, self.vari,
                           xy_ruudulla(self.x, self.y), 5, 0)

    def piirra(self):
        if self.rajahdys:
            vari = Pun
        else:
            vari = self.vari
        if not self.rajahtanyt:
            pygame.draw.circle(screen, vari,
                               xy_ruudulla(self.x, self.y), 5, 0)
            (a, b) = xy_ruudulla(self.x, self.y)
            (c, d) = xy_ruudulla(self.x-self.vx, self.y-self.vy)
            pygame.draw.line(screen, Kelt, (a, b), (c, d), 2)

    def liiku0(self):
        (self.x, self.y, self.vx, self.vy) = integ_euler(
            self.x, self.y, self.vx, self.vy, self.A, self.M)

    def liiku(self):
        self.rajahdys = (vuori.osuma(self.x, self.y) or (self.y < 20) or
                         maali.osuma(self.x, self.y))
        if not self.rajahdys:
            (self.x, self.y, self.vx, self.vy) = integ_euler(
                    self.x, self.y, self.vx, self.vy, self.A, self.M)

        else:
            self.vx = 0.0
            self.vy = 0.0

# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
# Pääohjelma alkaa tästä
# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
print("alku")
pygame.init()

pygame.display.init()
# Selvitetään peli-ikkunan koko näytöllä pikseleinä
D_Info = pygame.display.Info()
Wx = D_Info.current_w
Wy = int(0.9*D_Info.current_h)
# pelimaailman leveys "metreinä"
Wxmaasto = 5000.0
scale = Wx/Wxmaasto
# Korkeus pelimaailmassa
Wymaasto = Wy/scale
screen = pygame.display.set_mode((Wx, Wy))
pygame.display.set_caption("Morssari")
clock = pygame.time.Clock()
random.seed()
screen.fill(Tausta)
# Luodaan mörssäri ja vuori
vuori = Cl_vuori()
morssari = Cl_morssari()
kuulat = []

maali = Cl_maali(vuori.x1+2.0*vuori.leveys)

loppu = False
MuistaVanhat = False
while not loppu:
    clock.tick(Nayttotaajuus)
    if not MuistaVanhat:
        screen.fill(Tausta)
    for event in pygame.event.get():
        # Lopetetaan peli sulkemalla ikkuna tai painamalla ESC
        if event.type ==  pygame.QUIT:
            loppu = True
        if event.type ==  pygame.KEYDOWN:
            if event.key ==  pygame.K_ESCAPE:
                loppu = True
            # Pyyhitäänkö peli-ikkuna puhtaaksi joka kierroksella vai ei
            if event.key ==  pygame.K_F1:
                MuistaVanhat = not MuistaVanhat
            # säädellään ajopanoksen ruutimäärää
            if event.key ==  pygame.K_KP_PLUS:
                morssari.add_ruuti(1)
            if event.key ==  pygame.K_KP_MINUS:
                morssari.add_ruuti(-1)
            # valitaan kuulan tyyppi
            if event.key ==  pygame.K_KP1:
                morssari.valitseKuula('puu')
            if event.key ==  pygame.K_KP2:
                morssari.valitseKuula('rauta')
            if event.key ==  pygame.K_KP3:
                morssari.valitseKuula('lyijy')
    # Suunnataan ja siirrellään mörssäriä
    pressed = pygame.key.get_pressed()
    if pressed[pygame.K_UP]:
        morssari.asento(0.005*saatoaskel)
    if pressed[pygame.K_DOWN]:
        morssari.asento(-0.005*saatoaskel)
    if pressed[pygame.K_x]:
        morssari.liiku_x(saatoaskel)
    if pressed[pygame.K_z]:
        morssari.liiku_x(-saatoaskel)

    # Laukaistaan mörssäri
    if pressed[pygame.K_SPACE] and morssari.latausaika ==  0:
        kuulat.append(morssari.ammu())

    # Piirretään kaikki oliot peli-ikkunaan ja lasketaan kuulille ja
    # sirpaleille uudet paikat peli-ikkunassa
    morssari.piirra()
    vuori.piirra()

    if maali.ehja:
        maali.piirra()

#    if not kuulat =  []:
#        for kuula in kuulat:

#            kuula.liiku0()
#            kuula.piirra0()

#            kuula.piirra()
#            kuula.liiku()

    if not kuulat ==  []:
        for k, kuula in enumerate(kuulat):

            kuula.liiku0()
            kuula.piirra0()

            kuula.liiku()
            kuula.piirra()

    pygame.display.flip()
    # while silmukka päättyy tähän
pygame.quit()


Selityksiä ylläolevaan

        self.rajahdys = False
        self.rajahdysvaihe = 0
        self.rajahtanyt = 0


    def piirra0(self):
        pygame.draw.circle(screen, self.vari,
                           xy_ruudulla(self.x, self.y), 5, 0)


    def piirra(self):
        if self.rajahdys:
            vari = Pun
        else:
            vari = self.vari
        if not self.rajahtanyt:
            pygame.draw.circle(screen, vari,
                               xy_ruudulla(self.x, self.y), 5, 0)
            (a, b) = xy_ruudulla(self.x, self.y)
            (c, d) = xy_ruudulla(self.x-self.vx, self.y-self.vy)
            pygame.draw.line(screen, Kelt, (a, b), (c, d), 2)


Jos meneillään on kuulan räjähdys, piirretään kuula punaisella, muuten kuulan tiheyden kertovalla värillä.

Räjähdyksen loputtua kuula on räjähtänyt, eikä sitä enää piirretä.

Kuulalle piirretään — huvin vuoksi — sen nopeuteen verrannollinen pyrstö.

    def liiku0(self):
        (self.x, self.y, self.vx, self.vy) = integ_euler(
            self.x, self.y, self.vx, self.vy, self.A, self.M)


    def liiku(self):
        self.rajahdys = (vuori.osuma(self.x, self.y) or (self.y < 20) or
                         maali.osuma(self.x, self.y))
        if not self.rajahdys:
            (self.x, self.y, self.vx, self.vy) = integ_euler(
                    self.x, self.y, self.vx, self.vy, self.A, self.M)

        else:
            self.vx = 0.0
            self.vy = 0.0

Ammus räjähtää, jos se osuu vuoreen, maaliin tai maahan ( y < 2) jos ammus ei räjähdä, se lentää fysiikan lakien mukaan.

 #             kuula.liiku0()
 #             kuula.piirra0()

 #             kuula.piirra()
 #             kuula.liiku()


            kuula.liiku0()
            kuula.piirra0()

            kuula.liiku()
            kuula.piirra()

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



()

v4: Lisätään kuulan räjähdys.

# -*- coding: utf-8 -*-
import pygame
import random
from math import pi, cos, sin, sqrt
Musta = (0, 0, 0)  # Mustassa ei ole mitään valoa
Sin = (0, 0, 255)  # vain sinistä
Pun = (255, 0, 0)
Vihr = (0, 255, 0)
Kelt = (255, 255, 0)
Valk = (255, 255, 255)  # valkoisessa on kaikenväristä valoa
Tausta = (160, 200, 255)
MaastoVari = (95, 95, 20)

# Peli-ikkuna päivitetään Nayttotaajuus kertaa sekunnissa
Nayttotaajuus = 48

# Tällä tykin säätö saadaan jouhevaksi kaikilla näyttötaajuuksilla
saatoaskel = 48.0/Nayttotaajuus

# Nopeutus = 1.0 ->  oikea "filmin" nopeus
Nopeutus = 5

Tuuli = random.uniform(-50.0, 50.0)
# Tuuli = -50.0

g_maa = 9.81  # putoamiskiihtyvyys
RKuula = 0.1  # kuulan säde metreinä

# Lasketaan, mihin tietty pelimaailman piste kuuluu peli-ikkunassa
def xy_ruudulla(xmaasto, ymaasto):
    xpikseli = int(xmaasto*scale)
    ypikseli = int(Wy-ymaasto*scale)
    return (xpikseli, ypikseli)

def xy_skaalaus(w, h):
    xpikseli = int(w*scale)
    ypikseli = int(h*scale)
    return (xpikseli, ypikseli)

def r_xy(kulma, r):
    return (r*cos(kulma), r*sin(kulma))

def kuulanMassa(tiheys):
    VKuula = 4/3*pi*RKuula**3
    return tiheys*VKuula

def kiihtyvyys0():
    ax = 0.0
    ay = -g_maa
    return (ax, ay)

# Siirtymä ja nopeuden muutos ajassa dt
def integ_euler(x, y, vx, vy, A, M):
    dt = 1.0/Nayttotaajuus
    for i in range(Nopeutus):

        (ax, ay) = kiihtyvyys0()

        (x, y, vx, vy) = (x + vx*dt, y + vy*dt, vx + ax*dt, vy + ay*dt)
    return (x, y, vx, vy)

# Vuori ja "tuuliviiri"
class Cl_vuori:
    def __init__(self):
        self.x1 = random.uniform(0.3*Wxmaasto, 0.6*Wxmaasto)
        self.leveys = 0.1*Wxmaasto
        self.h = random.uniform(0.0, 0.6*Wymaasto)
    def piirra(self):
        # Vuori
        (a, b) = xy_ruudulla(self.x1, self.h)
        (w, h) = xy_skaalaus(self.leveys, self.h)
        pygame.draw.rect(screen, MaastoVari, (a, b, w, h), 0)

        # Tuuliviirin masto
        tuulix = Tuuli/50.0*0.05*Wx
        mastonKorkeus = 0.05*Wy
        pygame.draw.line(screen, Musta, (a+w/2, b),
                         (a+w/2, b-mastonKorkeus), 4)
        # Tuuliviiri
        pygame.draw.line(screen, Sin, (a+w/2, b-mastonKorkeus),
                         (a+w/2+tuulix, b-mastonKorkeus), 12)

    # Tarkistetaan, onko kuula, jonka koordinaatit ovat x ja y osunut vuoreen
    def osuma(self, x, y):
        return y < self.h and self.x1 < x and x < self.x1 + self.leveys

# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
# Maali
# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
class Cl_maali:
    def __init__(self, xmin):
        self.x = random.uniform(xmin, 0.95*Wxmaasto)
        self.y = 0.0
        self.leveys = 0.05*Wxmaasto
        self.korkeus = 0.03*Wymaasto
        (x, y) = xy_ruudulla(self.x, self.korkeus)
        (w, h) = xy_skaalaus(self.leveys, self.korkeus)
        self.vaunu = pygame.Rect(x, y, w, h)
        self.vari = (225, 255, 0)
        self.ehja = True

    def piirra(self):
        if self.ehja:
            pygame.draw.ellipse(screen, self.vari, self.vaunu, 0)
    def osuma(self, x, y):
        if (self.ehja and y < self.korkeus and
          self.x < x < self.x + self.leveys):
            self.ehja = False

            return True
        else:
            return False

# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
# Morssari
# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
class Cl_morssari:
    def __init__(self):
        self.x = 0.05*Wxmaasto
        self.y = 0.01*Wymaasto
        (w, h) = xy_skaalaus(0.05*Wxmaasto, 0.03*Wymaasto)
        self.w = w
        self.h = h
        self.ruuti = 1.0
        self.KuulanTiheys = 5000.0
        self.kuulanVari = Sin
        self.latausaika = Nayttotaajuus
        self.kulma = pi/7.0  # piipun asento
        self.p1x = 0.0  # piipun pään x-koordinaatti
        self.p1y = 0.0  # piipun pään y-koordinaatti
        self.vaunu = pygame.Rect(0, 0, w, h)

    def piirra(self):
        (x, y) = xy_ruudulla(self.x, self.y)
        # piirretään mörssärin piippu
        (sx, sy) = r_xy(self.kulma, 200.0)
        self.p1x = self.x + sx
        self.p1y = self.y + sy
        if self.latausaika > 0:
            self.latausaika = self.latausaika - 1
            vari = Musta
        else:
            vari = Pun
        pygame.draw.line(screen, vari, (x, y),
                         xy_ruudulla(self.p1x, self.p1y), 10)
        # Ajopanosten ruutimäärän osoitin
        pygame.draw.line(screen, Sin, (10, Wy),
                         (10, Wy-int(self.ruuti/12.0*Wy)), 8)
        # vaunu
        self.vaunu.center = (x-self.w/4, y)
        pygame.draw.ellipse(screen, Musta, self.vaunu, 0)

    # Vaunun liikuttelua z ja x näppäimillä.
    # Vaunulla ei pääse vuoren läpi ;-)
    def liiku_x(self, dx):
        self.x = self.x + dx
        if self.x > vuori.x1:
            self.x = 0.0

# num+ ja num- näppäimillä kutsutaan tätä metodia
    def add_ruuti(self, m):
        self.ruuti = min(11.0, max(1.0, self.ruuti + 0.5*m))

# num-1, num-2 ja num-3 näppäimillä valitaan kuulan tyyppi
    def valitseKuula(self, tyyppi):
        if tyyppi ==  'puu':
            self.KuulanTiheys = 1000.0
            self.kuulanVari = Kelt
        elif tyyppi ==  'rauta':
            self.KuulanTiheys = 5000.0
            self.kuulanVari = Sin
        elif tyyppi ==  'lyijy':
            self.KuulanTiheys = 12000.0
            self.kuulanVari = Musta

# Käännetään piippua nuolinäppäimillä
    def asento(self, dkulma):
        self.kulma = self.kulma + dkulma

# laukaistaan mörssäri välilyöntinäppäimellä
    def ammu(self):

        self.latausaika = random.randint(Nayttotaajuus/2, 2*Nayttotaajuus)
        M = kuulanMassa(self.KuulanTiheys)
        (vx, vy) = self.kuulan_alkunopeus(M)
        return Cl_ammus(self.p1x, self.p1y, vx, vy, M, self.kuulanVari)

    def kuulan_alkunopeus(self, M):
        Cv0 = 600.0
        v0 = Cv0*sqrt(self.ruuti/M)
        print("ruuti: {:2.1f}".format(self.ruuti),
              "kulma: {:2.1f}".format(self.kulma*180.0/pi),
              "massa: {:4.1f}".format(M),
              "nopeus: {:4.0f}".format(v0))
        return (v0*cos(self.kulma),
                v0*sin(self.kulma))

# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
# Ammus
# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
class Cl_ammus:
    def __init__(self, x, y, vx, vy, M, vari):
        self.x = x
        self.y = y
        self.vx = vx
        self.vy = vy
        self.M = M  # kg/m**3
        self.A = pi*RKuula**2
        self.vari = vari

        self.rajahdys = False
        self.rajahdysvaihe = 0
        self.rajahtanyt = 0

    def piirra(self):
        if self.rajahdys:
            vari = Pun
        else:
            vari = self.vari
        if not self.rajahtanyt:
            pygame.draw.circle(screen, vari,
                               xy_ruudulla(self.x, self.y), 5, 0)
            (a, b) = xy_ruudulla(self.x, self.y)
            (c, d) = xy_ruudulla(self.x-self.vx, self.y-self.vy)
            pygame.draw.line(screen, Kelt, (a, b), (c, d), 2)

    def liiku(self):
        self.rajahdys = (vuori.osuma(self.x, self.y) or (self.y < 20) or
                         maali.osuma(self.x, self.y))
        if not self.rajahdys:
            (self.x, self.y, self.vx, self.vy) = integ_euler(
                    self.x, self.y, self.vx, self.vy, self.A, self.M)

        else:
            self.vx = 0.0
            self.vy = 0.0

            self.rajahda()

    def rajahda(self):
        if self.rajahdysvaihe < Nayttotaajuus/2:

            self.rajahdysvaihe = self.rajahdysvaihe + 1
            for i in range(200):
                (p0x, p0y) = xy_ruudulla(self.x, self.y)
                (dx, dy) = r_xy(random.uniform(0, 2*pi),
                           self.rajahdysvaihe*random.uniform(0.25, 2.5))
                p1x = p0x + int(dx)
                p1y = p0y - int(dy)
                vari = (random.randint(160, 255),
                        random.randint(80, 255), 0)
                pygame.draw.line(screen, vari, (p0x, p0y), (p1x, p1y), 1)
        else:
            self.rajahtanyt = True

# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
# Pääohjelma alkaa tästä
# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
print("alku")
pygame.init()

pygame.display.init()
# Selvitetään peli-ikkunan koko näytöllä pikseleinä
D_Info = pygame.display.Info()
Wx = D_Info.current_w
Wy = int(0.9*D_Info.current_h)
# pelimaailman leveys "metreinä"
Wxmaasto = 5000.0
scale = Wx/Wxmaasto
# Korkeus pelimaailmassa
Wymaasto = Wy/scale
screen = pygame.display.set_mode((Wx, Wy))
pygame.display.set_caption("Morssari")
clock = pygame.time.Clock()
random.seed()
screen.fill(Tausta)
# Luodaan mörssäri ja vuori
vuori = Cl_vuori()
morssari = Cl_morssari()
kuulat = []

maali = Cl_maali(vuori.x1+2.0*vuori.leveys)

loppu = False
MuistaVanhat = False
while not loppu:
    clock.tick(Nayttotaajuus)
    if not MuistaVanhat:
        screen.fill(Tausta)
    for event in pygame.event.get():
        # Lopetetaan peli sulkemalla ikkuna tai painamalla ESC
        if event.type ==  pygame.QUIT:
            loppu = True
        if event.type ==  pygame.KEYDOWN:
            if event.key ==  pygame.K_ESCAPE:
                loppu = True
            # Pyyhitäänkö peli-ikkuna puhtaaksi joka kierroksella vai ei
            if event.key ==  pygame.K_F1:
                MuistaVanhat = not MuistaVanhat
            # säädellään ajopanoksen ruutimäärää
            if event.key ==  pygame.K_KP_PLUS:
                morssari.add_ruuti(1)
            if event.key ==  pygame.K_KP_MINUS:
                morssari.add_ruuti(-1)
            # valitaan kuulan tyyppi
            if event.key ==  pygame.K_KP1:
                morssari.valitseKuula('puu')
            if event.key ==  pygame.K_KP2:
                morssari.valitseKuula('rauta')
            if event.key ==  pygame.K_KP3:
                morssari.valitseKuula('lyijy')
    # Suunnataan ja siirrellään mörssäriä
    pressed = pygame.key.get_pressed()
    if pressed[pygame.K_UP]:
        morssari.asento(0.005*saatoaskel)
    if pressed[pygame.K_DOWN]:
        morssari.asento(-0.005*saatoaskel)
    if pressed[pygame.K_x]:
        morssari.liiku_x(saatoaskel)
    if pressed[pygame.K_z]:
        morssari.liiku_x(-saatoaskel)

    # Laukaistaan mörssäri
    if pressed[pygame.K_SPACE] and morssari.latausaika ==  0:
        kuulat.append(morssari.ammu())

    # Piirretään kaikki oliot peli-ikkunaan ja lasketaan kuulille ja
    # sirpaleille uudet paikat peli-ikkunassa
    morssari.piirra()
    vuori.piirra()

    if maali.ehja:
        maali.piirra()

#    if not kuulat =  []:
#        for kuula in kuulat:

#            kuula.piirra()
#            kuula.liiku()

    if not kuulat ==  []:
        for k, kuula in enumerate(kuulat):

            kuula.liiku()
            kuula.piirra()

    pygame.display.flip()
    # while silmukka päättyy tähän
pygame.quit()


Selityksiä ylläolevaan

            self.rajahda()


    def rajahda(self):
        if self.rajahdysvaihe < Nayttotaajuus/2:

            self.rajahdysvaihe = self.rajahdysvaihe + 1
            for i in range(200):
                (p0x, p0y) = xy_ruudulla(self.x, self.y)
                (dx, dy) = r_xy(random.uniform(0, 2*pi),
                           self.rajahdysvaihe*random.uniform(0.25, 2.5))
                p1x = p0x + int(dx)
                p1y = p0y - int(dy)
                vari = (random.randint(160, 255),
                        random.randint(80, 255), 0)
                pygame.draw.line(screen, vari, (p0x, p0y), (p1x, p1y), 1)
        else:
            self.rajahtanyt = True


Räjähdys on monivaiheinen ja kestää Nayttotaajuus/3 peli-ikkunan päivityksen ajan. Joka vaiheessa piirretään joukko räjähdyspisteestä alkavia punakeltaisia janoja. Tähän ei ole fysikaalista perustetta, mutta se näyttää mielestäni hauskalta.

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



()

v5: Otetaan huomioon ilmanvastus, räjäytetään maali, lisätään ääniä.

# -*- coding: utf-8 -*-
import pygame
import random
from math import pi, cos, sin, sqrt
Musta = (0, 0, 0)  # Mustassa ei ole mitään valoa
Sin = (0, 0, 255)  # vain sinistä
Pun = (255, 0, 0)
Vihr = (0, 255, 0)
Kelt = (255, 255, 0)
Valk = (255, 255, 255)  # valkoisessa on kaikenväristä valoa
Tausta = (160, 200, 255)
MaastoVari = (95, 95, 20)

# Peli-ikkuna päivitetään Nayttotaajuus kertaa sekunnissa
Nayttotaajuus = 48

# Tällä tykin säätö saadaan jouhevaksi kaikilla näyttötaajuuksilla
saatoaskel = 48.0/Nayttotaajuus

# Nopeutus = 1.0 ->  oikea "filmin" nopeus
Nopeutus = 5

Tuuli = random.uniform(-50.0, 50.0)
# Tuuli = -50.0

g_maa = 9.81  # putoamiskiihtyvyys
RKuula = 0.1  # kuulan säde metreinä

# Lasketaan, mihin tietty pelimaailman piste kuuluu peli-ikkunassa
def xy_ruudulla(xmaasto, ymaasto):
    xpikseli = int(xmaasto*scale)
    ypikseli = int(Wy-ymaasto*scale)
    return (xpikseli, ypikseli)

def xy_skaalaus(w, h):
    xpikseli = int(w*scale)
    ypikseli = int(h*scale)
    return (xpikseli, ypikseli)

def r_xy(kulma, r):
    return (r*cos(kulma), r*sin(kulma))

def kuulanMassa(tiheys):
    VKuula = 4/3*pi*RKuula**3
    return tiheys*VKuula

def kiihtyvyys0():
    ax = 0.0
    ay = -g_maa
    return (ax, ay)

def kiihtyvyys(vx, vy, A, m):
    c = 0.15
    vx = vx - Tuuli  # Kuulan vaakanopeus ilman suhteen
    v2 = vx**2 + vy**2
    v = sqrt(v2)
    Fd = -c*A*v2/m
    ax = vx/v*Fd
    ay = vy/v*Fd-g_maa
    return (ax, ay)

# Siirtymä ja nopeuden muutos ajassa dt
def integ_euler(x, y, vx, vy, A, M):
    dt = 1.0/Nayttotaajuus
    for i in range(Nopeutus):

        (ax, ay) = kiihtyvyys0()

        (ax, ay) = kiihtyvyys(vx, vy, A, M)

        (x, y, vx, vy) = (x + vx*dt, y + vy*dt, vx + ax*dt, vy + ay*dt)
    return (x, y, vx, vy)

def hehku(color):
    (r, g, b) = color
    r = max(0, min(255, r + random.randint(-10, 10)))
    g = max(0, min(255, g + random.randint(-10, 10)))
    b = max(0, min(255, b + random.randint(-10, 10)))
    return (r, g, b)

# Vuori ja "tuuliviiri"
class Cl_vuori:
    def __init__(self):
        self.x1 = random.uniform(0.3*Wxmaasto, 0.6*Wxmaasto)
        self.leveys = 0.1*Wxmaasto
        self.h = random.uniform(0.0, 0.6*Wymaasto)
    def piirra(self):
        # Vuori
        (a, b) = xy_ruudulla(self.x1, self.h)
        (w, h) = xy_skaalaus(self.leveys, self.h)
        pygame.draw.rect(screen, MaastoVari, (a, b, w, h), 0)

        # Tuuliviirin masto
        tuulix = Tuuli/50.0*0.05*Wx
        mastonKorkeus = 0.05*Wy
        pygame.draw.line(screen, Musta, (a+w/2, b),
                         (a+w/2, b-mastonKorkeus), 4)
        # Tuuliviiri
        pygame.draw.line(screen, Sin, (a+w/2, b-mastonKorkeus),
                         (a+w/2+tuulix, b-mastonKorkeus), 12)

    # Tarkistetaan, onko kuula, jonka koordinaatit ovat x ja y osunut vuoreen
    def osuma(self, x, y):
        return y < self.h and self.x1 < x and x < self.x1 + self.leveys

# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
# Maali
# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
class Cl_maali:
    def __init__(self, xmin):
        self.x = random.uniform(xmin, 0.95*Wxmaasto)
        self.y = 0.0
        self.leveys = 0.05*Wxmaasto
        self.korkeus = 0.03*Wymaasto
        (x, y) = xy_ruudulla(self.x, self.korkeus)
        (w, h) = xy_skaalaus(self.leveys, self.korkeus)
        self.vaunu = pygame.Rect(x, y, w, h)
        self.vari = (225, 255, 0)
        self.ehja = True

    def piirra(self):
        if self.ehja:
            pygame.draw.ellipse(screen, self.vari, self.vaunu, 0)
    def osuma(self, x, y):
        if (self.ehja and y < self.korkeus and
          self.x < x < self.x + self.leveys):
            self.ehja = False

            for i in range(500):
                sirpaleet.append(Cl_sirpale(self.x, self.korkeus, self.vari))
            osuma_snd.play()

            return True
        else:
            return False

# Sirpale
class Cl_sirpale:
    def __init__(self, x, y, color):
        self.x = x
        self.y = y
        self.sx = self.x*random.uniform(2, 12)/100.0
        self.sy = self.y*random.uniform(2, 12)/100.0
        self.paksuus = self.y*random.uniform(2, 12)/100.0
        self.A = self.sx*self.sy
        self.M = self.A*self.paksuus*random.uniform(500.0, 2000.0)
        self.color = color
        (self.vx, self.vy) = r_xy(random.uniform(0.5, pi-0.5),
                             random.uniform(10.0, 150.0))

    def piirra(self):
        (x, y) = xy_ruudulla(self.x, self.y)
        pygame.draw.rect(screen, self.color,
                         (x+int(self.sx/2), y+int(self.sy/2),
                          int(self.sx), int(self.sy)), 0)

    # Sirpaleet liikkuvat samalla dynamiikalla kuin kuulatkin
    def liiku(self):
        if self.y > 2.0:

            self.sx = min(12, max(2, self.sx + random.randint(-2, 2)))
            self.sy = min(12, max(2, self.sy + random.randint(-2, 2)))
            self.color = hehku(self.color)

            (self.x, self.y, self.vx, self.vy) = integ_euler(
                        self.x, self.y, self.vx, self.vy, self.A, self.M)

# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
# Morssari
# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
class Cl_morssari:
    def __init__(self):
        self.x = 0.05*Wxmaasto
        self.y = 0.01*Wymaasto
        (w, h) = xy_skaalaus(0.05*Wxmaasto, 0.03*Wymaasto)
        self.w = w
        self.h = h
        self.ruuti = 1.0
        self.KuulanTiheys = 5000.0
        self.kuulanVari = Sin
        self.latausaika = Nayttotaajuus
        self.kulma = pi/7.0  # piipun asento
        self.p1x = 0.0  # piipun pään x-koordinaatti
        self.p1y = 0.0  # piipun pään y-koordinaatti
        self.vaunu = pygame.Rect(0, 0, w, h)

    def piirra(self):
        (x, y) = xy_ruudulla(self.x, self.y)
        # piirretään mörssärin piippu
        (sx, sy) = r_xy(self.kulma, 200.0)
        self.p1x = self.x + sx
        self.p1y = self.y + sy
        if self.latausaika > 0:
            self.latausaika = self.latausaika - 1
            vari = Musta
        else:
            vari = Pun
        pygame.draw.line(screen, vari, (x, y),
                         xy_ruudulla(self.p1x, self.p1y), 10)
        # Ajopanosten ruutimäärän osoitin
        pygame.draw.line(screen, Sin, (10, Wy),
                         (10, Wy-int(self.ruuti/12.0*Wy)), 8)
        # vaunu
        self.vaunu.center = (x-self.w/4, y)
        pygame.draw.ellipse(screen, Musta, self.vaunu, 0)

    # Vaunun liikuttelua z ja x näppäimillä.
    # Vaunulla ei pääse vuoren läpi ;-)
    def liiku_x(self, dx):
        self.x = self.x + dx
        if self.x > vuori.x1:
            self.x = 0.0

# num+ ja num- näppäimillä kutsutaan tätä metodia
    def add_ruuti(self, m):
        self.ruuti = min(11.0, max(1.0, self.ruuti + 0.5*m))

        self.ruuti = min(11.0, max(1.0, self.ruuti + 2.0*m))

# num-1, num-2 ja num-3 näppäimillä valitaan kuulan tyyppi
    def valitseKuula(self, tyyppi):
        if tyyppi ==  'puu':
            self.KuulanTiheys = 1000.0
            self.kuulanVari = Kelt
        elif tyyppi ==  'rauta':
            self.KuulanTiheys = 5000.0
            self.kuulanVari = Sin
        elif tyyppi ==  'lyijy':
            self.KuulanTiheys = 12000.0
            self.kuulanVari = Musta

# Käännetään piippua nuolinäppäimillä
    def asento(self, dkulma):
        self.kulma = self.kulma + dkulma

# laukaistaan mörssäri välilyöntinäppäimellä
    def ammu(self):

        morssari_snd.play()

        self.latausaika = random.randint(Nayttotaajuus/2, 2*Nayttotaajuus)
        M = kuulanMassa(self.KuulanTiheys)
        (vx, vy) = self.kuulan_alkunopeus(M)
        return Cl_ammus(self.p1x, self.p1y, vx, vy, M, self.kuulanVari)

    def kuulan_alkunopeus(self, M):
        Cv0 = 600.0
        v0 = Cv0*sqrt(self.ruuti/M)
        print("ruuti: {:2.1f}".format(self.ruuti),
              "kulma: {:2.1f}".format(self.kulma*180.0/pi),
              "massa: {:4.1f}".format(M),
              "nopeus: {:4.0f}".format(v0))
        return (v0*cos(self.kulma),
                v0*sin(self.kulma))

# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
# Ammus
# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
class Cl_ammus:
    def __init__(self, x, y, vx, vy, M, vari):
        self.x = x
        self.y = y
        self.vx = vx
        self.vy = vy
        self.M = M  # kg/m**3
        self.A = pi*RKuula**2
        self.vari = vari

        self.rajahdys = False
        self.rajahdysvaihe = 0
        self.rajahtanyt = 0

    def piirra(self):
        if self.rajahdys:
            vari = Pun
        else:
            vari = self.vari
        if not self.rajahtanyt:
            pygame.draw.circle(screen, vari,
                               xy_ruudulla(self.x, self.y), 5, 0)
            (a, b) = xy_ruudulla(self.x, self.y)
            (c, d) = xy_ruudulla(self.x-self.vx, self.y-self.vy)
            pygame.draw.line(screen, Kelt, (a, b), (c, d), 2)

    def liiku(self):
        self.rajahdys = (vuori.osuma(self.x, self.y) or (self.y < 20) or
                         maali.osuma(self.x, self.y))
        if not self.rajahdys:
            (self.x, self.y, self.vx, self.vy) = integ_euler(
                    self.x, self.y, self.vx, self.vy, self.A, self.M)

        else:
            self.vx = 0.0
            self.vy = 0.0

            self.rajahda()

    def rajahda(self):
        if self.rajahdysvaihe < Nayttotaajuus/2:

            if self.rajahdysvaihe ==  0:
                kuula_snd.play()
                kuula_snd.fadeout(600)

            self.rajahdysvaihe = self.rajahdysvaihe + 1
            for i in range(200):
                (p0x, p0y) = xy_ruudulla(self.x, self.y)
                (dx, dy) = r_xy(random.uniform(0, 2*pi),
                           self.rajahdysvaihe*random.uniform(0.25, 2.5))
                p1x = p0x + int(dx)
                p1y = p0y - int(dy)
                vari = (random.randint(160, 255),
                        random.randint(80, 255), 0)
                pygame.draw.line(screen, vari, (p0x, p0y), (p1x, p1y), 1)
        else:
            self.rajahtanyt = True

# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
# Pääohjelma alkaa tästä
# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
print("alku")
pygame.init()

pygame.mixer.init(frequency=22050, size=-16, channels=2, buffer=512)
osuma_snd = pygame.mixer.Sound(
    'Explosion_Ultra_Bass-Mark_DiAngelo-1810420658.wav')
morssari_snd = pygame.mixer.Sound('morssari.wav')
kuula_snd = pygame.mixer.Sound('kuula.wav')

pygame.display.init()
# Selvitetään peli-ikkunan koko näytöllä pikseleinä
D_Info = pygame.display.Info()
Wx = D_Info.current_w
Wy = int(0.9*D_Info.current_h)
# pelimaailman leveys "metreinä"
Wxmaasto = 5000.0
scale = Wx/Wxmaasto
# Korkeus pelimaailmassa
Wymaasto = Wy/scale
screen = pygame.display.set_mode((Wx, Wy))
pygame.display.set_caption("Morssari")
clock = pygame.time.Clock()
random.seed()
screen.fill(Tausta)
# Luodaan mörssäri ja vuori
vuori = Cl_vuori()
morssari = Cl_morssari()
kuulat = []

maali = Cl_maali(vuori.x1+2.0*vuori.leveys)

sirpaleet = []

loppu = False
MuistaVanhat = False
while not loppu:
    clock.tick(Nayttotaajuus)
    if not MuistaVanhat:
        screen.fill(Tausta)
    for event in pygame.event.get():
        # Lopetetaan peli sulkemalla ikkuna tai painamalla ESC
        if event.type ==  pygame.QUIT:
            loppu = True
        if event.type ==  pygame.KEYDOWN:
            if event.key ==  pygame.K_ESCAPE:
                loppu = True
            # Pyyhitäänkö peli-ikkuna puhtaaksi joka kierroksella vai ei
            if event.key ==  pygame.K_F1:
                MuistaVanhat = not MuistaVanhat
            # säädellään ajopanoksen ruutimäärää
            if event.key ==  pygame.K_KP_PLUS:
                morssari.add_ruuti(1)
            if event.key ==  pygame.K_KP_MINUS:
                morssari.add_ruuti(-1)
            # valitaan kuulan tyyppi
            if event.key ==  pygame.K_KP1:
                morssari.valitseKuula('puu')
            if event.key ==  pygame.K_KP2:
                morssari.valitseKuula('rauta')
            if event.key ==  pygame.K_KP3:
                morssari.valitseKuula('lyijy')
    # Suunnataan ja siirrellään mörssäriä
    pressed = pygame.key.get_pressed()
    if pressed[pygame.K_UP]:
        morssari.asento(0.005*saatoaskel)
    if pressed[pygame.K_DOWN]:
        morssari.asento(-0.005*saatoaskel)
    if pressed[pygame.K_x]:
        morssari.liiku_x(saatoaskel)
    if pressed[pygame.K_z]:
        morssari.liiku_x(-saatoaskel)

    # Laukaistaan mörssäri
    if pressed[pygame.K_SPACE] and morssari.latausaika ==  0:
        kuulat.append(morssari.ammu())

    # Piirretään kaikki oliot peli-ikkunaan ja lasketaan kuulille ja
    # sirpaleille uudet paikat peli-ikkunassa
    morssari.piirra()
    vuori.piirra()

    if maali.ehja:
        maali.piirra()

#            kuula.piirra()
#            kuula.liiku()

    if not kuulat ==  []:
        for k, kuula in enumerate(kuulat):

            kuula.liiku()
            kuula.piirra()

            if kuula.rajahtanyt:
                del kuulat[k]

    if not sirpaleet ==  []:
        for sirpale in sirpaleet:
            sirpale.liiku()
            sirpale.piirra()

    pygame.display.flip()
    # while silmukka päättyy tähän
pygame.quit()


Selityksiä ylläolevaan

Ilmanvastuksen takia tuuli vaikuttaa kuulan lentorataan ja mitä kevyempi kuula, sitä enemmän ilmanastus ja tuuli vaikuttavat.

def kiihtyvyys0():
    ax = 0.0
    ay = -g_maa
    return (ax, ay)


def kiihtyvyys(vx, vy, A, m):
    c = 0.15
    vx = vx - Tuuli  #  Kuulan vaakanopeus ilman suhteen
    v2 = vx**2 + vy**2
    v = sqrt(v2)
    Fd = -c*A*v2/m
    ax = vx/v*Fd
    ay = vy/v*Fd-g_maa
    return (ax, ay)


Funktio kiihtyvyys() laskee kuulan x ja y -suuntaisen kiihtyvyyden, kun ilmanvastus otetaan huomioon. Ilmanvastuksen kerroin c on haettu kokeilemalla peliin sopiva arvo, jolla kuula lentää mukavasti. Se ei siis ole todellinen pyöreälle kuulalle oikeasti laskettu kerroin. Seuraava kuva yrittää selittää, miten fysiikan lakien, yhdenmuotoisten kolmioiden ja Pythagoraan lauseen perusteella ilmanvastuksen vaikutus lasketaan ylläolevassa funktiossa. Ilman aiempia tietoja selitystä voi olla vaikea ymmärtää, mutta sitä ei olekaan pakko ymmärtää.

../xml/Programming/PythonOpetus/voimat.png

Kuulan kohdistuvat voimat

Ilman aiheuttama vastus on verrannollinen nopeuden neliöön ja kappaleen poikkipintaan:

F d = c A v 2

Voiman aiheuttama kiihtyvyys saadaan Newton liikelaista:

a = F d / m
        (ax, ay) = kiihtyvyys0()

        (ax, ay) = kiihtyvyys(vx, vy, A, M)

def hehku(color):
    (r, g, b) = color
    r = max(0, min(255, r + random.randint(-10, 10)))
    g = max(0, min(255, g + random.randint(-10, 10)))
    b = max(0, min(255, b + random.randint(-10, 10)))
    return (r, g, b)


Muutetaan värin kutakin komponenttia satunnaisesti. Tätä funktiota käytetään maalin sirpaleiden värin asteittaiseen muuttamisen. Tämä efekti on kokeilu, jolle ei tässä tapauksessa ole fysikaalista 'esikuvaa'. Hiilloksen hehkun voisi ehkä toteuttaa tähän tyyliin.

    def piirra(self):
        if self.ehja:
            pygame.draw.ellipse(screen, self.vari, self.vaunu, 0)

    def osuma(self, x, y):
        if (self.ehja and y < self.korkeus and
          self.x < x < self.x + self.leveys):
            self.ehja = False

            for i in range(500):
                sirpaleet.append(Cl_sirpale(self.x, self.korkeus, self.vari))
            osuma_snd.play()

            return True
        else:
            return False


Maali räjähtää sirpaleiksi. Jokainen sirpale lisätään listaan sirpaleet. Käynnistetään räjähdyksen ääni

 #  Sirpale
class Cl_sirpale:
    def __init__(self, x, y, color):
        self.x = x
        self.y = y
        self.sx = self.x*random.uniform(2, 12)/100.0
        self.sy = self.y*random.uniform(2, 12)/100.0
        self.paksuus = self.y*random.uniform(2, 12)/100.0
        self.A = self.sx*self.sy
        self.M = self.A*self.paksuus*random.uniform(500.0, 2000.0)
        self.color = color
        (self.vx, self.vy) = r_xy(random.uniform(0.5, pi-0.5),
                             random.uniform(10.0, 150.0))


Maalin sirpaleet oletetaan suorakulmaisiksi särmiöiksi, joille annetaan satunnainen tiheys ja mitat. Ilmanvastukseen vaikuttavaa pinta-ala A ei tietenkään ole todellinen. Tarkoitus on vain luoda kappaleita, jotka eivät kaikki lennä aivan samalla tavalla.

Sirpale lähtee liikkeelle satunnaisella nopeudella satunnaiseen suuntaan 0.5 - pi-0.5 radiaania (pi radiaania = 180 astetta)


    def piirra(self):
        (x, y) = xy_ruudulla(self.x, self.y)
        pygame.draw.rect(screen, self.color,
                         (x+int(self.sx/2), y+int(self.sy/2),
                          int(self.sx), int(self.sy)), 0)


    #  Sirpaleet liikkuvat samalla dynamiikalla kuin kuulatkin
    def liiku(self):
        if self.y > 2.0:

            self.sx = min(12, max(2, self.sx + random.randint(-2, 2)))
            self.sy = min(12, max(2, self.sy + random.randint(-2, 2)))
            self.color = hehku(self.color)

            (self.x, self.y, self.vx, self.vy) = integ_euler(
                        self.x, self.y, self.vx, self.vy, self.A, self.M)


Vaihdellaan sirpaleiden kokoa asteittain, niin ne näyttävät elävämmiltä. Hehku on kokeilu, joka antaa sirpaleiden värin muuttua asteittain.

        self.ruuti = min(11.0, max(1.0, self.ruuti + 2.0*m))

        morssari_snd.play()

            if self.rajahdysvaihe ==  0:
                kuula_snd.play()
                kuula_snd.fadeout(600)

Räjähdyksen ensimmäisessä vaiheessa käynnistetään räjähdyksen ääni.
Ääniefektejä. Maalin räjähdyksen sangen sotaisan äänen löysin webistä. Muut — ei ehkä kaikkien mielestä kovin tyylikkäät ääniefektit — tein rumpusyntetisaattorilla.
pygame.mixer.init(frequency=22050, size=-16, channels=2, buffer=512)
osuma_snd = pygame.mixer.Sound(
    'Explosion_Ultra_Bass-Mark_DiAngelo-1810420658.wav')
morssari_snd = pygame.mixer.Sound('morssari.wav')
kuula_snd = pygame.mixer.Sound('kuula.wav')


sirpaleet = []


            if kuula.rajahtanyt:
                del kuulat[k]


kuula.piirra ei piirra räjähtäneitä kuulia, joten ne häviävät näkyvistä. Periaatteessa räjähtäneet kuulat samoin kuin maalin sirpaleet olisi kuitenkin parempi hävittää kokonaan del komennolla ylläesitetyllä tavalla, koska jokainen ohjelmassa luotu olio varaa hieman koneen muistia. Tämän pelin tapauksessa sillä ei kuitenkaan ole käytännön merkitystä.

    if not sirpaleet ==  []:
        for sirpale in sirpaleet:
            sirpale.liiku()
            sirpale.piirra()


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




(Kesäkuu 2015 )

Voimia ja liikettä

Mörssärin kuulaan vaikuttivat Maan vetovoima ja ilmanvastus. Seuraavan pelin avaruusalusta liikuttelevat Maan ja Kuun vetovoiman sekä rakettimoottorit. Siksi on hyvä kerrata, miten kappaleiden liikkeitä lasketaan.


Voiman F kappaleelle antama kiihtyvyys a on a = F/m, missä m on kappaleen massa. Kiihtyvyys on yhtä kuin nopeuden v muutos aikayksikössä eli, jos voima F on vakio, voimme laskea v(t+dt) = v(t) + F/m*dt. Nopeus v taas on aikayksikössä kuljettu matka s, eli jos nopeus on vakio, voimme laskea s(t+dt) = s(t) + v*dt. Voimat ja nopeudet eivät olleet mörssäripelissä vakioita, mutta kun laskimme uudet voimat ja nopeudet aina lyhyen ajan dt välein, laskenta oli niin tarkkaa, että virhettä ei huomannut.

Mörssäripelissä Maan vetovoima vaikutti vain pystysuuntaan — y-akselin suuntaan, mutta ilmanvastus liikettä vastaan, eli kuulan noustessa vinosti ylös ilmanvastus vaikutti vinosti alas ja lennon huipun jälkeen päinvastoin. Voima jaettiin x- ja y- suuntaiseen osaan ja laskettiin erikseen nopeudet ja siirtymät x- ja y-suuntaan. Näin syntyi kuulan rata.

Pari seuraava kappaletta voit harpata, ellet ole kiinnostunut numeerisesta matematiikasta.

Kiihtyvyys on nopeuden derivaatta ja paikan toinen derivaatta ajan suhteen, joten yhtälö a = F/m on differentiaaliyhtälö. Ylläesitettyä tapaa laskea kuulan rata kutsutaan differentiaaliyhtälön numeeriseksi ratkaisemiseksi Eulerin menetelmällä. Differentiaaliyhtälöiden ratkaisemin ei kuulu peruskoulun eikä edes lukion oppimäärään, mutta jos vähänkään kiinnostaa, siihen kannattaa perehtyä, koska oikean fysiikan soveltaminen tekee tämän tyyppisistä peleistä paljon kiehtovampia.

Seuraavassa pelissä laskin aluksi avaruusaluksen radan samalla menetelmällä kuin mörssäripelissä kuulan radan. Ellei alusta ohjata moottoreilla, sen pitäisi pysyä vakioradalla, mutta alukseni kiersi hitaasti kasvavaa spiraalia. Arvelin sen johtuvan Eulerin menetelmän epätarkkuudesta. Virhe tulee siitä, että oletetaan voima ja kiihtyvyys vakioiksi aikavälin dt ajaksi, mistä tulee sitä enemmän virhettä, mitä suurempi on dt. Jos taas dt:n valitsee liian lyhyeksi, derivaatat saattavat pyöristyä nolliksi, koska tietokoneella luvut esitetään äärellisellä tarkkuudella. (Ehkä nollaksi pyöristymisen takia alus "juuttui" muutaman kerran ulkoavaruuteen, vaikka sen olisi pitänyt pikku hiljaa lähteä Maata kohti.)

Siirryin käyttämään aluksen liikkeen laskemisessa Runge-Kutta menetelmää ja alus alkoi pysyä kiltisti radallaan. Algoritmin ohjelmointi vaati minulta pikkuisen miettimistä, joten koodia ei liioin liene ihan helppo ymmärtää. En ole koskaan yrittänyt ymmärtää, mihin Runge-Kutta algoritmi perustuu — miten se toimii. Tietysti minun pitäisi perehtyä, mutta moni matematiikan tuloksia voi soveltaa, vaikkei niitä osaakaan johtaa. Ainakin peleissä. Lentokoneita suunnitellessa on syytä ymmärtää käytettyjen matemaattisten menetelmien perusteet ja rajoitukset.



(Sun Mar 11 09:58:51 2018 )

KuuAlus.py

Tehtävänä on ohjata avaruusalus Maata kiertävältä radalta Kuuta kiertävälle radalle.

Alusta ohjataan nuolinäppäimillä sekä z- ja x-näppäimillä. x kiihdyttää alusta isolla raketilla, z jarruttaa isolla raketilla. Nuolet vasemmalle ja oikealle hidastavat ja kiihdyttävät pienemmällä voimalla. Ylä- ja alanuolilla rakettia voi ohjata sivusuunnassa liikerataan nähden. Jos tekee kiihdytykset ja hidastukset oikein, sivusuuntaista ohjausta ei tarvitse.
F2 tuo näkyviin ohjaamista helpottavia suureita.
Numeronäppäimistön 0, 1 ja enter vaikuttavat pelin nopeuteen.
Numeronäppäimistön + ja - zoomaavat näyttöä
m- ja k- näppäimet siirtävät näytön keskipisteeseen joko Maan tai Kuun F1 jättää aluksen radan näkyviin.

Perehdy ohjelman toimintaa tarkemmin lukemalla koodia.

Avaruuslento ei ole helppoa. Seuraavat peukalosäännöt saattavat auttaa.

Lento Kuun kiertoradalle pikkuisen räpistellen



()

Ohjelman runko, Maa ja Kuu

# -*- coding: utf-8 -*-
import pygame
import random
from math import pi, cos, sin, sqrt
Musta = (0, 0, 0)  # Mustassa ei ole mitään valoa
Sin = (0, 0, 255)  # vain sinistä
Pun = (255, 0, 0)
Vihr = (0, 255, 0)
Kelt = (255, 255, 0)
Valk = (255, 255, 255)  # valkoisessa on kaikenväristä valoa
Harmaa = (128, 128, 128)
Keula = (0, 128, 255)
Pera = (255, 200, 0)
# Gravitaatiovakio
G = 6.674e-11
# Etäisyydellä r suunnassa kulma olevan pisteen
# x- ja y-koordinaatit
def rw_xy(r, kulma):
    return (r*cos(kulma), r*sin(kulma))

# Pelin ja simuloinnin ajoitus
# Nopeutus: Montako kertaa todellista nopeammin pelin
# halutaan etenevän
# Tscalea käytetään nopeutuksen sovittamisessa oikeaksi
class cl_Ajat:
    def __init__(self):
        self.Nopeutus = 2000.0
        self.Nayttotaajuus = 24
        self.Dt = 10.0
        self.Tscale = max(1, int(self.Nopeutus/self.Nayttotaajuus/self.Dt))

    def Hidasta(self):
        if self.Nopeutus > 4000:
            self.Nopeutus -= 4000
            self.Tscale = max(1, int(self.Nopeutus/self.Nayttotaajuus/self.Dt))
            self.aikaraportti(self.Tscale, self.Dt, self.Nayttotaajuus)
    def Nopeuta(self):
        self.Nopeutus += 4000
        self.Tscale = max(1, int(self.Nopeutus/self.Nayttotaajuus/self.Dt))
        self.aikaraportti(self.Tscale, self.Dt, self.Nayttotaajuus)
    def Reset(self):
        self.Nopeutus = 1
        self.Tscale = max(1, int(self.Nopeutus/self.Nayttotaajuus/self.Dt))
        self.aikaraportti(self.Tscale, self.Dt, self.Nayttotaajuus)
    def aikaraportti(self, Tsc, Dt, Nfrek):
        print('Aika kuluu ' + str(int(Tsc*Dt*Nfrek)) +
         ' kertaa todellista nopeammin')

# Pelimaailman kuvaaminen näytölle peli-ikkunaan.
# Kuvakulman ja zoomauksen säätelyä.
class Cl_Kamera:
    def __init__(self):
        self.focus = 'maa'
        self.Wx = 1.0e+9
        self.Wy = self.Wx
        self.Wx0 = 0.0
        self.Wy0 = 0.0
# Lasketaan mitä pikseliä näytöllä vastaa pelimaailman piste.
    def xy_naytolla(self, xy):
        scale = Wxnaytto/self.Wx
        (x, y) = xy
        xpikseli = int(Wxnaytto/2 + (x-self.Wx0)*scale)
        ypikseli = int(Wynaytto/2-(y-self.Wy0)*scale)
        return (xpikseli, ypikseli)
    def xy_skaalaus(self, w, h):
        scale = Wxnaytto/self.Wx
        xpikseli = int(w*scale)
        ypikseli = int(h*scale)
        return (xpikseli, ypikseli)
    def skaalaus(self, x):
        scale = Wxnaytto/self.Wx
        xpikseli = int(x*scale)
        return xpikseli
# Sidotaan näytön keskipiste joko Maahan tai Kuuhun
    def liiku(self):
        if self.focus == 'kuu':
            (self.Wx0, self.Wy0) = Kuu.paikka
        else:
            (self.Wx0, self.Wy0) = (0.0, 0.0)
    def zoom(self, z):
        self.Wx *= z
        self.Wy = self.Wx

# Maa. Käytetään todellisia fysikaalisia arvoja.
# Maa pysyy paikallaan pelimaailman keskipisteessä.
class Cl_Maa:
    def __init__(self):
        self.R = 6.37e+6
        self.M = 5.9737e+24
# Sininen ympyrä symboloi maata.
    def piirra(self):
        (xi, yi) = Kamera.xy_naytolla((0, 0))
        ri = Kamera.skaalaus(self.R)
        pygame.draw.circle(screen, Sin, (xi, yi), ri, 0)
# http://fi.wikipedia.org/wiki/Painovoima#Newtonin_painovoimalaki
    def vetovoima(self, xy, m):
        (x, y) = xy
        d = sqrt(x**2 + y**2)
        F = G*m*self.M/d**2
        Fx = -F*x/d
        Fy = -F*y/d
        return (Fx, Fy)
    def crash(self, pxy):
        return (dist(pxy, (0.0, 0.0)) < self.R)
# Kuu. Käytetään todellisia arvoja, paitsi massalle, jonka kasvatin
# moninkertaiseksi, että alus olisi helpompi ohjata Kuun kiertoradalle.
class Cl_Kuu:
    def __init__(self):
        self.R = 1.74e+6
#        self.M = 7.35e+22
        self.M = 18.0e+23  # moninkertainen massa
        self.distMaa = 3.844e+8
        self.kulma = 0.0  # Kuun paikka radallaan radiaaneina
        self.paikka = rw_xy(self.distMaa, self.kulma)
        self.w = 2*pi/(27.3*24.0*3600.0)  # kiertonopeus rad/s
# Kuuta symboloi keltainen ympyrä
    def piirra(self):
        (xi, yi) = Kamera.xy_naytolla(self.paikka)
        pygame.draw.circle(screen, Kelt, (xi, yi),
                           Kamera.skaalaus(self.R), 0)
# piirretään Kuun rata helpottamaan navigointia
        pygame.draw.circle(screen, Harmaa, Kamera.xy_naytolla((0, 0)),
                           Kamera.skaalaus(self.distMaa), 1)
# Kuu liikettä ei lasketa vetovoimalakien mukaan vaan se kiertää
# pakotettua ympyrärataa Maan ympäri.
    def liiku(self):
        self.kulma += self.w*Aika.Dt*Aika.Tscale
        self.paikka = rw_xy(self.distMaa, self.kulma)
    def vetovoima(self, xy, m):
        (x0, y0) = self.paikka
        (x1, y1) = xy
        dx = x1-x0
        dy = y1-y0
        d = sqrt(dx**2 + dy**2)
        F = G*m*self.M/d**2
        Fx = -F*dx/d
        Fy = -F*dy/d
        return (Fx, Fy)
    def crash(self, pxy):
        return (dist(pxy, self.paikka) < self.R)

##= = = = = = = = = = = = = = = = = = = = = = =
#
# Pääohjelma alkaa tästä
# = = = = = = = = = = = = = = = = = = = = = = =
# peli-ikkunan leveys näytöllä pikseleinä
pygame.display.init()
# Selvitetään peli-ikkunan koko näytöllä pikseleinä
D_Info = pygame.display.Info()
Wynaytto = int(1.0*D_Info.current_h)
Wxnaytto = Wynaytto
screen = pygame.display.set_mode((Wxnaytto, Wynaytto))
pygame.display.set_caption("Matka Kuuhun")
pygame.init()
clock = pygame.time.Clock()
random.seed()
Kamera = Cl_Kamera()
Maa = Cl_Maa()
Kuu = Cl_Kuu()
Aika = cl_Ajat()

loppu = False
MuistaVanhat = False

while not loppu:

    if not MuistaVanhat:
        screen.fill(Musta)  # pyyhitään peli-ikkuna tyhjäksi
    for event in pygame.event.get():
        # Lopetetaan peli sulkemalla ikkuna tai painamalla ESC
        if event.type == pygame.QUIT:
            loppu = True
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_F1:
                MuistaVanhat = not MuistaVanhat

            if event.key == pygame.K_ESCAPE:
                loppu = True
            if event.key == pygame.K_KP_PLUS:
                Kamera.zoom(0.9)
            if event.key == pygame.K_KP_MINUS:
                Kamera.zoom(1.1)
            if event.key == pygame.K_m:
                Kamera.focus = 'maa'
            if event.key == pygame.K_k:
                Kamera.focus = 'kuu'
            if event.key == pygame.K_KP1:
                Aika.Nopeuta()
            if event.key == pygame.K_KP0:
                Aika.Hidasta()
            if event.key == pygame.K_KP_ENTER:
                Aika.Reset()

    Maa.piirra()
    Kuu.piirra()

    Kuu.liiku()
    Kamera.liiku()

    pygame.display.flip()
    clock.tick(Aika.Nayttotaajuus)
pygame.quit()


Selityksiä ylläolevaan

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



()

Alus ja sen liikkuminen

# -*- coding: utf-8 -*-
import pygame
import random
from math import pi, cos, sin, sqrt
Musta = (0, 0, 0)  # Mustassa ei ole mitään valoa
Sin = (0, 0, 255)  # vain sinistä
Pun = (255, 0, 0)
Vihr = (0, 255, 0)
Kelt = (255, 255, 0)
Valk = (255, 255, 255)  # valkoisessa on kaikenväristä valoa
Harmaa = (128, 128, 128)
Keula = (0, 128, 255)
Pera = (255, 200, 0)
# Gravitaatiovakio
G = 6.674e-11
# Etäisyydellä r suunnassa kulma olevan pisteen
# x- ja y-koordinaatit
def rw_xy(r, kulma):
    return (r*cos(kulma), r*sin(kulma))

# Kahden pisteen välinen etäisyys
def dist(p1, p2):
    (x1, y1) = p1
    (x2, y2) = p2
    return sqrt((x2-x1)**2 + (y2-y1)**2)
# kiihtyvyys = Voima/massa, xy-tasossa
def Newton(F1, F2, F3, m):
    (f1x, f1y) = F1
    (f2x, f2y) = F2
    (f3x, f3y) = F3
    ax = (f1x+f2x+f3x)/m
    ay = (f1y+f2y+f3y)/m
    return (ax, ay)

# Kuun ja Maan vetovoimakentässä liikkuvan kappaleen
# kiihtyvyys. Riippu kappaleen paikasta, rakettimoottorin
# synnyttämästä voimasta ja kappaleen massasta
def kiihtyvyys(paikka, F, m):
    (x, y) = paikka
    Fmaa = Maa.vetovoima((x, y), m)
    Fkuu = Kuu.vetovoima((x, y), m)
    return Newton(Fmaa, Fkuu, F, m)
# paikan (x, y) ja nopeuden (vx, vy) derivaatat ajan suhteen
# (f1, f2, f3, f4)
# Ohjelma ei pysähdy, vaikka alus törmäisi Maahan tai Kuuhun.
# "Koukkaus" läheltä Maan ydintä aiheuttaa niin suuria kiihtyvyyksiä,
# että simulointi saattaa antaa epäfysikaalisia tuloksia.
# Siksi niistä varoitetaan.
def dfdt(yy, F, m):
    [x, y, vx, vy] = yy
    (ax, ay) = kiihtyvyys((x, y), F, m)
    if ax**2 + ay**2 > 300.0:
            Alus.warning((ax, ay))
    f1 = vx
    f2 = vy
    f3 = ax
    f4 = ay
    return [f1, f2, f3, f4]
# Paikan ja nopeuden muutokset ajassa Dt
# lasketaan Runge-Kutta algoritmilla
def rk(yy, m, F):
    yk1 = [0, 0, 0, 0]
    yk2 = [0, 0, 0, 0]
    yk3 = [0, 0, 0, 0]
    yy1 = [0, 0, 0, 0]
    kk1 = dfdt(yy, F, m)
    for i in range(4):
        yk1[i] = yy[i]+kk1[i]*Aika.Dt/2.0
    kk2 = dfdt(yk1, F, m)
    for i in range(4):
        yk2[i] = yy[i]+kk2[i]*Aika.Dt/2.0
    kk3 = dfdt(yk2, F, m)
    for i in range(4):
        yk3[i] = yy[i]+kk3[i]*Aika.Dt
    kk4 = dfdt(yk3, F, m)
    for i in range(4):
        yy1[i] = yy[i] + Aika.Dt/6.0*(kk1[i] +
                2.0*kk2[i] + 2.0*kk3[i] + kk4[i])
    return(yy1)

# Pelin ja simuloinnin ajoitus
# Nopeutus: Montako kertaa todellista nopeammin pelin
# halutaan etenevän
# Tscalea käytetään nopeutuksen sovittamisessa oikeaksi
class cl_Ajat:
    def __init__(self):
        self.Nopeutus = 2000.0
        self.Nayttotaajuus = 24
        self.Dt = 10.0
        self.Tscale = max(1, int(self.Nopeutus/self.Nayttotaajuus/self.Dt))

    def Hidasta(self):
        if self.Nopeutus > 4000:
            self.Nopeutus -= 4000
            self.Tscale = max(1, int(self.Nopeutus/self.Nayttotaajuus/self.Dt))
            self.aikaraportti(self.Tscale, self.Dt, self.Nayttotaajuus)
    def Nopeuta(self):
        self.Nopeutus += 4000
        self.Tscale = max(1, int(self.Nopeutus/self.Nayttotaajuus/self.Dt))
        self.aikaraportti(self.Tscale, self.Dt, self.Nayttotaajuus)
    def Reset(self):
        self.Nopeutus = 1
        self.Tscale = max(1, int(self.Nopeutus/self.Nayttotaajuus/self.Dt))
        self.aikaraportti(self.Tscale, self.Dt, self.Nayttotaajuus)
    def aikaraportti(self, Tsc, Dt, Nfrek):
        print('Aika kuluu ' + str(int(Tsc*Dt*Nfrek)) +
         ' kertaa todellista nopeammin')

# Pelimaailman kuvaaminen näytölle peli-ikkunaan.
# Kuvakulman ja zoomauksen säätelyä.
class Cl_Kamera:
    def __init__(self):
        self.focus = 'maa'
        self.Wx = 1.0e+9
        self.Wy = self.Wx
        self.Wx0 = 0.0
        self.Wy0 = 0.0
# Lasketaan mitä pikseliä näytöllä vastaa pelimaailman piste.
    def xy_naytolla(self, xy):
        scale = Wxnaytto/self.Wx
        (x, y) = xy
        xpikseli = int(Wxnaytto/2 + (x-self.Wx0)*scale)
        ypikseli = int(Wynaytto/2-(y-self.Wy0)*scale)
        return (xpikseli, ypikseli)
    def xy_skaalaus(self, w, h):
        scale = Wxnaytto/self.Wx
        xpikseli = int(w*scale)
        ypikseli = int(h*scale)
        return (xpikseli, ypikseli)
    def skaalaus(self, x):
        scale = Wxnaytto/self.Wx
        xpikseli = int(x*scale)
        return xpikseli
# Sidotaan näytön keskipiste joko Maahan tai Kuuhun
    def liiku(self):
        if self.focus == 'kuu':
            (self.Wx0, self.Wy0) = Kuu.paikka
        else:
            (self.Wx0, self.Wy0) = (0.0, 0.0)
    def zoom(self, z):
        self.Wx *= z
        self.Wy = self.Wx

# Maa. Käytetään todellisia fysikaalisia arvoja.
# Maa pysyy paikallaan pelimaailman keskipisteessä.
class Cl_Maa:
    def __init__(self):
        self.R = 6.37e+6
        self.M = 5.9737e+24
# Sininen ympyrä symboloi maata.
    def piirra(self):
        (xi, yi) = Kamera.xy_naytolla((0, 0))
        ri = Kamera.skaalaus(self.R)
        pygame.draw.circle(screen, Sin, (xi, yi), ri, 0)
# http://fi.wikipedia.org/wiki/Painovoima#Newtonin_painovoimalaki
    def vetovoima(self, xy, m):
        (x, y) = xy
        d = sqrt(x**2 + y**2)
        F = G*m*self.M/d**2
        Fx = -F*x/d
        Fy = -F*y/d
        return (Fx, Fy)
    def crash(self, pxy):
        return (dist(pxy, (0.0, 0.0)) < self.R)
# Kuu. Käytetään todellisia arvoja, paitsi massalle, jonka kasvatin
# moninkertaiseksi, että alus olisi helpompi ohjata Kuun kiertoradalle.
class Cl_Kuu:
    def __init__(self):
        self.R = 1.74e+6
#        self.M = 7.35e+22
        self.M = 18.0e+23  # moninkertainen massa
        self.distMaa = 3.844e+8
        self.kulma = 0.0  # Kuun paikka radallaan radiaaneina
        self.paikka = rw_xy(self.distMaa, self.kulma)
        self.w = 2*pi/(27.3*24.0*3600.0)  # kiertonopeus rad/s
# Kuuta symboloi keltainen ympyrä
    def piirra(self):
        (xi, yi) = Kamera.xy_naytolla(self.paikka)
        pygame.draw.circle(screen, Kelt, (xi, yi),
                           Kamera.skaalaus(self.R), 0)
# piirretään Kuun rata helpottamaan navigointia
        pygame.draw.circle(screen, Harmaa, Kamera.xy_naytolla((0, 0)),
                           Kamera.skaalaus(self.distMaa), 1)
# Kuu liikettä ei lasketa vetovoimalakien mukaan vaan se kiertää
# pakotettua ympyrärataa Maan ympäri.
    def liiku(self):
        self.kulma += self.w*Aika.Dt*Aika.Tscale
        self.paikka = rw_xy(self.distMaa, self.kulma)
    def vetovoima(self, xy, m):
        (x0, y0) = self.paikka
        (x1, y1) = xy
        dx = x1-x0
        dy = y1-y0
        d = sqrt(dx**2 + dy**2)
        F = G*m*self.M/d**2
        Fx = -F*dx/d
        Fy = -F*dy/d
        return (Fx, Fy)
    def crash(self, pxy):
        return (dist(pxy, self.paikka) < self.R)

# Kuualus
# Sijoitetaan Maan kiertoradalle ja lasketaan nopeus,
# jolla se pysyy kiertoradalla.
class Cl_Alus:
    def __init__(self):
        self.m = 10000.0
        self.F = (0.0, 0.0)  # ohjausrakettien aiheuttama voima
        self.paikka = (Maa.R + 10000.0e+3, 0.0)
        self.nopeus = (0.0, self.alkunopeus())

# Lasketaan sopiva nopeus yhtälöstä
# Keskipakoisvoima = Maan vetovoima
    def alkunopeus(self):
        (x, y) = self.paikka
        return sqrt(x*9.81*(Maa.R/x)**2)
# Piirretään alukseksi kaksivärinen jana,
# josta näkee aluksen asennon ja suunnan
# Huomaa, että näytöllä y kasvaa alaspäin.
    def piirra(self):
        (vx, vy) = self.nopeus
        v = sqrt(vx**2+vy**2)
        lx = int(vx/v*12.0)
        ly = int(vy/v*12.0)
        (xi, yi) = Kamera.xy_naytolla(self.paikka)
        pygame.draw.line(screen, Pera, (xi-lx, yi+ly), (xi, yi), 3)
        pygame.draw.line(screen, Keula, (xi, yi), (xi+lx, yi-ly), 3)

    def liiku(self):
        (x, y) = self.paikka
        (vx, vy) = self.nopeus
        for i in range(Aika.Tscale):
            [x, y, vx, vy] = rk([x, y, vx, vy], self.m, self.F)
        self.paikka = (x, y)
        self.nopeus = (vx, vy)
# Ohjaus tapahtuu näppäimistön nuolilla.
# z ja x näppäimillä boostattu ohjaus
# Ohjauksilla 'vasemmalle' ja 'oikealle' ei yleensä ole
# käyttöä
    def ohjaus(self, suunta):
        (vx, vy) = self.nopeus
        v = sqrt(vx**2+vy**2)
        F = 100.0
        if suunta == 'vas':  # Jarruta
            self.F = (-vx/v*F, -vy/v*F)
        if suunta == 'oik':  # Kiihdytä
            self.F = (vx/v*F, vy/v*F)
        if suunta == 'ylos':  # vasemmalle
            self.F = (-vy/v*F, vx/v*F)
        if suunta == 'alas':  # oikealle
            self.F = (vy/v*F, -vx/v*F)
        if suunta == 'z':  # Jarruta lujaa
            self.F = (-10.0*vx/v*F, -10.0*vy/v*F)
        if suunta == 'x':  # Kiihdytä lujaa
            self.F = (10.0*vx/v*F, 10.0*vy/v*F)
# Törmäys! Simulointi laskee ehkä väärin tämän tilanteen.
    def warning(self, a):
        print('Törmäys?')
        screen.fill(Pun)
        pygame.time.wait(10)

##= = = = = = = = = = = = = = = = = = = = = = =
#
# Pääohjelma alkaa tästä
# = = = = = = = = = = = = = = = = = = = = = = =
# peli-ikkunan leveys näytöllä pikseleinä
pygame.display.init()
# Selvitetään peli-ikkunan koko näytöllä pikseleinä
D_Info = pygame.display.Info()
Wynaytto = int(1.0*D_Info.current_h)
Wxnaytto = Wynaytto
screen = pygame.display.set_mode((Wxnaytto, Wynaytto))
pygame.display.set_caption("Matka Kuuhun")
pygame.init()
clock = pygame.time.Clock()
random.seed()
Kamera = Cl_Kamera()
Maa = Cl_Maa()
Kuu = Cl_Kuu()
Aika = cl_Ajat()

Alus = Cl_Alus()

loppu = False
MuistaVanhat = False

while not loppu:

    Alus.F = (0.0, 0.0)

    if not MuistaVanhat:
        screen.fill(Musta)  # pyyhitään peli-ikkuna tyhjäksi
    for event in pygame.event.get():
        # Lopetetaan peli sulkemalla ikkuna tai painamalla ESC
        if event.type == pygame.QUIT:
            loppu = True
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_F1:
                MuistaVanhat = not MuistaVanhat

            if event.key == pygame.K_ESCAPE:
                loppu = True
            if event.key == pygame.K_KP_PLUS:
                Kamera.zoom(0.9)
            if event.key == pygame.K_KP_MINUS:
                Kamera.zoom(1.1)
            if event.key == pygame.K_m:
                Kamera.focus = 'maa'
            if event.key == pygame.K_k:
                Kamera.focus = 'kuu'
            if event.key == pygame.K_KP1:
                Aika.Nopeuta()
            if event.key == pygame.K_KP0:
                Aika.Hidasta()
            if event.key == pygame.K_KP_ENTER:
                Aika.Reset()

    pressed = pygame.key.get_pressed()
    if pressed[pygame.K_RIGHT]:
        Alus.ohjaus('oik')
    if pressed[pygame.K_LEFT]:
        Alus.ohjaus('vas')
    if pressed[pygame.K_UP]:
        Alus.ohjaus('ylos')
    if pressed[pygame.K_DOWN]:
        Alus.ohjaus('alas')
    if pressed[pygame.K_z]:
        Alus.ohjaus('z')
    if pressed[pygame.K_x]:
        Alus.ohjaus('x')

    Maa.piirra()
    Kuu.piirra()

    Alus.piirra()

    Kuu.liiku()
    Kamera.liiku()

    Alus.liiku()
    if Maa.crash(Alus.paikka) or Kuu.crash(Alus.paikka):
        print('!!! Törmäys !!!')
        screen.fill(Pun)
        pygame.time.wait(10)
#        loppu = True

    pygame.display.flip()
    clock.tick(Aika.Nayttotaajuus)
pygame.quit()


Selityksiä ylläolevaan

 #  Kuun ja Maan vetovoimakentässä liikkuvan kappaleen
 #  kiihtyvyys. Riippu kappaleen paikasta, rakettimoottorin
 #  synnyttämästä voimasta ja kappaleen massasta
def kiihtyvyys(paikka, F, m):
    (x, y) = paikka
    Fmaa = Maa.vetovoima((x, y), m)
    Fkuu = Kuu.vetovoima((x, y), m)
    return Newton(Fmaa, Fkuu, F, m)


 #  paikan (x, y) ja nopeuden (vx, vy) derivaatat ajan suhteen
 #  (f1, f2, f3, f4)
 #  Ohjelma ei pysähdy, vaikka alus törmäisi Maahan tai Kuuhun.
 #  "Koukkaus" läheltä Maan ydintä aiheuttaa niin suuria kiihtyvyyksiä,
 #  että simulointi saattaa antaa epäfysikaalisia tuloksia.
 #  Siksi niistä varoitetaan.
def dfdt(yy, F, m):
    [x, y, vx, vy] = yy
    (ax, ay) = kiihtyvyys((x, y), F, m)
    if ax**2 + ay**2 > 300.0:
            Alus.warning((ax, ay))
    f1 = vx
    f2 = vy
    f3 = ax
    f4 = ay
    return [f1, f2, f3, f4]


 #  Paikan ja nopeuden muutokset ajassa Dt
 #  lasketaan Runge-Kutta algoritmilla
def rk(yy, m, F):
    yk1 = [0, 0, 0, 0]
    yk2 = [0, 0, 0, 0]
    yk3 = [0, 0, 0, 0]
    yy1 = [0, 0, 0, 0]
    kk1 = dfdt(yy, F, m)
    for i in range(4):
        yk1[i] = yy[i]+kk1[i]*Aika.Dt/2.0
    kk2 = dfdt(yk1, F, m)
    for i in range(4):
        yk2[i] = yy[i]+kk2[i]*Aika.Dt/2.0
    kk3 = dfdt(yk2, F, m)
    for i in range(4):
        yk3[i] = yy[i]+kk3[i]*Aika.Dt
    kk4 = dfdt(yk3, F, m)
    for i in range(4):
        yy1[i] = yy[i] + Aika.Dt/6.0*(kk1[i] +
                2.0*kk2[i] + 2.0*kk3[i] + kk4[i])
    return(yy1)


Mörssärin kuulan radan laskin Eulerin menetelmällä, mutta kuualuksen liikeiden laskemiseen käytän Runge-Kutta menetelmää.

 #  Pelin ja simuloinnin ajoitus
 #  Nopeutus: Montako kertaa todellista nopeammin pelin
 #  halutaan etenevän
 #  Tscalea käytetään nopeutuksen sovittamisessa oikeaksi
class cl_Ajat:
    def __init__(self):
        self.Nopeutus = 2000.0
        self.Nayttotaajuus = 24
        self.Dt = 10.0
        self.Tscale = max(1, int(self.Nopeutus/self.Nayttotaajuus/self.Dt))


Dt on integrointiaskel Runge-Kutta algoritmissa. Mitä isompi Dt, sitä epätarkempaa laskenta, mutta sitä vähemmän peli kuormittaa tietokonetta.
Alus = Cl_Alus()

    Alus.F = (0.0, 0.0)

    Alus.piirra()

    Alus.liiku()

    if Maa.crash(Alus.paikka) or Kuu.crash(Alus.paikka):
        print('!!! Törmäys !!!')
        screen.fill(Pun)
        pygame.time.wait(10)
 #         loppu = True


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



()

Ohjauksen apuvälineitä

# -*- coding: utf-8 -*-
import pygame
import random
from math import pi, cos, sin, sqrt
Musta = (0, 0, 0)  # Mustassa ei ole mitään valoa
Sin = (0, 0, 255)  # vain sinistä
Pun = (255, 0, 0)
Vihr = (0, 255, 0)
Kelt = (255, 255, 0)
Valk = (255, 255, 255)  # valkoisessa on kaikenväristä valoa
Harmaa = (128, 128, 128)
Keula = (0, 128, 255)
Pera = (255, 200, 0)
# Gravitaatiovakio
G = 6.674e-11
# Etäisyydellä r suunnassa kulma olevan pisteen
# x- ja y-koordinaatit
def rw_xy(r, kulma):
    return (r*cos(kulma), r*sin(kulma))

# Kahden pisteen välinen etäisyys
def dist(p1, p2):
    (x1, y1) = p1
    (x2, y2) = p2
    return sqrt((x2-x1)**2 + (y2-y1)**2)
# kiihtyvyys = Voima/massa, xy-tasossa
def Newton(F1, F2, F3, m):
    (f1x, f1y) = F1
    (f2x, f2y) = F2
    (f3x, f3y) = F3
    ax = (f1x+f2x+f3x)/m
    ay = (f1y+f2y+f3y)/m
    return (ax, ay)

# Piirretään voiman suuntaa ja suuruutta kuvastava jana.
def nuoli(p0, Voima):
    (ix0, iy0) = p0
    (Fx, Fy) = Voima
    F = sqrt(Fx**2 + Fy**2)
# "ylipitkä" jana katkaistaan ja piirretään punaisella.
    if F > 600:
        color = Pun
        Fx = Fx/F*600.0
        Fy = Fy/F*600.0
    else:
        color = Vihr
    Fx = Fx*0.7
    Fy = Fy*0.7
# Huomaa, että näytöllä y kasvaa alaspäin, siksi -Fy.
    p1 = (ix0+Fx, iy0-Fy)
    pygame.draw.line(screen, color, p0, p1, 2)

# Kuun ja Maan vetovoimakentässä liikkuvan kappaleen
# kiihtyvyys. Riippu kappaleen paikasta, rakettimoottorin
# synnyttämästä voimasta ja kappaleen massasta
def kiihtyvyys(paikka, F, m):
    (x, y) = paikka
    Fmaa = Maa.vetovoima((x, y), m)
    Fkuu = Kuu.vetovoima((x, y), m)
    return Newton(Fmaa, Fkuu, F, m)
# paikan (x, y) ja nopeuden (vx, vy) derivaatat ajan suhteen
# (f1, f2, f3, f4)
# Ohjelma ei pysähdy, vaikka alus törmäisi Maahan tai Kuuhun.
# "Koukkaus" läheltä Maan ydintä aiheuttaa niin suuria kiihtyvyyksiä,
# että simulointi saattaa antaa epäfysikaalisia tuloksia.
# Siksi niistä varoitetaan.
def dfdt(yy, F, m):
    [x, y, vx, vy] = yy
    (ax, ay) = kiihtyvyys((x, y), F, m)
    if ax**2 + ay**2 > 300.0:
            Alus.warning((ax, ay))
    f1 = vx
    f2 = vy
    f3 = ax
    f4 = ay
    return [f1, f2, f3, f4]
# Paikan ja nopeuden muutokset ajassa Dt
# lasketaan Runge-Kutta algoritmilla
def rk(yy, m, F):
    yk1 = [0, 0, 0, 0]
    yk2 = [0, 0, 0, 0]
    yk3 = [0, 0, 0, 0]
    yy1 = [0, 0, 0, 0]
    kk1 = dfdt(yy, F, m)
    for i in range(4):
        yk1[i] = yy[i]+kk1[i]*Aika.Dt/2.0
    kk2 = dfdt(yk1, F, m)
    for i in range(4):
        yk2[i] = yy[i]+kk2[i]*Aika.Dt/2.0
    kk3 = dfdt(yk2, F, m)
    for i in range(4):
        yk3[i] = yy[i]+kk3[i]*Aika.Dt
    kk4 = dfdt(yk3, F, m)
    for i in range(4):
        yy1[i] = yy[i] + Aika.Dt/6.0*(kk1[i] +
                2.0*kk2[i] + 2.0*kk3[i] + kk4[i])
    return(yy1)

# Pelin ja simuloinnin ajoitus
# Nopeutus: Montako kertaa todellista nopeammin pelin
# halutaan etenevän
# Tscalea käytetään nopeutuksen sovittamisessa oikeaksi
class cl_Ajat:
    def __init__(self):
        self.Nopeutus = 2000.0
        self.Nayttotaajuus = 24
        self.Dt = 10.0
        self.Tscale = max(1, int(self.Nopeutus/self.Nayttotaajuus/self.Dt))

    def Hidasta(self):
        if self.Nopeutus > 4000:
            self.Nopeutus -= 4000
            self.Tscale = max(1, int(self.Nopeutus/self.Nayttotaajuus/self.Dt))
            self.aikaraportti(self.Tscale, self.Dt, self.Nayttotaajuus)
    def Nopeuta(self):
        self.Nopeutus += 4000
        self.Tscale = max(1, int(self.Nopeutus/self.Nayttotaajuus/self.Dt))
        self.aikaraportti(self.Tscale, self.Dt, self.Nayttotaajuus)
    def Reset(self):
        self.Nopeutus = 1
        self.Tscale = max(1, int(self.Nopeutus/self.Nayttotaajuus/self.Dt))
        self.aikaraportti(self.Tscale, self.Dt, self.Nayttotaajuus)
    def aikaraportti(self, Tsc, Dt, Nfrek):
        print('Aika kuluu ' + str(int(Tsc*Dt*Nfrek)) +
         ' kertaa todellista nopeammin')

# Pelimaailman kuvaaminen näytölle peli-ikkunaan.
# Kuvakulman ja zoomauksen säätelyä.
class Cl_Kamera:
    def __init__(self):
        self.focus = 'maa'
        self.Wx = 1.0e+9
        self.Wy = self.Wx
        self.Wx0 = 0.0
        self.Wy0 = 0.0
# Lasketaan mitä pikseliä näytöllä vastaa pelimaailman piste.
    def xy_naytolla(self, xy):
        scale = Wxnaytto/self.Wx
        (x, y) = xy
        xpikseli = int(Wxnaytto/2 + (x-self.Wx0)*scale)
        ypikseli = int(Wynaytto/2-(y-self.Wy0)*scale)
        return (xpikseli, ypikseli)
    def xy_skaalaus(self, w, h):
        scale = Wxnaytto/self.Wx
        xpikseli = int(w*scale)
        ypikseli = int(h*scale)
        return (xpikseli, ypikseli)
    def skaalaus(self, x):
        scale = Wxnaytto/self.Wx
        xpikseli = int(x*scale)
        return xpikseli
# Sidotaan näytön keskipiste joko Maahan tai Kuuhun
    def liiku(self):
        if self.focus == 'kuu':
            (self.Wx0, self.Wy0) = Kuu.paikka
        else:
            (self.Wx0, self.Wy0) = (0.0, 0.0)
    def zoom(self, z):
        self.Wx *= z
        self.Wy = self.Wx

# Kiintotahtiä taustaksi ja kiintopisteiksi
class cl_Tahdet:
    def __init__(self):
        self.tt = []
        for i in range(1000):
            self.tt.append((random.uniform(-8.0e+8, 8.0e+8),
                       random.uniform(-8.0e+8, 8.0e+8)))
    def piirra(self):
        for tahti in self.tt:
            p = Kamera.xy_naytolla(tahti)
            color = (random.randint(100, 255),
                     random.randint(100, 255),
                     random.randint(100, 255))
            # jana, jonka loppupiste ja alkupiste ovat samoja,
            # piirtyy näytölle pisteenä
            pygame.draw.line(screen, color, p, p, 1)

# Maa. Käytetään todellisia fysikaalisia arvoja.
# Maa pysyy paikallaan pelimaailman keskipisteessä.
class Cl_Maa:
    def __init__(self):
        self.R = 6.37e+6
        self.M = 5.9737e+24
# Sininen ympyrä symboloi maata.
    def piirra(self):
        (xi, yi) = Kamera.xy_naytolla((0, 0))
        ri = Kamera.skaalaus(self.R)
        pygame.draw.circle(screen, Sin, (xi, yi), ri, 0)
# http://fi.wikipedia.org/wiki/Painovoima#Newtonin_painovoimalaki
    def vetovoima(self, xy, m):
        (x, y) = xy
        d = sqrt(x**2 + y**2)
        F = G*m*self.M/d**2
        Fx = -F*x/d
        Fy = -F*y/d
        return (Fx, Fy)
    def crash(self, pxy):
        return (dist(pxy, (0.0, 0.0)) < self.R)
# Kuu. Käytetään todellisia arvoja, paitsi massalle, jonka kasvatin
# moninkertaiseksi, että alus olisi helpompi ohjata Kuun kiertoradalle.
class Cl_Kuu:
    def __init__(self):
        self.R = 1.74e+6
#        self.M = 7.35e+22
        self.M = 18.0e+23  # moninkertainen massa
        self.distMaa = 3.844e+8
        self.kulma = 0.0  # Kuun paikka radallaan radiaaneina
        self.paikka = rw_xy(self.distMaa, self.kulma)
        self.w = 2*pi/(27.3*24.0*3600.0)  # kiertonopeus rad/s
# Kuuta symboloi keltainen ympyrä
    def piirra(self):
        (xi, yi) = Kamera.xy_naytolla(self.paikka)
        pygame.draw.circle(screen, Kelt, (xi, yi),
                           Kamera.skaalaus(self.R), 0)
# piirretään Kuun rata helpottamaan navigointia
        pygame.draw.circle(screen, Harmaa, Kamera.xy_naytolla((0, 0)),
                           Kamera.skaalaus(self.distMaa), 1)
# Kuu liikettä ei lasketa vetovoimalakien mukaan vaan se kiertää
# pakotettua ympyrärataa Maan ympäri.
    def liiku(self):
        self.kulma += self.w*Aika.Dt*Aika.Tscale
        self.paikka = rw_xy(self.distMaa, self.kulma)
    def vetovoima(self, xy, m):
        (x0, y0) = self.paikka
        (x1, y1) = xy
        dx = x1-x0
        dy = y1-y0
        d = sqrt(dx**2 + dy**2)
        F = G*m*self.M/d**2
        Fx = -F*dx/d
        Fy = -F*dy/d
        return (Fx, Fy)
    def crash(self, pxy):
        return (dist(pxy, self.paikka) < self.R)

# Kuualus
# Sijoitetaan Maan kiertoradalle ja lasketaan nopeus,
# jolla se pysyy kiertoradalla.
class Cl_Alus:
    def __init__(self):
        self.m = 10000.0
        self.F = (0.0, 0.0)  # ohjausrakettien aiheuttama voima
        self.paikka = (Maa.R + 10000.0e+3, 0.0)
        self.nopeus = (0.0, self.alkunopeus())

        self.hanta = [self.paikka for i in range(201)]
        self.ptr = 0

# Lasketaan sopiva nopeus yhtälöstä
# Keskipakoisvoima = Maan vetovoima
    def alkunopeus(self):
        (x, y) = self.paikka
        return sqrt(x*9.81*(Maa.R/x)**2)
# Piirretään alukseksi kaksivärinen jana,
# josta näkee aluksen asennon ja suunnan
# Huomaa, että näytöllä y kasvaa alaspäin.
    def piirra(self):
        (vx, vy) = self.nopeus
        v = sqrt(vx**2+vy**2)
        lx = int(vx/v*12.0)
        ly = int(vy/v*12.0)
        (xi, yi) = Kamera.xy_naytolla(self.paikka)
        pygame.draw.line(screen, Pera, (xi-lx, yi+ly), (xi, yi), 3)
        pygame.draw.line(screen, Keula, (xi, yi), (xi+lx, yi-ly), 3)

# Ohjaamisen helpottamiseksi piirretään alukseen kohdistuvia voimia
# kuvaavat nuolet ja "häntä", joka auttaa hahmottamaan aluksen liikettä
        if not MuistaVanhat and PiirraVoimat:
            self.nuolet(self.paikka, self.F, self.m)
            self.piirraHanta()
    def nuolet(self, p0, F, m):
            Fmaa = Maa.vetovoima(p0, m)
            Fkuu = Kuu.vetovoima(p0, m)
            ip0 = Kamera.xy_naytolla(p0)
            nuoli(ip0, F)
            nuoli(ip0, Fmaa)
            nuoli(ip0, Fkuu)
# Hännän piirtämiseksi talletaan aluksen kulloinenkin paikka listaan
# ja piirretään piste kuhunkin listan pisteeseen.
    def piirraHanta(self):
        for p in self.hanta:
            ip = Kamera.xy_naytolla(p)
            pygame.draw.line(screen, Pera, ip, ip, 1)
        self.hanta[self.ptr] = self.paikka
        self.ptr += 1
        if self.ptr > 200:
            self.ptr = 0

    def liiku(self):
        (x, y) = self.paikka
        (vx, vy) = self.nopeus
        for i in range(Aika.Tscale):
            [x, y, vx, vy] = rk([x, y, vx, vy], self.m, self.F)
        self.paikka = (x, y)
        self.nopeus = (vx, vy)
# Ohjaus tapahtuu näppäimistön nuolilla.
# z ja x näppäimillä boostattu ohjaus
# Ohjauksilla 'vasemmalle' ja 'oikealle' ei yleensä ole
# käyttöä
    def ohjaus(self, suunta):
        (vx, vy) = self.nopeus
        v = sqrt(vx**2+vy**2)
        F = 100.0
        if suunta == 'vas':  # Jarruta
            self.F = (-vx/v*F, -vy/v*F)
        if suunta == 'oik':  # Kiihdytä
            self.F = (vx/v*F, vy/v*F)
        if suunta == 'ylos':  # vasemmalle
            self.F = (-vy/v*F, vx/v*F)
        if suunta == 'alas':  # oikealle
            self.F = (vy/v*F, -vx/v*F)
        if suunta == 'z':  # Jarruta lujaa
            self.F = (-10.0*vx/v*F, -10.0*vy/v*F)
        if suunta == 'x':  # Kiihdytä lujaa
            self.F = (10.0*vx/v*F, 10.0*vy/v*F)
# Törmäys! Simulointi laskee ehkä väärin tämän tilanteen.
    def warning(self, a):
        print('Törmäys?')
        screen.fill(Pun)
        pygame.time.wait(10)

##= = = = = = = = = = = = = = = = = = = = = = =
#
# Pääohjelma alkaa tästä
# = = = = = = = = = = = = = = = = = = = = = = =
# peli-ikkunan leveys näytöllä pikseleinä
pygame.display.init()
# Selvitetään peli-ikkunan koko näytöllä pikseleinä
D_Info = pygame.display.Info()
Wynaytto = int(1.0*D_Info.current_h)
Wxnaytto = Wynaytto
screen = pygame.display.set_mode((Wxnaytto, Wynaytto))
pygame.display.set_caption("Matka Kuuhun")
pygame.init()
clock = pygame.time.Clock()
random.seed()
Kamera = Cl_Kamera()
Maa = Cl_Maa()
Kuu = Cl_Kuu()
Aika = cl_Ajat()

Alus = Cl_Alus()

tahdet = cl_Tahdet()

loppu = False
MuistaVanhat = False

PiirraVoimat = True

while not loppu:

    Alus.F = (0.0, 0.0)

    if not MuistaVanhat:
        screen.fill(Musta)  # pyyhitään peli-ikkuna tyhjäksi
    for event in pygame.event.get():
        # Lopetetaan peli sulkemalla ikkuna tai painamalla ESC
        if event.type == pygame.QUIT:
            loppu = True
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_F1:
                MuistaVanhat = not MuistaVanhat

            if event.key == pygame.K_F2:
                PiirraVoimat = not PiirraVoimat

            if event.key == pygame.K_ESCAPE:
                loppu = True
            if event.key == pygame.K_KP_PLUS:
                Kamera.zoom(0.9)
            if event.key == pygame.K_KP_MINUS:
                Kamera.zoom(1.1)
            if event.key == pygame.K_m:
                Kamera.focus = 'maa'
            if event.key == pygame.K_k:
                Kamera.focus = 'kuu'
            if event.key == pygame.K_KP1:
                Aika.Nopeuta()
            if event.key == pygame.K_KP0:
                Aika.Hidasta()
            if event.key == pygame.K_KP_ENTER:
                Aika.Reset()

    pressed = pygame.key.get_pressed()
    if pressed[pygame.K_RIGHT]:
        Alus.ohjaus('oik')
    if pressed[pygame.K_LEFT]:
        Alus.ohjaus('vas')
    if pressed[pygame.K_UP]:
        Alus.ohjaus('ylos')
    if pressed[pygame.K_DOWN]:
        Alus.ohjaus('alas')
    if pressed[pygame.K_z]:
        Alus.ohjaus('z')
    if pressed[pygame.K_x]:
        Alus.ohjaus('x')

    Maa.piirra()
    Kuu.piirra()

    Alus.piirra()

    tahdet.piirra()

    Kuu.liiku()
    Kamera.liiku()

    Alus.liiku()
    if Maa.crash(Alus.paikka) or Kuu.crash(Alus.paikka):
        print('!!! Törmäys !!!')
        screen.fill(Pun)
        pygame.time.wait(10)
#        loppu = True

    pygame.display.flip()
    clock.tick(Aika.Nayttotaajuus)
pygame.quit()


Selityksiä ylläolevaan

 #  Piirretään voiman suuntaa ja suuruutta kuvastava jana.
def nuoli(p0, Voima):
    (ix0, iy0) = p0
    (Fx, Fy) = Voima
    F = sqrt(Fx**2 + Fy**2)
 #  "ylipitkä" jana katkaistaan ja piirretään punaisella.
    if F > 600:
        color = Pun
        Fx = Fx/F*600.0
        Fy = Fy/F*600.0
    else:
        color = Vihr
    Fx = Fx*0.7
    Fy = Fy*0.7
 #  Huomaa, että näytöllä y kasvaa alaspäin, siksi -Fy.
    p1 = (ix0+Fx, iy0-Fy)
    pygame.draw.line(screen, color, p0, p1, 2)


Piirretään voiman suuntaa ja suuruutta kuvastava 'nuoli' lähtemään pisteestä p0. Lennon aikana alukseen vaikuttaa sekä hyvin suuria, että hyvin pieniä voimia. Voimia ei ole skaalattu, joten yksi pikseli vastaa yhtä Newtonia, mikä on sattumalta riittävän hyvä skaalaus. Tärkeintä on nähdä Maan ja Kuun vetovoimat Kuuta lähestyttäessä.

 #  Kiintotahtiä taustaksi ja kiintopisteiksi
class cl_Tahdet:
    def __init__(self):
        self.tt = []
        for i in range(1000):
            self.tt.append((random.uniform(-8.0e+8, 8.0e+8),
                       random.uniform(-8.0e+8, 8.0e+8)))

    def piirra(self):
        for tahti in self.tt:
            p = Kamera.xy_naytolla(tahti)
            color = (random.randint(100, 255),
                     random.randint(100, 255),
                     random.randint(100, 255))
            #  jana, jonka loppupiste ja alkupiste ovat samoja,
            #  piirtyy näytölle pisteenä
            pygame.draw.line(screen, color, p, p, 1)


Peliin tulee aidompi tunnelma, kun laittaa taustaksi kiintotähtiä. Kun fokusoi kameran Kuuhun, taustan kiintotähdet tuovat liikkeen tuntua.

Annan tähden väriin satunnaisesti punaista, sinistä ja vihreää. Se saa tähdet tuikkimaan. Todellisuudessa tähtien tuikkeen aiheuttaa ilmakehän väreily, joten avaruudesta katsoen tähdet eivät tuiki. Voit poistaa tuikkeen sijoittamalla väriksi color = Valk.

        self.hanta = [self.paikka for i in range(201)]
        self.ptr = 0


tahdet = cl_Tahdet()


PiirraVoimat = True


            if event.key == pygame.K_F2:
                PiirraVoimat = not PiirraVoimat

    tahdet.piirra()


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






( )

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 ja huojuu


(Sun Mar 11 10:05:35 2018 )

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.



()

-

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




(Sun Mar 11 10:05:35 2018 )

synkronointi.py

Eri säikeissä toimivien ohjelmien synkronointi


()

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




(Sun Mar 11 10:05:35 2018 )

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.



()

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





( )

Lisäharjoituksia

Innostunut harrastaja — ehkä itsekin innostun — voi täydentää pelejä ja tehdä uusia.


Mörssäri pelissä maalin voisi laittaa sahaamaan edestakaisin maali-alueella. Ehkä tuulen suuntakin voisi vaihdella, mutta siten järkevästi, että pelaaminen ei mene sattuman kaupaksi.

Maan pintaan voisi laittaa kerroksen pensaikkoa tms., johon tulisi kuoppia kuulien osumista.

Kuulat voisi laittaa räjähtämään samalla periaatteella kuin maalinkin. Silloin ehkä voisi olla perusteltua kerätä roskat, eli deletetoida sirpaleet, ellei siten halua jättää niitä lojumaan maahan.

Pelin käynnistyttyä voisi aluksi näyttää peliohjeet tms. Hyttyspelissä voisi viimeisen hyttysen hiljennyttyä soittaa loppufanfaarin.

Mörssärin kuula ja KuuAlus liikkuvat fysiikan lakeja noudattaen. Sopiva harjoitus voisi olla selvittää, miten ne on toteutettu ohjelmassa. Samalla voisi selvittää, miten eri tavoin geometriaa on hyödynnetty ohjelmassa.

Haaveeni on ehtiä demonstroida musiikin tuottamista ohjelmallisesti pythonilla. Aiempi kokeiluni musiikin tuottamisesta ohjelmallisesti ei tuottanut järin mukavaa kuunneltavaa, mutta opetti minulle koulussa hämäräksi jääneitä musiikin perusteita. Aloitan ehkä perehtymällä, mitä jythonmusic.org tarjoaa.



(Toukokuu 2015 )

Miten ylläolevat dokumentit tuotettiin

Ohjelman vaiheittaisesta kehittelystä ei voi tehdä yllä olevan kaltaisia dokumentteja käsityönä. Virheiltä ei voisi välttyä. Kerran dokumentoitua ohjelmaa ei liioin voisi parannella eikä täydentää, koska dokumenttien korjaaminen vastaamaan muutoksia olisi aivan liian työlästä ja virhealtista.

Onneksi pythonilla oli melko helppo automatisoida vaiheittaisen kehittelyn dokumentti.


Automatisoinnin toteutin lisäämällä ohjelmieni kehitysversioon kommentteja, jotka kertovat ohjelman kehitysvaiheen ja mitä selityksiä kuhunkin vaiheeseen kuuluu.

Kunkin vaiheen otsikot ovat ohjelmatiedoston alussa perä perää. # shd 0-0:n tarkoittaa, että seuraava kommentti on vaiheen 0 otsikko

        # -*- coding: utf-8 -*-
        """
        Rakennetaan vaihe vaiheelta videopeli, jossa jahdataan hyttysiä.
        Hyttyslätkää ohjataan näppäimillä z,x, nuoli ylös ja nuoli alas.
        Ohjelma pysäytetään joko esc-näppäimellä tai sulkemalla peli-ikkuna.
        """
        # shd 0-0:m

        '''
        Vaihe 0: Peliohjelman perusrakenne
        '''

        # shd 1-1:m
        '''
        Vaihe 1: Luodaan lätkä hyttysten pyydystämiseen
        '''

        # shd 2-2:m
        '''
        Vaihe 2: Luodaan hyttynen
        '''

        # shd 3-3:m
        '''
        Vaihe 3: Laitetaan hyttynen liikkeelle
        '''
    

Esimerkki monimutkaisimmasta kohdasta ohjelmaa. Tämä tuottaa vaiheisiin 3 ja 4 sopivan koodin.

        # txt 3-3:m
        '''
        Korvataan ylläoleva monipuolisemmalla liikkeellä.
        '''
        # cod 3-3:v 4-100:z
        def liiku(self):
        # cod 4-4:v 5-100:z
        self.isku() # Onko lätkä osunut?
        if self.elossa:
        # cod 3-3:v 4-100:z
        self.nopeus = lenna(self.paikka, self.nopeus)
        # cod 4-4:v 5-100:z
        else:
        self.nopeus = self.putoa()
        # cod 3-3:v 4-100:z
        # Lasketaan uusi paikka eli siirrytään matka nopeus kertaa aika
        self.paikka = dfdt(self.paikka, self.nopeus)
        # cod 4-4:v 5-100:z

        # Jos Latka on lähellä tätä hyttystä sekä x- että y-suunnassa
        # lätkä osuu, hyttynen kuolee ja muuttuu punaiseksi.
        def isku(self):
        if (etaisyys(self.paikka, Latka.paikka) < Latka.koko and
        Latka.liikkuu):
        self.elossa = False
        self.color = Pun
        # txt 4-4:m
        '''
        Kuolleena putoavan hyttysen liike:
        Nopeus x-suuntaan on pieni satunnaisluku, eli hyttynen leijailee alas
        lähes pystysuoraan.
        Ellei hyttynen jo ole maassa, sillä on pieni satunnainen nopeus alaspäin
        (muista, että y kasvaa epäloogisesti alaspäin).
        Jos hyttynen on jo maassa, y-suuntainen nopeus asetetaan nollaksi
        '''
        # cod 4-4:v 5-100:z
        def putoa(self):
        vx = random.uniform(-10, 10)
        if self.paikka[1] < Wy-self.koko:
        vy = random.uniform(20, 70)
        else:
        vx = 0
        vy = 0
        return (vx, vy)
    

Ohjelman kehitysversio on paljoista kommenteista huolimatta luettavissa ja ennen kaikkea suoritettavissa, niin että voin aina tarkistaa, toimiiko se. Koska voin yhdellä komennolla tuottaa dokumentin lisäksi myös kutakin vaihetta vastaavan suoritettavan ohjelman, voin varmistaa, että dokumentissa esitelty ohjelma toimii.

Tein ohjelman yritys-ja-erehdys menetelmällä, koska vain kokeilemalla alan ymmärtää, mitä ohjelman pitää tehdä ja miten sen voi toteuttaa. Nyt osaisin tehdä paljon elegantimman ohjelman. Sitä odotellessa laitan näytille tämänhetkisen prototyypin . Se tuottaa xml-koodia, koska se sopii web-kehitysympäristööni, mutta pienellä vaivalla sen saanee tuottamaan html-koodia. Koodilaatikoiden css-tyylinä minulla on seuraava

        pre{
        background-color: #F8FCFF;
        margin-left: 2em; margin-right: 2em;
        margin-top: 0em; margin-bottom: 0em;
        padding-left: 1em; padding-right: 1em;
        padding-top: 0.5em; padding-bottom: 0.5em;
        font-family: 'Lucida Console', 'Courier New', monospace;
        font-weight: bold; white-space: pre-wrap;
        }
    

Ohjelmoimalla voi tehdä mahdottoman — ei helpoksi, mutta — mahdolliseksi. Joskus homma on mahdollista tehdä käsin — ehkä jopa nopeammin kuin laatimalla sitä varten ohjelman. Ohjelmoinnin hyöty tulee siitä, että saman homman voi toistaa napin painalluksella. Usein homma ei ole lainkaan mahdollista ilman ohjelmointia.


(Sun Mar 11 09:58:51 2018 )

Python2html.py

Tällä ohjelmalla on tuotettu tämän sivuston python-ohjelmien esittelyt.



()

Yhdestä python-ohjelmasta eri ohjelmaversioita ja niiden dokumentaatioita

# -*- coding: utf-8 -*-
# = = = = = = = = = = = = = = = = = = = = = = = = = =
# import sys
# print "This is the name of the script: ", sys.argv[0]
# print "Number of arguments: ", len(sys.argv)
# print "The arguments are: " , str(sys.argv)
# = = = = = = = = = = = = = = = = = = = = = = = = = =
import re
import sys
import ast
import time
import random

def tagline(tag, vari, vaihe, ln):
    if tag == 'shd':
        unique_id = projName + str(vaihe) + str(ln)
        tglin = '\n<section>\n<title anchor = "' + unique_id + '">'
    else:
        tglin = '\n<' + tag + ' mod = "' + vari + '">\n'
    return tglin
def tagline_s(tag, vari):
    tglin = '\n<' + tag + ' mod = "' + vari + '">\n'
    return tglin

def tarkistaVaihe_s(line, tag, vaihe):
    if not tag.group(1) == 'shd':
        attrs = re.findall(r"([0-9]*)-([0-9]*):([a-z])", line)
        for attr in attrs:
            if int(attr[0]) <= vaihe <= int(attr[1]):
                if attr[2] == 'z':
                    txt = False
                    code = True
                    vari = 'm'
                elif attr[2] == 'p':
                    txt = True
                    code = False
                    vari = 'p'
                else:
                    txt = True
                    code = True
                    vari = str(attr[2])
                tglin = tagline_s(tag.group(1), vari)
                return (txt, code, tglin)
    return(False, False, '')

def tarkistaVaihe_c(line, tag, vaihe, ln):
    if not tag.group(1) == 'txt':
        attrs = re.findall(r"([0-9]*)-([0-9]*):([a-z])", line)
        for attr in attrs:
            if int(attr[0]) <= vaihe <= int(attr[1]):
                if attr[2] == 'z':
                    code = True
                    vari = 'm'
                elif attr[2] == 'p':
                    code = True
                    vari = 'p'
                else:
                    code = True
                    vari = str(attr[2])
                tglin = tagline(tag.group(1), vari, vaihe, ln)
                return (code, tglin)
    return(False, '')

def tarkistaVaihe_py(line, tag, vaihe, ln):
    if tag.group(1) == 'cod':
        attrs = re.findall(r"([0-9]*)-([0-9]*):([a-z])", line)
        for attr in attrs:
            if int(attr[0]) <= vaihe <= int(attr[1]):
                if attr[2] == 'z':
                    code = True
                    vari = 'm'
                elif attr[2] == 'p':
                    code = False
                    vari = 'p'
                else:
                    code = True
                    vari = str(attr[2])
                tglin = tagline(tag.group(1), vari, vaihe, ln)
                return (code, tglin)
    return(False, '')

def endtag(tag, fout):
    if tag == 'shd':
        fout.write('\n</title>')
    elif not tag == '':
        fout.write('\n</' + tag + '>')
    return ''
def endtag_s(tag, fout):
    if not tag == '':
        fout.write('\n</' + tag + '>')
    return ''

def ilman_selityksia(fout, vaihe):
    with open(infil, 'r') as fin:
        ln = 0
        current = ''
        pr_cod = False
        for line in fin:
            ln = ln + 1
            tag = re.search(r"(?<=\# )(txt|cod|shd)", line)
            if tag:
                if pr_cod:
                    current = endtag(current, fout)
                (pr_cod, tagLine) = tarkistaVaihe_c(
                                            line, tag, vaihe, ln)
                if pr_cod:
                    current = tag.group(1)
                    fout.write(tagLine)
            elif pr_cod:
                    fout.write(repl(line))
        current = endtag(current, fout)

def selitykset(fout, vaihe):
    with open(infil, 'r') as fin:
        ln = 0
        current = ''
        pr_txt = False
        for line in fin:
            ln = ln + 1
            tag = re.search(r"(?<=\# )(txt|cod|shd)", line)
            if tag:
                if pr_txt:
                    current = endtag_s(current, fout)
                (pr_txt, pr_cod, tagLine) = tarkistaVaihe_s(
                                            line, tag, vaihe)
                if pr_txt:
                    current = tag.group(1)
                    fout.write(tagLine)
            elif pr_txt:
                    if current == 'txt':
                        fout.write(line)
                    elif current == 'cod':
                        alkuloppu = re.split(r"^#| #", line, maxsplit=1)
                        fout.write(repl(alkuloppu[0]))
                        if len(alkuloppu) > 1:
                            fout.write('<orange> # ' + repl(alkuloppu[1]) +
                                       '</orange>')
        current = endtag_s(current, fout)

def vain_koodi(progfil, vaihe):
    with open(infil, 'r') as fin:
        ln = 0
        pr_cod = False
        for line in fin:
            ln = ln + 1
            tag = re.search(r"(?<=\# )(txt|cod|shd)", line)
            if tag:
                (pr_cod, tagLine) = tarkistaVaihe_py(line, tag, vaihe, ln)
            elif pr_cod:
                    progfil.write(line)

def repl(line):
    return re.sub('<', '<', re.sub('>', '>', line))
def summary():
    with open(infil) as fd:
        file_contents = fd.read()
        module = ast.parse(file_contents)
        return ast.get_docstring(module)
def lisafileet(fout):
    narg = len(sys.argv)
    if narg > 3:
        fout.write(
        '\n<sp><iso>Lataa myös seuraavat, ellet ole vielä ladannut</iso></sp>')
        for i in range(3, narg):
            fout.write(
              '\n<p><weblink id="l' + str(random.randint(100,9999)) +
              '" webref="../xml/Programming/PythonOpetus/' +
              str(sys.argv[i]) + '">lisätiedosto: ' + str(sys.argv[i]) +
              '</weblink></p>')

def main():
    with open(xmlf, 'w') as fout:
        fout.write('<section>\n<title anchor="' + xmlf + '">' +
                   projName + '.py\n</title>\n')
        fout.write('\n<summary>')
        fout.write(summary())
        fout.write('\n<paivays>')
        fout.write(time.asctime(time.localtime(time.time())))
        fout.write('\n</paivays>')
        fout.write('\n</summary>')
        for vaihe in range(maxVaihe):
            ilman_selityksia(fout, vaihe)
            fout.write('<shd uid="h' + str(random.randint(100,9999)) +
                       '">Selityksiä ylläolevaan</shd>')
            selitykset(fout, vaihe)
            pfname = projName+'_'+str(vaihe)+'.py'
            with open(pfname, 'w') as progfil:
                vain_koodi(progfil, vaihe)
            fout.write(
              '<sp><iso><weblink  id="v' + str(random.randint(100,9999)) +
              '" webref="../xml/Programming/PythonOpetus/' +
              pfname + '">Lataa tästä ohjelman tämän hetkinen versio: ' +
              pfname + '</weblink></iso></sp>')
            lisafileet(fout)
            fout.write('\n</section>\n')
        fout.write('\n</section >')

if len(sys.argv) > 1:
    projName = str(sys.argv[1])
else:
    projName = 'ProjektiX'
if len(sys.argv) > 2:
    maxVaihe = int(sys.argv[2])
else:
    maxVaihe = 6
xmlf = projName+'_code.xml'
infil = projName+'.py'
summary()
if __name__ == "__main__":
    main()


Selityksiä ylläolevaan

Halusin esitellä miten kehittelin peliohjelmia vaihe vaiheelta. Esimerkiksi hyttysjahtipelin ensimmäinen versio ei tee muuta kuin luo näytölle ikkunan. Seuraavassa vaiheessa pelissä on nuolinäppäimillä siirreltävä hyttyslätkä jne.

Peliä kehittäessä huomasin usein tarpeen muuttaa aikaisempien vaiheiden koodia. Opin esimerkiksi vasta pelin valmistuttua, miten avata näyttölaitteen ruudun kokoinen peli-ikkuna. Jos olisin tehnyt oikeasti kuusi eri versioita pelistä, olisin joutunut muuttamaan niitä kaikkia. Niinpä tein tämän ohjelman, jolla voin "poimia" lopullisesta versiosta ohjelman eri kehitysvaiheita vastaavat ohjelmaversiot. Samalla teen ohjelmasta xml-tiedoston, josta luon tämänkaltaisia dokumentteja.

Generoin yhdestä python-ohjelmasta dokumentin ja eri versioita itse ohjelmasta.

Lisään python ohjelmaan seuraavanlaisia kommentteja:

 #shd n1-n1:v

shd-tagia seuraava kommentti näkyy dokumenttitiedostossa otsikkona.


 #cod n1-n2:v n3-n4:m n5-100:z
  # tähän tulee ohjelmakoodia, joka on ohjelmaversioissa n1:stä ylöspäin.
  # Lisäksi tämä koodi näkyy dokumentissa vaiheissa n1-n2 ja n3-n4
  # (Käytän kommenttirivejä, koska tämä teksti on siis suoritettavassa
  # ohjelmassa.)
 #txt n1-n2:v n3-n4:m

  Tähän tulevat rivit eivät näy ohjelmassa,
  mutta näkyvät vaiheiden n1-n2 ja n3-n4 dokumenteissa.

 

ylempi tarkoittaa, että sitä seuraavat rivit kirjoitetaan sekä dokumenttitiedostoon että ohjelmatiedostoon. Vaiheissa n1-n2 kirjoitetaan dokumenttitiedostoon vihreällä ja vastaavan vaiheen ohjelmatiedostoon. Vaiheissa n3-n4 rivit kirjoitetaan mustalla ja vaiheissa n5-100 ne kirjoitetaan pelkästään ohjelmatiedostoon. Vaiheissa ennen n1:tä, seuraavia rivejä ei kirjoiteta dokumenttiin eikä itse ohjelmaan.

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

 #  = = = = = = = = = = = = = = = = = = = = = = = = = =
 #  import sys
 #  print "This is the name of the script: ", sys.argv[0]
 #  print "Number of arguments: ", len(sys.argv)
 #  print "The arguments are: " , str(sys.argv)
 #  = = = = = = = = = = = = = = = = = = = = = = = = = =


import re
import sys
import ast
import time
import random


-
def tagline(tag, vari, vaihe, ln):
    if tag == 'shd':
        unique_id = projName + str(vaihe) + str(ln)
        tglin = '\n<section>\n<title anchor = "' + unique_id + '">'
    else:
        tglin = '\n<' + tag + ' mod = "' + vari + '">\n'
    return tglin


def tagline_s(tag, vari):
    tglin = '\n<' + tag + ' mod = "' + vari + '">\n'
    return tglin


a


def tarkistaVaihe_s(line, tag, vaihe):
    if not tag.group(1) == 'shd':
        attrs = re.findall(r"([0-9]*)-([0-9]*):([a-z])", line)
        for attr in attrs:
            if int(attr[0]) <= vaihe <= int(attr[1]):
                if attr[2] == 'z':
                    txt = False
                    code = True
                    vari = 'm'
                elif attr[2] == 'p':
                    txt = True
                    code = False
                    vari = 'p'
                else:
                    txt = True
                    code = True
                    vari = str(attr[2])
                tglin = tagline_s(tag.group(1), vari)
                return (txt, code, tglin)
    return(False, False, '')



a


def tarkistaVaihe_c(line, tag, vaihe, ln):
    if not tag.group(1) == 'txt':
        attrs = re.findall(r"([0-9]*)-([0-9]*):([a-z])", line)
        for attr in attrs:
            if int(attr[0]) <= vaihe <= int(attr[1]):
                if attr[2] == 'z':
                    code = True
                    vari = 'm'
                elif attr[2] == 'p':
                    code = True
                    vari = 'p'
                else:
                    code = True
                    vari = str(attr[2])
                tglin = tagline(tag.group(1), vari, vaihe, ln)
                return (code, tglin)
    return(False, '')


a


def tarkistaVaihe_py(line, tag, vaihe, ln):
    if tag.group(1) == 'cod':
        attrs = re.findall(r"([0-9]*)-([0-9]*):([a-z])", line)
        for attr in attrs:
            if int(attr[0]) <= vaihe <= int(attr[1]):
                if attr[2] == 'z':
                    code = True
                    vari = 'm'
                elif attr[2] == 'p':
                    code = False
                    vari = 'p'
                else:
                    code = True
                    vari = str(attr[2])
                tglin = tagline(tag.group(1), vari, vaihe, ln)
                return (code, tglin)
    return(False, '')


a
def endtag(tag, fout):
    if tag == 'shd':
        fout.write('\n</title>')
    elif not tag == '':
        fout.write('\n</' + tag + '>')
    return ''


def endtag_s(tag, fout):
    if not tag == '':
        fout.write('\n</' + tag + '>')
    return ''


a

def ilman_selityksia(fout, vaihe):
    with open(infil, 'r') as fin:
        ln = 0
        current = ''
        pr_cod = False
        for line in fin:
            ln = ln + 1
            tag = re.search(r"(?<=\# )(txt|cod|shd)", line)
            if tag:
                if pr_cod:
                    current = endtag(current, fout)
                (pr_cod, tagLine) = tarkistaVaihe_c(
                                            line, tag, vaihe, ln)
                if pr_cod:
                    current = tag.group(1)
                    fout.write(tagLine)
            elif pr_cod:
                    fout.write(repl(line))
        current = endtag(current, fout)


a

def selitykset(fout, vaihe):
    with open(infil, 'r') as fin:
        ln = 0
        current = ''
        pr_txt = False
        for line in fin:
            ln = ln + 1
            tag = re.search(r"(?<=\# )(txt|cod|shd)", line)
            if tag:
                if pr_txt:
                    current = endtag_s(current, fout)
                (pr_txt, pr_cod, tagLine) = tarkistaVaihe_s(
                                            line, tag, vaihe)
                if pr_txt:
                    current = tag.group(1)
                    fout.write(tagLine)
            elif pr_txt:
                    if current == 'txt':
                        fout.write(line)
                    elif current == 'cod':
                        alkuloppu = re.split(r"^#| # ", line, maxsplit=1)
                        fout.write(repl(alkuloppu[0]))
                        if len(alkuloppu) > 1:
                            fout.write('<orange> #  ' + repl(alkuloppu[1]) +
                                       '</orange>')
        current = endtag_s(current, fout)


a

def vain_koodi(progfil, vaihe):
    with open(infil, 'r') as fin:
        ln = 0
        pr_cod = False
        for line in fin:
            ln = ln + 1
            tag = re.search(r"(?<=\# )(txt|cod|shd)", line)
            if tag:
                (pr_cod, tagLine) = tarkistaVaihe_py(line, tag, vaihe, ln)
            elif pr_cod:
                    progfil.write(line)


a


def repl(line):
    return re.sub('<', '<', re.sub('>', '>', line))


def summary():
    with open(infil) as fd:
        file_contents = fd.read()
        module = ast.parse(file_contents)
        return ast.get_docstring(module)


def lisafileet(fout):
    narg = len(sys.argv)
    if narg > 3:
        fout.write(
        '\n<sp><iso>Lataa myös seuraavat, ellet ole vielä ladannut</iso></sp>')
        for i in range(3, narg):
            fout.write(
              '\n<p><weblink id="l' + str(random.randint(100,9999)) +
              '" webref="../xml/Programming/PythonOpetus/' +
              str(sys.argv[i]) + '">lisätiedosto: ' + str(sys.argv[i]) +
              '</weblink></p>')


a

def main():
    with open(xmlf, 'w') as fout:
        fout.write('<section>\n<title anchor="' + xmlf + '">' +
                   projName + '.py\n</title>\n')
        fout.write('\n<summary>')
        fout.write(summary())
        fout.write('\n<paivays>')
        fout.write(time.asctime(time.localtime(time.time())))
        fout.write('\n</paivays>')
        fout.write('\n</summary>')
        for vaihe in range(maxVaihe):
            ilman_selityksia(fout, vaihe)
            fout.write('<shd uid="h' + str(random.randint(100,9999)) +
                       '">Selityksiä ylläolevaan</shd>')
            selitykset(fout, vaihe)
            pfname = projName+'_'+str(vaihe)+'.py'
            with open(pfname, 'w') as progfil:
                vain_koodi(progfil, vaihe)
            fout.write(
              '<sp><iso><weblink  id="v' + str(random.randint(100,9999)) +
              '" webref="../xml/Programming/PythonOpetus/' +
              pfname + '">Lataa tästä ohjelman tämän hetkinen versio: ' +
              pfname + '</weblink></iso></sp>')
            lisafileet(fout)
            fout.write('\n</section>\n')
        fout.write('\n</section >')

a


if len(sys.argv) > 1:
    projName = str(sys.argv[1])
else:
    projName = 'ProjektiX'

if len(sys.argv) > 2:
    maxVaihe = int(sys.argv[2])
else:
    maxVaihe = 6

xmlf = projName+'_code.xml'
infil = projName+'.py'

summary()

if __name__ == "__main__":
    main()


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






( )

Simulaatiota



( Helmikuu 2018 )

Peikkometsän hiilitase

Me - peikkokoulun Python-kerho - teimme pelin, joka auttaa ymmärtämään kraatterimme metsien hakkuun vaikutusta kraatterimme ilmastoon.


Peikkojen metsä kasvaa lasikupolilla katetussa tulivuoren kraaterissa. Kraaterin ilmastoon vaikuttava hiili on varastoituneena metsän puihin, kraaterin ilmaan ja polttopuihin peikkojen puuliitereissä. Koska kraaterissa on oma pienoisilmastonsa, meidän täytyy pitää tarkasti huolta ilman hiilidioksidipitoisuudesta, että kraatteri pysyy sopivan lämpimänä.

(Jonkun mielestä kraatterin ilman lämpötilaa voisi säädellä tuulettamalla ja kaihtimilla, mutta eihän satu ole enää satu, jos kaikki tosiasiat otetaan huomioon. Ehkä ulkopuolinen ilma on saastunutta tai peikot pelkäävät noitien lennoillaan levittämää taikapölyä. Ehkä peikot ja kraatterin kasvit tarvitsevat kaiken saatavilla olevan auringonvalon.)

Satumetsän puut kasvavat sangen oikukkaasti, joten meidän satumetsän peikkojen on hoidettava metsää erityisellä huolella. Tätä varten päätimme tehdä simulaattorin, jolla harjoitella metsän hoitoa. Tai no, oikeastaan halusimme kokeilla, millaista ohjelmointi on. Olimme kulleet väitettävän ohjelmointia hauskaksi.

Puiden kasvusta on tehty tutkimuksia, mutta meillä oli niin kiire päästä ohjelmoinnin alkuun, ettemme varsinaisesti ehtineet tutustua niihin. Arvelimme, ettei metsän kasvua ole kovin monimutkaista mallintaa: Puu muuttaa ravinteet auringon valon avulla selluloosaksi yms. Mitä suurempi lehvästö ja juuristo, sitä vauhdikkaammin puu kasvaa. Jos puut kasvavat liian lähekkäin, ne varjostavat toisiaan ja kilpailevat vedestä ja ravinteista. Haitta on tietysti verrannollinen siihe, kuinka pahasti lomittain puut kasvavat. Satumetsän puut eivät kasva rajattomasti. Tietyn rajan jälkeen niiden kasvu hidastuu ja lopulta pysähtyy. Puuvanhukset kasvavat hitaammin kuin nuoret puut. Lopulta puu kuolee vanhuuteen ja alkaa lahota.

Oli yllättävän iso työ kirjoittaa työ python-kielelle. Se on ihan erilaista kuin meidän peikkojen kieli ja kaikki pitää kertoa yksityiskohtaisesti ja täsmällisesti. Python-kielessä ei esimerkiksi ole "vauhdikkaammin" -sanaa. Toisaalta oli sangen opettavasti miettiä perusteellisesti, mitä puhekieli tarkkaan ottaen tarkoittaa. Kävi ilmi, että meistä jokainen oli ymmärtänyt eri tavalla, mitä puhekielinen kuvaus metsän kasvusta tarkoittaa.

Simulointien tuloksista voi oppia monen laista. Opimme, että satumetsän puiden kasvu ei noudata kuvittelemiamme lainalaisuuksia. Karvas oppi, mutta onneksi simulaattorista tuli hauska peli, jonka suunnittelu opetti meidät miettimään, mikä on olennaista metsän kasvun ja kraatterin hiilidioksitaseen kannalta. Monet puhuvat ihan puuta-heinää, koska eivät ymmärrä ongelman ydintä.

Miten piirtää metsä tietokone-ohjelmalla

Tarkoituksemme oli, että simulaattori piirtää kuvaruudulle kasvamaan oikean näköisen metsän, jota voisi käännellä ja katsella miltä puolen haluaa. Voisi nähdä, miten ison puun varjo siirtyy auringon nousemisen myötä pois pienen puun yltä. Puiden lomassa lennähtelevät ja niiden oksilla sirkuttavat linnut olisivat olleet mukava yksityiskohta.

Loppujen lopuksi puista tuli pyörylöillä kuvattuja palloja ja metsää voi katsoa joko tasan ylhäältä tai tasan alhaalta. Aurinko paistaa suoraan ylhäältä. Puiden varjon korvasimme puuta ympäröivällä kehällä, mutta se teki näytöstä niin psykedeelisen, että tarjoamme vaihtoehtona sekä puun, että sen varjon kattavaa kiekkoa. Puun varjo ei liiku auringon mukana, koska aurinkokaan ei liiku. Varjo on eräänlainen keskimääräinen varjo. Kiekon väri kertoo, kuinka pahasti puu on muiden katveessa. Puun kuollessa se muuttuu ruskeaksi ja lahoaa hitaasti olemattomiin.

Ohjelmoimme erilaisia hakkuustrategioita polttopuun korjuuseen: Avohakkuu, vanhojen puiden hakkaaminen, varjoon jäävien karsiminen yms. Yhtä vähän kuin simulaattorin puut kasvoivat niin kuin kuvittelimme, hakkuut vaikuttivat niin kuin kuvittelimme. Toistuva avohakkuu tosin näyttää huonolta strategialta aivan kuten peikkojen perimätietokin kertoo.

Yksinkertaistamisen vastapainoksi päätimme laskea tarkkaan, paljonko pyörylät varjostavat toisiaan. Oikeasti puut eivät ole pyörylöitä, mutta näin tulee laskettua tarkkaan edes se, mitä tapahtuisi, jos ne olisivat. "Pii kertaa peukalo" mutisi opettaja nähtyään laskelmamme. "Miksi laskea jotain tarkkaan, jos muu on pelkkää arvailua" Vastasimme laskevamme kaiken parhaalla mahdollisella tarkkuudella. Muuten joutuu kaltevalle pinnalle ja päätyy piirtämään simulointitulokset vapaalla kädellä.

Puut ovat mallissamme palloja, joista auringonvalon suuntaa näkyy kiekko. Pieni puu on isoa matalampi, joten oikeastaan vain se on varjossa, mutta oikeassa metsässä pienikin puu liian lähellä häiritsee isomman kasvua. Imee vettä ja ravinteita, haittaa oksiston kasvua, varjostaa alaoksia, ... Päädyimme arvioon, että valo lankeaa toisiaan varjostaville puiden osille puiden säteiden suhteessa. Varjostus pinta-alojen suhteessa olisi looginen sekin.

Puuta voi varjostaa kahdeksan viereistä puuta. Aluksi laskimme kunkin puun aiheuttaman katveen yhteen, mutta silloin puun saama valo meni pahimmillaan miinukselle ei hyvä. Entä jos yhden puun aiheuttaman varjon alueella valo vähenee vain 1/8? Varjostus vaikutti "normaalitapauksissa" kovin vähän. Annoimme varjojen vaikuttaa reilusti, mutta leikkaamme vaikutukset niin, että valo ei mene miinukselle. Pikkupeikko koodasi tämän kohdan. En ole varma, onko koodi oikein. Selvää on, että oikeassa peikkometsässä homma ei mene ihan näin.

Tässä vaiheessa pitkällä kiipesimme kraatterin reunaa ylös tähyilemään metsää ylhäältä päin: metsä näytti vähän kuin tiuhassa kasvavilta palloilta. Siltä osin mallimme ei liene aivan pielessä, mutta kävelyllä satumetsän siimeksessä aloimme aavistella, että simulaattorimme puut eivät välttämättä kasva kuin satumetsän puut. Pitäisi olla ainakin sadan vuoden ajalta kuvia eri-ikäisten ja erilaisissa oloissa elävien puiden kasvusta ja lahoamisesta, että osaisi edes arvioida, onko mallissa mitään tolkkua. Päätimme vakaasti, että luemme tutkimuksia puiden kasvusta. Kunhan ensin kokeilemme ohjelmointia.

Hakkuustrategiat

Mielellään olisimme tehneet ohjelman, joka laskee optimaalisen tavan karsia satumetsää, mutta jo simulointimallin tekeminen otti hermoon sen verran, että jätimme optimoinnin myöhemmäksi ja ohjelmoimamme kokeiltavaksi muutaman erilaisen hakkuustrategian.

Avohakkuu
Kaadetaan kaikki puut kerralla. Arvauksemme on, että hakkuun jälkeen metsä kasvaa tosi hitaasti kiihtyen puiden lehvästön kasvaessa ja pyydystäessä entistä enemmän aurinkoa. Kasvu hidastuu metsän tullessa "täyteen" ja puiden täysikasvuiseksi. Metsän hiilivaraston osalta ollaan silloin samassa tilanteessa kuin ennen avohakkuuta. Jos hakattua puuta poltetaan säästeliäästi, avohakkuu ei ole ilmaston kannalta katastrofi, vaikkakin todennäköisesti huonoin ratkaisu.
Karsitaan vanhat puut
Korjataan talteen vanhat huonokuntoiset ja jo kuolleet puut.
Karsitaan isot puut
Korjataan talteen täysikasvuiset ja lähes täysikasvuiset puut. Nämä varjostavat nuoria hyväkasvuisia puita, mutta aivan vanhimpia lukuunottamatta nämä olisivat vielä pitkään hyviä hiilivarastoja.
Karsitaan puut muiden varjoista
Muiden varjossa kasvavat puut haittaavat niitä varjostavien kasvua ainakin pikkuisen eivätkä itse juurikaan kasva.
Kaadetaan joka toinen puu
Harvennetaan metsää niin, että jäljelläoleville tulee tilaa kasvaa.

Puiden polttaminen

Jos kaikki kaadetut puut varastoitaisiin pysyvästi, ei hirveästi haittaisi, miten satumetsän puita hakattaisiin. Satumetsä kasvaa sen verran hyvin, että hiiltä varastoituisi joka tapauksessa riittävästi. Jos taas polttaisimme heti kaiken hakatun puun, metsään olisi paras olla koskematta lainkaan. Simulaattorissa voi muuttaa vauhtia, jolla poltamme puita ja siten tehdä hakkuutehtävästä helpomman tai vaikeamman.

Alku aina hankala

Kopioimme itsellemme yksinkertaisen Python-kielisen pelin, josta arvelimme pienellä muokkauksella saavamme oivallisen metsäsimulaattorin. Installoimme ohjelmointiympäristön, latasimme koodin ja ...

Pelin ohjelmakoodi näytti englannin ja matematiikan sotkulta, eikä siitä ymmärtänyt mitään. Aina kun muutimme pelin koodia, ruutu tuli täyteen käsittämättömiä virheilmoituksia eikä näytölle piirtynyt puun puuta.

Melkoinen yllätys. Me peikot olemme tottuneet siihen, että osaamme mitä vaan ja saamme yhdessä hujauksessa valmiiksi kaiken mihin ryhdymme. Peikkovanhukset naureskelivat, että noin käy, kun ei noudata perinteitä vaan ryhtyy johonkin, mitä esi-isät eivät ole opettaneet. Totta, tämä oli ensimmäinen kerta, kun poikkesimme perinteistä. Siitä vanhukset eivät pitäneet. Kun eivät mitään ymmärtäneet.

Yksi meistä jatkoi sinnikkäästi kokeilemista, minä aloin googlailemalla selvittää, mitä ohjelman koodi tarkoittaa ja yksi meistä alkoi suorittaa ohjelmoinnin peruskurssia verkossa. Kolme kertaa päivässä kokoonnuimme kiistelemään siitä, mitä kannattaisi tehdä.

Kolmantena päivänä onnistuimme muuttamaan alkuperäisen pelin koodia niin, että yksi pelin hahmoista kääntyi ylösalaisin ja taivas värjäytyi keltaiseksi. Ilon päivä. Olimme koukussa ohjelmointiin.

Viikon kuluttua malliksi ottamamme peli oli muuttunut metsäsimulaattoriksi, joka kasvatti ensimmäisen puunsa. Puu kasvoi ja kasvoi, täytti koko ruudun ja lopetti vasta kun ruudulle tuli joku overflow -ilmoitus.

Vasta kuukauden kuluttua simulaattori oli valmis. Kuukauden! Peikkojen keskittymiskyvyllä jo päivä yhtä hommaa on huippusuoritus. Ohjelmointi koukuttaa.

Vilkaisimme opasta, jossa kuvailtiin laadukasta ja tyylikästä koodia. Ehkä joskus myöhemmin teemme simulaattorista tyylikkään version.

Tässä demovideo simulaattorista.

Algoritmin yksityiskohtia

Yritimme parhaamme mukaan kommentoida ohjelman koodia, mutta muutaman yksityiskohdan selitämme tässä. Koodi kannattaa kuitenkin lukea. Roiskaisimme sinne paljon kaikenlaisia havaintoja ohjelmoinnista ja sen opiskelusta yleensä sekä simuloinnista erityisesti.

Käytämme puiden kasvun simulointiin Eulerin algoritmia: $x(t+\Delta t) = x(t) + \dot{x}(t) \cdot \Delta t$ Se on yksinkertaisin mahdollinen. Ei kovin tarkka, mutta riittää näin yksinkertaisessa tehtävässä.

Integrointiaskeleen pituus? Kiperä kysymys! "Ehkä puu kasvaa s-käyrän muotoisesti". Ehkä. Piirsimme makaavan S:n ja sitä seurailemaan murtoviivan. 1/4 vuoden välein piirretty murtoviiva seurasi kuvittelemaamme metsän kasvun käyrää melko hyvin. Ehkä 1/4 vuosi olisi sopiva integrointiaskel. Tai sitten ei. Onneksi löysimme vihjeen: ""Kokeile, paljonko tulos muuttuu, jos lyhennät integrointiaskeleen puoleen." Ohjelmoimme algoritmin kokeilemaan joka askelta myös kahdella puoliaskeleella.

Virhe oli iso, vaikka kuinka pienensimme integrointiaskelta. Lisäsimme välitulostuksia, kunnes älysimme, että virhe oli suuri vain, kun puu oli lähes olemattoman pieni. Kun puu on lahonnut lähes olemattomiin, seuraava integrointiaskel vie sen massan miinukselle. Lyhyemmällä askelella algoritmi huomaa nopeammin, milloin puu on lahonnut olemattomiin, pitkällä askeleella se lahoaa enemmän miinukselle. Mitätön juttu, mutta suhteellinen virhe oli iso.

Tuollainen salapoliisityö tekee ohjelmoinnista mielenkiintoista. En tiedä, loppuuko tuollainen salapoliisityö sitten, kun oppii tämän homman.

Mitä opimme

Romaanien kirjoittajat väittävät, että lukijat tulkitsevat heidän tarinoitaan yllättävillä tavoilla. Se on kuitenkin pientä siihen verrattuna, miten yllättävästi tietokone tulkitsee ohjelmakoodia. Silloin kuin yleensä suostuu sitä tulkitsemaan.

Emme yllättyneet siitä, että metsä simulaattorissamme ei kasva ihan samalla tavalla kuin kuvittelimme peikkometsän kasvavan. Olemme seuranneet metsän kasvua, leikkineet siellä, vain reilut kymmenen vuotta, joten käsityksemme siitä, miten se kasvaa seuraavat muutaman sata vuotta, on sangen hatara. Kiusallista on, ettemme kaikilta osin ymmärrä, miksi simuloitu metsä kasvaa niin oudosti kuin kasvaa.

Koska mallimme perustuu kuvitelmiimme satumetsän kasvusta, simulaattorikin kertoo kuvitelmistamme. Oli opettavaista nähdä, että kuvitelmamme johti erilaiseen lopputulokseen kuin mitä — kuvittelimme. Jouduimme korjaamaan kuvitelmiemme metsän kasvun lainalaisuuksista. Toki joskus olimme niin varmoja kuvitelmistamme, että korjailiamme simulaattoria toimimaan niiden mukaisesti. Opettaja huomautti, että tiedettä ei tehdä noin, vaikka lopputulos toki käy järkeen.

Lisäkoodausta

Nykyisellä ohjelmaversiolla voi tehdä kaikenlaista hauskaa, jos osaa ohjelmoida ja tuntee koodin sen verran, että osaa varoa muutamaa siihen vielä jäänyttä sudenkuoppaa. Olisi kuitenkin hienoa tehdä ohjelmasta oikea peli, jossa olisi kunnon käyttöliittymä. Ensin tietysti pitäisi miettiä pelin idea.



(Kesäkuu 2015 )

Siltanosturi — virtuaalinen mekaaninen lelu

Siltanosturi ei ehkä ole pelinä erityisen jännittävä, mutta sopinee esimerkiksi tutkivasta oppimisesta.


Innostus tähän verryttelyyn heräsi lukion fysiikan tuntia seuratessa. Ajattelin tehdä vaikutuksen ja näyttää, miten parilla pienellä ohjelmanpätkällä voisi havainnollistaa dynaamisten järjestelmien toimintaa. Seuraavalla hyppytunnilla opettajanhuoneessa kävi ilmi, etten osaa kirjoittaa yksinkertaisen nosturin tilayhtälöitä. Tein mielestäni kaiken oikein, mutta kirjoittamilleni yhtälöille ei löytynyt ratkaisua. Ongelma kääntyi pakkomielteeksi, joka vaivasi pari kuukautta, eli siihen asti, että lopulta sain ongelman ratkaistua. Tulipa tutkittua ja opittua.

Siltanosturin simulointi ja liikeradan optimointi




(Helmikuu 2016 )

Prolog ohjelmointikieli

Esitän pari esimerkkiä prolog-ohjelmointikielestä. Niiden perusteella et ehkä opi prolog-ohjelmointia enkä ehkä edes onnistu selittämään tyhjentävästi, miten esimerkkien ohjelmat toimivat. Tavoitteeni on kuitenkin antaa hyvä yleisvaikutelma siitä, millainen ohjelmointikieli prolog on ja antaa muutama esimerkki siitä, millaisia ongelmia sillä on kätevä ratkaista.


Käytän näissä esimerkeissä gnu prologia, mutta tarjolla on monia muitakin prolog-implementaatioita.


(Tammikuu 2016 )

Lyhyt esittely


Seuraavassa tutustumme prologiin tekemällä päättelyitä sukulaisuussuhteista. Sukulaisuussuhteet on kuvattu prologilla seuraavasti:

mother(eila,maija).
mother(eila,kaisa).
mother(kaisa,ritva).
mother(kaisa,jukka).
mother(maija,hilkka).
mother(maija,ilkka).
mother(liisa,heikki).
mother(sofia,matti).

father(frans,matti).
father(matti,heikki).
father(heikki,ilkka).
father(heikki,hilkka).

prologissa vakiot kirjoitetaan pienellä kirjaimella ja muuttujat isolla. Isolla kirjaimella alkavat vakiot pitää kirjoittaa lainausmerkkeihin, esimerkiksi 'Eila'. Kirjoitin kuitenkin ihmisten nimet pienellä, koska en jaksanut näpytellä lainausmerkkejä ja ne olisivat ehkä johtaneet lukijaa harhaan.

Asioiden välisten suhteiden nimet -- kuten mother ja father -- kirjoitetaan myös pienellä.

Päättelyiden helpottamiseksi määrittelemme, millainen sukulaisuussuhde on vanhempi -- parent.

parent(A,B):- mother(A,B).
parent(A,B):- father(A,B).

Prologin "komentoja" kutsutaan predikaateiksi. Prolog-ohjelmaa voi lukea kuin logiikan kielellä kirjoitettua kuvausta tosiasioista ja niiden välisistä suhteista. Ylläolevan kuvauksen sukulaisuussuhteesta parent(A,B) voi lukea seuraavasti: Jos on olemassa A ja B siten, että A on B:n äiti tai A on B:n isä, niin A on B:n vanhempi. Vaihtoehdot -- "tai"-operaatio -- voidaan siis esittää allekkain ylläolevalla tavalla. Käynnistämme prolog-tulkin ja kokeilemme, miten määrittelymme toimii. | ?- on prolog-tulkin komentokehote, jonka perään kirjoitamme "kysymyksemme". Aluksi kerromme tulkille consult-komennolla, missä tiedostossa ongelmamme kuvaus on ja sen jälkeen kysymme, onko heikki jonkun vanhempi -- | ?- parent(heikki,X).

htv> gprolog
GNU Prolog 1.4.4 (64 bits)
Compiled Dec 17 2015, 21:30:59 with gcc
By Daniel Diaz
Copyright (C) 1999-2013 Daniel Diaz
| ?- consult(['suku.prolog']).
compiling suku.prolog for byte code...
suku.prolog compiled, 40 lines read - 3393 bytes written, 11 ms

| ?- parent(heikki,X).

X = ilkka ? ?
Action (; for next solution, a for all solutions, RET to stop) ? a

X = hilkka

yes
| ?- parent(X,heikki).

X = liisa ? a

X = matti

heikki on ilkan vanhempi, koska on olemassa tosiasia father(heikki,ilkka). Oletusarvoisesti prolog vastaa kysymykseen, onko olemassa X:ää, jolle pätee, että heikki on X:n vanhempi, siis isä tai äiti. Prolog ilmoittaa, että X:n arvolla ilkka, relaatio on tosi. Erikseen pyytämällä saamme prolog-tulkin kertomaan muutkin mahdolliset X:n arvot -- tässä tapauksessa hilkka. Samalla tavalla saamme selville, että heikin vanhempia ovat liisa ja matti.

Muita sukulaisuussuhteita voidaan kuvata vastaavasti:

grand_mother(A,B):- mother(A,X), mother(X,B).
grand_mother(A,B):- mother(A,X), father(X,B).

sibling(A,B):-
  parent(X,A),
  parent(X,B),
  A \= B.

cousin(A,B):-
  parent(X,A), parent(Z,X),
  parent(Z,Y), parent(Y,B),
  X \= Y.
  

Esimerkiksi suhteen isoäiti kuvaus voidaan lukea seuraavasti: A on B:n isoäiti, jos on olemassa A, B ja X siten, että A on X:n äiti ja X on B:n äiti. "," siis vastaa logiikan "ja"-operaattoria. Toinen vaihtoehto isoäitiydelle on esitetty seuraavalla rivillä. Relaatiossa sisaruus ehto A \= B varmistaa, että A ja B eivät ole sama henkilö vaan oikeasti sisaruksia. Kun koneita ohjelmoimaan tekemään loogisia päätelmiä, joku tämänkaltainen ihmiselle itsestäänselvä seikka jää helposti kertomatta. Näin yksinkertaisessa ohjelmassa virheen onneksi huomaa helposti ohjelman antamista ratkaisuista.

   | ?- cousin(ilkka,C).

    C = ritva ? a

    C = jukka

    | ?- cousin(C,ilkka).

    C = ritva ? a

    C = jukka

  

Prologissa tieto esitetään usein listoina. [kissa, koira, kana, lehma, lammas] on lista, jonka jäseninä on muutama kotieläin. Lista on järjestetty luettelo. Operaattorilla "|" voidaan erottaa listan ensimmäinen alkio listan loppuosasta.

listan_osat([Head|Tail],Head,Tail).

| ?- listan_osat([kissa, kana, koira], Eka, Loput).

Eka = kissa
Loput = [kana,koira]

yes

| ?- listan_osat(Lista, kana, [koira,lammas]).

Lista = [kana,koira,lammas]

yes
| ?- Elukat = [kissa|[kana,koira]].

Elukat = [kissa,kana,koira]

yes
| ?-



(Helmikuu 2016 )

Reititys

Esimerkki reitin hakemisesta pisteestä A pisteeseen B.


../xml/Programming/Prolog/reitit.png
Risteykset a..n ja niiden väliset tieosuudet

Tarkastellaan kuvan mukaista "tiestöä", jossa on risteyksiä a:sta n:ään ja niitä yhdistäviä tieosuuksia. Teemme ohjelman, joka hakee reitin mistä hyvänsä pisteestä mihin hyvänsä toiseen pisteeseen.

Ensiksi kuvaamme tiestön prolog-ohjelmointikielellä:

vali(a,b,10).
vali(a,c,20).
vali(a,d,10).
vali(b,m,50).
vali(d,g,10).
vali(d,h,10).
vali(c,e,10).
vali(c,f,30).
vali(f,j,10).
vali(f,i,15).
vali(e,l,40).
vali(e,k,25).
vali(i,m,10).
vali(i,k,5).
vali(k,n,26).

viereiset(A,B,L):- vali(A,B,L).
viereiset(A,B,L):- vali(B,A,L).

Kuvaamme tiestön joukolla prolog-predikaatteja vali(A,B,L), missä A ja B ovat tiestön vierekkäisiä risteyksiä ja L niiden välinen matka. Prolog-kielessä muuttujien nimet alkavat isoilla kirjaimilla. (Prolog-ohjelmointikielen "käskyjä" kutsutaan predikaateiksi syystä, jota yritän selittää jutun lopussa.)

Predikaatilla viereiset kerromme, että vierekkäisyys on symmetrinen relaatio. Muuten joutuisimme kirjoittamaan esimerkiksi sekä vali(a,b,10) että vali(b,a,10).

Käynnistämme prolog-tulkin ja kokeilemme, toimiiko määrittelymme.

  htv>gprolog
GNU Prolog 1.4.4 (64 bits)
Compiled Dec 17 2015, 21:30:59 with gcc
By Daniel Diaz
Copyright (C) 1999-2013 Daniel Diaz
| ?- consult(['reititys.prolog']).
compiling reititys.prolog for byte code...
reititys.prolog compiled, 45 lines read - 4624 bytes written, 6 ms

(4 ms) yes

| ?- vali(a,B,L).

B = b
L = 10 ? ?
Action (; for next solution, a for all solutions, RET to stop) ? a

B = c
L = 20

B = d
L = 10

yes
| ?- vali(X,a,L).

no
| ?-

Prolog-tulkki kertoo, että a:sta pääsee b:n ja matkaa on kymmenen kilometriä. Kun pyydämme nähtäväksi kaikki ratkaisut, tulkki luettelee myös yhteydet risteyksiin c ja d. Kyselyyn vali(X,a,L) ei löydy ratkaisua. Ikäänkuin mistään ei pääsisi risteykseen a, jos asiaa tulee kysyneeksi näin päin. Kokeillaanpa viereiset-predikaattia:

  | ?- viereiset(a,X,L).

L = 10
X = b ? a

L = 20
X = c

L = 10
X = d

| ?- viereiset(X,a,L).

L = 10
X = b ? a

L = 20
X = c

L = 10
X = d

yes
| ?-

  

Eli risteykseen a pääsee risteyksistä b, c ja d ja toisinpäin.

Seuraavassa teemme luetteloa risteyksistä, joiden kautta olemme ajaneet. Muistellaan ensin, miten listoja käsitellään. Merkintä [H|T] tarkoittaa, että listan T alkuun lisätään H:

| ?- T = [b,c,d], H = a, List = [H|T].

H = a
List = [a,b,c,d]
T = [b,c,d]

yes
| ?-

Määrittelemme predikaatin reitti(A,Z,Reitti), joka kuvaa reitin risteyksestä A risteykseen Z. Tarvitsemme apumuuttujan, johon kokoamme luettelon risteyksistä, joiden kautta olemme kulkeneet ja apumuuttujan, joka kertoo kulkemamme matkan. Seuraavalla "jipolla" kätkemme apumuuttujat "ulkopuolisilta". Laitamme alkupisteen kuljettujen risteysten luetteloon ja kuljetuksi matkaksi 0km. Prolog ymmärtää saman nimiset relaatiot eri relaatioiksi, jos niillä on eri määrä argumentteja. Olisimme voineet nimetä relaatiomme esimerkki reitti3 ja reitti5, mutta se ei ole tarpeen.

reitti(A,Z,Reitti):-
  reitti(A,Z,[A],0,Reitti).

Haemme reitin rekursiivisesti: Ajamme lähimpään risteykseen ja haemme reitin siitä loppuun. Jos joudumme umpikujaan, peruutamme hakemaan toista vaihtoehtoa. Prolog hoitaa "peruuttamisen" puolestamme, joten meidän ei tarvitse sitä ohjelmoida.

Jos loppumatkan alku- ja loppupiste ovat vierekkäiset, olemme viimeisen välin ajettuamme perillä maalissa. Kokonaismatka on tähän asti kuljettu matka Matka0 lisättynä viimeisen välin pituudella L. Koska =-merkki on prologissa varattu muuhun käyttöön, sijoituslauseessa käytetään operaattoria is -- Matka is Matka0+L,. Kuljettujen risteysten luettelo on nurinkurisessa järjestyksessä viimeinen risteys ensimmäisenä, joten käännämme luettelon reverse-komennolla. Lopulliseksi "vastaukseksi" palautamme listan, jossa ensimmäisenä alkiona on matkan pituus ja toisena alkiona luettelo reittiin kuuluvista risteyksistä.

reitti(A,Z,Kuljetut,Matka0,[Matka,Reitti]):-
    viereiset(A,Z,L),
    Matka is Matka0+L,
    reverse([Z|Kuljetut],Reitti).
    

Mikäli prolog-tulkki ei löydä perille yhdellä hypyllä, se kokeilee toista vaihtoehtoa:

  reitti(A,Z,Kuljetut,Matka0,Reitti):-
    viereiset(A,B,L),
    \+ member(B,Kuljetut),    % \+ tarkoittaa not
    Matka is Matka0+L,
    reitti(B,Z, [B|Kuljetut],Matka,Reitti).

Siirryttään johonkin A viereisistä risteyksistä B ja haetaan reitti B:stä maaliin. \+ member(B,Kuljetut),varmistaa, ettemme ole jo aiemmin kulkeneet B:n kautta -- emme halua ajaa jäädä ajamaan ympyrää. Jos reittiä maaliin ei löydy, kokeillaan jotain muuta A:n viereisistä risteyksistä. Jos ne kaikki johtavat umpikujaan, peruteetaan kauemmas taaksepäin, sinne, mistä jouduttiin A:han. Peruuttamisista pitää huolen prolog-tulkin backtrace-toiminto.

| ?- reitti(g,j,GJ).

GJ = [170,[g,d,a,b,m,i,k,e,c,f,j]] ? a

GJ = [115,[g,d,a,b,m,i,f,j]]

GJ = [105,[g,d,a,c,e,k,i,f,j]]

GJ = [80,[g,d,a,c,f,j]]

Teemme vielä predikaatin, joka kerää kaikki reittivaihtoehdot yhteen listaan ja järjestää sen matkan pituuden mukaiseen järjestykseen.

reitit(A,Z,Reitit):-
    findall(Reitti,reitti(A,Z,[A],0,Reitti),RR),
    sort(RR,Reitit).
| ?- reitit(g,j,GJ).

GJ = [[80,[g,d,a,c,f,j]],[105,[g,d,a,c,e,k,i,f,j]],[115,[g,d,a,b,m,i,f,j]],[170,[g,d,a,b,m,i,k,e,c,f,j]]]

yes

| ?- reitit(b,n,BN).

BN = [[91,[b,a,c,e,k,n]],[91,[b,m,i,k,n]],[106,[b,a,c,f,i,k,n]],[166,[b,m,i,f,c,e,k,n]]]

  

Tällaisen hakualgoritmin voi tietysti ohjelmoida jollain prologia tutummalla ohjelmointikielellä. Prologilla,kun siihen tottuu, monet monimutkaiset ongelmat ratkeavat kuitenkin selkeämmin ja vähemmällä ohjelmoinnilla.

Hakualgoritmi näyttää houkuttelevan yksinkertaiselta. Ainakaan siinä ei ole kovin montaa riviä. Harmi kyllä tarkistettavien vaihtoehtojen määrä kasvaa paljon nopeammin kuin risteysten ja tienpätkien lukumäärä. Jos risteysten määrä kasvaa kymmenkertaiseksi, laskenta-aika ei kasva kymmenkertaiseksi vaan ehkä 3 potenssiin kymmenkertaiseksi. Jonain päivänä ehkä esittelen keinoja rajata hakua tämän tapaisissa tehtävissä. Ja ehkä minulla joskus on aikaa tehdä ohjelma, joka ei pelkästään hae reittejä pisteestä toiseen vaan myös piirtää ne.

/* Reititys */

vali(a,b,10).
vali(a,c,20).
vali(a,d,10).
vali(b,m,50).
vali(d,g,10).
vali(d,h,10).
vali(c,e,10).
vali(c,f,30).
vali(f,j,10).
vali(f,i,15).
vali(e,l,40).
vali(e,k,25).
vali(i,m,10).
vali(i,k,5).
vali(k,n,26).

viereiset(A,B,L):- vali(A,B,L).
viereiset(A,B,L):- vali(B,A,L).

reitti(A,Z,Reitti):-
  reitti(A,Z,[A],0,Reitti).

reitti(A,Z,Kuljetut,Matka0,[Matka,Reitti]):-
    viereiset(A,Z,L),
    Matka is Matka0+L,
    reverse([Z|Kuljetut],Reitti).
reitti(A,Z,Kuljetut,Matka0,Reitti):-
    viereiset(A,B,L),
    \+ member(B,Kuljetut), % \+ tarkoittaa not
    Matka is Matka0+L,
    reitti(B,Z, [B|Kuljetut],Matka,Reitti).

reitit(A,Z,Reitit):-
    findall(Reitti,reitti(A,Z,[A],0,Reitti),RR),
    sort(RR,Reitit).

  

Ylläoleva reititys-ohjelman koodi .



()

junarata.prolog


()

Junaratoja

% "asennettu-predikaatti on osa keskenjäänyttä yritystä tehostaa ratkaisujen hakua.
%:- dynamic(asennettu/4).
% kiskon palan pituus (siirtymä xy-koordinaatistossa), kääntymissuunta ja -kulma
kisko(suora,220.0,0.0).
kisko(vasen,L, Kulma):-
    N = 8, % kaarrepalojen lukumäärä täydessä ympyrässä
    D = 410.0, %kaarrepaloista rakennetun ympyrän halkaisija
    Kulma is 2.0*pi/N,
    L is D*sin(Kulma/2.0).
kisko(oikea, L, Kulma):-
    N = 8, % kaarrepalojen lukumäärä täydessä ympyrässä
    D = 410.0, %kaarrepaloista rakennetun ympyrän halkaisija
    Kulma is -2.0*pi/N,
    L is D*sin(-Kulma/2.0).
% rata-alueen rajat millimetreinä xy-koordinaatistossa.
% Tämän verran on lattialla tilaa. Rakentaminen aloitetaan pisteestä (0,0)
alue(X, Y):-
    X > -1000.0,  X < 1000.0,
    Y > -600.0,  Y < 600.0.
 % Radan piirtämisen avuksi
pikkukaarre(Kulma,L):-
    N = 40,
    D = 410.0,
    Kulma is 2.0*pi/N,
    L is D*sin(Kulma/2.0).
% Muut osat: Risteykset, sillat yms. pitäisi ottaa mukaan,
%   jos haluaisi tehdä jotain käytännön kannalta mielenkiintoista.

% Nsuorat: Käytettävissä olevien suorien kiskojen lukumäärä
% Nkaarteet: Käytettävissä olevien kaarteiden lukumäärä
radat(Nsuorat, Nkaarteet):-
 %   retractall(asennettu/4),
    % haetaan kaikki erilaiset radat
    setof(Rata, rata(Nsuorat, Nkaarteet, Rata), Radat),
    % seulotaan pois ne, jotka saa pyöräyttämällä jotain toista rataa
    erilaiset(Radat, Eril),
    nl,
    length(Radat,M),
    length(Eril,N),
    write(['kaikki vs. erilaiset ',M, N]),nl,
    kuvat(N),
    piirraRadat(Eril,1, Nsuorat, Nkaarteet).

 % Ns0: käytettävissä olevien suorien kiskojen lukumäärä
 % Nk0: käytettävissä olevien kaarrekiskojen lukumäärä
 % Kiskot: Järjestetty luettelo käytetyistä kiskoista. Esim  [suora,vasen,vasen,oikea,...]
rata(Ns0, Nk0, [suora|Kiskot]):-
    Ns0 > 0,!,
    Ns is Ns0 - 1,
    kisko(suora, L, Kulma),
    kiskot(Kulma, L, 0.0, Ns, Nk0, Kiskot).
rata(0, Nk0, [vasen|Kiskot]):-
    Nk is Nk0 - 1,
    kisko(vasen, L, Kulma),
    X is L*cos(Kulma),
    Y is L*sin(Kulma),
    kiskot(Kulma,X,Y,0, Nk,Kiskot).
%  kiskot(0.0, 0.0, 0.0, Ns, Nk, Rata).

kiskot(Kulma, X, Y, Ns, Nk, []):-
    % Ollaanko origossa?
    round(X) =:= 0.0,
    round(Y) =:= 0.0,
    Nk < 4,
    Ns < 2,
	% Onko tehty nolla (=kahdeksikko) tai useampi tasakierros vastapäivään?
    kierrokset(Kulma),
%	write([Nk,Ns,Kulma,X,Y]),nl,
    % Laitetaan näytölle piste aina kun löydetään uusi ratkaisu
    put_char('.'),
    flush_output.

kiskot(Kulma0,X0,Y0,Ns0, Nk0,  [Suunta|Kiskot]):-
    Nk0 >= 0, Ns0 >= 0,
    riittaakokaaret(Kulma0,Nk0),
	riittaakokiskot(Nk0,Ns0,X0,Y0),
    kisko(Suunta, L, DKulma),
    kiskolkm(Suunta,Nk0,Ns0,Nk,Ns),
    Kulma is Kulma0+DKulma,
    X is X0+L*cos(Kulma),
    Y is Y0+L*sin(Kulma),
    alue(X, Y),
%    overlap(X0,X, Y0,Y),  (epäonnistunut/keskeneräinen yritys tehostaa hakua)
    kiskot(Kulma, X, Y, Ns, Nk, Kiskot).
kiskolkm(suora,Nk0,Ns0,Nk0,Ns):- Ns is Ns0 - 1.
kiskolkm(oikea,Nk0,Ns0,Nk,Ns0):- Nk is Nk0 - 1.
kiskolkm(vasen,Nk0,Ns0,Nk,Ns0):- Nk is Nk0 - 1.
 % Tavoite on 0 tai useampi kierros vastapäivään joten
% on turha mennä niin pitkälle myötäpäivään, että kaaret loppuvat
% kesken takaisinkääntymisen. (Vai rajaanko liikaa?)
riittaakokaaret(Kulma0, Nk0):-
Kulma0 + Nk0*pi/4.0 > -0.0001.   % >=0 riittävä ehto
 % Ollaanko niin kaukana, ettei ole mitään mahdollisuutta päästä alkupisteeseen.
 riittaakokiskot(Nk,Ns,X,Y):-
	kisko(suora,Ls,_),
	kisko(vasen,Lk,_),
	L is sqrt(X**2+Y**2),
	Lmax is Ns*Ls + Nk*Lk,
	L < 1.001*Lmax.
    Keskeneräinen yritys rajata hakua. Ajatuksena oli välttää uuden kiskon asentaminen
    aiemmin asennetun päälle.
    Jokko ajatus tai toteutus on väärä.
overlap(X0,X,Y0,Y):-
	Xr is round(X),
	Yr is round(Y),
	X0r is round(X0),
	Y0r is round(Y0),
	asennettu(X0r,Xr,Y0r,Yr),!,fail.
overlap(X0,X,Y0,Y):-
	Xr is round(X),
	Yr is round(Y),
	X0r is round(X0),
	Y0r is round(Y0),
	asserta(asennettu(X0r,Xr,Y0r,Yr)).
overlap(X0,X,Y0,Y):-
	Xr is round(X),
	Yr is round(Y),
	X0r is round(X0),
	Y0r is round(Y0),
	retract(asennettu(X0r,Xr,Y0r,Yr)).

kierrokset(Kulma):-
    Kulma > -0.001,  % Ei ole käännytty kierrostakaan negatiiviseen kiertosuuntaan
    % kommentoi seuraava, jos haluat muutakin kuin kahdeksikkoja
    Kulma < 0.001,  % Ei ole käännytty kierrostakaan positiiviseen kiertosuuntaan
    % Täyskierrokset: kulman ja 2*pi:n jakojäännös on lähellä nollaa
    % Allaoleva ei ole välttämätön, jos ylläoleva ehto Kulma < 0.001 käytössä.
    Jaannos is round(10.0*Kulma) rem round(20.0*pi),
    Jaannos =:= 0.

% Rekursion lopetusehto: kaikki radat tarkastettu
% erilaisten ratkaisujen joukon alkuarvo on []
erilaiset([],[]).
erilaiset([A1|An], Eril):-
    length(A1,N), % N on radan pituus
    % rotatoidaan enintään N-1 kertaa
    rotate(A1,Ar,N),
    % Onko rotatoitu sama, kuin joku jäljelläolevista radoista?
    % ellei ole, member epäonnistuu ja ohjelma
    % peruuttaa hakemaan rotatelta toista vaihtoehtoa
    member(Ar,An),!,
    % Jos löytyy samanlainen, tarkastellaan jäljellä olevia ratoja
    erilaiset(An,Eril).
% Ellei löytynyt samanlaista, ylläoleva failaa ja
% tullaan tähän vaihtoehtoon.
% Lisätään rata A1 ratkaisujen joukkoon ja siirrytään tarkastelemaan
% jäljelläolevia
erilaiset([A1|An], [A1|Eril]):-
    erilaiset(An,Eril),!.
rotate(A,B,_):- rotatelist(A,B).
rotate(A,B,N):-
    N > 2,
    M is N-1,
    rotatelist(A,Ar),
    rotate(Ar,B,M).
rotatelist([H|T], R):- append(T, [H], R).

demo:-
    erilaiset([[a,a,b,b],[a,b,a,b],[a,b,b,a],[b,a,b,a]],B),
    write(B), nl.

piirraRadat([],_,_,_).
piirraRadat([Rata1|Loput],M,Ns, Nk):-
    piirraRata(Rata1,M,Ns,Nk),!,
    N is M+1,
    piirraRadat(Loput,N,Ns,Nk).

piirraRata(Rata,N,Ns,Nk):-
    open('tmp.dat', write, Fil),
    radanpisteet(Fil,0,0,0,Rata,5,Minx,Maxx,Miny,Maxy),
    close(Fil),
    % kasataan linux-komento
    cmdline(Cmd,Minx,Maxx,Miny,Maxy,N,Ns,Nk),
    % ja suoritetaan se. Jostain syystä popen toimii, mutta spawn ei.
    popen(Cmd, write, Kuva),
    close(Kuva).

% Viimeinenkin kisko käsitelty; rekursion loppu.
% Alkuarvo 0 x:n ja y:n ääriarvoille
radanpisteet(Fil,X,Y,Kulma,[],_,0,0,0,0):-
    % pieni poikkiviiva kiskon loppuun
    tick(Fil,X,Y,Kulma).
radanpisteet(Fil,X0,Y0,Kulma0,[suora|Loput],_,Minx,Maxx,Miny,Maxy):- !,
    % pieni poikkiviiva kiskon alkuun
    tick(Fil,X0,Y0,Kulma0),
    kisko(suora,L,_),
    X is X0 + L*cos(Kulma0),
    Y is Y0 + L*sin(Kulma0),
    tick(Fil,X,Y,Kulma0),
    radanpisteet(Fil,X,Y,Kulma0,Loput,5,Minx0,Maxx0,Miny0,Maxy0),
    % toiminee myös: Minx = min(Minx0,X).
    min_list([Minx0,X],Minx),
    max_list([Maxx0,X],Maxx),
    min_list([Miny0,Y],Miny),
    max_list([Maxy0,Y],Maxy).
% Pikkukäännöksistä viimeinen
radanpisteet(Fil,X0,Y0,Kulma0,[vasen|Loput],0,Minx,Maxx,Miny,Maxy):- !,
    tick(Fil,X0,Y0,Kulma0),
    radanpisteet(Fil,X0,Y0,Kulma0,Loput,5,Minx,Maxx,Miny,Maxy).
% Pikkukäännös vasemmalle
radanpisteet(Fil,X0,Y0,Kulma0,[vasen|Loput],NPienetKaannokset,Minx,Maxx,Miny,Maxy):-
    write(Fil, X0), write(Fil,' '), write(Fil, Y0),nl(Fil),
    pikkukaarre(DKulma,L),
    Kulma is Kulma0+DKulma,
    X is X0+L*cos(Kulma),
    Y is Y0+L*sin(Kulma),
    M is NPienetKaannokset-1,
    radanpisteet(Fil,X,Y,Kulma,[vasen|Loput],M,Minx0,Maxx0,Miny0,Maxy0),
    min_list([Minx0,X],Minx),
    max_list([Maxx0,X],Maxx),
    min_list([Miny0,Y],Miny),
    max_list([Maxy0,Y],Maxy).
% Käännöksetn oikealle kuten vasemmallekin
radanpisteet(Fil,X0,Y0,Kulma0,[oikea|Loput],0,Minx,Maxx,Miny,Maxy):- !,
    tick(Fil,X0,Y0,Kulma0),
    radanpisteet(Fil,X0,Y0,Kulma0,Loput,5,Minx,Maxx,Miny,Maxy).
radanpisteet(Fil,X0,Y0,Kulma0,[oikea|Loput],NPienetKaannokset,Minx,Maxx,Miny,Maxy):- !,
    write(Fil, X0), write(Fil,' '), write(Fil, Y0),nl(Fil),
    pikkukaarre(DKulma,L),
    Kulma is Kulma0-DKulma,
    X is X0+L*cos(Kulma),
    Y is Y0+L*sin(Kulma),
    M is NPienetKaannokset-1,
    radanpisteet(Fil,X,Y,Kulma,[oikea|Loput],M,Minx0,Maxx0,Miny0,Maxy0),
    min_list([Minx0,X],Minx),
    max_list([Maxx0,X],Maxx),
    min_list([Miny0,Y],Miny),
    max_list([Maxy0,Y],Maxy).
tick(Fil,X,Y,Kulma):-
    L is 25,
    write(Fil, X), write(Fil,' '), write(Fil, Y),nl(Fil),
    X1 is X + L*sin(Kulma),
    Y1 is Y - L*cos(Kulma),
    write(Fil, X1), write(Fil,' '), write(Fil, Y1),nl(Fil),
    X2 is X - L*sin(Kulma),
    Y2 is Y + L*cos(Kulma),
    write(Fil, X2), write(Fil,' '), write(Fil, Y2),nl(Fil),
    write(Fil, X), write(Fil,' '), write(Fil, Y),nl(Fil).
% Muodostetaan linux-komento radan piirtämistä varten
cmdline(Cmd,Minx0,Maxx0,Miny0,Maxy0,RadanNumero,Ns, Nk):-
    Dx is abs(Minx0-Maxx0),
    Dy is abs(Miny0-Maxy0),
    Minx is 1.05*Minx0,
    Miny is 1.05*Miny0,
    % x- ja y-asteikon pitää olla samoja ettei kuva vääristy
    D = max(Dx,Dy),
    Maxx is D + Minx + 0.05*D,
    Maxy is D + Miny + 0.05*D,
    format_to_atom(Cmd,
    'graph -h 0.8 -w 0.8 -u 0.1 -r 0.1 -T png  -W 0.02 -L "max. ~d suoraa ja ~d kaarretta" --bg-color=yellowgreen -x ~f ~f -y ~f ~f tmp.dat > plotsTmp/plot~d.png',
    [Ns,Nk,Minx, Maxx, Miny, Maxy, RadanNumero]).
% Apukomento
rc:- consult(['junarata.prolog','kuvat.prolog']).
rc1:- consult(['junarata.prolog']).
rc2:- consult(['kuvat.prolog']).


Selityksiä ylläolevaan

Lattialla on M kappaletta junaradan kaarteita ja N kappaleiden suoria kiskoja. Minkämuotoisia ratoja niistä voi rakentaa?


Määritellään aluksi radan osien muoto. Meidän tarvitsee tietää suoran pituus jossain mittayksiköissä.Kaaresta meidän täytyy tietää pituus ja paljon yksi kaarre kääntää radan suuntaa. Tiedot saa helpoiten rakentamalla kaarrepaloista ympyrän ja mittaamalla sen halkaisijan radan keskipisteestä. Radan piirtämisen apuvälineeksi määrittelin vielä pikkukaarteen, eli kaarrepalan viidesosan.

Pienellä lisätyöllä ohjelmasta saisi yleispätevämmän niin, että valikoimaan olisi helppo lisätä muunkin laisia osia

Tämä viimeisin versio kelpuuttaa vain sellaiset radat, joissa kurvataan myötä- ja vastapäivään yhtä paljon, eli radat ovat jonkin tapaisia kahdeksikkoja.


 #  "asennettu-predikaatti on osa keskenjäänyttä yritystä tehostaa ratkaisujen hakua.
 # :- dynamic(asennettu/4).

 #  kiskon palan pituus (siirtymä xy-koordinaatistossa), kääntymissuunta ja -kulma

kisko(suora,220.0,0.0).
kisko(vasen,L, Kulma):-
    N = 8, #  kaarrepalojen lukumäärä täydessä ympyrässä
    D = 410.0, # kaarrepaloista rakennetun ympyrän halkaisija
    Kulma is 2.0*pi/N,
    L is D*sin(Kulma/2.0).
kisko(oikea, L, Kulma):-
    N = 8, #  kaarrepalojen lukumäärä täydessä ympyrässä
    D = 410.0, # kaarrepaloista rakennetun ympyrän halkaisija
    Kulma is -2.0*pi/N,
    L is D*sin(-Kulma/2.0).

 #  rata-alueen rajat millimetreinä xy-koordinaatistossa.
 #  Tämän verran on lattialla tilaa. Rakentaminen aloitetaan pisteestä (0,0)
alue(X, Y):-
    X > -1000.0,  X < 1000.0,
    Y > -600.0,  Y < 600.0.

 #  Radan piirtämisen avuksi
pikkukaarre(Kulma,L):-
    N = 40,
    D = 410.0,
    Kulma is 2.0*pi/N,
    L is D*sin(Kulma/2.0).

 #  Muut osat: Risteykset, sillat yms. pitäisi ottaa mukaan,
 #    jos haluaisi tehdä jotain käytännön kannalta mielenkiintoista.


Haetaan kaikki erilaiset vaihtoehtoiset radat. Ohjelma lähtee alkupisteestä (0,0) kokeilemaan, millaisia kiskoja yhdistelemällä päästään takaisin alkupisteeseen ennen kuin käytettävissä olevat kiskot loppuvat. Ohjelma hakee kaikki erilaiset kombinaatiot. Harmi kyllä ohjelmani ensimmäinen vaihee katsoo erilaiseksi myös sellaiset vaihtoehdot, jotka saadaan pyöräyttämällä rataa.

Esimerkiksi kahdesta suorasta ja kahdeksasta kaaresta saa kaksi erilaista rataa: ympyrän tai kaksi puoliympyrää yhdistettynä kahdella suoralla. Ohjelmani mielestä seuraavat ovat erilaisia ratoja: 1) Aloitetaan suoralla, 2) aloitetaan kaarella ja lisätään sitten suora, 3) Aloitetaan kahdella kaarella ja lisätään sitten suora, 4) Aloitetaan neljällä kaarella ja laitetaan sitten suora. Todellisuudessa nämä tulevat kuviin vain eri asentoihin. Tein suodattimen, joka karsii tällaiset ratkaisut pois. Sellainen oli helppo ohjelmoida, mutta on laskennallisesti raskasta luoda valtavasti ratkaisuja ja karsia niistä sitten suurin osa pois. Turhien ratkaisujen määrä kasvanee eksponentiaalisesti käytettävissä olevien kiskojen lukumäärän funktiona.

 #  Nsuorat: Käytettävissä olevien suorien kiskojen lukumäärä
 #  Nkaarteet: Käytettävissä olevien kaarteiden lukumäärä
radat(Nsuorat, Nkaarteet):-
 #    retractall(asennettu/4),
    #  haetaan kaikki erilaiset radat
    setof(Rata, rata(Nsuorat, Nkaarteet, Rata), Radat),
    #  seulotaan pois ne, jotka saa pyöräyttämällä jotain toista rataa
    erilaiset(Radat, Eril),
    nl,
    length(Radat,M),
    length(Eril,N),
    write(['kaikki vs. erilaiset ',M, N]),nl,
    kuvat(N),
    piirraRadat(Eril,1, Nsuorat, Nkaarteet).


Suunnitellaan yksi kelvollinen rata. Aloitetaan origosta suuntaan 0 (=itään) ja lisätään rekursiivisesti kisko kerrallaan. Jos tarjolla on suoria kiskoja, aloitetaan sellaisella. Muuten otetaan kaarre vasempaan. Näillä valinnoilla rajoitetaan turhia saman näköisiä ratkaisuja.

 #  Ns0: käytettävissä olevien suorien kiskojen lukumäärä
 #  Nk0: käytettävissä olevien kaarrekiskojen lukumäärä
 #  Kiskot: Järjestetty luettelo käytetyistä kiskoista. Esim  [suora,vasen,vasen,oikea,...]
rata(Ns0, Nk0, [suora|Kiskot]):-
    Ns0 > 0,!,
    Ns is Ns0 - 1,
    kisko(suora, L, Kulma),
    kiskot(Kulma, L, 0.0, Ns, Nk0, Kiskot).

rata(0, Nk0, [vasen|Kiskot]):-
    Nk is Nk0 - 1,
    kisko(vasen, L, Kulma),
    X is L*cos(Kulma),
    Y is L*sin(Kulma),
    kiskot(Kulma,X,Y,0, Nk,Kiskot).
 #   kiskot(0.0, 0.0, 0.0, Ns, Nk, Rata).


Voimme lopettaa, jos 1) olemme palanneet origoon, 2) rintamasuuntamme on tehnyt yhden tai useamman täyden kierroksen niin, että kiskojen päät sopivat yhteen eikä 3) kiskoja ole ylen määrin jäljellä. Kaksi suoraa saa yleensä sovitettua rataan halkaisemalla sen sopivasta kohdasta. Neljä kaarretta saa lisättyä rataan vastaavaan tapaan kuin kaksi suoraa. Sen sijaan pitää hyväksyä, että yksi suora voi jäädä yli tai jopa kolme kaarretta, kun halutaan radasta umpinaiden.

Pyöristän x- ja y-koordinaatin kokonaisluvuksi ja tarkistan, olemmeko origossa. Kun käytän mittayksikkönä millimetrejä, tämä tarkoittaa, että ratojen pään pitää osua millilleen kohdalleen eli laskuissa ei saa kertyä paljoakaan virhettä. Vähempikin tarkkuus riittäisi.

Tämä on siis rekursion loppuehto. Prolog kokeilee eri vaihtoehtoja siinä järjestyksessä, kuin ne ovat koodissa, joten rekursion lopetusehdon pitää olla ensimmäisenä.


kiskot(Kulma, X, Y, Ns, Nk, []):-
    #  Ollaanko origossa?
    round(X) =:= 0.0,
    round(Y) =:= 0.0,
    Nk < 4,
    Ns < 2,
	% Onko tehty nolla (=kahdeksikko) tai useampi tasakierros vastapäivään?
    kierrokset(Kulma),
 # 	write([Nk,Ns,Kulma,X,Y]),nl,
    #  Laitetaan näytölle piste aina kun löydetään uusi ratkaisu
    put_char('.'),
    flush_output.


Kokeillaan lisätä kaari oikealle.

Esimerkiksi kahdeksasta kaaresta voi rakentaa ympyrän kääntymällä aina oikealle tai kääntymällä aina vasemmalle. Radat ovat toistensa peilikuvia eli käytännössä samanlaisia. Siksi kelpuutan vai radat, joissa tehdään nolla, yksi tai useampi kierros vasemmalle eli positiiviseen kiertosuuntaan eli vastapäivään. Kahdeksikko on rata, missä rintamasuunnaan muutos on loppujen lopuksi nolla.


kiskot(Kulma0,X0,Y0,Ns0, Nk0,  [Suunta|Kiskot]):-
    Nk0 >= 0, Ns0 >= 0,
    riittaakokaaret(Kulma0,Nk0),
	riittaakokiskot(Nk0,Ns0,X0,Y0),
    kisko(Suunta, L, DKulma),
    kiskolkm(Suunta,Nk0,Ns0,Nk,Ns),
    Kulma is Kulma0+DKulma,
    X is X0+L*cos(Kulma),
    Y is Y0+L*sin(Kulma),
    alue(X, Y),
 #     overlap(X0,X, Y0,Y),  (epäonnistunut/keskeneräinen yritys tehostaa hakua)
    kiskot(Kulma, X, Y, Ns, Nk, Kiskot).

kiskolkm(suora,Nk0,Ns0,Nk0,Ns):- Ns is Ns0 - 1.
kiskolkm(oikea,Nk0,Ns0,Nk,Ns0):- Nk is Nk0 - 1.
kiskolkm(vasen,Nk0,Ns0,Nk,Ns0):- Nk is Nk0 - 1.

 #  Tavoite on 0 tai useampi kierros vastapäivään joten
 #  on turha mennä niin pitkälle myötäpäivään, että kaaret loppuvat
 #  kesken takaisinkääntymisen. (Vai rajaanko liikaa?)
riittaakokaaret(Kulma0, Nk0):-
Kulma0 + Nk0*pi/4.0 > -0.0001.   #  >=0 riittävä ehto

 #  Ollaanko niin kaukana, ettei ole mitään mahdollisuutta päästä alkupisteeseen.
 riittaakokiskot(Nk,Ns,X,Y):-
	kisko(suora,Ls,_),
	kisko(vasen,Lk,_),
	L is sqrt(X**2+Y**2),
	Lmax is Ns*Ls + Nk*Lk,
	L < 1.001*Lmax.

    Keskeneräinen yritys rajata hakua. Ajatuksena oli välttää uuden kiskon asentaminen
    aiemmin asennetun päälle.
    Jokko ajatus tai toteutus on väärä.

overlap(X0,X,Y0,Y):-
	Xr is round(X),
	Yr is round(Y),
	X0r is round(X0),
	Y0r is round(Y0),
	asennettu(X0r,Xr,Y0r,Yr),!,fail.

overlap(X0,X,Y0,Y):-
	Xr is round(X),
	Yr is round(Y),
	X0r is round(X0),
	Y0r is round(Y0),
	asserta(asennettu(X0r,Xr,Y0r,Yr)).

overlap(X0,X,Y0,Y):-
	Xr is round(X),
	Yr is round(Y),
	X0r is round(X0),
	Y0r is round(Y0),
	retract(asennettu(X0r,Xr,Y0r,Yr)).



Tämä on osa sen tarkistamista, olemmeko saaneet yhden radan valmiiksi. Tarkistetaan, ollaanko tehty nolla, yksi tai useampi täysi kierros vastapäivään. Kulman pitää olla 2*π:n monikerta. Tämä toteutuu, jos kulma on suurempi kuin pieni negatiivinen luku ja jos jakojäännös kulma/π on nolla. Koska laskiessa saattaa kertyä pientä virhettä, ehto ei ehkä toteutuisi, vaikka kulma olisi melkein 2*π. Siksi kerron osoittajan ja nimittäjän kymmenellä ja pyöristän kokonaisluvuksi ennen jakojännöksen laskemista.



kierrokset(Kulma):-
    Kulma > -0.001,  #  Ei ole käännytty kierrostakaan negatiiviseen kiertosuuntaan
    #  kommentoi seuraava, jos haluat muutakin kuin kahdeksikkoja
    Kulma < 0.001,  #  Ei ole käännytty kierrostakaan positiiviseen kiertosuuntaan
    #  Täyskierrokset: kulman ja 2*pi:n jakojäännös on lähellä nollaa
    #  Allaoleva ei ole välttämätön, jos ylläoleva ehto Kulma < 0.001 käytössä.
    Jaannos is round(10.0*Kulma) rem round(20.0*pi),
    Jaannos =:= 0.


Karsitaan ne radat, jotka saadaan jostain toisesta pyöräyttämällä.

Rata esitetään listana. Esimerkiksi [vasen,vasen, vasen, vasen, suora, vasen,vasen, vasen, vasen, suora] on rata, jossa kaksi puoliympyrää on yhdistetty kahdella suoralla. Sen kanssa yhdenmuotoinen on rata [vasen, vasen, vasen, suora, vasen,vasen, vasen, vasen, suora, vasen]. Jälkimmäisen saamme edellisestä rotatoimalla sen, eri siirtämällä ensimmäisen alkion viimeiseksi.

Seuraavassa kutakin rataa rotatoidaan ja tarkistetaan, onko se rotatoituna saman lainen kuin joku joku muista radoista. Ellei ole, rotatoidaan uudestaan niin monta kerta, kuin radassa on kiskoja. Ellei löydy samanlaista, se saa jäädä kokoelmaan, muuten se karsitaan.


 #  Rekursion lopetusehto: kaikki radat tarkastettu
 #  erilaisten ratkaisujen joukon alkuarvo on []
erilaiset([],[]).

erilaiset([A1|An], Eril):-
    length(A1,N), #  N on radan pituus
    #  rotatoidaan enintään N-1 kertaa
    rotate(A1,Ar,N),
    #  Onko rotatoitu sama, kuin joku jäljelläolevista radoista?
    #  ellei ole, member epäonnistuu ja ohjelma
    #  peruuttaa hakemaan rotatelta toista vaihtoehtoa
    member(Ar,An),!,
    #  Jos löytyy samanlainen, tarkastellaan jäljellä olevia ratoja
    erilaiset(An,Eril).

 #  Ellei löytynyt samanlaista, ylläoleva failaa ja
 #  tullaan tähän vaihtoehtoon.
 #  Lisätään rata A1 ratkaisujen joukkoon ja siirrytään tarkastelemaan
 #  jäljelläolevia
erilaiset([A1|An], [A1|Eril]):-
    erilaiset(An,Eril),!.


rotate(A,B,_):- rotatelist(A,B).
rotate(A,B,N):-
    N > 2,
    M is N-1,
    rotatelist(A,Ar),
    rotate(Ar,B,M).

rotatelist([H|T], R):- append(T, [H], R).



Pieni esimerkki prologin rekursiosta.



demo:-
    erilaiset([[a,a,b,b],[a,b,a,b],[a,b,b,a],[b,a,b,a]],B),
    write(B), nl.



Pyysin prolog-tulkkia kertomaan, miten se kutsuu predikaatteja rotatelist, rotate ja member suorittaessaan ylläolevaa demo predikaattia.

 ?- spy([rotatelist,rotate,member]).
Spypoint placed on rotatelist/2
Spypoint placed on rotate/3
Spypoint placed on member/2
The debugger will first leap -- showing spypoints (debug)

| ?- demo.
 +    4    3  Call: rotate([a,a,b,b],_176,4) ? l
 +    5    4  Call: rotatelist([a,a,b,b],_200) ? l
 +    5    4  Exit: rotatelist([a,a,b,b],[a,b,b,a]) ? l
 +    4    3  Exit: rotate([a,a,b,b],[a,b,b,a],4) ? l
 +    7    3  Call: member([a,b,b,a],[[a,b,a,b],[a,b,b,a],[b,a,b,a]]) ? l
 +    7    3  Exit: member([a,b,b,a],[[a,b,a,b],[a,b,b,a],[b,a,b,a]]) ? l
 +   10    4  Call: rotate([a,b,a,b],_335,4) ? l
 +   11    5  Call: rotatelist([a,b,a,b],_359) ? l
 +   11    5  Exit: rotatelist([a,b,a,b],[b,a,b,a]) ? l
 +   10    4  Exit: rotate([a,b,a,b],[b,a,b,a],4) ? l
 +   13    4  Call: member([b,a,b,a],[[a,b,b,a],[b,a,b,a]]) ? l
 +   13    4  Exit: member([b,a,b,a],[[a,b,b,a],[b,a,b,a]]) ? l
 +   16    5  Call: rotate([a,b,b,a],_494,4) ? l
 +   17    6  Call: rotatelist([a,b,b,a],_518) ? l
 +   17    6  Exit: rotatelist([a,b,b,a],[b,b,a,a]) ? l
 +   16    5  Exit: rotate([a,b,b,a],[b,b,a,a],4) ? l
 +   19    5  Call: member([b,b,a,a],[[b,a,b,a]]) ? l
 +   19    5  Fail: member([b,b,a,a],[[b,a,b,a]]) ? l
 +   16    5  Redo: rotate([a,b,b,a],[b,b,a,a],4) ? l
 +   19    6  Call: rotatelist([a,b,b,a],_571) ? l
 +   19    6  Exit: rotatelist([a,b,b,a],[b,b,a,a]) ? l
 +   21    6  Call: rotate([b,b,a,a],_631,3) ? l
 +   22    7  Call: rotatelist([b,b,a,a],_655) ? l
 +   22    7  Exit: rotatelist([b,b,a,a],[b,a,a,b]) ? l
 +   21    6  Exit: rotate([b,b,a,a],[b,a,a,b],3) ? l
 +   16    5  Exit: rotate([a,b,b,a],[b,a,a,b],4) ? l
 +   24    5  Call: member([b,a,a,b],[[b,a,b,a]]) ? l
 +   24    5  Fail: member([b,a,a,b],[[b,a,b,a]]) ? l
 +   16    5  Redo: rotate([a,b,b,a],[b,a,a,b],4) ? l
 +   21    6  Redo: rotate([b,b,a,a],[b,a,a,b],3) ? l
 +   24    7  Call: rotatelist([b,b,a,a],_708) ? l
 +   24    7  Exit: rotatelist([b,b,a,a],[b,a,a,b]) ? l
 +   26    7  Call: rotate([b,a,a,b],_768,2) ? l
 +   27    8  Call: rotatelist([b,a,a,b],_792) ? l
 +   27    8  Exit: rotatelist([b,a,a,b],[a,a,b,b]) ? l
 +   26    7  Exit: rotate([b,a,a,b],[a,a,b,b],2) ? l
 +   21    6  Exit: rotate([b,b,a,a],[a,a,b,b],3) ? l
 +   16    5  Exit: rotate([a,b,b,a],[a,a,b,b],4) ? l
 +   29    5  Call: member([a,a,b,b],[[b,a,b,a]]) ? l
 +   29    5  Fail: member([a,a,b,b],[[b,a,b,a]]) ? l
 +   16    5  Redo: rotate([a,b,b,a],[a,a,b,b],4) ? l
 +   21    6  Redo: rotate([b,b,a,a],[a,a,b,b],3) ? l
 +   26    7  Redo: rotate([b,a,a,b],[a,a,b,b],2) ? l
 +   26    7  Fail: rotate([b,a,a,b],_756,2) ? l
 +   21    6  Fail: rotate([b,b,a,a],_619,3) ? l
 +   16    5  Fail: rotate([a,b,b,a],_482,4) ? l
 +   17    6  Call: rotate([b,a,b,a],_520,4) ? l
 +   18    7  Call: rotatelist([b,a,b,a],_544) ? l
 +   18    7  Exit: rotatelist([b,a,b,a],[a,b,a,b]) ? l
 +   17    6  Exit: rotate([b,a,b,a],[a,b,a,b],4) ? l
 +   20    6  Call: member([a,b,a,b],[]) ? l
 +   20    6  Fail: member([a,b,a,b],[]) ? l
 +   17    6  Redo: rotate([b,a,b,a],[a,b,a,b],4) ? l
 +   20    7  Call: rotatelist([b,a,b,a],_597) ? l
 +   20    7  Exit: rotatelist([b,a,b,a],[a,b,a,b]) ? l
 +   22    7  Call: rotate([a,b,a,b],_657,3) ? l
 +   23    8  Call: rotatelist([a,b,a,b],_681) ? l
 +   23    8  Exit: rotatelist([a,b,a,b],[b,a,b,a]) ? l
 +   22    7  Exit: rotate([a,b,a,b],[b,a,b,a],3) ? l
 +   17    6  Exit: rotate([b,a,b,a],[b,a,b,a],4) ? l
 +   25    6  Call: member([b,a,b,a],[]) ? l
 +   25    6  Fail: member([b,a,b,a],[]) ? l
 +   17    6  Redo: rotate([b,a,b,a],[b,a,b,a],4) ? l
 +   22    7  Redo: rotate([a,b,a,b],[b,a,b,a],3) ? l
 +   25    8  Call: rotatelist([a,b,a,b],_734) ? l
 +   25    8  Exit: rotatelist([a,b,a,b],[b,a,b,a]) ? l
 +   27    8  Call: rotate([b,a,b,a],_794,2) ? l
 +   28    9  Call: rotatelist([b,a,b,a],_818) ? l
 +   28    9  Exit: rotatelist([b,a,b,a],[a,b,a,b]) ? l
 +   27    8  Exit: rotate([b,a,b,a],[a,b,a,b],2) ? l
 +   22    7  Exit: rotate([a,b,a,b],[a,b,a,b],3) ? l
 +   17    6  Exit: rotate([b,a,b,a],[a,b,a,b],4) ? l
 +   30    6  Call: member([a,b,a,b],[]) ? l
 +   30    6  Fail: member([a,b,a,b],[]) ? l
 +   17    6  Redo: rotate([b,a,b,a],[a,b,a,b],4) ? l
 +   22    7  Redo: rotate([a,b,a,b],[a,b,a,b],3) ? l
 +   27    8  Redo: rotate([b,a,b,a],[a,b,a,b],2) ? l
 +   27    8  Fail: rotate([b,a,b,a],_782,2) ? l
 +   22    7  Fail: rotate([a,b,a,b],_645,3) ? l
 +   17    6  Fail: rotate([b,a,b,a],_508,4) ? l
[[a,b,b,a],[b,a,b,a]]

(8 ms) yes
{debug}

Siirrytään piirtämään ratoja.

Prolog-kielessä !-merkki tarkoittaa, että jos ohjelma pääsee kyseisen kohdan yli, se ei enää peruuta sen yli takaisin hakemaan muita vaihtoehtoja. !-merkkiä kannattaa käyttää vain huolella harkituissa erikoistilanteissa. Tässä tapauksessa voin käyttää sitä, koska tiedän, että kunkin radan voi piirtää vain yhdellä tavalla. Hyötynä !-merkin käytöstä on, että prolog-tulkin ei tarvitse pitää muistissa !-merkin takaisia vaihtoehtoja.



piirraRadat([],_,_,_).

piirraRadat([Rata1|Loput],M,Ns, Nk):-
    piirraRata(Rata1,M,Ns,Nk),!,
    N is M+1,
    piirraRadat(Loput,N,Ns,Nk).


Kirjoitetaan kunkin kiskon alkupisteiden koordinaatit tilapäistiedostoon. Kaarteista kirjoitetaan lisäksi neljän välipisteen koordinaatit. Kiskon liitoskohtiin piirretään vielä poikkiviiva kuvion hahmottamisen helpottamiseksi. Piirroksen skaalaamiseksi haetaan samalla x- ja y-arvojen pienimmät ja suurimmat arvot. Piirtämiseen käytän linuxin graph-komentoa, joka on tarkoitettu 2D-kuvaajien piirtämiseen.


piirraRata(Rata,N,Ns,Nk):-
    open('tmp.dat', write, Fil),
    radanpisteet(Fil,0,0,0,Rata,5,Minx,Maxx,Miny,Maxy),
    close(Fil),
    #  kasataan linux-komento
    cmdline(Cmd,Minx,Maxx,Miny,Maxy,N,Ns,Nk),
    #  ja suoritetaan se. Jostain syystä popen toimii, mutta spawn ei.
    popen(Cmd, write, Kuva),
    close(Kuva).


Radan pisteiden laskeminen on pitkälti radan suunnittelun toisintoa. Eroina on se, että pisteitä ei kerätä listaan vaan kirjoitetaan tiedostoon ja se, että lasketaan x.lle ja y:lle ääriarvot.

Kaarien välipisteiden laskeminen on ehkä vähän hämmentävää. Lisälaskurin avulla tehdään yhden ison sijasta viisi pientä käännöstä.


 #  Viimeinenkin kisko käsitelty; rekursion loppu.
 #  Alkuarvo 0 x:n ja y:n ääriarvoille
radanpisteet(Fil,X,Y,Kulma,[],_,0,0,0,0):-
    #  pieni poikkiviiva kiskon loppuun
    tick(Fil,X,Y,Kulma).

radanpisteet(Fil,X0,Y0,Kulma0,[suora|Loput],_,Minx,Maxx,Miny,Maxy):- !,
    #  pieni poikkiviiva kiskon alkuun
    tick(Fil,X0,Y0,Kulma0),
    kisko(suora,L,_),
    X is X0 + L*cos(Kulma0),
    Y is Y0 + L*sin(Kulma0),
    tick(Fil,X,Y,Kulma0),
    radanpisteet(Fil,X,Y,Kulma0,Loput,5,Minx0,Maxx0,Miny0,Maxy0),
    #  toiminee myös: Minx = min(Minx0,X).
    min_list([Minx0,X],Minx),
    max_list([Maxx0,X],Maxx),
    min_list([Miny0,Y],Miny),
    max_list([Maxy0,Y],Maxy).

 #  Pikkukäännöksistä viimeinen
radanpisteet(Fil,X0,Y0,Kulma0,[vasen|Loput],0,Minx,Maxx,Miny,Maxy):- !,
    tick(Fil,X0,Y0,Kulma0),
    radanpisteet(Fil,X0,Y0,Kulma0,Loput,5,Minx,Maxx,Miny,Maxy).

 #  Pikkukäännös vasemmalle
radanpisteet(Fil,X0,Y0,Kulma0,[vasen|Loput],NPienetKaannokset,Minx,Maxx,Miny,Maxy):-
    write(Fil, X0), write(Fil,' '), write(Fil, Y0),nl(Fil),
    pikkukaarre(DKulma,L),
    Kulma is Kulma0+DKulma,
    X is X0+L*cos(Kulma),
    Y is Y0+L*sin(Kulma),
    M is NPienetKaannokset-1,
    radanpisteet(Fil,X,Y,Kulma,[vasen|Loput],M,Minx0,Maxx0,Miny0,Maxy0),
    min_list([Minx0,X],Minx),
    max_list([Maxx0,X],Maxx),
    min_list([Miny0,Y],Miny),
    max_list([Maxy0,Y],Maxy).

 #  Käännöksetn oikealle kuten vasemmallekin
radanpisteet(Fil,X0,Y0,Kulma0,[oikea|Loput],0,Minx,Maxx,Miny,Maxy):- !,
    tick(Fil,X0,Y0,Kulma0),
    radanpisteet(Fil,X0,Y0,Kulma0,Loput,5,Minx,Maxx,Miny,Maxy).

radanpisteet(Fil,X0,Y0,Kulma0,[oikea|Loput],NPienetKaannokset,Minx,Maxx,Miny,Maxy):- !,
    write(Fil, X0), write(Fil,' '), write(Fil, Y0),nl(Fil),
    pikkukaarre(DKulma,L),
    Kulma is Kulma0-DKulma,
    X is X0+L*cos(Kulma),
    Y is Y0+L*sin(Kulma),
    M is NPienetKaannokset-1,
    radanpisteet(Fil,X,Y,Kulma,[oikea|Loput],M,Minx0,Maxx0,Miny0,Maxy0),
    min_list([Minx0,X],Minx),
    max_list([Maxx0,X],Maxx),
    min_list([Miny0,Y],Miny),
    max_list([Maxy0,Y],Maxy).


tick(Fil,X,Y,Kulma):-
    L is 25,
    write(Fil, X), write(Fil,' '), write(Fil, Y),nl(Fil),
    X1 is X + L*sin(Kulma),
    Y1 is Y - L*cos(Kulma),
    write(Fil, X1), write(Fil,' '), write(Fil, Y1),nl(Fil),
    X2 is X - L*sin(Kulma),
    Y2 is Y + L*cos(Kulma),
    write(Fil, X2), write(Fil,' '), write(Fil, Y2),nl(Fil),
    write(Fil, X), write(Fil,' '), write(Fil, Y),nl(Fil).

 #  Muodostetaan linux-komento radan piirtämistä varten
cmdline(Cmd,Minx0,Maxx0,Miny0,Maxy0,RadanNumero,Ns, Nk):-
    Dx is abs(Minx0-Maxx0),
    Dy is abs(Miny0-Maxy0),
    Minx is 1.05*Minx0,
    Miny is 1.05*Miny0,
    #  x- ja y-asteikon pitää olla samoja ettei kuva vääristy
    D = max(Dx,Dy),
    Maxx is D + Minx + 0.05*D,
    Maxy is D + Miny + 0.05*D,
    format_to_atom(Cmd,
    'graph -h 0.8 -w 0.8 -u 0.1 -r 0.1 -T png  -W 0.02 -L "max. ~d suoraa ja ~d kaarretta" --bg-color=yellowgreen -x ~f ~f -y ~f ~f tmp.dat > plotsTmp/plot~d.png',
    [Ns,Nk,Minx, Maxx, Miny, Maxy, RadanNumero]).

 #  Apukomento
rc:- consult(['junarata.prolog','kuvat.prolog']).
rc1:- consult(['junarata.prolog']).
rc2:- consult(['kuvat.prolog']).



../xml/Programming/Prolog/plots/plot16.png
rata 16
../xml/Programming/Prolog/plots/plot15.png
rata 15
../xml/Programming/Prolog/plots/plot14.png
rata 14
../xml/Programming/Prolog/plots/plot13.png
rata 13
../xml/Programming/Prolog/plots/plot12.png
rata 12
../xml/Programming/Prolog/plots/plot11.png
rata 11
../xml/Programming/Prolog/plots/plot10.png
rata 10
../xml/Programming/Prolog/plots/plot9.png
rata 9
../xml/Programming/Prolog/plots/plot8.png
rata 8
../xml/Programming/Prolog/plots/plot7.png
rata 7
../xml/Programming/Prolog/plots/plot6.png
rata 6
../xml/Programming/Prolog/plots/plot5.png
rata 5
../xml/Programming/Prolog/plots/plot4.png
rata 4
../xml/Programming/Prolog/plots/plot3.png
rata 3
../xml/Programming/Prolog/plots/plot2.png
rata 2
../xml/Programming/Prolog/plots/plot1.png
rata 1

Lataa tästä ohjelman tämän hetkinen versio: junarata_0.prolog

Fri Feb 16 12:20:42 2018





(Helmikuu 2016 )

Polyrytmistä rummutusta Sonic Pi:llä

Miten tuottaa ohjelmallisesti jotain soiton tapaista muutamalla yksinkertaisella säännöllä ja ripauksella satunnaisuutta?


Tällaista sain aikaiseksi pienellä ohjelmanpätkällä. Ohjelma pysyy väsymättä tyylilleen uskollisena, mutta ihan yhtä ja samaa se ei soita. Pieniä yllätyksiä voi kuulla vielä tunninkin kuuntelun jälkeen. Tuollaiset yllätykset jäävät tietysti kuulijalta kokematta, ellei hän pidä tyylistä. Tämän jutun tarkoitus onkin innostaa lukijaa ohjelmoimaan oman tyylistä musiikkia.

Musiikin teoriaa en paljoa tunne, mutta afrikkalaiseen rummutukseen perehtyessäni opin, että polyrytmisyys voi tuoda eloa muuten yksitoikkoiseen jumputukseen. Laitoin siis ohjelmani soittamaan useampaa perusjumputusta kutakin omaan tahtiinsa.

Perusjumpukseni tahdit jaan kolmeen, neljään tai viiteen tasaväliseen iskuun. Jotenkin tuohon tyylin olen ymmärtänyt nuottejakin ripoteltavan tahdin sisään. Käytän sääntöä, että tahdin alussa on hyvä olla muita lujempi isku ja tahdin "keskivaiheilla" voi olla vähän vaimeampi isku ja vielä voi lyödä kevyesti pari kertaa tahdin muissa kohdissa.

Ylätason rytmi on kiinteä. Tempoa ohjelmani vaihtelee -- luullakseni. Tahtien jakoa iskuihin ja iskujen voimakkuuksia ohjelma vaihtaa satunnaisesti ylätason syklin vaihtuessa.

Musiikista enemmän ymmärtävä keksisi varmaan helposti sääntöjä, jotka tekisivät soitosta mielenkiintoisempaa. Saattaa olla, että teen jotain hölmösti ja pienellä musiikin lisäopiskelulla saisin aikaiseksi jotain jännempää.

Afrikkalaisessa musiikissa on kuulemma oma rytminsä käsille, lanteille ja jaloille. Innostuin laittamaan soittooni vielä neljännenkin rytmin, joten tämän tahdissa voisi afrikkalaisenkin olla vaikea tanssia.

Ohjelmanpätkääni tuli lopulta rivejä melkein 300, mutta ei se silti kovin monimutkainen ole. Ohjelma toimii, mutta saattaa siinä silti olla virheitä. Jos et siis ymmärrä jotain kohtaa, joko 1) et ymmärrä, 2) minä en ole ymmärtänyt.

# Ohjelma luo polyrytmistä rummutusta, joka ainakin minun epämusikaalisiini korviini
# kuulostaa aika ajoin musiikilta.
# Ohjelmassa neljä "rumpalia" paukuttaa kukin eri tahtiin yksinkertaista
# rytmikuviota. Polyrytmisyyden idea on se, että eri tahtisten paukutusten
# yhdistäminen, interferenssi, saattaa tuottaa mielenkiintoisia rytmejä.
# En tunne musiikkia enkä musiikin termistöä, joten siltä osin tästä ei
# kannata ottaa oppia

# Käytössä olevan lyömäsoittimet
allDrums = [:sn_dub,
            :sn_dolf,
            :sn_zome,
            :bd_ada,
            :bd_pure,
            :bd_808,
            :bd_zum,
            :bd_gas,
            :bd_sone,
            :bd_haus,
            :bd_zome,
            :bd_boom,
            :bd_klub,
            :bd_fat,
            :bd_tek,
            :drum_heavy_kick,
            :drum_tom_mid_soft,
            :drum_tom_mid_hard,
            :drum_tom_lo_soft,
            :drum_tom_lo_hard,
            :drum_tom_hi_soft,
            :drum_tom_hi_hard,
            :drum_splash_soft,
            :drum_splash_hard,
            :drum_snare_soft,
            :drum_snare_hard,
            :drum_cymbal_soft,
            :drum_cymbal_hard,
            :drum_cymbal_open,
            :drum_cymbal_closed,
            :drum_cymbal_pedal,
            :drum_bass_soft,
            :drum_bass_hard,
            :perc_bell,
            :perc_snap,
            :perc_snap2,
            :sn_dub,
            :sn_dolf,
            :sn_zome]

# Jollain näistä lyödaan tahdin pääisku. Esim 3 tarkoittaa ylläolevan listan
# neljättä soitinta (Indeksointi alkaa 0:sta, vähennä 10 rivinumerosta)
drum_hard = [3,8,9,10,14,17,19,21,32]

# Jollain näistä lyödään tahdin keskivaiheilla "puoli-isku"
drum_mid =  [6,15,16,20,31]

# Jollain näistä lyödään toisinaan edellisten lisäksi vaimea isku
drum_low =  [4,7,11,12,13,18]

tempo = 180
use_bpm tempo

# polyrytmisyyttä on selitetty seuraavassa
# http://music.tutsplus.com/articles/introduction-to-polyrhythms--audio-2573
# Perusidea on, että tietyssä ajassa eri soittajat soittavat eri määrän tahteja.
# Esimerkiksi 4/3 polyrytmissä toinen soittaa 12 tahdin jaksossa 4 tahtia, toinen 3.
# Rytmikuvio siis toistuu 12 tahdin mittaisissa jaksoissa

# Seuraavassa dt0 on tuo jakson pituus. 4/3 polyrytmissä sen siis pitäisi olla 12 ja
# tempoa säädettäisiin muuttujalla tempo.
# Osoittautui kuitenkin helpommaksi olla välittämättä tahdin pituuden ja tempon
#välisestä yhteydestä (tempo 60 bpm -> tahti on sekunnin mittainen) ja valita
# dt0:ksi joku luku ja virittää tempo sen jälkeen sopivaksi
dt0 = 18.0

# Ohjelmassa käytetään näennäissatunnaislukuja.
# Ne ovat tilastollisesti ottaen satunnaisia, eli näyttävät satunnaisilta,
# mutta ne toistuvat joka ajolla samoina.
# Muuttamalla random_seediä, saadaan erilainen sarja satunnaislukuja ja 'kappale'
# tavallaan alkaa eri kohdasta
use_random_seed 200

# Valittavana on jakaa tahti kolmeen, neljään tai viiteen iskuun
# Tämä selviää myöhemmin
sels = [3,4,5]

# # # # # # # # # # # # # # # #

# Funktio params tuo satunnaisuutta soittoon.
# Tämän funktion antamilla parametreillä soitetaan yksi jakso.
# Kommentoin pois osan eri iskujen voimakkuuksien satunnaisuudesta
define :params do
  #  amps = [0.2, 0.4, 0.6, 0.8, 1.0]
  #  pans = [-1.0, -0.8, -0.6, 0.6, 0.8, 1.0]
  #  clhs = [0.5,0.75,1.0,1.0]
  #  cls1 = [0.25,0.5,0.75,1.0]
  #  cls2 = [0.0,0.25,0.5,0.75]

  amps = [0.25, 0.5, 0.75, 1.0]
  pans = [-1.0, -0.75, 0.75, 1.0]
  #  clhs = [0.5, 0.75, 1.0]
  #  cls1 = [0.25, 0.5, 1.0]
  #  cls2 = [0.25, 0.5, 1.0]

  # Valitaan instrumentit
  # choose valitsee listalta satunnaisesti yhden alkion
  ih = choose(drum_hard)
  im = choose(drum_mid)
  il = choose(drum_low)
  dr_h = allDrums[ih]
  dr_lh = allDrums[im]
  dr_l = allDrums[il]

  # Valitaan iskujen voimakkuudet
  # Kommentoin pois osan satunnaisuudesta
  amph = choose(amps)
  #  clh = choose(clhs)
  #  cl1 = choose(cls1)
  #  cl2 = choose(cls2)
  clh = 1.0
  cl1 = 1.0
  cl2 = 1.0
  amplh = clh*amph
  ampl1 = cl1*amplh
  ampl2 = cl2*amplh

  pan = choose(pans)

  return dr_h, dr_lh, dr_l, amph, amplh, ampl1, ampl2, pan
end


# # # # # # # # # # # # # # # # # # # # # #
# Tahdin sisällä lyödään 3, 4 tai 5 iskua seuraavien rytmikuvioiden
# mukaisesti
# Parametri n kertoo, montako tahtia per rytmijakso lyödään
# lista nn kertoo, mitkä ylätason syklin tahdeista soitetaan,
# minkä aikana pidetään taukoa
# Kolme iskua per tahti: Tasavälein pääisku - puoli-isku - kevyt isku

define :iskut3 do |nn|
  n = nn.length
  dt = dt0/n/3.0
  dtb = dt0/n
  drumh, drumlh, druml, amph, amplh, ampl1, ampl2, pan = params
  for b in nn
    if b == 1 then
      sample drumh, amp: amph, pan: pan
      sleep dt
      sample drumlh, amp: amplh, pan: -pan
      sleep dt
      sample druml, amp: ampl1, pan: -pan
      sleep dt
    else
      sleep dtb
    end
  end
end

# Neljä iskua per tahti: Tasavälein
# pääisku - kevyt isku - puoli-isku - kevyt isku

define :iskut4 do |nn|
  n = nn.length
  dt = dt0/n/4.0
  dtb = dt0/n
  drumh, drumlh, druml, amph, amplh, ampl1, ampl2, pan = params
  for b in nn
    if b == 1 then
      sample drumh, amp: amph, pan: pan
      sleep dt
      sample druml, amp: ampl1, pan: -pan
      sleep dt
      sample drumlh, amp: amplh, pan: -pan
      sleep dt
      sample druml, amp: ampl2, pan: pan
      sleep dt
    else
      sleep dtb
    end
  end
end

# Viisi iskua per tahti: Tasavälein
# pääisku - kevyt isku - puoli-isku - kevyt isku - kevyt isku

define :iskut5 do |nn|
  n = nn.length
  dt = dt0/n/5.0
  dtb = dt0/n
  drumh, drumlh, druml, amph, amplh, ampl1, ampl2, pan = params
  for b in nn
    if b == 1 then
      sample drumh, amp: amph, pan: pan
      sleep dt
      sample druml, amp: ampl1, pan: -pan
      sleep dt
      sample drumlh, amp: amplh, pan: -pan
      sleep dt
      sample druml, amp: ampl2, pan: pan
      sleep dt
      sample druml, amp: ampl2, pan: pan
      sleep dt
    else
      sleep dtb
    end
  end
end

# # # # # # # # # # # # # # # # # # # # # #

# Tehosteella gverb saadaan rummutus kaikumaan
with_fx :gverb, room: 20.0, spread: 1.0, release: 3, mix: 0.5, damp: 0.5 do

  # Kullekin rummulle käynnistetään oma säie - thread.
  # Kutakin säiettä suoritetaan rinnakkain samanaikaisesti

  # # # # # # # # # # # # # # # # # # # # # #
  in_thread do
    sync :start3 # Tämä säie odottaa synkronointikäskyä :start3
    nn = [1,1,1] # kolme tahtia ylätason syklissä, soitetaan kaikki
    # seuraavan silmukan kierros vastaa yhtä polyrytmin sykliä
    loop do
      # Valitaan, montako iskua per tahti lyödään tässä syklissä
      sel = choose(sels)
      if sel = 3 then
        iskut3(nn)
      else
        if sel = 4 then
          iskut4(nn)
        else
          iskut5(nn)
        end
      end
    end
  end

  in_thread do
    sync :start4 # säie odottaa synkronointikäskyä :start4
    n_tahti = 4
    nn = [1,1,1,1]
    loop do
      sel = choose(sels)
      if sel = 3 then
        iskut3(nn)
      else
        if sel = 4 then
          iskut4(nn)
        else
          iskut5(nn)
        end
      end
    end
  end

  in_thread do
    sync :start5 # säie odottaa synkronointikäskyä :start5
    n_tahti = 5
    nn = [1,1,1,1,1]
    loop do
      sel = choose(sels)
      if sel = 3 then
        iskut3(nn)
      else
        if sel = 4 then
          iskut4(nn)
        else
          iskut5(nn)
        end
      end
    end
  end

  in_thread do
    sync :start7
    n_tahti = 7
    nn = [1,1,1,1,1,1,1]
    loop do
      sel = choose(sels)
      if sel = 3 then
        iskut3(nn)
      else
        if sel = 4 then
          iskut4(nn)
        else
          iskut5(nn)
        end
      end
    end
  end

  # # # # # # # # # # # # # # # # # # #

  ## Pääohjelma alkaa

  dt0 = 8

  # Aloitetaan rumpu kerrallaan.
  cue :start3
  sleep(3*dt0)
  cue :start4
  sleep(3*dt0)
  cue :start5
  sleep(3*dt0)
  cue :start7
  sleep(3*dt0)

  # Vaihdellaan syklin kestoa eli tempoa satunnaisin aikavälein.
  loop do
    dt0 = choose([8,10,12,14])
    print("dt0: ", dt0)
    sleep rrand(3*dt0, 6*dt0)
  end

end

 
  

Tein ohjelmani Sonic Pi:llä , jonka voi installoida Linuxiin, Windowsiin tai Maciin. Mukana tulee hyvä tutoriaali, jolla pääsee helposti ohjelmoinnin alkuun. Mukana tulee myös mielenkiintoisia oikean muusikon tekemiä esimerkkejä.



( )

Ohjelmoinnista ja ohjelmoinnin oppimisesta

Ohjelmoinnissa on monenlaista opittavaa:

— On sisäistettävä algoritminen ajattelu

— On opittava ohjelmointiympäristöjen 'nappulatekniikka'

— On opittava käytettävän ohjelmointikielen syntaksi eli kielioppi.

— On opittava tekemään laadukasta koodia, joka on ymmärrettävää ja helposti muuteltavissa ja siten uudelleenkäytettävissä.


Minun mielestäni pitäisi kehittää tapa ohjeistaa oppilaat laajentamaan ohjelman tynkää. Minusta tuntuu, että ohjeiden pitäisi olla kirjallisia, koska oppilaiden aloitettua työskentelyn sitä on vaikea keskeyttää ja keskeyttäminen häiritsee työskentelyä. Opetuksen eriyttämisestäkään ei tule mitään, jos homma hoidetaan kateederilta ohjeistaen.

Kunnollisten ohjeiden laatiminen on kuitenkin työlästä, joten yritin automatisoida niiden tuottamisen. Se kuitenkin tekee ohjeistamisesta pikkuisen jäykkää.

Sen lisäksi, että ohjeistetaan tekemään tai johdatellaan seuraamaan jonkin ohjelman tekemistä, pitää antaa ideoita vapaamuotoiseen kokeiluun ja kysymyksiä koodista. Ehkä luokan pitäisi saada yhdessä ideoida, mitä mielenkiintoista esimerkkiohjelmien aineksista voisi tehdä ja miten niitä voisi korjailla hauskemmiksi. Porukalla voisi olla hyvä tutkia, miten esimerkkiohjelmat tai joku niiden mielenkiintoinen yksityiskohta toimii.

Sekaan pitäisi vielä laittaa linkkejä, joissa esitellään esimerkkein ja ohjelmassa käytettäviä ohjelmointikielen peruspiirteitä, kuten funktio ja olio. Esimerkiksi olio on hyvin monimutkainen -- olio. Videopelit valikoituvat esimerkeiksi, koska niissä tapahtuu jotain ja niihin saa yhdistettyä matematiikkaa ja fysiikkaa. Esimerkkien videopelien perusrakenne — ikuinen silmukka, josta kutsutaan olioiden metodeja — saattaa kuitenkin olla hankala mieltää. Pygame modulin funktiot ovat myös pikkuisen hankalia ymmärtää. Mutta sellaista ohjelmointi on — kaikkea ei ole helppo ymmärtää.

Kaikesta ylläolevasta puuttuu pedagoginen näkökulma. Scratch harjoittelu mielestäni toimii niin, että lyhyen esittelyn jälkeen oppilaat voivat aloittaa ohjelmoinnin esimerkkinä tasoisia ohjeita seuraamalla. Scratch tarjoaa paljon enemmän kuin mitä esittelin esimerkissäni. Sillä voi tehdä musiikkia, animaatioita ja paljon muuta. Niistäkin pitäisi antaa jotain pientä esimerkkiä. Tilaa pitää antaa tietysti vapaalle kokeilemiselle, mutta ihan satunnainen haahuilu ei johda kuin kyllästymiseen. Olisi hienoa, jos opettaja tuntisi Scratchin niin hyvin, että osaisi opastaa kokeilijaa pääsemään eteenpäin ja löytämään itseään kiinnostavan alueen ohjelmoinnista.

Python ohjelmoinnissa alkuun pääseminen on Scrathciin verrattuna haasteellista, koska valmiita lohkoja ei ole tarjolla ja valmista ohjelmaakaan ei ole aluksi helppo hahmottaa. Esimerkkieni lisäongelma on se, että halusin hypätä suoraan johonkin mielenkiintoiseen, minkä takia jo peliohjelmieni 'tyngissä' on koodia aika paljon. Ehkä pitää tehdä vielä yksi esimerkki, jossa piirretään samanalainen monikulmio kuin alun Scratch-esimerkissä. Minusta on kuitenkin tärkeää, että ohjelmoinnin opetuksessa tutustutaan johonkin jotain 'oikeasti' tekevään ohjelmaan. Ei pelkästään opetella taitoja vaan opetellaan soveltamaan taitoja tutkivan oppimisen hengessä.

Tämän takia luovuin yrityksestä sovittaa esimerkit koulun opetussuunnitelmiin. Kaikki mielestäni mielenkiintoiset 'käytännön' ongelmat tai esimerkit edellyttävät opetussuunnitelmaan kuulumattomia tietoja ja taitoja. Vielä tärkeämpää, ne edellyttävät vahvaa taitoa soveltaa matematiikkaa ja luonnontieteitä ja kykyä ottaa haltuun kulloinkin tarvittavia taitoja ja tietoja.

Esimerkiksi fysikaalinen mallinnus ja numeerinen simulointi ovat nykymaailmassa keskeisiä menetelmiä — ilmastonmallinnus ja Angry Birds peli tarvitsevat niitä. Derivaatan perusolemus on hyvä selittää erotusosamäärän avulla, mutta ylläolevissä esimerkeissä alkuun pääsee selittämällä, että kiihtyvyys on nopeuden muutos aikayksikössä. Differentiaaliyhtälöitä ei välttämättä mainita edes lukiossa vaikka fysiikassa Newtonin peruslaki on differentiaaliyhtälö. Minun aikoinani differentiaaliyhtälöiden kurssilla oli mahdollisuus perehtyä erikoistapausten — fysiikan kursseista ja muustakin irrallisten — analyyttisten ratkaisujen määrittämiseen. Nykyään olisi mahdollisuus parissa tunnissa ottaa haltuun työkalu, jolla voi numeerisesti simuloida itse ohjelmoiden suoraviivaisen ja pyörivän liikkeen keskeiset ilmiöt ja piirtää käyriä siitä, miten energiat ja liikemäärät vaihtelevat. Ehkä teen esimerkin tuollaisesta työkalusta.

Olen innnostunut dynaamisista järjestelmistä, mutta ohjelmointi on tietysti tärkeä työkalu monilla aivan muilla aloilla. Eri aloilta pitäisi koota esimerkkejä.



Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.

sposti