Tehtävät viikolle 5

Pakolliset tehtävät on merkitty harmalla taustavärillä. Pakollisuus tarkoittaa oikeastaan hyvin voimakasta suositusta tehtävän tekemiseen. Jos pakollisia jää tekemättä, on suuri riski, että seuraavalla viikolla esitellään asioita jotka oletetetaan jo osatuiksi ja pakollisten tehtävien kautta harjoitelluiksi.

Lyyra-kortti

Tässä tehtäväsäsarjassa tehdään luokka LyyraKortti, jonka tarkoituksena on jäljitellä Lyyra-kortin käyttämistä Unicafessa.

Luokan runko

Projektiin tulee kuulumaan kaksi kooditiedostoa:

Kun luot projektin NetBeansissa, anna projektin nimeksi (Project Name) LyyraKortti ja pääluokan nimeksi (Main Class) Main. Lisää sitten projektiin uusi luokka painamalla projektin nimestä hiiren oikealla napilla vasemmalla olevasta projektilistasta ja valitsemalla New->Java Class. Anna luokan nimeksi (Class Name) LyyraKortti.

Uuden luokan luonti

Tee ensin LyyraKortti-olion konstruktori, jolle annetaan kortin alkusaldo ja joka tallentaa sen olion sisäiseen muuttujaan. Tee sitten toString-metodi, joka palauttaa kortin saldon muodossa "Kortilla on rahaa X euroa".

Seuraavassa on luokan LyyraKortti runko:

public class LyyraKortti {
    private double saldo;

    public LyyraKortti(double alkusaldo) {
        // kirjoita koodia tähän
    }

    public String toString() {
        // kirjoita koodia tähän
    }
}

Seuraava pääohjelma testaa luokkaa:

public class Main {
    public static void main(String[] args) {
        LyyraKortti kortti = new LyyraKortti(50);
        System.out.println(kortti);
    }
}

Ohjelman tulisi tuottaa seuraava tulostus:

Kortilla on rahaa 50.0 euroa

Kortilla maksaminen

Täydennä LyyraKortti-luokkaa seuraavilla metodeilla:

    public void syoEdullisesti() {
        // kirjoita koodia tähän
    }

    public void syoMaukkaasti() {
        // kirjoita koodia tähän
    }

Metodin syoEdullisesti tulisi vähentää kortin saldoa 2,40 eurolla ja metodin syoMaukkaasti tulisi vähentää kortin saldoa 4,00 eurolla.

Seuraava pääohjelma testaa luokkaa:

public class Main {
    public static void main(String[] args) {
        LyyraKortti kortti = new LyyraKortti(50);
        System.out.println(kortti);
        kortti.syoEdullisesti();
        System.out.println(kortti);
        kortti.syoMaukkaasti();
        kortti.syoEdullisesti();
        System.out.println(kortti);
    }
}

Ohjelman tulisi tuottaa seuraava tulostus:

Kortilla on rahaa 50.0 euroa
Kortilla on rahaa 47.6 euroa
Kortilla on rahaa 41.2 euroa

Ei-negatiivinen saldo

Mitä tapahtuu, jos kortilta loppuu raha kesken? Ei ole järkevää, että saldo muuttuu negatiiviseksi. Muuta metodeita syoEdullisesti ja syoMaukkaasti niin, että ne eivät vähennä saldoa, jos saldo menisi negatiiviseksi.

Seuraava pääohjelma testaa luokkaa:

public class Main {
    public static void main(String[] args) {
        LyyraKortti kortti = new LyyraKortti(5);
        System.out.println(kortti);
        kortti.syoMaukkaasti();
        System.out.println(kortti);
        kortti.syoMaukkaasti();
        System.out.println(kortti);
    }
}

Ohjelman tulisi tuottaa seuraava tulostus:

Kortilla on rahaa 5.0 euroa
Kortilla on rahaa 1.0 euroa
Kortilla on rahaa 1.0 euroa

Siis toinen metodin syoMaukkaasti kutsu ei vaikuttanut saldoon, koska saldo olisi mennyt negatiiviseksi.

Kortin lataaminen

Lisää LyyraKortti-luokkaan seuraava metodi:

    public void lataaRahaa(double rahamaara) {
        // kirjoita koodia tähän
    }

