\documentclass[a4paper,10pt]{article}
\usepackage{amsmath}                    % Lisäyksiä LaTeXin matematiikkatukeen
%\usepackage{SIunits}                   % Tuki SI-yksiköille (esim. \meter\per\second)
\usepackage[latin1]{inputenc}           % Ääkköstuki
%\usepackage{color}                     % Tuki väritetylle tekstille
\usepackage{array}
% \usepackage{multirow}                   % Tuki useita rivejä kattaville taulukkoelementeille
\usepackage[T1]{fontenc}
\usepackage[finnish]{babel}             % Suomenkielinen lokalisaatio
\usepackage{graphicx}                   % Tuki EPS-kuville

% ---
% Tästä voit muokata sivun marginaaleja.
% ---
\addtolength{\evensidemargin}{-1cm}
\addtolength{\oddsidemargin}{-1cm}
\addtolength{\textwidth}{2cm}
\addtolength{\topmargin}{-0.5in}
\addtolength{\textheight}{0.5in}
\setlength{\parindent}{0in}             % Kappaleiden sisennys pois päältä
\setlength{\parskip}{4pt plus 4pt minus 2pt}               % Tyhjää tilaa kappaleiden välillä

% ---
% Ohjeita LaTeXin käyttöön esimerkiksi täältä: http://www.andy-roberts.net/misc/latex/
% LaTeX-editoreja: http://en.wikipedia.org/wiki/LaTeX#Editors_and_IDEs
% ---

\begin{document}
\emergencystretch=16pt
% \overfullrule=5pt

% ---
% Otsikkosivu. Tämä olisi kiva toteuttaa käyttäen hyväksi LaTeXin ohjelmoitavuutta.
% ---

\begin{center}
Tampereen teknillinen yliopisto

Ohjelmistotekniikan laitos
\vspace{4cm}

\rule{4.5in}{0.4mm}

\begin{Huge}
OHJ-1151

Ohjelmointi IIe
\end{Huge}

\vspace{0.4cm}
\begin{Large}
Harjoitustyö

\vspace{-2mm}
Hervannan Hakkerit

\vspace{-1mm}
Loppudokumentti %Poista tarpeeton
\end{Large}
\rule{4.5in}{0.4mm}

\end{center}

\vfill
21.5.2008

Petteri Aimonen (205441)

\vspace{-2mm}
aimonen

\vspace{-2mm}
petteri.aimonen@tut.fi
\newpage
% 
% \tableofcontents
% 
% \newpage

% -----------------------
% Kappaleet alkavat tästä
% -----------------------

% ---
% Esimerkki EPS-muotoon tallennetun UML-kaavion liittämisestä dokumenttiin.
% Huomaa, että EPS-kuvia käytettäessä suora käännös PDF-muotoon 'pdflatex':lla
% ei onnistu (ainakaan ilman kikkailua). Käännös tulee suorittaa ensin DVI-muotoon,
% mistä edelleen PDF:ksi. Muita kuvamuotoja käytettäessä tilanne on ymmärtääkseni
% päinvastainen, eli ainoastaan 'pdflatex' toimii.
% ---
%\section*{UML-kaavio}
%\begin{center}
%\includegraphics[scale=0.6, trim = 0 0 0 0, clip]{classes2}
%\end{center}

\section{Ohjelman rakenne}

\subsection{Ohjelman tietorakenteet}
Pelitilanne sisältyy Pelitilanne-structiin, jossa on tallennettuna kaikki
peliin liittyvä tilatieto.

\begin{verbatim}
struct Asetukset {
    Asetukset();

    // Yleisasetustiedoston tiedot.
    int koodauskulutus;
    int reitityskulutus;
    int puskurointikulutus;
    int tankkauskulutus;
    int ryhmanvarastot;
    int kolauusiutuminen;
};
\end{verbatim}

\begin{verbatim}
struct Hakkeriryhma {
    Hakkeriryhma(const std::string &ryhmannimi);

    std::string nimi; // Hakkeriryhmän nimi.
    std::vector<Hakkeri *> hakkerit; // Osoittimet ryhmän hakkereihin.
    int vuoro; // Vuorossa olevan hakkerin indeksi.
};
\end{verbatim}

\begin{verbatim}
struct Pelitilanne {
    Pelitilanne();

    Asetukset asetukset; // Pelin yleisasetukset.
    std::vector<Hakkeriryhma> ryhmat; // Peliin kuuluvat hakkeriryhmät.
    int vuoro; // Vuorossa olevan ryhmän indeksi.
    
    bool lopetettu; // True, jos pelissä ei enää suoriteta vuoroja.
};
\end{verbatim}

Pelin siirrot tallennetaan structiin, jossa on siirron tyyppi enumina ja siirtoon
liittyvien hakkerien tunnisteet. Structin kenttien käyttö riippuu siirtotyypistä.
Tämä struct voidaan välittää parametrina mm. siirron oikeellisuuden tarkistusta varten.

\begin{verbatim}
enum Siirtotyyppi {EISIIRTOA, KOODAA, PUSKUROI, REITITA, RESETOI, HYOKKAA,
                   TANKKAA, ODOTA};
\end{verbatim}

\begin{verbatim}
struct Siirto {
    Siirto(Siirtotyyppi ntyyppi);

    Siirtotyyppi tyyppi; // Siirron tyyppi - määrää muiden kenttien käytön
    int maara; // KOODAA: maara on koodattavan viruksen taso.
        // TANKKAA: tankattavan kolan määrä.
        // PUSKUROI, REITITA: maara on puskurointien/reititysten määrä.
        // RESETOI, HYOKKAA, ODOTA: kenttä ei ole käytössä.

    Hakkeri *kohde; // Vain HYOKKAA-komennossa, jossa osoitin hyökkäyksen
                    // kohteena olevaan hakkeriin
    
    std::vector<Hakkeri *> reitityskohteet; // Vain REITITA-komennossa,
                // jossa vektori osoittimista annettuihin reitityskohteisiin.
};
\end{verbatim}

\subsection{Ohjelman suoritus}
Pääohjelma alustaa ensiksi pelitilanteen ja lukee sitten käyttäjältä rivejä,
jotka välitetään komentotulkille. Komentotulkki suorittaa komennot ja välittää
siirrot Hakkeri-olioiden suoritettavaksi. Pääohjelman silmukka loppuu, kun
siirron jälkeen havaitaan pelin loppuneen. Lopuksi tulostetaan mahdollinen
voittaja, ja lopputilanne.

