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? Kaksi tapaa käynnistää sovellus: käynnistysmetodi
(@main
) ja 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.
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
Jo muinaiset roomalaiset aloittivat kunkin uuden ohjelmointityökalun opettelun tekemällä sillä ns. "Hello, World" -ohjelman. 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. 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:
File → New → Module. Esiin tulee apuikkuna.
Apuikkunan vasemmassa yläkulmassa on valinta New Module. Varmista, että se on valittuna. Oikealla pitäisi nyt näkyä asetuksia kuten moduulin nimi ja sijainti.
Kirjoita moduulille nimeksi Ave. Älä kuitenkaan vielä paina Create-nappia, vaan katso asetukset kuntoon:
Huolehdi, että Location on polku kansioon, jossa on koko O1-projektisi — siis se pääkansio, jonka loit kurssin alussa ja jonka sisältä löytyvät kaikki tähän mennessä lataamasi kurssimoduulit omissa alikansioissaan.
Älä siis valitse sijainniksi mitään toisista moduuleista (kuten O1Library). Valitse se kansio, joka sisältää nuo toiset moduulit.
Huolehdi, että valittuna kielenä on Scala.
Huolehdi, että työkalustoksi on valittu IntelliJ (eikä esimerkiksi sbt).
Huolehdi, että kohdassa Scala SDK on valittuna Scalan versio 3.3.
Huolehdi, että Add sample code -kohtaa ei ole valittuna. (Muutoin IntelliJ luo moduuliisi automaattisesti esimerkkikoodia, joka ei sovi tarkoituksiimme ja häiritsee tämän luvun suorittamista.)
Paina Create. Ave-moduuli näkyy nyt IntelliJ’n Project-näkymässä.
Luo uusi tyhjä kooditiedosto:
Katso luomasi Ave-moduulin sisältöä: siellä on
src
-kansio, joka on tyhjä. IntelliJ tykkää luoda kooditiedostoja varten tuonnimisen kansion (jonka nimi tulee sanasta source ja viittaa lähdekoodiin). Ei ole välttämätöntä, että koodisi on noin nimetyssä kansiossa, mutta mennään nyt sillä.Klikkaa
src
-kansiota oikealla napilla. Valitse New → File.IntelliJ pyytää tiedostolle nimeä. Kirjoita
Ave.scala
Tiedosto näkyy nyt
src
-kansion sisällä ja avautuu myös IntelliJ’n editoriin. Se on tyhjä.
Ensimmäinen yritys kirjoittaa tervehdysohjelma:
Kirjoita yllä annetut tulostuskäskyt luomaasi tyhjään tiedostoon. Käännä moduuli F10.
Havaitse: käännös epäonnistuu. Build-välilehti kertoo: Illegal start of toplevel definition. Mistä on kyse?
Kun laadimme sovellusta, meidän on määriteltävä, mikä koodi toimii suorituksen "lähtöpisteenä". Tuo koodi voi sitten aktivoida (kutsua) muita ohjelman osia. Tervehdyssovellukseltamme tällainen lähtöpiste vielä puuttuu.
Scalassa "lähtöpisteen" kirjaamiseen on kaksi vaihtoehtoista tapaa, joista molempia on käytämme O1-kurssin eri ohjelmissa:
Voimme määrätä tietyn funktion sovelluksen käynnistysfunktioksi (main function). Esimerkiksi Pong-sovelluksellamme on käynnistysfunktiona
runPong
-niminen funktio, joka suoritetaan ensimmäisenä, kun peli käynnistetään.Voimme määrätä tietyn olion sovelluksen käynnistysolioksi (app object), joka tavallaan kuvaa koko sovellusta kokonaisuutena. Esimerkiksi GoodStuff-sovelluksellamme on käynnistysoliona
GoodStuff
-niminen yksittäisolio, jonka sisältämä koodi suoritetaan ensimmäisenä, kun peli käynnistetään.
Kumpikin tapa on helppo, ja alla on esimerkkejä molemmista.
Tapa 1: Käynnistysfunktio
Käynnistysfunktion määrittely
Kirjoitetaan käynnistysfunktio. Tässä sille aihio:
@main def ajaSovellukseni() =
// Tämä on käynnistysfunktio.
// Tänne käskyt, jotka suoritetaan, kun sovellus ajetaan.
... alkuun on kirjoitettu merkintä @main
, joka tekee funktiosta
käynnistysfunktion.
Käynnistysfunktiot ovat vaikutuksellisia. Hyvän tavan mukaisesti (luku 2.6) merkitsemme parametrittoman vaikutuksellisen funktion tyhjillä sulkeilla.
Tervehdysohjelma käynnistysfunktiolla
Suuremmassa ohjelmassa funktioita, luokkia ja vastaavia osia voi olla kymmeniä, satoja
tai enemmän. Tervehtivään miniohjelmaamme tarvitsemme vain yhden funktion, jonka sisään
tulostuskäskyt kirjoitetaan ja joka myös toimii käynnistysfunktiona. Pannaan sen nimeksi
vaikkapa ajaTervehdysohjelma
.
Muokkaa Ave.scala
-tiedostoa niin, että sen sisältö on tällainen:
@main def ajaTervehdysohjelma() =
println("Ave, Munde!")
println("No avepa ave sinnekin!")
Nyt ohjelman voi ajaa avaamalla Ave
-tiedoston valikon Project-välilehdellä ja
valitsemalla Run 'ajaTervehdysohjelma'. Vielä helpompi tapa on napsauttaa @main
def
-rivin viereen koodin marginaaliin ilmestynyttä Play-ikonia . Kokeile.
Tulosteet tulevat IntelliJ’n Run-välilehdelle alas. Tällaista tulostukseen tarkoitettua aluetta sanotaan tekstikonsoliksi (text console) tai vain konsoliksi.
Olet nyt tehnyt kokonaisen joskin pikkuruisen 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ä:
Ohjelmassa on graafinen käyttöliittymä, jonka osia klikkailemalla käyttäjä voi kertoa ohjelmalle, mitä haluaa sen tekevän.
Ohjelma osaa ladata kiintolevylle tallennettuja tiedostoja, joista se saa dataa käsiteltäväkseen.
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.
readLine
-funktion avulla yllä kuvatun ohjelman voi toteuttaa seuraavasti.
import scala.io.StdIn.*
@main def ajaTervehdysohjelma() =
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ä
tekstikonsolista luettavaan syötteeseen.
Kirjoita itse tällainen ohjelma. Voit joko muokata jo palauttamaasi Ave
-ohjelmaa tai
tehdä uuden tiedoston.
Aja ohjelma. Katso, mitä konsoliin tulostuu, ja vastaa ohjelman kysymykseen. (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äynnistysfunktioon. Määrittele
funktio Ymparoi.scala
-nimiseen tiedostoon. Luo tuo tiedosto aiemmin
luomaasi Ave-moduulin src
-kansioon Ave.scala
n rinnalle.
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Tapa 2: Käynnistysolio
Kuten mainittu, Scala-sovellukselle voi määritellä käynnistysfunktion sijaan käynnistysolion. Käynnistysolio ikään kuin edustaa koko sovellusta kokonaisuutena. Se ilmoittaa, mitä tapahtuu, kun sovellus käynnistetään. Esimerkiksi Ave-ohjelman voi määritellä näinkin:
object Ave extends App:
println("Ave, Munde!")
println("No avepa ave sinnekin!")
App
on Scalaan valmiiksi määritelty tietotyyppi, joka kuvaa
käynnistettäviä sovelluksia.
Määrittelemme, että Ave
on yksittäisolio, joka on erikoistapaus
App
-tyypistä. (Vertaa luvun 2.4 Teräsmies, joka oli erikoistapaus
henkilötyypistä.) Tämä nimenomainen oliomme on kuten kaikki muutkin
App
-tyyppiset oliot sikäli, että ohjelman voi käynnistää sen
kautta. Se laajentaa tuota yleistä määrittelyä sikäli, että...
... kun se ajetaan, suoritetaan juuri nämä käskyt.
Käynnistysoliosta on toinen esimerkki seuraavassa tehtävässä.
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 paluuarvoja
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. Tulosteesta puuttuu tietoja keskeltä, ja lopusta uupuu koko toista tapahtumaa käsittelevä osio.Sinun täytyy lisätä
Odds
-oliot luovat käskyt ja kutsua luotujen olion metodeita sopivissa kohdissaOddsTest1
-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
jaOdds
ovat samassa pakkauksessa, joten voit luodaOddsTest1
-ohjelmassaOdds
-olioita helposti lisäämättäimport
ia.Huomaat, että annetussa koodissa on käytetty
readInt
- jareadDouble
-nimisiä funktioita. Ne ovat samankaltaisia kuin yllä esiteltyreadLine
, mutta tulkitsevat käyttäjältä saadut merkit lukuarvoina. EsimerkiksireadInt
palauttaaInt
-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 luontiparametreina. Vältä turha toisto toteuttamalla
tämä ohjelma hieman toisella tavalla:
Laadi
OddsTest1
-yksittäisoliolle parametriton, vaikutuksellinen metodirequestOdds
, joka lukee kaksi kokonaislukua, luo niiden perusteellaOdds
-olion ja palauttaa viittauksen luomaansa olioon.Kutsu
requestOdds
ia (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 havaittavassa 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 = Palikka(20, 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 pikakertaus
Muistatko, kun luvun 2.4 lopussa kirjoitimme luokan Henkilo
? Ja siitä erikoistapauksen
näin:
object terasmies extends Henkilo("Clark"):
def lenna = "WOOSH!"
Tuo yksittäisoliomme on henkilö siinä missä muutkin, minkä lisäksi määrittelimme
sille lenna
-metodin. Aivan hetken päästä löydämme konkreettisempaa käyttöä luokan
"laajentamiselle" noin.
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.4 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 = Palikka(20, 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.
object nakyma extends View(malli): def makePic = val tausta = rectangle(500, 500, Black) val palikanKuva = rectangle(malli.koko, malli.koko, malli.vari) tausta.place(palikanKuva, malli.sijainti)// defined object nakyma
Kyseessä on eräänlainen View
-olio, mutta erityinen sellainen;
käytämme extends
-sanaa. Luontiparametriksi 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 graafinen sovellus, jolla on käynnistysfunktio.
val tausta = rectangle(500, 500, Black)
val palikka = Palikka(20, Pos(300, 50), Gray)
object nakyma extends View(palikka, "Palikkaohjelma"):
def makePic =
val palikanKuva = rectangle(palikka.koko, palikka.koko, palikka.vari)
tausta.place(palikanKuva, palikka.sijainti)
end nakyma
@main def kaynnistaPalikkaohjelma() =
nakyma.start()
Meillä on malli, jonka haluamme esittää käyttäjälle.
Ja näkymä, joka osaa esittää mallin eräällä tavalla.
Kun sovellus käynnistetään, saamme näkymän esiin kutsumalla
sen start
-metodia. Käynnistysfunktiomme hoitaa tämän.
Käynnistysfunktion koodi on tämän jälkeen suoritettu, mutta
sovelluksemme pyörii edelleen erillisessä ikkunassaan, kunnes
käyttäjä toisin haluaa.
Pieni lisäys: voimme määritellä sovelluksellemme otsikon
antamalla lisäparametrin luotavalle View
-oliolle.
Tämä teksti tulee näkyviin ikkunan otsikkoriville.
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 kaikilla View
-tyyppisillä oliolla.
Teknisemmin sanoen makePic
on luokan View
abstrakti metodi.
Abstrakteista metodeista kerrotaan tarkemmin luvussa 7.3.
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
end Taivaankappale
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
Tuo koodi on moduulin Pikkusovelluksia tiedostossa o1/avaruus/Avaruus.scala
.
Samasta moduulista löytyy myös Avaruusohjelma.scala
, jossa on alku tuollaisen
avaruuden mallin esiin laittavalle ohjelmalle:
val avaruus = 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
object ikkuna extends View(???, "Yksinkertainen näkymä avaruuteen"):
def makePic = ???
end ikkuna
@main def kaynnistaAvaruusohjelma() =
??? // Tässä pitäisi käynnistää ikkuna-niminen näkymä.
Luodaan aihealueen malliksi yksi
Avaruus
-tyyppinen olio.
Luodaan muutama kuva, joita käytämme näkymän osina.
Määritellään näkymää kuvaava olio. Pannaan
sen nimeksi vaikkapa ikkuna
.
Tehtäväksesi jää täydentää:
1) View
’n ensimmäinen luontiparametri,
joka kertoo, mitä mallia tämä View
-luokasta
johdettu yksittäisolio 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.)
3) 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 (luontiparametri)
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
)
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
)
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äkymään viittaa tässä ikkuna
-niminen muuttuja, ja käynnistävän
metodin nimi on start
, joten ratkaisuksi sopii ikkuna.start()
.
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Kertaus ja selvennys View
-luokasta
Tässä laatikossa ei ole varsinaista uutta asiaa. Jos tuntuu, että ymmärsit jo yllä olevan,
voit hyvin ohittaa tämän selonteon siitä, mikä View
on ja miten se käyttäytyy. Voit myös
palata tutkimaan tätä myöhempien tehtävien aikana tarpeen mukaan.
View
-luokka, start
ja makePic
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 (erilaiset 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 palikkaa kuvaava View
käskyllä, jossa luki View(palikka, ...)
, ja alla tehtävässä luot
View
’n, joka toimii käyttöliittymänä eräälle Game
-tyyppiselle
oliolle: View(gameenViittaavaMuuttuja, ...)
.
(Yleisesti ottaen View
-olioiden tulee tietää, mikä olio toimii
mallina, jotta näkymä toimisi oikein. Mutta tähän hätään ei ole
tarpeen sen kummemmin pohtia View
-olioiden sisäistä toimintatapaa.)
Pelkkä View
-olion luominen ei tuo 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. Tuosta reagoinnista kerrotaan 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 näkymä on käynnistetty start
ia kutsumalla, se piirtää
itsensä näkyviin käyttäen omaa makePic
-metodiaan, jonka sille
määrittelet. makePic
iä 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 aina vain
tutummiksi, kun kolmoskierros jatkaa siitä mihin kakkonen jää.
FlappyBug-tehtävä (osa 3/17: käyttöliittymän alkua)
Pohjustus
Tässä on pohjakoodia sovellukselle. Se löytyy myös moduulin tiedostosta
FlappyBugApp.scala
.
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, 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ää käyttöliittymän koodi. Kommentti näyttää, mihin kirjoitat koodisi. Tarkemmat ohjeet löytyvät tuosta alta.
Muuttuja bugPic
viittaa kuvaan, jolla esitämme ötökän
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ä.
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ä.
Työvaiheet ja vinkkejä
Luo uusi olio, joka toimii aihealueen mallina. FlappyBug-pelin malliksi sopii
Game
-tyyppinen olio.Game
-olion saa luotua ilmaisullaGame()
(luku 2.6). Luontiparametreja ei tarvita.Luo myös muuttuja, joka viittaa tuohon
Game
-olioon. Nimeä se haluamallasi tavalla.
Luo näkymä, joka esittää tuon
Game
-olion ikkunassa. Tee siitä yksittäisolio, joka on erikoistapausView
-luokasta (extends View
). Idea on samantapainen kuin edeltävässä palikkaohjelmassa, mutta nyt mallina onPalikka
-olion sijaanGame
-olio.Nimen näkymälle voisi muuten valita itse, mutta käytä nyt automaattisen arvioinnin vuoksi nimeä
flappyView
.Välitä
flappyView
-oliota määritellessäsi parametreiksi sekä viittausGame
-olioon että otsikkomerkkijono.Kirjaa ikkunan otsikoksi "FlappyBug".
Laadi näkymälle
makePic
-metodi. Voit nyt aluksi panna metodin palauttamaan vain maisemakuvan (scenery
). Lisätään ötökkä ja este kohta.Lisää ohjelmaan käynnistysfunktio. Anna funktiolle nimeksi
launchFlappy
ja muista tyhjät kaarisulkeet perään. Käynnistysfunktion ei tarvitse tehdä muuta kuin kutsuastart
-metodia ja tuoda sitenflappyView
-näkymä esiin.Huomaa, että
launchFlappy
-funktio tulee erilleen uloimmalle sisennystasolle, ei esimerkiksiflappyView
-näkymän koodin sisää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
jabugPic
viittaavat.Kuvattakoon esteet nyt "kivinä" eli mustina ympyröinä. Voit kutsua funktiota
rockPic
muodostaaksesi annettua estettä vastaavan kuvan.Kuten luvussa 2.6 määrittelit,
Game
-oliolla on kaksi muuttujaa:bug
jaobstacle
. 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
- taiagainst
-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.
Eivät liiku... vielä.
Kommentteja kolmoskierroksen Flappy-jatkotehtäviä jo tehneiltä:
Mahtavaa, miten FlappyBug rakentuu pala palalta ja herää eloon!
Mukava tunne tulee, kun FlappyBug alkaa jo liikkua johonkin. Pienillä muutoksilla saa paljon aikaan.
Hämmentävää, että olen saanut aikaan jotain, mikä liikkuu ja mitä voi ohjailla.
Ihme että näin ison onnistumisen tunteen voi saada pikseliötökän liitelystä näytöllä!
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Lisäpuuha: nätimpää koodia
Annettu FlappyBug-koodi muodostaa taustakuvan 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ä.
Osaatko kirjoittaa tuon osan koodista uusiksi niin, että
scenery
ei olekaan muuttuja vaan parametriton funktio, joka palauttaa maisemakuvan? Näin:def scenery = // Kirjoita koodi, joka // muodostaa kuvan, tänne.
Tilapäissäilöt kuten
foliage
ovat tässä ratkaisussa paikallisia muuttujia, jotka määritteletscenery
n monirivisen rungon sisällä.Entä jos vaihdat äskeisestä ratkaisusta ilmaisun
def scenery
muotoonval scenery
? Toimiiko? Mitä eroa tällä ratkaisulla on äskeiseen?
Yhteenvetoa
Scala-sovellukselle voi määritellä käynnistyskohdan (eli "mitä tapahtuu ensin") kahdella eri tavalla.
Käynnistysfunktio merkitään
@main
. Käynnistysolio merkitäänextends App
.Kurssilla käytämme kumpaakin tapaa. Kurssin ulkopuolella käynnistysfunktiot ovat yleisempiä.
Isomman ohjelman tapauksessa käynnistysfunktion tai -olion 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 edustavaView
.
Lukuun liittyviä termejä sanastosivulla: käynnistysfunktio, 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, Kaisa Ek, Joonatan Honkamaa, Antti Immonen, Jaakko Kantojärvi, Niklas Kröger, Kalle Laitinen, Teemu Lehtinen, Mikael Lenander, Ilona Ma, Jaakko Nakaza, Strasdosky Otewa, Timi Seppälä, Teemu Sirkiä, 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 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 nyt 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 suunnitteluun ja toteutukseen on osallistunut useita opiskelijoita yhteistyössä O1-kurssin opettajien kanssa.
Kurssin tämänhetkinen henkilökunta löytyy luvusta 1.1.
Joidenkin lukujen lopuissa on lukukohtaisia lisäyksiä tähän tekijäluetteloon.
Tämä on ihan tavallinen funktion määrittely, paitsi että...