Sisällysluettelo

Pelejä

Harjoitellaan peliohjelmointia pythonilla


2019-2-3

/ Heikin pohteita/ Ohjelmointia, matematiikkaa, fysiikkaa … Pelejä / Muutama python-kielinen videopeli

 

Muutama python-kielinen videopeli

Muutama python-kielinen videopeli, joiden ohjelmointi ja pelaaminen opettaa matematiikkaa ja Newtonin mekaniikkaa.

Parantelin ja kokosin yhteen pakettiin viime vuosina ohjelmoimiani pelejä. Pelini eivät ole juonellisia seikkailupelejä hienoine maisemineen vaan interaktiivisia yksinkertaisten järjestelmien simulaattoreita. Perimmäinen tarkoitukseni oli keksiä innostavia ohjelmointiharjoituksia, jotka opettavat niin ohjelmointia kuin matematiikkaa ja fysiikkaakin.

Tuotokseni eivät sellaisenaan kelvanne opetusmateriaaliksi kuin innnokkaalle harrastajalle.

Ensimmäisen peleistä kehitin tietotekniikan opetusharjoittelussa. Ideana oli tarjota videopelin runko, jota vaiheittain täydentämällä syntyy toimiva peli. Minun mielestäni ohjelmoinnin alkeisopetuksessa kannattaa antaa opiskelijoille valmis pohja, jota lähdetään kehittelemään eteenpäin. En minäkään koskaan aloita tyhjästä vaan jostain vanhasta pohjasta. Voi selittää, mitä kaikkea pohjassa on, mutta ei sitä aluksi tarvitse ymmärtää.

Jos malliksi valittu ohjelma piirtää näytölle ympyrän, voi aluksi selvittää, mistä se saa värinsä ja kokeilla muuttaa väriä. Sitten voi kokeilla siirtää ympyrän eri paikkaan. Sen jälkeen pitäisi saada ympyrä vaeltelemaan näytölle ja ehkä vaihtelemaan väriä omia aikojaan. Miten saada aikaan liikettä? Sen keksimiseen tarvinnee valmiin esimerkin, kaverin tai opettajan. Oleellista on päästä heti kokeilemaan ja nähdä tulos näytöllä.

On olemassa pelimoottoreita — pelien ohjelmointiympäristöjä, joita soveltaessa ei tarvitse ymmärtää, miten liike saadaan aikaiseksi. Minä käytän pygame-kirjastoa, joka ei piilota pelisilmukkaa, jolla liike saadaan aikaiseksi. Ohjelmoinnin oppimisen kannalta se on vähintäänkin ok, peliohjelmoinnin ymmärtämisen kannalta jopa hyväksi. Aikaa toki kannattaa varata sen tutkimiseen, miten pelisilmukka toimii.

Kun pyörylä on saatu liikkumaan näytöllä omia aikojaan, seuraavaksi pitää selvittää, miten käyttäjä voisi sitä ohjata. Kun sekin onnistuu, alkanee syntyä ideoita siitä, mitä kaikkea voi tehdä. Monimutkaisempien ideoiden toteuttaminen käy hankalaksi, ellei perehdy funktioihin, olioihin ja tietorakenteisiin. Niistä kaikista näissä ohjelmissa on esimerkkejä. Niitä on mukava opettaa, jos opiskelijat voivat saman tien kokeilla niitä pelin kaltaisessa sovelluksessa. Tarkoitus käy selväksi, kun näkee, miten paljon niistä on hyötyä. Hyvää ohjelmointityyliäkin on helpompi opettaa, kun voi kommentoida opiskelijoiden ohjelmia. (Vaikka parantelinkin koodiani, eivät ohjelmani kelpaa esimerkiksi erinomaisesta koodista.)

Pygame-kirjastosta käytän muutamia perustoimintoja. Ken innostuu peliohjelmoinnista, jaksanee kaivaa dokumentaatiosta tietoa itselleen hyödyllisistä funktiosta, vaikka vähän rankkaa se on. Olennainen osa ohjelmointia on jatkuva tarve opetella uutta.

