Tehtävät viikolle 2

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.

Tavaroita ja laatikkoja

Talletettavia

Muuton yhteydessa tarvitaan muuttolaatikoita. Laatikoihin talletetaan erilaisia esineitä. Kaikkien laatikoihin talletettavien esineiden on toteutettava seuraava rajapinta:

public interface Talletettava {
    double paino();
}

Lisää rajapinta ohjelmaasi. Rajapinta lisätään melkein samalla tavalla kuin luokka, new Java class sijaan valitaan new Java interface.

Tee rajapinnan toteuttavat luokat Kirja ja CDLevy. Kirjan paino vaihtelee ja se annetaan konstruktorin parametrina. Kaikkien CD-levyjen paino sen sijaan on 0.1 kg.

Koska luokat toteuttavat rajapinnan, ne täytyy määritellä seuraavasti:

public class Kirja implements Talletettava {
  //...
}
public class CDLevy implements Talletettava {
  // ... 
}

Luokat voivat toimia esim. seuraavasti:

    public static void main(String[] args) {
        Kirja kirja1 = new Kirja("Fedor Dostojevski", "Rikos ja Rangaistus", 2);
        Kirja kirja2 = new Kirja("Robert Martin", "Clean Code", 1);
        Kirja kirja3 = new Kirja("Kent Beck", "Test Driven Development", 0.5);

        CDLevy cd1 = new CDLevy("Pink Floyd", "Dark Side of the Moon", 1973);
        CDLevy cd2 = new CDLevy("Wigwam", "Nuclear Nightclub", 1975);

        System.out.println(kirja1);
        System.out.println(kirja2);
        System.out.println(kirja3);
        System.out.println(cd1);
        System.out.println(cd2);
    }

Tulostus:

Fedor Dostojevski: Rikos ja Rangaistus
Robert Martin: Clean Code
Kent Beck: Test Driven Development
Pink Floyd: Dark Side of the Moon (1973)
Wigwam: Nuclear Nightclub (1975)

Saat muotoilla tulostuksen haluamallasi tavalla. Kirjoilla ja levyillä voi myös olla muita ominaisuuksia kuin esimerkissä. Tulostuksessa painoa ei ilmoiteta.

Talletettavat listalla

Olisi kätevää jos voisimme sijoittaa mitä tahansa rajapinnan Talletettava toteuttavia esineitä listalle. Ja sehän onnistuu:

        ArrayList<Talletettava> tavarat = new ArrayList<Talletettava>();

        tavarat.add(  new Kirja("Fedor Dostojevski", "Rikos ja Rangaistus", 2) );
        // ...

Eli listalle talletettavien alkioiden tyypiksi on määritelty Talletettava ja listalle voi laittaa minkä tahansa olion joka toteuttaa rajapinnan Talletettava!

Muuta edellisen tehtävän ohjelmasi tähän muotoon. Eli talleta tavarat listalle ja tulosta listan sisältö.

Laatikko

Tee luokka laatikko, jonka sisälle voidaan tallettaa Talletettava-rajapinnan toteuttavia tavaroita. Laatikolla on maksimikapasiteetti, ja jotta laatikko ei hajoaisi, siihen ei saa tallettaa enempää tavaraa kuin kapasiteetin verran.

Seuraavassa esimerkki laatikon käytöstä:

    public static void main(String[] args) {
        Laatikko laatikko = new Laatikko(10);

        laatikko.lisaa(  new Kirja("Fedor Dostojevski", "Rikos ja Rangaistus", 2) ) ;
        laatikko.lisaa( new Kirja("Robert Martin", "Clean Code", 1) );
        laatikko.lisaa( new Kirja("Kent Beck", "Test Driven Development", 0.5) );

        laatikko.lisaa( new CDLevy("Pink Floyd", "Dark Side of the Moon", 1973) );
        laatikko.lisaa( new CDLevy("Wigwam", "Nuclear Nightclub", 1975) );

        System.out.println( laatikko );
    }

Tulostuu

Laatikko: 5 esinettä, paino yhteensä 3.7 kiloa

Laatikon paino

Jos teit laatikon sisälle oliomuuttujan double paino, joka muistaa laatikossa olevien esineiden painon, korvaa se metodilla, joka laskee painon:

public class Laatikko {
    //...

    public double paino() {
        double paino = 0;    
        // laske laatikkoon talletettujen yhteispaino 
        return paino;
    }
}

Kun tarvitset laatikon sisällä painoa esim. uuden tavaran lisäyksen yhteydessä, riittää siis kutsua laatikon painon laskevaa metodia.

Metodi toki voisi palauttaa myös oliomuuttujan arvon. Harjoittelemme tässä kuitenkin tilannetta, jossa oliomuuttujaa ei tarvitse eksplisiittisesti ylläpitää vaan se voidaan tarpeentullen laskea. Seuraavan kohdan jälkeen laatikkoon talletettu painotieto ei kuitenkaan välttämättä enää toimisi. Miksi?

Laatikkokin on talletettava!

Hmm. alussa määritelty rajapinta siis on seuraavanlainen

public interface Talletettava {
    double paino();
}

Rajapinnan toteutus siis edellyttää että luokalla on metodi double paino(). Laatikollehan lisättiin juuri tämä metodi. Laatikosta voidaan siis tehdä talletettava:

public class Laatikko implements Talletettava {
  // ...
}