Metodin tarkoituksena on kasvattaa kortin saldoa parametrina annetulla rahamäärällä. Kuitenkin kortin saldo saa olla korkeintaan 150 euroa, joten jos ladattava rahamäärä ylittäisi sen, saldoksi tulisi tulla silti tasan 150 euroa.

Seuraava pääohjelma testaa luokkaa:

public class Main {
    public static void main(String[] args) {
        LyyraKortti kortti = new LyyraKortti(10);
        System.out.println(kortti);
        kortti.lataaRahaa(15);
        System.out.println(kortti);
        kortti.lataaRahaa(10);
        System.out.println(kortti);
        kortti.lataaRahaa(200);
        System.out.println(kortti);
    }
}

Ohjelman tulisi tuottaa seuraava tulostus:

Kortilla on rahaa 10.0 euroa
Kortilla on rahaa 25.0 euroa
Kortilla on rahaa 35.0 euroa
Kortilla on rahaa 150.0 euroa

Monta korttia

Tee pääohjelma, jossa luodaan kaksi LyyraKortti-oliota: yksi Pekalle (alkusaldo 20 euroa) ja toinen Matille (alkusaldo 30 euroa). Tee ohjelmassa seuraavat asiat:

Pääohjelman runko on seuraava:

public class Main {
    public static void main(String[] args) {
        LyyraKortti pekanKortti = new LyyraKortti(20);
        LyyraKortti matinKortti = new LyyraKortti(30);
        // kirjoita koodia tähän
    }
}

Ohjelman tulisi tuottaa seuraava tulostus:

Pekka: Kortilla on rahaa 16.0 euroa
Matti: Kortilla on rahaa 27.6 euroa
Pekka: Kortilla on rahaa 36.0 euroa
Matti: Kortilla on rahaa 23.6 euroa
Pekka: Kortilla on rahaa 31.200000000000003 euroa
Matti: Kortilla on rahaa 73.6 euroa

Saldo kokonaislukuna

Tosielämässä kortin saldon tallennus double-muuttujaan ei olisi hyvä idea, koska double-arvoissa esiintyy pyöristysvirheitä. Sellainen esiintyy jopa yllä olevassa esimerkissä: Pekan kortilla kuuluisi olla lopuksi 31,2 euroa, mutta siellä onkin muka 31,200000000000003 euroa.

Yksi ratkaisu ongelmaan on käyttää double-muuttujan sijasta int-muuttujaa ja tallentaa rahamäärä sentteinä. Tee tarvittavat muutokset luokkaan.

Kello laskurin avulla

Tässä tehtävässä tehdään luokka YlhaaltaRajoitettuLaskuri ja sovelletaan sitä kellon tekemiseen.

Rajoitettu laskuri

Tehdään luokka YlhaaltaRajoitettuLaskuri. Luokan olioilla on seuraava toiminnallisuus:

Tee projektiin luokat Main ja YlhaaltaRajoitettuLaskuri vastaavasti kuin tehtävässä 1.1. Näin tehdään myös tulevissa tehtäväsarjoissa.

Luokan runko on siis seuraava:

public class YlhaaltaRajoitettuLaskuri {
    private int laskuri;
    private int ylaraja;

    public YlhaaltaRajoitettuLaskuri(int ylarajanAlkuarvo) {
        // kirjoita koodia tähän
    }

    public void seuraava() {
        // kirjoita koodia tähän
    }

    public String toString() {
        // kirjoita koodia tähän
    }
}

Vihje: et voi palauttaa toStringissä suoraan kokonaislukutyyppisen oliomuuttujan laskuri arvoa. Kokonaislukumuuttujasta x saa merkkijonomuodon esim. lisäämällä sen eteen tyhjän merkkijonon eli kirjoittamalla ""+x.

Seuraavassa on pääohjelma, joka käyttää laskuria:

public class Main {
    public static void main(String[] args) {
        YlhaaltaRajoitettuLaskuri laskuri = new YlhaaltaRajoitettuLaskuri(4);
        System.out.println("arvo alussa: " + laskuri );

        for ( int i=0; i < 10; i++ ){
            laskuri.seuraava();
            System.out.println("arvo: " + laskuri );
        }
    }
}