Ensimmäinen peleistä — hyttysjahti — on harjoitus luoda liikkuvia olioita — satunnaisesti liikkuvia ja pelaajan liikuttamia. Saattaisi toimia yläasteella.

Mörssäri on peli, jonka ohjelmointi edellyttää Newtonin mekaniikan soveltamista ja differentiaaliyhtälöiden numeerista ratkaisemista. Jälkimmäiseen toki annetaan valmis algoritmi, joten opittavaksi jää algoritmin soveltaminen ja ratkaisun ihasteleminen. Saattaisi toimia yläasteella ja lukiossa. Differentiaaliyhtälöt eivät kuulune opetussuunnitelmaan, mutta ovat olennainen osa monia videopelejä, sään ennustamista, ilmastonmuutoksen ennakointia, insinööritieteitä ja melkein kaikkea muuta olennaista. Johan mekaniikan peruslakikin — F = ma — on differentiaaliyhtälö.

Kuualus opettaa soveltamaan Newtonin mekaniikkaa ja gravitaatiolakia. Lisäksi pelin myötä saa harjoitusta avaruuslennon perusteista.

Siltanosturi:n ohjailemista on hauska kokeilla. Siltanosturi on yksinkertaisen oloinen mekaaninen järjestelmä, jonka tilayhtälöiden johtaminen osoittautui niin vaikeaksi, että jouduin opiskelemaan lisää yliopistotasoista matematiikkaa.

pelien uusimmat versiot zip-arkistona

Näiden pelien kehittämistä esittelen yksityiskohtaisesti seuraavissa luvuissa. Esittelen, miten monimutkaisenkin ohjelman voi tehdä vaihe vaiheelta. Kaiken opetteleminen alusta saakka on hidasta, joten kopioidaan pohjaksi ohjelma, joka piirtää näytölle ympyrän ja lähdetään kehittelemään sitä.

(Ohjelman kehittely ei ole pelkästään uuden koodin lisäämistä vaan työn edetessä oppii uutta, mikä 'pakottaa' parantelemaan aiempaa koodia. Jatkossa pääsee vähemmälle, kun perusta on tehty kunnolla ja tyylikkäästi. Tämä teki vaiheittain etenemisen niin työlääksi dokumentoida, että automatisoin dokumentoinnin. Minulla ei ole viittä eri ohjelmaa viidestä eri työvaiheesta, vaan yksi ohjelma, johon on merkitty, missä kehityksen vaiheessa mikin osa on siihen lisätty. Kun ohjelma on 'valmis' jaan sen tekemälläni ohjelmalla viideksi eri ohjelmaksi työvaiheiden mukaan. Toimii muuten hyvin, mutta tuottaa liian paljon tekstiä.)


2015-04-25

/ Heikin pohteita/ Ohjelmointia, matematiikkaa, fysiikkaa … Pelejä / Muutama python-kielinen videopeli /Python hyttysjahdissa

 

Python hyttysjahdissa

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

Päivitys (huhtikuu 2020): Jos alat tehdä omia pelejä, tutustu arcade moduliin. Se on Pygamea uudempi ja vaikuttaa sitä kätevämmältä.

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.


/ Heikin pohteita/ Ohjelmointia, matematiikkaa, fysiikkaa … Pelejä / Muutama python-kielinen videopeli /Python hyttysjahdissa / "Hyttysjahti" oppitunnin kulku

 

"Hyttysjahti" oppitunnin kulku

Esittelyvideo pelistä