Laatikot ovat oliota joihin voidaan laittaa Talletettava-rajapinnan toteuttavia olioita. Laatikot toteuttavat itsekin rajapinnan. Eli laatikon sisällä voi olla myös laatikoita!

Kokeile että näin varmasti on, eli tee ohjelmassasi muutama laatikko, laita laatikoihin tavaroita ja laita pienempiä laatikoita isompien laatikoiden sisään.

Kokeile, mitä tapahtuu, jos laitat laatikon itsensä sisälle.

Oliopohjainen tekstiseikkailu

Tehtäväsarjassa tehdään laajennettava tekstiseikkailupeli. Tekstiseikkailu koostuu "kohdista". Jokaisessa kohdassa ruudulle tulee tekstiä, johon käyttäjä vastaa kirjoittamalla jotain. Kohta voisi esimerkiksi sanoa:

Huoneessa on kaksi ovea. Kumman avaat?

1. Vasemmanpuoleisen.
2. Oikeanpuoleisen.
3. Juoksen pakoon.

Tähän käyttäjä voi vastata 1, 2 tai 3, ja vastauksesta riippuu, mihin kohtaan siirrytään seuraavaksi.

Pelissä voi olla hyvinkin erilaisia kohtia, ja yllä olevan kaltainen monivalinta on vain eräs vaihtoehto. Tehdään siis kohdille rajapinta:

public interface Kohta {
    public String teksti();
    public Kohta seuraavaKohta(String vastaus);
}

Peliin tullaan tekemään pääohjelma, joka käsittelee kaikkia kohtia Kohta-rajapinnan kautta. Pääohjelma tulee kysymään metodilta teksti tekstiä, joka käyttäjälle näytetään. Sitten pääohjelma kysyy käyttäjältä vastauksen, ja antaa sen parametrina metodille seuraavaKohta. seuraavaKohta palauttaa vastauksen perusteella seuraavan kohdan, johon pelin on määrä siirtyä. Peli loppuu, kun seuraavaKohta palauttaa null.

Koska pääohjelma tulee käyttämään kohtia vain Kohta-rajapinnan kautta, voidaan peliin lisätä vaikka minkälaisia kohtia pääohjelmaa muuttamatta. Riittää tehdä uusia Kohta-rajapinnan toteuttavia luokkia.

Epäinteraktiiviset kohdat

Yksinkertaisin kohta, joka tekstiseikkailussa voi olla, on ei-interaktiivinen tekstiruutu, josta pääsee etenemään millä tahansa syötteellä. Tee luokka Valivaihe seuraavasti:

Luokkaa voi testata seuraavalla pääohjelmalla:

public static void main(String[] args) {
    Valivaihe vaihe1 = new Valivaihe("Olet ohjelmointipajassa.");
    Valivaihe vaihe2 = new Valivaihe("Olet tyytyväinen, koska sait juuri koodisi toimimaan.");
    Valivaihe vaihe3 = new Valivaihe("Mutta seikkailusi on vasta alkamassa!");
    vaihe1.asetaSeuraava(vaihe2);
    vaihe2.asetaSeuraava(vaihe3);
    
    System.out.println(vaihe1.teksti());
    System.out.println(vaihe1.seuraavaKohta("ok").teksti());
    System.out.println(vaihe1.seuraavaKohta("ok").seuraavaKohta("niin olen").teksti());
    System.out.println(vaihe1.seuraavaKohta("ok").seuraavaKohta("niin olen").seuraavaKohta("jännittävää!"));
}
Olet ohjelmointipajassa.
(jatka painamalla enteriä)
Olet tyytyväinen, koska sait juuri koodisi toimimaan.
(jatka painamalla enteriä)
Mutta seikkailusi on vasta alkamassa!
(jatka painamalla enteriä)
null

Peli

Tee pääohjelmaan metodi private static void pelaaPelia(Kohta kohta). Metodi näyttää annetun kohdan tekstin, pyytää käyttäjältä syötettä ja siirtyy aina seuraavaan kohtaan, kunnes seuraava kohta on null.

Voit käyttää seuraavaa pääohjelmaa:

public static void main(String[] args) {
    Valivaihe vaihe1 = new Valivaihe("Olet ohjelmointipajassa.");
    Valivaihe vaihe2 = new Valivaihe("Olet tyytyväinen, koska sait juuri koodisi toimimaan.");
    Valivaihe vaihe3 = new Valivaihe("Mutta seikkailusi on vasta alkamassa!");
    vaihe1.asetaSeuraava(vaihe2);
    vaihe2.asetaSeuraava(vaihe3);
    
    pelaaPelia(vaihe1);
    
    System.out.println("Peli loppui!");
}

Pelisessio voi näyttää seuraavalta:

Olet ohjelmointipajassa.
(jatka painamalla enteriä)

> tähän voi kirjoittaa mitä tahansa

Olet tyytyväinen, koska sait juuri koodisi toimimaan.
(jatka painamalla enteriä)

> 

Mutta seikkailusi on vasta alkamassa!
(jatka painamalla enteriä)

> 

Peli loppui!

Teemme seuraavaksi vielä pari toteutusta Kohta-rajapinnalle. pelaaPelia-metodin pitäisi toimia sellaisenaan millä tahansa tulevalla Kohta-toteutuksella.

Kysymyksiä ja vastauksia

Tekstiseikkailussa voi olla kysymyksiä, joihin on annettava oikea vastaus ennen kuin pelaaja pääsee eteenpäin. Tee luokka Kysymys seuraavasti:

Luokkaa voi testata seuraavalla pääohjelmalla:
public static void main(String[] args) {
    Kysymys eka = new Kysymys("Minä vuonna Javan ensimmäinen versio julkaistiin?", "1995");
    Valivaihe toka = new Valivaihe("Hyvä!");
    Kysymys kolmas = new Kysymys("Mikä on Libyan pääkaupunki?", "Tripoli");
    eka.asetaSeuraava(toka);
    toka.asetaSeuraava(kolmas);
    
    pelaaPelia(eka);
    
    System.out.println("Peli loppui!");
}
Minä vuonna Javan ensimmäinen versio julkaistiin?

> 2000

Minä vuonna Javan ensimmäinen versio julkaistiin?

> 1995

Hyvä!
(jatka painamalla enteriä)

> 

Mikä on Libyan pääkaupunki?

> emt

Mikä on Libyan pääkaupunki?

> Tripoli

Peli loppui!

Monivalintakysymykset

Tee luokka Monivalinta, seuraavasti:

Vaihtoehtojen esittäminen ja valinta voi näyttää pelisessiossa esimerkiksi seuraavalta:

Kello on 13:37 ja päätät mennä syömään. Minne menet?

1. Exactumiin
2. Chemicumiin

> 1

Monivalinnan seuraavaKohta(String vastaus)-toteutus voi siis olettaa vastauksen olevan jonkin vastausvaihtoehdon järjestysluku. Metodin on palautettava järjestyslukua vastaavan vaihtoehdon seuraava kohta.

Stringistä saat intin metodilla Integer.parseInt. Rivinvaihto stringinä on "\n".

Luokkaa voi testata seuraavalla pääohjelmalla:

public static void main(String[] args) {
    Kohta alku = rakennaLounasseikkailu();
    pelaaPelia(alku);
    System.out.println("Peli loppui!");
}

private static Kohta rakennaLounasseikkailu() {
    Monivalinta lounas = new Monivalinta(
        "Kello on 13:37 ja päätät mennä syömään. Minne menet?"
    );
    Monivalinta chemicum = new Monivalinta(
        "Lounasvaihtoehtosi ovat seuraavat:"
    );
    Valivaihe exactum = new Valivaihe(
        "Exactumista on kaikki loppu, joten menet Chemicumiin :("
    );
    exactum.asetaSeuraava(chemicum);
    
    lounas.lisaaVaihtoehto("Exactumiin", exactum);
    lounas.lisaaVaihtoehto("Chemicumiin", chemicum);
    
    Valivaihe nom = new Valivaihe("Olipas hyvää");
    chemicum.lisaaVaihtoehto("Punajuurikroketteja, ruohosipuli-soijajogurttikastiketta", nom);
    chemicum.lisaaVaihtoehto("Jauhelihakebakot, paprikakastiketta", nom);
    chemicum.lisaaVaihtoehto("Mausteista kalapataa", nom);
    
    return lounas;
}

Tilastot kuntoon

NHL:ssä pidetään pelaajista yllä monenlaisia tilastotietoja. Teemme nyt oman ohjelman NHL-pelaajien tilastojen hallintaan.

Pelaajalistan tulostus

Tee luokka Pelaaja, johon voidaan tallettaa pelaajan nimi, joukkue, pelatut ottelut, maalimäärä ja syöttömäärä. Jos haluat tarkemmat tilastot, mahdollista myös seuraavien tietojen talletus: plusmiinustieto, jäähyminuutit ja laukaukset maalia kohti

Tee kaikille edelläminituille myös getterimetodit, joiden avulla vastaavan arvon saa selville, esim. String getNimi

Talleta seuraavat pelaajat ArrayList:iin ja tulosta listan sisältö:

    public static void main(String[] args) {
        ArrayList<Pelaaja> pelaajat = new ArrayList<Pelaaja>();
        pelaajat.add( new Pelaaja("Alex Ovechkin", "WSH", 71, 28, 46) );
        pelaajat.add( new Pelaaja("Dustin Byfuglien", "ATL", 69, 19, 31) );
        pelaajat.add( new Pelaaja("Phil Kessel", "TOR", 70, 28, 24) );
        pelaajat.add( new Pelaaja("Brendan Mikkelson", "ANA, CGY", 23, 0, 2) );
        pelaajat.add( new Pelaaja("Matti Luukkainen", "SaPKo", 1, 0, 0 ) );

        for (Pelaaja pelaaja : pelaajat) {
            System.out.println( pelaaja );
        }
    }

Pelaajan toString:in muodostaman tulostuksen tulee olla seuraavassa muodossa:

	
Alex Ovechkin WSH 71 28 + 46 = 74
Dustin Byfuglien ATL 69 19 + 31 = 50
Phil Kessel TOR 70 28 + 24 = 52
Brendan Mikkelson ANA, CGY 23 0 + 2 = 2
Matti Luukkainen SaPKo 1 0 + 0 = 0
Ensin siis nimi, sitten joukkue jonka jälkeen ottelut, maalit, plusmerkki, syötöt, yhtäsuuruusmerkki ja kokonaispisteet eli maalien ja syöttöjen summa.

Tulostuksen siistiminen

Siisti tulostustasi, siten että se näyttää seuraavalta:

Alex Ovechkin             WSH             71  28 + 46 = 74
Dustin Byfuglien          ATL             69  19 + 31 = 50
Phil Kessel               TOR             70  28 + 24 = 52
Brendan Mikkelson         ANA, CGY        23   0 +  2 =  2
Matti Luukkainen          SaPKo            1   0 +  0 =  0

