Harjoitellaan peliohjelmointia pythonilla ja opitaan samalla matematiikan ja Newtonin mekaniikan soveltamista
Heikin pohteita > Ohjelmointia, matematiikkaa, fysiikkaa … > Pelejä > Muutama python-kielinen videopeli (edit: 2019-2-3)
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ä.)
Heikin pohteita > Ohjelmointia, matematiikkaa, fysiikkaa … > Pelejä > Muutama python-kielinen videopeli > Python hyttysjahdissa (edit: 2015-04-25)
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ä.
Päivitys (helmikuu 2024): SGE on pelien ohjelmointiin tarkoitettu python-moduli. En ole kokeillut.
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. (Vuonna 2024+ python2 tuskin tulee sinulle vastaan.)
Windowsissa tai Macissa en ole ohjelmia kehitellyt, joten kommenttejani niistä ei kannata ottaa suurena viisautena.
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.Päivitys (helmikuu 2024): Anaconda on paljon käytetty python-ohjelmointiympäristö.
Installointia Maciin — minulle vieraiseen järjestelmään — en ole päässyt kokeilemaan, mutta senkään ei pitäisi olla kovin vaikeaa.
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.
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 (edit: )
Esittelyvideo pelistä
video varalle, jos yo. ei toimi
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 (edit: )
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 (edit: )
# -*- 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:
Musta = (0, 0, 0) # Mustassa ei ole mitään valoa Sin = (0, 0, 255) # vain sinistä Pun = (255, 0, 0) Vihr = (0, 255, 0) Valk = (255, 255, 255) # valkoisessa on kaikenväristä valoa Tausta = (160, 200, 255)
Ohjelmoinnissa värit voidaan esittää sinisen, vihreän ja punaisen valon yhdistelminä – RGB-koodina – väri = (punaisen määrä, vihreän määrä, sinisen määrä). Kunkin perusvärin määrä valojen sekoituksessa ilmoitetaan kokonaisluvulla väliltä 0..255. Mustassa ei ole mitään valoa ja valkoisessa on kaiken väristä valoa. (128, 128, 128) lienee keskiharmaa. Värit voisi ohjelmassa esittää toisinkin, mutta RGB–koodia käyttämällä voi tehdä hauskoja väriefektejä.
Sulkumerkkien avulla voidaan Pythonissa koota kätevästi monta asiaa yhteen – yhdeksi muuttujaksi. Jos innostut enemmän Python-ohjelmoinnista, googlaa jossain vaiheessa "python tuple". Musta, Sin, Pun, Vihr, Valk ovat muuttujien nimiä, jotka voi itse valita.
# Peli-ikkunan koko pikseleinä. Pikseli on piste näytöllä. Wx = 800 Wy = 600 Wxy = (Wx, Wy) # peli-ikkunan korkeus ja leveys
Ylläoleva kertoo, että peli-ikkunan leveys on 800 pikseliä ja korkeus 600 pikseliä. Näitä voit vapaasti muuttaa.
Pygame peli-ikkunan koordinaatisto on jostain syystä erilainen kuin matematiikassa normaalisti käytetty. Piste (0,0) on vasemmassa yläkulmassa ja piste (Wx,Wy) oikeassa alakulmassa. x siis kasvaa ihan järkevästi oikealle mentäessä, mutta y kasvaa alaspäin mentäessä.
Nayttotaajuus = 24 # Voit kokeilla eri näyttötaajuuksia
Dt = 1.0/Nayttotaajuus
Peli-ikkuna piirretään uudelleen – päivitetään – näyttötaajuus kertaa sekunnissa eli päivitysten väli on Dt sekuntia. Tavallisissa elokuvissa kuva vaihtuu 24 kertaa sekunnissa. Kunhan saamme ohjelman toimimaan, voit kokeilla, mitä näyttötaajuuden laskeminen esimerkiksi kymmeneen vaikuttaa. Millä taajuudella ihminen erottaa kuvat erillisiksi niin, että peli alkaa nykiä?
Pelin olioille pitää laskea uusi paikka ajan Dt välein, eli pitää laskea, paljonko pelin oliot liikkuvat ajassa Dt.
Jotkin Pythonin funktiot – kuten pygamen piirtofuktiot – vaativat argumenteikseen kokonaislukuja, koska kokonaisluvut ja reaaliluvut tallennetaan tietokoneen muistiin eri tavalla. Reaaliluvusta saa kokonaisluvun int-funtiolla seuraavasti: Komento x = 4/3 antaa x:lle arvon 1.33333... ja i = int(x) tai komento i = int(4/3) antaa i:lle arvon 1
# # # # # # # # # # # # # # # # # # # # # # # # # # # # Pääohjelma alkaa tästä # # # # # # # # # # # # # # # # # # # # # # # # # # #
Alustuksia, jotka kopioin saamastani malliohjelmasta. Luodaan peli-ikkuna screen ja annetaan sille nimi "Hyttysjahti".
Ilman malliohjelmaa minulta olisi mennyt ties miten pitkään ymmärtää, mitä tällaisia komentoja tarvitaan pelin alustamiseksi.
pygame.display.init() # Selvitetään peli-ikkunan koko näytöllä pikseleinä. Pikseli on piste näytöllä. D_Info = pygame.display.Info() Wx = D_Info.current_w Wy = int(0.9*D_Info.current_h) Wxy = (Wx, Wy) # peli-ikkunan korkeus ja leveys
Ylläoleva kertoo, että peli-ikkunan leveys on Wx pikseliä ja korkeus Wy pikseliä.
Pygame peli-ikkunan koordinaatisto on jostain syystä erilainen kuin matematiikassa normaalisti käytetty. Piste (0,0) on vasemmassa yläkulmassa ja piste (Wx,Wy) oikeassa alakulmassa. x siis kasvaa ihan järkevästi oikealle mentäessä, mutta y kasvaa alaspäin mentäessä.
screen = pygame.display.set_mode((Wx, Wy)) pygame.display.set_caption( "Hyttysjahti") pygame.init() clock = pygame.time.Clock() random.seed()
# # Toistetaan allaolevaa while-silmukkaa, kunnes käyttäjä sulkee peli-ikkunan # tai painaa ESC-iä # loppu = False while not loppu:
Alla komento clock.tick(Nayttotaajuus) oleva varmistaa, että tätä while-silmukkaa toistetaan Nayttotaajuus kertaa sekunnissa. Ohjelma jää odottamaan tähän, kunnes aikaa on kulunut tarpeeksi. Peliohjelman odotellessa tietokoneesi pääsee suorittamaan muita ohjelmia.
clock.tick(Nayttotaajuus)
Jos käyttäjä on sulkenut peli-ikkunan tai painanut ESC-näppäintä, asetetaan loppu todeksi niin, että tämä jää viimeiseksi while-silmukan toistoksi
for event in pygame.event.get(): if event.type == pygame.QUIT: loppu = True if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE: loppu = True
Jokaisella while-silmukan toistolla täytetään peli-ikkuna taustavärillä eli ikkuna pyyhkiytyy tyhjäksi
screen.fill(Tausta)
# näytetään kaikki yllä 'piirretty' näytöllä pygame.display.flip() # # while silmukka päättyy tähän # # Ohjelman loppu pygame.quit()
Tämä ohjelma ei vielä tee mitään, mutta näytölle olisi pitänyt ilmestyä Hyttysjahti niminen ikkuna.
Lataa tästä ohjelman tämän hetkinen versio: HyttysJahti_0.py
Heikin pohteita > Ohjelmointia, matematiikkaa, fysiikkaa … > Pelejä > Muutama python-kielinen videopeli > Python hyttysjahdissa > HyttysJahti.py > Vaihe 1: Luodaan lätkä hyttysten pyydystämiseen (edit: )
# -*- 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 (edit: )
# -*- 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ä (edit: )
# -*- 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
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 (edit: )
# -*- 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ä (edit: )
# -*- 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.
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
Heikin pohteita > Ohjelmointia, matematiikkaa, fysiikkaa … > Pelejä > Muutama python-kielinen videopeli > Voimia ja liikettä (edit: 2015-06-02)
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 (edit: )
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" (edit: )
# -*- 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 (edit: )
# -*- 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)
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:
# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = # 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. (edit: )
# -*- 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
# 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. (edit: )
# -*- 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. (edit: )
# -*- 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ä. (edit: )
# -*- 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ää.
(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)
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 > ??? (edit: )
# -*- 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 (edit: )
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 (edit: )
# -*- 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 (edit: )
# -*- 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))
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ä (edit: )
# -*- 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
Heikin pohteita > Ohjelmointia, matematiikkaa, fysiikkaa … > Pelejä > Muutama python-kielinen videopeli > Lisäharjoituksia (edit: 2015-05-26)
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.
Heikin pohteita > Ohjelmointia, matematiikkaa, fysiikkaa … > Pelejä > Muutama python-kielinen videopeli > Miten ylläolevat dokumentit tuotettiin (edit: 2015-05-13)
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 (edit: )
Tällä ohjelmalla on tuotettu tämän sivuston python-ohjelmien esittelyt.
Wed May 24 13:09:20 2023
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 (edit: )
# -*- 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 anchor="' + 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.
<orange> # -*- coding: utf-8 -*- </orange> <orange> # = = = = = = = = = = = = = = = = = = = = = = = = = = </orange><orange> # import sys </orange><orange> # print "This is the name of the script: ", sys.argv[0] </orange><orange> # print "Number of arguments: ", len(sys.argv) </orange><orange> # print "The arguments are: " , str(sys.argv) </orange><orange> # = = = = = = = = = = = = = = = = = = = = = = = = = = </orange>
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"^#|<orange> # ", line, maxsplit=1) </orange> fout.write(repl(alkuloppu[0])) if len(alkuloppu) > 1: fout.write('<orange><orange> # ' + repl(alkuloppu[1]) + </orange> '</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 anchor="' + 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()
Lataa tästä ohjelman tämän hetkinen versio: Python2html_0.py