\begin{figure}[hbt]
\begin{center}
\includegraphics[scale=0.7]{kaavio.eps}
\caption{Ohjelman suoritus}
\end{center}
\end{figure}

\section{Moduulit}

\subsection{Pääohjelma}
\subsubsection{Apufunktio: tulkitseParametrit}
Tulkitsee komentoriviparametrit ja toteuttaa ne.
Voi tuottaa poikkeukset Parametrivirhe, Avausvirhe ja Lukuvirhe.

{\em Esittely:}\\
{\tt bool tulkitseParametrit(int argc, char *argv[], Pelitilanne \&pelitilanne);}

{\em Parametrit:}\\
Parametri argc ja argv ovat pääohjelman saamat parametrit.\\
Parametri pelitilanne on struct, johon luetut asetukset ja ryhmät tehdään.

{\em Paluuarvo:}\\
Palauttaa true jos peli alkaa normaalisti.
Palauttaa false, jos parametrina oli $--$help, eli pelin ei pidä alkaa.

{\em Toiminta:}\\
Käydään parametrit järjestyksessä läpi. Jos parametri on -a, -1 tai -2, seuraava
on sille annettu tiedostonimi. Tiedostonimet tallennetaan väliaikaismuuttujiin.

Jos parametri on -h tai $--$help, tulostetaan ohjeteksti, asetetaan lopetusarvo ja palautetaan false.

Virheitä havaittaessa tuotetaan poikkeus ja vapautetaan jo luodut hakkerit.

{\em Käyttö:}\\
Pääohjelma kutsuu tätä funktiota.

\subsubsection{Funktio: main}
{\em Toiminta:}\\
Pelitilanne on mainissa paikallisena muuttujana, joka alustetaan apufunktiolla tulkitseParametrit.
Jos se tuottaa poikkeuksen, tulostetaan vastaava virheilmoitus ja lopetetaan
suoritus paluuarvolla {\bf EXIT\_FAILURE}. Jos poikkeusta ei tule, mutta paluuarvo
on false, on kyseessä ohjetekstin tulostus ja lopetetaan paluuarvolla {\bf EXIT\_SUCCESS}.

Muutoin siirrytään pääsilmukkaan. Tulostetaan komentotulkin kautta haettu
kehote ja luetaan {\tt std::getline}:lla yksi rivi.
Mikäli rivi alkaa risuaidalla, luetaan uusi rivi.
Välitetään luettu rivi komentotulkin toteutaRivi-funktiolle. Silmukka päättyy
kun vuoron suorittamisen seurauksena {\em pelitilanne.lopetettu} on true
tai rivin lukeminen epäonnistuu.

Silmukan jälkeen tulostetaan lopputilanne ja lopetetaan paluuarvolla EXIT\_SUCCESS.

\subsection{Moduuli: Komentotulkki}
Komentotulkki hoitaa komentojen tulkitsemisen ja osittain suorittamisen.

Tässä moduulissa on määritelty tietotyypit ja olemassaolevat komennot:
\begin{verbatim}
enum Toiminto {EITOIMINTOA, SIIRTO, TILANNE, TIEDOT, OHJE, LOPETUS};

struct Komento {
    std::string komento;
    Toiminto toiminto;
    SiirtoTyyppi siirtotyyppi; // Vain jos toiminto == SIIRTO
    int parametreja_enintaan; // Jos == -1, tarkistus ei käytössä
    int parametreja_vahintaan; // Jos == -1, tarkistus ei käytössä
};
\end{verbatim}

\begin{verbatim}
const Komento KOMENNOT[] = {{"LOPETA", LOPETUS, 0, 0, 0},
                            {"HYOKKAA", SIIRTO, HYOKKAA, 1, 1},
                            ...
                            };
\end{verbatim}

\subsubsection{Funktio: toteutaRivi}
Toteuttaa rivin tai tulostaa virheen.

{\em Esittely:}\\
{\tt bool toteutaRivi(const std::string \&rivi, Pelitilanne \&pelitilanne,\\
std::ostream \&out, std::ostream \&err);}

{\em Parametrit:}\\
Parametri {\bf rivi} on käyttäjältä luettu kokonainen rivi.\\
Parametri {\bf pelitilanne} on struct, johon komennot suoritetaan.\\
Parametri {\bf out} on virta, johon pelin normaalit tulosteet tulostetaan.\\
Parametri {\bf err} on virta, johon virheilmoitukset tulostetaan.\\

{\em Paluuarvo:}\\
False, jos komennon suorituksessa oli virhe.

{\em Toiminta:}\\
Rivin tyhjällä tilalla erotellut osat luetaan {\em merkkijonokasittely}-moduulin
lueOsat-funktiolla vektoriin. Jos vektori on tyhjä (tarkoittaa että rivillä on vain tyhjää
tilaa) palautetaan {\bf false} tulostamatta mitään. Muutoin ensimmäinen osa
eli komentosana välitetään etsiKomento-funktiolle, joka etsii vakiotaulukosta
vastaavan Komento-structin.

Jos tämä palauttaa {\bf false}, tulostetaan ``Virhe: Tuntematon komento.''
virhevirran kautta ja palautetaan {\bf false}.

Loppujen osien määrää verrataan komennon {\tt parametreja\_enintaan}- ja
{\tt pa\-ra\-met\-re\-ja\_va\-hin\-taan}-kenttiin. Virheistä tulostetaan joko
``Virhe: Liikaa parametreja.'' tai ``Virhe: Liian vähän parametreja.'' ja
palautetaan {\bf false}.

Muutoin komento ja parametrit välitetään toteutaKomento-funktiolle.

{\em Käyttö:}\\
Pääohjelma kutsuu tätä luettuaan käyttäjältä rivin.

\subsubsection{Funktio: etsiKomento}
Tulkitsee komennon etsimällä vastaavan komennon vakiotaulukosta.
Palauttaa false jos vastaavuuksia ei ole tai on liikaa.

{\em Esittely:}\\
{\tt bool etsiKomento(const std::string \&annettu, Komento \&tulos);}

{\em Parametrit:}\\
Parametri {\bf annettu} on käyttäjän antama komentosana.\\
Parametri {\bf tulos} on viite, jonka kautta löydetty structi tallennetaan.

{\em Paluuarvo:}\\
True, jos löytyi täsmälleen yksi vastaavuus.

