Student: Olav Gjerde E-mail: [email protected] / [email protected]
ISimObject:
*La til decreaseHealth(), decreaseHealth(double amount), increaseHealth(), double getHealth()
-> AbstractSimObject implementerer disse med 'default' verdier. (det vil si at der er og en health-variabel)
AbstractMovingObject:
*Utvidet klassen med nye feltvariabler (se: klassehierarkiet) siden det var felles for nye
klasser og ikke kom i konflikt med oppførsel og struktur fra del 1.
*Generer tilfeldig direction hver 200 steps -> for bruk i subklasser
SimAnimal:
*Grafikk vil rotere når en viss vinkel er nådd og grafikk for å vise "liv".
*Grafikk for "liv" og synsvinkel
*Har nå funksjonalitet for å opprette lytter i konstruktør (legges til i habitat)
*Konsum av IEdibleObject utløser en 'event' ("yum")
*Forandret konstruktør til å matche nye variabel i AbstractMovingObject
*Nye metoder: 'getClosestRepellant', 'averageRepellantAngle',
'increaseHealth', 'decreaseHealth', 'getBestFood' (fungerende).
Blob:
*Forandret konstruktør til å matche ny variabel i AbstractMovingObject
Direction:
*Nye metoder: 'angleDifference', 'toRadians'.
Setup:
*Registrerte en/flere "factory" (lambda) for å opprette nye Sim... - objekter
*Generer meteorer og romskip etter "random"-generering
Habitat:
*Endret step() for å unngå "ConcurrentModificationError"
SimMain:
*Endret variabler anngående dimensjonene på habitatet (større + mindre side meny)
*La til en boolsk variabel for å vite om lyd effekter skal vere på (satt på fra setup)
*La til en bakgrunn i drawBackground()-metoden
*La til metoder for å spille av bakgrunnmusikk og å sette lydvariabel til true/false
i tillegg en get metode for denne.
SimAnimalAvoidingTest:
*Ordnet på 'avoidDangerTest1' -> denne skjekker nå om avstanden fra 'faren'
til SimAnimal-objektet øker etter en gitt mengde steg. (tester begge)
System
Design
- Planen er å lage noe som kan ligne på de gamle "space-shooters" fra Atari-tiden. Dette er noe som jeg
tror kan passe godt med hvordan objektene har mulighet til å bevege seg rundt i kordinatsystemet mtp.
360 grader osv. For å få til dette må der være romskip, planen er å få laget 2 forskjellige skip med
litt forskjellige oppførsel. Jeg tenker at det ene skipet kan være et som hovedsaklig vil jakte på
den andre typen skip. Dette skal få være det tregeste objektet i simuleringen slik at det andre skipet
har mulighet for å kunne overleve. Skip nr.2 skal være mer agilt enn det første og dermed ha litt
raskere fart og retningjusteringer. Jeg tenkte at dette skipet ikke skal utgjøre en trussel til skip nr.1
direkte men kan prøve å ødlegge meteorer som kommer flygende inn, som igjen vil kunne treffe begge skipene,
men siden skip nr.1 er tregere vil det ha større sannsynlighet for å treffe dette. Merk derimot at begge
skipene har mulighet til å "se" meteorer og vil prøve å unnvike dem. Dette vil si at jeg også vil ha et
objekt som representerer meteorer. Planen er at meteorer skal virke som de kommer flygende fra ingen plass
og har som i tidlige atari spill muligheten for å explodere til mindre meteorer. Meteorene skal og ha
mulighet til å kunne kresje med hverandre og deretter forandre retning. All denneoppførselen vil kreve
at jeg får laget et objekt som kan oppføre seg som prosjektiler som skipene skal kunne skyte.
Alle bevegende ting skal ha en livs-variabel, og vil kunne ha metoder som øk/senk liv osv. Om denne
utnyttes i objektene vil variere, i utgangspunktet tenker jeg at skipene og meteorene vil ha liv der det
skjer noe om denne verdien treffer 0. Skip nr.1 skal bare forsvinne ved liv 0, skip nr.2 skal opprette
noe som kan konsumerest av skip nr.1. SimMeteor skal eksplodere ved liv 0 (eller hard kollisjon), og om
meteorene som kom etter eksplosojnen dør skal det opprettes noe som kan konsumerest av skip nr.2.
(Se komplett oversikt over oppførsel og regler lenger nede)
Implementasjonsvalg
- Startet med å lage en klasse som senere ble SimHunter når riktig grafikk hadde blitt lagt på. Oppførselen
ble definert gjennom step()-metoden og tok i bruk hjelpemetoder i en ny klasse jeg laget; SimObjectHelper.
Denne hjelpeklassen laget jeg etter jeg såg at SimHunter og SimPrey, som er klasser som ligner på hverandre,
trengte mye av samme informasjonen for å definere sin egen oppførsel. I denne klassen la jeg til statiske
metoder som klassene jeg hadde laget kunne bruke -> i tillegg la jeg til ekstra metoder i denne etterhvert
som det kom nye objekter, dette mye redundans i de forskjellige klassene. Både SimHunter og SimPrey fikk en
synsvinkel på 180 grader (ved hjelp av angleDifference metoden jeg implementerte i Direction), så hva som
"interesant" osv avhenger av avstandbegrensning og denne vinkelen. Et unntak er at SimPrey vil merke at
SimHunter nærmer seg. Jeg valgte heretter å lage en prosjektil-klasse som hadde to forskjellige oppførseler
alt etter hvilken type man valgte i konstruktøren, dette gjor jeg slik at SimPrey og SimHunter kunne ha
ulike "ammunisjon" som skadet ulikt. Da unngår jeg at prosjektilene fra SimHunter skader seg selv og det
samme for SimPrey. Jeg valgte også her å integrere en rekkevidde slik at "kamp områdene" ble litt mer
innesperret og at disse objektene ikke skadet ting på andre siden av "habitat". Så laget jeg SimMeteor
klassen, her laget jeg flere konstruktører slik at jeg kunne få en til å bruke ved eksplosjonseffekten
jeg ville implementere, en for bruk ved testing og to som generer ganske tilfeldige retninger og plasseringer
utifra dimensjonene på "habitat". For å kunne få disse til å kresje med andre objekt la jeg til nye
hjelpemetoder i SimObjectHelper slik at klassen nå fikk mulighet til å vite nærmeste romskip og meteor.
Felles for både SimProjectile og SimMeteor er at de kaller det nye "decreaseHealth(..)" metodene på
objektene de treffer, dette gjor jeg slik at ikke både den som "skal" treffe og den som blir truffet
må holde rede på det. Dette lettet kodearbeidet og gjor ting ryddigere. Til slutt laget jeg SimGoldStar
og SimSilverStar slik at både SimHunter og SimPrey hadde sine separate ting å kunne overleve på. Her la
jeg til en teller som holder rede på hvor lang "holdbarhet" det skal være på objektet -> slik at det ikke
blir mye oppsamlet i habitat over tid (gjør ting litt vanskligere for romskipene). Jeg har også forandre litt
på dimensjonene i SimMain for å få litt større "pusterom" noe som såg naturlig ut i noe som skal være i
verdensrommet. Mot slutten la jeg til en SimUfo klassen som skal "spawne" sjeldent og legger fra seg
IEdibleObjects over skjermen. SimSounds klassen ble tilpasset fra koden jeg laget til semoppg 1, og
prøver å laste inn lydfiler ved initialisering. (Ekstra dokumentasjon finnes i klassene og under)
Klasseoversikt:
Objects in simulation:
SimHunter
SimPrey
SimMeteor
SimProjectile
SimUfo
SimGoldStar
SimSilverStar
Helper:
SimObjectHelper
SimSounds
Tests:
SimHunterTest
SimPreyTest
SimMeteorTest
SimProjectileTest
SimGoldStarTest
SimSilverStarTest
Beskrivelse av klassehierarkiet (alt som er nytt):
AbstractSimObject
Feltvariabler: double health
*SimGoldStar (IEdibleObject)
Feltvariabler: double ENERGY_FACTOR, DIAMETER, size
int counter, expirationTimer
*SimSilverStar (IEdibleObject)
Feltvariabler: ^Samme som over
AbstractMovingObject
Feltvariabler: Random randomGen
Direction randomPath
int stepCount
Habitat habitat
*SimHunter
Feltvariabler: double defaultSpeed (brukt i konstruktør)
int deathTimer (animasjonsrelatert)
*SimPrey
Feltvariabler: double defaultSpeed
int deathTimer (animasjonsrelatert)
*SimMeteor
Feltvariabler: double defaultSpeed, height, width
int type, hitCount (lydrelatert)
*SimProjectile
Feltvariabler: double defaultSpeed
int range, type
*SimUfo
Feltvariabler: double defaultSpeed
Utenforstående
*SimObjectHelper
Feltvariabler: Ingen, kun statiske hjelpemetoder
*SimSounds
Laster inn lydfiler til bruk i simuleringen
Tester
*SimPreyTest
*SimHunterTest
*SimMeteorTest
*SimProjectileTest
*SimGoldStarTest
*SimSilverStarTest
- De nye klassene bygger videre på funksjonaliteten som allerede er i AbstractSimObject og
i AbstractMovingObject. Det har blitt lagt til metoder i ISimObject som sier at der skal
være metoder som øker/minker 'health' o.l, en "default" implementasjon av dette har blitt laget
i AbstractSimObject. AbstractMovingObject har blitt utvidet med 4 nye variabler, en for Random,
Direction, (int) stepCount, og Habitat.
Fra toppen har vi SimGoldStar og SimSilverStar som utvider AbstractSimObject dette er IEdibleObjects
som har en fast posisjon og som vil forsvinne etter et gitt antall steps. Næringsinnholdet er som med
SimFeed i del 1 bestemt etter størrelsen på objektene -> og minker ved konsumering. Lavere i hierarkiet
har vi SimHunter, SimPrey, SimUfo, SimMeteor og SimProjectile. Merk derimot at SimProjectile kun blir nyttet
gjennom SimHunter og SimPrey, og eventuelt 'direkte' under testing. Utenom dette er der en hjelpeklasse
SimObjectHelper som kun har statiske hjelpemetoder til bruk sammen med de nye klassene. Der er også en
SimSounds klasse som prøver å laste lydfiller når klassen blir initialisert.
Feil/mangler
- "Ingen kritiske forhåpentligvis" - Olav :)
Oppførsel/"Spilleregler"
- SimHunter
Mål: Unngå meteorer, konsumere SimGoldStars, og jakter på SimPrey til slutt.
Fart: min 2 - maks 3 (unntak ved å komme seg inn til habitat igjen)
Liv: 1 -> maks 1.5
Prosjektil: 1/50 sjangs for å skyte når SimPrey er innen rekkevidde
og er i synsfeltet (180grader). Skyter type 0.
Prosjektilrekkevidde -> 275
Vanlig retning: Går mot midten så lenge avstanden til midten er større enn 900,
vil ta en tilfeldig retning om den er innenfor.
Siktavstand: 375
- SimPrey
Mål: Jakter på meteorer og øker livet sitt ved å konsumere SimSilverStars.
Prøver å unngå SimHunter som "siste" prioritet.
Fart: min 2.5 - maks 3.75 (unntak ved å komme seg inn til habitat igjen)
Liv: 1 - maks 2.0
Prosjektil: 1/70 sjangs for å skyte når SimMeteor er innen rekkevidde
og er i synsfeltet (180grader). Skyter type 1.
Prosjektilrekkevidde -> 300
Vanlig retning: Går mot midten så lenge avstanden til midten er større enn 900,
vil ta en tilfeldig retning om den er innenfor.
Siktavstand: 325
- SimUfo
Mål: Sprer konsumerbare object ut til romskipene. Er relativt sjelden.
Er også upåvirkbar.
Fart: 2.5
Vanlig retning: Fra venstre til høyre.
- SimMeteor
Mål: Blir opprettet utenfor habitat og kommer seilende inn. Utgjør en farefaktor.
Fart: 2.75
Liv: 1.0
Retning: Får tilskrevet en tilfeldig retning under initialisering, forandrer bane ved
kollisjon med andre SimMeteors, SimPreys eller SimHunters.
Ved død: Om type 0 -> exploderer til 2 mindre SimMeteors(type 1)
Om type 1 -> lager et SimSilverStar-object på sin posisjon
- SimProjectile
Mål: Skade objekter i sin egen bane alt etter type prosjektil.
Type 0 -> Skader SimPrey og eventuelt SimMeteor
Type 1 -> Skader SimMeteor og eventuelt SimHunter
Fart: 4
Retning: Som den er brukt i SimHunter og SimPrey tar den retningen som "de" var i
under initialisering.
- SimGoldStar/SimSilverStar
Mål: Fungere som liv til SimHunter/SimPrey
Fart: 0 -> står stille
Ekstra: Har en viss holdbarhet og vil begynne å blinke
når denne grensen nærmer seg.
Svar til innledende spørsmål:
Hvordan virker "Position.move()", og hvordan er den annerledes fra Position-klassen i Lab 5?
- move()-metoden som AbstractMovingObject benytter fungerer ved at den tar inn et Direction object og
en double som representerer "distance". Distance fra simuleringen sin side er i dette tilfellet farten vi
vil at et bestemt objekt skal opperere med. Dette er fordi move()-metoden tar inn disse variablene og oppretter
et nytt posisjonsobjekt med en relativ bevegelse i forhold til parameterne som den mottok. I retur får vi et
nytt posisjonsobjekt som AbstractSimObject kan sette til å være sitt nye "Position"-objekt.
Merk at her setter vi den gamle Position-variabelen i AbstractSimObject til det nye Position-objektet som
vart opprettet fra 'move()', dette er fordi Position-klassen er det vi kaller for "immutable"
og ikke kan forandre sine feltvariabler etter objektet har blitt opprettet, på denne måten forhindrer vi
direkte forandringer/muteringer av et Position-objekt sine feltvariabler.
*De to andre move()-metodene skaper og relativ bevegelse utifra parameterene som blir parset inn, det er
bare flere varianter av metoden til å bruke etter hvilke posisjons-data man sitter med (hendig med testdata).
Hovedforskjellen mellom denne varianten og den som finnes i Lab 5 er at Labben sin metode forandret på
feltvariablene til objektet og dermed muterte seg selv. Man kunne altså ikke vite etter initialisering av
"Position"-objektet at verdiene som det vart opprettet med alltid ville være sanne (ikke invariant som i sem2).
Hva er forskjellen på "AbstractSimObject" og "AbstractMovingObject"?
- AbstractSimObject inneholder den grunnleggende logikken for objekt som skal simulerest i programmet, og
tilskriver objektene med egenskaper som posisjon, retning, dø/levende og lignende. Mens AbstractMovingObject
er en arver fra AbstractSimObject og utvider denne med logikk som beror seg på bevegelse slik som akselerering,
farten til objektet, og hva som må brukes for å reposisjonere objektet.
Posisjonen er lagret som en private feltvariabel, hvordan skal da subklasser justere posisjonen?
- Posisjonsdatene blir konstruert utifra hvilke data som blir gitt fra retningsdataene (les: Direction),
for hver iterering av programmet vil et bevegende objekt få tilskrevet et nytt Position-objekt gjennom step()
i AbstractMovingObject som bruker "reposition(..)" i kombinasjon med "Position.move(...)" (se beskrivelse av
dette i spm.1). Etter nok "steps" vil objektet komme til ønsket posisjon ved å gå i en gitt retning (for å
oppnå dette kan man da gjøre dir = "nytt 'Direction'-objekt her").
Hva gjør hjelpemetodene "distanceTo" og "directionTo"?
- 'distanceTo' tar inn et Position-objekt og regner ut avstanden mellom objektet som kalte på metoden til
den andre posisjonen som ble parset inn, og returnerer en double med denne avstanden. 'directionTo' tar også
inn et Position-objekt, videre blir et nytt Direction-objekt returnert som beskriver vinkelen fra objektet
som kalte på metoden og til den gitte posisjonen (dermed 'retning' til en gitt posisjon).
"AbstractMovingObject" har en metode "accelerateTo" for å endre på farten.
Kunne det være smart å gjøre "speed" feltvariablen private?
- Sånn som strukturen til programmet er bygd opp fra den orginale koden kan denne variabelen likegodt vere
private siden den kun blir justert gjennom metodene i egen klasse, i tillegg har man en public getSpeed()
metode for å finne ut hvafarten er satt til uten å måtte går direkte til feltvariablen. Jevn akselerering
ser også mer naturlig ut enn et plutselig "hopp" i fart.
Der er ikke laget hjelpemetoder for å justere retningen. Hva må man gjøre for å endre retningen i en subklasse?
Burde man ha metoder også for å endre retning?
- Som nevnt i et tidligere spørsmål vil man for å endre retning sette Direction feltvariabelen til objektet
til et nytt Direction-objekt. Dette gjøres på denne måten fordi Direction klassen, slik som Position klassen
er ikke-muterbar eller "immutable". Om Direction klassen skal fortsette å vere dette kan man iallfall ikke
ha hjelpemetoder i selve Direction klassen som forandrer på feltvariablene. Men man kunne f.eks hatt en "changeDir"
metode i AbstractSimObject som tok inn et nytt Direction object og gjor (this.dir = "ny retning her") uten at
man måtte repetere denne koden lavere i hierarkiet, slik som i SimAnimal.
Der er ingen public metoder for å endre posisjon, retning, osv. Hadde det vært lurt å ha det? (Hvorfor / hvorfor ikke)
- Hadde der vært public metoder i de Abstrakte klassene for å endre på retning, posisjon og lignende, ville
forskjellige klasser som ikke er samme "pakke" som disse kunne forandret posisjonen til objektene. Det vil si
at man kunne gjort slik som SimAnimal.setPos(pos) og den ville flyttet seg i steden for at den nå kun forandrer
sin posisjon og retning gjennom step()-metoden. Dette er nødvendigvis ikke noe man ønsker om hvert av objektene
i simuleringen har egne definisjoner for hvordan de skal oppføre seg eller bevege seg og denne logikken kommer
fram gjennom det som har blitt definert i dens "step()"-metode. Et plus er at man kanskje har fleksibiliteten
til å kunne gjøre uventede forandringer, men denne kresjer kanskje litt med tanken om en "simulering".
Svar for 1.7 (Se bakgrunn for spørsmål i "README.md" under del 1.7):
Hvorfor tror du vi har laget systemet på denne måten? Hadde det vært andre måter å få det til på
(ville det i såfall vært like fleksibelt)?
- Det minimalistiske kode stilen gjør at man unngår unødvendig mye kode rundt om i "programmet", ved å utvide
den allerede eksisterende kode med flere objekt holder man på den Abstrakte struktur som har blitt konstruert.
Man går fra å ha veldig generelle egenskaper på "toppen" av hierarkiet til mer detaljrike egenskaper nederst,
og noen av disse egenskapene kommer fra slik som komparator osv. Ved å bruke anonyme klasser unngår vi unødvendige
separerte klasser i filsystemet som forstyrrer den enkle strukturer som er der. Man kunne i værste tilfelle
hatt all funksjonalitet inn i hver klasse (til og med det som er felles for f.eks SimObjects) dette hadde ført
til mye kode gjenbruk, og en generelt sett dårlig kode stil.
Kunne vi fått dette til uten å bruke grensesnitt?
- Man kunne laget samme program uten interface-klasser men det er mye enklere å kunne forholde seg til disse
siden man vet utifra grensesnittet hva klassene må inneholde av metoder. Man kan og ha definerte forklaringer
for hvordan metodene skal fungere i samsvar med objektet som implementerer de. På denne måten fungerer grensesnittene
som en kontrakt som man kan forholde seg til. Man kan og senere velge hvilke klasser som oppfyller kravene til grensesnittene.
I tillegg måtte man uten grensesnitt definert måter å håndtere hvert enkelt objekt i forskjellige situasjoner,
noe som hadde blitt til veldig mye kode.
Lydeffekter og musikk
Dreamy Flashback Kevin MacLeod (incompetech.com)
Licensed under Creative Commons: By Attribution 3.0 License
http://creativecommons.org/licenses/by/3.0/
"hunterCrash" - By n_audioman (https://freesound.org/people/n_audioman/)
License: https://creativecommons.org/licenses/by/3.0/
"laser1" - By ani_music (https://freesound.org/people/ani_music/)
License: https://creativecommons.org/licenses/by/3.0/
"laser1Failure" - By moca (https://freesound.org/people/moca/)
License: https://creativecommons.org/publicdomain/zero/1.0/
"laser2" - By ani_music (https://freesound.org/people/ani_music/)
License: https://creativecommons.org/licenses/by/3.0/
"meteorExplode" - By smcameron (https://freesound.org/people/smcameron/)
License: https://creativecommons.org/licenses/by/3.0/
"meteorHit1" - By Reitanna (https://freesound.org/people/Reitanna/)
License: https://creativecommons.org/publicdomain/zero/1.0/
"meteorHit2" - By Reitanna (https://freesound.org/people/Reitanna/)
License: https://creativecommons.org/publicdomain/zero/1.0/
"meteorHitShip" - By OwlStorm (https://freesound.org/people/OwlStorm/)
License: https://creativecommons.org/licenses/by/3.0/
"preyCrash" - By n_audioman (https://freesound.org/people/n_audioman/)
License: https://creativecommons.org/publicdomain/zero/1.0/
"ufoPassing" - By JohnsonBrandEditing (https://freesound.org/people/JohnsonBrandEditing/)
License: https://creativecommons.org/publicdomain/zero/1.0/
Grafikk:
"Space Shooter Redux" By Kenney (http://www.kenney.nl).
License: https://creativecommons.org/publicdomain/zero/1.0/
"Space Shooter Extension (250+)" By Kenney (http://www.kenney.nl).
License: https://creativecommons.org/publicdomain/zero/1.0/
Ekstra
* The angleDifference()- and averageAngle..() methods - was inspired by Anya's tips section and the links that followed
* Rammeverkkode: © Anya Helene Bagge (basert på tidligere utgaver, laget av Anya Helene Bagge, Anneli Weiss og andre).
* pipp.png, bakgrunn.png © Anya Helene Bagge, This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License