Obligatorisk innlevering uke 8-9 (innlevering nr. 7)

Frist for innlevering: 20.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

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:

Informasjonen skal lagres i tilh?rende instansvariabler (f. eks skal bilde lagres i self._bilde).

Sau skal i tillegg ha to instansvariabler _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:

  1. En metode sett_posisjon som setter posisjonen (self._posisjon_venstre og self._posisjon_topp) til en ny posisjon. Denne metoden m? alts? ta to parametere (i tillegg til self).
  2. En metode hent_posisjon_venstre og en metode hent_posisjon_topp som returnerer de to posisjonene.
  3. En metode hent_fra_fra_venstre og en metode hent_fart_fra_topp som returnerer farten (fra venstre og fra toppen).
  4. En metode sett_fart som setter farten (self._fart_fra_venstre og self._fart_fra_topp) til en ny fart.
  5. 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? skal self._posisjon_topp endres ved at self._fart_fra_topp plusses p?, og tilsvarende for self._posisjon_venstre.
  6. 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 at fart_fra_venstre = 1 og fart_fra_topp=1), s? skal den nye farten bli fart_fra_venstre=-1 og fart_fra_topp=-1. Vi snur sauen 180 grader ved ? gange fart_fra_venstre og fart_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.
  7. En metode tegn som tar et parameter skjerm som representerer skjermen vi skal tegne sauen p? n?r spillet kj?rer. Ikke implementer denne metoden n?, bare bruk pass eller return 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):

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.

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:

  1. Installer Pygame Zero hvis du ikke har gjort det enda.

  2. Lage 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 sin oppdater-metode hver gang noe skal oppdateres (som i praksis skjer mange ganger i sekundet).

  3. 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 tar skjerm som parameter. skjerm er et objekt i Pygame Zero som har ulike metoder for interagere med bildet som vises p? skjermen. Vi skal kalle metoden blit 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:

screenshot

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:

screenshot

Oppgave 6: Flere objekter (ulv, gress, stein)

Lag en klasse Gress i en fil gress.py:

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.

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:

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:

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_spistog 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:

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:

screeenshot

Ekstra valgfrie utvidelser

I neste innlevering skal vi pr?ve ? gj?re sauene og ulvene mye smartere. M?let for sauene er ? overleve lengst mulig og spise mest mulig gress f?r de blir spist av en ulv. Det kommer til ? v?re en konkurranse der m?let er ? lage de smarteste sauene, og det vil bli en turnering der sauer etter tur konkurrerer om h?yest mulig score p? samme baner. Sauene vil bli testet p? et utvalg av (hemmelige) baner og m?let er ? overleve lengst og spise mest mulig gress.

Legg til en variabel _score i klassen Sau. Hver gang en Sau spiser gress, skal scoren ?ke med 1. Print scoren til terminalen n?r en Sau blir spist av en Ulv.

Utvid init-metoden til Sau slik at spillbrettet ogs? sendes inn (p? samme m?te som i Ulv). Pr?v ? forbedre bevegelsen til sauen slik at den fors?ker ? se hvor det er ulver og beveger seg trygt i forhold til det. Dette skal vi se mer p? i neste innlevering.

PS: Merk at det n? ikke er noen begrensning p? farten til sauen. Man kan alts? lage en sau som er veldig flink til ? unng? ? bli spist ved ? bare la den bevege seg veldig fort. I neste innlevering kommer vi til ? ha begresninger p? hvor fort ulver og sauer kan bevege seg.

Krav til innlevering

Hvordan levere oppgaven

Kommenter p? f?lgende sp?rsm?l i kommentarfeltet i Devilry. Sp?rsm?lene skal besvares.

For ? levere:

  1. Logg inn p? Devilry.
  2. Lever alle .py-filene , og husk ? svare p? sp?rsm?lene i kommentarfeltet.
  3. 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.
  4. 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.