Tehtävät viikolle 6

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.

Pääsiäisloma 21-27.4., eli viikon tehtävien palautus 29.4. pajassa

Tämän viikon tehtävistä tarvitsee tehdä normaalin 90%:n sijaan vain 85% viiteen kurssipisteeseen.

Liikkuva kuvio

Teemme ohjelman, jossa käyttäjä voi liikutella näppäimistön avulla ruudulle piirrettyjä kuvioita.

Aluksi tehdään muutama luokka jolla kuvioita hallitaan. Tehtävän osassa 1.3 päästään kuvioita piirtämään ruudulle.

Abstrakti luokka Kuvio

Tee abstrakti luokka Kuvio. Kuviolla on attribuutit x ja y, jotka kertovat kuvion sijainnin ruudulla sekä metodi void siirra(int dx, int dy) jonka avulla kuvion sijainti siirtyy parametrina olevien kordinaattisiirtymien verran. Esim. jos sijainti aluksi on (100,100), niin kutsun siirra(10,-50) jälkeen sijainti on (110, 50). Luokan konstruktori asettaa kuviolle alkusijainnin.

Luokalla on myös abstrakti metodi public abstract void piirra(Graphics g); jolla kuvio piirretään ruudulle. Kuvion piirtämismetodia tulee kutsumaan piirtoalustana käytettävän JPanel:in paint()-metodi. Piirtoalusta tehdään kohdassa 1.3

Ympyra

Tee luokka Ympyra joka perii Kuvion. Ympyrällä on halkaisija jonka arvo asetetaan konstruktorissa. Konstruktorissa asetetaan myös alkuperäinen sijainti. Ympyra määrittelee metodin piirra asiaan kuuluvalla tavalla, eli kutsumalla parametrina saamansa Graphics-olion sopivaa metodia.

Kertaa monisteen lukuja 12 ja 16 jos et muista mitä kaikkea abstraktin luokan perimiseen liittyy.

Piirtoalusta

Käytetään aluksi seuraavaa pääohjelmaa:

    public static void main(String[] args) {
        Kuvio kuvio = new Ympyra(50, 50, 30);

        Piirtoalusta piirtoalusta = new Piirtoalusta(kuvio);

        JFrame ikkuna = new JFrame();
        Container container = ikkuna.getContentPane();
        container.add(piirtoalusta);

        ikkuna.setSize(480, 360);
        ikkuna.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
        ikkuna.setVisible(true);
    }

Luo luokka Piirtoalusta joka perii luokan JPanel, mallia voit ottaa materiaalin luvusta 20.3. Piirtoalusta saa konstruktorin parametrina Kuvio-tyyppisen olion. Tee Piirtoalustalle paint(Graphics g)-metodi, jossa se pyytää konstruktiorin välityksellä saamaansa kuvioa piirtämään itsensä, eli kutsuu kuvion piirra-metodia.

Testaa että ruudulle piirtyy ympyrä.

Näppäimistöohjaus

Laajenna piirtoalustaa siten, että kuvioa voi liikutella nuolinäppäinten avulla. Materiaalin luvusta 20.4 ja viime viikon tehtävästä 2 on tässä apua.

Nelio ja Laatikko

Peri luokasta Kuvio luokat Nelio ja Laatikko. Neliölle asetetaan konstruktorissa alkusijainnin lisäksi sivun pituus. Laatikolla on korkeus ja leveys.

Varmista, että neliöt ja laatikot piirtyvät ja liikkuvat oikein Piirtoalustalla.

Koostekuvio

Peri luokasta Kuvio luokka KoosteKuvio. Koostekuvio sisältää joukon muita kuvioita jotka se tallettaa ArrayList:iin. Koostekuviolla on metodi public void liita(Kuvio k) jonka avulla koostekuvioon voi liittää kuvio-olion. Koostekuviolla ei ole omaa sijaintia. Koostekuvio piirtää itsensä pyytämällä osiaan piirtämään itsensä, koostekuvion siirtyminen tapahtuu samoin.

Testaa että koostekuviosi piirtyy ja siirtyy oikein, esim. seuraavan koostekuvion avulla:

    Kuvio y1 = new Ympyra(90, 100, 50);
    Kuvio y2 = new Ympyra(95, 105, 40);
    KoosteKuvio takaRengas = new KoosteKuvio();
    takaRengas.liita(y1);
    takaRengas.liita(y2);

    Piirotalusta piirtoalusta = new Piirotalusta(takaRengas);	
    // ...