Laskurille asetetaan ylärajaksi konstruktorissa 4, joten laskurin arvo on luku 0:n ja 4:n väliltä. Huomaa, miten metodi seuraava vie laskurin arvoa eteenpäin, kunnes se pyörähtää 4:n jälkeen 0:aan:

Ohjelman tulostuksen tulisi olla seuraava:

arvo alussa: 0
arvo: 1
arvo: 2
arvo: 3
arvo: 4
arvo: 0
arvo: 1
arvo: 2
arvo: 3
arvo: 4
arvo: 0

Etunolla tulostukseen

Tee toString-metodista sellainen, että se lisää arvon merkkijonoesitykseen etunollan, jos laskurin arvo on vähemmän kuin 10. Eli jos laskurin arvo on esim. 3, palautetaan merkkijono "03", jos arvo taas on esim. 12, palautetaan normaaliin tapaan merkkijono "12".

Muuta pääohjelma seuraavaan muotoon ja varmista, että tulos on haluttu.

public class Main {
    public static void main(String[] args) {
        YlhaaltaRajoitettuLaskuri laskuri = new YlhaaltaRajoitettuLaskuri(14);
        System.out.println("arvo alussa: " + laskuri );

        for ( int i=0; i < 16; i++ ){
            laskuri.seuraava();
            System.out.println("arvo: " + laskuri );
        }
    }
}
arvo alussa: 00
arvo: 01
arvo: 02
arvo: 03
arvo: 04
arvo: 05
arvo: 06
arvo: 07
arvo: 08
arvo: 09
arvo: 10
arvo: 11
arvo: 12
arvo: 13
arvo: 14
arvo: 00
arvo: 01

Kello, ensimmäinen versio

Käyttämällä kahta laskuria voimme muodostaa kellon. Tuntimäärä on laskuri, jonka yläraja on 23, ja minuuttimäärä on laskuri jonka yläraja on 59. Kuten kaikki tietävät, kello toimii siten, että aina kun minuuttimäärä pyörähtää nollaan, tuntimäärä kasvaa yhdellä.

Tee ensin laskurille metodi arvo, joka palauttaa laskurin arvon:

    public int arvo() {
        // kirjoita koodia tähän
    }

Tee sitten kello täydentämällä seuraava pääohjelmarunko:

public class Main {
    public static void main(String[] args) {
        YlhaaltaRajoitettuLaskuri minuutit = new YlhaaltaRajoitettuLaskuri(59);
        YlhaaltaRajoitettuLaskuri tunnit = new YlhaaltaRajoitettuLaskuri(23);     

        for ( int i=0; i < 121; i++) {
            System.out.println( tunnit + ":" + minuutit);   // tulostetaan nykyinen aika
            // minuuttimäärä kasvaa
            // jos minuuttimäärä menee nollaan, tuntimäärä kasvaa
        }
    }
}

Jos kellosi toimii oikein, sen tulostus näyttää suunnilleen seuraavalta:

00:00
00:01
...
00:59
01:00
01:01
01:02
...
01:59
02:00

Varmista, että kellosi siirtyy näyttämään keskiyöllä aikaa 00:00.

Kello, toinen versio

Laajenna kelloasi myös sekuntiviisarilla. Tee myös laskurille metodi asetaArvo, jolla laskurille pystyy asettamaan halutun arvon. Tämän metodin avulla voit muuttaa kellon ajan heti ohjelman alussa haluamaksesi.

Laita kellosi alkamaan ajasta 23:59:50 ja varmista, että vuorokauden vaihteessa kello toimii odotetusti.

Voit testata kellon toimintaa seuraavalla ohjelmalla

public class Main {
    public static void main(String[] args) throws Exception {
        YlhaaltaRajoitettuLaskuri sekunnit = new YlhaaltaRajoitettuLaskuri(59);
        YlhaaltaRajoitettuLaskuri minuutit = new YlhaaltaRajoitettuLaskuri(59);
        YlhaaltaRajoitettuLaskuri tunnit = new YlhaaltaRajoitettuLaskuri(23);

        sekunnit.asetaArvo(50);
        minuutit.asetaArvo(59);
        tunnit.asetaArvo(23);

        while ( true) {
            System.out.println( tunnit + ":" + minuutit + ":" + sekunnit);
            Thread.sleep(1000);
            // lisää kellon aikaa sekunnilla eteenpäin
        }
    }
}