Nimen jälkeen joukkueen niimien siis täytyy alkaa samasta kohdasta. Saat tämän aikaan esim. muotoilemalla nimen tulostuksen yhteydessä seuraavasti:

String nimiJaTyhjaa = String.format("%-25s", nimi);

Komento tekee merkkijonon nimiJaTyhjaa joka alkaa merkkijonon nimi sisällöllä ja se jälkeen tulee välilyöntejä niin paljon että merkkijonon pituudeksi tulee 25.

Joukkueen nimi tulee vastaavalla tavalla tulostaa 14:n pituisena merkkijonona.

Myös lukuarvot eli ottelu-, maali-, syöttö- ja pistemäärä tulee muotoilla kunnolla, kaikki kahden mittaisena, eli lukeman 0 sijaan tulee tulostua välilyönti ja nolla. Seuraava komento auttaa tässä:

String maalitMerkkeina = String.format("%2d", maalit);

Pistepörssin tulostus

Lisää luokalle Pelaaja rajapinta Comparable<Pelaaja>, jonka avulla pelaajat voidaan järjestää kokonaispistemäärän mukaiseen järjestykseen. Järjestä pelaajat Collections-luokan avulla ja tulosta pistepörssi:

        Collections.sort(pelaajat);

        System.out.println( "NHL pistepörssi:\n" );
        for (Pelaaja pelaaja : pelaajat) {
            System.out.println( pelaaja );
        }

Tulostuu:

NHL pistepörssi:

Alex Ovechkin             WSH           71  28 + 46 = 74
Phil Kessel               TOR           70  28 + 24 = 52
Dustin Byfuglien          ATL           69  19 + 31 = 50

Ohjeita tähän tehtävään materiaalissa.

Kaikkien pelaajien tiedot

Tilastomme on vielä hieman vajavainen, siinä on vaan muutaman pelaajan tiedot (ja nekin vastaavat 16.3. tilannetta). Kaikkien tietojen syöttäminen käsin olisi kovin vaivalloista. Onneksemme internetistä osoitteesta http://restfulnhl-dev.heroku.com/txt/stats löytyy päivittyvä, koneen luettavaksi tarkoitettu lista pelaajatiedoista.

Huom: kun menet osoitteeseen ensimmäistä kertaa, sivun latautuminen kestää muutaman sekunnin (sivu pyörii virtuaalipalvelimella joka sammutetaan jos sivua ei ole hetkeen käytetty). Sen jälkeen sivu toimii nopeasti.

Datan lukeminen internetistä on helppoa. Kopioi seuraava luokka projektiisi:

import java.net.URL;
import java.util.Scanner;

public class StatReader {
    private String osoite = "http://restfulnhl-dev.heroku.com/txt/stats";
    private Scanner lukija;

    public StatReader() {
        try {
            URL url = new URL(osoite);
            lukija = new Scanner(url.openStream());
        } catch (Exception ex) {
        }
        lukija.nextLine();
    }

    public boolean hasMorePlayers() {
        return lukija.hasNextLine();
    }

    public String nextPlayer() {
        String rivi = lukija.nextLine();
        lukija.nextLine()
        return rivi.trim();
    }
}

StatReader-olio lukee pelaajien tilastotiedot internetistä. Metodilla nextPlayer() saadaan selville yhden pelaajan tiedot. Tietoja on tarkoitus lukea niin kauan kuin pelaajia riittää, tämä voidaan tarkastaa metodilla hasMorePlayers()

Kokeile että ohjelmasi onnistuu tulostamaan StatReader:in hakemat tiedot:

    public static void main(String[] args) {
        StatReader tilasto = new StatReader();

        while ( tilasto.hasMorePlayers() ) {
            String pelaajaRivina = tilasto.nextPlayer();
            System.out.println( pelaajaRivina );
        }
    }

Tulostus on seuraavan muodoinen:

Alex Ovechkin;WSH;71;28;46;74;41;18;325;
Dustin Byfuglien;ATL;69;19;31;50;59;-4;303;
Phil Kessel;TOR;70;28;24;52;20;-20;291;
// ja yli 800:n muun pelaajan tiedot

Tulostuksessa pelaajan tiedot on erotettu toisistaan puolipisteellä. Ensin nimi, sitten joukkue, ottelut, maalit, pisteet, jäähyt, plus-miinustilasto ja laukaukset.

Pelaajaa vastaava merkkijono on siis yksittäinen merkkijono. Saat pilkottua sen osiin split-komennolla seuraavasti:

        while ( tilasto.hasMorePlayers() ) {
            String pelaajaRivina = tilasto.nextPlayer();
            String[] pelaajaOsina = pelaajaRivina.split(";");
            for (int j = 0; j < pelaajaOsina.length; j++) {
                System.out.print(pelaajaOsina[j] + " ");
            }
            System.out.println("");
        }

Kokeile että tämä toimii.

Kaikien pelaajien pistepörssi

Tee kaikista StatParser:in hakemien pelaajien tiedoista Pelaaja-olioita ja lisää ne ArrayListiin. Eli täydennä koodi

    public static void main(String[] args) {
        StatReader tilasto = new StatReader();

        ArrayList<Pelaaja> pelaajat = new ArrayList<Pelaaja>();

        while ( tilasto.hasMorePlayers() ) {
            String pelaajaRivina = tilasto.nextPlayer();
            String[] pelaajaOsina = pelaajaRivina.split(";");
            int ottelut = Integer.parseInt( pelaajaOsina[2] );
            // ...

            pelaajat.add( new Pelaaja( ... ) );
        }

        Collections.sort(pelaajat);

        System.out.println( "NHL pistepörssi:\n" );

        for (Pelaaja pelaaja : pelaajat) {
            System.out.println( pelaaja );
        }
    }

