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

Kurssin viimeisimmän version löydät täältä: O1: 2024

Luku 2.7: Käynnistyksiä ja käyttöliittymiä

Tästä sivusta:

Pääkysymyksiä: Miten kirjoitan ja tallennan kokonaisen Scala-ohjelman? Mistä Scala-sovelluksen suorittaminen alkaa? Miten luen näppäimistösyötettä sovelluksen käyttäjältä? Nyt kun olen laatinut käsitteellisen mallin pelimaailmasta, miten määrittelen graafisen käyttöliittymän, joka tekee tämän maailman näkyväksi?

Mitä käsitellään? Sovelluksen käynnistysolio (App). Näppäimistösyöte (readLine). Graafisten käyttöliittymien alkeita (o1.View).

Mitä tehdään? Enimmäkseen ohjelmoidaan ohjeiden mukaisesti.

Suuntaa antava työläysarvio:? Pari tuntia.

Pistearvo: A75.

Oheismoduulit: Ave (joka luodaan itse), Odds, Pikkusovelluksia (uusi), FlappyBug.

../_images/person06.png

Johdanto

Olet luonut olioita ja kutsunut metodeita. Tämä on tapahtunut lähinnä REPLissä, joka sopiikin kokeilukäyttöön hyvin. Kuitenkaan REPLiin kirjoitetuista käskyistä ei muodostu kokonaisuutta, joka jäisi talteen, jonka voisi kätevästi myöhemmin ajaa uudestaan, jota voisi muokata tai jonka voisi kopioida jonkun toisen käytettäväksi. Näitä tarkoituksia varten on ohjelma tallennettava tiedostoon.

Kokeillaan.

Perinteikäs ohjelma

../_images/ave_munde.png

Jo muinaiset roomalaiset aloittivat kunkin uuden ohjelmointikielen tai -työkalun opettelun käyttämällä sitä ns. "Hello, World" -ohjelman tekemiseen. Tällainen ohjelma yksinkertaisesti tervehtii käyttäjää.

Laaditaan ja tallennetaan nyt ohjelma, jossa on pari Scala-kielistä käskyä ja jonka voi määrätä suoritettavaksi haluttaessa. Käytetään vaikkapa näitä tulostuskäskyjä:

println("Ave, Munde!")
println("No avepa ave sinnekin!")

Luo tätä kokeilua varten IntelliJ’ssä uusi moduuli:

  1. File → New → Module.
  2. Esiin tulleessa pikkuikkunassa huolehdi, että valittuna on Scala. Paina Next.
  3. Kirjoita moduulille nimeksi Ave. Älä vielä paina Finish, vaan...
  4. ... avaa alareunasta More Settings ja tyhjennä Create source root -tekstikenttä, jossa lukee "src". Jätä kuitenkin viereinen rasti ruutuun.
    • (Tämä vaihe ei ole muuten tärkeä, mutta näin tekemällä saat moduulin, jonka rakenne on samanlainen kuin muiden O1-moduulien ja jonka A+ Courses osaa palauttaa arvioitavaksi. Vaiheen ohittaminen luo src-nimisen alikansion ja sijoittaa Scala-koodit sinne.)
  5. Paina Finish. Ave-moduuli näkyy nyt IntelliJ’n Project-näkymässä.

Luo uusi tyhjä kooditiedosto:

  1. Klikkaa Ave-moduulia oikealla napilla. Valitse New → File.
  2. IntelliJ pyytää tiedostolle nimeä. Kirjoita Ave.scala
  3. Tiedosto näkyy nyt moduulin sisällä ja avautuu myös IntelliJ’n editoriin. Se on tyhjä.

Ensimmäinen yritys kirjoittaa tervehdysohjelma:

  1. Kirjoita yllä olevat tulostuskäskyt luomaasi tyhjään tiedostoon. Käännä moduuli (F10).
  2. Havaitse: käännös epäonnistuu. Messages-välilehti kertoo: Error: expected class or object definition. Mistä on kyse?

Virheilmoituksen taustalla on se, että emme ole jäsentäneet sovellustamme kelvolliseksi olio-ohjelmaksi. Kun laadimme Scala-sovellusta, on määriteltävä olio, joka toimii suorituksen "lähtöpisteenä". Tuo olio voi sitten aktivoida (kutsua) muita ohjelman osia.

Esimerkiksi GoodStuff-ohjelmassa oli käynnistysoliona GoodStuff-niminen yksittäisolio, josta käsin käynnistit sovelluksen ainakin luvussa 1.2. Käynnistysolio löytyy myös Pong-ohjelmasta nimellä PongApp. Tervehdyssovellukseltamme tällainen lähtöpiste kuitenkin vielä puuttuu.

Käynnistysolio