Nyt kello käy ikuisesti ja kasvattaa arvoaan sekunnin välein. Sekunnin odotus tapahtuu komennolla Thread.sleep(1000);. Jotta komento toimisi, pitää main:in esittelyriville tehdä pieni lisäys: public static void main(String[] args) throws Exception {, eli tummennettuna oleva throws Exception.

Tärkeitä kommentteja liittyen olioiden käyttöön. Lue nämä ehdottomasti.

Olio-ohjelmoinnissa on kyse pitkälti käsitteiden eristämisestä omaksi kokonaisuudekseen tai toisin ajatellen abstraktioiden muodostamisesta. Voisi ajatella, että on turha luoda oliota jonka sisällä on ainoastaan luku, eli että saman voisi tehdä suoraan int-muuttujilla. Asia ei kuitenkaan ole näin. Jos kello koostuu pelkästään kolmesta int-muuttujasta joita kasvatellaan, muuttuu ohjelma lukijan kannalta epäselvemmäksi, koodista on vaikea "nähdä" mistä on kysymys. Aiemmin materiaalissa mainitsimme jo kokeneen ja kuuluisan ohjelmoijan Kent Beckin neuvon "Any fool can write code that a computer can understand. Good programmers write code that humans can understand", eli koska viisari on oikeastaan oma selkeä käsitteensä, on siitä ohjelman ymmärrettävyyden parantamiseksi hyvä tehdä oma luokka, eli YlhaaltaRajoitettuLaskuri.

Käsitteen erottaminen omaksi luokaksi on monellakin tapaa hyvä idea. Ensinnäkin tiettyjä yksityiskohtia (esim. laskurin pyörähtäminen) saadaan piilotettua luokan sisään (eli abstrahoitua). Sen sijaan että kirjoitetaan if-lause ja sijoitusoperaatio, riittää, että laskurin käyttäjä kutsuu selkeästi nimettyä metodia seuraava(). Aikaansaatu laskuri sopii kellon lisäksi ehkä muidenkin ohjelmien rakennuspalikaksi, eli selkeästä käsitteestä tehty luokka voi olla monikäyttöinen. Suuri etu saavutetaan myös sillä, että koska laskurin toteutuksen yksityiskohdat eivät näy laskurin käyttäjille, voidaan yksityiskohtia tarvittaessa muuttaa.

Totesimme että kello sisältää kolme viisaria, eli koostuu kolmesta käsitteestä. Oikeastaan kello on itsekin käsite ja teemme ensi viikolla luokan Kello, jotta voimme luoda selkeitä Kello-olioita. Kello tulee siis olemaan olio jonka toiminta perustuu "yksinkertaisimpiin" olioihin eli viisareihin. Tämä on juuri olio-ohjelmoinnin suuri idea: ohjelma rakennetaan pienistä selkeistä yhteistoiminnassa olevista olioista.

Nyt otamme varovaisia ensiaskelia oliomaailmassa. Huhtikuun lopussa oliot alkavat kuitenkin olla jo selkärangassa ja nyt ehkä käsittämättömältä tuntuva lausahdus, ohjelma rakennetaan pienistä selkeistä yhteistoiminnassa olevista olioista alkaa tuntua meistä ehkä järkeenkäyvältä ja itsestäänselvältä.

Lyyra-kortin parantelu

Monta konstruktoria

Lisää luokkaan LyyraKortti uusi konstruktori, jolla ei ole parametreja. Tässä tapauksessa kortin aloitussaldon tulisi olla 20 euroa. Kutsu uudessa konstruktorissa alkuperäistä konstruktoria sopivalla tavalla.

Seuraava pääohjelma testaa luokkaa:

public class Main {
    public static void main(String[] args) {
        LyyraKortti kortti1 = new LyyraKortti();
        System.out.println(kortti1);
        LyyraKortti kortti2 = new LyyraKortti(30);
        System.out.println(kortti2);
    }
}

Ohjelman tulisi tuottaa seuraava tulostus:

Kortilla on rahaa 20.0 euroa
Kortilla on rahaa 30.0 euroa

Sama koodi vain kerran

Metodien syoEdullisesti ja syoMaukkaasti ongelmana on, että niissä on samantapainen tarkistus sen varalta, että kortin saldo uhkaa mennä negatiiviseksi. Olisi hauskempaa, jos tarkistus olisi vain yhdessä paikassa.

Tee uusi metodi vahennaSaldoa, jonka tehtävänä on vähentää kortin saldoa annetulla rahamäärällä. Metodin runko on seuraava:

    private void vahennaSaldoa(double rahamaara) {
        // kirjoita koodia tähän
    }

Tee tämäkin metodi niin, että metodi ei muuta saldoa, jos saldo menisi negatiiviseksi.

Toteuta nyt metodit syoEdullisesti ja syoMaukkaasti niin, että ne kutsuvat metodia vahennaSaldoa. Nyt negatiivisen saldon tarkistus on vain yhdessä paikassa luokassa.

Muista myös testata, että metodit toimivat tämän muutoksen jälkeen.

Lukutilasto

Lukujen määrä

Tee luokka Lukutilasto, joka tuntee seuraavat toiminnot:

Luokan ei tarvitse tallentaa mihinkään lisättyjä lukuja, vaan riittää muistaa niiden määrä. Metodin lisaaLuku ei tässä vaiheessa tarvitse edes ottaa huomioon, mikä luku lisätään tilastoon, koska ainoa tallennettava asia on lukujen määrä.

Luokan runko on seuraava:

public class Lukutilasto {
    private int maara;

    public Lukutilasto() {
        // alusta tässä muuttuja maara
    }
    
    public void lisaaLuku(int luku) {
        // kirjoita koodia tähän
    }

    public int haeMaara() {
        // kirjoita koodia tähän
    }
}

Seuraava ohjelma esittelee luokan käyttöä:

public class Main {
    public static void main(String[] args) {
	Lukutilasto tilasto = new Lukutilasto();
	tilasto.lisaaLuku(3);
	tilasto.lisaaLuku(5);
	tilasto.lisaaLuku(1);
	tilasto.lisaaLuku(2);
	System.out.println("Määrä: " + tilasto.haeMaara());
    }
}

Ohjelman tulostus on seuraava:

Määrä: 4

Summa ja keskiarvo

Laajenna luokkaa seuraavilla toiminnoilla:

Luokan runko on seuraava:

public class Lukutilasto {
    private int maara;
    private int summa;

    public Lukutilasto() {
        // alusta tässä muuttujat maara ja summa
    }
    
    public void lisaaLuku(int luku) {
        // kirjoita koodia tähän
    }

    public int haeMaara() {
        // kirjoita koodia tähän
    }

    public int haeSumma() {
        // kirjoita koodia tähän
    }

    public double haeKeskiarvo() {
        // kirjoita koodia tähän
    }
}

Seuraava ohjelma esittelee luokan käyttöä:

public class Main {
    public static void main(String[] args) {
        Lukutilasto tilasto = new Lukutilasto();
        tilasto.lisaaLuku(3);
        tilasto.lisaaLuku(5);
        tilasto.lisaaLuku(1);
        tilasto.lisaaLuku(2);
        System.out.println("Määrä: " + tilasto.haeMaara());
        System.out.println("Summa: " + tilasto.haeSumma());
        System.out.println("Keskiarvo: " + tilasto.haeKeskiarvo());
    }
}

Ohjelman tulostus on seuraava:

Määrä: 4
Summa: 11
Keskiarvo: 2.75

Summa käyttäjältä

Tee ohjelma, joka kysyy lukuja käyttäjältä, kunnes käyttäjä antaa luvun -1. Sitten ohjelma ilmoittaa lukujen summan.

Käytä ohjelmassa luokkaa Lukutilasto summan laskemiseen.

Anna lukuja:
4
2
5
4
-1
Summa: 15

Kaksi summaa

Muuta edellistä ohjelmaa niin, että ohjelma laskee erikseen parillisten ja parittomien lukujen summaa.

Määrittele ohjelmassa kaksi oliota luokasta Lukutilasto: toinen laskee parillisten lukujen summan ja toinen laskee parittomien lukujen summan.

Anna lukuja:
4
2
5
2
-1
Parillisten summa: 8
Parittomien summa: 5

Arpoja

Satunnaisluku lukuväliltä

Tee ohjelma, joka kysyy käyttäjältä alarajan ja ylärajan. Tämän jälkeen ohjelma arpoo luvun alarajan ja ylärajan välillä.

Ohjelmassa kannattaa käyttää Random-luokan metodia nextInt(n), joka arpoo luvun väliltä 0...n - 1. Mieti, miten voit arpoa tämän metodin avulla luvun väliltä a...b.

Esimerkiksi seuraavassa esimerkissä ohjelma arpoo luvun väliltä 81...85.

Anna alaraja: 81
Anna yläraja: 85
Satunnaisluku: 82

Arpoja-luokka

Tee luokka Arpoja, josta luodun olion avulla voi arpoa lukuja tietyltä lukuväliltä.

Luokan runko on seuraava:

import java.util.*;
public class Arpoja {
    private int alaraja;
    private int ylaraja;
    private Random arpoja = new Random();

    public Arpoja(int alaraja, int ylaraja) {
        // kirjoita koodia tähän
    }

    public int arvoLuku() {
        // kirjoita koodia tähän
    }
}

Luokkaa käytetään seuraavasti pääohjelmassa:

public class Main {
    public static void main(String[] args) {
        Arpoja noppa = new Arpoja(1, 6);
        System.out.println("Kolme nopanheittoa:");
        System.out.println(noppa.arvoLuku());
        System.out.println(noppa.arvoLuku());
        System.out.println(noppa.arvoLuku());
    }
}

Ohjelman tulostus voi olla seuraava:

Kolme nopanheittoa:
5
2
6

Toimiiko arvonta?

Tee ohjelma, joka heittää noppaa miljoona kertaa ja tulostaa eri tulosten lukumäärät. Käytä ohjelmassa äsken tehtyä Arpoja-luokkaa.

Tämän ohjelman avulla voi tarkistaa, että arvonta toimii halutulla tavalla (nopan kaikkia silmälukuja tulee suunnilleen yhtä paljon).

Ohjelman tulostus voi olla seuraava:

1: 166410 kpl
2: 166565 kpl
3: 166700 kpl
4: 166870 kpl
5: 166894 kpl
6: 166561 kpl

Kivi, paperi, sakset

Tässä tehtäväsarjassa tehdään tekoäly kivi, paperi, sakset -peliin.

Tekoälyn runko

Tee luokka KPSTekoaly, josta tulee tekoäly kivi, paperi, sakset -peliin. Luokkaan tulee metodi teeSiirto, joka palauttaa tekoälyn tekemän siirron. Merkkijonot "k", "p" ja "s" vastaavat siirtoja kivi, paperi ja sakset.

Tee tekoälystä ensin sellainen, että sen siirto on aina kivi.

Luokan runko on seuraava:

import java.util.*;
public class KPSTekoaly {
    public String teeSiirto() {
        // kirjoita koodia tähän
    }
}

Pääluokka näyttää seuraavalta:

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

    public static void main(String[] args) {
        KPSTekoaly tekoaly = new KPSTekoaly();
        System.out.print("Kuinka monta kierrosta? ");
        int kierrokset = Integer.parseInt(lukija.nextLine());
        for (int i = 0; i < kierrokset; i++) {
            System.out.print("Anna siirto (k, p tai s): ");
            String pelaajanSiirto = lukija.nextLine();
            String tekoalynSiirto = tekoaly.teeSiirto();
            System.out.println("Pelaajan siirto: " + pelaajanSiirto);
            System.out.println("Tekoälyn siirto: " + tekoalynSiirto);
        }
    }
}

