Tämä kurssi on jo päättynyt.

Luku 2.6: Monenlaista käyttöä muuttujille

Tästä sivusta:

Pääkysymyksiä: Muuttujilla näköjään tehdään kaikenlaista? Miten laadin useasta oliosta koostuvan mallin kuvaamaan esimerkiksi graafisen pelin tilanteita? Miten viittaan laatimastani luokasta toiseen?

Mitä käsitellään? Muuttujia. Muuttujien rooleja eli käyttötapoja: vakiot ja muut kiintoarvot, tilapäissäilöt, kokoojat, tuoreimman säilyttäjät. Viittaukset ilmentymämuuttujissa; luokat ilmentymämuuttujien tyyppeinä. Scalaan liittyviä yksityiskohtia: tyhjät parametriluettelot, package-määrittelyt.

Mitä tehdään? Ensin luetaan, sitten aloitetaan erään pelin ohjelmointi.

Suuntaa antava työläysarvio:? Pari tuntia. Alussa on helppo välipala. Lopun asiatkaan eivät ole erityisen haastavia esitietojen ollessa kunnossa.

Pistearvo: A55.

Oheismoduulit: Oliointro, FlappyBug (uusi).

../_images/person06.png

Muuttujien luokittelutapoja

Luku 1.4 kertoi: tietokoneohjelman muuttuja on nimetty varastointipaikka yhdelle arvolle. Tuo pätee kaikille muuttujille, jotka olet tähän mennessä kohdannut, mutta kohdatut muuttujat eroavat toisistaan monella muulla tavalla. On hyvä pysähtyä hetkeksi setvimään tilannetta.

Muuttujia voi ryhmitellä usealla eri perusteella.

Muutettavuuden mukaan

Scala-koodissa ilmeisimmällä tavalla näkyvä muuttujien luokittelu on jako var- ja val-muuttujiin, jota käsiteltiin luvussa 1.4. Sen voi esittää kuvallisesti vaikkapa näin:

../_images/variables_valvar-fi.png

Tätä jakoa ei ole tässä muodossa läheskään kaikissa muissa ohjelmointikielissä.

Tietotyypin mukaan

Toinen melko ilmeinen tapa on jakaa muuttujat tietotyypin mukaan:

../_images/variables_type-fi.png

Käyttöyhteyden mukaan

Kolmas jaottelu voidaan tehdä sen mukaan, mihin yhteyteen muuttuja on määritelty.

Osa muuttujista on ilmentymämuuttujia olioiden yhteydessä (luku 2.4). Osa on paikallisia muuttujia, jotka ovat väliaikaisesti olemassa kutsupinon kehyksissä tietyn aliohjelmakutsun ajan. Erikoistapauksen paikallisista muuttujista muodostavat parametrimuuttujat, joihin ei sijoiteta arvoa sijoituskäskyllä vaan jotka saavat arvonsa aliohjelmakutsun parametrilausekkeet evaluoimalla (luku 1.7). Myös REPLissä määrittelemiemme muuttujien voi sanoa olevan eräänlaisia paikallisia muuttujia, joiden elinkaari on REPL-käyttökerran mittainen.

../_images/variables_context-fi.png

Muuttujien roolit

../_images/variables_role-fi.png

Muuttujia voi ryhmitellä myös niiden käyttötavan eli roolin mukaan. Vaikka tästä ei vielä ole nimenomaisesti puhetta ollutkaan, niin olet jo nähnyt muuttujien käyttöä muutamassa eri roolissa. Perehdytään asiaan tarkemmin:

Tilioliollamme (luku 2.2) oli pari muuttujaa, joita ei käytetty keskenään samaan tapaan. Tilin numero-muuttujan arvo pysyi aina samana. Toisaalta tilin saldo-muuttujan arvo vaihtui talletusten ja nostojen yhteydessä: uusi saldo määrittyi vanhasta saldosta ja muutoksen koosta. Näillä muuttujilla oli siis eri roolit.

Muuttujan rooli (role) on kuvaus siitä, miten tiettyä muuttujaa käytetään ohjelmassa: millä perusteella sen arvoa vaihdetaan, jos sitä yleensäkään vaihdetaan? Aiheesta tehtyjen tutkimusten perusteella on todettu, että valtaosaa tietokoneohjelmissa esiintyvistä muuttujista kuvaa osuvasti yksi noin kymmenestä roolinimikkeestä. Kahdeksan roolinimeä riittää luonnehtimaan lähes kaikkia tämän kurssin esimerkkiohjelmien muuttujia. Voimme esimerkiksi sanoa, että tilioliomme saldo on rooliltaan "kokooja": sen kulloinenkin arvo on saatu kokoamalla tehtyjen toimenpiteiden yhteisvaikutus, siis talletusten ja nostojen summa.

Muuttujien rooleja käytetään jatkossa tässä kurssimateriaalissa selkiyttämään ohjelmien suunnittelua. Monissa kurssin esimerkkimoduuleissa on rooleja myös merkitty ohjelmakoodin kommentteihin alla näkyvään tapaan.

var saldo = 0.0           // kokooja

On tärkeää huomata, ettei muuttujan rooli ole määritelmä siitä, mitä muuttujalla voisi periaatteessa tehdä. Esimerkiksi saldo-muuttujaanhan voisi teknisesti ottaen sijoittaa aivan mitä vain lukuarvoja, jos ohjelmoija niin määrää. Rooli on kuvaus siitä, mitä jollakin muuttujalla todella tehdään tietyssä ohjelmassa. Roolilla on merkitystä ihmiselle, ei tietokoneelle.

Käydään seuraavaksi läpi ne muutama rooli, joista kurssimateriaalissa on jo ollut kunnollisia esimerkkejä.

Kiintoarvot

../_images/fixed_value.png

Kiintoarvo on kiveen hakattu.

Yksinkertaisin muuttujan rooli on kiintoarvo (fixed value). Sen jälkeen, kun kiintoarvoisen muuttujan arvo on kerran alustettu, sitä ei enää vaihdeta lainkaan. Kiintoarvot ovat Scalassa lähes poikkeuksetta val-muuttujia.

Ilmentymämuuttuja, joka on kiintoarvo, kuvaa jotakin olion pysyvää ominaisuutta (esim. tilinumero).

Tyypillisiä esimerkkejä paikallisista kiintoarvomuuttujista ovat parametrimuuttujat. Vaikkapa Tyontekija-luokan kuukausikulut-metodin kulukerroin-parametrimuuttuja on kiintoarvo, joka on koko metodikutsun ajan muuttumaton:

def kuukausikulut(kulukerroin: Double) = this.kkpalkka * this.tyoaika * kulukerroin