video varalle, jos yo. ei toimi

  • Katsokaa esittelyvideo pelistä
  • Lataa pelin runko käytössäsi olevaan python kehitysympäristöön (esim. Spyder tai IDLE). Tämän pitäisi onnistua klikkaamalla tästä
  • Paina F5. Näytölle pitäisi ilmestyä peliohjelman ikkuna. (Siinä ei vielä tapahdu mitään.)
  • Tutustu ohjelmaan kehitysympäristössä. Alempana tällä sivulla on selityksiä koodiin. Tutustu niihinkin. Läheskään kaikkea tästä peliohjelman rungosta ei kannata yrittää ymmärtää tässä vaiheessa.
  • Voit kokeilla muuttaa esimerkiksi ikkunan otsikkoa ja taustaväriä. Sulje peliohjelman ikkuna painamalla esc tai ruksaamalla se kiinni. Editoi koodia ja paina F5.
  • Siirry vaiheeseen 1: "Luodaan lätkä ja /webPy." Lukaise koodi ja sen selitykset ja lataa selitysten alapuolella olevasta linkistä vaiheen 1 koodi. Voit myös leikata koodin pätkät selaimen ikkunasta ja liimata ne oikeille paikoilleen ohjelmaan. Se on vaikempaa, mutta myös opettavaisempaa
  • Kokeile ohjelmaa painamalla F5. Peli-ikkunan pitäisi ja siihen ilmestyä punavihreä pyörylä, jota voi liikuttaa z ja x näppäimillä ja ylä- ja alanuolella. Yritä ymmärtää, miten ohjelma saa liikkeen aikaiseksi. Se ei ole aivan helppoa, koska tällaiset peli-ohjelmat eivät ole aivan suoraviivaisia rakenteeltaan.
  • Jatka samalla lailla jokainen vaihe lävitse. Selvitä itsellesi joka vaiheessa, miten ohjelma toimii. Tee ohjelmaan pieniä muutoksia.

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.


/ Heikin pohteita/ Ohjelmointia, matematiikkaa, fysiikkaa … Pelejä / Muutama python-kielinen videopeli /Python hyttysjahdissa /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.

Wed Apr 22 08:13:34 2020


/ Heikin pohteita/ Ohjelmointia, matematiikkaa, fysiikkaa … Pelejä / Muutama python-kielinen videopeli /Python hyttysjahdissa /HyttysJahti.py /Vaihe 0: Peliohjelman perusrakenne

 

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-koodi na – 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


/ Heikin pohteita/ Ohjelmointia, matematiikkaa, fysiikkaa … Pelejä / Muutama python-kielinen videopeli /Python hyttysjahdissa /HyttysJahti.py /Vaihe 1: Luodaan lätkä hyttysten pyydystämiseen

 

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


/ Heikin pohteita/ Ohjelmointia, matematiikkaa, fysiikkaa … Pelejä / Muutama python-kielinen videopeli /Python hyttysjahdissa /HyttysJahti.py /Vaihe 2: Luodaan hyttynen

 

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


/ Heikin pohteita/ Ohjelmointia, matematiikkaa, fysiikkaa … Pelejä / Muutama python-kielinen videopeli /Python hyttysjahdissa /HyttysJahti.py /Vaihe 3: Laitetaan hyttynen lentelemään ympäriinsä

 

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


/ Heikin pohteita/ Ohjelmointia, matematiikkaa, fysiikkaa … Pelejä / Muutama python-kielinen videopeli /Python hyttysjahdissa /HyttysJahti.py /Vaihe 4: Ohjelmoidaan hyttyneen reagoimaan lätkän osumaan

 

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


/ Heikin pohteita/ Ohjelmointia, matematiikkaa, fysiikkaa … Pelejä / Muutama python-kielinen videopeli /Python hyttysjahdissa /HyttysJahti.py /Vaihe 5: Luodaan parvi hyttysiä

 

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


2015-06-02

/ Heikin pohteita/ Ohjelmointia, matematiikkaa, fysiikkaa … Pelejä / Muutama python-kielinen videopeli /Voimia ja liikettä

 

Voimia ja liikettä