Peli voi edetä esimerkiksi seuraavasti:

Kuinka monta kierrosta? 3
Anna siirto (k, p tai s): s
Pelaajan siirto: s
Tekoälyn siirto: k
Anna siirto (k, p tai s): p
Pelaajan siirto: p
Tekoälyn siirto: k
Anna siirto (k, p tai s): k
Pelaajan siirto: k
Tekoälyn siirto: k

Voittajan tarkistus

Lisää projektiin vielä yksi luokka KPSTuomari, jonka runko on seuraava:

public class KPSTuomari {
    public void maaritaVoittaja(String pelaajanSiirto, String tekoalynSiirto) {
        // kirjoita koodia tähän
    }
}

Metodin maaritaVoittaja tarkoituksena on selvittää pelaajan ja tekoälyn siirtojen perusteella, kumpi on voittaja vai tuliko tasapeli. Toteuta metodi niin, että se tulostaa tiedon voittajasta.

Kivi, paperi, sakset -pelissä kivi voittaa sakset, sakset voittavat paperin ja paperi voittaa kiven.

Pääluokka on nyt seuraava:

import java.util.Scanner;

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

    public static void main(String[] args) {
        KPSTekoaly tekoaly = new KPSTekoaly();
        KPSTuomari tuomari = new KPSTuomari();
        System.out.print("Kuinka monta kierrosta? ");
        int kierrokset = Integer.parseInt(lukija.nextLine());
        for (int i = 0; i < kierrokset; i++) {
            System.out.print("Anna siirto (k, p tai s): ");
            String pelaajanSiirto = lukija.nextLine();
            String tekoalynSiirto = tekoaly.teeSiirto();
            System.out.println("Tekoälyn siirto: " + tekoalynSiirto);
            tuomari.maaritaVoittaja(pelaajanSiirto, tekoalynSiirto);
        }
    }
}

