Luku 2.6: Monenlaista käyttöä muuttujille

../_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

Kolmannen jaottelun voi 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.

Käytämme tässä kurssimateriaalissa muuttujien rooleja selkiyttämään ohjelmien suunnittelua. Monissa kurssin esimerkkimoduuleissa on rooleja 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. tiliolion 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.

Vakioilla 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 "temppimuuttuja" 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 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 = 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 vastaavat 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ä, mutta rooleista 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 kertovat aloittelijalle, mihin muuttujia voi käyttää, ja vinkkaavat, 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.

Rooleista ja suunnittelumalleista

Roolit ovat eräs tapa nimetä ohjelmissa usein esiintyviä ratkaisumalleja. Ne liittyvät yksittäisiin muuttujiin eli hyvin pieniin ohjelman osiin. Myös suuremmille ratkaisumalleille on kehitetty nimiä ja kuvauksia. Tunnetuin esimerkki tästä ovat suunnittelumallit (design patterns), jotka kuvaavat ohjelmakokonaisuuden suunnittelussa usein toistuvia tilanteita ja niille sopivia, yleensä yhden tai useamman kokonaisen luokan suuruisia ratkaisuja. Suunnittelumalleja käsitellään mm. kurssilla CS-C2120 Ohjelmointistudio 2.

Toisin kuin jotkin suunnittelumallit, roolinimikkeet eivät kuulu useimpien ammattiohjelmoijien sanastoon, mutta saattaa roolinimistä olla hyötyä heillekin esimerkiksi koodin dokumentoinnissa. Ehkä seuraava ohjelmoijasukupolvi tuntee ne vähän paremmin?

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 luontiparametreiksi nimi, asiakasnumero sekä sähköposti- ja katupostiosoitteet:

val testihenkilo = 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 luontiparametreiksi tilausnumero ja tilaaja. Jälkimmäinen näistä on viittaus asiakasolioon:

val testitilaus = 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

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 sisältää viittauksen asiakasolioon, ja asiakasolion osoite-ilmentymämuuttuja sisältää 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:

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
end Tilaus

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"
end Tilaus

Tässä Asiakas-luokka on Tilaus-luokan luontiparametrin 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

end Hirvio

Vaikka emme tätä esimerkkiä oikeaksi peliksi työstäkään, jatketaan 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 = 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 = Hirvio("peikko", this.haaste)

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

  override def toString = "aarre (arvo " + this.arvo + "), jota vartioi " + this.vartija

end Aarre

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 = 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 = 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

end Employee

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

end Taivaankappale

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 = Taivaankappale("Maa", 15.9, Pos(10,  this.korkeus / 2))
  val kuu = Taivaankappale("Kuu", 4.3,  Pos(971, this.korkeus / 2))

  override def toString = s"${this.leveys}x${this.korkeus} alue avaruudessa"

end Avaruus

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 = 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 mallia 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 = Obstacle(150, 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

end Obstacle

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

end Obstacle

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

end Obstacle

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.

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ä, kirjoita 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 = Bug(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ä luontiparametriksi 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.

  • Jotta ötökkä toimii kuten REPL-esimerkissä yllä, on luokkaan laadittava toString-metodi.

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

val testGame = Game()testGame: o1.flappy.Game = o1.flappy.Game@10eb1721

Kun luomme olion luokasta, jolle ei anneta luontiparametreja, kirjoitamme perään vain tyhjät sulkeet.

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 luontiparametreja lainkaan. Luokan rungon avaavan kaksoispiteen voi kirjoittaa 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.

  • Aseta 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.

    • Voit käyttää olionluontikäskyä ilmentymuuttujia määritellessäsi: val muuttuja = 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.

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, Kaisa Ek, Joonatan Honkamaa, Antti Immonen, Jaakko Kantojärvi, Onni Komulainen, Niklas Kröger, Kalle Laitinen, Teemu Lehtinen, Mikael Lenander, Ilona Ma, Jaakko Nakaza, Strasdosky Otewa, Timi Seppälä, Teemu Sirkiä, Joel Toppinen, Anna Valldeoriola Cardó 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, Juha Sorva ja Jaakko Nakaza. 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; sitä 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 suunnitteluun ja toteutukseen on osallistunut useita opiskelijoita yhteistyössä O1-kurssin opettajien 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...