Linja-auto

Linja-autolla on kaksi rengasta. Molemmat ovat kahdesta ympyrästä muodostuvia koostekuvioita. Linja-autolla on myös runko ja ainakin yksi ikkuna. Linja-auto on siis koostekuvio joka koostuu useasta kuviosta joista osa on koostekuvioita. Luo linja-auto ja liikuttele sitä ruudulla.

Huomaa miten olioiden vastuut jakautuvat tehtävässä. Jokainen Kuvio on vastuussa itsensä piirtämisestä ja siirtämisestä. Yksinkertaiset kuviot siirtyvät kaikki samalla tavalla. Jokaisen yksinkertaisen kuvion on itse hoidettava piirtymisestään. KoosteKuvio siirtää itsensä pyytämällä osiaan siirtymään, samoin hoituu koostekuvion piirtyminen. Piirtoalusta tuntee Kuvio-olion joka siis voi olla mikä tahansa yksinkertainen kuvio tai koostekuvio, kaikki piirretään ja siirretään samalla tavalla. Piirtoalusta siis toimii samalla tavalla kuvan oikeasta tyypistä huolimatta, piirtoalustan ei tarvitse tietää kuvion yksityiskohdista mitään. Kun piirtoalusta kutsuu kuvion metodia piirra tai siirra polymorfismin ansiosta kutsutuksi tulee kuvion todellista tyyppiä vastaava metodi.

Huomionarvoista tehtävässä on se, että KoosteKuvio voi sisältää mitä tahansa Kuvio-olioita, siis myös koostekuvioita! Luokkarakenne mahdollistaakin mielivaltaisen monimutkaisen kuvion muodostamisen ja kuvion siirtely ja piirtäminen tapahtuu aina täsmälleen samalla tavalla.

Luokkarakennetta on myös helppo laajentaa, esim. perimällä Kuvio-luokasta uusia kuviotyyppejä: kolmio, piste, viiva, ym... KoosteKuvio toimii ilman muutoksia myös uusien kuviotyyppien kanssa, samoin piirtoalusta.

Linja-autotehdas

Monimutkaisen kuvion, kuten linja-auton tekeminen vaatii monia koodirivejä ja koodi uhkaa muuttua ikäväksi luettavuudeltaan. Kuvion rakentaminen kannattaakin siirtää erillisen luokan vastuulle. Tee luokka Tehdas, jolla on metodi public static Kuvio teeLinjaAuto(int x, int y), joka muodostaa ja palauttaa linja-autoa vastaavan kuvion, jonka vasen ylänurkka on parametrien määräämässä pisteessä.

Tehdasta käytetään seuraavalla tavalla:

    Kuvio linjaAuto = Tehdas.teeLinjaAuto(50, 100);
    Piirotalusta piirtoalusta = new Piirotalusta(linjaAuto);

Voit tehdä tehtaaseesi myös muita metodeja, esim. public static Kuvio teeKaksiOvinenHenkiloAuto(int x, int y). Tämän tyylisiä "oliotehtaita" käytetään olio-ohjelmoiniissa hyvin yleisesti. Viikon 4 tehtävän 6.3 luokka Iteraattirit toimi itseasiassa täysin samaa periaatetta noudattaen, eli se tarjosi metodeinaan "tehtaita", joiden avulla pystyttiin luomaan erilaisia Iterable-rajapinnan toteuttavia olioita.

Vaalikoneen jatko-osa

Viime viikolla teimme vaalikoneen rungon. Nyt jatkamme ohjelman tekemistä. Jos et ehtinyt tekemään tehtävää viime viikolla, niin voit ottaa pohjaksesi täältä löytyvän version. Tehtävät 1.1. ja 1.2. kannattaa tehdä samanaikaisesti.

Valitsin

Toistaiseksi vaalikoneen toimintalogiikka on vähintäänkin arvelluttavaa, sillä ehdokkaan valintalogiikka nojaa täysin tietokoneen kykyyn lukea käyttäjän ajatuksia. Lisätään vaalikoneeseen liukurivalitsin, jonka avulla käyttäjä voi kertoa vaalikoneelle poliittisen kantansa. Tavoitteena on tehdä seuraavan kuvan kaltainen ohjelma.

Tarkoituksena on luoda komponentti, jota voidaan käyttää samaan tapaan kuin jo ennestään käytettyjä Swingin komponentteja (esimerkiksi JLabel). Swingin JPanel sopii piirtämisen lisäksi myös tämänkaltaiseen tarkoitukseen. Siihen voidaan lisätä komponentteja lähes vastaavaan tapaan kuin JFrameen käyttämällä JPanelin add()-metodia.