Peli voi edetä esimerkiksi seuraavasti:

Kuinka monta kierrosta? 3
Anna siirto (k, p tai s): s
Tekoälyn siirto: k
Tekoäly voitti!
Anna siirto (k, p tai s): p
Tekoälyn siirto: k
Pelaaja voitti!
Anna siirto (k, p tai s): k
Tekoälyn siirto: k
Tuli tasapeli!

Satunnainen tekoäly

Nyt on aika tehdä tekoälystä parempi, koska ei ole järkevää, että tekoäly valitsee aina kiven.

Tee tekoälystä satunnainen: se valitsee yhtä todennäköisesti kiven, saksen ja paperin.

Pisteenlasku

Laajenna tuomariluokkaa niin, että se pitää kirjaa pelaajan ja tekoälyn voitoista. Lisää luokkaan myös metodi tulostaTilasto, joka tulostaa tilaston voitoista. Lisää tämän metodin kutsu pääohjelman loppuun.

Kuinka monta kierrosta? 5
Anna siirto (k, p tai s): s
Tekoälyn siirto: s
Tuli tasapeli!
Anna siirto (k, p tai s): p
Tekoälyn siirto: s
Tekoäly voitti!
Anna siirto (k, p tai s): k
Tekoälyn siirto: k
Tuli tasapeli!
Anna siirto (k, p tai s): s
Tekoälyn siirto: p
Pelaaja voitti!
Anna siirto (k, p tai s): s
Tekoälyn siirto: k
Tekoäly voitti!

