wiki:k2012/demot/demo6-luonnos
Last modified 6 years ago Last modified on 2012-02-14 07:48:10

Demot » Demo 6, 20.2.2012

Tehtävien nimeämisestä: Älä anna C#-tiedostoille nimiä, jotka eivät ole kuvaavia. Esim. tehtG1.cs on huono sen kannalta, että jos opetellaan käyttämään nimiä jotka toimisivat oikeassakin ohjelmassa. Pallopeli.cs on taas paljon parempi (ja muista se luokan nimen suhde tiedoston nimeen).

PP

PP tehtävät (Pahasti Pihalla) on tarkoitettu niille, joilla on vaikeuksia aivan perusasioissa. Tarkoitus on että nämä ovat helpompia tehtäviä (mutta yhdessä on monta tehtävää), joiden avulla pakollisen viikottaisen 2 pisteen saaminen on mahdollista. PP tehtävät eivät ole tarkoitettu niille, jotka ovat tehneet säännöllisesti 4 tai enemmän tehtäviä/kerta. Arvioi tehtävät PP1 ja PP2 asteikolla 0..1, eli tekemällä kaikki PP-tehtävät voit saada enintään 2 pistettä.

PP-tehtäviä voivat palauttaa vain ne, ketkä osallistuvat erillisiin PP-ohjaustuokioihin. Ilmoittaudu niihin Korpissa. Jos et ole niihin osallistunut, niin voit skipata tehtävät PP1 ja PP2.

PP1

Tee funktio LaskeMatkanKesto, joka laskee, kuinka kauan jokin matka kestää annetulla matkan pituudella ja keskinopeudella, ja palauttaa tuon tiedon. Pääohjelma voisi näyttää tältä (voit copy-pastettaa tämän omaan ohjelmaasi).

public static void Main(string[] args)
{
    double matkanPituus_km = 124.5;
    double keskinopeus = 120;

    double matkanKesto = LaskeMatkanKesto(matkanPituus_km, keskiNopeus);
    Console.WriteLine("Matka kestää " + matkanKesto + " tuntia");
    Console.ReadKey();
}

Sinun pitää tehdä siis funktio LaskeMatkanKesto, sekä sille dokumentaatio. Aloita tekemällä funktion esittelyrivi (se missä on public static jne.).

Anna luokan nimeksi MatkanKesto, ja kooditiedostolle nimi MatkanKesto.cs. Palauta tuo tiedosto NettiDemoWWW:hen. Voit antaa itsellesi yhden pisteen, jos ohjelma tekee sen mitä pyydetään ja lisäksi dokumentaatiot ovat kunnossa.

PP2

Tee ohjelma, missä kysytään kolme sanaa käyttäjältä, ja tulostetaan pisin sana.

Ota tästä valmiiksi tehty pääohjelma (tätä EI tarvitse muuttaa).

public static void Main(string[] args)
{
  Console.Write("Anna 1. sana > ");
  String sana1 = Console.ReadLine();
  Console.Write("Anna 2. sana > ");
  String sana2 = Console.ReadLine();
  Console.Write("Anna 3. sana > ");
  String sana3 = Console.ReadLine();
  String pisin = Pisin(sana1, sana2, sana3);
  Console.WriteLine("Kiitos vastauksistasi! Pisin sana on \"" + pisin + "\".");
  Console.ReadKey();
}

Sinun pitää tehdä funktio Pisin, sekä sille dokumentaatio. Aloita tekemällä funktion esittelyrivi (se missä on public static jne.).

Valmis ohjelma tulostaa konsoliin esimerkiksi tällaista. ([ret] tarkoittaa Enter-näppäimen painallusta, eikä se kuulu varsinaiseen merkkijonoon.)

Anna 1. sana > Koira[ret]
Anna 2. sana > Pekka[ret]
Anna 3. sana > Kalle-Petteri[ret]
Kiitos vastauksistasi! Pisin sana on "Kalle-Petteri".

