*********************************** Ohjelmoinnin perusteet, syksy 2007 5. harjoitukset (1.-5.10.2007) Esimerkkiratkaisuja Janne Salo *********************************** ********** Tehtävä 20 ********** Laivalla on suunta 0...359 astetta ja nopeus 0.0...40.0 solmua. Toteuta laiva luokkana Laiva: * public Laiva() luo laivan, jonka suunta on 0 ja nopeus 0.0 * public Laiva(double nopeus) luo laivan, jonka nopeus annetaan, suunta on 0 * public Laiva(int suunta) luo laivan, jonka suunta annetaan, nopeus on 0.0 * public Laiva(double nopeus, int suunta) luo laivan, jonka nopeus ja suunta annetaan * public int mikäSuunta() palauttaa arvonaan laivan suunnan asteina * public double mikäNopeus() palauttaa arvonaan laivan nopeuden solmuina * public void styyrpuuriin() kääntää laivaa oikealle yhden asteen (huomaa että kun suunta on 359 ja käännetään oikealle yksi aste, uusi asteluku on 0!) * public void paarpuuriin() kääntää laivaa vasemmalle yhden asteen * public void asetaNopeus(double nopeus) asettaa uudeksi nopeudeksi parametrina annetun luvun Hyvän ohjelmointityylin mukaisesti laivan tietorakenteet piilotetaan käyttäjältä, joka siis pääsee vain aksessoreilla ohjaamaan laivaa. Miten virheellisiin parametrien arvoihin tulisi suhtautua? Varusta luokka tavalliseen tapaan toString()-metodilla, jotta laivan tilan tulostaminen on vaivatonta. public class Laiva { // Vakioita private static final double MINIMINOPEUS = 0.0; private static final double MAKSIMINOPEUS = 40.0; // Sisäiset muuttujat private double nopeus; private int suunta; // Konstruktorit /** * Luo uuden laivan, jonka nopeus on 0.0 ja suunta 0. */ public Laiva(){ this.nopeus = 0.0; this.suunta = 0; } /** * Luo uuden laivan, jolla on annettu nopeus ja suunta 0 */ public Laiva(double nopeus){ this.asetaNopeus(nopeus); //Käytetään hyödyksi olemassaolevaa koodia this.suunta = 0; } /** * Luo uuden laivan, jolla on annettu suunta ja nopeus 0.0 */ public Laiva(int suunta){ if (suunta > 0) this.suunta = suunta % 360; //Tulos aina väliltä 0..359 (Tällaista "kikkailua" ei tehtävässä oikeastaan vaadittu...) else { this.suunta = (360 + suunta % 360) % 360; } this.nopeus = 0.0; } /** Luo uuden laivan, jolla on annettu suunta ja nopeus */ public Laiva(double nopeus, int suunta){ if (suunta > 0) this.suunta = suunta % 360; //Tulos aina väliltä 0..359 else { this.suunta = (360 + suunta % 360) % 360; } this.asetaNopeus(nopeus); } // Aksessorit public int mikäSuunta(){ return this.suunta; } public double mikäNopeus(){ return this.nopeus; } /** * Kääntää laivaa oikealle yhden asteen */ public void styyrpuuriin(){ this.suunta++; if (this.suunta == 360) this.suunta = 0; } /** Kääntää laivaa vasemmalle yhden asteen */ public void paarpuuriin(){ this.suunta--; if (this.suunta == -1) this.suunta = 359; } /** * Asettaa laivalle uuden nopeuden. * Jos annettu nopeus ylittää maksiminopeuden, laivan nopeudeksi asetetaan maksiminopeus. * Jos annettu nopeus alittaa miniminopeuden, laivan nopeudeksi asetetaan miniminopeus. */ public void asetaNopeus(double nopeus){ if (nopeus > MAKSIMINOPEUS) this.nopeus = MAKSIMINOPEUS; else if (nopeus < MINIMINOPEUS) this.nopeus = MINIMINOPEUS; else this.nopeus = nopeus; } public String toString(){ return "Suunta " + this.suunta + ", Nopeus "+this.nopeus; } } ********** Tehtävä 21 ********** Laadi edellisen tehtävän luokkaa käyttäen sovellus Kippari, jolla voi "ohjata laivaa". Laivaa ohjataan näppäimistöltä seuraavasti: * kirjain "v": käännetään vasemmalle, ohjelma kysyy astemäärän * kirjain "o": käännetään oikealle, ohjelma kysyy astemäärän * kirjain "n": säädetään nopeutta, ohjelma kysyy uuden nopeuden * muut syötteet lopettavat purjehduksen Ohjelma tulostaa joka operaation jälkeen laivan tilan. Tässä tehtävässä ei tarvitse varautua numeeristen syötteiden ei-numeerisuuteen. import java.util.Scanner; public class Kippari { private static Scanner lukija = new Scanner(System.in); public static void main(String[] args) { Laiva purtilo = new Laiva(); // Tulostetaan ohjeet System.out.println("Tervetuloa laivaan, kapteeni!"); System.out.println("Käytä laivan ohjaamiseen seuraavia komentoja:"); System.out.println("v - käänny vasemmalle"); System.out.println("o - käänny oikealle"); System.out.println("n - muuta nopeutta"); System.out.println("Muut syötteet lopettavat\n"); boolean jatketaanko = true; do{ System.out.println(purtilo); System.out.println("Komentonne, söör?"); String komento = lukija.next(); if (komento.equals("v")){ System.out.println("Paapuuriin ahoi!"); System.out.print("Montako astetta: "); int asteet = lukija.nextInt(); for (int i = 0; i < asteet; i++) purtilo.paapuuriin(); } else if (komento.equals("o")){ System.out.println("Styyrpuuriin ahoi!"); System.out.print("Montako astetta: "); int turn = lukija.nextInt(); for (int i = 0; i < turn; i++) purtilo.styyrpuuriin(); } else if (komento.equals("n")){ System.out.println("Muutetaan nopeutta"); System.out.print("Uusi nopeus: "); double nopeus = lukija.nextInt(); purtilo.asetaNopeus(nopeus); } else { jatketaanko = false; } } while(jatketaanko); System.out.println("Näkemiin, kapu!"); } } ********** Tehtävä 22 ********** Muistin virkistämiseksi Pikkuvarasto-luokka (kurssimateriaalista): public class Pikkuvarasto { // toteutuksen tietorakenteet: private double määrä; // tuotteen määrä >= 0 private String tuote; // tuotteen nimi // ----- konstruktorit: ----------- public Pikkuvarasto() { this.määrä = 0.0; this.tuote = "(nimetön)"; } public Pikkuvarasto(double määrä, String tuote) { this.tuote = tuote; if (määrä > 0) this.määrä = määrä; else this.määrä = 0.0; } // ----- aksessorit: -------- public double paljonkoOn() { return this.määrä; } public String mikäNimi() { return this.tuote; } public void vieVarastoon(double paljonko) { // vain >0 kelpaa if (paljonko > 0) this.määrä += paljonko; } public double otaVarastosta(double paljonko) { // vain >0 kelpaa if (paljonko <= 0) return 0; if (paljonko <= this.määrä) { // vähennetään varastosta this.määrä -= paljonko; // koko parametri return paljonko; } else { // annetaan mitä voidaan! paljonko = this.määrä; this.määrä = 0; return paljonko; } } // ------ muita työkaluja Pikkuvarasto-olioiden käsittelyyn: ------- public String toString() { // varastotilanne merkkijonona return "("+this.tuote+": "+this.määrä+")"; } public Pikkuvarasto summa(Pikkuvarasto toinen) { return new Pikkuvarasto(this.määrä + toinen.paljonkoOn(), this.tuote + toinen.mikäNimi()); } } /* HUOM! Viljavarasto vaatii toimiakseen Pikkuvaraston.*/ public class Viljavarasto { private Pikkuvarasto siilo1, siilo2; private double siilo1vetoisuus, siilo2vetoisuus; public Viljavarasto(double ekaSiilo, double tokaSiilo) { this.siilo1 = new Pikkuvarasto(0, "Vilja"); this.siilo2 = new Pikkuvarasto(0, "Vilja"); this.siilo1vetoisuus = ekaSiilo; this.siilo2vetoisuus = tokaSiilo; } public double ekassaOn() { return this.siilo1.paljonkoOn(); } public double tokassaOn() { return this.siilo2.paljonkoOn(); } public boolean siirräViljaa(double määrä, boolean ekasta) { if (määrä < 0) return false; if (ekasta) { // jos siilo1:ssä on tarpeeksi, ja jos siilo2:ssa on tilaa if ((ekassaOn() >= määrä) && (this.siilo2vetoisuus - tokassaOn() >= määrä)) { this.siilo1.otaVarastosta(määrä); this.siilo2.vieVarastoon(määrä); return true; } } else { // jos siilo2:ssa on tarpeeksi, ja jos siilo1:ssä on tilaa if ((tokassaOn() >= määrä) && (this.siilo1vetoisuus - ekassaOn() >= määrä)) { this.siilo2.otaVarastosta(määrä); this.siilo1.vieVarastoon(määrä); return true; } } return false; } public boolean lisääViljaa(double määrä) { if ((määrä > (this.siilo1vetoisuus - ekassaOn())) || (määrä < 0)) return false; this.siilo1.vieVarastoon(määrä); return true; } public double otaViljaa(double määrä) { return this.siilo1.otaVarastosta(määrä); } public void hiirivahinko(double vakavuus) { if (vakavuus < 0) return; this.siilo1.otaVarastosta(ekassaOn() * vakavuus / 100); this.siilo2.otaVarastosta(tokassaOn() * vakavuus / 100); } public String toString() { String s = "\nsiilo1 (vetoisuus " + this.siilo1vetoisuus + ")\n * Sisältö: " + this.siilo1; s += "\nsiilo2 (vetoisuus " + this.siilo2vetoisuus + ")\n * Sisältö: " + this.siilo2; return s; } public static void main(String[] args) { Viljavarasto v = new Viljavarasto(1000, 100); System.out.println(v); v.lisääViljaa(1500); System.out.println(v); v.lisääViljaa(500); System.out.println(v); v.siirräViljaa(300, true); System.out.println(v); v.siirräViljaa(80, true); System.out.println(v); v.otaViljaa(5); System.out.println(v); double hiiriprosentti = 10; v.hiirivahinko(hiiriprosentti); System.out.println(v); } } Viljavaraston käyttöön tarkoituksenmukaisempi Siilo-luokka saattaisi näyttää vaikkapa tältä (luokan kirjoittamista ei tehtävässä vaadittu): public class Siilo { private double määrä; // viljan määrä >= 0 private double vetoisuus; // siilon vetoisuus public Siilo() { this(0.0, 10); } public Siilo(double vetoisuus, double määrä) { if (vetoisuus > 0) this.vetoisuus = vetoisuus; else this.vetoisuus = 0.0; if (määrä > 0) this.määrä = määrä; else this.määrä = 0.0; if (this.määrä > this.vetoisuus) this.määrä = this.vetoisuus; } public double annaMäärä() { return this.määrä; } public double annaVetoisuus() { return this.vetoisuus; } public boolean lisää(double paljonko) { if (paljonko < 0) return false; if (this.määrä + paljonko > this.vetoisuus) return false; else this.määrä += paljonko; return true; } public boolean siirrä(Siilo siilosta, double paljonko) { if (siilosta.annaMäärä() <= paljonko) return false; if (this.vetoisuus - this.määrä <= paljonko) return false; this.määrä += siilosta.ota(paljonko); return true; } public double ota(double paljonko) { if (paljonko <= 0) return 0; if (paljonko <= this.määrä) { this.määrä -= paljonko; return paljonko; } else { paljonko = this.määrä; this.määrä = 0; return paljonko; } } public String toString() { return "(vetoisuus:" + this.vetoisuus + ", määrä:" + this.määrä + ")"; } } Siiloa käyttävä, paranneltu Viljavarasto voisi puolestaan olla jotakin tämän näköistä: public class Viljavarasto { private Siilo siilo1, siilo2; public Viljavarasto(double ekaSiilo, double tokaSiilo) { siilo1 = new Siilo(ekaSiilo, 0); siilo2 = new Siilo(tokaSiilo, 0); } public double ekassaOn() { return siilo1.annaMäärä(); } public double tokassaOn() { return siilo2.annaMäärä(); } public boolean siirräViljaa(double määrä, boolean ekasta) { if (ekasta) return siilo2.siirrä(siilo1, määrä); else return siilo1.siirrä(siilo2, määrä); } public boolean lisääViljaa(double määrä) { return siilo1.lisää(määrä); } public double otaViljaa(double määrä) { return siilo1.ota(määrä); } public void hiirivahinko(double vakavuus) { if (vakavuus < 0) return; siilo1.ota(ekassaOn() * vakavuus / 100); siilo2.ota(tokassaOn() * vakavuus / 100); } public String toString() { return "\nSiilo1: " + siilo1 + "\nSiilo2: " + siilo2; } } Huomionarvoista on, että uuden Viljavaraston käyttäjälle näkyvä toteutus ("rajapinta") on täsmälleen sama kuin vanhassa Viljavarastossa. Luokan käyttäjältä ei siis vaadita mitään toimenpiteitä, vaikka Viljavaraston toteutus muuttuisikin. ********** Tehtävä 23 ********** import java.util.Scanner; public class Kyselija { private static Scanner lukija = new Scanner(System.in); private String kysymyksenAlku, kysymyksenLoppu; private boolean isotJaPienetSamaistettu; private int kysymyksiä, oikein; public Kyselija(String kysymyksenAlku, String kysymyksenLoppu) { this.kysymyksenAlku = kysymyksenAlku; this.kysymyksenLoppu = kysymyksenLoppu; this.isotJaPienetSamaistettu = false; this.kysymyksiä = this.oikein = 0; } /** * Vastaus hyväksytään oikeaksi, vaikka se sisältäisi ylimääräisiä välilyöntejä alussa tai lopussa. * Metodi palauttaa arvonaan true, jos vastaus on oikein, muuten false. * Myös onnistumisesta metodi raportoi */ public boolean kysy(String kysytty, String oikeaVastaus) { System.out.println(this.kysymyksenAlku + kysytty + " " + this.kysymyksenLoppu); String vastaus = lukija.nextLine(); vastaus = vastaus.trim(); //Poistetaan vastauksesta ylimääräiset välilyönnit this.kysymyksiä++; // Tarkistetaan vastaus if ((this.isotJaPienetSamaistettu && vastaus.equalsIgnoreCase(oikeaVastaus)) || (!this.isotJaPienetSamaistettu && vastaus.equals(oikeaVastaus))) { System.out.println("OIKEIN!"); this.oikein++; return true; } else { System.out.println("Väärin! Oikea vastaus on \"" + oikeaVastaus + "\"."); return false; } } /** * Jos metodia on kutsuttu viimeksi parametrilla true, isot ja pienet kirjaimet samaistetaan, * jos false, niitä ei samaisteta. * Oletusarvoisesti on samaistaIsotJaPienet(false). * Asiantilaa voi tarvittaessa siis muuttaa vaikka kysymyksittäin */ public void samaistaIsotJaPienet(boolean samaista) { this.isotJaPienetSamaistettu = samaista; } /** * palauttaa tähän mennessä kysyttyjen kysymysten lukumäärän */ public int montakoKysymystä() { return this.kysymyksiä; } /** * palauttaa oikeiden vastausten lukumäärän tähän mennessä */ public int montakoOikein() { return this.oikein; } public static void main(String[] args) { Kyselija kilpa = new Kyselija("Mitä on italian ", "suomeksi?"); kilpa.samaistaIsotJaPienet(true); boolean oikein; oikein = kilpa.kysy("tutti", "kaikki"); if (oikein) System.out.println("Hienoa, hyvä alku!"); kilpa.kysy("ferro", "rauta"); // Arvon palauttavaa metodia voi // kutsua myös arvoa käyttämättä! oikein = kilpa.kysy("matto", "hullu"); if (!oikein) System.out.println("No mitäs nyt?"); System.out.println("Kysymyksiä oli " + kilpa.montakoKysymystä() + ", oikeita vastauksia " + kilpa.montakoOikein() + "."); } }