Seuraavassa pelissä ammutaan mörssärillä. Sen kuulaan vaikuttavat Maan vetovoima ja ilmanvastus . Mörssäpeliä seuraavan pelin avaruusalusta liikuttelevat Maan ja Kuun vetovoiman sekä rakettimoottorit. Siksi on hyvä kerrata, miten kappaleiden liikkeitä lasketaan.

Kesäkuu 2015

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 ole mörssäripelissä vakioita, mutta kun laskemme uudet voimat ja nopeudet aina lyhyen ajan dt välein, laskenta on niin tarkkaa, että virhettä ei huomaa.

Mörssäripelissä Maan vetovoima vaikuttaa vain pystysuuntaan — y-akselin suuntaan, mutta ilmanvastus liikettä vastaan, eli kuulan noustessa vinosti ylös ilmanvastus vaikuttaa vinosti alas ja lennon huipun jälkeen päinvastoin. Voima kannattaa jakaa x- ja y- suuntaiseen osaan ja laskea erikseen nopeudet ja siirtymät x- ja y-suuntaan. Näin syntyy kuulan rata. Selitän tämän uudellen mörssäripelin koodin esittelyn yhteydessä.

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 kuuraketti-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.

Esimerkkejä differentiaaliyhtälöiden numeerisesta ratkaisemisesta pythonilla.

Esittelyvideo seuraavasta pelistä — mörssäristä.

video varalle, jos yo. ei toimi


/ Heikin pohteita/ Ohjelmointia, matematiikkaa, fysiikkaa … Pelejä / Muutama python-kielinen videopeli /Voimia ja liikettä /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.

Wed Apr 22 08:14:27 2020


/ Heikin pohteita/ Ohjelmointia, matematiikkaa, fysiikkaa … Pelejä / Muutama python-kielinen videopeli /Voimia ja liikettä /morssari.py /v0: Ohjelman runko + mörssäri + "vuori"

 

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


/ Heikin pohteita/ Ohjelmointia, matematiikkaa, fysiikkaa … Pelejä / Muutama python-kielinen videopeli /Voimia ja liikettä /morssari.py /v1: Kuula, mörssärin laukaiseminen ja kuulan lento

 

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


/ Heikin pohteita/ Ohjelmointia, matematiikkaa, fysiikkaa … Pelejä / Muutama python-kielinen videopeli /Voimia ja liikettä /morssari.py /v2: Lisätään maali, tuuli ja tuuliviiri.

 

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


/ Heikin pohteita/ Ohjelmointia, matematiikkaa, fysiikkaa … Pelejä / Muutama python-kielinen videopeli /Voimia ja liikettä /morssari.py /v3: Pysäytetään kuula sen osuessa johonkin.

 

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


/ Heikin pohteita/ Ohjelmointia, matematiikkaa, fysiikkaa … Pelejä / Muutama python-kielinen videopeli /Voimia ja liikettä /morssari.py /v4: Lisätään kuulan räjähdys.

 

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


/ Heikin pohteita/ Ohjelmointia, matematiikkaa, fysiikkaa … Pelejä / Muutama python-kielinen videopeli /Voimia ja liikettä /morssari.py /v5: Otetaan huomioon ilmanvastus, räjäytetään maali, lisätään ääniä.

 

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


/ Heikin pohteita/ Ohjelmointia, matematiikkaa, fysiikkaa … Pelejä / Muutama python-kielinen videopeli /Voimia ja liikettä /morssari.py /???

 

???

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

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



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



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

Seuraavassa pelissä lennetään Kuuhun. Viodella ento Kuun kiertoradalle pikkuisen räpistellen

video varalle, jos yo. ei toimi


/ Heikin pohteita/ Ohjelmointia, matematiikkaa, fysiikkaa … Pelejä / Muutama python-kielinen videopeli /Voimia ja liikettä /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.

Wed Apr 22 09:45:29 2020