Vaalikoneeseen luodaan Valitsin-komponentti periyttämällä se JPanel-luokasta. Siihen on tarkoitus asetella yksi liukurivalitsin (JSlider) sekä valintaa kuvaava tekstikenttä (JLabel). Luokka ohjelmoidaan seuraavaan luokkarunkoon.

public class Valitsin extends JPanel {
  /* Tähän olion kentät, eli JSliderin ja JLabelin ilmentymät */
  
  /* Konstruktori luo JSliderin sekä JLabelin ja asettelee ne perittyyn JPaneliin. */
  public Valitsin(String viesti) {
    super(new BorderLayout());
    /* Loput konstruktorista */
  }

  /* Metodi palauttaa JSliderin arvon normalisoituna se välille 0..1. */
  public double arvo() {
    /* Kutsu JSliderin metodia getValue() ja normalisoi 
     * se välille [0...1] metodien getMaximum() ja getMinimum() avulla */
  }
}

Edellisen viikon materiaalissa käytettiin esimerkkinä BorderLayoutia. Jotta sen käyttäminen onnistuisi JPanelin tapauksessa, pitää JPanelille antaa konstruktorissa BorderLayoutin ilmentymä. Tutustu JSlider-luokan dokumentaatioon selvittääksesi millaisia konstruktoreita JSliderilla on ja mikä niistä on sopivin tähän tapaukseen.

Toteuta ylläolevaan luokkarunkoon Valitsin-luokka ja lisää sille sopivat importit sekä pakkausmääreet.

Valitsimen sitominen vaalikoneeseen

Tässä tehtävässä lisätään Valitsin-luokan ilmentymä vaalikoneen pääikkunaan. Lisäksi muutetaan EhdokasValitsin-luokkaa siten, että se huomioi lisätyssä valitsimessa asetetun mielipiteen.

Oletusarvoisesti JFrame-luokassa (siten myös Vaalikoneessa!) käytettävä BorderLayout ei oikein taivu usean päällekkäin asetellun komponentin näyttämiseen. Javassa on useita erilaisia mahdollisuuksia vaikuttaa elementtien sijoitteluun Swingin säiliöissä. Jos tahdot tutustua erilaisiin sijoittelutapoihin Swingissä, niin tämän linkin takaa löytyy kattava opas. Eräs sopiva layout on javax.swing.BoxLayoutia. BoxLayoutia käytetään hiukan eri tavalla kuin BorderLayoutia:

// Konstruktorissa pitää asettaa käytössä oleva säiliö BoxLayoutille:
Container pane = getContentPane();
BoxLayout boxlayout = new BoxLayout(pane, BoxLayout.Y_AXIS);
pane.setLayout(boxlayout);

// Lisättäessä objekteja ne voidaan tasata seuraavasti
JLabel tekstikentta = new JLabel("aloitusteksti");
tekstikentta.setAlignmentX(Component.CENTER_ALIGNMENT);
pane.add(tekstikentta);

Asettele vaalikoneeseen muutama Valitsin-komponentti. Käytä haluamaasi Layout-luokkaa asetellaksesi valitsimet järkevästi. Muuta myös EhdokasValitsin-luokkaa ottamaan valitsimesta saadut arvot huomioon.

Ehdokas-luokka

Tee luokka Ehdokas, joka tallettaa ehdokkaan nimen ja poliittiset mielipiteet. Sopiva talletusrakenne ehdokkaan mielipiteiden talletukseen on HashMap<String, Double>. Mielipiteitä voidaan etsiä niiden merkkijono-esitysten perusteella. Mahdollinen tapa käyttää Ehdokas-luokkaa olisi seuraavanlainen:

Ehdokas aku = new Ehdokas("Aku Ankka");
aku.lisaaMielipide("Perustulo", 0.99);
aku.lisaaMielipide("Eläkeikää korotettava", 0.11);
System.out.println(aku);

Tällöin ohjelma voisi tulostaa:

Aku Ankka
Perustulo: 0.99
Eläkeikää korotettava: 0.11

EhdokasValitsin ja ehdokkaat

Lisää EhdokasValitsin-luokkaan metodi void lisaaEhdokas(Ehdokas e), jonka avulla ehdokasvalitsimeen voidaan tallettaa ehdokkaita. Ehdokkaiden tallettamiseen voit käyttää haluamaasi tapaa.

