Arcade-pelien ohjelmointi
Pythonilla ja PygamellaChapter 4: Pelaamisen satunnaisuus Random-funktion ja silmukoiden avulla
Ennen kuin siirrymme grafiikan tekemiseen tutustumme silmukoiden koodaamiseen. Silmukat ovat osana lähes jokaisessa pelissä. Silmukassa toistetaan samaa ohjelman osaa uudelleen ja uudelleen. Esimerkiksi alla olevassa esimerkissä 'loopataan' käyttäjän arvaamia lukuja:
Hei! Ajattelen kokonaislukua 1 - 100. Arvaa, mikä se on. --- Yritys 1 Arvaa, mitä lukua ajattelen: 50 Liian suuri. --- Yritys 2 Arvaa, mitä lukua ajattelen: 25 Liian suuri. --- Yritys 3 Arvaa, mitä lukua ajattelen: 17 Liian suuri. --- Yritys 4 Arvaa, mitä lukua ajattelen: 9 Liian pieni. --- Yritys 5 Arvaa, mitä lukua ajattelen: 14 Liian suuri. --- Yritys 6 Arvaa, mitä lukua ajattelen: 12 Liian suuri. --- Yritys 7 Arvaa, mitä lukua ajattelen: 10 Liian pieni. Ups, liian monta yritystä. Luku oli 11.
Mutta, mitäs tällä on tekemistä pelien grafiikan kanssa? Paljonkin. Jokainen näytön päivitys eli frame suoritetaan silmukassa. Tähän palataan siis vielä myöhemmin käsitteen frames-per-second (FPS) parissa. FPS tarkoittaa näytön päivitystaajuutta, eli kuinka monta kertaa kone päivittää näyttökuvan sekunnin aikana. Mitä suurempi FPS on, sitä jouhevammin näyttö päivittyy. (Tosin FPS taajuus 60 on jo monelle näytölle liikaa, eikä suuria FPS arvoja kannata käyttää). Kuvassa 4.1 on peli Eve Online ja grafiikkaa siitä, kuinka suureen päivitystaajuteen kone kykenee.
Peleissä olevien silmukoiden toiminta on esitetty oheisessa vuokaaviossa. Kuva 4.2. Huolimatta tietokonepelien monimutkaisuudesta, niin silmukan periaate on varsin yksinkertainen (kuten laskinohjelmassa kappaleessa 1). Luetaan käyttäjän antama syöte. Suoritetaan tarvittavat laskutoimenpiteet. Esitetään tulokset. Graafisessa pelissä tämä toistetaan 60 kertaa sekunnissa.
Silmukan sisällä voi olla silmukoita. Näitä sanotaan sisäkkäisiksi silmukoiksi. Katsotaan seuraavaa“Draw Everything” vuokaaviota. Kuva 4.2. Tämä algoritmi käy silmukassa läpi pelin jokaisen objektin ja päivittää ne. Tämä silmukka on pääsilmukan sisällä, jossa toistetaan näytön päivitys. Tämä tilanne on esitetty kuvassa 4.3.
Pythonissa on kaksi silmukkatyyppiä, for silmukat ja while silmukat. for silmukka suorittaa ennalta määrätyn määrän toistoja, kun taas while silmukka toistaa silmukkaa, kunnes lopetusehto saavutetaan (esim. pelaaja klikkaa Lopeta-painiketta).
Esimerkiksi for silmukassa voidaan tulostaa koko nimilista, kunhan listassa olevien nimien lukumäärä tiedetään. while silmukassa voitaisiin odotella esimerkiksi käyttäjän hiiren klikkausta. Odotusaikaa ei kuitenkaan ennalta tiedetä.
4.1 For silmukka
sAlla oleva for silmukkaesimerkki toistaa print lauseen viisi kertaa. Toistojen määrä olisi helppo asettaa vaikkapa arvoon 100 tai 1,000,000 muuttamalla ainoastaan range-funktion parametrin arvoa. Huomaa myös for silmukan ja if lauseen syntaksin samankaltaisuus. Molemmat päättyvät kaksoispisteeseen ja molemmissa tarvitaan lohkon lauseiden sisennys.
for i in range(5): print("En syö purukumia oppitunnilla.") |
Output:
En syö purukumia oppitunnilla. En syö purukumia oppitunnilla. En syö purukumia oppitunnilla. En syö purukumia oppitunnilla. En syö purukumia oppitunnilla. |
i rivillä 1 on niin sanottu 'juokseva muuttuja', kierroslaskuri. i:n tilalla voisi olla mikä tahansa kelvollinen muuttujan nimi, mutta i on varsin yleisesti käytetty kierroslaskurimuuttuja (i on lyhennys engl. sanasta increment = kasvattaa).
range funktio kontrolloi silmukan toistojen määrää. Tässä määrä on siis viisi.
Seuraavassa silmukassa tulostetaan sana “Saisinko,” viisi kertaa ja lause“minä mennä ostarille?” vain kerran. “minä mennä ostarille??” ei ole mukana laskurin lohkon sisennyksessä, eli se ei kuulu enää for silmukkaan ja se tulostetaan vasta for silmukan päätyttyä.
for i in range(5): print("Saisinko,") print("minä mennä ostarille?") |
Output:
Saisinko, Saisinko, Saisinko, Saisinko, Saisinko, minä mennä ostarille? |
Seuraavassa koodiesimerkissä rivi 3 onkin mukana for-silmukassa sisennettynä. Tämä muutos saa aikaan sen, että nyt molemmat tulostuslauseet suoritetaan viisi kertaa.
for i in range(5): print("Saisinko,") print("minä mennä ostarille?") |
Output:
Saisinko, minä mennä ostarille? Saisinko, minä mennä ostarille? Saisinko, minä mennä ostarille? Saisinko, minä mennä ostarille? Saisinko, minä mennä ostarille? |
Seuraava koodi tulostaa luvut 0:sta 9:ään. Huomaa, että silmukka aloitetaan 0:sta, mutta lukua 10 ei tulosteta. range(10) funktioon sisältyy 10, mutta sitä ei tulosteta.
for i in range(10): print(i) |
Output:
0 1 2 3 4 5 6 7 8 9 |
Tässä i muuttujan nimi voisi olla vaikkapa lineNumber, jos käsiteltäisiin esim. tekstitiedostoa.
Jos haluaisit tulostaa luvut 1 - 10, voit käyttää seuraavaa tapaa. Aseta range funktiolle kaksi parametria (arvoa) yhden sijasta. Ensimmäinen parametri on aloituskohta ja toinen on lopetuskohta (silmukka päättyy siis yhtä aiemmin).
Esimerkiksi for silmukassa range (1,11) tulostaa luvut 1 - 10.
for i in range(1, 11): print(i) |
Output:
1 2 3 4 5 6 7 8 9 10 |
Toinen tapa lukujen 1 - 10 tulostamiseen käyttäen edelleen range(10) funktiota ja 'juoksuttamalla' muuttujaa i 0:sta 9:ään, mutta lisätään muuttujaan luku 1 ennen tulostamista. Molemmat edellä olevat tavat ovat kelvollisia.
# Tulostetaan luvut 1 - 10. for i in range(10): print(i + 1)
4.1.1 Erilaisia laskurimalleja
Jos silmukkalaskurin pitäisikin tulostaa joka toinen luku tai vaikkapa joka kolmas, niin toimikaamme lähes samoin kuin edellä. Tässä helpointa on syöttää range funktiolle kolmaskin parametri, joka kertoo 'hypyn' pituuden. Toinen ratkaisumalli olisi edelleen askeltaa silmukkaa yhden hypyillä, mutta kerrotaan muuttuja esim luvulla 3, jolloin saataisiin 3:n mittaisia hyppyjä. Alla esinmerkki molemmista tavoista.
# Kaksi tapaa tulostaa parilliset luvut 2 - 10 for i in range(2,12,2): print(i) for i in range(5): print((i + 1) * 2) |
Output:
2 4 6 8 10 2 4 6 8 10 |
Silmukkalaskuri saadaan 'juoksemaan' myös alaspäin, kohti nollaa. Tällöin range funktiolle annetaan negatiivinen hyppyluku (step). Alla esimerkissä aloitetaan luvusta 10 ja lasketaan alaspäin nollaan -1 hypyillä. Tosin luku 0 ei tulostu. Tässä kohtaa for silmukan aloitusarvo range funktiossa on suurempi kuin lopetusarvo, normaalistihan kasvatus juoksee pienemmästä luvusta kohti suurempaa.
for i in range(10, 0, -1): print(i) |
Output:
10 9 8 7 6 5 4 3 2 1 |
Jos pitää tulostaa jokin epäsäännöllinen lukusarja, voidaan for silmukkaan luvut syöttää listana. (Listoista puhutaan tarkemmin kappaleessa 7). Tässä vain yksinkertainen esimerkki mielivaltaisesta lukulistasta ja sen tulostamisesta.
for i in [2,6,4,2,4,6,7,4]: print(i)
Tämä tulostaa:
2 6 4 2 4 6 7 4
4.1.2 Sisäkkäiset silmukat
Onko sinulla aavistusta, mitä seuraava koodi tulostaa ja miksi? Kirjoita koodi editorilla ja kokeile ajaa ohjelma.
# Mitä tulostuu? Miksi? for i in range(3): print("a") for j in range(3): print("b")
Seuraava koodi on lähes identtinen edellisen kanssa. Erona on, että toinen for silmukka on sisennetty yhden sarkaimen verran ensimmäisen silmukan sisällä. Tämä aiheuttaa merkittävän muutoksen tulokseen. Kokeile tämäkin käytännössä.
# Mitä tulostuu nyt ja miksi? for i in range(3): print("a") for j in range(3): print("b") print("Tehty")
Tätä koodia ei selitetä tarkemmin, vaan sinun tulee selvittää se itse koneella kokeilemalla.
4.1.3 Summauslaskureita yms.
Peleissä silmukoita käytetään erilaisten summien ja lukujen laskemiseen. Seuraava “running total” koodi esiintyy tässä julkaisussa useaan kertaan. Laskureita voisi olla esim. yhteispisteet (score), käyttäjän tapahtumalaskuri, erilaisten keskiarvojen laskeminen jne. Voisit tehdä selaimeesi kirjanmerkin tähän kohtaan, koska tähän palataan useasti myöhemmin. Alla oleva koodi laskee käyttäjän antamien lukujen yhteissumman.
total = 0 for i in range(5): new_number = int(input("Syötä jokin kokonaisluku: " )) total += new_number print("Lukujen summa on: ", total)
Rivillä 1 luodaan muuttuja total ja asetetaan sille alkuarvo nolla. Alkuarvon asetus on tärkeää, koska muutoin kone ei osaa tehdä rivin 4 lisäystä. Ilman alkuarvoa ei ole tiedossa, mihin total lukuun new_number lisätään, koska total muuttujan arvo on tuntematon.
Tässä helposti virheellisesti voisi lisätä i muuttujan arvon total muuttujaan new_number muuttujan sijasta. Muistathan, että laskemme tässä käyttäjän syöttämiä lukuja yhteen, ei silmukkalaskurin toistokertoja.
Tällaisella "silmukkasummalla" voidaan laskea myös erilaisia matemaattisia operaatioita. Esimerkiksi:
$s=\sum\limits_{n=1}^{100}n$
Kaava saattaa olla sinulle tuntematon, mutta tässä yksinkertaisesti esitetään lukujonon summa, jossa luku kasvaa aina yhdellä:
$s=1+2+3+4+5 \ldots 98+99+100$
Alla on koodiesimerkki, jossa kokonaislukujonon 1 - 100 summa lasketaan ja sijoitetaan muuttujaan sum.
# Mikä tulee muuttujan sum arvoksi? sum = 0 for i in range(1, 101): sum = sum + i print(sum)
Seuraavaksi toisenlainen muunnos laskurisilmukasta. Silmukassa luetaan käyttäjän syöttämiä kokonaislukuja ja laskuri total laskee syötettyjen nollien lukumäärän:
total = 0 for i in range(5): new_number = int(input( "Syötä kokonaisluku: ")) if new_number == 0: total += 1 print("Syötit yhteensä", total, "nollaa")
Tässä vaiheessa, jos olet mielestäsi ymmärtänyt for silmukan ja silmukassa suoritettavat laskurit, niin osaat varmasti selvittää seuraavan koodin tulostuksen. Mikä on a:n arvo suorituksen jälkeen?
# Minkä arvon a saa? a = 0 for i in range(10): a = a + 1 print(a) # Minkä arvon a saa? a = 0 for i in range(10): a = a + 1 for j in range(10): a = a + 1 print(a) # Minkä arvon a saa? a = 0 for i in range(10): a = a + 1 for j in range(10): a = a + 1 print(a)
Älähän kiirehdi. Selvitä yllä olevat koodit tarkkaan niin, ettää ymmärrät niiden toiminnan. Kopioi koodi Pythonin editoriin ja aja ohjelma tarkistaaksesi vastauksesi. Selvitä myös mahdolliset virheesi.
4.2 Esimerkki for silmukat
Tässä esimerkissä on yleisesti for silmukoista ja niiden toiminnasta.
# Sample Python/Pygame Programs # Simpson College Computer Science # http://programarcadegames.com/ # http://simpson.edu/computer-science/ # Print 'Hi' 10 times for i in range(10): print("Hi") # Print 'Hello' 5 times and 'There' once for i in range(5): print("Hello") print("There") # Print 'Hello' 'There' 5 times for i in range(5): print("Hello") print("There") # Print the numbers 0 to 9 for i in range(10): print(i) # Two ways to print the numbers 1 to 10 for i in range(1, 11): print(i) for i in range(10): print(i + 1) # Two ways to print the even numbers 2 to 10 for i in range(2, 12, 2): print(i) for i in range(5): print((i + 1) * 2) # Count down from 10 down to 1 (not zero) for i in range(10, 0, -1): print(i) # Print numbers out of a list for i in [2, 6, 4, 2, 4, 6, 7, 4]: print(i) # What does this print? Why? for i in range(3): print("a") for j in range(3): print("b") # What is the value of a? a = 0 for i in range(10): a = a + 1 print(a) # What is the value of a? a = 0 for i in range(10): a = a + 1 for j in range(10): a = a + 1 print(a) # What is the value of a? a = 0 for i in range(10): a = a + 1 for j in range(10): a = a + 1 print(a) # What is the value of sum? sum = 0 for i in range(1, 101): sum = sum + i
4.3 While silmukat
for silmukkaa käytetään silloin, kun toistokertojen määrä on ennalta määrätty. Silmukkaa suoritetaan tietty määrä. while silmukkaa toistetaan, kunnes toistoehto ei enää ole tosi.
Tosin while silmukalla voidaan toteuttaa myös kaikki for silmukalla tehtävät toistot. Siinä voidaan toistaa silmukkaa niin kauan, kunnes silmukkalaskuri saavuttaa tietyn arvon. No, mihin for silmukkaa tarvitaan, jos while silmukalla voidaan toteuttaa kaikki toistolauseet? for silmukka on kuitenkin yksinkertaisin käyttää ja koodata. Esimerkki for silmukasta näyttää vaikkapa tältä...
for i in range(10): print(i)
...ja voitaisiin tehdä while silmukalla tällä tapaa:
i = 0 while i < 10: print(i) i = i + 1
Rivillä 1 while silmukassa asetetaan laskurimuuttuja, jolla lasketaan suorituskertojen määrä. Laskurin arvoa pitää kasvattaa jokaisella toistokierroksella. for silmukassa laskurin kasvatus on automaattinen, jolloin päästään vähäisemmällä määräällä koodia. Rivillä 2 while lause muistuttaa if lausetta. Jos ehto on voimassa, niin toistolauseen lohko suoritetaan. Kuten esimerkistä naemme, one for silmukka kompaktimpi kuin while silmukka ja sitä on helpompi lukea.
Yleinen virhe on sekoittaa for- while silmukka. Alla esimerkki, jossa ohjelmoija on sekoittanut kunnolla for lauseen ja while lauseen syntaksin. Lopputulos ei tietenkään toimi.
while range(10): print(i)
range funktiota voidaan käyttää vain for silmukassa.
4.3.1 Kasvatusoperaattori
Kasvatusoperaattoria käytetään while-silmukoissa. Operaattori voidaan kirjoittaa lyhennettynä.
i = i + 1
... ja sama lyhyemmässä muodossa:
i += 1
while silmukassa tämä olisi seuraavanlainen:
i = 0 while i < 10: print(i) i += 1
Tätä esitystapaa voidaan kasvatuksen lisäksi käyttää vähennykseen ja kertolaskuun. Esimerkiksi:
i *= 2
On sama kuin:
i = i * 2
Yritäpä selvittää, mitä seuraava koodi tulostaa:
i = 1 while i <= 2 ** 32: print(i) i *= 2
4.3.2 Ikuinen silmukka käyttäjän keskeyttämänä
Ikuinen silmukka on sopiva tilanteessa, jossa silmukkaa toistetaan niin kauan kunnes käyttäjä haluaa sen lopettaa:
quit = "n" while quit == "n": quit = input("Do you want to quit? ")
Silmukka voidaan keskeyttää usealla eri tavalla. Yksi tapa on käyttää Boolean tyyppistä muuttujaa. Tässä esimerkki:
done = False while not done: quit = input("Do you want to quit? ") if quit == "y": done = True attack = input("Does your elf attack the dragon? ") if attack == "y": print("Bad choice, you died.") done = True
Tämä esimerkki on puutteellinen. Kun käyttäjä haluaa lopettaa, niin ohjelma kysyy vielä, haluaako pelaaja hyökätä lohikäärmeen kimppuun. Miten tämä korjattaisiin?
Seuraavassa esimerkissä while silmukkaa toistetaan kunnes value muuttuja on riittävän lähellä luku 0.999:
value = 0 increment = 0.5 while value < 0.999: value += increment increment *= 0.5 print(value)
4.3.3 Yleisiä ongelmia while silmukoissa
Ohjelmoija haluaisi laskea alaspäin 10:stä 1:een. Mikä on pielessä ja miten korjataan?
i = 10 while i == 0: print(i) i -= 1
Nyt ohjelman pitäisi laskea kymmeneen, mutta ei toimi. Mikä pielessä?
i = 1 while i < 10: print(i)
4.4 Example while Loops
Alla esimerkki while silmukoiden käytöstä. Käy se tarkoin läpi (alkup. englannin kielinen versio).
# Sample Python/Pygame Programs # Simpson College Computer Science # http://programarcadegames.com/ # http://simpson.edu/computer-science/ # A while loop can be used anywhere a for loop is used: i = 0 while i < 10: print(i) i = i + 1 # This is the same as: for i in range(10): print(i) # It is possible to short hand the code: # i = i + 1 # With the following: # i += 1 # This can be done with subtraction, and multiplication as well. i = 0 while i < 10: print(i) i += 1 # What would this print? i = 1 while i <= 2**32: print(i) i *= 2 # A very common operation is to loop until the user performs # a request to quit quit = "n" while quit == "n": quit = input("Do you want to quit? ") # There may be several ways for a loop to quit. Using a boolean # to trigger the event is a way of handling that. done = False while not done: quit = input("Do you want to quit? ") if quit == "y": done = True attack = input("Does your elf attach the dragon? ") if attack == "y": print("Bad choice, you died.") done = True value = 0 increment = 0.5 while value < 0.999: value += increment increment *= 0.5 print(value) # -- Common problems with while loops -- # The programmer wants to count down from 10 # What is wrong and how to fix it? i = 10 while i == 0: print(i) i -= 1 # What is wrong with this loop that tries # to count to 10? What will happen when it is run? i = 1 while i < 10: print(i)
4.5 Satunnaislukuja
Satunnaislukuja käytetään peleissä, ohjelmissa yleensä ja simulaatioissa.
4.5.1 randrange funktio
Oletusarvoisesti, Python ei osaa luoda satunnaislukuja. Tähän tarvitaan avuksi tätä tarkoitusta varten 'implementoitu' Python koodikirjasto, jossa on valmiina satunnaislukujen luonti. Jotta random-kirjasto saadaan ohjelmassa käyttöön, pitää ohjelman alkuun kirjoittaa varattu sana import:
import random
Aivan kuten pygame-paketissa, niin millään koodatulla tiedostolla ei saa olla samaa nimeä kuin import-lauseessa liitetyllä paketilla. Jos esimerkiksi olisi koodattu (ja tallennettu) tiedosto nimellä random.py aiheutuisi tästä se, että satunnaislukukirjaston sijasta ohjelma lataisi import-lauseessa tämän tiedoston satunnaislukukirjaston sijaan.
Randon-kirjaston randrange funktiolla saadaan generoitua satunnaislukuja. Esimerkiksi alla oleva koodi generoi satunnaisluvun 0 - 49. Oletuksena alaraja on 0.
my_number = random.randrange(50)
Seuraava koodi generoi satunnaisluvun väliltä 100 - 200. Aivan kuin range funktio, toinen parametri määrää ylärajan (joka ei kuitenkaan kuulu lukujoukkoon). Siksi haluttaessa luku 200 mukaan, pitää ylärajaksi asettaa 201.
my_number = random.randrange(100, 201)
Mitä, jos ei halutakaan satunnaislukua, vaan haluttaisiin arpoa jokin satunnainen muu alkio? Tähän tarvitaan avuksi lista. (Lista käsitellään tarkemmin kappaleessa 7). Katso helppo esimerkki alla olevasta koodista, miten listasta arvotaan satunnaisesti yksi kolmesta (kivi, paperi, sakset):
my_list = ["rock", "paper", "scissors"] random_index = random.randrange(3) print(my_list[random_index])
4.5.2 random funktio
Edellä olleet esimerkit generoivat jonkin kokonaisluvun. Satunnainen desimaaliluku (float) saadaan generoitua random funktiolla.
Alla olevalla koodilla generoidaan satunnaisluku 0 - 1, esim. 0.4355991106620656 (huomaa desimaaliPISTE).
my_number = random.random()
Arvottavan desimaaliluvun lukualue voidaan määrä yksinkertaisella matematiikalla. Esimerkiksi alla oleva koodi generoi desimaaliluvun väliltä 10 - 15:
my_number = random.random() * 5 + 10
4.6 Review
4.6.1 Multiple Choice Quiz
Klikkaa tästä monivalintatehtävään.
4.6.2 Short Answer Worksheet
Klikkaa tästä kappaleen kertaustehtäviin.
4.6.3 Lab
Klikkaa tästä ohjelmointitehtäviin.
You are not logged in. Log in here and track your progress.
English version by Paul Vincent Craven
Spanish version by Antonio Rodríguez Verdugo
Russian version by Vladimir Slav
Turkish version by Güray Yildirim
Portuguese version by Armando Marques Sobrinho and Tati Carvalho
Dutch version by Frank Waegeman
Hungarian version by Nagy Attila
Finnish version by Jouko Järvenpää
French version by Franco Rossi
Korean version by Kim Zeung-Il
Chinese version by Kai Lin