Huom: StatParser:ilta tuleva tieto on String-muodossa, joten numeromuotoisnen esim. ottelut on muutettava int:iksi Integer.parseInt-metodilla.

Maali ja syöttöpörssi

Haluamme tulostaa myös maalintekijäpörssin eli pelaajien tiedot maalimäärän mukaan järjestettynä sekä syöttöpörssin. NHL:n kotisivu tarjoaa tämänkaltaisen toiminnallisuuden, eli selaimessa näytettävä lista on mahdollista saada järjestettyä halutun kriteerin mukaan.

Edellinen tehtävä määritteli pelaajien suuruusjärjestyksen perustuvan kokonaispistemäärään. Luokalla voi olla vain yksi compareTo-metodi, joten joudumme muunlaisia järjestyksiä saadaksemme turvautumaan muihin keinoihin.

Vaihtoehtoiset järjestämistavat toteutetaan erillisten luokkien avulla. Pelaajien vaihtoehtoisten järjestyksen määräävän luokkien tulee toteuttaa Comparator<Pelaaja>-rajapinta. Järjestyksen määräävän luokan olio vertailee kahta parametrina saamaansa pelaajaa. Metodeja on ainoastaan yksi compareTo(Pelaaja p1, Pelaaja p2), jonka tulee palauttaa negatiivinen arvo, jos pelaaja p1 on järjestyksessä ennen pelaajaa p2, positiivinen arvo jos p2 on järjestyksessä ennen p1:stä ja 0 muuten.

Periaatteena on luoda jokaista järjestämistapaa varten oma vertailuluokka, esim. maalipörssin järjestyksen määrittelevä luokka:

import java.util.Comparator;
 
public class Maali implements Comparator<Pelaaja> {
  public int compare(Pelaaja p1, Pelaaja p2) {
    // maalien perusteella tapahtuvan vertailun koodi tänne
  }
}

Järjestäminen tapahtuu edelleen luokan Collections metodin sort avulla. Metodi saa nyt toiseksi parametrikseen järjestyksen määräävän luokan olion:

Maali maalintekijat = new Maali();
Collections.sort(pelaajat, maalintekijat);
System.out.println("NHL parhaat maalintekijät\n");

// tulostetaan maalipörssi

Järjestyksen määrittelevä olio voidaan myös luoda suoraan sort-kutsun yhteydessä:

Collections.sort(pelaajat, new Maali());
System.out.println("NHL parhaat maalintekijät\n");

// tulostetaan maalipörssi

Kun sort-metodi saa järjestyksen määrittelevän olion parametrina, se käyttää olion compareTo()-metodia pelaajia järjestäessään.

Tarkempia ohjeita vertailuluokkien tekemiseen täällä

Tee maali ja syöttöpörssien generoimiseen sopivat sopivat vertailufunktiot ja tulosta kaikki tilastot:

NHL pistepörssi:

Daniel Sedin              VAN             71  38 + 52 = 90
Steven Stamkos            TBL             70  43 + 43 = 86
Henrik Sedin              VAN             71  16 + 68 = 84
// ...

NHL parhaat maalintekijät

Steven Stamkos            TBL             70  43 + 43 = 86
Daniel Sedin              VAN             71  38 + 52 = 90
Corey Perry               ANA             69  36 + 39 = 75
// ...

NHL eniten syöttöjä:

Daniel Sedin              VAN             71  38 + 52 = 90
Steven Stamkos            TBL             70  43 + 43 = 86
Corey Perry               ANA             69  36 + 39 = 75
// ...

Tilastopalvelu

Pääohjelmaan kertynyt koodi on nyt aika eristää omaksi palveluksi. Tee luokka Tilastopalvelu, joka tallettaa hakee pelaajatiedot StatParser:in avulla ja tallettaa tiedot sisäisesti ArrayListille. Palvelu tarjoaa metodit piste-, maali- ja tulospörssin tulostamiseen. Pääohjelmasi tulisi näyttää seuraavalta:

public class NHL {

    public static Scanner lukija = new Scanner(System.in);
    public static void main(String[] args) {
        Tilastopalvelu tilasto = new Tilastopalvelu();
        
        tilasto.tulostaPisteporssi();
        tilasto.tulostaMaaliporssi();
        tilasto.tulostaSyottoporssi();
    }
}

Tilastopalvelu, pelaajan tietojen haku

Tee tilastopalveluun metodi String haePelaajanTiedot(haettava) jonka avulla saadaan selville parametrina annetun pelaajan tiedot.

Käyttöesimerkki:

        String tiedot = tilasto.haePelaajanTiedot("Teemu Selanne");
        System.out.println( tiedot );

Tulostuu:

Teemu Selanne             ANA             60  21 + 42 = 63

Tilastopalvelu, joukkuetilastot

Tee myös metodi String haeJoukkueenTiedot(haettava) jonka avulla saadaan selville parametrina annetun joukkueen pelaajan pistepörssi.

Käyttöesimerkki:

        String tiedot = tilasto.haeJoukkueenTiedot("ANA");
        System.out.println( tiedot);

Tulostuu:

Corey Perry               ANA             69  36 + 39 = 75
Bobby Ryan                ANA             69  32 + 28 = 60
Teemu Selanne             ANA             60  21 + 42 = 63
Ryan Getzlaf              ANA             54  17 + 40 = 57
Lubomir Visnovsky         ANA             68  14 + 42 = 56
Saku Koivu                ANA             62  14 + 22 = 36
Jason Blake               ANA             63  12 + 14 = 26
Brad Winchester           STL, ANA        63  10 +  5 = 15
Brandon McMillan          ANA             47   9 +  9 = 18
Cam Fowler                ANA             63   7 + 23 = 30
Dan Sexton                ANA             42   4 +  8 = 12
Toni Lydman               ANA             65   3 + 20 = 23
Francois Beauchemin       TOR, ANA        68   3 + 11 = 14
Jarkko Ruutu              OTT, ANA        60   3 +  9 = 12
Matt Beleskey             ANA             27   3 +  7 = 10
George Parros             ANA             66   3 +  1 =  4
Luca Sbisa                ANA             55   2 +  8 = 10
Todd Marchant             ANA             68   1 +  7 =  8
Andreas Lilja             ANA             51   1 +  6 =  7
Danny Syvret              ANA              6   1 +  1 =  2
Kyle Palmieri             ANA             10   1 +  0 =  1
Andy Sutton               ANA             37   0 +  4 =  4
Kyle Chipchura            ANA             38   0 +  2 =  2
Josh Green                ANA             12   0 +  0 =  0
Brett Festerling          ANA              1   0 +  0 =  0

Tiedot on tulostettava tehtyjen pisteiden mukaisessa järjestyksessä.

Huomaa, että jos pelaajalla on monta joukkuetta, on nykyinen joukkue viimeisenä. Metodi saa tulostaa ainoastaan ne pelaajat, jotka pelaavat nykyään haettavana olevassa joukkueessa.

Jos pyydetään joukkueen "ANA" tietoja esim. seuraava ei saa tulostua sillä nykyinen joukkue on "CGY":

Brendan Mikkelson         ANA, CGY        23   0 +  2 =  2

Valikkopohjainen käyttöliittymä

Täydennä seuraavassa oleva pohja interaktiiviseksi tilastopalveluksi

public class NHL {

    public static Scanner lukija = new Scanner(System.in);
    public static void main(String[] args) {
        Tilastopalvelu tilasto = new Tilastopalvelu();
        tulostaKayttoOhje();

        while ( true ) {
            System.out.print("komento: ");
            String komento = lukija.nextLine();
            
            if ( komento.equals("pp") ) {
              // ...
            } else if ( komento.equals("mp") ) {
              // ...
            } else if ( komento.equals("sp") ) {
              // ...
            } else if ( komento.equals("j") ) {
              // ...
            } else if ( komento.equals("p") ) {
              // ...
            } else break;
        }
    }

    private static void tulostaKayttoOhje() {
        System.out.println("NHL-tilastopalvelu");
        System.out.println("==================\n");
        System.out.println("komennot:");
        System.out.println(" pp    pistepörssi");
        System.out.println(" mp    maalipörssi");
        System.out.println(" sp    syöttöpörssi");
        System.out.println(" j     joukkueen tilastot");
        System.out.println(" p     pelaajan tilastot");
    }
}

Jar

Tätä niinkun mitä tahansa muutakin ohjelmaa olisi mukava suorittaa myös NetBeansin ulkopuolella. Tämä onnistuu seuraavasti:

Palaamme muutaman viikon päästä NHL:n pariin. Teemme ohjelmaan graafisen käyttöliittymän

Bonus: toteuta ohjelmaasi lisää ominaisuuksia, esim.

Ohjelman nykyinen rakenne ei ole ehkä optimaali kaikkiin muutoksiin. TilastoPalvelun tulostusmetodit tulostaPisteporssi() kannattaisi muokata palauttamaan pelaajien tiedot joko ArrayList:illisenä merkkijonoja tai Pelaaja-oliona. Graafisen käyttöliittymän tekemisen yhteydessä teemme ohjelmaamme nämä muutokset.

Mäkihyppy

Useimmissa tähänastisissa tehtävissä pääohjelma on annettu valmiiksi ja muutenkin tehtävän tekemistä ohjeistettu tarkasti askel askeleelta. Nyt on aika ruveta kokeilemaan omien siipien kantavuutta.

Mäkihyppääjä ja kilpailun alku

Toteutetaan ensimmäinen versio mäkihyppääjää kuvaavasta luokasta Hyppaaja.

Hyppääjällä on nimi sekä tieto ensimmäisen ja toisen hypyn pituudesta. Tässä vaiheessa hyppyihin ei vielä liity tyylipisteitä. Voit olettaa, että hyppyjen pituuksien tyyppi on int.

Kilpailu kuvataan myös omana luokkanaan Kilpailu. Kilpailulla on nimi ja kilpailuun liittyy joukko hyppääjiä. Kilpailu talettaa hyppääjäoliot ArrayList:iin. Hyppääjät luodaan Kilpailun konstruktorin suorituksen yhteydessä. Voit joko kysyä hyppääjien nimet käyttäjältä, tai ainakin testausvaiheessa lienee viisainta "kovakoodata" ohjelmaan joukon hyppääjiä:

	
  hyppaajat.add(new Makihyppaaja("Nykänen"));
  hyppaajat.add(new Makihyppaaja("Ahonen"));
  hyppaajat.add(new Makihyppaaja("Olli"));
  hyppaajat.add(new Makihyppaaja("Amman"));

