Obligatorisk innlevering uke 8-9 (innlevering nr. 7)
Frist for innlevering: 26.10. kl 23:59
OBS: Denne innleveringen er obligatorisk og m? v?re godkjent for at man skal kunne ta eksamen i faget.
Det er mulig ? levere innleveringen f?r fristen og f? den rettet tidligere ved ? skrive "Klar til retting" i kommentarfeltet i Devilry. Man vil da ikke kunne f? ny retting senere.
Sp?rsm?l eller kommentarer til oppgaveteksten sendes til ivargry@ifi.uio.no.
Introduksjon
Oppgavene i denne innleveringen g?r ut p? ? bruke objektorientert programmering til ? lage en "spill-motor" til en verden der sauer og ulver (objekter) beveger seg rundt. Denne motoren skal ogs? brukes i neste obligatoriske innlevering, hvor vi skal implementere intelligens til objektene i spillet. Vi skal jobbe med et 2-dimensjonalt spillbrett, der ulike dyr beveger seg rundt p? skjermen. Objektorientert programmering er et nyttig verkt?y i en slik setting, fordi vi kan representere hvert dyr ved hjelp av et objekt, og enkelt legge til eller fjerne dyr. Hvert objekt vil kjenne til sin egen posisjon, bevegelsesretning og lignende.
F?r du begynner
- S?rg for ? ha installert PyGame Zero. Se denne siden for informasjon om hvordan du installerer og en kort introduksjon. Ta kontakt s? tidlig som mulig om du har problemer med ? installere.
- Test at du kan kj?re kommandoen
pgzrun
i terminalen og at du f?r f?lgende output:pygame 1.9.6 Hello from the pygame community. https://www.pygame.org/contribute.html Usage: pgzrun [options] pgzrun: error: You must specify which module to run.
- Lag en mappe
images
i mappen du skriver denne innleveringen. I mappenimages
legger du f?lgende bilder: sau.png, sau2.png, ulv.png, gress.png, stein.png. Om du senere vil bytte ut bildene med dine egne bilder, kan du gj?re det, men pass p? at de er like store som originalbildene.
Oppgave 1: Implementere en Sau-klasse
Filnavn: sau.py
F?r vi starter med ? visualisere objektene v?re i spillbrettet, skal vi f?rst implementere en Sau-klasse og sjekke at den fungerer. Denne klassen ligner litt p? klassen Sau
fra forrige innlevering, men har en del endringer og utvidelser, s? det er lurt ? skrive en ny klasse fra starten av.
Lag en fil sau.py
hvor du definerer en klasse Sau
. En sau har et bilde (en streng som peker til en bildefil) og en posisjon (antall pixler fra venstre side og toppen av skjermen). Konstrukt?ren skal ta denne informasjonen ved hjelp av tre parametere og lagre informasjonen i tilh?rende instansvariable:
posisjon_venstre
: Antall pixler sauen befinner seg fra venstre side av skjermen)posisjon_topp
: Antall pixler sauen befinner seg fra toppen av skjermen)bilde
: Navnet p? et bilde (Pygame Zero vil automatisk lete etter bilder i mappen images, s? strengen "sau" vil gi bildet "images/sau.png")
Informasjonen skal lagres i tilh?rende instansvariabler (f. eks skal bilde
lagres i self._bilde
).
Sau skal i tillegg ha to instansvariable _fart_fra_venstre
og _fart_fra_topp
som forteller oss n?v?rende fart til sauen (hvor mange pixler per tidsenhet den beveger seg fra venstre mot h?yre side og fra toppen mot bunnen av skjermen). Disse kan begges settes til ? v?re 1.
Videre ?nsker vi at klassen v?r skal ha disse metodene:
- En metode
sett_posisjon
som setter posisjonen (self._posisjon_venstre
ogself._posisjon_topp
) til en ny posisjon. Denne metoden m? alts? ta to parametere (i tillegg tilself
). - En metode
hent_posisjon_venstre
og en metodehent_posisjon_topp
som returnerer de to posisjonene. - En metode
hent_fra_fra_venstre
og en metodehent_fart_fra_topp
som returnerer farten (fra venstre og fra toppen). - En metode
sett_fart
som setter farten (self._fart_fra_venstre
ogself._fart_fra_topp
) til en ny fart. - En metode
beveg
som endrer posisjonen. Metoden tar ingen parametere, men endrer posisjonen i forhold til n?v?rende fart, ved ? legge farten til posisjonen. Alts? skalself._posisjon_topp
endres ved atself._fart_fra_topp
plusses p?, og tilsvarende forself._posisjon_venstre
. - En metode
snu
som gj?r at sauen endrer bevegelesesretning 180 grader. Hvis sauen for eksempel beveger seg skr?tt nedover fra ?verste venstre hj?rne til nederste h?yre hj?rne (dvs atfart_fra_venstre = 1
ogfart_fra_topp=1
), s? skal den nye farten blifart_fra_venstre=-1
ogfart_fra_topp=-1
. Vi snur sauen 180 grader ved ? gangefart_fra_venstre
ogfart_fra_topp
med -1 (dette reverserer retningen). Hvis du er usikker p? hvorfor dette fungerer, er det ikke viktig ? forst? dette n?. Etter ? ha jobbet mer med spillbrettet senere vil man f? mer forst?else for koordinater og retninger. - En metode
tegn
som tar et parameterskjerm
som representerer skjermen vi skal tegne sauen p? n?r spillet kj?rer. Ikke implementer denne metoden n?, bare brukpass
ellerreturn
slik at programmet ikke krasjer hvis metoden blir kalt.
Det kan v?re lurt ? tegne et spillbrett p? et ark for ? gj?re ting enklere ? forst?.
Oppgave 2: Test Sau
Filnavn: test.py
Lag en ny fil test.py
hvor du importerer klassen Sau: from sau import Sau
.
Lag en prosedyre test_sau()
hvor du gj?r f?lgende (der metode ikke er spesifisert, pr?v ? tenke selv hvilke metoder du m? kalle):
- Opprett et nytt sau-objekt med bildestrengen "sau" og posisjonen
50, 50
(alts? 50 pixler fra venstre og 50 pixler fra toppen). - Sett deretter posisjonen til ? v?re
0, 0
ved ? kallesett_posisjon
- Sett farten til ? v?re
10, 20
(dvs. 10 i venstre-til-h?yre-retning og 20 i topp-til-bunn-retning) - Beveg sauen 2 ganger ved ? kalle
beveg
to ganger - Sjekk at posisisjonen n? er
20, 40
(enten ved ? printe posisjonen eller brukeassert
) - Snu retningen sauen beveger seg
- Sjekk at farten n? er
-10, -20
- Beveg sauen én gang
- Sjekk at posisjonen n? er
10, 20
Kall prosedyren test_sau()
nederst i filen test.py for ? kj?re testene.
Oppgave 3: Spillbrett
Filnavn: spillbrett.py
Vi kommer til ? ha en del ulike objekter p? spillbrettet v?rt, og ?nsker derfor ? ha en klasse Spillbrett
for ? holde orden p? objektene.
I f?rste omgang best?r spillbrettet kun av en mengde sauer (sau-objekter), men vi kommer senere til ? utvide denne klassen.
-
Lag en ny fil
spillbrett.py
med en klasseSpillbrett
som har en konstrukt?r. Konstrukt?ren tar ingen parametere n?, men initierer en instansvariabel_sauer
som peker p? en tom liste. Denne listen skal holde p? alle sauene p? spillbrettet. -
Klassen skal ha en metode
opprett_sau(bilde, posisjon_venstre, posisjon_topp)
som lager en ny sau og legger den til i listen over sauer . Husk ? importere Sau-klassen ved ? hafrom sau import Sau
?verst ispillbrett.py
-
Lag en metode
oppdater
. Metoden tar ingen parametere. Denne metoden kommer til ? bli kalt hver gang spillbrettet skal oppdateres (mange ganger i sekundet), og etter hvert vil denne metoden holde styr p? en del objekter. N? ?nsker vi bare at denne metoden beveger alle sauene én gang. Dette gj?r du ved ? kalle beveg-metoden p? alle sau-objektene p? spillbrettet (ved hjelp av en for-l?kke). -
Lag en metode
tegn
. Denne metoden kalles hver gang spillbrettet skal tegnes p? skjermen (noe som skjer mange ganger i sekundet). Her ?nsker vi ? tegne alle sauene v?re, noe vi gj?r ved ? kalle en tegn-metode p? sau-objektet (denne metoden finnes ikke n?, men vi legger den til i neste oppgave):def tegn(self, skjerm): for sau in self._sauer: sau.tegn(skjerm)
I neste oppgave skal vi implementere metoden tegn
til klassen Sau.
Oppgave 4: Vis sauen p? spillbrettet
Vi ?nsker n? ? bruke et veldig enkelt spillrammeverk PyGame Zero til ? visualisere sauen v?r p? spillbrettet. Vi skal bruke lite funksjonalitet fra Pygame Zero, og du trenger ikke ? sette deg inn i Pygame Zero. M?let er bare ? visualisere objektene v?re. F?lg disse stegene:
-
Installer Pygame Zero hvis du ikke har gjort det enda.
-
Lag en fil hovedprogram.py som inneholder f?lgende kode:
from spillbrett import Spillbrett # Her lager vi et nytt spillbrett og oppretter to sauer med ulike bilder og ulike start-posisjoner spillbrett = Spillbrett() spillbrett.opprett_sau("sau", 100, 100) spillbrett.opprett_sau("sau2", 400, 400) # Dette er prekode som gj?r at pygame zero fungerer. Ikke endre dette: WIDTH = 900 HEIGHT = 700 def draw(): screen.fill((128, 81, 9)) spillbrett.tegn(screen) def update(): spillbrett.oppdater()
I koden over importerer vi Spillbrett klassen, lager et Spillbrett-objekt og oppretter to sauer. Resten av koden er kun der for ? fortelle PyGame Zero at den skal tegne et rektangel og kalle spillbrettet sin
tegn
-metode hver gang den skal tegne noe, og kalle spillbrettet sinoppdater
-metode hver gang noe skal oppdateres (som i praksis skjer mange ganger i sekundet). -
For ? f? sauene v?re til ? vises p? spillbrettet m? vi tegne bildet til sauen p? skjermen. Lag en metode
tegn
til klassen sau som tarskjerm
som parameter.skjerm
er et objekt i Pygame Zero som har ulike metoder for interagere med bildet som vises p? skjermen. Vi skal kalle metodenblit
som lar oss tegne et bilde p? skjermen:
def tegn(self, skjerm):
skjerm.blit(self._bilde, (self._posisjon_venstre, self._posisjon_topp))
Dette er alt som trengs for ? f? Pygame Zero til ? vise et spillbrett og visualisere sauene p? dette brettet. Vi ?nsker n? ? kj?re hovedprogram.py, men i stedet for ? kalle
main.py ved ? kj?re python3 hovedprogram.py
vil vi kj?re hovedprogram.py
gjennoom Pygame Zero. Programmet vil fortsatt kj?res som et python-program, men f?r noen ekstra egenskaper som gj?r at vi f?r vist spillet p? skjermen.
Du gj?r dette ved ? kj?re pgzrun hovedprogram.py
i terminalen. Hvis alt virker som det skal b?r du se noe slikt:
Hvis du vil kan du f?r du g?r videre leke deg litt ved ? for eksempel sette en annen fart p? sauene og se hva som skjer.
Oppgave 5: Bedre bevegelse av sauene
N? g?r sauene bare i en helt rett linje og forsvinner ut av skjermen. Vi ?nsker ? gj?re noen forbedringer.
Sjekk kollisjon mot kanten av brettet
Utvid beveg-metoden til sauen slik at sauen f?rst beveger seg og deretter sjekker om den er utenfor bildet.
Sauen er 50 px bred og 50 px h?y, og bildet er 900 px bredt og 700 px h?yt.
Hvis sauen er utenfor bildet, snu sauen ved ? kalle snu
-metoden s?nn at at den g?r tilbake der den kom fra.
La sauen bevege seg mer tilfeldig rundt
Importer randint ved ? legge til from random import randint
?verst i filen.
Vi ?nsker ? gi sauen en mer tilfeldig bevegelse rundt p? skjermen.
Utvid beveg-metoden slik at sauen av og til (basert p? tilfeldige tall) endrer retning og/eller fart. Bruk fantasien, v?r gjerne kreativ og pr?v deg fram med ulike m?ter ? trekke tilfeldige tall p? og la de tallene p?virke bevegelsen til sauen. Pr?v ? ende opp med noe som gj?r at sauen f?r en noenlunde "naturlig" bevegelse. Husk at beveg-metoden kalles mange ganger i sekundet, s? bevegelsen kan bli veldig hakkete om sauen endrer bevegelse ofte.
Her er et eksempel p? bruk av tilfeldige tall for ? f? sauen til ? g? rett til venstre med 0.5% sannsynlighet:
if randint(1, 1000) <= 5:
self.sett_fart(-1, 0)
Her er eksempel p? noen sauer som beveger seg tilfeldig rundt:
Oppgave 6: Flere objekter (ulv, gress, stein)
Lag en klasse Gress i en fil gress.py
:
- Klassen skal ha samme signatur som Sau (dvs. at init-metoden skal ta de samme parameterene)
- I tillegg skal den ha en instansvariabel
_er_spist
som er satt til False - Lag en metode
blir_spist
som setter_er_spist
til True og en metodeer_spist()
som returnerer True hvis gresset er spist og False hvis ikke. - Importer Gress-klassen i spillbrett.py og main.py (p? samme m?te som du importerer Sau)
- Lag en metode
oppett_gress
i Spillbrett som oppretter et nytt gress-objekt og legger objektet til i en liste_gress
p? samme m?te som med sauene. - Utvid tegn-metoden i
Spillbrett
slik at den ogs? kallertegn
p? alle gress-objektene. Du trenger ikke ? gj?re noe i oppdater-metoden, fordi gress skal ikke bevege p? seg. - Endre hovedprogram.py slik at du oppretter to gress-objekter (bilde skal v?re "gress", velg posisjon selv).
- Sjekk at du ser de to gress-objektene p? spillbrettet
Gjenta punktene over for Ulv og Stein (Ulv
-klassen skal ligge i en fil ulv.py
og Stein
-klassen i stein.py
). Ulv
har bildet "ulv" og stein har bildet "stein". Lag et par ulv- og stein-objekter p? samme m?te som med gress, og sjekk at de vises p? brettet.
Du trenger ikke ? implementere noen andre metoder i disse klassene n?. I neste oppgave skal vi f? ulvene til ? bevege seg.
Oppgave 8: Gi Ulvene bevegelse
Til n? har ulvene st?tt stille, men vi ?nsker ? gi dem litt bevegelse. Gj?re enten oppgave a eller b under. Oppgave b er mer krevende, og gir ikke noe mer poeng p? obligen.
a)
Implementer at ulvene beveger seg tilfeldig rundt p? samme m?te som sauene, alts? ved ? implementere en beveg-metode i klassen Ulv og kall den metoden i oppdater-metoden til Spillbrett.
b)
Implementer smartere bevegelse til ulvene ved at hver ulv beveger seg mot den sauen som er n?rmest. For ? f? til dette ?nsker vi at ulver skal ha tilgang til spillbrettet slik at de kan se hva som er p? brettet og bevege seg etter det.
- Utvid Ulv-klassen slik at konstrukt?ren ogs? tar et spillbrett som lagres i en instansvariabel.
Du m? ogs? endre
opprett_ulv
-metoden i Spillbrett slik at spillbrettet sendes med som det 4. parameteret. Det kan du gj?re ved ? sende medself
, som peker p? spillbrettet.- Alternativt kan du endre Ulv-klassen til ? ta inn en liste over sauer og en liste over steiner, ettersom det er alt Ulven trenger ? se. Fordelen er at Ulven da slipper ? m?tte hente ut sauer og steiner fra spillbrettet, men init-metoden vil f? to ekstra parametere i stedet for ett. Du kan velge selv hvilken l?sning du vil g? for.
- Lag en metode
finn_naermeste_sau
i Ulv-klassen. Denne metoden skal g? gjennom alle sauene i spillbrettet og finne og returnere den sauen som er n?rmest ulven. For ? f? til dette m? den g? gjennom alle sauene, regne ut avstanden fra sauen til Ulven og returnere den sauen som har kortest avstand. Her vil du m?tte bruke Pytagaros-formelen (som vi alle har l?rt enn gang p? skolen), men det er lov ? sp?rre om hjelp her hvis du ikke f?r til ? regne ut avstanden til n?rmeste sau. - Lag en prosedyre
test_finn_naermeste_sau
i test.py der du oppretter et spillbrett, legger inn en Ulv og noen sauer og kallerfinn_naermeste_sau
p? ulv-objektet. Print koordinatene til den sauen som returneres, og sjekk at det faktisk er det n?rmeste sauen:
def test_finn_naermeste_sau():
brett = Spillbrett()
brett.opprett_sau("sau", 0, 0)
brett.opprett_sau("sau", 100, 100)
ulv = brett.opprett_ulv("ulv", 90, 80)
naermeste_sau = ulv.finn_naermeste_sau()
print(naermeste_sau.hent_posisjon_venstre(), naermeste_sau.hent_posisjon_topp()) # Det b?r printes 100, 100, ettersom denne sauen er n?rmest ulven
OBS: For testingen v?r sin skyld, ?nsker vi ? ha tilgang til ulven som blir opprettet n?r vi kaller opprett_ulv
p? spillbrettet. Derfor er det greit ? utvide opprett_ulv
til ? returnere ulven som blir laget. Da fungerer ulv = brett.opprett_ulv("Ulv", 90, 80)
som tenkt i koden over.
N?r du har f?tt finn_narmeste_sau
til ? fungere, kan du implementere beveg-metoden. Ulven skal bevege seg slik:
- F?rst finner den n?rmeste sau
- Hvis sauen sin venstre-koordinat er lavere enn ulven sin, endrer den fart fra venstre til -1. Hvis sauens venstre-koordinat er h?yere enn ulvens venstre-koordinat, endrer den fart fra venstre til 1.
- Samme regel f?lges for bevegelse i topp-mot-bunn-retning
- Implementer ogs? den samme sjekken som du har i Sau for ? unng? at Ulven g?r utenfor brettet. Du trenger da ogs? en snu-metode, som du kan kopiere fra Sau-klassen.
- Husk at beveg-metoden m? kalles fra
oppdater
i Spillbrett p? samme m?te som med sauer.
Oppgave 9: Sjekk kollisjoner
Vi ?nsker snart ? implementere at ulver spiser sauer de kommer over, og at sauer spise gress de kommer over.
Vi trenger da en m?te for ? sjekke om to objekter har kollidert (om de overlapper p? skjermen). Vi har n? fire forskjellig type objekter (sau, stein, gress og ulv), og alle har en posisjon og st?rrelse (alle er 50px brede og h?ye). For ? slippe ? implementere en metode i hver av disse klassene for ? sjekke kollisjon mot et annet objekt, kan vi i stedet lage en funksjon som tar to objekter og sjekker om de har kollidert.
Lag en funksjon (ikke en klasse-metode) har_kollidert(objekt1, objekt2)
i filen spillbrett.py
. Denne funksjonen
tar to ulike objekter som parametere, og den trenger ikke ? vite hvilke objekter det er, s? lenge de har
metodene hent_posisjon_venstre
og hent_posisjon_topp
. Implementer funksjonen slik at den returnerer True
hvis to objekter overlapper og False hvis ikke (se reglene lenger nede om du sliter med dette).
Lag en prosedyre test_har_kollidert
i test.py
(husk ? importere har_kollidert
?verst i filen: from spillbrett import har_kollidert
). I denne prosedyren kan du enkelt teste om du har implementert har_kollidert
riktig, og det er mye enklere ? finne feil slik enn ? kj?re hele spillet. Her er noen eksempler p? test-caser. Implementer 2 til test-caser p? samme m?te:
def test_har_kollidert():
# Test-case 1: Disse to objektene har kollidert, fordi ulven ligger delvis opp? sauen
sau = Sau("sau", 50, 50)
ulv = Ulv("ulv", 60, 60)
assert har_kollidert(sau, ulv)
# Rekkef?lgen skal ikke ha noe ? si
assert har_kollidert(ulv, sau)
# Test-case 2: Disse to objektene ligger rett ved siden av hverandre
# og har ikke kollidert (husk at de er 50px brede/h?ye):
gress = Gress("gress", 100, 100)
sau = Sau("sau", 150, 150)
assert not har_kollidert(gress, sau)
# Implementer to test-caser til her:
# ...
Hvis du sliter med ? implementere har_kollidert
kan du pr?ve ? f?lge disse reglene:
En kollisjon skjer hvis:
- et objekt sin venstre-posisjon er st?rre enn det andre objektets venstre-posisjon minus 50 pixler og lavere enn det andre objektets venstre-posisjon pluss 50 pixler ...
- ... og objektet sin topp-posisjon er st?rre enn det andre objekts topp-posisjon minus 50 pixler og lavere enn det andre objekts topp-posisjon pluss 50 pixler
Disse regelen fungerer fordi vi antar at alle objektene er 50 pixler brede/h?ye.
Oppgave 10: La ulvene spise sauer og sauene spise gress
Utvid klassen Sau slik at den har en instansvariabel _er_spist
, en metode blir_spist
og en metode er_spist
(som fungerer akkurat likt som i klassen Gress).
Endre tegn-metoden i klassen spillbrett slik at den bare tegner sauer og gress som ikke er spist.
Utvid oppdater
-metoden i klassen Spillbrett
:
- For hver ulv, g? gjennom alle sauene som ikke er spist og sjekk om ulven overlapper med den sauen. Hvis den gj?r det, kall
blir_spist
p? sau-objektet. - For hver sau som ikke er spist, g? gjennom alle gress, og gj?r tilsvarende.
- For hver sau, sjekk om sauen krasjer med en stein. Hvis den gj?r det, kall snu-metoden slik at sauen g?r tilbake der den kom ifra. Sauer skal ikke kunne g? p? stein, men Ulver skal det (enn s? lenge).
Legg gjerne til mer gress, flere steiner og flere sauer/ulver for ? teste dette.
Her er et eksempel p? hvordan det kan se ut n?r du har implementert alt:
Valgfri konkurranse
Det er ingen ekstra valgfri konkurranse direkte knyttet til denne obligen, ettersom neste oblig bygger p? denne og det vil der v?re en stor konkurranse. Men om du blir ferdig med obligen tidlig, kan du lage et space invaders spill. V?r gjerne kreativ og se om du klarer ? lage noe g?y. Send gjerne spillet ditt med n?r du leverer denne obligen, s? kan du f? litt feedback p? det, og kanskje vi pr?ver ut noen av spillene i fredagstimen i uke 9.
Krav til innlevering
- Kun .py-filene skal leveres inn (du trenger ikke ? levere mappen "images")
- Sp?rsm?lene til innleveringen (nedenfor) skal besvares i kommentarfeltet.
- Koden skal inneholde gode kommentarer som forklarer hva programmet gj?r.
- Programmet skal inneholde gode utskriftssetninger som gj?r det enkelt for bruker ? forst?.
Hvordan levere oppgaven
Kommenter p? f?lgende sp?rsm?l i kommentarfeltet i Devilry. Sp?rsm?lene skal besvares.
- Hvordan synes du innleveringen var? Hva var enkelt og hva var vanskelig?
- Hvor lang tid (ca) brukte du p? innleveringen?
- Var det noen oppgaver du ikke fikk til? Hvis ja: i. Hvilke(n) oppave(r) er det som ikke fungerer i innleveringen? ii. Hvorfor tror du at oppgaven ikke fungerer? iii. Hva ville du gjort for ? f? oppgaven til ? fungere hvis du hadde mer tid?
For ? levere:
- Logg inn p? Devilry.
- Lever alle .py-filene , og husk ? svare p? sp?rsm?lene i kommentarfeltet.
- Husk ? trykke lever/add delivery og sjekk deretter at innleveringen din er komplett. Du kan levere flere ganger, men alle filer m? v?re med i hver innlevering.
- Den obligatoriske innleveringen er minimum av hva du b?r ha programmert i l?pet av en uke. Du finner flere oppgaver for denne uken p? semestersiden.