Kirjoitetaan käynnistysolio. Tässä sille aihio:

object IkiomaSovellukseni extends App {
  // Tämä on käynnistysolio.
  // Tänne käskyt, jotka suoritetaan, kun sovellus ajetaan.
}
Scalan sana extends voidaan tässä lukea: "on eräänlainen".
Määritellään, että tämä yksittäisolio kuvaa erään sovelluksen (app eli application). Kun olio määritellään näin, se saa kaikki ne ominaisuudet, jotka Scalan valmiille App-tietotyypille on määritelty. Olio edustaa nyt sovellusta kokonaisuutena ja tarjoaa mahdollisuuden käynnistää sovellus.
Käynnistysoliolle ei tarvitse kirjoittaa nimettyjä metodeita lainkaan: sovellukseen kuuluvia käskyjä voi kirjoittaa yksinkertaisesti käynnistysolion määrittelyn sisään (tässä näkyvien kommenttien paikalle). Käynnistysolion sisään kirjoitetut käskyt suoritetaan peräjälkeen, kun sovellus käynnistetään.

Tässä vaiheessa kurssia voit siis ajatella näin: extends App tarkoittaa, että olio toimii sovelluksen käynnistysoliona. Myöhemmin (esim. luvussa 7.2) osoittautuu, että extends-sana on muissakin yhteyksissä käyttökelpoinen.

Kokonainen pikkuruinen Scala-ohjelma

Suuremmassa ohjelmassa luokkia ja yksittäisolioita voi olla kymmeniä, satoja tai enemmän. Tervehtivään miniohjelmaamme tarvitsemme vain yhden olion, jonka sisään tulostuskäskyt kirjoitetaan ja joka myös toimii käynnistysoliona.

Muokkaa Ave.scala-tiedostoa niin, että sen sisältö on tällainen:

object Ave extends App {
  println("Ave, Munde!")
  println("No avepa ave sinnekin!")
}

Nyt ohjelman voi ajaa avaamalla Ave-tiedoston valikon Project-välilehdellä ja valitsemalla Run 'Ave'. Vielä helpompi tapa on napsauttaa object-sanan viereen koodin marginaaliin ilmestynyttä Play-ikonia. Kokeile.

(Juuri tuohon tapaan käynnistit GoodStuffin ja Pongin luvussa 1.2. Nämä ja vaihtoehdoiset tavat käynnistää Scala-sovellus esiteltiin siellä.)

Huomaa, että tulosteet tulevat IntelliJ’n Run-välilehdelle alas. Tällaista tulostukseen tarkoitettua aluetta sanotaan tekstikonsoli (text console) tai vain konsoliksi.

Olet nyt tehnyt kokonaisen Scala-sovelluksen.

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

Syötteen lukeminen

Äsken laadittu ohjelma tuottaa aina saman tulosteen, eikä ohjelman käyttäjällä ole mahdollisuutta vaikuttaa sen toimintaan ohjelma-ajon aikana tai muutenkaan. Useimmat käytännön sovellusohjelmat kuitenkin tarjoavat käyttäjilleen jonkinlaisen mahdollisuuden antaa ohjelmalle syötettä (input) joko suorasti tai epäsuorasti. Tässä joitakin esimerkkejä:

  1. Ohjelmassa on graafinen käyttöliittymä, jonka osia klikkailemalla käyttäjä voi kertoa ohjelmalle, mitä haluaa sen tekevän.
  2. Ohjelma osaa ladata kiintolevylle tallennettuja tiedostoja, joista se saa dataa käsiteltäväkseen.
  3. Tekstikonsolissa toimiva sovellus osaa pysähtyä odottamaan käyttäjän näppäimistöltä syöttämiä tekstirivejä.

Palataan graafisiin käyttöliittymiin kohta ja tiedostojen lataamiseen luvussa 11.3. Ensin tutkailemme kolmantena mainittua tapaa, jolla ohjelma ja käyttäjä voivat vuorovaikuttaa.

Sovellus tekstikonsolissa

Laaditaan ohjelma, joka toimii IntelliJ’n Run-välilehdellä seuraavasti:

Seis! Kuka siellä?
posteljooni Petskin
Ave, posteljooni Petskin!
Korostettu keskimmäinen rivi on käyttäjän antama syöte. Tietokone pysähtyy tässä kohtaa odottamaan syötettä ja jatkaa vasta käyttäjän Enter-painalluksen jälkeen.
Jälkimmäisessä tulosteessa käytetään juuri sitä nimeä, jonka käyttäjä syötti sillä ohjelman ajokerralla.

readLine-funktio

Funktio readLine vastaanottaa eli "lukee" yhden rivin käyttäjän antamaa syötettä. Ohjelman suoritus pysähtyy readLine-kutsuun, kunnes syöte on annettu. Funktio palauttaa käyttäjän antaman syötteen String-tyyppisenä arvona.