Ensimmäinen hyppykierros

Pääohjelma luo kilpailuolion ja kutsuu sen metodia kayKilpailu(). Ruvetaan toteuttamaan metodia ja tehdään hyppääjäluokkaan tarvittavat lisäykset.

Kilpailun aluksi hyppääjät laitetaan satunnaiseen hyppyjärjestykseen. Luokasta Collections löytyy tähän valmis metodi, etsi metodi Java API:sta. Hyppyjärjestys tulostetaan näytölle:

	
Garmisch-Partenkirchen 2011 
1. kierroksen hyppyjärjestys: 
 Amman
 Ahonen
 Olli
 Nykänen

Toteuta hyppääjille metodi public int hyppaa(int kierros), joka arpoo annetun kierroksen hypylle pituuden. Hypyn pituuden tulee olla 60-120 metriä. Ohjeet kokonaisluvun arpomiseen löytyvät tämän ohjelmoinnin perusteiden luentomateriaalista. Metodi palauttaa hypyn pituuden, jotta kutsuja voi tulostaa sen.

Kilpailu-olion kayKilpailu()-metodi laittaa jokaisen hyppääjän hyppäämään ensimmäisen kierroksen hyppynsä. Hyppyjen tiedot myös tulostetaan:

	
ensimmäinen kierros:
 Amman 1. kierroksella 68 metriä
 Ahonen 1. kierroksella 88 metriä
 Olli 1. kierroksella 92 metriä
 Nykänen 1. kierroksella 72 metriä

Hyppääjien vertailu

Jotta tuloslista eli ArrayList:issa olevat hyppääjäliot voidaan järjestää, on hyppääjien toteutettava rajapinta Comparable<Makihyppaaja>. Mäkihyppääjien vertailu perustuu hyppyjen pituuteen.

Ensimmäisen kierroksen jälkeinen järjestys tulostetaan:

ensimmäisen kierroksen tulokset:
1. Olli ensimmäinen hyppy: 92 metriä
2. Ahonen ensimmäinen hyppy: 88 metriä
3. Nykänen ensimmäinen hyppy: 72 metriä
4. Amman ensimmäinen hyppy: 68 metriä

Huom: vaikka kayKilpailu() on toistaiseksi ainoa luokan Kilpailu julkinen metodi, älä laita kaikka toiminnalisuutta suoraan metodin sisään vaan määrittele luokalle sopivia apumetodeja, jotka huolehtivat esim. yhden hyppykierroksen hyppäämisestä.

Toinen kierros ja lopputulokset

Toinen kierros hypätään ensimmäisen kierrosten tuloksen mukaisessa käänteisessä järjestyksessä:

toinen kierros:
 Amman 2. kierroksella 78 metriä
 Nykänen 2. kierroksella 86 metriä
 Ahonen 2. kierroksella 58 metriä
 Olli 2. kierroksella 70 metriä

Lopulta tulostetaan tuloslista, jonka järjestys perustuu hyppyjen yhteenlaskettuun pituuteen.

Garmisch-Partenkirchen 2010 lopputulokset:
1. Olli ensimmäinen hyppy: 92 metriä,toinen hyppy: 70 metriä
2. Nykänen ensimmäinen hyppy: 72 metriä,toinen hyppy: 86 metriä
3. Ahonen ensimmäinen hyppy: 88 metriä,toinen hyppy: 58 metriä
4. Amman ensimmäinen hyppy: 68 metriä,toinen hyppy: 78 metriä

Hyppyolio ja tyylipisteet

Muutetaan hyppääjää siten, että hyppääjä ei suoraan talleta hyppyjen pituuksia, vaan hypyn tiedot tallettaa luokan Hyppy olio. Hypyllä on pituuden lisäksi tyylipisteet. Tyylipisteet talletetaan 5:n kokoiseen taulukkoon. Voit olettaa, että tyylipisteet ovat todellisuudesta poiketen kokonaislukuja. Hypyn pituudelle ja tyylipisteille arvotaan arvot hypyn konstruktorissa.

Kokonaispisteiden laskeminen

Hyppääjien vertailu perustuu tyylipisteiden ja hypyn pituuden perusteella laskettavaan kokonaispistemäärään. Muuta ohjelmaasi siten, että se laskee hyppääjien pisteet wikipediassa esitetyn mukaan. Oleta, että mäen K-piste on 90

Hyppyjen yhteydessä tulostataan pituuden lisäksi tyylipisteet:

	
ensimmäinen kierros:
 Nykänen 1. kierroksella 91 metriä
   tyylipisteet 11 18 13 16 12
 Ahonen 1. kierroksella 96 metriä
   tyylipisteet 15 14 18 18 17
 Olli 1. kierroksella 62 metriä
   tyylipisteet 17 19 20 13 16
 Amman 1. kierroksella 52 metriä
   tyylipisteet 15 20 11 11 19

Järjestäminen siis tapahtuu pisteiden perusteella. Tuloslistoissa ilmoitetaan myös hyppyjen pituudet:

	
Garmisch-Partenkirchen 2010 lopputulokset:
1. Ahonen pisteitä 222 ensimmäinen hyppy: 96 metriä, toinen hyppy: 74 metriä
2. Nykänen pisteitä 213 ensimmäinen hyppy: 91 metriä, toinen hyppy: 71 metriä
3. Olli pisteitä 202 ensimmäinen hyppy: 62 metriä, toinen hyppy: 81 metriä
4. Amman pisteitä 170 ensimmäinen hyppy: 52 metriä, toinen hyppy: 68 metriä