Kuormita ehdokasvalitsimen annaEhdokas()-metodi siten, että se ottaa nyt parametrinaan tyyppiä HashMap<String, Double> olevan olion, jonka avulla valitaan ehdokas, jonka mielipiteet ovat lähimpänä annettussa HashMapissa olevia mielipiteitä. Ehdokasvalitsinta voitaisiin käyttää seuraavaan tapaan:

EhdokasValitsin eValitsin = new EhdokasValitsin();
eValitsin.lisaaEhdokas(aku);
eValitsin.lisaaEhdokas(roope);

HashMap<String, Double > mielipiteet = new HashMap<String, Double >();
mielipiteet.put("Perustulo", 0.5);
mielipiteet.put("Eläkeikää korotettava", 0.7);
System.out.println(eValitsin.annaEhdokas(mielipiteet));

Jolloin ohjelma voisi tulostaa:

Roope Ankka
Perustulo: 0.01
Eläkeikää korotettava: 0.99

Sopivin ehdokas kannattaa etsiä laskemalla "etäisyys" mielipiteiden välillä. Tällöin etsittäessä sopivinta ehdokasta voidaan laskea ero kaikkiin valitsimeen talletettuihin mielipiteisiin ja sen jälkeen palauttaa ehdokas, jonka mielipite on lähimpänä parametrina annettua. Eräs mahdollinen etäisyys mielipiteille olisi mielipiteiden erotusten itseisarvojen summa, joka olisi esimerkissämme.

Roope Ankka: (0.5 - 0.01) + (0.99 - 0.7) = 0.49 + 0.29 = 0.78
Aku Ankka: (0.99 - 0.5) + (0.7 - 0.11) = 0.49 + 0.59 = 1.08

Esimerkeissä laskettiin ensin erotus perustuloa koskevista mielipiteistä ja sen jälkeen eläkeiän nostoa koskevista mielipiteistä.

Hienompi tapa laskea mielipiteiden etäisyys

Nyt mielipiteiden etäisyyden laskeminen on hiukan kankeahkoa. Informaatioteoriassa on käytössä Kullback-Leibler divergenssi, joka määrittelee eräänlaisen etäisyyden kahden todennäköisyysjakauman välille. Jos P ja Q ovat todennäköisyysjakaumia, niin niiden Kullback-Leibler divergenssi lasketaan kaavalla:

KL-divergenssi

Käytä Kullback-Leibler divergenssiä mielipiteiden erotusten laskemiseen. Tällöin sinun pitää normalisoida mielipiteen kohdat siten, että niiden summa on 1. Huomaa, että jos jokin mielipide on arvoltaan 0, tulee KL-divergenssin arvoksi äärettömyys. Näistä kannattaa myös hommautua eroon jollain sopivalla tavalla.

Käyttöliittymän ja uuden ehdokasvalitsimen yhteistoiminta

Aseta käyttöliittymäsi käyttämään uutta EhdokasValitsimen annaEhdokas-metodia. Pystyäksesi käyttämään sitä mahdollisimman helposti kannattaa lisätä Valitsin-luokkaan sopiva apumetodi mielipiteen tekstimuotoisen kuvauksen kyselemiseen.

Samalla kannattaa tallettaa kaikki Vaalikone-luokan valitsimet johonkin sopivaan säiliöön (esimerkiksi ArrayList), jolloin niiden läpikäyminen on helppoa. Tämän jälkeen "Anna ehdokas"-napin painaminen johtaa siihen, että Valitsimiin talletetut arvot talletetaan HashMappiin ja syötetään sen jälkeen toimintalogiikasta vastaavalle EhdokasValitsin-luokalle, luokalle joka palauttaa sopivimman ehdokkaan.

Viimeistely osa 1

Tälläisenään vaalikone on vielä hiukan vaivalloinen käyttää, sillä jokainen vaalikoneessa oleva ehdokas on määriteltävä Java-koodina. Tee EhdokasValitsin-luokkaan metodit void alustaTiedostosta(String tiedostonNimi) ja TreeSet<String> mielipiteet(). Ensimmäinen metodi lukee ehdokkaat ja heidän mielipiteensä tiedostosta. Tiedosto voi näyttää seuraavalta:

Roope Ankka
Perustulo 0.01
Eläkeikää korotettava 0.99

Aku Ankka
Perustulo 0.99
Eläkeikää korotettava 0.11