Sen avulla yllä kuvatun ohjelman voi toteuttaa seuraavasti.

import scala.io.StdIn._

object Tervehdysohjelma extends App {

  println("Seis! Kuka siellä?")
  val nimi = readLine()
  println("Ave, " + nimi + "!")

}
readLine-funktion käyttö sujuu kätevimmin, kun otat sen ensin käyttöön import-käskyllä. Nimi StdIn on lyhenne sanoista standard input, jotka viittavat tässä käytännössä tekstikonsolilta luettavaan syötteeseen.

Kirjoita itse tällainen ohjelma. Voit joko muokata jo palauttamaasi Ave-oliota tai tehdä uuden tiedoston.

Aja ohjelma. Katso, mitä konsoliin tulostuu, ja vastaa ohjelman kysymykseen. (Huom. Joudut näpäyttämään konsolia hiirellä ennen kuin saat syötettyä sinne merkkejä näppäimistöllä.)

Mainittakoon vielä, että readLine-funktiosta on myös kätevä variaatio, joka ottaa parametrikseen merkkijonokehotteen, tulostaa sen käyttäjän nähtäväksi ja lukee syötteen samalta riviltä, jolle kehotekin tulostettiin. Esimerkiksi seuraava ohjelma:

val nimi = readLine("Seis! Kuka siellä? ")
println("Ave, " + nimi + "!")

Tuottaa tällaisen tulosteen:

Seis! Kuka siellä? posteljooni Petskin
Ave, posteljooni Petskin!

Pikkuharjoitus

Tee ohjelma, joka kysyy käyttäjältä kaksi merkkijonoa ja ympäröi ensimmäisen toisella kuten näissä esimerkkiajoissa:

Anna ympäröitävät merkit: laama
Anna ympäröivät merkit: !?
!?laama!?
Anna ympäröitävät merkit: johto
Anna ympäröivät merkit: vesi
vesijohtovesi

Kirjoita koodisi Ymparoi-nimiseen käynnistysolioon. Määrittele käynnistysolio Ymparoi.scala-nimiseen tiedostoon. Luo tuo tiedosto aiemmin luomaasi Ave-moduuliin Ave.scalan rinnalle.

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

Odds-tehtävä (osa 5/9)

Kehitellään pieni testiohjelma lukujen 2.4 ja 2.5 Odds-moduuliin. Ohjelma luo käyttäjän antaman syötteen perusteella Odds-olioita ja raportoi niiden metodien palautusarvoja tulostein.

Tehtävänanto

Käytetään tässä tehtävässä kahta Odds-moduulin tiedostoa. Odds.scala on edeltä tuttu; se määrittelee Odds-luokan. Tutustu nyt myös tiedostoon OddsTest1.scala. Se sisältää käynnistysolion määrittelyn, joka ei kuitenkaan ole vielä ihan valmis, sillä se ei käytä Odds-luokkaa laisinkaan.

Täydennä käynnistysolio sellaiseksi, että se tuottaa täsmälleen seuraavan ajoesimerkin mukaisen tulosteen. (Syötteet voivat tietysti olla toisiakin kuin esimerkissä.)

Please enter the odds of an event as two integers on separate lines.
For instance, to enter the odds 5/1 (one in six chance of happening), write 5 and 1 on separate lines.
7
2
The odds you entered are:
In fractional format: 7/2
In decimal format: 4.5
Event probability: 0.2222222222222222
Reverse odds: 2/7
Odds of happening twice: 77/4
Please enter the size of a bet:
50.0
If successful, the bettor would claim 225.0
Please enter the odds of a second event as two integers on separate lines.
10
1
The odds of both events happening are: 97/2
The odds of one or both happening are: 70/29

Ohjeita ja vinkkejä

  • Voit kokeilla käynnistää OddsTest1-ohjelman jo annetussakin muodossa. Se kyllä lukee syötettä, muttei vielä tuota ihan oikeanlaista tulostetta.
  • Sinun täytyy lisätä Odds-olion luovat käskyt ja kutsua luotujen olion metodeita sopivissa kohdissa OddsTest1-ohjelman koodia.
    • Älä muokkaa Odds-luokkaa äläkä kopioi sitä tai osia siitä OddsTest1.scala-tiedostoon. Käytä Odds-luokkaa sellaisenaan.
    • Käskyjen järjestys on merkityksellinen. Valitse ajatuksella kohdat, joissa luot uudet Odds-oliot. Oliohan voidaan luoda vasta sitten, kun tarvittavat syötteet on luettu, mutta toisaalta olio on luotava ennen kuin sen metodeita voi kutsua.
  • OddsTest1 ja Odds ovat samassa pakkauksessa, joten voit luoda OddsTest1-ohjelmassa Odds-olioita helposti lisäämättä importia.
  • Huomaat, että annetussa koodissa on käytetty readInt- ja readDouble-nimisiä funktioita. Ne ovat samankaltaisia kuin yllä esitelty readLine, mutta tulkitsevat käyttäjältä saadut merkit lukuarvoina. Esimerkiksi readInt palauttaa Int-tyyppisen arvon eikä String-tyyppistä.
  • Kohdassa, jossa pitää laskea todennäköisyys sille, että tapahtuma tapahtuu kahdesti, voit käyttää both-metodia. (Esimerkki: kahden kuutosen heittäminen nopalla on sitä, että heitetään nopalla kuutonen ja heitetään nopalla toinenkin kuutonen.)

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