Vakiot

Sellaisia kiintoarvoisia muuttujia, joiden arvo on tiedossa jo ennen ohjelman ajoa, sanotaan usein vakioiksi (constant). Vakioksi voidaan kirjata jokin yleismaailmallinen totuus kuten piin likiarvo tai jokin tiettyyn sovellukseen liittyvä muuttumaton ja etukäteen tunnettu asia.

val Pi = 3.141592653589793
val MinimumAge = 18
val DefaultGreeting = "Hello!"
Vakioiden nimet on Scalassa tapana kirjoittaa isolla alkukirjaimella (kuten kurssin tyyliopaskin kertoo).
Vakion ei tarvitse olla luku. Siihen voi tallentaa muunkintyyppistä muuttumatonta tietoa.

Vakioiden avulla voi usein kätevästi parantaa ohjelmien luettavuutta. Vakion nimi koodissa kertoo "maagista arvoa" eli ohjelman toimintaan dokumentoimattomalla tavalla liittyvää literaalia paremmin, mitä koodi tekee.

Muokattavuuskin voi parantua. Jos vakioarvo halutaan myöhemmin vaihtaa toiseksi, kun ohjelmasta laaditaan uusittu versio, niin tämä onnistuu yhdestä paikasta muuttamalla vakion määrittelyä, vaikka vakiota olisikin käytetty eri puolilla ohjelmaa (tai eri ohjelmissa). Maagisten arvojen käyttö useassa paikassa aiheuttaa ohjelmakoodin osien välille implisiittisiä riippuvuuksia (implicit coupling): kun yhtä kohtaa muuttaa, pitäisi aina muistaa muuttaa toisiakin sieltä täältä. Implisiittiset riippuvuudet aiheuttavat koodiin helposti bugeja.

Vakioita löytyy myös ohjelmakirjastoista. Esimerkiksi pakkauksessa scala.math on määritelty kiintoarvoinen muuttuja nimeltä Pi, johon on tallennettu piin likiarvo. Pakkauksen o1 tarjoamat värit (Red, Blue, CornflowerBlue jne.) ovat vakioita, joista kukin viittaa tiettyyn Color-tyyppiseen olioon.

Tilapäissäilöt

../_images/temporary.png

"Hetkeksi korvan taakse."

Lukujen 1.8 ja 2.2 tehtävissä olet itsekin toteuttanut tilapäissäilöjä (temporary; ohjelmoijien suussa usein "temppi" tms.). Tilapäissäilö on muuttuja, johon otetaan väliaikaisesti talteen jokin arvo, jota tarvitaan algoritmin myöhemmissä vaiheissa. Esimerkiksi tiliolion nostometodissa täytyi ottaa nostettava summa tilapäisesti talteen saldon laskemisen ajaksi, jonka jälkeen se palautettiin.

def nosta(summa: Int) = {
  val nostettu = min(summa, this.saldo) // nostettu on tilapäissäilö
  this.saldo = this.saldo - nostettu
  nostettu
}

Tilapäissäilöön voi myös esimerkiksi ottaa välituloksen talteen aritmeettisessa laskennassa. Tyypillisimmin tilapäissäilöt ovat paikallisia muuttujia.

Tilapäissäilön arvo ei yleensä vaihdu, kun se on kerran asetettu; Scala-ohjelmien tilapäissäilöt ovat melkein aina val-muuttujia. Joissakin tapauksissa on makuasia, kutsuuko muuttujaa kiintoarvoksi vai tilapäissäilöksi.

Kokoojat

Kun kokoojalle (gatherer) sijoitetaan uusi arvo, se määritetään yhdistämällä jollakin tapaa kokoojan vanha arvo ja jokin uusi data (esim. käyttäjän antama syöte tai muu parametriarvo).

Esimerkkejä ilmentymämuuttujista, jotka ovat kokoojia:

  • Tilin saldo (selitetty yllä). Käsky this.saldo = this.saldo - nostettu on kokoojille tyypillinen: siinä uusi saldo lasketaan vanhan saldon ja nostetun määrän perusteella.
  • Saldoa vastaavasti toimivat hirviön kuntopisteet (luku 2.4).
  • Pelihahmon sijainti pelissä, jossa uusi sijainti määräytyy aina vanhan sijainnin ja annetun kulkusuunnan perusteella. Nykysijainti siis riippuu kaikista hahmolle aiemmin annetuista liikkumiskomennoista.
    • Koodi voi näyttää esimerkiksi tältä: this.sijainti = this.sijainti.naapuri(kulkusuunta). (Tässä hahmo-olio kysyy itseensä liitetyltä paikkaa kuvaavalta oliolta, mikä sen naapuri tietyssä kulkusuunnassa on, ja asettaa uudeksi sijainniksi saamansa vastauksen.)
    • Jotain samantapaista on luvassa myös alempana tässä luvussa.

Voidaan ajatella, että kokooja ikään kuin "ottaa sisään juttuja" ja sen arvo riippuu kaikista "aiemmin käsitellyistä jutuista".

Kokoojat ovat myös paikallisina muuttujina yleisiä. Ensimmäisen esimerkin tästä näet luvussa 5.5.

Tuoreimman säilyttäjät

Tyontekija-olion nimeksi voi sijoittaa uuden arvon:

val testi = new Tyontekija("Leena Herppeenluoma", 1957, 7000)testi: o1.luokkia.Tyontekija = o1.luokkia.Tyontekija@1100b25
testi.nimires0: String = Leena Herppeenluoma
testi.nimi = "Leena Hefner"testi.nimi: String = Leena Hefner

Muuttujaa nimi käytetään pitämään kirjaa viimeisimmästä työntekijäoliolle asetetusta nimestä. Toisin kuin kokoojan tapauksessa, aiempi nimi ei ole osatekijänä, kun uusi nimi muodostetaan, vaan uusi annettu arvo yksinkertaisesti korvaa aiemman. Muuttuja kuten nimi, joka pitää kirjaa viimeisimmästä tietynlaisesta arvosta, on rooliltaan tuoreimman säilyttäjä (most-recent holder).

Äskeinen esimerkki on tyypillinen tapaus: ilmentymämuuttujaa käytetään tuoreimman säilyttäjänä kuvaamassa sellaista olion ominaisuutta, jonka arvoa voi vaihtaa. Tuoreimman säilyttäjille löytyy käyttöä myös paikallisina muuttujina, mistä näet esimerkkejä mm. luvussa 5.5.

../_images/most-recent_holder-fi.png

Vanhat arvot eivät jää talteen tuoreimman säilyttäjään.