{\em Toiminta:}\\
Käydään KOMENNOT-vakiotaulukko järjestyksessä läpi. Komentoja verrataan
merkkijonomoduulin funktiolla vertaaLyhennos ja lasketaan vastaavuudet.
Jos vastaavuuksia on tasan yksi, tallennetaan se ja palautetaan {\bf true}.
Muutoin palautetaan {\bf false}.

{\em Käyttö:}\\
Funktio toteutaRivi tulkitsee tämän funktion avulla käyttäjän antaman komennon.

\subsubsection{Funktio: toteutaKomento}
Tulkitsee parametrit ja toteuttaa komennon.
Virheen sattuessa palauttaa false ja tulostaa virheen.

{\em Esittely:}\\
{\tt bool toteutaKomento(const Komento \&komento,\\
const std::vector<std::string> \&parametrit, Pelitilanne \&pelitilanne,\\
std::ostream \&out, std::ostream \&err);}

{\em Parametrit:}\\
Parametri {\bf komento} on tulkittu Komento-struct.\\
Parametri {\bf parametrit} on vektori annetuista parametreista.\\
Parametri {\bf pelitilanne} on struct, johon komento toteutetaan.\\
Parametri {\bf out} on tulostevirta, ja parametri {\bf err} on virhevirta.\\

{\em Paluuarvo:}\\
True, jos komennossa ei ollut virheitä.

{\em Toiminta:}\\
Jos komennon tyyppi on SIIRTO, tulkitaan parametrit tulkitseSiirto-funktiolla.
Se voi tuottaa Hakkerivirhe- ja Lukuvirhe-poikkeukset komennon virheistä.
Ne kaapataan myöhemmin tässä funktiossa.
Tulkittu siirto tarkistetaan vuorossa olevan hakkerin tarkistaSiirto-jäsenfunktiolla,
joka hoitaa myös virheilmoitusten tulostamisen. Jos virheitä ei ollut,
kutsutaan {\em hakkeripeli}-moduulin suoritaSiirto-funktiota, joka hoitaa
myös vuoron vaihtamisen.

Muut komentotyypit suoritetaan vastaavasti, lähinnä {\em hakkeripeli}-moduulin
funktioita hyödyntäen tai suoraan tietoja tulostaen ja muuttaen.

Mahdolliset Lukuvirheet ja Hakkerivirheet kaapataan, ja tulostetaan niistä
virheilmoitukset. Hakkerivirhe ilmaisee olematonta hakkeria, ja Lukuvirhe
parametria, joka ei ollut positiivinen kokonaisluku.

{\em Käyttö:}\\
Funktio toteutaRivi käyttää tätä funktiota.

\subsubsection{Funktio: tulkitseSiirto}
Tulkitsee siirroksi tiedetyn komennon parametrit
Siirto-rakenteeseen. Tuottaa poikkeuksen Lukuvirhe tai Hakkerivirhe jos
parametreissa on virheitä.

{\em Esittely:}\\
{\tt Siirto tulkitseSiirto(const Komento \&komento, \\
const std::vector<std::string> \&parametrit, const Pelitilanne \&pelitilanne);}

{\em Parametrit:}\\
Parametri {\bf komento} on tulkittu Komento-struct.\\
Parametri {\bf parametrit} on vektori annetuista parametreista\\
Parametri {\bf pelitilanne} on struct, josta haetaan osoittimet siirron
kohteita varten.

{\em Paluuarvo:}\\
Paluuarvo on Siirto-struct, johon tulkitut tiedot on tallennettu. Mahdolliset
virheet hoidetaan poikkeuksin.

{\em Toiminta:}\\
Komento-structista saadaan tieto siirron tyypistä. Parametrit tulkitaan
hyödyntäen kokonais\-luvuksi- ja etsiHakkeri\_P-funktioita. Jälkimmäinen on
apufunktio, joka tuottaa poikkeuksen hakkerin puuttuessa. Normaali
etsiHakkeri ei tätä tee, jotta sillä voi tehokkaasti myös tarkistaa
että nimi on vapaana.

{\em Käyttö:}\\
Funktio toteutaKomento kutsuu tätä funktiota parametrien tulkitsemiseksi.

\subsection{Moduuli: Merkkijonokäsittely}
Tämä moduuli sisältää pieniä merkkijonojen käsittelyyn käytettyjä
apufunktioita:
\begin{verbatim}
// Lukee kaikki välilyönnillä erotellut osat virrasta vektoriin
void lueOsat(std::istream &virta, std::vector<std::string> &tulos);

// Muuntaa merkkijonon positiiviseksi kokonaisluvuksi.
// Tuottaa poikkeuksen Lukuvirhe jos merkkijonossa on virheitä.
// Poikkeuksen parametrina on annettu merkkijono.
int kokonaisluvuksi(std::string merkkijono);

// Vertaa merkkijonoja kirjainkokoriippumattomasti.
bool vertaaKKR(const std::string &eka, const std::string &toka);

// Vertaa merkkijonoja kirjainkokoriippumattomasti siten,
// että riittää kun annettu on kokonaisen alku.
bool vertaaLyhennos(const std::string &annettu,
                    const std::string &kokonainen);
\end{verbatim}

\subsection{Moduuli: Poikkeukset}
Tämä moduuli sisältää pelin käyttämät poikkeusluokat:
\begin{itemize}
\item {\bf Parametrivirhe:} Ohjelmaa käynnistettäessä on annettu virheellinen
komentoriviparametri. Käytössä pääohjelmassa.
\item {\bf Avausvirhe:} Asetustiedostoa ei voitu avata. Käytössä
hakkeripeli-moduulissa, kaapataan pääohjelmassa. Parametrina tiedoston nimi.
\item {\bf Lukuvirhe:} Lukemisessa tapahtunut virhe, asetustiedostoissa
tai komentotulkin komentojen parametreissa. Parametri on virheeseen liittyvä
merkkijono, kuten tiedoston nimi tai virheellinen komennon parametri.
\item {\bf Hakkerivirhe:} Komentotulkille annettua hakkerin nimeä ei löydy.
Parametrina annettu nimi.
\end{itemize}

\subsection{Moduuli: Hakkeripeli}
Sisältää pelitilanteen hallintaan liittyviä funktioita.