Lisäharjoitus: toisteisuutta vähemmäksi metodilla

OddsTest1:n koodissa toistuu sama rakenne kahdesti: luetaan kaksi lukua ja käytetään niitä Odds-olion konstruktoriparametreina. Vältä turha toisto toteuttamalla tämä ohjelma hieman toisella tavalla:

  • Laadi OddsTest1-yksittäisoliolle parametriton, vaikutuksellinen metodi requestOdds, joka lukee kaksi kokonaislukua, luo niiden perusteella Odds-olion ja palauttaa viittauksen luomaansa olioon.
  • Kutsu requestOddsia (kahdesti).

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

Sovellus graafisella käyttöliittymällä

Ohjelman aihepiiriä kuvaa ohjelmoijan laatima malli; käyttöliittymä esittää tuon mallin jossakin käyttäjän havaittavissa olevassa muodossa, yleensä kuvina tai tekstinä (luku 1.2). Käyttöliittymä voi myös tarjota käyttäjälle mahdollisuuden vuorovaikuttaa mallin kanssa ja muuttaa mallin tilaa.

Moneen sovellukseen sopii graafinen käyttöliittymä eli GUI (graphical user interface), joka rakentuu erilaisista visuaalisista osasista ja esitetään tyypillisesti omassa ikkunassaan.

Malliesimerkki

Edellisessä luvussa loimme mallin FlappyBug-pelille luokista Bug, Game ja Obstacle. Ennen kuin teemme graafisen käyttöliittymän tuolle mallille, aloitetaan hieman yksinkertaisemmasta.

Tässä mallissa on vain yksi luokka, Palikka:

class Palikka(val koko: Int, val sijainti: Pos, val vari: Color) {
  override def toString = this.vari.toString + " block at " + this.sijainti
}

Alla on käyttöesimerkki. Se toimii, jos REPLiin on ladattu äskeinen luokka, joka löytyy Pikkusovelluksia-moduulista.

val malli = new Palikka(20, new Pos(300, 50), Gray)malli: Palikka = Gray block at (300.0,50.0)

Luodaan graafinen käyttöliittymä, jolla voi esittää ikkunassa tällaisen palikan taustaa vasten.

Pohjustava kertaus

Muistatko, kun luvun 2.4 lopussa kirjoitimme luokan Henkilo ja loimme siitä ilmentymiä? Teräsmiestä kuvaavan ilmentymän loimme tällaisella käskyllä:

val terasmies = new Henkilo("Clark") {
  def lenna = "WOOSH!"
}

Tuo luomamme olio on henkilöluokan ilmentymä, minkä lisäksi määrittelimme sille lenna-metodin. Metodin määrittely noin yhdelle luokan ilmentymälle tulee kohta tarpeeseen.

Käyttöliittymäikkuna esiin: o1.View

Graafista käyttöliittymää ei joudu laatimaan joka sovellukseen "tyhjästä" pikseli kerrallaan, vaan apuna käytetään jotakin tarkoitukseen sopivaa ohjelmakirjastoa.

GUI-kirjastoja on monia. Eräs tunnetuista kirjastoista on nimeltään Swing; sitä käytämme luvussa 12.3 sijoittaaksemme ruudulle nappuloita, tekstikenttiä ja sensellaista. Nyt käytämme O1:n omaa GUI-kirjastoa, joka on erityisen näppärä geometrisia kuvioita hyödyntävien pienten sovellusten laatimiseen (ja jota voi yhdistellä Swingin kanssa).

Pakkaus o1 tarjoaa tyypin View, jolla nimensä mukaisesti saa esiin näkymän johonkin mallina toimivaan olioon. Käytetään mallina tätä Palikka-luokan ilmentymää:

val malli = new Palikka(20, new Pos(300, 50), Gray)malli: Palikka = Gray block at (300.0,50.0)