Roolien merkityksestä

Roolit kuvaavat tyypillisiä asioita, joita muuttujilla ohjelmissa tehdään. Ne tarjoavat työkalupakin, jota voi käyttää apuna ohjelmoidessa ja ohjelmia lukiessa. Jos tiedät tietyn muuttujan roolin, tiedät myös jotain siitä, miten ohjelma toimii.

Kukin rooli kuvaa abstraktin ratkaisun sellaiseen tarpeeseen, joka toistuu kerta toisensa jälkeen mitä erilaisimpien ohjelmointiongelmien osana. Käyttämämme roolinimikkeet on valittu vastaamaan sellaisia yhtäläisyyksiä muuttujien käytössä, joita kokeneet ohjelmoijat näkevät toisistaan muuten riippumattomienkin ohjelmien välillä.

Sinun opiskelijana ei ole pakko itse käyttää roolinimiä. Kuitenkin roolien käytöstä voi olla sinullekin apua hahmotellessasi ratkaisuja ohjelmointiongelmiin; moni aloitteleva ohjelmoija on niistä hyötynyt. Yksi ohjelmoinnin oppimisen haasteista on oppia tunnistamaan yleisiä ohjelmointiongelmissa esiintyviä tarpeita ja tilanteita sekä niihin sopivia ratkaisuja. Roolit voivat osaltaan auttaa tässä. Ne voivat antaa aloittelevalle ohjelmoijalle vinkkiä siitä, mihin muuttujia voi käyttää, ja siitä, mitä kannattaa tehdä, kun halutaan saavuttaa tietynlainen tavoite.

Voit hyödyntää rooleja ajattelun tukena:

"Hmm... Pitäisi laskea syötettyjen mittaustulosten summa... Summastahan voisi pitää kirjaa kokoojamuuttujassa, jonka arvoa päivitetään aina kun käsitellään uusi mittaustulos: lasketaan vanhan summan ja uuden mittaustuloksen summa."

Kun suunnittelet ohjelmia, mieti sekä tarvitsemiesi muuttujien tietotyypit että roolit. Kun luet ohjelmia, kiinnitä huomiota muuttujien käyttötapoihin. Kun dokumentoit ohjelmia, roolien merkitseminen koodiin saattaa auttaa lukijaa.

Lisää rooleja

Pian luvussa 3.1 kohtaat uuden roolin, askeltajan. Kurssilla puhutaan myöhemmin myös säiliöistä (luku 4.2), sopivimman säilyttäjistä (luku 4.2) ja lipuista (luku 5.6).

Toisiinsa viittaavia olioita

Aiemmista esimerkeistä on käynyt ilmi, että olio-ohjelman toiminta perustuu olioiden väliseen viestintään. Viestinnän pohjana ovat olioiden välille muodostetut yhteydet: esimerkiksi kurssioliosta voi olla viittaus kurssin opetuspaikkaa kuvaavaan saliolioon sekä ilmoittautuneita opiskelijoita kuvaaviin olioihin, ja GoodStuff-moduuliin Category-olioon voi liittyä useita Experience-olioita. Pelihahmoa kuvaavan olion tietoihin voi kuulua viittaus sijaintiolioon.

Aiemmissa luvuissa olet nähnyt, miten tietynlaisten olioiden ominaisuudet kuvataan luokkana. Kuitenkaan vastaan ei ole vielä tullut sellaista koodiesimerkkiä, jossa yhdestä itse laaditusta luokasta viitattaisiin toiseen. Nyt tulee.

Käsitellään ensin irrallista esimerkkiä, jonka kaksi luokkaa kuvaavat — erittäin yksinkertaistetusti! — tilauksia ja asiakkaita kuvitteellisessa verkkokauppaohjelmassa. Sen jälkeen pääset soveltamaan esimerkin oppeja, kun laadit oliomallin peliä varten.

Tavoite: luokat asiakkaille ja tilauksille

Asiakasoliota luodessa annetaan konstruktoriparametreiksi nimi, asiakasnumero sekä sähköposti- ja katupostiosoitteet:

val testihenkilo = new Asiakas("T. Testaaja", 12345, "test@test.fi", "Testitie 1, 00100 Testaamo")testihenkilo: o1.luokkia.Asiakas = o1.luokkia.Asiakas@a7de1d

Asiakasoliolta voi pyytää tekstimuotoisen kuvauksen parametrittomalla kuvaus-metodilla:

println(testihenkilo.kuvaus)#12345 T. Testaaja <test@test.fi>

Tilausoliolle annetaan konstruktoriparametreiksi tilausnumero ja tilaaja. Jälkimmäinen näistä on viittaus asiakasolioon:

val testitilaus = new Tilaus(10001, testihenkilo)testitilaus: o1.luokkia.Tilaus = o1.luokkia.Tilaus@18c6974

Tilaukseen voi lisätä tuotteita lisaaTuote-metodilla. Tässä esimerkissä yksittäisistä tuotteista ei varsinaisesti tallenneta tietoja, vaan ainoastaan ilmoitetaan tuotteen kappalehinta ja montako kappaletta tilataan.

Tässä tilataan 100 kpl 10,50 euron tuotteita ja yksi 500 euron tuote:

testitilaus.lisaaTuote(10.5, 100)testitilaus.lisaaTuote(500.0, 1)

Myös tilausolioilla on kuvaus-niminen metodi:

println(testitilaus.kuvaus)tilaus 10001, tilaaja: #12345 T. Testaaja <test@test.fi>, yhteensä 1550.0 euroa

Kuten yltä näkyy, tilaajan kuvaus muodostaa osan tilausta kuvaavasta merkkijonosta.

Tilausoliolta voi myös kysyä, mikä asiakasolio on tilaajana, jolloin saadaan viittaus juuri siihen asiakasolioon, joka tilaajaksi aiemmin asetettiin:

testitilaus.tilaajares1: Asiakas = o1.luokkia.Asiakas@a7de1d

Huomaa, että saimme nimenomaan Asiakas-tyyppisen viittauksen, emme merkkijonoa. Pääsimme tilausolion kautta käsiksi tilaukseen liittyvään toiseen olioon, ja voimme käyttää tuota oliota tavalliseen tapaan, vaikkapa näin ketjuttaen:

testitilaus.tilaaja.osoiteres2: String = Testitie 1, 00100 Testaamo

Tässä siis testitilaus-muuttuja sisältää viittauksen tilausolioon, tuon tilausolion tilaaja-muuttuja viittauksen asiakasolioon, ja asiakasolion osoite-ilmentymämuuttuja viittauksen merkkijonoon.

Luokkien toteutus

