Tehtävät viikolle 5

Pakolliset tehtävät on merkitty harmaalla taustavärillä. Pakollisuus tarkoittaa, että kyseiset tehtävät ovat erityisen oleellisia ja niiden tekeminen on hyvin suositeltavaa. Jos joskus jokin "pakollinen" tehtävä jää tekemättä, kurssi ei kuitenkaan kaadu siihen.

Vaalikone

Eduskuntavaalit ovat jo ovella ja nukkuvien puolue on huolestuttavan suuri. Tässä tehtävässä luodaan vaalikone, joka äänestäjiä löytämään itselleen sopivan ehdokkaan.

Alustava sovelluslogiikka

Ohjelmoi luokka EhdokasValitsin, jolla on metodi public String annaEhdokas(). Ehdokkaiden valintaan saat käyttää mitä menetelmää tahansa, esim. arpomalla nimen luokan sisällä olevalta ehdokaslistalta. Luokkaa voisi käyttää esimerkiksi seuraavalla tavalla:

System.out.println(new EhdokasValitsin().annaEhdokas());

Ohjelma voisi tulostaa esimerkiksi:

Aku Ankka

Huomaa, että sinun ei ole välttämätöntä määritellä luokallesi konstruktoria. Yleisesti ottaen konstruktorin voi jättää luomatta, jos luokan ilmentymät ovat sellaisenaan valmiita käyttöön.

Käyttöliittymän runko

Tee luokka Vaalikone periyttämällä se Swingin JFrame-luokasta. Luotaessa luokan ilmentymä, aukeaa ohjelman käyttäjälle tyhjä ikkuna, jonka nimi on "Vaalikone" (näkyy yläpalkissa). Tutustu JFramen API-kuvaukseen, jotta saat ohjelman nimen liitettyä ikkunaan. Ota mallia myös materiaalin luvusta 20.2.

Käyttöliittymän käynnistäväksi pääohjelmaksi riittää

public static void main(String[] args) {
  new Vaalikone();
}

Ohjelmasi tulisi näyttää suunnilleen seuraavalta:

Kuva vaalikoneesta

Käyttöliittymän laajennus

Lisää käyttöliittymään (Vaalikone-luokkaan) yksi JLabel-tekstielementti ja JButton-nappula, jossa lukee "Anna ehdokas". Tekstielementissä oletusarvoisesti näytettäväksi tekstiksi voit asettaa mitä tahdot (laita kuitenkin jotain, jotta havaitset elementin).

Ohjelmasi tulisi näyttää suunnilleen seuraavalta.

Kuva vaalikoneesta

JFrame-luokan metodi pack() pakkaa ikkunan automaattisesti sopivan kokoiseksi (oikeastaan tämä metodi tulee aina luokasta java.awt.Window asti).

Toimintalogiikan lisääminen ja tapahtumien kuuntelu

Lisää Vaalikone-luokkaan tehtävän ensimmäisessä osassa tekemäsi EhdokasValitsin-luokan ilmentymä. Tee ohjelmasta sellainen, että nappulaa painamalla tekstikenttään näytetään ehdokasvalitsimen annaEhdokas()-metodin palauttama merkkijono. Tämä onnistuu laittamalla Vaalikone toteuttamaan ActionListener-rajapinta sopivalla tavalla.

Ohjelma voisi näyttää seuraavalta kun nappia painellaan.

Nopeustesti

Tässä tehtäväsarjassa toteutetaan seuraavan tapainen peli:

Käyttöliittymä

Pelin pääluokka on Nopeustesti, jonka koodi on seuraava:

import java.awt.*;
import javax.swing.*;

public class Nopeustesti {
    public static void main(String[] args) {
        JFrame ikkuna = new JFrame();
        ikkuna.setSize(440, 240);
        ikkuna.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
        ikkuna.setVisible(true);
        Peli peli = new Peli();
        peli.setBackground(Color.BLACK);
        Container sisalto = ikkuna.getContentPane();
        sisalto.add(peli);
    }
}

Varsinainen peli on luokassa Peli, joka toteuttaa luokan JPanel. Luokan pohja on seuraava:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class Peli extends JPanel {

    public void paint(Graphics g) {
        super.paint(g);
	// tähän tulee piirtokoodia
    }

}