/ Heikin pohteita/ Ohjelmointia, matematiikkaa, fysiikkaa … Pelejä / Muutama python-kielinen videopeli /Voimia ja liikettä /KuuAlus.py /Ohjelman runko, Maa ja Kuu

 

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


/ Heikin pohteita/ Ohjelmointia, matematiikkaa, fysiikkaa … Pelejä / Muutama python-kielinen videopeli /Voimia ja liikettä /KuuAlus.py /Alus ja sen liikkuminen

 

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


/ Heikin pohteita/ Ohjelmointia, matematiikkaa, fysiikkaa … Pelejä / Muutama python-kielinen videopeli /Voimia ja liikettä /KuuAlus.py /Ohjauksen apuvälineitä

 

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



2016-4-8

/ Heikin pohteita/ Ohjelmointia, matematiikkaa, fysiikkaa … Pelejä / Muutama python-kielinen videopeli / Rekursio, säikeet, synkronointi

 

Rekursio, säikeet, synkronointi

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

puu kasvaa

video varalle, jos yo. ei toimi

ja huojuu

video varalle, jos yo. ei toimi


/ Heikin pohteita/ Ohjelmointia, matematiikkaa, fysiikkaa … Pelejä / Muutama python-kielinen videopeli / Rekursio, säikeet, synkronointi /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.

Tue Apr 14 14:01:18 2020


/ Heikin pohteita/ Ohjelmointia, matematiikkaa, fysiikkaa … Pelejä / Muutama python-kielinen videopeli / Rekursio, säikeet, synkronointi /PuutPilvet.py /-

 

-

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


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


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


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


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


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


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


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


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


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


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


Selityksiä ylläolevaan

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

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



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

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

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



Seuraavien parametrien merkitys selviää myöhemmin.

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

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



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



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



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



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



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



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


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

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

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

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

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

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

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



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


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



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

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

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

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

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


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


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

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

        if Wait:
            pygame.display.flip()

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



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

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

Pilvet liikkuvat vasemmalta oikealle.



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

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

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

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


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

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



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

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

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



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

    pygame.display.init()

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

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

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

    random.seed()

    screen.fill(Tausta)

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

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

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

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

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

    pygame.time.wait(500)

    s = 0.0075

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

    pygame.quit()

if __name__ == "__main__":

    main()



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


/ Heikin pohteita/ Ohjelmointia, matematiikkaa, fysiikkaa … Pelejä / Muutama python-kielinen videopeli / Rekursio, säikeet, synkronointi /synkronointi.py

 

synkronointi.py

Eri säikeissä toimivien ohjelmien synkronointi

Tue Apr 14 14:02:30 2020


/ Heikin pohteita/ Ohjelmointia, matematiikkaa, fysiikkaa … Pelejä / Muutama python-kielinen videopeli / Rekursio, säikeet, synkronointi /synkronointi.py /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


/ Heikin pohteita/ Ohjelmointia, matematiikkaa, fysiikkaa … Pelejä / Muutama python-kielinen videopeli / Rekursio, säikeet, synkronointi /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.

Tue Apr 14 14:01:49 2020


/ Heikin pohteita/ Ohjelmointia, matematiikkaa, fysiikkaa … Pelejä / Muutama python-kielinen videopeli / Rekursio, säikeet, synkronointi /PuutPilvetSaikeet.py /Säikeiden synkronointia

 

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


2015-05-26

/ Heikin pohteita/ Ohjelmointia, matematiikkaa, fysiikkaa … Pelejä / Muutama python-kielinen videopeli /Lisäharjoituksia

 

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.


2015-05-13

/ Heikin pohteita/ Ohjelmointia, matematiikkaa, fysiikkaa … Pelejä / Muutama python-kielinen videopeli /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

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.


/ Heikin pohteita/ Ohjelmointia, matematiikkaa, fysiikkaa … Pelejä / Muutama python-kielinen videopeli /Miten ylläolevat dokumentit tuotettiin /Python2html.py

 