\subsubsection{Funktio: lueYleisasetukset}
Yleisasetustiedoston lukeminen.
Virheen sattuessa tuottaa poikkeuksen Lukuvirhe.
Funktiosta on myös ylikuormitettu versio, joka ottaa hoitaa tiedoston avauksen
sekä lisää tiedostonimen tuotettuihin poikkeuksiin.

{\em Esittely:}\\
{\tt void lueYleisasetukset(std::istream \&virta, Asetukset \&tulos);}

{\em Parametrit:}\\
Parametri {\bf virta} on virta, josta asetustiedosto luetaan.\\
Parametri {\bf tulos} on struct, johon yleisasetukset luetaan.

{\em Paluuarvo:}\\
Ei paluuarvoa.

{\em Toiminta:}\\
Syötevirrasta luetaan kaikki osat lueOsat-funktiolla. Jos osia ei ole
tasan 6, tuotetaan Lukuvirhe-poikkeus. Osat muunnetaan kokonaisluvuiksi
kokonaisluvuksi-funktiolla, joka myös tuottaa tarvittaessa virheen.

Osat tallennetaan järjestyksessä tulos-structin kenttiin {\em koodauskulutus},
{\em reitityskulutus}, {\em puskurointikulutus}, {\em tankkauskulutus},
{\em ryhmanvarastot} ja {\em kolauusiutuminen}.

{\em Käyttö:}\\
Pääohjelman tulkitseParametrit-funktio kutsuu tätä funktiota asetustiedoston
lukemiseksi.

\subsubsection{Funktio: lueHakkeriryhma}
Hakkeriryhmän tiedoston lukeminen ja Hakkeri-olioiden luominen.
Virheen sattuessa tuottaa poikkeuksen Lukuvirhe.
Funktiosta on myös ylikuormitettu versio, joka ottaa hoitaa tiedoston avauksen
sekä lisää tiedostonimen tuotettuihin poikkeuksiin.

{\em Esittely:}\\
{\tt void lueHakkeriryhma(std::istream \&virta, Pelitilanne \&pelitilanne);}

{\em Parametrit:}\\
Parametri {\bf virta} on virta, josta hakkeriryhmätiedosto luetaan.\\
Parametri {\bf pelitilanne} on struct, johon luetut hakkeriryhmät tallennetaan.

{\em Paluuarvo:}\\
Ei paluuarvoa.

{\em Toiminta:}\\
Aluksi luetaan {\em std::getline}:llä ryhmän nimi. Loput rivit
käydään järjestyksessä läpi ja jaetaan osiin. Osista tulkitaan
hakkerin nimi sekä hermo- ja kolavarannot, jotka välitetään Hakkeri-luokan
rakentajalle.

Osoittimet dynaamisesti luotuihin hakkereihin tallennetaan Hakkeriryhma-structin
sisällä olevaan vektoriin.

Lisäksi suoritetaan muita tarkistuksia, kuten nimien yksiselitteisyys
sekä osuuksien summa. Näistäkin tuotetaan poikkeus Lukuvirhe.
Poikkeuksen sattuessa luodut hakkerit vapautetaan eikä ryhmää lisätä tilanteeseen.

{\em Käyttö:}\\
Pääohjelman tulkitseParametrit-funktio käyttää tätä hakkeriryhmien lukemiseen.


\subsubsection{Funktio: etsiHakkeri}
Etsii hakkerin nimeä annetuista ryhmistä. Palauttaa osoittimen tai NULL.

{\em Esittely:}\\
{\tt Hakkeri *etsiHakkeri(const std::string \&nimi,\\
const std::vector<Hakkeriryhma> \&ryhmat);}

{\em Parametrit:}\\
Parametri {\bf nimi} on etsitty hakkerin nimi, joka on kirjainkokoriippumaton.\\
Parametri {\bf ryhmat} on Hakkeriryhmat-structeja sisältävä vektori, josta hakkeria etsitään.

{\em Paluuarvo:}\\
Paluuarvo on osoitin etsittyyn Hakkeri-olioon, tai NULL jos hakkeria ei löytynyt.

{\em Toiminta:}\\
Ryhmien hakkerit käydään läpi ja nimiä verrataan vertaaKKR-funktiolla.
Löydetty hakkeri palautetaan heti. Silmukan loputtua palautetaan {\bf NULL}.

{\em Käyttö:}\\
Hakkeriryhmien lukeminen tarkistaa tällä ettei nimi ole vielä käytössä.
Komentotulkki käyttää tätä komentojen tulkitsemisessa.

\subsubsection{Funktio: haeVuoro}
Palauttaa osoittimen vuorossa olevaan hakkeriin.

{\em Esittely:}\\
{\tt Hakkeri *haeVuoro(const Pelitilanne \&pelitilanne);}

{\em Parametrit:}\\
Parametri {\bf pelitilanne} on struct, josta vuoro haetaan.

{\em Paluuarvo:}\\
Paluuarvo on osoitin vuorossa olevaan Hakkeri-olioon.

{\em Toiminta:}\\
Palautetaan suoraan pelitilaneen ja hakkeriryhmän vuoro-indeksien perusteella
haettu osoitin.

{\em Käyttö:}\\
Tätä funktiota käytetään eri osista ohjelmaa, eniten komentotulkista
ja hakkeripeli-moduulista.

\subsubsection{Funktio: laskeKulutus}
Laskee siirron kolakulutuksen.

{\em Esittely:}\\
{\tt int laskeKulutus(const Siirto \&siirto, const Asetukset \&asetukset) ;}

{\em Parametrit:}\\
Parametri {\bf siirto} on Siirto-struct, jonka sisältämän siirron kulutus lasketaan. \\
Parametri {\bf asetukset} on Yleisasetukset-struct, josta haetaan kulutustiedot.

{\em Paluuarvo:}\\
Paluuarvo on kokonaisluku, joka ilmaisee kulutetun kolan määrän.

{\em Toiminta:}\\
Siirron tyypin ja määrän perusteella palautetaan kulutus. Jos siirto ei kuluta
kolaa, palautetaan {\bf 0}.

{\em Käyttö:}\\
Hakkeri-oliot käyttävät tätä suorittaessaan siirtoa.

\subsubsection{Funktio: suoritaSiirto}
Suorittaa siirron vuorossa olevalla hakkerilla, tulostaa ilmoitukset
ja vaihtaa tarvittaessa vuoron.

{\em Esittely:}\\
{\tt void suoritaSiirto(const Siirto \&siirto, Pelitilanne \&pelitilanne,\\
std::ostream \&out);}