Tässä ensin asiakasluokka. Siinä ei ole mitään uudenlaista:

class Asiakas(val nimi: String, val asiakasnumero: Int, val email: String, val osoite: String) {

  def kuvaus = "#" + this.asiakasnumero + " " + this.nimi + " <" + this.email + ">"

}
Kullakin asiakasoliolla on muutama kiintoarvoinen ilmentymämuuttuja, jossa se pitää kirjaa muuttumattomista tiedoistaan.

Hahmotellaan tilauksia kuvaava luokka ensin pseudokoodina (luku 2.5):

class Tilaus(kiintoarvoisia tietoja: tilausnumero ja tilauksen tehnyt asiakas) {

  kokoojamuuttuja pitäköön kirjaa kokonaishinnasta, joka on aluksi nolla

  def lisaaTuote(kappalehinta: Double, lukumaara: Int) = {
    lisää kokonaishinnan kokoojaan parametrien mukainen summa
  }

  def kuvaus = palauta merkkijonokuvaus tilauksen tiedoista; pyydä
               siihen sisältyvät asiakastiedot kyseiseltä asiakasoliolta
}
Tilausolion tulee kerätä yhteen kaikkien tuotelisäysten kokonaisvaikutus hintaan. Siihen sopii kokoojana käytetty ilmentymämuuttuja.
Useasta luokasta koostuvan mallimme kannalta keskeistä on, että tilausolion tiedoista on viitattava asiakasolioon.
Viittauksen avulla voimme sitten esimerkiksi konsultoida asiakasoliota muodostaessamme tilauksen kuvausta.

Pseudokoodin kuvaama ajatus on helppo esittää myös varsinaisena ohjelmakoodina, sillä itse laadittuakin luokkaa kuten Asiakas voi mainiosti käyttää toisen luokan määrittelyssä:

class Tilaus(val numero: Int, val tilaaja: Asiakas) {

  var kokonaishinta = 0.0

  def lisaaTuote(kappalehinta: Double, lukumaara: Int) = {
    this.kokonaishinta = this.kokonaishinta + kappalehinta * lukumaara
  }

  def kuvaus = "tilaus " + this.numero + ", " +
               "tilaaja: " + this.tilaaja.kuvaus + ", " +
               "yhteensä " + this.kokonaishinta + " euroa"

}
Tässä Asiakas-luokka on Tilaus-luokan konstruktoriparametrin tyyppinä, joten tilausoliota luotaessa on jälkimmäiseksi parametriksi annettava viittaus asiakasolioon. val-sanalla ilmoitetaan, että kyseinen viittaus halutaan talteen ilmentymämuuttujaan.
Lausekkeen this.tilaaja arvo on viittaus asiakasolioon, jonka metodia voi kutsua kirjoittamalla this.tilaaja.kuvaus. Tässä siis tilausolio pyytää asiakasoliota kertomaan kuvauksensa ja käyttää sitten vastauksena saamaansa merkkijonoa osana omaa kuvaustaan.

Näin saimme määriteltyä suhteen tilausluokan ja asiakasluokan välille ja sitä kautta myös suhteen kustakin tilausoliosta yhteen asiakasolioon. Olioiden välisiä suhteita on kuvattu myös seuraavassa diagrammissa.

Lisätehtävä: toString-metodeita

Vapaaehtoisena harjoituksena voit muokata annettuja Asiakas- ja Tilaus-luokkia siten, että niillä on kuvaus-metodin sijaan toString-metodi (luku 2.5). Luokat löytyvät Oliointro-moduulista.

Kunhan toteutat asiakkaiden toStringin, niin Tilaus-luokan toString-metodissa sinun ei ole pakko nimenomaisesti kutsua sitä. Riittää yhdistää toisiinsa merkkijono ja viittaus asiakasolioon.

Jos haluat, voit myös muokata luokkien toString-metodit käyttämään merkkijonoupotuksia (s ja dollarit) plussien sijaan.

A+ esittää tässä kohdassa tehtävän palautuslomakkeen.

Entä suhteet yhdestä moneen olioon?

Entä jos haluamme viitata yhdestä kurssista useaan ilmoittautujaan tai yhdestä kategoriasta useaan kokemukseen? Tai vaikka kirjata kullekin asiakkaalle luettelo hänen tilauksistaan?

Noiden kysymysten vastaukseen pääsemme kunnolla käsiksi luvussa 4.2. Lyhyesti sanottuna se on: kytketään olioon kokoelma, jonka alkioina ovat nuo ilmoittautujat, kokemukset tai tilaukset.

Lisää esimerkkejä

Esimerkki: aarteiden vartijoita

Luvussa 2.4 hahmottelimme luokkaa Hirvio. Tässä siitä toimiva versio (jossa kuvaus-metodi on korvattu toString-metodilla):

class Hirvio(val tyyppi: String, val taysiKunto: Int) {
  var nykykunto = taysiKunto

  override def toString = this.tyyppi + " (" + this.nykykunto + "/" + this.taysiKunto + ")"

  def vahingoitu(paljonko: Int) = {
    this.nykykunto = this.nykykunto - paljonko
  }
}

Vaikka emme tätä esimerkkiä oikeaksi peliksi työstäkään, jatketaan kuitenkin tätä teemaa yhdellä pikkuluokalla.

Sanotaan, että kuvitteellisessa pelissämme on eriarvoisia aarteita, joita kutakin vartioi peikko — jokaiseen Aarre-olioon liittyy sen vartijana toimiva Hirvio-olio. Tässä ensin käyttöesimerkki:

val kulta = new Aarre(1000.0, 50)kulta: Aarre = aarre (arvo 1000.0), jota vartioi peikko (50/50)
kulta.arvores3: Double = 1000.0
kulta.vartijares4: Hirvio = peikko (50/50)
kulta.houkuttelevuusres5: Double = 20.0
Kullakin aarteella on sen arvoa kuvaava desimaaliluku. Aarteella on myös haastetaso, luku sekin.
Aarretta vartioi sitä voimakkaampi peikko, mitä isompi haastetaso aarreoliolle annetaan.
Aarteelta voi tiedustella sen arvoa ja vartijaa.
Aarretta kuvaa myös sen houkuttelevuusluku, joka saadaan jakamalla aarteen arvo sen vartijan kyseisenhetkisillä kuntopisteillä. Houkuttelevuus siis kasvaa aarteen vartijan heikentyessä. Tässä esimerkissä aarretta vartioi täysivoimainen 50-pistepeikko, joten houkuttelevuus on 1000.0/50 eli 20.0.

Alla on toteutus Aarre-luokalle.

class Aarre(val arvo: Double, val haaste: Int) {