Tilasto:
Pelaaja: 1 voittoa
Tekoäly: 2 voittoa

Muistava tekoäly

Lisää tekoälyyn muisti: tekoäly ottaa siirtonsa valinnassa huomioon, mitä käyttäjä on valinnut aiemmin eri tilanteissa. Päätä itse, millä tavoin tarkalleen tekoäly hyödyntää muistia.

Lisää tekoälyyn metodi kirjaaSiirto, jota kutsutaan käyttäjän siirron jälkeen. Sen avulla tekoäly saa tietoonsa, mitä siirtoja käyttäjä on tehnyt.

Pääluokkaan tehdään seuraava muutos:

...
        String pelaajanSiirto = lukija.nextLine();
        String tekoalynSiirto = tekoaly.teeSiirto();
        tekoaly.kirjaaSiirto(pelaajanSiirto);
        System.out.println("Tekoälyn siirto: " + tekoalynSiirto);
...

Jos tekoäly käyttää muistiaan hyvin, se voi sopeutua esimerkiksi tilanteeseen, jossa käyttäjä valitsee aina saman siirron:

Kuinka monta kierrosta? 5
Anna siirto (k, p tai s): s
Tekoälyn siirto: p
Pelaaja voitti!
Anna siirto (k, p tai s): s
Tekoälyn siirto: k
Tekoäly voitti!
Anna siirto (k, p tai s): s
Tekoälyn siirto: k
Tekoäly voitti!
Anna siirto (k, p tai s): s
Tekoälyn siirto: k
Tekoäly voitti!
Anna siirto (k, p tai s): s
Tekoälyn siirto: k
Tekoäly voitti!

Tilasto
Pelaaja: 1 voittoa
Tekoäly: 4 voittoa