{\em Parametrit:}\\
Parametri {\bf siirto} on suoritettava siirto.\\
Parametri {\bf pelitilanne} on struct, johon siirto suoritetaan.\\
Parametri {\bf out} on tulostevirta.

{\em Paluuarvo:}\\
Ei paluuarvoa.

{\em Toiminta:}\\
Vuorossa olevan hakkerin suoritaSiirto-jäsenfunktiota kutsutaan. Se palauttaa
tiedon vuoron vaihtumisesta.

Jos vuoro vaihtuu, etsitään seuraavaksi vuoroon tuleva hakkeri, lisätään sille
kolaa ja tulostetaan ilmoitus vuorossa olevasta hakkerista.

Pelin loppuminen tarkistetaan peliLoppu-funktiolla. Pelin loppuessa
asetetaan pelitilanne.lopetettu trueksi, jolloin pääohjelman silmukka
päättyy.

{\em Käyttö:}\\
Komentotulkki kutsuu tätä siirron suorittamiseksi.

\subsubsection{Funktio: ryhmaMukana}
Tarkistaa, että ryhmä on yhä mukana pelissä.

{\em Esittely:}\\
{\tt bool ryhmaMukana(const Hakkeriryhma \&ryhma);}

{\em Parametrit:}\\
Parametri {\bf ryhma} on hakkeriryhmä, jonka aktiivisuus tarkistetaan.

{\em Paluuarvo:}\\
True, jos ryhmässä on ainakin yksi aktiivinen hakkeri.

{\em Toiminta:}\\
Ryhmän hakkerit käydään läpi, ja tarkistetaan niiden aktiivisuus online-jäsenfunktiolla.
Aktiivisen hakkerin löydyttyä palautetaan {\bf true}, muutoin silmukan loputtua {\bf false}.

{\em Käyttö:}\\
Funktio peliLoppu käyttää tätä ryhmien tarkistamiseen ja pääohjelma voittajan
toteamiseen.

\subsubsection{Funktio: peliLoppu}
Tarkistaa, onko peli loppu.

{\em Esittely:}\\
{\tt bool peliLoppu(const std::vector<Hakkeriryhma> \&ryhmat);}

{\em Parametrit:}\\
Parametri {\bf ryhmat} on vektori pelissä olevista hakkeriryhmistä.

{\em Paluuarvo:}\\
True, jos pelissä on enää yksi aktiivinen ryhmä.

{\em Toiminta:}\\
Käydään ryhmät läpi ja tarkistetaan aktiivisuus ryhmaMukana-funktiolla.
Jos aktiivisia ryhmiä oli alle kaksi, palautetaan {\bf true}.

{\em Käyttö:}\\
Siirron suoritettuaan suoritaSiirto tarkistaa tällä funktiolla, onko peli päättynyt.

\subsubsection{Funktio: tulostaVuoro}
Tulostaa vuorossa olevan ryhmän ja hakkerin nimen.

{\em Esittely:}\\
{\tt void tulostaVuoro(const Pelitilanne \&pelitilanne, std::ostream \&out);}

{\em Parametrit:}\\
Parametri {\bf pelitilanne} on struct, josta vuoro haetaan.\\
Parametri {\bf out} on tulostusvirta.

{\em Paluuarvo:}\\
Ei paluuarvoa.

{\em Toiminta:}\\
Haetaan vuorossa olevan hakkeriryhmän ja hakkerin nimet, ja tulostetaan ne.

{\em Käyttö:}\\
Funktio suoritaSiirto kutsuu tätä funktiota vaihdettuaan vuoron.

\subsubsection{Funktio: vapautaHakkerit}
Vapauttaa dynaamisesti luodut Hakkeri-oliot ja tyhjentää vektorit.

{\em Esittely:}\\
{\tt void vapautaHakkerit(std::vector<Hakkeriryhma> \&ryhmat);}

{\em Parametrit:}\\
Parametri {\bf ryhmat} on vektori vapautettavista hakkeriryhmistä.
Se tyhjennetään vapautuksen jälkeen.

{\em Paluuarvo:}\\
Ei paluuarvoa.

{\em Toiminta:}\\
Ryhmät käydään läpi ja hakkerit vapautetaan.

{\em Käyttö:}\\
Pääohjelma kutsuu tätä pelin päätyttyä.

\subsubsection{Funktio: tulostaTilanne}
Tulostaa pelitilanteen TILANNE-komentoa varten.

{\em Esittely:}\\
{\tt void tulostaTilanne(const std::vector<Hakkeriryhma> \&ryhmat,\\
std::ostream \&out);}

{\em Parametrit:}\\
Parametri {\bf ryhmat} on vektori hakkeriryhmistä, joiden hakkerit tulostetaan.\\
Parametri {\bf out} on tulostusvirta.

{\em Paluuarvo:}\\
Ei paluuarvoa.

{\em Toiminta:}\\
Ryhmien hakkerit käydään läpi ja kutsutaan niiden tulostaTilanne-jäsenfunktiota
tilannerivin tulostamiseksi.

{\em Käyttö:}\\
Komentotulkki kutsuu tätä TILANNE-komennon yhteydessä.

\subsection{Luokka: Hakkeri}
Yksittäinen hakkeri. Pitää kirjaa hakkerin hermoista ja kolavarannosta.

Sisältää HakkeriTila-tyypin:
\begin{verbatim}
enum HakkeriTila {PERUS, HYOKKAYS, REITITYS, PUSKUROINTI, OFFLINE};
\end{verbatim}

\subsubsection{Jäsenmuuttujat}
\begin{verbatim}
// Viite yleisasetukset sisältävään structiin.
const Asetukset &asetukset_;

std::string nimi_; // Hakkerin nimi.
HakkeriTila tila_; // Hakkerin tila.

// Hakkerin hallussa olevan viruksen taso. Arvoltaan 0 jos hakkerilla ei ole
// virusta.
int virustaso_; 

// Hakkerit, joille viruksia reititetään. Vain REITITYS-tilassa.
std::vector<Hakkeri *> reitityskohteet_;
int reititysvuoro_; // Seuraavan kohteen indeksi.

// Jäljellä olevien toimintokertojen määrä. Vain REITITYS- ja
// PUSKUROINTI-tiloissa.
int maara_;