Anna luokan nimeksi PisinSana, ja kooditiedostolle nimi PisinSana.cs. Palauta tuo tiedosto NettiDemoWWW:hen. Voit antaa itsellesi yhden pisteen, jos ohjelma tekee sen mitä pyydetään ja lisäksi dokumentaatiot ovat kunnossa.

V1

Tee Ville-tehtävät: 5.6-5.8, 9.4-9.6 . Muista: Villen käyttöohje ja Ville-tehtävien palauttamisohjeet.

TDD1

Jos tarkistat vähintään kahden funktion toiminnan automaattisella testillä (ComTest), saat merkitä yhden lisäpisteen. Palauta DemoWWW:ssä tekstitiedosto tdd.txt (jonka siis arvostelet yhden pisteen arvoiseksi), missä kerrot minkä tehtävän ja minkä funktion/funktioiden toiminnan testasit. Voit antaa samassa tiedostossa palautetta ja kehitysehdotuksia Comtestin käytöstä.

Tehtävä 1

Wiki: Aliohjelmien kirjoittaminen Tee seuraavia aliohjelmakutsuja vastaavat aliohjelmien (tai funktioiden) esittelyrivit ja aliohjelmien lyhyimmät mahdolliset rungot, jotta aliohjelmat ovat syntaktisesti oikein (niiden ei tarvitse toimia loogisesti oikein, vielä). Muista kirjoittaa myös dokumentaatiot: niiden tekeminen onnistuu, vaikka ohjelma ei vielä toimikaan loogisesti oikein.

Esimerkki:

public static void Main(string[] args)
{
  // kutsu
  String lyhyempi = LyhyempiJono("Matti", "Pertti"); 
}

// seuraavaksi lyhyin mahdollinen esittelyrivi 
// ja syntaktisesti oikea runko
// muista dokumentaatio

/// <summary>
/// Palauttaa kahdesta merkkijonosta lyhyemmän.
/// </summary>
/// <param name="s1">Ensimmäinen jono</param>
/// <param name="s2">Toinen jono</param>
/// <returns>Lyhyempi jonoista</returns>
public static String LyhyempiJono(String s1, String s2)
{
    return s1;
}

Varsinainen tehtävä:

public static void Main(string[] args)
{
    StringBuilder muuttuvaJono;
    String jono;
    int i;
    double d;
    int[] luvut;
    double[] t;
    bool onko;

    muuttuvaJono = new StringBuilder("kissa istuu puussa");
    onko = PoistaJononViimeinen(muuttuvaJono, "istuu");
    luvut = new int[] {4, 2, 4, 5, 1};
    i = LaskeMaara(luvut, 4);  // laskee montako 4:sta on luvuissa
    jono = TuplaaMerkki("Janne", 'a'); // "Jaanne"
    t = LuoTaulukko(10, 3.0, 1.1);  // luo taulukon jossa on 10 lukua 
                                    // aloittaen 3.0 ja kasvattaen 1.1:llä
                                    // 3.0, 4.1, 5.2, ...
    d = Keskiarvo(t); 
}

Tehtävä 2

M: 13. Ehtolauseet. Reaalilukuja ei (erittäin harvoja erikoistilanteita lukuun ottamatta) saa verrata == -operaattorilla. Kirjoita reaalilukujen yhtäsuuruusvertailun avuksi funktiot, joita voisi käyttää esimerkiksi seuraavasti:

double a = 7.1001;
double b = 7.1002;
double c = 7.2002;
bool lahella = Samat(a, b, 0.01);
if (lahella)            Console.WriteLine("Ovat melkein samoja");
if (!Samat(a, b))       Console.WriteLine("Ovat eri suuria");
if (!Samat(a, c, 0.01)) Console.WriteLine("Ovat eri suuria");
if (Samat(a, c, 0.2))   Console.WriteLine("Ovat sinnepäin");