Lisää metodiin paint koodi, joka piirtää testiksi pelin käyttöliittymän. Tähän kuuluvat neljä tummanharmaata ympyrää sekä tekstit pistemäärää ja näppäinohjetta varten. Käyttöliittymän tulisi näyttää suunnilleen tältä:

Tällä komennolla voit piirtää tekstiä:

g.drawString("F2: Uusi peli", 100, 100);

Näillä komennoilla voit vaihtaa piirtoväriä ennen piirtämistä:

g.setColor(Color.DARK_GRAY);
g.setColor(Color.WHITE);

Sytytetty nappi

Pelin aikana yksi neljästä napista on sytytetty. Valitse sytytetyille napeille sopivat kirkkaat värit ja lisää peliin metodi, joka sytyttää satunnaisen napin. Lisää pelin alkuun komento, joka sytyttää satunnaisen napin.

Seuraavassa kuvassa toinen nappi on sytytetty:

Näppäimistön käsittely

Liitä peliin näppäimistön käsittely materiaalin ohjeen mukaisesti. Muista lisätä metodin määrittelyyn maininta rajapinnan KeyListener toteuttamisesta. Myös pääohjelman main-metodin loppuun tarvitaan seuraava lisäys:

ikkuna.addKeyListener(peli);

Lisää metodiin keyPressed testikoodia, jolla voit tutkia, mitä näppäimiä käyttäjä painaa. Saat näppäinkoodin selville seuraavalla komennolla:

int koodi = e.getKeyCode();

Voit vaikkapa tulostaa näppäinkoodin tutulla System.out.println-metodilla. Voit myös testata näppäimistön käsittelyä sytyttämällä satunnaisen napin, kun käyttäjä painaa jotain näppäintä.

Ajastin

Peliin tarvitaan ajastin, joka sytyttää uuden satunnaisen napin tietyn ajan kuluttua.

Voit tehdä ajastimesta seuraavan luokan:

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Timer;

public class Ajastin extends Timer implements ActionListener {

    private Peli peli;

    public Ajastin(Peli peli, int aika) {
        super(aika, null);
        this.peli = peli;
        this.addActionListener(this);
        this.setRepeats(false);
    }

    public void actionPerformed(ActionEvent e) {
        peli.aikaOnKulunut();
    }
}

Ajastin käynnistetään näin Peli-luokassa:

    Ajastin ajastin = new Ajastin(this, 1000);
    ajastin.start();

Ajastimelle annetaan odotettava aika millisekunneissa (1000 millisekuntia on 1 sekunti). Kun aika on kulunut loppuun, ajastin kutsuu luokassa Peli olevaa metodia aikaOnKulunut.

Muuta peliä niin, että sytytetty nappi vaihtuu sekunnin välein ajastimen avulla. Toimiihan arvontasi niin, että samaa nappia ei sytytetä kaksi kertaa peräkkäin (jolloin pelaaja ei huomaisi vaihtumista)?

Huom! Älä kutsu ajastinta paint-metodissa!

Pelaajan valinnat

Muuta peliä niin, että se kirjaa muistiin pelaajan painaman napin. Sitten kun ajastimen aika on päättynyt, pelin tulisi tarkistaa, onko pelaaja painanut oikeasta napista. Jos nappi on oikea, seuraava nappi syttyy, jos nappi on väärä, peli päättyy.

Pelin vaikeutus

Peli olisi liian helppo, jos nappi vaihtuisi aina sekunnin välein. Muuta peliä niin, että vaihtumistahti kiihtyy jatkuvasti, kunnes saavutetaan tietty raja. Hyvä ratkaisu voisi olla aloittaa 1000 ms:stä ja pienentää vaihtumisväliä 10 ms:llä, kunnes vaihtumisväli on 200 ms.

Viimeistely

Lisää peliin vielä pisteenlasku ja varmista, että pelin aloittaminen (F2) ja lopettaminen (Esc) toimivat oikealla tavalla. Saat ohjelman lopettamaan kutsumalla metodia System.exit(0); parametrin arvo voi kutsussa olla mikä tahansa, yleensä käytetään arvoa 0.

Peliin sopisi vielä jonkinlainen graafinen efekti kohtaan, jossa pelaaja tekee virheen ja peli päättyy. Luonteva ratkaisu on vilkuttaa nappeja sopivalla tavalla pelin päättymisen merkiksi.