int kola_; // Pitää kirjaa hakkerin kolavarannoista.
int hermot_; // Pitää kirjaa hakkerin hermoista.
int maxkola_; // Hakkerin kolavarastojen maksimikoko (alkukoko).
int maxhermot_; // Vastaavasti hermojen maksimiarvo.
\end{verbatim}

\subsubsection{Yksinkertaiset jäsenfunktiot}
Tässä pelkkä lista jäsenfunktioista, joiden toteutuksessa ei ole mitään ihmeellistä:
\begin{verbatim}
const std::string &haeNimi() const; // Palauttaa hakkerin nimen
bool online() const; // Onko hakkerin tila jokin muu kuin OFFLINE?

// Lisäksi automaattisesti tuotetut kopiorakentaja ja sijoitusoperaattori.
\end{verbatim}

\subsubsection{Rakentaja, parametrina asetustiedostosta lasketut tiedot}
{\em Esittely:}\\
{\tt Hakkeri(const Asetukset \&asetukset,\\
const std::string \&nimi, int maxkola, int maxhermot);}

{\em Parametrit:}\\
Parametri {\bf asetukset} on viite pelin yleisasetukset sisältävään structiin.\\
Parametri {\bf nimi} on hakkerin nimi.\\
Parametri {\bf maxkola} on hakkerin kolavarastojen suurin koko ja samalla alkukoko.\\
Parametri {\bf maxhermot} on vastaavasti hakkerin hermojen määrä.

{\em Jäsenmuuttujien alustus:}\\
Rakentajan alustuslista:
\begin{verbatim}
asetukset_(asetukset),
nimi_(nimi),
tila_(PERUS),
virustaso_(0),
reitityskohteet_(),
maara_(0),
kola_(maxkola),
hermot_(maxhermot),
maxkola_(maxkola),
maxhermot_(maxhermot)
\end{verbatim}

\subsubsection{Julkinen jäsenfunktio: lisaaKolaa}
Suorittaa vuoron alussa tehtävän kolavaraston täydentämisen.

{\em Esittely:}\\
{\tt void lisaaKolaa();}

{\em Parametrit:}\\
Ei parametreja.

{\em Paluuarvo:}\\
Ei paluuarvoa.

{\em Toiminta:}\\
Lasketaan lisättävän kolan määrä yleisasetuksen ja
Hakkerin maksimikolavarastojen perusteella. Suoritetaan lisäys
ja varmistetaan ettei tulos ylitä maksimivarastoja.

\subsubsection{Julkinen jäsenfunktio: suoritaSiirto}
Suorittaa siirron, muuttaen hakkerin tilaa ja tarvittaessa lähettää viruksen
siirrossa olevan osoittimen kautta toisen hakkerin otaVirus-jäsenfunktiolle.

{\em Esittely:}\\
{\tt bool suoritaSiirto(const Siirto \&siirto, std::ostream \&out);}

{\em Parametrit:}\\
Parametri {\bf siirto} on suoritettava siirto, joka on jo tarkistettu lailliseksi.\\
Parametri {\bf out} on tulostevirta, johon kirjoitetaan ilmoitukset siirron suorituksesta.

{\em Paluuarvo:}\\
False, jos hakkerin tila vaihtui, eli vuoron pitäisi päättyä. True muutoin.

{\em Toiminta:}\\
Siirron tyypin perusteella tehdään jotain seuraavista:

{\bf KOODAA:} Tulostetaan ilmoitus ``{\em hakkerin nimi}  koodaa tason {\em x}  viruksen.''
Vaihdetaan tilaksi HYOKKAYS vaihdaTila-jäsenfunktiolla, joka nollaa tilaan liittyvät
jäsenmuuttujat. Asetetaan sitten jäsenmuuttujaan {\tt virustaso\_} koodatun viruksen taso.

{\bf PUSKUROI:} Tulostetaan ilmoitus, vaihdetaan tilaksi PUSKUROINTI ja asetetaan jäsenmuuttujaan
{\tt maara\_} siirrosta saatu puskurointien määrä.

{\bf REITITA:} Tulostetaan ilmoitus, vaihdetaan tilaksi REITITYS ja asetetaan
jäsenmuuttujiin reititysten määrä ja reitityskohteet.

{\bf RESETOI:} Tulostetaan ilmoitus ja vaihdetaan tilaksi PERUS.

{\bf HYOKKAA:} Tulostetaan ilmoitus ja otetaan talteen jäsenmuuttujasta
hallussa olevan viruksen taso. Vaihdetaan tilaksi PERUS ja lähetetään
virus hyökkäyskohteelle.

{\bf TANKKAA:} Tulostetaan ilmoitus ja kasvatetaan {\tt hermot\_}-jäsenmuuttujaa,
kuitenkin varmistaen ettei maksimi ylity.

{\bf ODOTA:} Tulostetaan ilmoitus.

Lopuksi kulutetaan kolaa hakkeripeli-moduulin laskeKulutus-funktiolta saadun
tiedon perusteella. Palautetaan {\bf true} jos siirron tyyppi oli TANKKAA,
muut siirrot aiheuttavat vuoron vaihtumisen.

{\em Käyttö:}\\
Hakkeripeli-moduulin suoritaSiirto kutsuu tietyn hakkerin tätä jäsenfunktiota suorittaakseen komentotulkilta
saadun siirron.

\subsubsection{Julkinen jäsenfunktio: tarkistaSiirto}
Tarkistaa siirron kelvollisuuden nykyisessä tilanteessa ja tulostaa mahdolliset
virheilmoitukset.

{\em Esittely:}\\
{\tt bool tarkistaSiirto(const Siirto \&siirto, std::ostream \&err);}

{\em Parametrit:}\\
Parametri {\bf siirto} on tarkistettava siirto.\\
Parametri {\bf err} on virheiden tulostamiseen käytetty virta.

{\em Paluuarvo:}\\
True, jos siirto oli kelvollinen. Ellei siirto ole kelvollinen, tulostetaan
virheilmoitus ja palautetaan false.

{\em Toiminta:}\\
Tarkistetaan seuraavat virhetilanteet:
\begin{itemize}
\item Siirron tyyppi on EISIIRTOA, mikä tarkoittaa virhettä ohjelman
sisäisessä rakenteessa. Tällöin tuotetaan poikkeus, jotta virhe varmasti
huomataan.

\item Siirron tyyppi on HYOKKAA mutta {\tt virustaso\_} on 0. Tulostetaan
``Virhe: {\em hakkerin nimi}  ei omista virusta.'' ja palautetaan {\bf false}.