Luodaan View-olio. Se kuvaa sellaisen graafisen käyttöliittymän ikkunan, joka toimii näkymänä yhteen Palikka-tyyppiseen olioon.

val nakyma = new View(malli) {
  def makePic = {
    val tausta = rectangle(500, 500, Black)
    val palikanKuva = rectangle(malli.koko, malli.koko, malli.vari)
    tausta.place(palikanKuva, malli.sijainti)
  }
}
Kyseessä on View-luokan ilmentymä. Konstruktoriparametriksi annetaan viittaus malliin, joka näkymässä on tarkoitus esittää.
Luomamme olio kykenee kaikkeen, mihin View-oliot yleensäkin. Sen lisäksi sille on tässä määritelty vaikutukseton metodi makePic, joka muodostaa kuvan mallista. Kuva on tyyppiä Pic, ja voimme muodostaa sen tutuilla välineillä.
Näkymän kuva esittää mallia (tässä: palikkaa) ja muodostetaan mallin tietoja apuna käyttäen.

Tuo koodi ei vielä tuonut mitään graafista näkyviin, vaan sitä varten meidän on käynnistettävä käyttöliittymämme. Kaikilta View-olioilta löytyy vaikutuksellinen metodi, joka tekee juuri sen. Seuraava käsky tuo esiin alkeellisen käyttöliittymämme. Kokeile.

nakyma.start()

Kukin View-olio osaa piirtää itsensä näkyviin käyttäen makePic-metodinsa palauttamaa kuvaa. Saamme esiin ikkunan, jossa on palikan kuva mustalla taustalla. Ikkunan kulmassa on myös sen pienentämiseen ja sulkemiseen sopivat nappulat; tuon vähäisen vuorovaikutusmahdollisuuden View tarjoaa ilman lisämäärittelyjäkin.

Sovellus graafisella näkymällä

Yhdistetään luvussa esitellyt asiat: tehdään käynnistysolio graafiselle sovellukselle.

object PalikkaApp extends App {

  val tausta = rectangle(500, 500, Black)

  val palikka = new Palikka(20, new Pos(300, 50), Gray)

  val nakyma = new View(palikka, "Palikkaohjelma") {
    def makePic = {
      val palikanKuva = rectangle(palikka.koko, palikka.koko, palikka.vari)
      tausta.place(palikanKuva, palikka.sijainti)
    }
  }

  nakyma.start()

}
Käynnistysolion sisällä...
... luomme mallin, jonka haluamme esittää käyttäjälle, ja näkymän, joka esittää tuon mallin eräällä tavalla.
Pieni lisäys: voimme määritellä sovelluksellemme otsikon antamalla lisäparametrin luotavalle View-oliolle. Tämä teksti tulee näkyviin ikkunan otsikkoriville.
Lopuksi muistamme kutsua start-metodia, joka käynnistää käyttöliittymämme. Käynnistysolion koodi on tämän jälkeen suoritettu mutta sovelluksemme pyörii edelleen erillisessä ikkunassaan, kunnes käyttäjä toisin haluaa.

Tämä ohjelma löytyy Pikkusovelluksia-moduulista; voit ajaa sen.

Olipas tylsä sovellus. Onneksi FlappyBugista tulee mielenkiintoisempi.

Lisätieto: makePic abstraktina metodina

Kysymys: mitä tapahtuu, jos poistat makePic-metodin määrittelyn äskeisestä sovelluksesta?

Vastaus: saat melko selväsanaisen käännösaikaisen virheilmoituksen, joka kertoo, ettet voi luoda sellaista View-oliota, jolla ei ole makePic-metodia.

Selitys: View-luokka on määritelty niin, että vaikka siellä ei olekaan toteutusta makePic-metodille, niin se edellyttää tuollaisen toteutuksen olevan olemassa kaikilla View-tyyppisillä oliolla. Teknisemmin sanoen makePic on luokan View abstrakti metodi. Abstrakteista metodeista kerrotaan tarkemmin luvussa 7.2.

Valinnainen lisätreeni ensin

Jos äskeinen esimerkki tuntui epäselvältä tai seuraava tehtävä tuottaa vaikeuksia, voit tehdä tämän pienen lisäharjoituksen.

Muodostetaan graafinen näkymä, joka esittää Maan ja Kuun avaruudessa. Aihealueen mallina toimii Avaruus-luokan ilmentymä, jonka osina on kaksi Taivaankappale-oliota. Tässä noiden yksinkertaisten luokkien koodi toistettuna luvun 2.6 valinnaisesta osiossa, jossa ne ensi kerran esiintyivät:

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

  def halkaisija = this.sade * 2

  override def toString = this.nimi

}
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"
}