Python2html.py

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

Wed Apr 22 09:52:48 2020


/ Heikin pohteita/ Ohjelmointia, matematiikkaa, fysiikkaa … Pelejä / Muutama python-kielinen videopeli /Miten ylläolevat dokumentit tuotettiin /Python2html.py /Yhdestä python-ohjelmasta eri ohjelmaversioita ja niiden dokumentaatioita

 

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 os
import sys
import ast
import time
import random
from pathlib import Path, PurePath


def tagline(tag, vari, vaihe, ln):
    if tag == 'shd':
        unique_id = projName + str(vaihe) + str(ln)
        tglin = '\n<section>\n<title id= "' + 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, curdir):
    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):
            webref = PurePath('/webPy/', curdir, str(sys.argv[i]))
            fout.write(
              '\n<p><weblink id="l' + str(random.randint(100,9999)) +
              '" webref="' + webref + ' type="downLoad">lisätiedosto: ' + str(sys.argv[i]) +
              '</weblink></p>')


def main():
    with open(xmlf, 'w') as fout:
        fout.write('<section>\n<title id="' + xmlf.as_posix() + '">' +
                   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)
            webroot = Path(os.path.expandvars("$WEBPY"))
            curdir = Path.cwd().relative_to(webroot)
            webref = PurePath('/webPy/', curdir, pfname).as_posix()
            fout.write(
              '<sp><iso><weblink  id="v' + str(random.randint(100,9999)) +
              '" webref= "' + webref + '" type= "downLoad">Lataa tästä ohjelman tämän hetkinen versio: ' +
              pfname + '</weblink></iso></sp>')
            lisafileet(fout, curdir)
            fout.write('\n</section>\n')
        fout.write('\n</section >')


if len(sys.argv) > 1:
    projName = str(sys.argv[1])
    print(projName)
else:
    projName = 'ProjektiX'
if len(sys.argv) > 2:
    maxVaihe = int(sys.argv[2])
else:
    maxVaihe = 6
xmlf = PurePath(projName + '_code.xml')
infil = Path(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 os
import sys
import ast
import time
import random
from pathlib import Path, PurePath



-
def tagline(tag, vari, vaihe, ln):
    if tag == 'shd':
        unique_id = projName + str(vaihe) + str(ln)
        tglin = '\n<section>\n<title id= "' + 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(' #  ' + repl(alkuloppu[1]) +
                                       '')
        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, curdir):
    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):
            webref = PurePath('/webPy/', curdir, str(sys.argv[i]))
            fout.write(
              '\n<p><weblink id="l' + str(random.randint(100,9999)) +
              '" webref="' + webref + ' type="downLoad">lisätiedosto: ' + str(sys.argv[i]) +
              '</weblink></p>')



a

def main():
    with open(xmlf, 'w') as fout:
        fout.write('<section>\n<title id="' + xmlf.as_posix() + '">' +
                   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)

            webroot = Path(os.path.expandvars("$WEBPY"))
            curdir = Path.cwd().relative_to(webroot)
            webref = PurePath('/webPy/', curdir, pfname).as_posix()
            fout.write(
              '<sp><iso><weblink  id="v' + str(random.randint(100,9999)) +
              '" webref= "' + webref + '" type= "downLoad">Lataa tästä ohjelman tämän hetkinen versio: ' +
              pfname + '</weblink></iso></sp>')
            lisafileet(fout, curdir)
            fout.write('\n</section>\n')
        fout.write('\n</section >')


a


if len(sys.argv) > 1:
    projName = str(sys.argv[1])
    print(projName)
else:
    projName = 'ProjektiX'

if len(sys.argv) > 2:
    maxVaihe = int(sys.argv[2])
else:
    maxVaihe = 6

xmlf = PurePath(projName + '_code.xml')
infil = Path(projName + '.py')

summary()

if __name__ == "__main__":
    main()



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