\item Siirron tyyppi on TANKKAA mutta {\tt tila\_} ei ole PERUS. Tulostetaan
``Virhe: Tämän komennon voi suorittaa vain perusmoodissa.'' ja palautetaan {\bf false}.

\item Siirron tyyppi on TANKKAA mutta {\tt kola\_} on pienempi kuin annettu
{\tt maara}. Tulostetaan ``Virhe: Liian vähän kolaa :'(.'' ja palautetaan {\bf false}.
\end{itemize}

Muutoin palautetaan {\bf true} eikä tulosteta mitään.

{\em Käyttö:}\\
Komentotulkki käyttää tätä käyttäjältä saadun siirron tarkistamiseksi.
Komentotulkki suorittaa jo siirtoa tulkitessaan
osan tarkastuksista, kuten etsii hakkerit nimien perusteella.

\subsubsection{Julkinen jäsenfunktio: otaVirus}
Kutsutaan, kun tälle hakkerille on lähetetty virus. Suorittaa toimenpiteitä
hakkerin tilan perusteella.

{\em Esittely:}\\
{\tt void otaVirus(int taso);}

{\em Parametrit:}\\
Parametri {\bf taso} on lähetetyn viruksen taso.

{\em Paluuarvo:}\\
Ei paluuarvoa.

{\em Toiminta:}\\
{\bf Aina:} Tulostetaan ilmoitus ``{\em hakkerin nimi}  saa tason {\em viruksen taso}  viruksen koneelleen!''

{\bf PERUS- tai HYOKKAYS-tilassa:} Kutsutaan {\tt kulutaHermoja}-jäsenfunktiota viruksen tasolla.
Tämä funktio tulostaa tarvittavat ilmoitukset ja vaihtaa hermojen loppuessa hakkerin tilan.

{\bf PUSKUROINTI-tilassa:} Tulostetaan ilmoitus puskuroinnista ja lisätään taso jäsenmuuttujaan {\tt virustaso\_}. Pienennetään {\tt maara\_}-jäsenmuuttujaa.
Jos uusi {\tt maara\_} on 0, vaihdetaan {\tt tila\_}:ksi HYOKKAYS ja tulostetaan ilmoitus
puskurin täyttymisestä.

{\bf REITITYS-tilassa:} Haetaan seuraava reitityskohde vektorista
indeksin perusteella ja päivitetään indeksiä. Tulostetaan ilmoitus reitityksestä.

Pienennetään {\tt maara\_}-jäsenmuuttujaa. Jos uusi {\tt maara\_} on 0, siirrytään
tilaan PERUS ja tulostetaan ilmoitus reititysten purkamisesta.

Kutsutaan kohdehakkerin otaVirus-funktiota viruksen tasolla.

{\bf OFFLINE-tilassa:} Tulostetaan ilmoitus ``{\em hakkerin nimi}  on terassilla. Häntä ei kiinnosta.''

{\em Käyttö:}\\
Toiset hakkerit kutsuvat tätä suorittaessaan hyökkäystä tai reititystä.

\subsubsection{Julkinen jäsenfunktio: tulostaTiedot}
Tulostaa hakkerin tiedot TIEDOT-komentoa varten.

{\em Esittely:}\\
{\tt void tulostaTiedot(std::ostream \&out) const;}

{\em Parametrit:}\\
Parametri {\bf out} on tulostusvirta.

{\em Paluuarvo:}\\
Ei paluuarvoa.

{\em Toiminta:}\\
Tulostetaan tiedot seuraavassa muodossa:
\begin{verbatim}
<hakkerin nimi>
Moodi:     <moodin kuvaus>
[Taso:     <reititysten/puskurointien määrä>] // Vain jos tila_ on REITITYS
                                              // tai PUSKUROINTI
[Hermot:   <hermot>/<maksimihermot>] // Ei offline-tilassa
[Kola:     <kola>/maksimikola>] // Ei offline-tilassa

[Virus:    tason <viruksen taso> virus] // Vain jos virustaso_ != 0
[Kohteet:  <kohde 1>, <kohde 2>, ... ] // Vain jos tila_ on REITITYS
\end{verbatim}

{\em Käyttö:}\\
Komentotulkki kutsuu tätä suorittaessaan TIEDOT-komentoa.

\subsubsection{Julkinen jäsenfunktio: tulostaTilanne}
Tulostaa tilannelistausta varten sopivan tietorivin.

{\em Esittely:}\\
{\tt void tulostaTilanne(std::ostream \&out) const;}

{\em Parametrit:}\\
Parametri {\bf out} on tulostusvirta.

{\em Paluuarvo:}\\
Ei paluuarvoa.

{\em Toiminta:}\\
Jos {\tt tila\_} on OFFLINE, tulostetaan ``{\em hakkerin nimi} Terassilla''.

Muutoin tulostetaan
``{\em hakkerin nimi} {\em hermot}/{\em maxhermot} {\em kola}/{\em maxkola} {\em moodi}''.
Moodi on Irkkaa, Koodaa, Reitittää tai Puskuroi.

{\em Käyttö:}\\
Hakkeripeli-moduulin tulostaTilanne-funktio kutsuu vuorotellen kaikkien hakkerien tätä
jäsenfunktiota.

\subsubsection{Yksityinen jäsenfunktio: kulutaHermoja}
Vähentää hakkerin hermoja ja tulostaa siihen liittyvän ilmoituksen. Hermojen
loppuessa tulostaa ilmoituksen ja vaihtaa hakkerin tilan. Palauttaa tiedon
hermojen riittämisestä.

{\em Esittely:}\\
{\tt bool kulutaHermoja(int maara, std::ostream \&out);}

{\em Parametrit:}\\
Parametri {\bf maara} on hermoista vähennettävä määrä.\\
Parametri {\bf out} on tulostusvirta.

{\em Paluuarvo:}\\
True, jos hermot riittivät ja false muutoin.

{\em Toiminta:}\\
Tulostetaan ilmoitus ``{\em hakkerin nimi}  menettää hermojaan {\em x}  pisteen verran!''.
Pienennetään jäsenmuuttujaa {\tt hermot\_} tämän verran.

Jos tulos on negatiivinen tai 0, tulostetaan ilmoitus
``{\em hakkerin nimi}  menettää hermonsa ja lähtee terassille.'' ja palautetaan {\bf false}.
Muutoin palautetaan {\bf true}.