Pelle Peloton
Perustulo 0.77
Eläkeikää korotettava 0.33

Halutessasi voit muokata tiedoston muotoilua helpommin käsiteltävään muotoon. Jälkimmäinen metodi mielipiteet palauttaa joukon, jossa on lueteltuna kaikki tiedostossa esiintyneet mielipiteet. TreeSet on eräs Javan valmis toteutus joukolle. Sen etuna tavalliseen listaan on se, että siihen ei tallennu duplikaatteja. Sitä käytetään seuraavaan tapaan:

TreeSet<String> joukko = new TreeSet<String>();
joukko.add("eka");
joukko.add("toka");
joukko.add("kolmas");
joukko.add("toka");
joukko.add("eka");

for(String sana : joukko) {
  System.out.println(sana);
}

Ohjelma tulostaa:

eka
kolmas
toka

Huomaa tulostuksesta, että TreeSet tallettaa sen alkiot järjestykseen eikä salli duplikaatteja.

Viimeistely osa 2

Muokkaa Vaalikone-luokkaa siten, että se kysyy EhdokasValitsin-luokalta mitä mielipiteitä ehdokkailla on, ja luo sitten näitä mielipiteitä vastaavat valitsimet. Mielipiteet voidaan kysyä käyttämällä ehdokasvalitsimen mielipiteet()-metodia. Tämän muutoksen jälkeen vaalikoneen pitäisi osata toimia täysin sille annetun ehdokaslistauksen perusteella. Kokeile ohjelmaasi listaamalla ehdokkaille useita mielipiteitä. Kokeile myös miten ohjelmasi käyttäytyy, jos jollain ehdokkaalla ei ole kaikkia mahdolisia mielipiteitä.

Matopeli

Matopeli on tullut tutuksi mm. Nokian kännyköistä. Puolassa opiskelijat ovat muuttaneet kerrostalon matopeliksi:

Ruudukon piirtäminen

Matopelin ruudukon tallentamiseen soveltuu hyvin kaksiulotteinen taulukko. Matopeliin sopiva kaksiulotteinen taulukko voidaan määritellä seuraavasti:

    int[][] ruudukko = {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
                        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
                        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
                        {0, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0},
                        {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0},
                        {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0},
                        {0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0},
                        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
                        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
                        {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}};

Tässä luku 0 tarkoittaa tyhjää ruutua, luku 1 tarkoittaa madon osaa ja luku 2 tarkoittaa aarretta. Taulukon lukuihin voi viitata merkinnällä ruudukko[rivi][sarake]. Esimerkiksi ruudukko[3][1] tarkoittaa rivin 3 saraketta 1, joka sisältää tässä ruudukossa aarteen (luku 2).

Tässä taulukon korkeus on 10 alkiota ja leveys 15 alkiota. Nämä tiedot saadaan myös kirjoittamalla ruudukko.length (korkeus) ja ruudukko[0].length (leveys).

Tee matopelin ensimmäinen versio, jossa paint-metodi piirtää näkyviin peliruudukon taulukon ruudukko sisällön mukaisesti. Ota mallia viime viikon nopeuspelin pohjasta. Seuraavat komennot ovat hyödyllisiä:

g.setColor(Color.BLUE); // värin vaihtaminen
g.drawRect(x, y, leveys, korkeus); // suorakulmion ääriviivat
g.fillRect(x, y, leveys, korkeus); // täytetty suorakulmio

Pelin tulisi nyt näyttää suunnilleen tällaiselta:

Huom! Sinun ei tarvitse muokata paint-metodia tulevissa tehtävissä, vaan riittää, että se piirtää ruudukon sisällön. Pelin toiminnallisuus tulee muihin metodeihin, jotka eivät ole yhteydessä kuvan piirtämiseen.

Madon liike

Madon tulisi liikkua niin, että joka hetkellä madon pää siirtyy ruudun edemmäs ja madon hännästä poistuu yksi ruutu. Tällaisen liikkumisen toteuttamiseen on kuin luotu tietorakenne jono.

Matoon kuuluvat ruudut on näppärää tallentaa seuraavanlaisiin olioihin:

public class Kohta {
    public int y;
    public int x;

    public Kohta(int y, int x) {
        this.y = y;
        this.x = x;
    }
}

Matoa vastaavan jonon voi tallentaa esimerkiksi javan LinkedList-rakenteeseen. Määrittely on tällöin seuraava:

LinkedList<Kohta> mato = new LinkedList<Kohta>();