Ennätyslista

Peli kaipaa vielä ennätyslistaa, jonka toteutukseksi sopii viime viikolla tehty luokka Ennatyslista. Luokkaan olisi ehkä hyvä lisätä metodi, jolta voi kysyä, pääseekö tulos listalle.

Seuraavalla koodilla voit näyttää viestejä käyttäjälle:

JOptionPane.showMessageDialog(this, "Ennätyslista:\nAAA 100\nBBB 50\nCCC 20");

Seuraavalla koodilla voit kysyä käyttäjän nimeä:

String nimi = JOptionPane.showInputDialog("Anna nimesi:");

Pino ja automaattiset testit

Pino on kaikille ihmisille tuttu asia. Esim. Unicafessa lautaset ovat pinossa. Pinon päältä voi ottaa lautasen ja pinon päälle voi lisätä lautasia. On myös helppo selvittää onko pinossa vielä lautasia jäljellä.

Pino on myös ohjelmoinnissa usein käytetty aputietorakenne. Toteutetaan seuraavassa luokka OmaPino. Pinon talletetaan lukuja. Pinoon mahtuvien lukujen määrä annetaan pinon konstruktorissa. Pino toteuttaa seuraavan rajapinnan:

public interface Pino {
    boolean tyhja();
    boolean taynna();
    void pinoon(int luku);
    int pinosta();
    int huipulla();
    int lukuja();
}

Metodien on tarkoitus toimia seuraavasti:

Toteutamme pinon hieman aiempaa poikkeavasti. Emme tee alussa pääohjelmaa ollenkaan, vaan valistuneiden atk-ammattilaisten tapaan käytämme pääohjelman sijasta automatisoituja JUnit-testejä. Periaate ei ole vaikea kun perusteet selviävät. Aloitetaan.

Tutustuminen JUnitiin

Käytämmä tällä kertaa valmiina olevaa projektia. Lataa zip:iksi pakattu projekti täältä. Pura pakkaus sopivaan kohtaan ja avaa projekti NetBeansilla. Kysy pajaohjaajan tai vieressä istuvan apua jos operaatio ei onnistu.

Projekti näyttää seuraavalta:

Sovelluksen koodi (Main, OmaPino ja rajapinta Pino) ovat tavalliseen tapaan Source package:n alla. Uutta on Test package:sta löytyvä testikoodi. Tutkitaan sitä tarkemmin.

import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;

public class PinoTest {

    Pino pino;

    @Before
    public void setUp() {
        pino = new OmaPino(3);
    }

    @Test
    public void alussaTyhja() {
        assertTrue(pino.tyhja());
    }

    @Test
    public void lisayksenJalkeenEiTyhja() {
        pino.pinoon(5);
        assertFalse(pino.tyhja());
    }

    @Test
    public void lisattyAlkioTuleePinosta() {
        pino.pinoon(3);
        assertEquals(3, pino.pinosta());
    }

    @Test
    public void lisayksenJaPoistonJalkeenPinoOnTaasTyhja() {
        pino.pinoon(3);
        pino.pinosta();
        assertTrue(pino.tyhja());
    }

    @Test
    public void lisatytAlkiotTulevatPinostaOikeassaJarjestyksessa() {
        pino.pinoon(1);
        pino.pinoon(2);
        pino.pinoon(3);

        assertEquals( 3, pino.pinosta() );
        assertEquals( 2, pino.pinosta() );
        assertEquals( 1, pino.pinosta() );
    }

    @Test
    public void tyhjennyksenJalkeenPinoonLaitettuAlkioTuleeUlosPinosta() {
        pino.pinoon(1);
        pino.pinosta();

        pino.pinoon(5);

        assertEquals( 5, pino.pinosta() );
    }

    // ...
}

Testikoodi koostuu pienistä metodeista joiden edessä on merkintä @Test. Tämän lisäksi löytyy metodi setUp joka ainoastaan luo pino-olion.

setUp on metodi joka suoritetaan ennen jokaista testiä. Jokainen testi siis alkaa tilanteesta, jossa on luotu uusi tyhjä pino. Jokainen yksittäinen @Test-merkitty metodi on oma testinsä. Jokainen testi testaa yhtä pientä osaa pinon toiminnallisuudesta. Testit suoritetaan toisistaan täysin riippumattomina, eli jokainen testi alkaa "puhtaaltä pöydältä", setUp:in alustamasta tilanteesta.

