Innleveringsoppgave 6 INF109 Dataprogrammering for naturvitskap Dette er den sjette av syv obligatoriske oppgaver. Du kan få totalt 15 poeng på denne oppgaven. Innleveringsfristen er: fredag, 15. april, 23:59. Oppgaven skal leveres inn som én enkelt.py-fil. Oppgaven skal leveres inn på kursets hjemmeside på Mi Side. Oppgavene skal løses individuelt selv om samarbeid er tillatt. Lykke til! 1 Introduksjon I denne obligatoriske oppgaven skal du lage et skytespill. Litt begrenset blir det: Du skal lage et spill som tegner en blink på et tilfeldig sted på skjermen og så skal du ta tiden på hvor lang tid spilleren bruker på å treffe blinken. Senere skal du legge inn straffetid dersom spilleren bommer på blinken. Til sist skal du legge inn to blinker, én som man skal treffe og én som er en jukseblink, hvor spilleren umiddelbart taper dersom hun treffer den. 2 Oppgave 1 Spillet I denne oppgaven skal du tegne en blink (en sirkel) på en tilfeldig plass i vinduet. Følgende kodesnutt setter x og y til tilfeldige verdier mellom 1 og 10: >>> from random import randint >>> x = randint (1,10) >>> y = randint (1,10) Lag en funksjon random coordinate(x min, x max, y min, y max) som returnerer et (x, y)-koordinat hvor x-verdien ligger i [x min, x max] og y-verdien ligger i [y min, y max]. Dette koordinatet skal brukes som sentrum i en sirkel med en gitt radius r. Husk at når denne funksjonen kalles må ikke x- eller y-verdiene være slik at sirkelen med radius r går utenfor vinduets rammer. Varm først opp med å lage et program som lager et vindu på størrelse 600 400 som tegner en rød fylt sirkel med radius 50 piksler på et tilfeldig sted i vinduet (hele sirkelen skal være med). Programmet skal avsluttes når brukeren trykker i sirkelen (se hjelpefunksjonen distance i Seksjon 4). Legg deretter inn funksjonalitet for å telle antall ganger brukeren bommer, og så skrive det ut når programmet avsluttes. For å få en rød sirkel på hvit bakgrunn, bruk følgende kodesnutt. (Funksjonen setfill fyller sirkelen rød, mens funksjonen setoutline tegner kanten 1
rød.) Se Figur 1 for å se et eksempel 1. Merk at objektet circ er av typen Circle og må opprettes ved å gi den et punkt og en radius, e.g. c = Circle(Point(150,250), 50). win. setbackground (" white ") circ. setfill (" red ") circ. setoutline (" red ") Figure 1: En rød sirkel, med radius 50 piksler, tegnet på posisjon (150,250). 2.1 Første funksjon Du har nå laget et program som tegner en sirkel et tilfeldig sted på vinduet og teller hvor mange ganger spilleren bommer på sirkelen. Dette blir nå den første funksjonen. 2 Fullfør følgende funksjon. (Merk at center er et Point og ikke et tall.) def draw_and_count ( win, center, radius ): """ This function draws a red circle with center and radius as given in the given window and counts the amount of tries the user needs to hit inside it. Finally it undraws the circle and returns the number of misses the user made. """ misses = 0 # Here be your code target. undraw () # undraws the target from window return misses Denne funksjonen bør, som alltid, testes. Prøv at alt fungerer ved å lage et vindu, deretter kalle denne funksjonen med noen argumenter du velger selv, og sjekk at dersom du treffer på første forsøk, returnerer funksjonen 0 og at den i alle andre tilfeller returnerer antall ganger du trykket på feil sted (bruk print("brukeren bommet", draw and count(...), "ganger.")). 1 Kanskje du finner det merkelig at y-koordinatet 250 er overraskende langt nede i bildet. Grunnen er at dersom man ikke setter koordinatsystemet slik vi har gjort i tidligere oppgaver, er koordinatet (0, 0) oppe i venstre hjørne; Det er dette som er standarden i de aller fleste grafikkbiblioteker og som kan være kjekt å vite dersom man skulle komme over andre biblioteker eller programmeringsspråk. 2 Med unntak fra de åpenbare hjelpefunksjonene, distance m.m., funksjonen som sjekker om et punkt er inni et objekt is point inside, men en sirkel denne gang, ikke et rektangel. 2
2.2 Tidtaking Nå har du første delen av spillet på plass. Det neste du skal gjøre er å lage en stoppeklokke. Du har muligheten i Python til å få tak i det nåværende klokkeslettet. Dette gir deg igjen muligheten til å måle hvor lang tid brukeren brukte på å fullføre en oppgave: Du henter nåværende tid, lar brukeren gjøre seg ferdig med det vedkommende skal gjøre seg ferdig med, og henter nåværende tid igjen. Da har du to klokkeslett, et start- og et sluttklokkeslett. Varigheten er selvsagt differansen stop - start. Spilleren har i denne oppgaven ti sekunder på seg til å klare å treffe ti blink. Det vil si at vi må ta tiden på hvor lang tid det tar fra spillet begynner til spilleren har truffet ti blinker. Dette gjør vi med pakken datetime. I den pakken ligger det et objekt med samme navn, datetime, som vi kan bruke for å få tak i klokkeslettet (merk at som i eksempler med tilfeldige tall, vil du sannsynligvis se andre tall enn i følgende eksempel): >>> from datetime import datetime >>> print ( datetime. now ()) 2012-11 -05 00:01:35.819285 >>> start = datetime. now () >>> stop = datetime. now () >>> print ( stop - start ) 0:00:02.947108 >>> stop = datetime. now () >>> print ( stop - start ) 0:00:24.135830 >>> delta = ( stop - start ) >>> delta datetime. timedelta (0, 24, 135830) >>> print ( delta. total_seconds ()) 24.13583 Merk at delta er et merkelig objekt. Det er et slags klokkeobjekt som holder på informasjon om en varighet, en differanse mellom to klokkeslett. Husk at du kan skrive help(delta) for å få opp informasjon om dette objektet og hvilke funksjonalitet det har. Spesielt kan du, som du ser i eksempelet, hente ut antall sekunder differansen er på. Lag en funksjon som følger: def run_game_round ( win, n): """ This function draws a circle n times and for each time waits for the user to press inside the circle. Returns a pair containing the time used ( timedelta ) and the number of misses. """ misses = 0 start = datetime. now () # Here goes code, remember to use draw_and_count! return ( delta, misses ) Helt til slutt i denne oppgaven skal du regne ut om spilleren klarte spillet eller ikke. Spilleren får ett straffesekund per bomskudd, og for å vinne, må spilleren ikke ha brukt mer enn ti sekunder totalt (inkl. straffetid). 3 Oppgave 2 Blinken krymper Gå tilbake i den forrige funksjonen du laget, run game round og gjør blinken mindre for hver runde. Dette kan du gjøre enkelt ved å la størrelsen originalt være 3
target_size = 50 og for hver gang du tegner den på nytt, dersom i er telleren fra 0 til n 1, lar du sirkelens radius være som under. Hvis du synes den krymper for fort eller for sakte, kan du alltids endre denne funksjonen etter eget ønske (så lenge det er en mulighet for at radiusen endres). Du kan også gjøre radiusen til en tilfeldig størrelse ved å bruke randint. radius = target_size - (5 * i) Under ser du et bilde av spillet når spilleren har klart å trykke korrekt på et par blinker, og blinken har blitt betydelig mindre. Figure 2: Skjermbilde fra midt i spilløpet. 4 Oppgave 3 Lureblink Også i denne oppgaven skal du endre i en tidligere funksjon, men denne gangen i draw and count. Du skal lage en lureblink som spilleren for all del ikke må treffe. Den skal tegnes med sentrum anti center, så du må endre funksjonen til å se slik ut: def draw_and_count ( win, center, anti_center, radius ): """... """ #... return misses Dersom spilleren treffer denne fører det til umiddelbar død (eller ihvertfall til tap). Lureblinken skal tegnes på et tilfeldig sted, akkurat som den ekte blinken, og den skal ha samme størrelse (radius). Det vil si at den skal krympe, akkurat som den ekte blinken. Denne blinken skal være vanskelig å skille fra den egentlige blinken, og det gjør vi ved å tegne blinken med en farge som er bare så vidt mørkere enn den egentlige. (Du kan manuelt lage hvilke som helst farge med (r, g, b)-verdier på en måte som du ser her.) 4
anti_target. setfill ( color_rgb (180,0,0) ) Dersom spilleren treffer denne blinken, skal du umiddelbart avslutte spillet og skrive ut at spilleren har tapt. Dette fører til et lite dilemma, hvordan skal du klare det når funksjonen draw and count bare returnerer antall ganger du har bommet? Her må du selv tenke på noe lurt som gjør det lett for deg. Du kan velge å returnere et negativt tall, None eller andre verdier som gjør det mulig for deg å finne ut om spilleren traff lureblinken. Figure 3: Spill med lureblink (til venstre). I tillegg må du være påpasselig på at denne nye blinken ikke overlapper med den forrige blinken. from math import pow, sqrt def def sq(x): """ Returns x^2, x squared """ return pow (x,2) distance (x1,x2,y1,y2): """ Returns distance from (x1,x2) to (y1,y2)""" return sqrt ( sq( x1 - x2) + sq( y1 - y2)) Bruk distansefunksjonen for å sørge for at avstanden mellom lureblinken og den ekte blinken er minst (to ganger) radiusen, og slik sørge for at de aldri overlapper. Dersom avstanden ikke er stor nok, må du hente ut to nye tilfeldige koordinater for lureblinken. (Funksjonen kan også brukes for å sjekke om spilleren har klikket inni en sirkel eller ikke; Spilleren har trykket i sirkelen dersom avstanden fra museklikket til sentrum er mindre enn radiusen, her bruker vi Pythagoras teorem.) 5 Oppsummering Nå er du ferdig. Pakk sammen alle sakene i en funksjon som heter main og som ikke tar inn noen argumenter. Denne skal altså kalle den store funksjonen du har jobbet med i de forrige oppgavene, run game round med et vindu på korrekt størrelse og med n = 10. 5
6 Innlevering Lagre programmet ditt i en.py-fil som heter oblig6-abc123.py, hvor abc123 skal erstattes med ditt brukernavn, og lever besvarelsen på studentportalen Mi Side, i mappen 7 Vurdering Innleveringsmappe Vurderingsmappe Innlevering 6. For denne oppgaven kan du få maksimalt 15 poeng. Fordeling av poeng er som følger. Programmet fungerer som forventet 12 (ca. 4 + 4 + 4) Programmet er veldokumentert og oversiktlig 2 Navngiving 1 15 Programmet fungerer som forventet. Her får man trekk om programmet enten skriver ut feil data, har feil format på utskrift (avviker fra eksempelet), kræsjer på noen som helst måte eller om metodenavn ikke er som oppgitt. Programmet er veldokumentert med kommentarer og oversiktlig. Her får man trekk om programmet ikke er kommentert godt nok, men også om programmet er for mye kommentert. Man kan også få trekk om programmet er uoversiktlig. Navngiving. Her får man trekk dersom navn på variabler og metoder ikke er fornuftig valgt og at navngivingen som er brukt er inkonsistent. Husk å velge enten mixedcase eller underscore. 3 Oppgavene vil være ferdigrettet innen én uke har gått, og poengsum og kommentarer fra retter vil bli postet i vurderingsmappen under kommentarer. Questions and/or complaints to be directed at one of the group leaders. Obs: Den nye karakteren vil bli stående og det er ikke garantert at den ikke vil gå ned. 3 mixedcase er når variablene ser ut som innlestdata, mens underscore er når variablene skrives som innlest data. Velg én av disse to, og hold dere til den måten. I boken bruker de førstnevnte, mens det er kanskje vanligere ellers i Python-verdenen å bruke sistnevnte. Aldri begynn med stor forbokstav, e.g. InnlestData eller lignende, da disse er reservert til klassenavn, noe som er delvis utenfor pensum for dette kurset. 6