Alussa madon ruudut voidaan siirtää jonoon seuraavasti:

mato.addFirst(new Kohta(6, 7));
mato.addFirst(new Kohta(5, 7));
mato.addFirst(new Kohta(4, 7));
mato.addFirst(new Kohta(3, 7));

Nyt jonon sisältö on seuraava:

(3, 7) -> (4, 7) -> (5, 7) -> (6, 7)

Seuraavat komennot siirtävät matoa askeleen ylöspäin:

// hännän poistaminen
Kohta vanha = mato.removeLast();
// uuden ruudun lisääminen:
mato.addFirst(new Kohta(2, 7));

Nyt jonon sisältö on seuraava:

(2, 7) -> (3, 7) -> (4, 7) -> (5, 7)

Toteuta peliin ajastimen ja jonon avulla madon liikkuminen. Aluksi riittää, että mato liikkuu koko ajan ylöspäin. Suunnittele koodi niin, että jos madon pää törmää yläreunaan, se siirtyy sieltä alareunaan. Muista tehdä myös vastaavat muutokset taulukkoon ruudukko, jotta pelin tilanne päivittyy oikein.

Madon ohjaus

Lisää peliin näppäimistön käsittely ja toteuta muutkin madon liikkumissuunnat (vasemmalle, alas ja oikealle). Huomaa, että mato ei voi tehdä täyskäännöstä: jos mato liikkuu ylöspäin, se voi kääntyä vasemmalle ja oikealle mutta ei alaspäin.

Törmäykset ja aarteet

Pelin tulisi päättyä, jos mato törmää itseensä. Toteuta peliin tämä ominaisuus.

Lisää peliin myös aarteiden käsittely: jos mato törmää aarteeseen, mato pidentyy ja uusi aarre arvotaan sellaiseen ruudukon kohtaan, joka on tällä hetkellä tyhjänä.

Viimeistely

Lisää peliin pistelaskuri (aina kun mato syö aarteen, pisteet karttuvat) sekä ennätyslista.

Kaksinpeli

Muuta peli kaksinpeliksi: matoja onkin kaksi. Valitse pelaajille sopivat näppäimet.

Oma ominaisuus

Toteuta peliin vielä jokin oma ominaisuus. Tällainen voisi olla esimerkiksi seinä (jos mato törmää seinään, peli päättyy), teleportti (mato siirtyy sen kautta toiseen kohtaan ruudukossa) tai bonusaarteet.

Geneerisiä luokkia ja metodeja

Geneerinen pari

Javassa metodi voi palautaa vain yhden arvon. Toisinaan olisi kätevää, jos metodi voisi palauttaa vaikka kaksi arvoa ilman, että näiden säilyttämiseen pitää määritellä uutta luokkaa.

Tee geneerinen luokka Pari<T1, T2>. Pariluokka ottaa siis kaksi tyyppiparametria, T1 ja T2. Esimerkiksi kokonaisluvun ja merkkijonon sisältävän parin tyyppi olisi Pari<Integer, String>. Parissa tulisi olla kaksi public-kenttää: eka ja toka. Tee parille myös kaksiparametrinen konstruktori, joka alustaa nämä kentät.

Seuraavan koodinpätkän pitäisi toimia:

Pari<String, Integer> nimiJaIka = new Pari<String, Integer>("Ville", 5);
nimiJaIka.toka += 1;
System.out.println(nimiJaIka.eka);
System.out.println(nimiJaIka.toka);

Huomaa, että tyyppiparametreina ei valitettavasti voi käyttää alkeistyyppejä int, boolean, ... vaan on käytettävä niiden olioversioita Integer, Boolean, ...

Parille apumetodeja

Tee pariluokkaan seuraavat metodit:

Pari<String, Integer> nimiJaIka = Pari.luo("Ville", 5);
Pari<Integer, String> ikaJaNimi = nimiJaIka.kaannettyna();

Javasta graafinen laskin

Geneerinen funktio

Joskus olisi kätevää voida välittää metodeja parametreina. Javassa tämä ei kuitenkaan (ainakaan vielä) onnistu suoraan, vaan parametrina on annettava rajapinta, joka vaatii sopivan metodin. Esimerkki tästä on käyttöliittymien nappuloille välitettävä ActionListener, jossa on actionPerformed-metodi.

Tehdään nyt yleispätevä abstraktio yksiparametriselle funktiolle:

public interface Funktio<X, Y> {
    public Y laske(X x);
}