Yksittäiset testit noudattavat samaa kaavaa. Ensin saadaan aikaan tilanne josta varsinainen testattava asia alkaa. Sitten tehdään testattava toimenpide, esim. metodikutsu. Lopuksi tarkastetaan onko tilanne odotetun kaltainen. Jokainen testi on nimetty mahdollisimman kuvaavalla tavalla. Esim:

    @Test
    public void lisayksenJaPoistonJalkeenPinoOnTaasTyhja() {
        pino.pinoon(3);
        pino.pinosta();
        assertTrue(pino.tyhja());
    }

Testataan toimiiko metodi tyhja() jos pino on tyhjennetty. Eli ensin laitetaan pinoon luku ja tyhjennetään pino kutsumalla metodia pinosta(). Eli on saatettu aikaan tilanne jossa pino on tyhjennetty. Viimeisellä rivillä testataan, assertEquals()-testausmetodilla, että pinon metodi tyhja() palauttaa true. Jos metodin palauttama arvo ei ole odotettu, ilmoittaa NetBeans tilanteesta kuten yo. kuvasta näkyy.

Jokainen testi päättyy jonkun assert-metodin kutsuun. Esim. assertEquals():illa voidaan varmistaa onko metodin palauttama luku tai merkkijono haluttu.

Suorita nyt testit joko painamalla alt ja F6 tai valitsemalla Run -> Test project.

Aloitetaan testien toimintaansaattaminen

Toteutetaan OmaPino siten, että pinottavat luvut talletetaan pinon oliomuuttujana olevaan taulukkoon.

Pinon sisällä olevaa taulukkoa kannattaa käyttää seuraavan kuvasarjan osoittamalla tavalla:

 p = new OmaPino(4);

  0   1   2   3
-----------------
|   |   |   |   |
-----------------
alkioita: 0

p.pinoon(5)

  0   1   2   3
-----------------
| 5 |   |   |   |
-----------------
alkiota: 1

p.pinoon(3)

  0   1   2   3
-----------------
| 5 | 3 |   |   |
-----------------
alkiota: 2

p.pinoon(7)

  0   1   2   3
-----------------
| 5 | 3 | 7 |   |
-----------------
alkiota: 3

p.pinosta()

  0   1   2   3
-----------------
| 5 | 3 |   |   |
-----------------
alkiota: 2

Eli muistetaan kuinka monta alkiota pinossa on. Uusi alkio laitetaan jo pinossa olevien perään. Alkion poisto aiheuttaa sen, että taulukon viimeinen käytössä ollut paikka vapautuu ja alkiomäärän muistavan muuttujan arvo pienenee.

Tehdään pinoa vähän kerrallaan siten, että lopulta kaikki testit toimivat.

Aloitetaan hyvin varovasti. Laita ensin ensimmäinen testi alussaTyhja toimimaan. Älä tee mitään kovin monimutkaista, "quick and dirty"-ratkaisu kelpaa näin alkuun. Kun testi menee läpi (eli näyttää vihreää), siirry seuraavaan kohtaan.

metodi taysi()

Jatka toteutusta siihen pisteesee, että ensimmäiset 2 testiä menevät läpi.

listty tulee ulos pinosta

Laajenna toteutustasi niin, että kolmas ja neljäs testi lisattyTuleeUlosPinosta ja lisayksenJaPoistonJalkeenPinoOnTaasTyhja toimivat.

kaikki testit toimimaan

Eli laajenna toteutustasi siten, että kaikki testit toimivat, eli tuloksena on jokaisen ohjelmoijan unelma "green bar".

omia testejä

Tee testit metodille lukuja(). Mieti mitä kaikkea pitää testata, että testit ovat kattavat eli kaikki tärkeät sovellustilanteet tulee testatuksi. Kirjoita jokaiselle tilanteelle oma testi.

Toteuta metodi.

lisää omia testejä

Tee testit metodille huipulla(). Mieti mitä kaikkea pitää testata, että testit ovat kattavat eli kaikki tärkeät sovellustilanteet tulee testatuksi. Kirjoita jokaiselle tilanteelle oma testi.

Toteuta metodi.

ArrayList:iä käyttävä pino