Samat–funktion ideana on siis se, että jos kaksi lukua ovat "riittävän" lähellä toisiaan, palautetaan true, muuten false. Esimerkissä "riittävän" on 0.01 ja 0.2. Jos tarkkuutta ei anneta, silloin käytetään esimerkiksi 0.00001 tms. varsin pientä lukua. Huomaa että toinen kutsu on vähemmillä parametreillä kuin muut (kuormitettu funktio, function overloading, hoituu C#:issa myös ns. oletusparametreillä, eng. default parameters). Tee aina ensin aliohjelma sellaiseksi, että ne ovat syntaktisesti oikein, mutta eivät tee mitään järkevää. Esimerkiksi ensin tuo aliohjelma muotoon:

public static bool Samat(double a, double b, double eps=0.00001) 
{
  return false;
}

Sitten aja ohjelma ja totea se syntaktisesti oikeaksi. Tämän jälkeen pienillä muutoksilla tee siitä kunnolla toimiva. Kiinnitä myös huomiota funktioiden kommentoitiin ja muuttujien nimeämiseen. Jos käytät Visual Studion Auto Method Stub-toimintoa, niin huomaa, että parametrien nimet eivät oletuksena ole välttämättä lainkaan kuvaavia.

Tehtävä 3

  1. M: 13. Ehtolauseet.

Kirjoita ilman minkään valmiin funktion käyttöä funktioaliohjelma jota voi kutsua muodossa

double lukuEiNegatiivisena = Itseisarvo(luku);

joka palauttaa luvun aina positiivisena (tai nollana). Aloita kirjoittamalla sopiva testipääohjelma (tai ComTest-testit), jossa kutsut funktiota erilaisilla testattavilla arvoilla.

  1. M: 9. Aliohjelman paluuarvo.

Kirjoita funktio Etaisyys, jota voidaan kutsua seuraavasti.

double a, b;
...
double etaisyys = Etaisyys(a, b);

joka palauttaa kahden reaaliluvun välisen etäisyyden. Esimerkiksi Etaisyys(3.2, 8.5) on melkein 5.3, samoin Etaisyys(8.5, 3.2). Voit hyödyntää a-kohtaa, sekä tehtävässä 2 tekemääsi Samat-funktiota.

Tehtävä 4

M: 16. Toistorakenteet, 15. Taulukot: Katso Wikipediasta Keskiluvut-kohdasta erilaisia keskilukuja. Yksi lisää voisi olla keskiarvoa lähinnä oleva joukon alkio, josta tässä käytetään nimitystä miidi. Tuollaista keskilukua ei siis oikeasti ole olemassa. Tee funktio Miidi joka palauttaa reaalilukutaulukon lukujen miidin. Et valitettavasti voi käyttää hyväksesi (eli kutsua) luentojen Summa-funktiota (koska se oli int-taulukolle), vaan joudut kopioimaan sen ja muuttamaan taulukon tyypin. Aloita kuitenkin tekemällä tuo reaalilukutaulukon keskiarvon laskeva funktio. Voit testata vaikka aineistolla:

double[] luvut = {1, 2, 3, 2, 5}; // keskiarvo == 2.6
double m1 = Miidi(luvut);              // 3
double m2 = Miidi(new double[] {1});   // 1
double m3 = Miidi(new double[] {3, 3});// 3
double m4 = Miidi(new double[] {});    // 0
// tulosta m1-m4

Vinkki 1: Lähimmän etsimiseksi unohda aluksi koko C# ja tee kynällä ja paperilla vastaava tehtävä ja mieti vaiheittain mitä joudut tekemään ja mitä "apumuuttujia" käyttämään. Ajattele niin, että joku näyttää sinulle yksi kerrallaan yhtä lukua, et tiedä tulevia etkä muistele menneitä, joten sinun täytyy "pitää muistissa" tietoa siitä, mikä on tähän mennessä lähinnä etsittävää lukua.

Vinkki 2: Tehtävän ratkaisuun on muutamia eri tapoja. Voit lähteä seuraavasta: Tee (ellet jo tehnyt) funktiot Itseisarvo, Etäisyys, Summa, Keskiarvo, sekä Miidi.

Tehtävä 5

M: 9. Aliohjelman paluuarvo. Tee funktio

Skaalaa(double luku, double min, double max)

joka skaalaa välillä [0, 1] olevan luvun välille [min, max]. Esimerkkejä:

Skaalaa(0.2, -3, 3) ~~~ -1.8;
Skaalaa(0.2, 1, 6)  ~~~ 2.0;
Skaalaa(0.0, 1, 6)  ~~~ 1.0;
Skaalaa(1.0, 1, 6)  ~~~ 6.0;

Eli esimerkiksi 1. kutsu tarkoittaa että välin [0, 1] luku 0.2 on omaan väliinsä nähden samassa suhteessa kuin luku -1.8 on väliin [-3,3]. Piirrä vaikka molemmat välit ja ko. luvut oman välinsä sisälle.

Vinkki: jos sinulla on luku väliltä [0, 1[ ja haluat saada siitä luvun välille [a, b[, niin mieti mitä pitää tehdä jotta 0:sta tulisi a ja 1:stä b. (eli f(x) = a + (b-a)*x ).

Perustelu: Edellä vinkissä on väli [0, 1[ esimerkkinä, koska ohjelmointikielten tyypillinen satunnaislukugeneraattori tuottaa lukuja puoliavoimelle välille ja jatkossa meillä on tälle Skaalaa-funktiolle käyttöä nimenomaan tuottamaan satunnaisia lukuja muillekin väleille. Tosin onneksi esim. Jypelissä tämä on valmiina. Pyöristysten kanssa on nimittäin oltava tarkkana :-)

Tehtävä 6

M: 9. Aliohjelman paluuarvo. Muistele koulusta (tai katso Wikipediasta), miten laskettiin suorakulmaisen kolmion hypotenuusa Pythagoraan lauseen avulla. Päättele, miten tämän tiedon avulla voit laskea 2-ulotteisella tasolla olevan kahden pisteen välisen etäisyyden (piirrä kuva paperille, se on 2-ulotteinen taso :-). Kirjoita sitten funktio

double Etaisyys(double x1, double y1, double x2, double y2)

joka laskee kahden pisteen p1 ja p2 (missä p1=(x1, y1) ja p2=(x2, y2)) välisen euklidisen (eli tuon meidän parhaiten ymmärtämän) etäisyyden.

B1

M: 15.5 Moniulotteiset taulukot: Tee funktioaliohjelma, joka etsii 2-ulotteisen reaalilukutaulukon suurimman alkion. Käyttöesimerkki:

public static void Main(String[] args) 
{
  double[,] mat1 = {{1, 2, 3}, {2, 2, 2}, {4, 2, 3}};
  double[,] mat2 = {{9, 2, 8}, {1, 2, 5}, {3, 19, -3}};
  double suurin1 = Suurin(mat1);
  double suurin2 = Suurin(mat2);        
}

Tulosta lopuksi tulos.

B2

M: 13. Ehtolauseet: Tehtävässä 2 vertailit ehkä lukujen absoluuttista suuruutta. Kuitenkin esimerkiksi 1000 ja 1100 ovat samoja 10% tarkkuudella, mutta eivät 0.1:n tarkkuudella. Usein voikin jolla järkevä puhua suhteellisesti yhtäsuuruudesta absoluuttisen yhtäsuuruuden sijaan. Kirjoita vielä yksi funktioaliohjelma, jolle pätee:

SuhtSamat(0.10, 0.12, 0.1) === false
SuhtSamat(0.10, 0.11, 0.1) === true
SuhtSamat(1.0, 1.2, 0.1)   === false
SuhtSamat(1.0, 1.1, 0.1)   === true
SuhtSamat(10, 12, 0.1)     === false
SuhtSamat(10, 11, 0.1)     === true
SuhtSamat(1000, 1200, 0.1) === false
SuhtSamat(1000, 1100, 0.1) === true

B3

Ota edellisen demokerran Sopulit.cs ja tee siitä graafinen versio jossa on paljon ruutuja (suuruusluokkaa 60 y-suunnassa) ja sukupolvia lasketaan 0.1 sekunnin välein. Luo uusi FysiikkaPeli (tai PerusPelikin käy aivan hyvin, siinä ei ole fysiikka eikä sitä tässä tarvita) ja kirjoita luokkaan seuraava koodi ja täydennä aliohjelmat (ja metodit). Liitä projektiin tuo Sopulit.cs ja käytä sitä sukupolvi-taulukoiden päivittämiseen.

Projektiin liittäminen:

  • kopioi tiedosto samaan paikkaan kuin projektin muut .cs tiedostot
  • Solution Explorerissa paina hiiren oikeata projektin nimen päällä
  • Add/Existing? Item
  • valitse lisättävä tiedosto Add
  • lisää omaan .cs tiedostoon alkuun (tässä esimerkissä)
    using Demo5;
    
    tällöin kääntäjä tietää että esim kutsut:
    Sopulit.Arvo(sukupolvi, 0, 1);
    Demo5.Sopulit.Arvo(sukupolvi, 0, 1);
    
    ovat samoja.

Koodipohjaa uudelle pelille:

public class GameOfLife : PhysicsGame
{
    private const int NY = 60;
    private int[,] sukupolvi;
    private int[,] seuraavaSukupolvi;
    private GameObject[,] oliot;
    private Timer timer = new Timer();
    private Color[] varit = {Color.Black, Color.White};

    public override void Begin()
    {
        Level.Background.Color = Color.Black;

        double dy = Screen.Height / NY; // Lasketaan ruudun korkeus pikseleinä
        int nx = (int)(Screen.Width / dy);  // ja montako mahtuu X-suuntaan
        int ny = NY;

        sukupolvi = new int[ny, nx];     //  Luodaan taulukot
        seuraavaSukupolvi = new int[ny, nx];
        oliot = new GameObject[ny, nx];
        // seuraavaSukupolvi = sukupolvi; // jos tämä on mukana, käyttäytyy eri tavalla

        LuoOliot(this,oliot);

        Camera.ZoomToAllObjects();

        timer.Interval = 0.1; // timeri antamaan tapahtuma 0.1 sek välein
        timer.Timeout += LaskeJaPiirraSeuraavaSukupolvi;
        ArvoSukupolvi();  // jos halutaan käynnistää automaattisesti
    }

    private void ArvoSukupolvi()
    {
        Sopulit.Arvo(sukupolvi, 0, 1);
        timer.Start();
    }

    private static void LuoOliot(PhysicsGame game, GameObject[,] oliot)
    {
      /// Täydennä niin, että luodaan yhtä monta suorakaidetta kuin
      /// on taulukoissa alkioita ja kukin luotu suorakaide tallennetaan
      /// oliot taulukkoon, jotta sen väriä päästään muuttamaan jatkossa.
      /// Koordinaatison yksikkönä kannattaa käyttää 1 ruutu = 1 yksikkö,
      /// ja origo on vasemmalla alhaalla.  Kameran zoomaus hoitaa
      /// kuvan kuntoon sitten.   
    }

    private void LaskeJaPiirraSeuraavaSukupolvi()
    {
      /// Täydennä niin, että lasketaan seuraava sukupolvi 
      /// (ks. demo5:n SeuraavaSukupolvi, kutsu sitä)
      /// ja sitten sukupolven taulukosta otetaan värit oliot-taulukon
      /// olioille siten että 0 => musta ja 1 => valkoinen
    }
}

G1

Täydennä GameOfLife (ks. B3) siten, että voit Delete napista tyhjentää kentän ja sitten hiirellä klikkailla päälle ja pois ruutuja. Hiiren klikkaus pysäyttää aina animaation ja sitten voi rauhassa rakentaa haluamansa kuvion (ks: http://www.bitstorm.org/gameoflife/ ja Wikipedia) ja sitten laittaa Enter-nappulalla animoinnin uudelleen käyntiin. Välilyönti arpoo kokonaan uuden alkutilanteen.

Varalla

M: 15. Taulukot, 16. Toistorakenteet. Muuta tehtävän 1 pääohjelma sellaiseksi, että luvut kysytään silmukassa.