  def vartija = new Hirvio("peikko", this.haaste)

  def houkuttelevuus = this.arvo / this.vartija.nykykunto

  override def toString = "aarre (arvo " + this.arvo + "), jota vartioi " + this.vartija
}
Houkuttelevuus määritellään jakolaskulla vartijan nykykunnon perusteella.

Annettu Aarre-luokan toteutus toimii siinä määrin, että se tuottaa äskeisen REPL-esimerkin mukaisen tulosteen. Kaikilta osin se ei kuitenkaan toimi halutusti.

Alla on käskysarja, jossa käytetään Aarre-luokkaa. Se ei tee haluttua; miksei?

val aarre = new Aarre(500.0, 100)
println(aarre.houkuttelevuus)   // tulostaa 5.0 kuten pitääkin
aarre.vartija.vahingoitu(50)
aarre.vartija.vahingoitu(40)
println(aarre.houkuttelevuus)   // ei tulosta 50.0 kuten pitäisi

Viimeisen rivin siis haluttaisiin tulostavan 50.0, koska vartijalla on vain 10 alkuperäisestä sadasta kuntopisteestään jäljellä, jolloin aarteen houkuttelevuus on 500.0/10 eli 50.0. Mikä seuraavista kuvaa ongelman oikein?

Toimiiko tämä hieman erilainen käskysarja?

val aarre = new Aarre(500.0, 100)
println(aarre.houkuttelevuus)
val hirvio = aarre.vartija
hirvio.vahingoitu(50)
hirvio.vahingoitu(40)
println(aarre.houkuttelevuus)
Ainoa ero edelliseen on, että tässä käytämme lisämuuttujaa hirvio, joka viittaa aarteen vartijaan ja jonka kautta kutsumme vahingoitu-metodia.

Mikä seuraavista pitää paikkansa?

Aarre-luokka ei siis ole kunnossa. Ongelma liittyy ilmaisuun def vartija, joka määrittelee aarteen vartijan palauttavan metodin. Entä jos se olisi val vartija eli muuttuja metodin sijaan?

Esimerkki: pomojen pomoja

Tilausesimerkissä tuli esille, että kahden luokan välille voi määritellä "linkin" käyttämällä ilmentymämuuttujaa, jonka tyyppinä on toinen luokka. Entä luokan linkittäminen itseensä?

class Employee(val boss: Employee) {

  // ... muita luokan osia

}
Tuossa koodissa on käytetty muuttujan tyyppinä luokkaa itseään. Arvioi tai arvaa, onko siinä mitään järkeä.

Esimerkki: taivaankappaleita

Tämä on valinnainen lisäesimerkki yhden luokan käytöstä toisen määrittelyssä ja olioiden liittämisestä näin toisiinsa. Voit käydä esimerkin läpi, jos Tilaus- ja Aarre-esimerkit jäivät epäselviksi tai jos ajaudut hankaluuksiin seuraavana alla tulevan pelihankkeen kanssa. Uutta asiaa tässä esimerkissä ei ole.

Luokat Taivaankappale ja Avaruus

Laaditaan kokeeksi pari luokkaa, joilla mallinnamme Maan ja Kuun sijaintia avaruudessa kaksiulotteisessa koordinaatistossa. Taivaankappale-luokan ilmentymät vastaavat yksittäisiä taivaankappaleita. Avaruus-luokan ilmentymä puolestaan on osio avaruudesta, jossa Maa ja Kuu sijaitsevat: kuhunkin Avaruus-olioon liittyy pari Taivaankappale-oliota. (Vertaa: kuhunkin Tilaus-olioon liittyy yksi Asiakas; kuhunkin Aarre-olioon liittyy yksi Hirvio.)

Tässä ensin yksinkertainen luokka Taivaankappale.

class Taivaankappale(val nimi: String, val sade: Double, var sijainti: Pos) {

  def halkaisija = this.sade * 2

  override def toString = this.nimi

}
Taivaankappaleilla on nimet, säteet ja sijainnit. Sijainti on Pos-olio: kunkin taivaankappaleolion osaksi tallennetaan viittaus Pos-olioon, joka pitää sisällään x- ja y-koordinaatin.

Tässä Taivaankappale-luokkaa käyttävä Avaruus-luokka:

class Avaruus(koko: Int) {
  val leveys = koko * 2
  val korkeus = koko

  val maa = new Taivaankappale("Maa", 15.9, new Pos(10,  this.korkeus / 2))
  val kuu = new Taivaankappale("Kuu", 4.3,  new Pos(971, this.korkeus / 2))

  override def toString = s"${this.leveys}x${this.korkeus} alue avaruudessa"
}
Avaruus-oliota luotaessa ilmaistaan konstrutoriparametrilla sen koko (koordinaattiyksiköissä). Sovitaan tässä pikkuesimerkissä, että tällainen avaruuden kaistale on aina kaksi kertaa niin leveä kuin korkea. Leveys ja korkeus tallennetaan Avaruus-olion ilmentymämuuttujiin.
Avaruutta mallintava olio koostuu leveyden ja korkeuden lisäksi kahdesta Taivaankappale-oliosta. Leluesimerkissämme niillä on juuri tietyt nimet, säteet ja sijainnit, jotka kirjaamme koodiin suoraan.

Noin määriteltyjä luokkia voi käyttää REPLissä näin:

val avaruus = new Avaruus(500)avaruus: Avaruus = 1000x500 alue avaruudessa
avaruus.maares6: Taivaankappale = Maa
avaruus.kuures7: Taivaankappale = Kuu
Loimme Avaruus-olion, jolloin...
... samalla tuli luoduksi kaksi Taivaankappale-oliota, joihin Avaruus-olion ilmentymämuuttujat maa ja kuu viittaavat.

Voimme käyttää näiden olioiden muuttujia ja metodeita:

avaruus.maa.halkaisijares8: Double = 31.8
avaruus.maa.sijaintires9: o1.Pos = (10.0,250.0)
avaruus.kuu.sijaintires10: o1.Pos = (971.0,250.0)
avaruus.kuu.saderes11: Double = 4.3
avaruus.kuu.sijainti.xDiff(avaruus.maa.sijainti)res12: Double = -961.0
Avaruutta kuvaavaan olioon on kytketty kaksi Taivaankappale-oliota, joihin pääsemme käsiksi muuttujien kautta.
Taivaankappale-olioihin on puolestaan kytketty Pos-oliot, joihin pääsee käsiksi taivaankappaleiden sijainti-muuttujista.

Tämä valinnainen esimerkki saa jatkoa seuraavassa luvussa 2.7, jossa piirrämme tuollaista mallia vastaavan näkymän avaruudesta.