Tee luokka ArrayListPino, joka toteuttaa rajapinnan Pino, mutta käyttää sisäisesti taulukon sijasta ArrayList:iä.

Koska ArrayList:illä ei ole enimmäiskokoa, on myös ArrayListPino:n koko rajoittamaton.

Kokeile että testit menevät läpi, jos setUp:issa luodaan OmaPino:n sijasta ArrayListPino. Koska ArrayListPino ei täyty koskaan, kommentoi testi public void kunPinoTaynnaIlmoitetaanSenOlevanTaysi() pois.

Järkevämpää olisi toki luoda oma testi ArrayListPino:a varten. Voit tehdä näin valitsemalla New -> other -> JUnit test -> versio 4.

Laskutoimitus

Tavoitteenamme on tehdä ohjelma jonka avulla voidaan laskea käyttäjän antaman laskutoimituksen arvo. Laskutoimitus voi sisältää nollaa suurempia kokonaislukuja, operaatioita + ja * sekä sulkuja. Laskutoimitus voi siis olla esim. 1 + 2 tai monimutkaisempi, kuten (2 + 3) * (4 + 6) tai sisältää myös sisäkkäisiä sulkuja.

Kun laskutoimitukset kirjoitetaan normaaliin tapaan 1 + 2 eli ns. infixmuodossa missä operaattori on lukujen keskellä, ei laskutoimituksen tekeminen ole täysin suoraviivaista sillä on huomioitava esim. eri operaattorien laskujärjestys.

Jos laskutoimitus muutetaan ensin ns. postfix-muotoon, eli muotoon missä operaattori tulee operandien (eli operoitavien arvojen) jälkeen, on laskutoimituksen teko suoraviivaista.

Esim. 1 + 2 on postfix-muodossa 1 2 + ja (2 + 3) * (4 + 6) on postfix-muodossa 2 3 + 4 6 + *. Emme nyt opettele itse tekemään infix-muodosta postfix-muotoa, vaan käytämme valmista muunninta.

Muunnin löytyy jar-pakettina täältä. Lataa muunnin koneellesi sopivaan hakemistoon, esim. tehtävän projektin alle. Jar-paketit ovat Javassa yleinen tapa valmiiden ohjelmien tai kirjastorutiinien levittämiseen.

Infixmuodosta postfixmuotoon

Liitä muunnin projektiisi klikkaamalla vasemmalta projektinäkymästä libraries:in kohdalla oikeaa hiiren nappia ja valitsemalla add jar. Jos operaatio ei meinaa onnistua, kysy ohjaajalta tai vieressä istuvalta neuvoa.

Muunninta käytetään seuraavaan tyyliin (huomaa import):

import java.util.Scanner;
import muunnin.InfixToPostfix;

public class Main {
    public static Scanner lukija = new Scanner(System.in);
    public static void main(String[] args) {
        System.out.print("lasku: ");
        String lauseke = lukija.nextLine();
        InfixToPostfix muuntaja = new InfixToPostfix(lauseke);
        for (String merkki : muuntaja.muunna() ) {
            System.out.print( merkki + " " );
        }

}

Muuntimen konstruktori ottaa laskun merkkijonomuotoisena. Metodi muunna palauttaa listan merkkijonoja, jotka muodostavat laskun postfix-muodon. Esim. jos syöte on "1+2", palauttaa metodi listan jonka sisältönä ovat merkkijonot "1", "2", "+".

Tee luokka Laskin, ja sille metodi public static void laske(String lasku) joka saa parametriksi laskun merkkijonomuodossa. Aluksi metodi ei vielä laske laskun tulosta vaan pelkästään tulostaa laskun infix-muodossa käyttäen InfixToPostfix-olioa kuten yllä olevassa esimerkissä.

HUOM: tee tämä ohjelma pinon kanssa samaan projektiin sillä pinoa tarvitaan pian.

Testaa luokkaa seuraavan pääohjelman avulla:

    public static void main(String[] args) {
        while( true ) {
            System.out.print("lasku: ");
            String lasku = lukija.nextLine();
            if ( lasku.equals("") ) break;
            Laskin.laske( lasku );
        }
    }

Ohjelman tulisi toimia seuraavaan tyyliin:

lasku: 1+2
1 2 + 
lasku: 1*2+3
1 2 * 3 + 
lasku: (1+2)*3
1 2 + 3 * 

Laskimen testit

Äskeinen versio metodista laske on vasta välivaihe. Kun olet varmistunut että metodi tulostaa laskun oikein postfix-muodossa, poista tulostus ja muuta metodin tyyppiä siten, että se palauttaa kokonaisluvun. Laita aluksi metodin loppuun return -1;.

Ennenkuin ohjelmoimme luokan, teemme sille joukon testejä.

Testejä varten tehdään oma testiluokka. Valitse projektin kohdalta new -> Junit test tai new -> other -> junit -> JUnit test. Anna nimeksi esim. LaskinTest

Testiluokassa on hieman ylimääräistä tavaraa. Voit korjava sen siällön seuraavalla:

import org.junit.Test;
import static org.junit.Assert.*;

public class LaskinTest {
    @Test
    public void yksiPlusKaksi(){
        assertEquals( 3, Laskin.laske("1+2") );
    }
}

Mukana on siis valmiina yksi testi, joka tarkastaa, että 1 + 2 todellakin on 3.

Tee testejä lisää. Pyri tekemään testeistä sellaisia, että ne testaavat olellisia, asioita, esim. laskujärjestystä, sulutuksen toimivuutta. Sisäkkäisiä sulkuja.

Laskin toimintaan

Kun olet saanut aikaan kattavan joukon testejä, on aika ohjelmoida metodi valmiiksi.

Periaate laskutoimituksen suorittamiseen on seuraava:

Eli toteuta algoritmi metodiisi, käytä edellisen tehtävän pinoa. Muista, että merkkijonomuodossa olevan luvun saa muutettua int:iksi metodilla Integer.parseInt( merkkijono ).

Varmista, että kaikki testit menevät läpi.

Muuta vielä pääohjelmasi seuraavaan muotoon, ja testaa ohjelmaasi manuaalisesti.

    public static void main(String[] args) {
        while( true ) {
            System.out.print("lasku: ");
            String lasku = lukija.nextLine();
            if ( lasku.equals("") ) break;
            System.out.println( Laskin.laske( lasku ) );
        }
    }

Virheiden käsittely

Ohjelma kaatuu tylysti poikkeukseen, jos syötteen muoto on virheellinen. Testaa ohjelmaa erilaisilla virheellisillä syötteillä ja kokeile mitä poikkeuksia on mahdollsita saada aikaan.

Hoida poikkeukset siten, että laske-metodi sieppaa kaikki aiheutuneet poikkeukset ja heittää poikkeustilanteessa kutsujalleen poikkeuksen IllegalArgumentException. Varaudu poikkeukseen pääohjelmassa ja tulosta poikkeuksen tullessa käyttäjälle virheilmoitus.

MunLukija

Javan Scanner-luokka on melko hyvä väline syötteen lukemiseen. Toisinaan olisi kuitenkin tarvetta hieman erilaiselle toiminnallisuudelle. Toteutetaan nyt oma versio syötteenlukijasta. Oma versio, MunLukija käyttää käytä syötteen lukemiseen Scannerin nextLine()-metodia, mutta tarjoaa kutsujalle hieman mukavamman käyttörajapinnan kun Javan Scanner.

Merkkijonon lukeminen

Tee projekti normaaliin tapaan.

Sijoitamme MunLukija-luokan omaan pakkaukseen. Luo projektiin pakkaus valitsemalla vasemmalta projektinäkymästä oikealla napilla new -> java package, anna pakkaukselle sopiva nimi, esim. lukija. Pakkausten nimet on tapana kirjoittaa pienellä alkukirjaimella.. Luo luokka MunLukija pakkauksen sisään.

Varmista, että tulos näyttää seuraavalta:

Eli ohjelmassa on nyt "oletuspakkauksen" (eli olemattoman pakkauksen) lisäksi pakkaus lukija. Main sijaitsee oletuspakkauksessa ja MunLukija pakkauksessa lukija. Huomaa, että MunLukija:n ylimpänä rivinä ilmoitenaan pakkaus jossa koodi sijaitsee. Näin täytyy tehdä kaikille muualla kuin oletuspakkauksessa sijaitseville luokille. NetBeans lisää rivin automaattisesti.

Tehdään munlukijalle metodi public static String lueRivi(). Käytetään testaamiseen seuraavaa pääohjelmaa:

public class Main {
    public static void main(String[] args) {
        String luettiin = lukija.MunLukija.lueRivi();
        System.out.println( "luettiin: "+ luettiin );
    }
}

Koska luokka on pakkauksessa, viitataan luokan nimeen muodossa pakkaus.Luokka. Tämä on hiukan kömpelö tapa, joten importoidaan pakkaus, tällöin pakkauksessa oleva koodi on käytössä suoraan:

import lukija.MunLukija;

public class Main {
    public static void main(String[] args) {
        String luettiin = MunLukija.lueRivi();
        System.out.println( "luettiin: "+ luettiin );
    }
}

Edellä importoitiin lukija-pakkauksesta ainoastaan luokka MunLukija. Määrittelemällä import lukija.*; olisi importoitu koko pakkauksen sisältö.

Toteuta nyt metodi. Käytä MunLukija:n sisällä Scanneria syötteen lukemiseen.

Lisää merkkijonon lukemista

Tee metodista kuormitettu versio, jossa munLukija tulostaa käyttäjälle parametrina annetun kehotteen:

        String luettiin = MunLukija.lueRivi("kirjoita tekstiä: ");
        System.out.println( "luettiin: "+ luettiin );

Toiminta:

kirjoita tekstiä: Java rules!
luettiin: Java rules!

Epätyhjä syöte

Tee edellisestä metodista hieman poikkeava versio public static String lueEiTyhjaRivi joka ei hyväksy tyhjää syötettä. Jos käyttäjä kuitenkin antaa tyhjän syötteen (pelkkä enter), kysyy metodi syötettä uudelleen.

Testipääohjelma:

        String luettiin = MunLukija.lueEiTyhjaRivi("kirjoita tekstiä (tyhjä ei kelpaa): ");
        System.out.println( "luettiin: "+ luettiin );

Tuloste:

kirjoita tekstiä (tyhjä ei kelpaa): 
kirjoita tekstiä (tyhjä ei kelpaa): 
kirjoita tekstiä (tyhjä ei kelpaa): uskotaan
luettiin: uskotaan

Huom: pyri tekemään MunLukija:sta sellainen, että Scannerin metodia nextLine kutsutaan vain yhdestä kohtaa koodia. Eli jokainen MunLukijan metodi ei kutsu Scannerin nextLineä, kutsu hoidetaan yhdestä paikasta ja MunLukijan metodit kutsuvat sopivasti toisiaan. Esim. lueEiTyhjaRivi tekee työnsä kutsumalla metodia lueRivi.

Luvun lukeminen

Toteuta luokalle metodi public static int lueLuku, joka pyytää käyttäjältä kokonaislukua. Metodi ottaa parametrikseen käyttäjälle näytettävän kysymyksen. Jos käyttäjä syöttää arvon, joka ei ole numero, esitetään kysymys uudestaan kunnes käyttäjä syöttää numeron.

Metodi ei saa heittää poikkeusta minkään muotoisella syötteellä. huomaa, että mekiijonon lukemisen pitää toimia luvun lukemisen jälkeen.

Kaksi vihjettä:

Testaa seuraavalla:

        int luku = MunLukija.lueLuku("syötä kokonaisluku: ");
        System.out.println( "luku oli: " + luku);

        String luettiin = MunLukija.lueEiTyhjaRivi("kirjoita tekstiä (tyhjä ei kelpaa): ");
        System.out.println( "luettiin: "+ luettiin );

Tulos:

syötä kokonaisluku: ads
syötä kokonaisluku: -22,1
syötä kokonaisluku: 321
luku oli: 321
kirjoita tekstiä (tyhjä ei kelpaa): toimii!
luettiin: toimii!

Rajoitetun luvun lukeminen

Tee vielä metodi public static int lueLukuValilta(String viesti, int alku, int loppu) joka hyväksyy syötteen ainoastaan jos se on annetulla välillä.

Tee metodi siten, että se käyttää mahdollisimman paljon MunLukija:n olemassaolevia metodeita!

Testaa että metodisi toimii oikein:

