Tehtävä 1 - Scala API:n ulkopuolisten kirjastojen käyttö, ristinollapeli Deadline 7.10. (Muokkaus 25.9. PDF-tiedoston linkit korjattu) Taustaa Tällä tehtäväkierroksella tutustutaan Scala Standard API:n ulkopuolisten kirjastojen käyttöön ja harjoitellaan kurssin palautuskäytäntöjä vastaavan palautuksen laatimista. Katso mitä kirjastolla tarkoitetaan Ohjelmointi 1 -kurssin oppimateriaalin sanastosta. Kierroksella käytetään kahta ulkoista kirjastoa, joista toinen on tätä tehtävää varten erikseen laadittu muutaman apufunktion sisältävä kirjasto ja toinen on visuaaliseen tietokonetaiteen luomiseen tarkoitettu Processing-kirjasto. Kirjaston tarjoamien funktioiden listaus löytyy täältä: https://processing.org/reference/. Scala on Java-ohjelmointikielen pohjalta rakennettu. Se käyttää Javan virtuaalikonetta (JVM, tästä enemmän Ohjelmointi 1 -kurssin opintomateriaalin luvussa 5.2.) ja kirjoittamasi ohjelmakoodi käännetään samanlaisiksi tavukoodin sisältäviksi.class-tiedostoiksi kuin Java-koodi käännettäisiin. Tämä yhteys mahdollistaa näiden kahden kielen saumattoman käytön, vaikka kielet hiukan eroavat syntaksiltaan (eli miten asiat kirjoitetaan koodina). Kuten Scala, myös Processing on Java-pohjainen, mutta syntaksisesti yksinkertaisempi ja siis lähempänä Scalaa. Nyt ei kannata kuitenkaan pelästyä, että asiat menevät heti vaikeiksi. Voit kirjoittaa jo oppimaasi Scalasyntaksia ongelmitta ja saat Processingin tarjoamat ominaisuudet käyttöösi varsin helposti (tämä neuvotaan tehtävänannossa). Poiketen Ohjelmointi1-kurssista, tällä kurssilla jokaisella ohjelmointikierroksella koodisi tarkistaa myös ihminen. Tämä pakottaa ihmisluettavan koodin tuottamiseen, mutta toisaalta antaa suuremman vapauden oman ratkaisun laatimisessa kun koodin ei välttämättä tarvitse asettua tietynlaiseen muottiin automaattista tarkistusta varten. Mitä selkeämpi, siistimpi ja tarvittaessa dokumentoidumpi koodisi on, sitä helpommin assari saa sen tarkastettua. Lisäksi kun itse palaat koodin pariin pitkän tauon jälkeen, on tärkeää, että pystyt helposti ymmärtämään, mitä olet ajatellut koodaushetkellä ja mitä koodin olisi tarkoitus saada aikaiseksi. Toteutuksen toimivuutta ei toki sovi unohtaa.
Koodin luettavuutta voit helposti parantaa esimerkiksi seuraavilla kikoilla: Pidä rivien pituus alle 120 merkin Saat Eclipsestä asetettua marginaalin asettamalla halutun arvon "Print margin column"- valintaan Preferences --> General -> Editors -> Text Editors Nimeä muuttujasi kuvaavasti ja yhdenmukaisesti Älä sekoita muuttujien nimissä esimerkiksi suomen ja englannin kieltä Älä nimeä muuttujiasi epätäsmällisesti tyyliin "var teksti:string = "jokin teksti"" Kommentoi ratkaisusi tarvittaessa Hyvä käytäntö on kirjoittaa lyhyt kuvaus ainakin objektille/luokalle sekä kullekin sen funktiolle. Funktioiden sisällä hankalammat ratkaisut on syytä kommentoida lyhyesti. Muuttujia harvoin tarvitsee kommentoida, jos ne on nimetty kuvaavasti ja käyttötarkoitus on ilmeinen. Älä pelkää välilyöntejä ja muita tyhjiä merkkejä Ohjelmointi 1-kurssin tyylioppaasta löydät laajemmat ohjeet, pyri noudattamaan niitä. Vaikka assarisi ei ole kone, hän haluaisi arvostella palautuksesi kuin kone. Jos siis esimerkiksi jostain syystä laitat ohjelmasi tiettyyn pakkaushierarkiaan ja sinulla lukee tässä kooditiedostossa jotain tyyliin "package omapakkaukseni.nimi", niin säilytä tämä tiedostohierarkia myös palauttamassasi pakkauksessa. Älä myöskään jätä pakkauksesta pois mitään, mikä aiheuttaa virheilmoituksia. Jos jossain tehtävässä esimerkiksi annetaan tehtävänannon yhteydessä jotain valmista koodia, palauta myös tämä tiedosto, mikäli sitä käytetään. Siinä mielessä assari ei kuitenkaan ole kone, että hän ei osaa lukea.class-tiedostoja ilman apuvälineitä, niitä ei siis tarvitse palauttaa.
Tehtävänanto Tehtävässä laadittava ohjelma on ristinollapeli, jonka toteuttamiseen tarvitset suunnilleen 100 riviä koodia. Tähän tehtävänantoon liittyvät Ohjelmointi1-kurssin oppimateriaalin luvut: Luku 1.3: Lukuja ja tekstiä Scalalla, lähinnä jakolaskut ja lukutyyppien yhdistely Luku 1.4: Arvojen tallentaminen muuttujiin Luku 1.5: Kokoelmia ja viittauksia Luku 1.6: Aliohjelmien käyttäminen Luku 1.8: Funktioista, tyypeistä ja virheistä Luku 2.2: Olioiden sisäinen toiminta Luku 2.5: Erilaisia muuttujia Luku 2.6: Rajapintoja ja dokumentteja, lähinnä dokumentaatio ja näkyvyysmääreet Luku 2.8: Totuusarvoja ja vertailua Luku 2.9: Valintoja Luku 3.2: Säiliöitä... ja kaatuva ohjelma Luku 3.4: Logiikkaa ja ostoksia Tämän tehtävänannon suorittamisen jälkeen osaat (suurimmat asiat): Käyttää Scala API:n ulkopuolisia kirjastoja omassa koodissasi. Kirjoittaa yksinkertaisen ohjelman Processing-kirjaston avulla, jossa piirretään ruudulle kuvia, tekstiä ja erilaisia geometrisia primitiivejä. 1. Osatehtävä - Ulkoisten kirjastojen näkyminen kääntäjälle Lataa viimeistään nyt tässä tehtävässä tarvittavat tiedostot mycoursesista ja tuo projekti Eclipseen (File.. -> Import.. -> Existing Project into workspace -> Select archive file) Tässä vaiheessa projektissasi näkyy virheitä, mutta ei anneta sen häiritä.
Huolehditaan nyt, että saat käyttöösi Processingin ja sinulle tehdyn apukirjaston ominaisuudet käyttöösi. Kumpikin kirjasto löytyy jarpakkauksesta lib-kansiossa. Saat lisättyä kirjastot käyttöösi valitsemalla ensin kummankin, ja hiiren oikean painikkeen painalluksen esiin tuomasta valikosta valitsemalla Build Path -> Add to Build Path (kts. kuva oikealla). Nyt kääntäjä löytää kirjastot ja pystyy luomaan.class-tiedostot, joiden avulla pystyt ajamaan ohjelmasi virtuaalikoneella. Koska tässä jaettu tictactoe.zip oli tehty Eclipsen projektikansiosta ja siinä ovat mukana piilotetut projektitiedostot (esimerkiksi.project ja.classpath), linkitys ulkoisiin kirjastoihin oltaisiin voitu tehdä ennen projektin jakamista ja linkki olisi tallentunut projektin.classpath-tiedostoon. Vastaavasti kun kierroksen lopussa valmistelet palautettavan pakettisi projektikansiosta, projektin asetukset toimivat oikein assarin koneella. Tässä vaiheessa voit testata nopeasti onnistuiko kirjastojen linkitys sijoittamalla annetun TicTacToe.scala-tiedoston alkuun import-käskyn, jolla lisäät processing.core-pakkauksen. Tämän jälkeen kaikkien virheilmoitusten tulisi kadota. Voit samalla lisätä myös annetun helper.tictactoehelper-paketin käyttöösi. Tässä vaiheessa olet jo: Kirjastojen lisäys Eclipsessä. Luonut uuden Eclipse-projektin annetun projektikansion pohjalta Linkittänyt Scala Standard API:n ulkopuolisia kirjastoja oman projektisi käyttöön 2. Osatehtävä - Olion tictactoe rakenne Osatehtävässä tutustutaan luotavan olion rakenteeseen. Lue tämä osio huolella, jotta ymmärrät paremmin mitä sinun seuraavissa osatehtävissä tulee toteuttaa. Tämän kierroksen ristinollapelin toteutus kirjoitetaan yksittäisoliona, joka perii Processing:in määrittelemän PApplet-luokan (olion määrittely "object TicTacToe extends PApplet"). Oliolla on kuusi metodia, ja sinun tehtäväsi on toteuttaa viisi ensimmäiseksi alla mainittua. Kunkin metodin
toiminnan tarkempi kuvaus löytyy jäljempänä löytyvistä osatehtävistä. def setup() : Unit Processingissa määritelty metodi, josta luodaan oma toteutus eli metodi ylikirjoitetaan (override, tästä tarkemmin Ohjelmointi 1 kurssin oppimateriaalin luvussa 6.4.). Metodia kutsutaan kerran kun oliosta luodaan instanssi; mikäli PApplet-instanssi luodaan selainympäristössä, kutsuu selain automaattisesti init-metodia. Oma toteutuksemme on kuitenkin erillinen ohjelma, joten joudumme kutsumaan initmetodia itse. Voit lukea lisää setup-metodista täältä. def draw() : Unit Processingissa määritelty metodi, josta luodaan oma toteutus. Tätä metodia kutsutaan automaattisesti setup-metodin kutsun jälkeen kunnes ohjelman suoritus lakkaa tai kutsutaan noloop-metodia. draw-metodissa kannattaa siis olla se koodi, jonka halutaan päivittävän ikkunaa ja se on pakko toteuttaa, mikäli halutaan seurata hiiren toimintoja. Voit lukea lisää täältä. def mouseclicked() : Unit Processingissa määritelty metodi, josta luodaan oma toteutus. Metodia kutsutaan automaattisesti kun pelaaja painaa peli-ikkunaa kursorillaan. Painalluksen yhteydessä sijoitetaan pelaajan pelimerkki laudalle, mikäli valittu ruutu on tyhjä. Tämän jälkeen tarkistetaan loppuiko peli ja ilmoitetaan pelaajalle mahdollisesta voitosta. Voit lukea lisää mouseclicked-metodista täältä. def getturn() : String Apumetodi, joka palauttaa vuorossa olevan pelaajan pelimerkin merkkijonona. Mikäli vuorossa on nolla, palautetaan "O" (iso o-kirjain) ja mikäli vuorossa on risti palautetaan "X". Tätä metodia käytetään, kun tallennetaan pelin tilanne voittotarkistusta varten. def changeturn() : Unit Apumetodi, joka vaihtaa vuorossa olevaa pelaajaa. Mikäli vuorossa on nolla, muutetaan vuorossa olevaksi pelaajaksi risti ja vastaavasti toisinpäin. Metodi ei palauta mitään. def main(args: Array[String]) main-metodi on niin sanottu pääohjelmametodi. Se ajetaan kun oliosta luodaan instanssi. Pääohjelmametodilla on oliosta riippumatta aina samanlainen allekirjoitus, tai määrittely ja sitä kutsutaan automaattisesti kun luot oliostasi instanssin. Perusmuodossaan pääohjelmametodi on seuraavanlainen:
def main(args: Array[String]) { // käskyt jotka tulee suorittaa ajettaessa } Metodin tulee olla nimeltään main ja ottaa parametrinaan ainoastaan merkkijonoja säilövä Array-tyyppinen taulukko. Taulukon arvot haetaan komentoriviltä ajokomennon jälkeen, mutta tässä tehtävässä parametrin käyttöön ei ole tarvetta. TicTacToe:n olion määrittelyn lopussa löytyvä pääohjelmametodi on tässä toteutettu valmiiksi. Aluksi luodaan peli-ikkunalle kehys, jonka sisällöksi kyseinen olio asetetaan. Olion alustuksen jälkeen määritellään ikkunan koko, tiivistetään ikkunan sommittelua, asetetaan ikkuna näkyville ruudun keskelle ja määritellään, että ikkunan sulkeminen lopettaa ohjelman suorituksen. Tässä vaiheessa olet edellisten lisäksi: Tutustunut tässä toteutettavan olion rakenteeseen. Tutustunut tarkemmin joihinkin Processingin ominaisuuksiin. Tutustunut pääohjelmametodin käsitteeseen. 3. Osatehtävä - Olion alustus ja ikkunan päivittäminen Tässä osatehtävässä määritellään oliolle ilmentymämuuttujat sekä laaditaan setup- ja draw-metodit. Ohjeistus on vain kaikkein pakollisimpien ilmentymämuuttujien toteuttamiseen, mutta voit käyttää lisäksi myös muita omia muuttujia. Olion ilmentymämuuttujat Yksinkertainen oliomme selviäisi periaatteessa ilman muuttujiakin. Mutta koska haluamme noudattaa hyvää tyyliä ja välttää maagisia lukuja, määrittelemme muutaman tarpeellisen arvoon muistiin. Ensiksi tee kokonaislukutyyppiset muuttujat säilömään: ruudun koko ruutujen määrä pelialueen koko Ruudun koko kannattaa olla 80 (pikseliä) tai alle, sillä tämän tehtävän tiedostopaketissa olleet kuvatiedostot ovat 80x80 pikseliä ja niiden venyttäminen ruuduille sopiviksi voi aiheuttaa turhaa pikselöintiä. Ruutujen määrä kuvastaa yhden sivun ruutujen määrää. Jos siis lähdetään toteuttamaan perinteistä 3x3 ristinollaa, asetat muuttujan arvoksi 3. Ruudukon koko on luonnollisesti kahden
edellisen tulo. Seuraavaksi ladataan ja tallennetaan kuvatiedostot talteen. Tallenna kumpaakin pelimerkkiä vastaava kuva PImage-tyyppiseen muuttujaan. Käytä kuvien lataamiseen loadimage-metodia, kts. PImage. Luo lisäksi PImage-tyyppinen muuttuja säilömään vuorossa olevan pelaajan pelimerkki. (Tämän muuttujan arvoa päivitetään changeturn-metodissa.) Aseta vuoron säilövän muuttujan alkuarvo niin, että nolla-pelimerkki aloittaa. Tarvitset enää mekanismit voiton tarkistamiseen. Varaa totuusarvomuuttuja säilömään tieto siitä, onko peli jo päättynyt. Pelin tilanne tallennetaan kaksiulotteiseen merkkijonoja säilövään Array-muuttujaan, eli taulukkoon. Se on hyvin samankaltainen kuin Buffer, mutta Array:n koko on muuttumaton joten siitä voi ainoastaan muuttaa alkioiden arvoja (alkioiden poisto ja lisäys ei ole mahdollista). Tarkemmin taulukot on käyty läpi Ohjelmointi 1 -kurssin oppimateriaalin luvussa 5.6, vähintään silmäile luku jo tässä vaiheessa ja erityisesti kohta, jossa selvennetään moniulotteisia taulukoita. Voit luoda halutunlaisen kaksiulotteisen taulukon helposti esimerkiksi seuraavalla tavalla: var taulukko = Array(Array("", "", ""), Array("", "", ""), Array("", "", "")) Keksitkö, mikä tällaisessa määrittelyssä voisi mennä pieleen? Mitä tapahtuu, jos haluammekin luoda 4x4-ristinollapelin? Tai 5x5? Koska emme halua joka kerta muokata myös taulukon alustusta, kun päätämme vaihtaa pelialueemme kokoa, käytämme fill-metodia (kts. alla). Kuvankaappaus Array:n dokumentaatiosta: http://www.scala-lang.org/api/2.11.1/index.html#scala.array$. Parametrit n1 ja n2 (ensimmäinen parametrilista) sinun tapauksessasi ovat samoja ja niissä sinun tulee välittää ruutujen määrä. Toisessa parametrilistassa (yllä "elem: => T") määrittelet, minkä arvon taulukon alkiot saavat. Me haluamme asettaa jokaiselle alkiolle alkuarvoksi tyhjän merkkijonon, jolloin pelkkä """" riittää. Sijoita fill-metodin kutsun jälkeen ensimmäisien sulkujen sisään taulukon kokoa kuvaavat kaksi arvoa ja toisien sulkujen sisään taulukon alkioiden alkuarvoa kuvaava arvo. Tarkista vielä, mitkä muuttujista voivat olla val-muuttujia? Entä minkä muuttujien on oltava varmuuttujia? Vaikein osuus tässä osatehtävässä on nyt selätetty, siirrytään metodien kimppuun! Sekä setup- että draw-metodi kutsuvat ainoastaan Processing- ja TicTacToeHelper-kirjastoissa määriteltyjä metodeita. Tutustu viimeistään nyt tarkemmn TicTacToeHelperin tarjoamiin metodeihin. Löydät dokumentaation
projektin doc-kansiosta. setup-metodi Koska draw-metodikutsut alkavat välittömästi setup-metodin suorituksen jälkeen, jää metodin runko lyhyeksi. Määritämme tässä ainoastaan peli-ikkunan koon, minkä avuksi olet luonut oliolle muuttujan. Tässä voisimme tehdä myös muita aloitustoimintoja, mutta mikä tärkeintä koon asettaminen tulee tapahtua aina ensimmäisenä (kuten kuvailtu size-metodin dokumentaatiossa). draw-metodi Metodin vastuulla on piirtää ruudukko ja siihen pelimerkit oikeille paikoilleen. Aluksi asetetaan ikkunalle taustaväri (kts. miksi esimerkiksi draw:n dokumentaatio). Tämän jälkeen kutsumme TicTacToeHelperin tarjoamia ruudukonpiirtometodia ja kuvien uudelleenpiirtometodia. Voit valita ruudukolle haluamasi värin stroke-metodilla ja luonnollisesti valita myös haluamasi taustavärin pelillesi. Metodin perustoiminnallisuus on nyt toteutettu, mutta haluamme, ettei metodia kutsuta turhaan kun peli on päättynyt. Laadi siis metodiin tarkistus if-else-valintarakenteen avulla hyödyntäen äsken luomaasi totuusarvomuuttujaa, joka säilöö tiedon pelin päättymisestä. Mikäli peli on päättynyt kutsutaan noloop-metodia, minkä jälkeen draw-metodia ei enää kutsuta, muulloin suoritetaan yllä kuvaillut komennot. Tässä vaiheessa olet edellisten lisäksi: Luonut muuttujat o ruudun koon o ruudukon koon o ruutujen määrän säilömiseen. Ladannut pelimerkkien kuvat käyttöösi. Alustanut muuttujan vuoron vaihtumiseen. Varannut totuusarvomuuttujan pelin päättymisen seuraamiseen. Luonut taulukon, jonka jokaiselle solulle asettanut arvoksi tyhjän merkkijonon. Toteuttanut setup-metodin, joka asettaa pelille koon. Toteuttanut draw-metodin, joka päivittää ikkunasi sisältöä pelin aikana. 4. Osatehtävä - Apumetodit vuoronhallintaan Ennen kuin siirrytään tarkastelemaan vuorovaikutusta peli-ikkunan kanssa luodaan pelissä tarvittavat apumetodit. TicTacToe-olion apumetodit huolehtivat vuoron vaihtamisesta (changeturn) ja vuorossa olevan pelaajan merkin palauttamisesta merkkijonona (getturn). getturn-metodi
Tarvitset metodin toteuttamiseen yksinkertaisen if-else-valintarakenteen. Koska olet tallentanut vuorossa olevan pelaajan (tai oikeastaan pelimerkin) tiedon PImage-tyyppiseen muuttujaan, voit tarkistaa helposti kumpaa pelimerkkiä se vastaa. Metodi palauttaa pelimerkkiä vastaavan merkkijonon, eli joko "O" (iso o-kirjain) tai "X" (iso x-kirjain). changeturn-metodi Vuoron vaihdosta huolehtiva metodi ei ole edellistä hankalampi. Jälleen valintarakenteen avulla voit tarkistaa kumpi pelaaja on vuorossa ja asettaa toisen pelaajan vuoroon. Metodi ei palauta mitään vaan ainoastaan tallentaa muuttuneen pelivuoron. Tässä vaiheessa olet edellisten lisäksi: Luonut pohjan pelilaudan tilanteen seuraamiseen. Luonut mekanismin vuoron vaihtamiseen. 5. Osatehtävä - mouseclicked-metodi Tässä osatehtävässä luodaan toteutus mouseclickedmetodille, joka mahdollistaa pelaajan hiirenklikkauksiin reagoimisen. Saat hiiren koordinaatit Processingin mousex- ja mousey-muuttujista. Jos kokeilet ajaa ohjelmasi ja mouseclicked-metodissa tulostat nämä muuttujat huomaat, että x-koordinaatti kasvaa vasemmasta reunasta oikealle ja y-koordinaatti ylhäältä alas. Helpointa on noudattaa vastaavaa logiikkaa myös pelaajien siirrot säilövässä tietorakenteessamme (kts. kuva oikealla). Metodin toteutus onkin kuvailtu jo dokumentaatiossa, mutta tässä vielä yksinkertaisesti sama avattuna. Ikkunan ja taulukon koordinaatistot. Tallenna aluksi x- ja y-koordinaatit, joita käytät tallentaessasi pelaajan pelimerkin taulukkoon. Huomaa, että ikkunan koordinaatit eivät vastaa taulukon solujen indeksejä, mutta koska olet tallentanut ruudun koon muistiin saat helposti laskettua oikean arvon indekseille. Mikäli pelaajan klikkaus osui pelialueelle, peli ei ole loppunut ja kyseinen paikka taulukossa ei ole vielä täytetty, tehdään seuraavat asiat: Piirretään pelaajan pelimerkki oikealle kohdalle ruudukossa, käytä Processingin image-metodia. Tallennetaan taulukkoon oikean alkion tilalle pelaajan pelimerkki merkkijonona, muista käyttää juuri luomaasi apumetodia.
Tarkista loppuiko peli TicTacToeHelperin tarjoaman metodin avulla. Mikäli peli loppui, aseta ruudulle ilmoitus pelin loppumisesta, joka sisältää tiedon siitä kumpi pelaaja voitti. Muista käsitellä myös tilanne, missä peli on päättynyt tasapeliin. Processingin text-metodi on tässä kohdassa hyödyllinen. Muista myös muuttaa pelin päättymisestä kertovan totuusarvotyyppisen muuttujan arvo. (Koska olet aikaisemmin laatinut draw-metodiisi tarkistuksen pelin päättymisestä tämän muuttujan avulla, ja kutsut tarvittaessa noloop-metodia, ruutu ei enää päivity seuraavalla draw-metodin kutsulla ja asettamasi teksti jää näkyviin.) Lopuksi vaihda pelivuoroa äsken luomallasi apumetodilla. Ristinollasi pitäisi olla nyt pelikunnossa! Tässä vaiheessa olet edellisten lisäksi: Tehnyt metodin, joka reagoi pelaajan klikkauksiin kun ne osuvat pelialueella tyhjään ruutuun... ja joka piirtää oikean pelimerkin oikealle kohdalle... ja joka tarkastaa onko peli loppunut, sekä ilmoittaa tästä pelaajalle... ja joka lopuksi vaihtaa vuoroa, jotta peliä voidaan pelata kahdestaan. Lopuksi Testaa vielä lopuksi kaikkien tekemiesi luokkien toiminta ja varmista, että kaikki toimii toivomallasi tavalla. Viimeistään tässä vaiheessa kommentoi koodisi ne pätkät, jotka saattavat olla vaikeaselkoisia. Arvostelu Tehtävä arvostellaan asteikolla 0-5 pistettä. Lisäominaisuuksista on mahdollista saada korkeintaan 1 lisäpiste. Palautus Pakkaa projektisi zip-paketiksi (Export -> Archive File ) ja anna sille nimeksi opiskelijanumero_tictactoe.zip. Palauta toteutuksesi mycoursesissa.