Tuo koodi on moduulin Pikkusovelluksia tiedostossa Avaruus.scala. Samasta moduulista löytyy myös Avaruusohjelma.scala, jossa on alku tuollaisen avaruuden mallin esiin laittavalle ohjelmalle:

object Avaruusohjelma extends App {

  val avaruus = new Avaruus(500)

  val tausta = rectangle(avaruus.leveys, avaruus.korkeus, Black)
  val maanKuva = circle(avaruus.maa.sade * 2, MediumBlue)
  val kuunKuva = circle(avaruus.kuu.sade * 2, Beige)

  // Kirjoita toimiva koodi kysymysmerkeillä merkittyihin kohtiin
  val gui = new View(???, "Yksinkertainen näkymä avaruuteen") {
    def makePic = ???
  }

  ??? // Tässä pitäisi käynnistää gui-muuttujan osoittama näkymä.
}
Luodaan aihealueen malliksi yksi Avaruus-tyyppinen olio.
Luodaan muutama kuva, joita käytämme näkymän osina.
Määritellään muuttuja, joka viittaa View-luokan ilmentymään. Laitetaan sen nimeksi vaikkapa gui.

Tehtäväksesi jää täydentää:

1) View’n ensimmäinen konstruktoriparametri, joka kertoo, mitä mallia luotava View-luokan ilmentymä kuvaa.
2) Toteutus makePic-metodille niin, että Maan ja Kuun kuvat tulevat asemoiduiksi sijainteihinsa tausta-kuvaa vasten. (Käytännössä Maa tulee aivan näkymän vasempaan laitaan ja Kuu lähelle oikeaa reunaa.)
  1. Näkymän käynnistävä (esiin laittava) käsky.

Tee muutokset Pikkusovelluksia-moduuliin ja kokeile, mitä tulee näkyviin, kun ajat ohjelman.

Voit katsoa vinkin tai ratkaisun alta. Voit myös palauttaa ratkaisusi saadaksesi palautteen.

Ensimmäisen ???:n korvaava koodi (konstruktoriparametri)

Näytä ratkaisuPiilota

Aihealueen mallina toimii tässä Avaruus-tyyppinen olio, joka luotiin edellä. Siihen viittaa muuttuja avaruus, joten ensimmäiset kysymysmerkit voi korvata tuon muuttujan nimellä.

Vinkki toisen ???:n korvaamiseksi (makePic)

Näytä vinkkiPiilota

Kaksi kuvaa on asemoitava tausta-muuttujan osoittaman kuvan päälle. Siihen sopii place-metodi, jota voit kutsua kahdesti.

Sijainnit saat avaruus-muuttujan kautta (vrt. luku 2.6).

Luvun 2.3 heppaesimerkistä voi olla apua, kun yhdistelet Pic-olioiden metodikutsuja. Muista, että Pic-oliot ovat muuttumattomia: kohdista jälkimmäinen place-kutsu ensimmäisen tuottamaan tulokseen.

Toisen ???:n korvaava koodi (makePic)

Näytä ratkaisuPiilota

Esimerkiksi nämä toteutukset toimivat:

def makePic = tausta.place(maanKuva, avaruus.maa.sijainti).place(kuunKuva, avaruus.kuu.sijainti)
def makePic = {
  val maaLisatty = tausta.place(maanKuva, avaruus.maa.sijainti)
  maaLisatty.place(kuunKuva, avaruus.kuu.sijainti)
}

Viimeisen ???:n korvaava koodi (käynnistäminen)

Näytä ratkaisuPiilota

Näkymään viittaa tässä gui-niminen muuttuja, ja käynnistävän metodin nimi on start, joten ratkaisuksi sopii gui.start().

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

FlappyBug-tehtävä (osa 3/17: käyttöliittymän alkua)

../_images/module_flappybug.png

Kaavio moduulin luokkien välisistä yhteyksistä. Oikealla näkyvä malli (model) meillä on jo. Nyt tehdään käyttöliittymä (UI).

Pohjustus

Tässä on pohjakoodi käynnistysoliolle. Se löytyy myös moduulin tiedostosta FlappyBugApp.scala.

import constants._

object FlappyBugApp extends App {

  val sky        = rectangle(ViewWidth, ViewHeight,  LightBlue)
  val ground     = rectangle(ViewWidth, GroundDepth, SandyBrown)
  val trunk      = rectangle(30, 250, SaddleBrown)
  val foliage    = circle(200, ForestGreen)
  val tree       = trunk.onto(foliage, TopCenter, Center)
  val rootedTree = tree.onto(ground, BottomCenter, new Pos(ViewWidth / 2, 30))
  val scenery    = sky.place(rootedTree, BottomLeft, BottomLeft)

  val bugPic = Pic("ladybug.png")


  def rockPic(obstacle: Obstacle) = circle(obstacle.radius * 2, Black)