        int luku = MunLukija.lueLukuValilta("syötä kokonaisluku väliltä 3...7: ", 3, 7);
        System.out.println( "luku oli: " + luku);

Tulos:

syötä kokonaisluku väliltä 3...7: 42
syötä kokonaisluku väliltä 3...7: -1
syötä kokonaisluku väliltä 3...7: xyz
syötä kokonaisluku väliltä 3...7: 3
luku oli: 3

jar-paketti

Nyt tehtyä MunLukija olisi mukava käyttää jatkossa myös muissa ohjelmissa. Luokan voi toki copy-pasteta aina uuteen projektiin, mutta parempikin keino on olemassa. MunLukija:sta voi tehdä jar-paketin joka voidaan liittää helposti muihin ohjelmiin.

Luodaan nyt jar-paketti. Kommentoi pääohjelma pois. Paina NetBeansin työkalurivillä olevaa vasaraa. Tulostuksesta selviää jar:in sijainti:


To run this application from the command line without Ant, try:
java -jar "/home/mluukkai/tira/ohja/viikko5/MunLukija/dist/MunLukija.jar"

Voit kopioida tiedoston minne tahansa, tai jättää sen paikoilleen. Tärkeintä että tiedät uusia ohjelmia tehdessäsi, mistä se löytyy.

Tee nyt uusi projekti ja liitä jar projektin käyttöön klikkaamalla vasemmalta projektinäkymästä libraries:in kohdalla oikeaa hiiren nappia ja valitsemalla add jar. Kysy ohjaajalta neuvoa jos et löydä jar:ia.

MunLukijan käyttö on helppoa, lisää import jokaisen munlukijaa käyttävän luokan yläosaan:

import lukija.MunLukija;

public class Main {
    public static void main(String[] args) {
        String rivi = MunLukija.lueEiTyhjaRivi("syötä tekstiä ");
        // ...
    }
}

Tuottavuutesti paranee, tämän jälkeen ei ole enää koskaan tarvetta kitjoittaa public static Scanner lukija = new Scanner(System.in);

Sudoku

Sudokutehtävät ovat välillä ihmiselle hyvin vaikeita, mutta tietokone pystyy ratkaisemaan ne sekunnin murto-osassa. Seuraavaksi tehdään ohjelma, joka ratkaisee sudokutehtävän.

Sudokun ratkaisija

Ohjelmalle annetaan sudokuruudukko, jossa puuttuvien lukujen tilalla on kysymysmerkki. Sitten ohjelma tulostaa ruudukon uudestaan niin, että kysymysmerkit on korvattu oikeilla luvuilla.

Ohjelman toiminnan tulisi näyttää tältä:

Anna sudoku:
8?64??12?
?9???1476
?4??????3
?5??1?2??
2?83?57?1
??4?9??8?
6??????1?
4851???3?
?17??35?2
Ratkaisu:
836457129
592831476
741962853
359718264
268345791
174296385
623574918
485129637
917683542

Suoraviivainen (ja riittävän tehokas) tapa toteuttaa ohjelma on käydä läpi kaikki järkevät mahdollisuudet sijoittaa lukuja kysymysmerkkien kohdalle.

Esimerkiksi jos ohjelma käy ruudukkoa läpi ylhäältä alas ja vasemmalta oikealle, ensimmäisen kysymysmerkin kohdalla tilanne on seuraava:

Niinpä ohjelma haarautuu tutkimaan vaihtoehtoja, joissa kysymysmerkin kohdalle tuleva luku on 3 tai 7:

             8?64??12?
             ?9???1476
             ?4??????3
             ?5??1?2??
             2?83?57?1
             ??4?9??8?
             6??????1?
             4851???3?
             ?17??35?2
            /         \
           /           \                 
          /             \             
         /               \            
8364??12?                 8764??12?
?9???1476                 ?9???1476
?4??????3                 ?4??????3
?5??1?2??                 ?5??1?2??
2?83?57?1                 2?83?57?1
??4?9??8?                 ??4?9??8?
6??????1?                 6??????1?
4851???3?                 4851???3?
?17??35?2                 ?17??35?2

Vastaava haarautuminen tapahtuu kaikissa kohdissa, joissa on kysymysmerkki. Haarautumisen toteuttamiseen soveltuu erittäin hyvin rekursiivinen metodi. Jos ohjelma onnistuu korvaamaan jossain haarassa kaikki kysymysmerkit luvuilla synnyttämättä ristiriitoja ruudukkoon, sudokun ratkaisu on valmis.