Peliprojekti

Aloittakaamme uusi hanke, jota kehität vähitellen useassa luvussa samalla kun perehdyt syvemmin GoodStuff-kestoprojektiin ja teet muita erillisharjoituksia. Tuloksena syntyy konkreettinen peliohjelma.

FlappyBug

Tehdään ohjelma, jossa pelaaja ohjaa leppäkerttua eli "ötökkää" ja väistelee esteitä.

Ötökkä liikahtaa ylös käyttäjän komentaessa sitä iskemään siivillään. Toisaalta ötökkä vajoaa koko ajan alaspäin, joten käyttäjän on pidettävä se ilmassa komennoillaan. Ötökkä liikkuu vain pystysuoraan; esteitä lentää oikealta vaakasuoraan.

../_images/flappybug_sketch-fi.png

Suunnitelmamme.

Tässä luvussa aloitamme laatimaan mallin ohjelman sisuskaluista, emme käyttöliittymää. Tässä tapauksessa malli tarkoittaa pelin sisältöön liittyviä käsitteitä (esim. este) ja niihin liittyviä toimintoja (esim. lentäminen).

Aloitamme yksinkertaistetusta pelistä, jossa on yksi ötökkä ja vain yksi este. Tulevissa luvuissa paitsi kehitämme nyt laadittavaa mallia monipuolisemmaksi myös laadimme pelille käyttöliittymän.

Laaditaan seuraavat luokat:

  • Bug: kuvaa otökän käsitettä, sen ominaisuuksia ja toimintoja.
  • Obstacle: kuvaa esteitä vastaavasti.
  • Game: Tämän luokan ilmentymä vastaa yhtä pelikertaa ja pitää kirjaa sen etenemisestä. Game-olion kautta pääsee käsiksi pelin osiin (ötökkään ja esteeseen), ja se määrää, miten noiden osien toimintoja pelin edetessä aktivoidaan.

Aloitetaan luokasta Obstacle, jolle syntyy toteutus esimerkissä alla. Sitten pääset itse laatimaan Bugin ja Gamen harjoitustehtävissä.

Luokka Obstacle

Obstacle-oliota luodessa määritetään sen koko (säde) sekä lähtösijainti pelialueen kattavassa kaksiulotteisessa koordinaatiossa. Esimerkiksi näin:

val bigObstacle = new Obstacle(150, new Pos(800, 200))bigObstacle: o1.flappy.Obstacle = center at (800.0,200.0), radius 150

Yksinkertaisessa peliversiossamme este ei juuri tee mitään, mutta lentää se osaa:

bigObstacle.approach()bigObstacleres13: o1.flappy.Obstacle = center at (790.0,200.0), radius 150
bigObstacle.approach()bigObstacle.approach()bigObstacleres14: o1.flappy.Obstacle = center at (770.0,200.0), radius 150
Obstacle-oliolla on muuttuva tila, johon approach-metodin kutsuminen vaikuttaa. Aina, kun metodia kutsutaan, esteen x-koordinaatti vähenee kymmenellä.

Pseudokoodi toteutuksesta:

class Obstacle(kiintoarvoinen muuttuja säteelle, kokooja sijainnille) {

  def approach() = {
    Sijoita sijainnista kirjaa pitävälle kokoojalleni uusi arvo.
    Uusi arvo saadaan vanhasta lisäämällä x-koordinaattiin -10.
  }

  override def toString = yhdistä esteen tiedot esimerkin mukaiseksi merkkijonoksi

}

Scalaksi:

class Obstacle(val radius: Int, var pos: Pos) {

  def approach() = {
    this.pos = this.pos.addX(-10)
  }

  override def toString = "center at " + this.pos + ", radius " + this.radius

}

Ennen kuin etenemme luokkien Bug ja Game pariin, kiinnitä huomio kahteen asiaan:

Metodin toteutukseen on kirjattu maaginen luku 10, joka säätelee esteiden etenemisvauhtia.
Parametrittoman approach-metodin määrittelyssä on tyhjät kaarisulkeet. Miksi? Ehkä ihmettelit samaa jo ylempänä, missä approach-metodia kutsuttaessa käytettiin vastaavasti tyhjiä sulkeita.

Maaginen luku vakioksi

Määritellään maagisen luvun syrjäyttäjäksi vakio:

val ObstacleSpeed = 10

Tehdään nyt niin, että määrittelemme tällaisia FlappyBug-pelin sääntöihin liittyviä vakioita varten erillisen paikan.

Oheismoduulista FlappyBug löydät osittaisen toteutuksen pelille, jossa tuo vakio on määritelty tiedostoon constants.scala. Esteluokka on puolestaan tiedostossa Obstacle.scala, ja sen approach-metodi hyödyntää vakiota:

class Obstacle(val radius: Int, var pos: Pos) {

  def approach() = {
    this.pos = this.pos.addX(-ObstacleSpeed)
  }

  override def toString = "center at " + this.pos + ", radius " + this.radius
}
Käytämme vakion nimeä maagisen luvun sijaan.

Välipuhe parametrittomista vaikutuksellisista metodeista — ja sulkeista

Äskeisessä koodissa esiintyneet tyhjät sulkeet liittyvät siihen, että kyseessä on parametriton metodi: parametriluettelo on tyhjä. Toisaalta olemme laatineet sellaisia parametrittomia metodeita, joihin ei ole tyhjiä sulkeita kirjoitettu. Tällaisiahan ovat äskeinen toString ja kuvaus ylempänä ja moni muu aiempien esimerkkien metodi.

Kyseessä on Scala-kielen käytäntö, jolla vaikutukselliset ja vaikutuksettomat parametrittomat metodit erotetaan toisistaan. Vaikutuksellisiin parametrittomiin metodeihin (kuten approach) kirjoitetaan tyhjät kaarisulkeet parametrittomuuden merkiksi. Toisaalta vaikutuksettomista parametrittomista metodeista (kuten toString tai kuvaus) nämä sulkeet jätetään pois. Tämä siis lisäyksenä funktioiden määrittelemiseen liittyviin välimerkkisääntöihin luvusta 1.7.

Tyhjiä kaarisulkeita käytetään tai ollaan käyttämättä vastaavasti myös metodeita kutsuttaessa. Sulkeet metodikutsussa bigObstacle.approach() korostavat ohjelmakoodin lukijalle, että kyseessä on metodikutsu, joka vaikuttaa esteolion tilaan.