  // INSERT YOUR OWN CODE BELOW.


}
Koodin ensimmäiset rivit vain muodostavat maisemakuvan, jota on tarkoitus käyttää taustana. Muuttuja scenery viittaa lopputulokseen. Ei ole välttämätöntä, että ymmärrät, miten tämä kuva on muodostettu, mutta löydät selityksen luvun 2.5 lopun vapaaehtoisesta materiaalista.
Tehtäväsi on täydentää annettu käynnistysolio. Kaikki tarvittava koodi tulee merkittyyn kohtaan.
Muuttuja bugPic viittaa kuvaan, jota käytämme ötökän esittämiseen käyttöliittymässä. Funktiolla rockPic voi muodostaa kuvan esteestä. Tarvitset näitä tehtävän seuraavassa vaiheessa, et vielä. Keskity aluksi pelkän taustan esittämiseen pelinäkymässä.

Suositellut työvaiheet ja vinkkejä

Muistutus: älä vain unohda pyytää vinkkiä neuvontafoorumeillamme tai harjoitusryhmässä, jos jäät jumiin! Apua voi olla myös yllä olevasta vapaaehtoisesta Avaruusohjelma-tehtävästä.

  1. Luo uusi olio, joka toimii aihealueen mallina. FlappyBug-pelin malliksi sopii Game-tyyppinen olio.
    • Game-olion saa luotua ilmaisulla new Game (luku 2.6). Konstruktoriparametreja ei tarvita.
    • Luo myös muuttuja, joka viittaa tuohon Game-olioon. Nimeä se haluamallasi tavalla.
  2. Luo näkymä (View), joka esittää tuon Game-olion ikkunassa.
    • Laita otsikoksi "FlappyBug".
    • Idea on samantapainen kuin edeltävässä palikkaohjelmassa, mutta nyt mallina on Palikka-olion sijaan Game-olio. Välitä View-oliota luodessasi parametreiksi sekä viittaus Game-olioon että otsikkomerkkijono.
    • Määrittele myös muuttuja, joka viittaa tuohon näkymäolioon (kuten nakyma palikkaesimerkissä). Nimen voisi muuten valita itse, mutta käytä nyt automaattisen arvioinnin vuoksi nimeä gui.
  3. Laadi näkymälle makePic-metodi. Voit nyt aluksi laittaa metodin palauttamaan vain maisemakuvan (scenery). Lisätään ötökkä ja este kohta.
  4. Lisää ohjelmaan start-metodikutsu, joka käynnistää näkymän.

Ajaessa esiin pitäisi tulla yksinkertainen maisemakuva otsikollisessa ikkunassa. Lopputulos ei paljon poikkea siitä, mitä olemme aiemmin show-käskyllä saaneet aikaan. Vielä.

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

FlappyBug-tehtävä (osa 4/17: este ja ötökkä näkyviin)

Täydennä näkymän makePic-metodi. Sen tulee muodostaa ja palauttaa pelitilanteen kuva taustakuvan, ötökän kuvan ja esteen kuvan yhdistelmänä niin, että ötökkä ja este ovat pelitilanteen mukaisissa sijainneissa taustan päällä.

Siis:

  • Käytä annettuja kuvia, joihin scenery ja bugPic viittaavat.
  • Kuvattakoon esteet nyt "kivinä" eli mustina ympyröinä. Voit kutsua funktiota rockPic muodostaaksesi annettua estettä vastaavan kuvan.
  • Luvussa 2.6 luomallasi Game-oliolla on kaksi muuttujaa: bug ja obstacle. Pääset niiden kautta käsiksi peliin kuuluvaan ötökkään ja esteeseen, mikä onkin tarpeen.
    • Esim. gameOlioonViittaavaMuuttuja.bug
  • Pääset ötökän ja esteen sijainteihin käsiksi niiden pos-muuttujien kautta (luku 2.6).
    • Esim. gameOlioonViittaavaMuuttuja.bug.pos
  • Asemoi ötökkä ja este juuri niihin sijainteihin, joissa ne aluksi ovat ja jotka on niiden pos-muuttujiin kirjattu. Käytä place- tai against-metodia.
    • Tähän tyyliin: scenery.place(viittausEsteeseen, esteenSijainti).
    • Vrt. palikkaohjelma (ja valinnainen avaruusohjelma) yllä.
    • Kertaa tarvittaessa esim. luvun 2.3 heppaesimerkistä, miten Pic-olioiden metodikutsuja yhdistellään.

Aja ohjelma. Esteen ja ötökän pitäisi näkyä paikoillaan. Ne eivät liiku. Vielä.

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

Kertaus ja selvennys: View-luokka, start ja makePic