Kielen tunnistaja

Tässä tehtäväsarjassa tehdään ohjelma, joka tunnistaa, onko käyttäjän kirjoittama lause suomea vai englantia.

Vokaali vai konsonantti?

Tee metodit vokaali ja konsonantti, jotka selvittävät, onko niille annettu merkki vokaali (aeiouyåäö) tai konsonantti (bcdfghjklmnpqrstvwxz).

Seuraava ohjelma testaa metodeita:

import java.util.Scanner;
public class Main {
    private static Scanner lukija = new Scanner(System.in);

    // kirjoita metodit tähän

    public static void main(String[] args) {
	System.out.println("Kirjoita sana: ");
	String sana = lukija.nextLine();
	for (int i = 0; i < sana.length(); i++) {
	    char kirjain = sana.charAt(i);
	    if (vokaali(kirjain)) {
		System.out.println(kirjain + " on vokaali");
	    } else if (konsonantti(kirjain)) {
		System.out.println(kirjain + " on konsonantti");
	    } else {
		System.out.println(kirjain + " ei ole kirjain");
	    }
	}
    }
}

Ohjelman suoritus voi näyttää seuraavalta:

Kirjoita sana: kuu-ukko
k on konsonantti
u on vokaali
u on vokaali
- ei ole kirjain
u on vokaali
k on konsonantti
k on konsonantti
o on vokaali

Kirjainmäärät

Tee metodit vokaalienMaara ja konsonanttienMaara, jotka laskevat merkkijonossa olevien vokaalien ja konsonanttien määrät.

Seuraava ohjelma testaa metodeita:

import java.util.Scanner;
public class Main {
    private static Scanner lukija = new Scanner(System.in);

    // kirjoita metodit tähän

    public static void main(String[] args) {
	System.out.println("Kirjoita sana: ");
	String sana = lukija.nextLine();
	System.out.println("Sanassa on " + vokaalienMaara(sana) + " vokaalia.");
	System.out.println("Sanassa on " + konsonanttienMaara(sana) + " konsonanttia.");
    }
}

Ohjelman suoritus voi näyttää seuraavalta:

Kirjoita sana: kuu-ukko
Sanassa on 4 vokaalia.
Sanassa on 3 konsonanttia.

Vokaalisuhde

Tee metodi vokaalisuhde, joka laskee, kuinka suuri osuus sanan kirjaimista on vokaaleja. Metodin tulee palauttaa prosenttiluku väliltä 0–100.

Esimerkiksi sanassa kuu-ukko on 4 vokaalia ja 3 konsonanttia. Niinpä vokaalisuhde on (4 / (4 + 3)) * 100 = 57,14 %.

Seuraava ohjelma testaa metodia:

import java.util.Scanner;
public class Main {
    private static Scanner lukija = new Scanner(System.in);

    // kirjoita metodit tähän

    public static void main(String[] args) {
	System.out.println("Kirjoita sana: ");
	String sana = lukija.nextLine();
	System.out.println("Vokaalisuhde: " + vokaalisuhde(sana));
    }
}

Ohjelman suoritus voi näyttää seuraavalta:

Kirjoita sana: kuu-ukko
Vokaalisuhde: 57.14285714

Suomeksi vai englanniksi?

Tekstiaineistojen perusteella on laskettu, että suomen kielen vokaalisuhde on noin 48 %, kun taas englannin kielen vokaalisuhde on noin 40 %. Tämä tarjoaa keinon päätellä, onko käyttäjän antama lause suomea vai englantia. Riittää laskea lauseen vokaalisuhde ja tarkistaa, kumpaa lukua lähempänä se on.

Tee ohjelma, joka kysyy käyttäjältä lauseen ja päättelee, onko lause suomea vai englantia. Kuinka usein ohjelmasi osuu oikeaan?

Seuraavassa on esimerkkejä ohjelman suorituksesta:

Anna lause: Toimiiko tämä ohjelma?
Lause on suomea.
Anna lause: Does this program work?
Lause on englantia.

Parannuksia ohjelmaan

Kielen päättelijä ei ole tällaisenaan vielä erehtymätön. Siihen on kuitenkin helppoa keksiä monenlaisia parannuksia. Esimerkiksi jos lauseessa on kirjaimia ä ja ö, se on lähes varmasti suomea. Toisaalta jos lauseessa on sana "the", se on lähes varmasti englantia.

Suunnittele ja toteuta ohjelmaasi muutama tämäntapainen parannus.

Lukuspiraali

Lukuspiraali

Tee ohjelma, joka tulostaa lukuspiraalin seuraavien esimerkkien mukaisesti.

Anna koko: 4
   1   2   9  10
   4   3   8  11
   5   6   7  12
  16  15  14  13
Anna koko: 5
   1   2   9  10  25
   4   3   8  11  24
   5   6   7  12  23
  16  15  14  13  22
  17  18  19  20  21
Anna koko: 6
   1   2   9  10  25  26
   4   3   8  11  24  27
   5   6   7  12  23  28
  16  15  14  13  22  29
  17  18  19  20  21  30
  36  35  34  33  32  31

Osaatko tehdä ohjelman niin, että se tulee toimeen muutamalla muuttujalla (eli ohjelma ei käytä apuna taulukkoa)?