Javassa voi määritellä rajapinnan toteutuksen suoraan kirjoittamalla new Rajapinta() { [...metodien toteutukset...] }. Tämä on kätevää funktioiden määrittelyssä:

Funktio<Integer, Integer> kaksinkertaista = new Funktio<Integer, Integer>() {
    public Integer laske(Integer x) { return 2*x; }
};

System.out.println(kaksinkertaista.laske(4));

Toteuta harjoituksen vuoksi pari funktiota.

Tee luokka Funktiot ja luo sinne staattinen metodi yhdista, joka ottaa kaksi funktiota f ja g ja palauttaa niiden yhdisteen h, jolle pätee h(x) = g(f(x)). Metodisi tulisi toimia kaikilla yhteensopivilla funktiotyypeillä.

Esimerkiksi seuraavan pitäisi toimia:

Funktio<Integer, String> merkkijonoksi = new Funktio<Integer, String>() {
    public String laske(Integer x) {
        return ""+x;
    }
};

Funktio<String, Character> viimeinenMerkki = new Funktio<String, Character>() {
    public Character laske(String x) {
        return x.charAt(x.length()-1);
    }
};

Funktio<Integer, Character> viimeinenNumero = Funktiot.yhdista(merkkijonoksi, viimeinenMerkki);
System.out.println(viimeinenNumero.laske(12345));

Funktion piirto

Tyyppiä Funktio<Double, Double> olevalla funktiolla voidaan esittää lukiomatematiikassa käsiteltyjä tavallisia reaalilukufunktioita, joiden kuvaajia piirrettiin ahkerasti. Tee luokka Piirtopinta extends JPanel, joka piirtää siihen lisätyt funktiot koordinaatistoon. Tee piirtopintaan metodi public void lisaaFunktio(Funktio<Double, Double> f, Color vari), jolla piirrettäviä funktioita lisätään.

Piirrä käyrät väliltä -5 ≤ x ≤ 5, -5 ≤ y ≤ 5. Käyrät voi piirtää piste pisteeltä esimerkiksi piirtämällä paljon pieniä ympyröitä komennolla g.drawOval(x, y, 1, 1).

Piirtämisessä täytyy huomioida seuraavaa:

Koordinaatistomuunnoksen idea kannattaa miettä ennen koodausta rauhassa vaikka kynällä ja paperilla.

Testiohjelma:

Funktio<Double, Double> sini = new Funktio<Double, Double>() {
    public Double laske(Double x) {
        return Math.sin(x);
    }
};

Funktio<Double, Double> kosini = new Funktio<Double, Double>() {
    public Double laske(Double x) {
        return Math.cos(x);
    }
};

Funktio<Double, Double> sincos = Funktiot.yhdista(sini, kosini);

Funktio<Double, Double> ylospainAukeavaParaabeli = new Funktio<Double, Double>() {
    public Double laske(Double x) {
        return x*x;
    }
};

Funktio<Double, Double> polynomi = new Funktio<Double, Double>() {
    public Double laske(Double x) {
        return 3*x*x*x - 5*x*x + 1;
    }
};


Piirtopinta pinta = new Piirtopinta();
pinta.lisaaFunktio(sini, Color.RED);
pinta.lisaaFunktio(kosini, Color.BLUE);
pinta.lisaaFunktio(sincos, Color.GRAY);
pinta.lisaaFunktio(ylospainAukeavaParaabeli, Color.GREEN);
pinta.lisaaFunktio(polynomi, Color.MAGENTA);

JFrame ikkuna = new JFrame();
ikkuna.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
ikkuna.getContentPane().add(pinta);
ikkuna.setSize(640, 640);
ikkuna.setVisible(true);

Esimerkkikuva:

P.S. Keksitkö, miten voit tehdä metodin Funktio<Double, Double> derivaatta(Funktio<Double, Double> f), joka palauttaa f:n derivaattafunktion approksimaation? Entä integraalifunktion?

Säännölliset lausekkeet

Säännöllinen lauseke määrittelee tiiviissä muodossa joukon merkkijonoja. Tarkastellaan esimerkiksi tehtävää, jossa täytyy tarkistaa, onko käyttäjän antama opiskelijanumero oikeanmuotoinen (alussa on numerot 01 ja niiden jälkeen 7 numeroa väliltä 0–9). Miten tekisit tämän Javalla?

