Kurssin viimeisimmän version löydät täältä: O1: 2024
Luku 7.4: Raittiuspeli ja kaupunkisimulaattori
Tästä sivusta:
Pääkysymyksiä: Lisäharjoitusta, kiitos? Miten tutustun laajempaan annettuun koodiin, jossa usea luokka riippuu toisistaan?
Mitä käsitellään? Luku koostuu kahdesta pisteytetystä ja yhdestä vapaaehtoisesta ohjelmointitehtävästä. Treeniä mm. periytymisestä ja kokoelmametodeista. Algoritmista harjoittelua. Esimerkki refaktoroinnista periytymisen avulla.
Mitä tehdään? Tutustutaan annettuun koodiin ja ohjelmoidaan.
Suuntaa antava työläysarvio:? Viisi tuntia? Vaihtelee paljon; joka tapauksessa varmasti kierroksen työläin luku.
Pistearvo: B60 + C60.
Oheisprojektit: Viinaharava (uusi), Segregation (uusi). Lisätehtävässä AuctionHouse2 (uusi).
Viinaharava-peli
Paikallinen raittiusseura on tilannut veden juomiseen kannustavan pelin. Tuloksena on jo syntynyt melkein valmis raittiuspeli nimeltä Viinaharava, jonka täydentäminen jää nyt sinun huoleksesi.
Viinaharava-pelissä on sijoitettu pieniä juomalaseja suorakaiteen muotoiseen ruudukkoon. Suurimmassa osassa on vettä, mutta muutamassa on tiukkaa alkoholijuomaa. Pelaajan tehtävänä on juoda kaikki vedet haksahtamatta viinaan. Virtuaalijuonti käy näpäyttämällä juotavaksi valittua lasia. Pelaajan tehtävää helpottaa se, että kunkin juodun lasin pohjasta löytyy vihje: tieto siitä, kuinka monta viinaa kyseisen lasin viereisissä ruuduissa on. Peli päättyy, kun kaikki vedet tai yksikin viina on juotu.
Tehtävänanto
Osittain toimiva toteutus pelille löytyy Viinaharava-projektista. Se on esitelty tarkemmin alempana tällä sivulla.
Tutustu projektiin ja täydennä puuttuvat osat.
Suositellut työvaiheet
- Aja Viinaharava-peli käynnistysoliosta
Viinaharava
lähtien. Huomaat: pelikenttä tulee näkyviin, mutta peli ei toimi. - Tutustu luokkaan
o1.Grid
, jota Viinaharavan toteutuksessa on käytetty apuna. Siitä on lisätietoja alla. - Tutustu pakkauksen
o1.viinaharava
luokkiin. Aloita pakkauksen esittelystä alempana tällä sivulla. Jatka scaladoceihin ja ohjelmakoodiin. - Kun ymmärrät annetun ohjelman, täydennä sen puuttuvat osat. Lisää ohjeita ja vinkkejä on tarjolla alempana tällä sivulla.
Työkaluja ruudukon esittämiseen
Muistat matopelin luvusta 6.2. Siinä madon osat ja ruoka sijaitsivat eri kohdissa
ruudukkomaista pelikenttää, minkä kirjasimme käyttäen GridPos
-olioita. Kukin GridPos
muodostui kahdesta kokonaisluvusta x
ja y
, jotka vastaavat tiettyä "ruutua".
Viinaharavassa on samaa kuin matopelissä: siinäkin pelimaailman muodostaa eräänlainen
ruudukko. Voimme käyttää lasien sijaintien kuvaamiseen GridPos
-luokkaa.
(Tässä tehtävässä sinun ei tarvitse lainkaan käsitellä pikseleita tai grafiikkaa. Annettu
käyttöliittymä huolehtii niistä. Voit keskittyä pelin sisäiseen maailmaan, jossa sijainteja
käsitellään ruutujen numeroina eli GridPos
-olioina.)
Matopelissä ruudukko oli "harva": ainakin lähtökohtaisesti ruudukkoon sijoitettuja asioita
(madon osia, ruokaa) oli vain vähän suhteessa ruudukon kokoon. Mallinsimme pelin kirjaamalla
vain sen, missä GridPos
-koordinaateissa on jotain ja muualla oli sitten tyhjää.
Tällä kertaa teemme toisin ja mallinnamme Viinaharavan pelilautaa "tiheänä" ruudukkona: kirjaamme jokaisesta ruudukon sijainnista erikseen, millainen lasi siinä on. Onko se vesi? Onko lasi jo tyhjennetty? Montako vaarallista naapuria sillä on?
Ruudukon kuvaaminen tiheästi käy kätevämmin, kun otamme käyttöön lisäapuvälineen, luokan
Grid
.
Luokka Grid
Kurssin peruskirjastossa O1Library on luokka o1.Grid
. Kukin Grid
-olio edustaa
ruudukkoa, jossa jonkinlaisia keskenään samankokoisia elementtejä (esim. laseja) on
asemoitu suorakaiteen muotoon xy-koordinaatistoon. Grid
tarjoaa metodeita tällaisen
ruudukon käsittelemiseen. Sen avulla voi esimerkiksi poimia koordinaattiparin perusteella
tietyn ruudun (elementAt
tai apply
), etsiä kaikki tietyn ruudun naapuriruudut
(neighbors
) ja selvittää ruudukon koon (width
, height
ja size
).
Grid
-luokka on abstrakti. Siitä ei voi luoda ilmentymiä noin vain käskyllä new Grid
vaan vain aliluokkiensa kautta. Tämä abstrakti luokka on tarkoitettu sopimaan erilaisiin
ohjelmiin, joissa on tarpeen käsitellä ruudukkoja ja ruutujen koordinaatteja: Grid
ei
ota kantaa siihen, millainen yksittäinen ruutu on, vaan se jää aliluokkien määriteltäväksi.
Viinaharava on eräs Grid
-luokan käyttötapaus: pelilauta on ruudukko, joka muodostuu
laseja kuvaavista olioista. Myöhemmissä tehtävissä tulemme käyttämään Grid
iä myös
muunsisältöisten ruudukkojen kuvaamiseen.
Viinaharava-projekti yleisesti
Viinaharava-projekti muodostuu kahdesta pakkauksesta. Käyttöliittymäpakkaukseen
o1.viinaharava.gui
sinun ei tarvitse tarkemmin tutustua, eikä sitä tässä tarkemmin
esitellä. Riittää kun saat käyttöliittymän käyntiin. Tehtävässä muokataan pelin
varsinaista sisältöä, joka on pakkauksessa o1.viinaharava
.
Projektin kaksi keskeistä luokkaa ovat:
Glass
: tämän luokan ilmentymät ovat yksittäisiä laseja, joista pelilauta koostuu.GameBoard
-olio puolestaan edustaa pelilautaa kokonaisuutena. Pelilauta on eräänlainen ruudukko;GameBoard
onGrid
in aliluokka.
Tässä kokoava kaavio mainituista luokista ja niiden välisistä suhteista:
Kaavion alaosa tahtoo sanoa, että kuhunkin pelilautaan liittyy useita laseja, joista
kullakin on omat koordinaatit kyseisellä laudalla. Tietyn GameBoard
-olion tietty
Glass
-olio voidaan poimia GridPos
-olion perusteella.
Glass
-luokka ja sen puuttuvat metodit
Yksittäinen lasi voi olla joko täynnä tai tyhjä. Sen sisältö voi olla joko vettä tai viinaa. Kukin lasiolio myös pitää kirjaa vaarallisuudestaan eli siitä, montako viinaa vaanii viereisissä ruuduissa (0–8 kpl; vinosuunnat lasketaan mukaan).
Lasiolioille on määritelty ilmentymämuuttujat, joissa voi pitää kirjaa täyttöasteesta,
sisällön tyypistä ja vaarallisuudesta. Lisäksi kukin Glass
-olio "tietää", millä
pelilaudalla se on ja missä koordinaateissa.
Lasi on luotaessa täynnä vettä. Glass
-luokan on tarkoitus tarjota metodeita, joilla
lasin tilaa voi päivittää. Päivityksiä on kahdenlaisia:
- Lasin voi täyttää viinalla (
pourBooze
). Tämä myös kasvattaa viereisten ruutujen vaarallisuutta.pourBooze
-metodia kutsutaan ennen pelin alkua, kun lauta viinoitetaan. (Käyttöliittymässä on myös mahdollista testimielessä kaataa lisää viinoja pelin aikana.) - Lasin voi juoda tyhjäksi. Metodia
drink
kutsutaan, kun käyttäjä näpäyttää hiirellä lasin kuvaa käyttöliittymässä. Jos pelaaja osuu viinaan, tyhjenevät samalla kaikki muutkin viinalasit (ja peli päättyy).
pourBooze
- ja drink
-metodit ovat kuitenkin kunnolla toteuttamatta annetussa
ohjelmakoodissa. Siksi peli ei tee mitään laseja hiirellä klikatessa. Samoin puuttuu
neighbors
-metodi, jolla tulisi voida selvittää lasin viereiset lasit.
GameBoard
-luokka ja sen puuttuvat metodit
Tässä alkua GameBoard
-luokalle:
class GameBoard(width: Int, height: Int, boozeCount: Int) extends Grid(width, height) {
// ...
Grid
-oliota luotaessa tarvitaan leveys ja korkeus.
Nämä kaksi parametriarvoa välitetään yliluokan ohjelmakoodin
käsiteltäviksi.Alkuriville tarvitaan vielä pieni lisäys ennen kuin määrittely toimii. Yliluokka Grid
vaatii nimittäin konstruktoriparametrien lisäksi tyyppiparametrin. Samaan
tapaan kuin vaikkapa tuttua luokkaa Buffer
käyttäessä täsmennämme hakasulkeissa,
mikä puskurin alkoiden tyyppi on, myös Grid
-luokkaa käyttäessämme meidän tulee kirjata
millaisista elementeistä ruudukko koostuu.
class GameBoard(width: Int, height: Int, boozeCount: Int) extends Grid[Glass](width, height) {
// ...
GameBoard
-olio on sellainen Grid
, jossa ruudukon joka
kohdassa on Glass
-olio:Kuten kokeilemalla näit, annettu Viinaharava-toteutus jo luo laudan täyteen vesilaseja. Katsotaan annettua koodia hieman pidemmälle, niin näemme, mikä osa on tästä vastuussa.
class GameBoard(width: Int, height: Int, boozeCount: Int) extends Grid[Glass](width, height) {
def initialElements = {
val allLocations = (0 until this.size).map( n => GridPos(n % this.width, n / this.width) )
allLocations.map( loc => new Glass(this, loc) )
}
this.placeBoozeAtRandom(boozeCount)
Grid
jättää abstraktiksi metodin, joka tulee tuottaa luettelo kaikista
niistä elementeistä, jotka ruudukossa aluksi ovat. (Yliluokka
kutsuu tätä metodia automaattisesti, kun uusi ruudukko luodaan.)GameBoard
toteuttaa tämän metodin palauttamalla
kokoelmallisen tyhjiä Glass
-olioita. Voit tutustua tähän
toteutukseen, jos haluat; tehtävän kannalta välttämätöntä se
ei ole. Tähän metodiin ei tarvitse eikä pidä tehdä muutoksia.placeBoozeAtRandom
-metodikutsu
on osa GameBoard
-ilmentymän alustavaa koodia (eli konstruktoria).
Se suoritetaan aina, kun uusi pelilauta luodaan.Tuo placeBoozeAtRandom
-metodi on kuitenkin annettuun koodiin toteutamatta,
joten pelilaudalle ei tule viinoja. Asia täytyy korjata. Samoin puuttuu toteutus
isOutOfWater
-metodilta, jota käytetään pelin päättymisen tarkistamiseen.
Alla on kolmivaiheinen ehdotus siitä, miten voit edetä.
Mahdollinen toteutusjärjestys, vaihe 1/3: vedet
Täydennä Glass
-luokan drink
-metodiin vesilaseja käsittelevä
if
-haara. Siinä ei juurikaan ole tekemistä.
Toteuta sitten GameBoard
-luokan metodi isOutOfWater
.
- Saat kaikki laudan lasit helposti
Grid
-luokasta periytyvälläallElements
-metodilla. - Sopivalla korkeamman asteen metodilla (luku 6.2) toteutus on yksinkertainen.
Kokeile peliä uudestaan. Laseja voi nyt tyhjentää mielin määrin. Kun kaikki vedet on klikattu, sovellus ilmoittaa asiasta. Viinat ja niiden tuoma jännitys puuttuvat.
Mahdollinen toteutusjärjestys, vaihe 2/3: viinat laudalle
Toteuta Glass
-luokan neighbors
-metodi. Vinkki: hyödynnä olemassa olevaa metodia, niin
ratkaisusta tulee hyvin yksinkertainen.
Täydennä sen jälkeen samaan luokkaan pourBooze
-metodi. Kun olet tehnyt tämän, niin
viinaa saa laseihin ja eri lasien "vaarallisuus" nousee asianmukaisesti. Kuitenkaan pelin
toiminta ei vielä muutu, koska kyseistä metodia ei vielä kutsuta mistään.
Siirry GameBoard
-luokan yksityiseen placeBoozeAtRandom
-metodiin. Täydennä se siten,
että se kaataa sattumanvaraisiin laseihin viinoja niin monta kuin sen parametri määrää.
Tarkoituksena on arpoa tietty määrä viinoja pelilaudalle siten, että sijainnit
vaihtelevat pelikertojen välillä. Jokaisen arvotun sijainnin on oltava erilainen, sillä
viinat eivät voi olla päällekkäin.
Alla on kuvattu kaksi eri tapaa lähestyä ongelmaa. Voit valita niistä kumman haluat, tai voit käyttää jotakin muutakin tapaa, kunhan lopputulos toimii.
Algoritmi 1:
- Arvo satunnaisgeneraattoria käyttäen koordinaattipari.
- Selvitä, onko juuri arvotuissa koordinaateissa jo viina.
- Jos on, älä tee mitään.
- Jos ei, kaada viina kyseiseen lasiin.
- Toista kohtia 1 ja 2, kunnes laudalla on haluttu lukumäärä viinoja.
Kumpi on parempi?
Voit miettiä, kumpi viereisistä ratkaisuista vaatii tietokoneelta enemmän töitä (aikaa). Miten työmäärä riippuu laudan koosta ja viinojen lukumäärästä?
Algoritmi 2:
- Muodosta kokoelma, jossa on jokainen lasiolioista.
- Sekoita kokoelman sisältämät oliot sattumanvaraiseen järjestykseen.
(Tämän voi tehdä vaikka silmukalla itsekin, mutta helpommin se sujuu
käyttämällä
Random
-olion kätevääshuffle
-metodia, josta on esimerkki hieman alempana.) - Ota kokoelmasta haluttu määrä laseja. Kaada viina kuhunkin niistä.
Tässä esimerkki mainitusta shuffle
-metodista:
import scala.util.Randomimport scala.util.Random val lukuja = (1 to 10).toVectorlukuja: Vector[Int] = Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) Random.shuffle(lukuja)res0: Vector[Int] = Vector(8, 9, 7, 4, 6, 1, 10, 2, 5, 3) Random.shuffle(lukuja)res1: Vector[Int] = Vector(8, 6, 4, 5, 9, 1, 3, 7, 2, 10)
Mahdollinen toteutusjärjestys, vaihe 3/3: viinojen juonti
Voit jälleen kokeilla ohjelmaa. Sitä pitäisi jo voida pelata, mutta viinat eivät tule näkyviin niihin osuessa. Tarkoitus olisi, että pelaajan haksahtaessa näkyviin tulisivat (eli tyhjenisivät) kaikki koko laudan viinat.
Täydennä drink
-metodin toinen haara eli viinaan osumisen tapaus.
- Voit hyödyntää pelilaudan
boozeGlasses
-metodia, joka palauttaa vektorissa kaikki laudan viinalasit. - Voit tyhjentää viinalasit yksinkertaisesti säätämällä kunkin lasin
isFull
-muuttujan arvoa. (Tässä kohden ei ole tarpeen esimerkiksi kutsuadrink
-metodia sen itsensä sisältä tms.)
Kokeile taas. Ohjelman pitäisi nyt olla varsin pelikelpoinen.
Kulaus per klikkaus, toistaiseksi
Mieleesi saattaa juolahtaa, että kun juodun veden vaarallisuus on nolla, eli naapurissa ei ole yhtään viinaa, niin samalla näpäyksellä voisi turvallisesti siemaista naapurilasitkin tyhjiksi. Tällainen lisätoiminnallisuus toteutetaankin luvussa 11.2 rekursioksi kutsutulla ohjelmointitekniikalla.
Palauttaminen
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Kaupunkilaisia simulaattorissa
Yksi ohjelmoinnin väkevimmistä voimista on maailmassa esiintyvien ilmiöiden ja prosessien mallintaminen. Tässä tehtävässä käytämme laskennallista mallia sosiaalisen ilmiön tutkimiseen. Otat haltuun projektin Segregation, jossa mallinnamme kaupunkilaisten muuttoliikehdintää kaupungin kartalla ja erityisesti sitä, miten asukkaiden kuuluminen eri kansanosiin voi vaikuttaa kaupungin sosiaalisiin rakenteisiin.
Kansanosalla (demographic) tarkoitamme tässä mitä tahansa sellaista kaupunkilaisten osajoukkoa, jonka ihmiset saattavat kokea merkitykselliseksi arvioidessaan naapurustoaan. Kaupunkilaiset voidaan luokitella kansanosiin vaikkapa taloudellisen tilanteen, poliittisen kannan, etnisen taustan tai iän perusteella. Tämän luvun versiossa ohjelmasta on vain kaksi kansanosaa, jotka on kuvattu punaisella ja sinisellä värillä; luvussa 9.2 laajennamme ohjelman käsittelemään useampia ryhmiä.
Kunkin simulaation alussa kartalle sijoitetaan punaisen ja sinisen kansanosan edustajia sattumanvaraisiin paikkoihin. Simulaatio etenee askelittain: kullakin askelella kaupunkilaiset arvioivat tyytyväisyytensä nykyiseen naapurustoonsa ja saattavat muuttaa johonkin tyhjistä asunnoista. Tyytyväisyyttä arvioidaan sen perusteella, kuuluuko riittävän suuri osuus naapurustosta samaan kansanosaan.
Käyttämämme malli perustuu taloustieteen nobelisti Thomas Schellingin työhön. Se on luonnollisesti yksinkertaistus todellisesta maailmasta; mallit ovat.
Let me remind you of the particular characteristics of all of these behavior systems[.] It is that people are impinging on other people and adapting to other people. What people do affects what other people do.
—Thomas Schelling
Segregation-projekti
Segregation-projektissa on kaksi pakkausta. Varsinaisen kaupunkimallimme muodostaa
pakkaus o1.segregation
, jonka keskeiset luokat ovat:
Simulator
(annettu osittaisena). Simulaattorioliota voi pyytää käynnistämään uuden simulaation (startNew
) tai edistämään viimeksi käynnistettyä simulaatiota yhdellä askelella eteenpäin (moveResidents
). Apunaan simulaattori käyttää kaupungin karttaa kuvaavaa oliota, joka on tyyppiä:CityMap
(annettu valmiina). Kartta on eräänlainen ruudukko:CityMap
periytyyGrid
-luokasta kutenGameBoard
edellisessä tehtävässä. Kukin ruudukon elementeistä on tyyppiä:Demographic
(puuttuu). Yksinkertainen suljettu piirreluokka toimii yläkäsitteenä luokalleOccupied
ja yksittäisoliolleVacant
, jotka kuvaavat varattuja ja vapaita osoitteita.
Pakkauksen o1.segregation.gui
luokat määrittelevät sovelluksen käyttöliittymän. Ne
voit jättää huomiotta, kunhan tiedät, että SimulatorApp
toimii käynnistysoliona: se luo
Simulator
-luokasta ilmentymän ja komentaa sitä käyttäjän nappuloilla ja liukusäätimillä
antamien ohjeiden mukaisesti.
Kaavio mainittujen luokkien suhteista:
Tehtävänanto
Lisää piirreluokka Demographic
ja määrittele Occupied
ja Vacant
sen alakäsitteiksi.
Lisää Simulator
-luokkaan metodit findDemographic
, dissatisfiedResidents
ja
moveResidents
. (Luokasta puuttuu myös residents
-niminen metodi, mutta sen
toteuttaminen jääköön myöhemmäksi.)
Kokeile erilaisia asetuksia ohjelman käyttöliittymässä ja pohdi, miten ne vaikuttavat naapurustojen muodostumiseen.
Ohjeita ja vinkkejä
Demographic
-piirreluokka:
- Tämä piirreluokka on erittäin yksinkertainen: siihen ei tarvita metodeita lainkaan.
- Alakäsitteisiin
Occupied
jaVacant
ei tarvitse lisätä kuinextends
-määrittely. - Sulje piirreluokka
sealed
-sanalla (luku 7.2). Nyt minkä tahansaDemographic
-olion on väistämättä oltava jokoOccupied
taiVacant
. - Lopputulos muistuttaa
Option
-luokkaa ja senSome
- jaNone
-alakäsitteitä.Option
iakin olisi tässä voinut käyttää, muttaDemographic
alatyyppeineen mallintaa selvemmin juuri tähän sovellukseen liittyviä käsitteitä.
findDemographic
-metodi:
- Huomaa, että sinulla on
Simulator
-olioncityMap
-ilmentymämuuttujassa viittaus aktiiviseen kaupungin karttaan eliCityMap
-olioon, joka sisältääDemographic
-olioita ruudukossa. - Metodille on varsin yksinkertainen toteutus, kunhan vain poimit työkaluiksi
sopivat metodit
Simulator
ja/taiCityMap
-luokkien dokumentaatiosta.
dissatisfiedResidents
-metodi:
- Tyytyväisyys riippuu siitä, kuuluuko riittävän iso prosentti
naapureista samaan kansanosaan. Tuo käyttäjän liukusäätimellä
asettama tavoiteprosenttiosuus on sinulle tarjolla lukuna
0:n ja 100:n väliltä muuttujassa
similarityDesired
. - Jokaisella asunnolla ei ole samaa määrää naapureita. Huomioi huolellisesti Scaladocin kuvaamat erilaiset tilanteet.
- Pyri jaottelemaan metodin koko tehtävä osatehtäviin. Yksi osatehtävä on esimerkiksi se, että on tutkittava, onko tietyn osoitteen naapurusto epätyydyttävä.
- Voit laatia apufunktioita joko yksityisiksi metodeiksi tai
paikallisiksi funktioiksi
dissatisfiedResidents
-metodin sisään. - Hyödynnä taas
CityMap
-olion toimintoja. Muista, ettäCityMap
on eräänlainenGrid
, joten sillä on ruudukkojen yleismetodit. - Jos käytät jakolaskua, niin muista, että kokonaislukujen jakolasku heittää desimaalit pois.
moveResidents
-metodi:
- Käytä kahta muuta toteuttamaasi metodia apuna.
- Viinaharava-tehtävän esittelemästä
Random.shuffle
-metodista on taas apua, ja ehkäRandom
-luokasta (luku 3.5) muutenkin.
Lisäapua metodin toteutukseen
Ranka yhdelle ratkaisutavalle:
- Muodosta puskuri, jossa on kaikki vapaiden asuntojen sijainnit.
- Muodosta kokoelma, jossa on kaikki tyytymättömien asukkaiden sijainnit sattumanvaraisessa järjestyksessä.
- Toista jokaiselle tyytymättömälle:
Poimi uusi osoite sattumanvaraisesti
vapaiden asuntojen joukosta. Siirrä
asukas
CityMap
issä uuteen osoitteeseen. Korvaa vapaiden osoitteiden puskurista kohdeosoite lähtöosoitteella.
Tutki ilmiötä simulaattorilla
Kokeile simulaattoria oletusasetuksilla. Askella ensin Single Step-nappulalla. Kokeile myös Run-nappia. Asukkaiden tyytyväisyyskynnys on 70 % eli he ovat varsin helposti tyytymättömiä nykyiseen osoitteeseensa.
Kokeile isommilla ja pienemmillä kynnysarvoilla.
Selvältä tuntuu, että jos asukkaat haluavat paljon itsensä kaltaisia naapureita, he päätyvät asumaan oman kansanosansa pariin. Vähemmän itsestäänselvää on, kuinka pieni oman ryhmän kaipuu yksilötasolla riittää aiheuttamaan kollektiivisesti ryhmäytymisen. Tutki.
Voisiko samankaltaisella mallilla selittää myös sosiaalisen median "kuplia"?
Mitä kaikkea tämä malli ei huomioi?
Mitä tapahtuisi, jos esimerkiksi yksi kansanosa välittää naapurustosta kuten ohjelmassamme, mutta toinen on aina tyytyväinen tai vaikkapa satunnaisesti 99 % ajasta tyytyväinen? Tai mitä jos asukkaat eivät ainoastaan laittaisi alarajaa naapuruston samankaltaisuudelle vaan myös ylärajan?
Palauttaminen
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Jos kiinnostuit
Ilmaisena versionakin tarjolla oleva kirja Networks, Crowds, and Markets: Reasoning About a Highly Connected World kertoo lisää mm. sosiaalisten, taloudellisten ja lääketieteellisten ilmiöiden mallintamisesta. Tässä tehtävässä käsiteltyä Schellingin mallia esitellään kirjan neljännessä luvussa.
Huutokaupat uusiksi yliluokilla
Loput tästä luvusta muodostaa ohjelmointitehtävä, joka on jatkoa aiemmille kauppapaikka-aiheisille tehtäville. Itse tehtävä on vapaaehtoinen, mutta suosittelemme että vähintäänkin tutustut siihen, mihin tehtävässä pyritään.
Luvussa 4.4 kehitit luokan FixedPriceSale
, ehkä myös luokat DutchAuction
ja
EnglishAuction
. Nämä luokat kuvaavat eri tavoin myyntiin laitettuja esineitä. Luvun 5.3
esimerkissä puolestaan laadimme luokan AuctionHouse
, joka edusti sellaisia kauppapaikkoja,
joissa kaikki esineet ovat huudettavissa perinteiseen EnglishAuction
-tyyliin.
Jos teit mainitut aiemmat tehtävät, voit käyttää omia ratkaisujasi pohjana seuraavalle tehtävälle. Jos et, voit käyttää esimerkkiratkaisuja (FixedPriceSale, DutchAuction, EnglishAuction).
Uudistus luokkarakenteeseen
Aiemmissa tehtävissä esiintyneet luokat suhtautuvat toisiinsa suurin piirtein näin:
Toisin sanoen: AuctionHouse
ssa on englantilaistyyppisiä huutokauppoja.
Luokat FixedPriceSale
ja DutchAuction
ovat täysin irrallisia.
Tässä tehtävässä refaktoroit aiemmin laadittua koodia. Refaktoroinnin
tavoite on parempi laatu: muokkaat FixedPriceSale
-, EnglishAuction
- ja
DutchAuction
-luokkia siten, että niitä kaikkia voi käyttää yhdessä. Samalla vähennät
turhaa toistoa koodissa. Refaktoroinnin työkaluna käytämme tässä tapauksessa periytymistä
(luku 7.3).
Tarkoitus olisi rakentaa tällainen versio:
Keskeisin uudistus on, että abstrakti luokka ItemForSale
on yliluokkana eri tavoin
myytäville esineille. Kun näin on, voimme käyttää yläkäsitettä ItemForSale
, kun
toteutamme luvun 5.3 versiota yleishyödyllisemmän AuctionHouse
-luokan. Lisäksi
InstantPurchase
-luokka kokoaa yhteen vakiohintaisten esineiden ja
hollantilaishuutokauppojen yhteisiä ominaisuuksia.
Toteuta uudistus
Toteuta luokat ItemForSale
, EnglishAuction
, InstantPurchase
, FixedPriceSale
,
DutchAuction
ja AuctionHouse
sellaisiksi, että ne vastaavat projektiin
AuctionHouse2 liitettyä Scaladoc-dokumentaatiota.
Ohjeita ja vinkkejä
Luokat kannattanee toteuttaa yllä luetellussa järjestyksessä.
Katso dokumentaatiosta tarkasti, mitkä luokat ja metodit ovat abstrakteja sekä mitkä metodit periytyvät kullekin luokalle sen yläkäsitteiltä.
Kuten luvun 7.3 tehtävissä, älä nytkään toista
val
-sanaa aliluokan konstruktoriparametreissa, jos kyseinen muuttuja on jo yliluokassa määritelty. Älä siis käytä esimerkiksidescription
-muuttujan kohdallaval
-sanaa aliluokkien alussa vaan vain yliluokassaItemForSale
. Aliluokissakin voi kyllä olla tuon niminen konstruktoriparametri.Tässäkin kauppapaikka-aiheisessa tehtävässä voit käyttää annettua käyttöliittymää kokeillaksesi tärkeimpien metodien toimivuutta. Käynnistysolion
o1.auctionhouse.gui.TestApp
ohjelmakoodista tosin tulee aluksi virheilmoituksia, mutta ne kaikkoavat, kunhan saat oman toteutuksesi käynnistyskuntoon.Ainoa
AuctionHouse
-luokaan tarvittava muutos on, että korvaatEnglishAuction
in yleisemmällä käsitteellä.
Palauttaminen
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Tehtävän tehtyäsi voit ihalla, miten periytyminen muutti vanhat irralliset ja toisteiset koodit kauniiksi käsitteelliseksi malliksi, jossa kustakin käsitteestä (luokasta) on määritelty vain juuri ne muutamat asiat, jotka erottavat sen sen sukulaiskäsitteistä.
Palaute
Huomaathan, että tämä on henkilökohtainen osio! Vaikka olisit tehnyt lukuun liittyvät tehtävät parin kanssa, täytä palautelomake itse.
Tekijät
Tämän oppimateriaalin kehitystyössä on käytetty apuna tuhansilta opiskelijoilta kerättyä palautetta. Kiitos!
Kierrokset 1–13 ja niihin liittyvät tehtävät ja viikkokoosteet on laatinut Juha Sorva.
Kierrokset 14–20 on laatinut Otto Seppälä. Ne eivät ole julki syksyllä, mutta julkaistaan ennen kuin määräajat lähestyvät.
Liitesivut (sanasto, Scala-kooste, usein kysytyt kysymykset jne.) on kirjoittanut Juha Sorva sikäli kuin sivulla ei ole toisin mainittu.
Tehtävien automaattisen arvioinnin ovat toteuttaneet Riku Autio, Jaakko Kantojärvi, Teemu Lehtinen, Timi Seppälä, Teemu Sirkiä ja Aleksi Vartiainen.
Lukujen alkuja koristavat kuvat ja muut vastaavat kuvituskuvat on piirtänyt Christina Lassheikki.
Yksityiskohtaiset animaatiot Scala-ohjelmien suorituksen vaiheista ovat suunnitelleet Juha Sorva ja Teemu Sirkiä. Niiden teknisen toteutuksen ovat tehneet Teemu Sirkiä ja Riku Autio käyttäen Teemun toteuttamia Jsvee- ja Kelmu-työkaluja.
Muut diagrammit ja materiaaliin upotetut vuorovaikutteiset esitykset on laatinut Juha Sorva.
O1Library-ohjelmakirjaston ovat kehittäneet Aleksi Lukkarinen ja Juha Sorva. Useat sen keskeisistä osista tukeutuvat Aleksin SMCL-kirjastoon.
Opetustapa, jossa käytämme O1Libraryn työkaluja (kuten Pic
) yksinkertaiseen graafiseen
ohjelmointiin on saanut vaikutteita tekijöiden Flatt, Felleisen, Findler ja Krishnamurthi
oppikirjasta How to Design Programs sekä Stephen Blochin oppikirjasta Picturing Programs.
Oppimisalusta A+ on luotu Aallon LeTech-tutkimusryhmässä pitkälti opiskelijavoimin. Pääkehittäjänä toimii tällä hetkellä Jaakko Kantojärvi, jonka lisäksi järjestelmää kehittävät useat tietotekniikan ja informaatioverkostojen opiskelijat.
Kurssin tämänhetkinen henkilökunta on kerrottu luvussa 1.1.
GameBoard
-olio tarvitsee kolme konstruktoriparametria: laudan leveyden ruutuina, laudan korkeuden ruutuina ja viinalasien lukumäärän.