Toisaalta on niin, että lausekkeesta testihenkilo.kuvaus ei näy, onko kyseessä kuvaus-nimisen metodin kutsu vai kuvaus-nimisen muuttujan arvon tutkiminen. Scala-kielen kehittäjät ovat nimenomaan halunneet, että vaikutuksettoman parametrittoman metodin kutsu näyttää ohjelmakoodissa täsmälleen samalta kuin ilmentymämuuttujan arvon katsominen (joka ei sekään muuta ohjelman tilaa). Tälle halulle on syynsä, joista tässä vaiheessa ymmärrettävin on se, että luokan käyttäminen on näin hieman helpompaa: luokan käyttäjän ei tarvitse muistella, onko kyseessä ilmentymämuuttuja vai metodi.

Noudata näitä suljesääntöjä

Äsken esiteltyjä sulkeidenkäyttösääntöjä tulee noudattaa. Erityisesti: kun teet tehtävää, jossa tehtävänannossa on tyhjät sulkeet metodin nimen perässä, niin laita sulkeet myös laatimaasi toteuttavaan ohjelmakoodiin ja käytä sulkeita tuota metodia kutsuessasi.

FlappyBug-tehtävä (osa 1/17: Bug-luokka)

Ötökän toivottu toimintatapa

Näin luodaan ötökkäolio:

val myBug = new Bug(new Pos(300, 200))myBug: o1.flappy.Bug = center at (300.0,200.0), radius 15

Kuten toStringin ylle tuottamasta kuvauksestakin voi päätellä, ötököillä on esteiden tapaan sijainti ja säde. Niitä voi tutkia myös erikseen:

myBug.posres15: o1.Pos = (300.0,200.0)
myBug.radiusres16: Int = 15
Ötökän sijainti on aluksi juuri se, mikä konstruktoriparametriksi annettiin.
Ainakin tässä versiossa ohjelmastamme millä tahansa ötökällä on aina sama muuttumaton säde, 15.

Ötökällä on metodi, jolla se "iskee siipiään". Mallinnamme lentämistä nyt yksinkertaisesti muuttamalla ötökän y-koordinaattia parametrin verran pienemmäksi:

myBug.flap(9.5)myBug.posres17: o1.Pos = (300.0,190.5)
myBug.flap(20.5)myBug.posres18: o1.Pos = (300.0,170.0)

Ötökän toiminnallisuuteen kuuluu myös alaspäin putoaminen. Metodi fall kasvattaa y-koordinaattia kahdella.

myBug.fall()myBug.posres19: o1.Pos = (300.0,172.0)
myBug.fall()myBug.posres20: o1.Pos = (300.0,174.0)

Tehtävänanto

Toteuta Bug-luokka tiedostoon Bug.scala moduulissa FlappyBug. Sen täytyy toimia yllä kuvattuun tapaan.

Ohjeita ja vinkkejä

  • Huomaa, että sijainnin pitää olla saatavilla juuri nimellä pos. Muidenkin luokan käyttäjälle merkityksellisten nimien pitää olla juuri pyydetyt (radius, fall, flap). Sama pätee myös muihin harjoitustehtäviin, joissa pyydetään toteuttamaan luokka tai olio juuri spesifikaation mukaiseksi.
    • Sijainti vaihtuu ja olkoon siis var. Säde ei ja olkoon val. fall ja flap ovat metodeita eli def.
  • Pyydetty luokka muistuttaa monin tavoin edellä laadittua Obstacle-luokkaa.
  • Huomaa, että metodi fall on vaikutuksellinen ja parametriton. Sovella äsken opetettua sulutussääntöä.
  • Voit käyttää toteutuksessasi tiedoston constants.scala vakioita maagisten lukujen sijaan.
  • Muista testatessasi avata REPLiin juuri FlappyBug-moduuli.

A+ esittää tässä kohdassa tehtävän palautuslomakkeen.

FlappyBug-tehtävä (osa 2/17: Game-luokka)

Yksi Game-tyyppinen olio kokoaa yhteen tietyn pelitilanteen, tässä versiossamme siis yhden ötökän ja yhden esteen. Game-olio myös tarjoaa toiminnot tuon pelitilanteen muuttamiseen. Se esimerkiksi määrittää, mitä tapahtuu, kun aikaa kuluu: tällöin pelin ötökkä putoaa ja este liikkuu. Apuna Game-olio käyttää viittauksia toisiin olioihin, joita se komentaa.

Peliolion toivottu toimintatapa

Uuden Game-olion luominen käy yksinkertaisesti ilman konstruktoriparametreja:

val testGame = new GametestGame: o1.flappy.Game = o1.flappy.Game@10eb1721

Näin luotu olio kuvaa pelin alkutilannetta, joka on aina samanlainen: pelissä on ötökkä sijainnissa (100,40) ja este, jonka säde on 70, sijainnissa (1000,100).

Pelioliolla on val-ilmentymämuuttujat nimeltä bug ja obstacle, jotka viittaavat ötökkään ja esteeseen:

testGame.bugres21: o1.flappy.Bug = center at (100.0,40.0), radius 15
testGame.obstacleres22: o1.flappy.Obstacle = center at (1000.0,100.0), radius 70

Parametrittomalla metodilla timePasses pelitilanne etenee ötökkää pudottamalla ja estettä liikuttamalla:

testGame.timePasses()testGame.bugres23: o1.flappy.Bug = center at (100.0,42.0), radius 15
testGame.obstacleres24: o1.flappy.Obstacle = center at (990.0,100.0), radius 70
Pelin ötökän ja esteen koordinaatit muuttuivat.

Lisäksi Game-oliolla on parametriton metodi activateBug, jolla on tarkoitus reagoida pelaajan komentoihin. Kun pelin activateBug-metodia kutsutaan, se ohjeistaa pelin (ainoan) ötökän käyttämään siipiään voimakkuudella 15:

testGame.activateBug()testGame.bugres25: o1.flappy.Bug = center at (100.0,27.0), radius 15
Y-sijainti on pienennyt 15 yksikön verran.

Tehtävänanto

Toteuta kuvattu Game-luokka tiedostoon Game.scala.