Tässä laatikossa ei ole uutta asiaa. Jos tuntuu, että ymmärsit jo yllä olevan, voit hyvin ohittaa tämän lisäselonteon siitä, mikä View on ja miten se käyttäytyy.

Sovelluksen pääosat ovat sisäinen malli sekä käyttöliittymä. Käyttöliittymä tekee mallin näkyväksi ihmiskäyttäjälle ja voi sallia tämän vuorovaikuttaa mallin kanssa (luku 1.2).

View on luokka, jonka ilmentymät (View-oliot) ovat graafisia ikkunoita, jotka toimivat käyttöliittyminä. Kuhunkin View-olioon kytketään jokin muu olio, joka toimii sisäisenä mallina ja jonka tuo View siis graafisesti tuo näkyviin.

Kun luot View-olion, ilmoitat samalla, mikä tuo sisäisenä mallina toimiva olio on. Esimerkiksi yllä luotiin View käskyllä new View(game, ...) ja määrättiin näin tuo View-olio toimimaan käyttöliittymänä eräälle Game-tyyppiselle oliolle.

Pelkkä View-olion luominen ei laita ikkunaa näkyviin. Se tapahtuu, kun käynnistät View’n kutsumalla sen start-metodia. Kun kutsut tuota metodia, ikkuna paitsi ilmestyy myös alkaa reagoida käyttäjän toimiin (mistä lisää pian kolmoskierroksella).

En oikein ymmärtänyt miten määritetty makePic metodi "ajoi" itsensä. Se vain määritettiin, mutta sitä ei kutsuttu missään vaiheessa ohjelmaa.

Kunhan View on käynnistetty startia kutsumalla, se piirtää itsensä näkyviin käyttäen omaa makePic-metodiaan, jonka sille määrittelet. makePiciä ei tosiaan tarvitse erikseen kutsua, vaan View-olio osaa huolehtia siitä itse käynnistyttyään. Ikkunaan piirtyy juuri se kuva, jonka makePic palauttaa.

Sekä makePic että View-luokka yleensäkin tulevat tutummiksi, kun kolmoskierros jatkaa siitä mihin kakkonen jäi.

Lisäpuuha: nätimpää koodia

Annetussa FlappyBugAppissa taustakuva on muodostettu useilla peräkkäisillä käskyillä käyttäen useita muuttujia (esim. foliage, ground) tilapäissäilöinä. Nuo muuttujat vain pitävät tallessa kuvanmuodostuksen välivaiheita ja jäävät turhiksi, kun lopullinen maisemakuva on valmis. Voimme kaunistaa koodiamme jäsentämällä sen hieman toisin. Kokeile näitä.

  1. Osaatko kirjoittaa tuon osan koodista uusiksi niin, että scenery ei olekaan muuttuja vaan parametriton funktio, joka palauttaa maisemakuvan? Näin:

    def scenery = {
      // kuvan muodostava koodi tänne
    }
    

    Tilapäissäilöt kuten foliage ovat tässä ratkaisussa paikallisia muuttujia, jotka määrittelet sceneryn monirivisen rungon sisällä.

  2. Entä jos vaihdat äskeisestä ratkaisusta ilmaisun def scenery muotoon val scenery? Toimiiko? Mitä eroa tällä ratkaisulla on äskeiseen?

Yhteenvetoa

  • Scala-sovellukselle määritellään käynnistysolio ilmaisulla extends App.
    • Tällaiselle oliolle ei tarvitse kirjoittaa metodeita. Olion sisältämä koodi suoritetaan järjestyksessä, kun sovellus käynnistetään.
    • Isomman ohjelman tapauksessa käynnistysolion käskyt aktivoivat muita ohjelman osia.
  • Scala-kirjastoista löytyy funktioita, joilla Scala-ohjelma voi lukea ohjelman käyttäjän kirjoittamaa näppäimistösyötettä tekstikonsolissa.
  • Sovellusohjelman ohjelmakoodissa tyypillisesti erotetaan toisistaan aihealueen malli ja tuota mallia kuvaava ja käsittelevä käyttöliittymä.
  • Graafisen käyttöliittymän laatimiseen on tarjolla apukirjastoja.
    • Yksi sellainen löytyy pakkauksesta o1. Sen keskeinen luokka on käyttöliittymäikkunoita edustava View
  • Lukuun liittyviä termejä sanastosivulla: käynnistysolio; syöte, I/O; malli, käyttöliittymä, graafinen käyttöliittymä eli GUI; tekstikonsoli.

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 ja Nikolas Drosdek yhteistyössä Juha Sorvan, Otto Seppälän, Arto Hellaksen ja muiden kanssa.

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

Joidenkin lukujen lopuissa on lukukohtaisia lisäyksiä tähän tekijäluetteloon.

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