Säännöllistä lauseketta käyttämällä tarkistus on hyvin helppo. Merkkijonon metodilla matches voi tarkistaa, vastaako merkkijonon muoto tiettyä säännöllistä lauseketta. Opiskelijanumeron tapauksessa sopiva säännöllinen lauseke on "01[0-9]{7}", jolloin koodi näyttää seuraavalta:

System.out.print("Anna opiskelijanumero: ");
String numero = lukija.nextLine();
if (numero.matches("01[0-9]{7}")) {
    System.out.println("Muoto on oikea.");
} else {
    System.out.println("Muoto ei ole oikea.");
}

Tavallisimmat säännöllisten lausekkeiden merkinnät ovat seuraavat:

Vaihtoehdot

Pystyviiva tarkoittaa, että säännöllisen lausekkeen osat ovat vaihtoehtoisia. Esimerkiksi lauseke 00|111|0000 määrittelee merkkijonot 00, 111 ja 0000.

Sulut

Sulkujen avulla voi määrittää, mihin säännöllisen lausekkeen osaan muut merkinnät vaikuttavat. Esimerkiksi lauseke 00(0|1) määrittelee merkkijonot 000 ja 001.

Toistot

Käytössä ovat seuraavat toistomerkinnät:

* toisto 0... kertaa
+ toisto 1... kertaa
? toisto 0 tai 1 kertaa
{a} toisto a kertaa
{a,b} toisto a...b kertaa
{a,} toisto a... kertaa

Esimerkiksi lauseke (01)* määrittelee tyhjän merkkijonon sekä merkkijonot 01, 0101, 010101, 01010101, jne. Vastaavasti lauseke 110?110? määrittelee merkkijonot 1111, 11011, 11110 ja 110110.

Merkkiryhmät

Merkkiryhmän avulla voi määritellä lyhyesti joukon merkkejä. Merkit kirjoitetaan hakasulkujen sisään, ja merkkivälin voi määrittää viivan avulla. Esimerkiksi merkintä [145] tarkoittaa samaa kuin (1|4|5) ja merkintä [2-46-9] tarkoittaa samaa kuin (2|3|4|6|7|8|9).

Viikonpäivä

Tee säännöllisen lausekkeen avulla ohjelma, joka tarkistaa, onko merkkijono viikonpäivän lyhenne (ma, ti, ke, to, pe, la tai su).

Esimerkkitulostuksia:

Anna merkkijono: ti
Muoto on oikea.
Anna merkkijono: abc
Muoto ei ole oikea.

Vokaalitarkistus

Tee säännöllisen lausekkeen avulla ohjelma, joka tarkistaa, ovatko merkkijonon kaikki merkit vokaaleja.

Esimerkkitulostuksia:

Anna merkkijono: aie
Muoto on oikea.
Anna merkkijono: ane
Muoto ei ole oikea.

Kellonaika

Tee säännöllisen lausekkeen avulla ohjelma, joka tarkistaa, onko merkkijono muotoa tt:mm:ss oleva kellonaika (tunnit, minuutit ja sekunnit kaksinumeroisina).

Esimerkkitulostuksia:

Anna merkkijono: 17:23:05
Muoto on oikea.
Anna merkkijono: abc
Muoto ei ole oikea.
Anna merkkijono: 33:33:33
Muoto ei ole oikea.

Jaollisuus

Säännöllinen lauseke "[0-9]*[02468]" tunnistaa 2:lla jaolliset ei-negatiiviset kokonaisluvut ja lauseke "[0-9]*[05]" tunnistaa 5:llä jaolliset ei-negatiiviset kokonaisluvut.

Näissä tapauksissa säännöllisen lausekkeen tekeminen on helppoa, koska luvun viimeinen numero paljastaa 2:lla tai 5:llä jaollisuuden. Millä tahansa luvulla jaollisuuden voi tarkistaa säännöllisellä lausekkeella, mutta joskus tämä on paljon vaikeampaa.

Suunnittele säännöllinen lauseke, joka tunnistaa 3:lla jaolliset ei-negatiiviset kokonaisluvut.

Huom! Tämä tehtävä on vaikea mutta opettavainen!

Demo

Demo on liikkuvaa grafiikka sisältävä tietokoneohjelma, jonka avulla ohjelmoija voi esitellä taitojaan.

Demo voi olla vaikkapa tällainen:

Oma demo

Toteuta Javalla mahdollisimman hieno demo.

Kurssipalaute

Kurssipalaute

Anna kurssista palautetta seuraavalla sivulla:

https://ilmo.cs.helsinki.fi/kurssit/servlet/Valinta