Ohjeita ja vinkkejä

  • Game-luokka ei tarvitse konstruktoriparametreja lainkaan. Luokan rungon voi kirjoittaa aaltosulkeisiin heti class Game -alustuksen perään. Näin onkin tehty annetussa pohjakoodissa.
  • Sinun täytyy siis määritellä kaksi ilmentymämuuttujaa (nimiksi bug ja obstacle) sekä kaksi metodia (nimiksi activateBug ja timePasses).
    • Muista, että vaikka luokat nimetäänkin isolla kuten Bug, noilla muuttujilla tulisi olla pienellä alkavat nimet kuten bug.
  • Laita ilmentymämuuttujat viittaamaan Bug- ja Obstacle-olioihin. Luo nuo oliot Game-luokan koodissa.
    • Älä siis kopioi koodia Bug- tai Obstacle-luokan sisältöä Game-luokkaan. Vaan käytä noita luokkia Game-luokan koodissa: luo niistä kummastakin yksi olio new-sanalla.
    • Voit käyttää new-käskyä ilmentymuuttujiakin määritellessäsi tuttuun tyyliin: val muuttuja = new Luokka(parametrit)
    • Jos tämän kanssa on hankaluuksia, voi apua olla valinnaisesta Avaruus-esimerkistä yllä.
  • Kun toteutat metodit activateBug ja timePasses, älä suinkaan lähde toteuttamaan ötököille ja esteille aiemmin määriteltyjä toimintoja (esim. laskemaan koordinaateilla). Sen sijaan: kutsu Bug-olion ja Obstacle-olion metodeita!
  • Käytä taas tyhjiä kaarisulkeita parametrittomissa mutta vaikutuksellisissa metodeissa.
  • Voit käyttää tiedoston constants.scala vakioita. Voit myös halutessasi määritellä sinne lisää vakioita ja käyttää niitä.
  • Ei haittaa, jos et vielä täysin hahmota, miten tätä luokkaa nyt sitten voi hyödyntää graafisen pelin osana. Pelistämme puuttuu vielä käyttöliittymä, mutta laadimme sen pian.

A+ esittää tässä kohdassa tehtävän palautuslomakkeen.

package-määrittelyistä

Monen jo muokkaamasi kooditiedoston alussa on rivi, jossa lukee package-alkuinen merkintä kuten tämä:

package o1.flappy

Merkintä on varsin itseselitteinen: package-sanan avulla kirjataan kunkin Scala-kooditiedoston alkuun, mihin pakkaukseen kyseisen tiedoston sisältö kuuluu.

Ohjelman osat, jotka on näin määritelty osaksi samaa pakkausta, voivat käyttää toisiaan ilman erillistä import-käskyä tai pakkauksen nimen mainitsemista. Esimerkiksi äsken et tarvinnut Game-luokan määrittelyssä import-käskyä, koska Game, Bug ja Obstacle ovat kaikki samassa pakkauksessa.

package-määrittelyt ovat tarpeellisia mutta rutiininomaisia. Niitä ei useimpiin tämän kurssimateriaalin koodinpätkiin ole kirjoitettu mukaan. (Oheismoduulien koodissa ne toki ovat.)

Toistaiseksi olemme sysänneet lähes kaiken koodin yleispakkaukseen o1, mutta kurssin edetessä jaottelemme koodimme huolellisemmin.

Yhteenvetoa

  • Muuttujia voi ryhmitellä erilaisilla tavoilla: var vai val? Mikä tietotyyppi? Paikallinen vai ilmentymämuuttuja? Mikä käyttötapa eli rooli?
  • Noin kymmenellä roolinimikkeellä voidaan osuvasti kuvata valtaosaa muuttujista, joita ohjelmissa käytetään.
    • Kiintoarvoja käytetään mm. olioiden pysyvien ominaisuuksien kuvaamiseen, kokoojia muodostamaan tuloksia usean arvon perusteella, tilapäissäilöjä välitulosten tallentamiseen hetkeksi ja tuoreimman säilyttäjiä mm. sellaisten ominaisuuksien kuvaamiseen, joiden arvon voi vaihtaa toiseksi.
  • Vakioiksi sanotaan kiintoarvoja, joiden arvo tunnetaan jo ennen ohjelma-ajoa.
    • Vakioilla voi helposti parantaa koodin luettavuutta ja muokattavuutta.
    • Niiden nimet on tapana kirjoittaa Scalassa isolla alkukirjaimella.
  • Voit käyttää itse laatimiasi luokkia myös ilmentymämuuttujien tyyppeinä.
    • Näin voit luoda yhteyksiä luokkien välille.
    • Tällainen ilmentymämuuttuja tarkoittaa, että kunkin tietyntyyppisen olion (esim. tilauksen) tietoihin lukeutuu viittaus toiseen olioon (esim. asiakkaaseen).
  • Scalassa pitää olla tarkkana kaarisulkeiden kanssa parametrittomissa metodeissa.
  • Lukuun liittyviä termejä sanastosivulla: muuttuja; muuttujan rooli, kiintoarvo, tilapäissäilö, kokooja, tuoreimman säilyttäjä; vakio, maaginen luku t. arvo; viittaus; malli.

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!

Materiaalin luvut tehtävineen ja viikkokoosteineen on laatinut Juha Sorva.

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: (aakkosjärjestyksessä) Riku Autio, Nikolas Drosdek, Joonatan Honkamaa, Jaakko Kantojärvi, Niklas Kröger, Teemu Lehtinen, Strasdosky Otewa, 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 suunnittelivat Juha Sorva ja Teemu Sirkiä. Teemu Sirkiä ja Riku Autio toteuttivat ne apunaan Teemun aiemmin rakentamat työkalut Jsvee- ja Kelmu.

Muut diagrammit ja materiaaliin upotetut vuorovaikutteiset esitykset laati Juha Sorva.

O1Library-ohjelmakirjaston ovat kehittäneet Aleksi Lukkarinen ja Juha Sorva. Useat sen keskeisistä osista tukeutuvat Aleksin SMCL-kirjastoon.

Tapa, jolla 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+ luotiin alun perin Aallon LeTech-tutkimusryhmässä pitkälti opiskelijavoimin. Nykyään tätä avoimen lähdekoodin projektia kehittää Tietotekniikan laitoksen opetusteknologiatiimi ja tarjoaa palveluna laitoksen IT-tuki. Pääkehittäjänä on tällä hetkellä Markku Riekkinen, jonka lisäksi A+:aa ovat kehittäneet kymmenet Aallon opiskelijat ja muut.

A+ Courses -lisäosa, joka tukee A+:aa ja O1-kurssia IntelliJ-ohjelmointiympäristössä, on toinen avoin projekti. Sen ovat luoneet Nikolai Denissov, Olli Kiljunen, Nikolas Drosdek, Styliani Tsovou, Jaakko Närhi ja Paweł Stróżański yhteistyössä Juha Sorvan, Otto Seppälän, Arto Hellaksen ja muiden kanssa.

Kurssin tämänhetkinen henkilökunta löytyy luvusta 1.1.

Lisäkiitokset tähän lukuun

FlappyBug on saanut vaikutteita Dong Nguyenin pelistä.

a drop of ink
Palautusta lähetetään...