{\em Käyttö:}\\
Jäsenfunktio otaVirus kutsuu tätä viruksen kuluttaessa hakkerin hermoja.
Jäsenfunktio kulutaKolaa kutsuu tätä ellei kolamäärä riitä.

\subsubsection{Yksityinen jäsenfunktio: kulutaKolaa}
Ottaa tarvittavan määrän kolaa hakkerin kolavarastoista. Ellei kola riitä,
määrä otetaan hermoista. Tankkauksen yhteydessä puuttuvaa määrää ei pidä ottaa
hermoista, mutta kolan riittäminen tankkaukseen tarkistetaan ennen siirron
toteutusta.

Palauttaa totuusarvon joka kertoo kolan/hermojen riittämisestä. False tarkoittaa
hermojen menneen, eli hakkerin siirtyneen terassille.

{\em Esittely:}\\
{\tt bool kulutaKolaa(int maara, std::ostream \&out);}

{\em Parametrit:}\\
Parametri {\bf maara} on tarvitun kolan määrä.\\
Parametri {\bf out} on tulostusvirta mahdollista hermojen kulumisesta kertovaa
ilmoitusta varten.

{\em Paluuarvo:}\\
True, jos kolaa tai hermoja riitti. False jos hakkeri menetti hermonsa.

{\em Toiminta:}\\
Tarkistetaan riittääkö kolan määrä. Jos riittää, vähennetään se suoraan
jäsenmuuttujasta {\tt kola\_} ja palautetaan {\bf true}.

Muutoin lasketaan kolavarastojen ja tarvitun määrän erotus, asetetaan
jäsenmuuttujaan {\tt kola\_} arvo 0 ja kutsutaan jäsenfunktiota {\tt kulutaHermoja}
erotuksella. Palautetaan saatu arvo.

{\em Käyttö:}\\
Jäsenfunktio suoritaSiirto käyttää tätä siirron tarvitseman kolan kuluttamiseen.

\subsubsection{Yksityinen jäsenfunktio: vaihdaTila}
Vaihtaa hakkerin tilaa nollaten samalla tilaan liittyvät jäsenmuuttujat.

{\em Esittely:}\\
{\tt void vaihdaTila(HakkeriTila uusitila);}

{\em Parametrit:}\\
Parametri {\bf uusitila} on hakkerin seuraava tila.

{\em Paluuarvo:}\\
Ei paluuarvoa.

{\em Toiminta:}\\
Asetetaan uusi tila jäsenmuuttujaan {\tt tila\_}. Nollataan {\tt virustaso\_}
ja {\tt maara\_}. Tyhjennetään {\tt reitityskohteet\_}.

{\em Käyttö:}\\
Jäsenfunktio suoritaSiirto käyttää tätä hakkerin tilan vaihtamiseen. Tilaan
liittyvät arvot asetetaan tämän funktion kutsumisen jälkeen.

\newpage
\section{Loppudokumentaatio}

\subsection{Muutokset alkuperäiseen suunnitelmaan}
Alunperin hakkeripeli-moduulin sisältö oli toteutettu yhtenä luokkana.
Assari neuvoi muuttamaan tämän, sillä C++:ssa ei ole tapana tehdä luokkia
tällaisista asioista.

Siirroissa ja muissa yhteyksissä Hakkeri-olioihin viitattaessa oli käytetty
indeksiä HakkeriPeli-olion sisältämässä vektorissa. Tämä neuvottiin muuttamaan
pointtereiksi, mikä yksinkertaisti ohjelman toteutusta. Toisaalta tämä monimutkaisti
huvikseni tekemääni AI:ta, jonka täytyy suorittaa pelitilanteen kopiointia.

Välipalautussuunnitelmaan lisäsin kaksi moduulia, poikkeukset sekä merkkijonojen
käsittelyn. Virheenkäsittelyä muussa ohjelmassa muutettiin käyttämään poikkeuksia.
Poikkeusten käytön ansiosta virheilmoitusten tulostusta pystyi keskittämään
ja monimutkaisilta if-rakenteilta vältyttiin.

Vuorojen hallinta oli aluksi toteutettu dequeta pyörittämällä. Koska ryhmät,
hakkerit ja reitityskohteet pitää kuitenkin tulostaa samassa järjestyksessä
kuin ne on annettu, vaihdoin tämän käyttämään vektoria ja indeksimuuttujaa.

Tulosteet suoritettiin alunperin HakkeriPeli-moduulin sisältämien osoittimien
kautta. Nyt tulostus- ja virhevirrat välitetään parametreina niitä käyttäville
funktioille.

\subsection{Kuvaus harjoitustyön tekemisestä}
Toteuttaminen suunnittelun jälkeen oli helppo, joskin HakkeriPeli-luokasta
luopuminen vaati jonkin verran uutta suunnittelua.

Välipalautusdokumenttiin meni noin 12 tuntia.
Vaiheen 1 vaatiman asetustiedostojen lukemisen toteuttamiseen meni
noin 8 tuntia. Vaiheen 2 komentotulkkiin kului noin 4 tuntia. Vaiheen 3
yhteydessä toteutin jo pelin testausta vaille valmiiksi, mihin meni
noin 10 tuntia. Lisäksi kului testaukseen noin 4 tuntia ja loppudokumenttiin
8 tuntia. Yhteensä harjoitustyöhön kului siis 46 tuntia.

Mielestäni harjoitustyöni onnistui hyvin.

\subsection{Palautetta}
Harjoitustyön tehtävänanto yms. dokumentit oli laadittu hyvin, mutta
esimerkkiajot olisi saatu tehdä tarkemmin. Nyt niissä oli välilyönneissä
ja tyhjissä riveissä vaihtelevaa käyttäytymistä, minkä vuoksi diffaaminen oli
työlästä.

Dokumenttipohja oli hyödyllinen, mutta alkusivun otsikko olisi saanut olla
ajan tasalla jo valmiiksi.

Tentti oli mielestäni hieman huono, siinä painottui liikaa juuri luennoilla
esitetyt käsitykset asioista ja parhaista käytännöistä. Joskin näihinkin
kohtiin saa yleisellä tietämyksellä pääteltyä vastauksia, mutta tällaista
subjektiivista tietoa olisi parempi kysyä ``Pohdi syitä, miksi ...'' eikä
``Miksi ...''. Tällöin ei jäisi tuntumaa, että kysymykseen on yksi oikea
prujussa esitetty vastaus.

\end{document} 
