Tämä kurssi on jo päättynyt.

Harjoitustyö: Susipalatsi - peli valtakunnasta

Harjoitustyö tehdään kahden hengen ryhmissä. Kurssi tarjoaa osan toimannillisuutta ja kurssin puolen koodia on käytettävänä osana harjoitustyötä.

Deadlinet

  • Lopullinen palautus: 19.4.2021
  • Armonaika päättyy: 26.4.2021

Toteutettava ohjelma

Kaupunkivaltion hallitsija on kuollut, ja perillinen ei ole vielä täysi-ikäinen. Kaupungin neuvoston on siis valittava keskuudestaan joku sijaishallitsijaksi. Neuvoston tärkeänä jäsenenä näet, että hallitsijuuden vastuu ja velvollisuus kuuluvat luonnollisesti juuri sinulle. Nyt sinun on vain hankittava riittävästi vaikutusvaltaa muihin neuvoston jäseniin, jotta saat taivuteltua heidät puolellesi ratkaisevassa äänestyksessä…

Pelin kulku

Ohjelmaa käynnistettäessä näkyviin tulee ensin asetusikkuna. Asetuksien valinnan jälkeen siirrytään pelin pääikkunaan, jossa esitetään kaupunkia kuvaava pelilauta, jolle pelaajat (kaksi tai useampia) asettavat kortteja ja pelimerkkejään. Laudalla on useita eri alueita (vähintään neljä), esim. kauppiaiden kilta, kaupunginvartiosto jne. Kutakin aluetta edustaa tietty hallintoneuvoston jäsen. Kullakin alueella on oma pelipakkansa, joka sisältää ainakin seuraavanlaisia kortteja:

  • Vaikutusvaltakortit: Pelin lopussa pelaajien käteensä keräämät vaikutusvaltakortit vaikuttavat ratkaisevan äänestyksen lopputulokseen.
  • Agenttikortit: Agenttikortteja voi pelata pelilaudalle, jossa ne voivat suorittaa pelaajan tavoitteita edistäviä operaatioita.

Pelaaja voi pelata kädessään olevan agenttikortin mille tahansa pelilaudan alueelle. Tällöin kortti asetetaan laudalle, ja sen päälle asetetaan pelaajan pelimerkki osoittamaan kortin omistaja. Alueella oleva agentti voi suorittaa siellä ainakin seuraavia operaatioita:

  • Suhteiden vaaliminen: Pelaaja lisää agentin päälle uusia pelimerkkejä ilmaisemaan vahvempia suhteita alueen toimijoihin. Paremmat suhteet tehostavat muiden operaatioiden suorittamista.
  • Resurssien kerääminen: Pelaaja nostaa alueen pakasta kortteja käteensä.
  • Neuvottelut: Pelaaja asettaa uusia pelimerkkejä alueelle kuvastamaan kasvanutta vaikutusvaltaansa siihen liittyvään neuvoston jäseneen.
  • Vetäytyminen: Pelaaja nostaa agenttikortin takaisin käteensä.

Pelin aikana kukin pelaaja voi vuorollaan pelata laudalle kädestään agentteja ja/tai suorittaa laudalla olevilla agenteillaan operaatioita. Pelin lopussa tulee äänestys, jossa kunkin neuvoston jäsenen ääni perustuu pelilaudalle heidän alueelleen asetettuihin pelimerkkeihin ja pelaajien kädessä oleviin vaikutusvaltakortteihin. Äänestyksen voittanut pelaaja voittaa pelin, ja valtaistuimen.

Toteutus

Ohjelmaan on toteutettu valmiiksi pelin tilan hallinta, kuten korttien nostaminen pelaajan käteen ja pelaaminen laudan alueelle. Itse toteutettavaksi jäävät pelin graafinen käyttöliittymä, agentit ja niiden operaatiot, sekä muutama muu pelimekaniikan yksityiskohta. Alla on hahmotelma siitä, miltä pelin käyttöliittymä saattaisi näyttää.

../../_images/gui_luonnos.png

Monet pelin yksityiskohdat on jätetty pelin kuvauksessa tarkoituksella avoimiksi, ja ne voi toteuttaa parhaaksi katsomallaan tavalla. Tällaisia asioita ovat mm.

  • Pelin alkutilanne: Miten pelaajat saavat ensimmäiset agentit laudalle pelattavaksi? Onko heillä valmiiksi kädessään joitain kortteja, voiko pelialueelle pelata jotenkin agentin ilman kädessä olevaa korttia, voiko operaatioita suorittaa myös ilman agenttia?
  • Agenttien suhteiden vaikutus operaatioihin: Avaavatko käyttöön uusia operaatioita, parantavatko onnistumisen todennäköisyyttä, parantavatko lopputulosta?
  • Vuorojen kulku: Mitä kaikkea pelaaja voi tehdä yhden vuoron aikana?
  • Pelin päättyminen: Milloin on lopullisen äänestyksen aika? Jonkin kierrosmäärän kuluttua, kaikkien pelipakkojen tyhjennyttyä, jonkun pelaajan saatua yliotteen neuvostosta?
  • Ratkaiseva äänestys: Miten kunkin neuvoston jäsenen ääni tarkalleen ottaen määräytyy?
  • Pelin käyttöliittymä: Millainen pelilauta pelissä on? Miten pelin eri toimintoja voidaan suorittaa?

Käyttötapaus

Seuraava toimintojen sarja havainnollistaa pelin käynnistystä ja toimintaa. Itse toteutettavien toimintojen osalta kyseessä on esimerkki, eli pelin ei tarvitse toimia juuri tässä kuvatulla tavalla.

  • Peliohjelma käynnistetään itse toteutetun pääohjelman avulla. Peliohjelma kysyy erillisellä konfiguraatio-ikkunalla pelaajien määrän ja muuta pelin mahdollisesti tarvitsemaa alkutietoa. Tämän jälkeen se luo valmiiksi toteutetun peliolion ja asettaa sille pelaajat, kortit ym. muut alkutilanteen yksityiskohdat. Sitten se avaa itse toteutettavan pääikkunan, jossa näkyvät pelilauta kortteineen ja pelimerkkeineen sekä vuorossa olevan pelaajan kädessä olevat kortit.
  • Pelaaja aloittaa pelin klikkaamalla nappulaa pääikkunassa. Itse toteutettava pelilogiikka käynnistää valmiiksi toteutetun peliajurin, joka yrittää selvittää vuorossa olevan pelaajan seuraavan siirron. Koska kyseessä on käyttöliittymän kautta pelaava ihminen, ei ajuri pysty tuottamaan siirtoa itse vaan palauttaa kontrollin pääikkunalle.
  • Vuorossa oleva pelaaja klikkaa hiirellä pääikkunasta yhtä pelilaudan aluetta, ja valitsee avautuvista vaihtoehdoista tietyn agentin lähettämisen alueelle. Logiikka luo agentin lähettämistä kuvaavan siirto-olion ja välittää sen ajurille. Ajuri suorittaa siirron, joka tekee tarvittavat muutokset pelin tilaan. Pelin oliot signaloivat, että kyseisen alueen ja pelaajan käden tilat ovat muuttuneet ja ne on päivitettävä ikkunassa, ja ajuri signaloi että siirto on suoritettu. Ajuri toteaa olevansa edelleen kyvytön tuottamaan ihmispelaajan siirtoja ja palauttaa taas kontrollin ikkunalle.
  • Vuorossa oleva pelaaja klikkaa hiirellä pääikkunasta laudalla nyt näkyvää agenttia ja valitsee avautuvista vaihtoehdoista neuvottelun. Ajuri käsittelee operaation, jonka tuloksena pelioliolle ilmoitetaan vaikutusvallan kasvusta kyseisellä alueella, ja alueen tilan signaloidaan jälleen päivittyneen ja siirron tulleen suoritetuksi. Kontrolli palaa taas pääikkunaan.
  • Pelaaja päättää vuoronsa pääikkunasta käsin. Logiikka vaihtaa vuoroon seuraavan pelaajan, jolloin peliltä tulee jälleen päivityssignaali. Ajuri käynnistetään selvittämään seuraavan pelaajan siirtoa.
  • Kunkin pelaajan pelattua kymmenen vuoroa siirrytään ratkaisevaan äänestykseen. Pelilogiikka kysyy pelioliolta oleelliset tilatiedot, ja ilmoittaa niiden perusteella pelaajille äänestyksen tuloksen ja pelin voittajan.

Osaamistavoitteet

Työssä harjoitellaan seuraavia asioita käytännössä:

  • Olemassa olevan ohjelmakoodin toimintaan tutustuminen ja sen käyttö tarjottujen rajapintojen avulla (sopimussuunnittelun hyödyntäminen)
  • Toiminnallisuuden lisääminen sovellukseen siihen määriteltyjen rajapintojen mukaan (sopimussuunnittelun noudattaminen)
  • Graafisen käyttöliittymän toteuttaminen osaksi ohjelmaa
  • Poikkeusten käsittely ja virhetilanteiden hallinta
  • Oman koodin testaus yksikkötasolla
  • Ohjelman toteuttaminen tiimityönä

Vaatimukset

Harjoitustyöhön on toteutettava vähintään tietyt perusominaisuudet, jotta sen suoritus voidaan hyväksyä. Lisäksi tarjolla on joukko vapaaehtoisia lisäominaisuuksia, joita toteuttamalla voi korottaa työn arvosanaa.

Pakolliset ominaisuudet

Harjoitustyössä on toteutettava ainakin alla olevat ominaisuudet. Minimitoteutuksen täyttävästä työstä voi ansaita hyväksytyn suorituksen (arvosana 1). Perustoteutukselle määriteltyjen ominaisuuksien mukaisella toteutuksella voi työstä ansaita korkeintaan arvosanan 3. Korkeampi arvosana edellyttää myös joidenkin lisäosien toteutusta.

Minimitoteutus (maksimiarvosana 1):

  • Peli kääntyy
  • Peliin on toteutettu jonkinlaiset pelimekaniikat
  • Yksi dialogi-ikkuna (esim. alkumenu, jossa määritellään pelaajamääriä, tavoitteita yms.)
  • Peliin on toteutettu ainakin yksi oma agenttikortti. Luokalta vaadittu toiminnallisuus on määritelty AgentInterface -rajapinnan avulla.
  • Yksikkötestit on kirjoitettu vähintään itsetoteutettavalla agenttikorttien luokalle.
  • Agentin toiminnallisuudesta vähintään kaksi operaatiota on toteutettu
  • Loppudokumentti, josta löytyy:
    • Pelin säännöt
    • Peliohjeet
    • Luokkien vastuujako (luokkakaavio on tässä hyödyllinen)
    • Ryhmän työnjako

Huom! Minimitoteutus saa käyttää käyttöliittymänään kurssin tarjoamaa minimikäyttöliittymätoteutusta.

Perustoteutus (maksimiarvosana 3):

  • Peli täyttää minimitoteutuksen vaatimukset
  • Peliin on toteutettu graafinen käyttöliittymä kokonaan itse (ei käytetä kurssikirjaston tarjoamaa versiota), jossa esitetään ainakin pelilauta, siellä olevat kortit ja pelimerkit, sekä vuorossa olevan pelaajan kädessä olevat kortit.
  • Agentteja voi pelata pelilaudan alueille, ja niiden avulla voi suorittaa neljä pelin kuvauksessa esitettyä operaatiota.
  • Peli toimii eli pelin kulusta pidetään kirjaa, niin että pelaajien vuorot vaihtuvat ja lopullinen äänestys tulee ajallaan ja lopullisessa äänestyksessä pelin voittaja ratkaistaan ottaen huomioon ainakin pelilaudalla olevat vaikutusvaltaa kuvastavat pelimerkit ja pelaajien käsissä olevat vaikutusvaltakortit.
  • Peli selviää hallitusti asetustiedostojen käsittelyssä tapahtuvista virheistä.
  • Yksikkötesteissa on huomioitu myös sopimussuunnittelun ehdot
  • Toteutuksen bonuspisteillä voi kompensoida virheitä yhden arvosanan verran

Huipputoteutus (maksimiarvosana 5):

  • Peli täyttää minimi- ja perusvaatimukset
  • Pelissä on vähintään kaksi lisäosaa
  • Toteutuksen bonuspisteillä voi kompensoida virheitä yhden arvosanan verran

Lisäominaisuudet

Alla olevia lisäominaisuuksia ei tarvitse toteuttaa, mutta hyvästä lisäosan toteutuksesta voi ansaita korotuksen arvosanaan. Lähtökohtaisesti kukin lisäosa on arvoltaan 0,5 arvosanaa. Yksittäisen lisäosan arvoa voidaan kuitenkin tarvittaessa kasvattaa myöhemmin, jos se osoittautuu erityisen työlääksi. Korotuksen saaminen edellyttää, että lisäosa on dokumentoitu palautuksessa siten, että ne selkeästi on esitetty lisäosiksi. Töiden tarkastajat eivät etsi lisäosia palautetusta koodista.

  • Agenttien/alueiden ominaisuudet. Kullakin agenttikortilla ja/tai pelilaudan alueella on yksilöllisiä ominaisuuksia, jotka vaikuttavat suoritettaviin operaatioihin.
  • Hyökkäysoperaatiot. Laudalla olevat agentit voivat hyökätä toisiaan vastaan ja häiritä näin muiden pelaajien tekemisiä. Hyökkäyksen tarkat mekaniikat voi päättää itse.
  • Uudet korttityypit. Pelipakoissa on mukana uudenlaisia kortteja, joilla on oma käyttötapansa. Esimerkki: resurssikortit, joita pelaamalla voi tehostaa yksittäisiä suoritettavia operaatioita.
  • Kortit datana. Pelissä mukana olevia kortteja ei ole kovakoodattu, vaan niiden tiedot luetaan pelin alussa tiedostoista. Tiedostot voi kirjoittaa esim. JSON- tai XML-formaatissa, joiden käsittelyyn Qt:ssa on valmiita luokkia.
  • Dynaamisesti rakennetut kortit. Pelialueelle piirrettävät kortit eivät ole valmiita kuvia, vaan niiden ulkoasu rakennetaan dynaamisesti kortin ominaisuuksien perusteella. Näin ominaisuuden muutos koodissa (tai tiedostossa) tulee näkyviin myös pelialueelle ilman kuvien muokkausta. Pisteiden saaminen tästä lisäosasta edellyttää, että korteilla todella on tällaisia ominaisuuksia; jos niillä ei ole erityisiä pelimekaanisia ominaisuuksia, agenteille voi vaikkapa antaa yksilöllisen nimen ja tittelin väriä tuomaan.
  • Skaalautuva tai zoomattava pelialue. Joko: Pelilauta sekä sen päällä olevat kortit ja pelimerkit skaalautuvat peli-ikkunan koon mukana. Tai: Pelialue voidaan zoomata lähietäisyydelle, jossa korttien yksityiskohdat erottuvat selvästi, tai kauemmas niin että koko lauta näkyy kerralla. Skaalauksen ja zoomauksen saa toteuttaa molemmatkin, mutta ne lasketaan yhdessäkin vain yhdeksi lisäosaksi. (max. 1 arvosana)
  • Korttien katselunäkymä. Vietäessä hiiren kursori jonkin pelialueella olevan kortin yläpuolelle, kyseinen kortti piirretään automaattisesti peli-ikkunaan erilliseen näkymään suurempikokoisena, niin että pelaajan on helpompi tarkastella sen yksityiskohtia.
  • Pelaaja-asetukset. Pelin käynnistyksen yhteydessä avataan asetusikkuna, josta voi valita mukana olevien pelaajien määrän (ainakin väliltä 2-4) ja antaa kullekin pelaajalle nimen. Peli-ikkunassa on oltava jatkuvasti näkyvillä kustakin pelaajasta ainakin nimi ja kädessä olevien korttien määrä.
  • Tekoäly. Osa pelaajista voidaan asettaa tietokoneen hallittaviksi. Tekoälyn ei tarvitse osata pelata hyvin, kunhan se on edes periaatteessa kykenevä tekemään minkä tahansa laillisen siirron. Lisäosan kriteerit täyttyvät siis vaikka täysin satunnaisia siirtoja tekevällä tekoälyllä. (max. 1 arvosana)
  • Pelin tallettaminen ja tallennuksesta pelin jatkaminen (max. 1 arvosana)
  • Oma lisäominaisuus. Ohjelmaan on toteutettu jokin itse keksitty tämän listan ulkopuolinen lisäominaisuus. Omat lisäominaisuudet on hyväksytettävä hyvissä ajoin etukäteen kurssihenkilökunnalla, jotta niistä voisi saada pisteitä.
  • Huom! Lisäominaisuuden pitää olla selkeästi koodaustyötä vaativa ominaisuus.
  • Huom! KAIKKI LISÄOSAT PITÄÄ DOKUMENTOIDA PISTEET SAADAKSEEN

Arvosanan määräytyminen

Arvosana koostuu erillisistä osioista ja bonuspisteistä. Loppuarvosanaa rajaa yllä määritellut toiminnalliset vaatimukset. Perustoteutuksesta ylöspäin bonuspisteillä voi kompensoida yhden arvosanan verran pistemenetyksiä. Kahden lisäosan toteuttamisen jälkeen bonuspisteet voivat kompensoida kaksi lisäarvosanaa.

Arvosteltavat osiot:

  • Pelin suunnittelu ja kokonaisrakenne
  • Luokkasuunnittelu ja toteutus
    • Olio-ohjelmoinnin periaatteiden käyttö
    • Luokkien dokumentaatio
  • Poikkeuskäsittely
    • Poikkeusten oikea käyttö ja käsittely
  • Koodin laatu
    • Hyvien ohjelmointitapojen ja -periaatteiden noudattaminen
    • Koodityyli
  • Versiohistoria ja tiimityö
  • Bonuspisteet
    • Annetaan lisäosista ja erityisen hyvin toteutetuista toiminnallisuuksista

Arvosteltavista osoista pisteytys muodostuu:

  • 50% => 1
  • 70% => 2
  • 90% => 3

Lisätyö, joka lasketaan bonuspisteisiin:

  • Ylimääräiset, hyvin toteutetut yksikkötestit (yht. max. 0,5 arvosana)
  • Ylimääräiset uniikit pelitoimijat (yht. max. 0,5 arvosana)
  • Grafiikan taso (max. 1 arvosana)
  • Erittäin hyvä käyttöliittymä (max. 1 arvosana)
  • Taustatarina ja maailman kuvaus (max. 0,5 arvosana)

Esimerkkejä:

  • Vain minimivaatimukset saavutettu ja täydet pisteet arvosteltavista osioista, ei bonuspisteitä, ( 3 + 0 => 1)
  • Minimi ja perusvaatimukset saavutettu, 75% arvosteltavien osioiden pisteistä, 0,5 arvosanan verran bonuspisteitä ( 2 + 0.5 => 2.5)
  • Mimini ja perusvaatimukset saavutettu, 60% arvosteltavien osioiden pisteistä, 2 lisäosaa 60% arvosteltavien osioiden pisteillä (1 + 3.5 => 4 )
    • Vain 3 pistettä huomioidaan saavutetusta 3,5 pisteestä (1 arvosteltavien osioiden kompensaationa, 2 lisäosista)

Ympäristö

Työympäristö haetaan Gitlabista. Jokaisella ryhmällä on oma ryhmän nimellä nimetty repositorio. Kannattaa huomata, että joidenkin ryhmien nimiä on jouduttu muokkaamaan, jotta ne käyvät GitLabin repositorionimiksi. Valmiit toteutuksen osat tulevat myös versionhallinnan kautta saataville.

Luokkakaavio

Ohjelman toteutuksen kannalta olennaiset luokat:

../../_images/luokkakaavio.png

Luokkakaavio

HUOM! Opiskelijoiden toteuttamien luokkien osalta kaavio on täysin kuvitteellinen, luokkia voi olla enemmän (ja on luultavasti syytäkin olla), ja niiden väliset suhteet voivat olla aivan erilaiset.

Rajapintojen Doxygen-dokumentaation saa generoitua suoraan projektista. Tutustu projektiin tarkemmin lukemalla README.

Ohjelmaan toteutetaan kurssin puolesta valmiiksi mm. alla olevat osat.

Game sisältää pelilaudan ja pelaajien kulloisenkin tilan. Useimpiin muihin olioihin päästään käsiksi sen kautta. Se myös signaloi muutokset vuorossa olevan pelaajan tilanteessa.
  • Location kuvaa yhtä pelilaudan aluetta pakkoineen ja agentteineen. Se signaloi muutokset omassa tilassaan.
  • DeckInterface on rajapinta korttipakoille. Sen toteutukseen ei ole tarkoitus koskea oman toteutuksen puolelta.
  • Player kuvaa pelin yksittäisen pelaajan tilan.
  • CardInterface ja sen toteuttavat luokat kuvaavat pelikortteja. Neuvoston jäseniä esittävien Councilor-korttien käyttö ei ole työssä pakollista, mutta niitä voi halutessaan hyödyntää; Game sisältää niille tarkoitetun pakan.
  • AgentInterface kuvaa agenttikorttiluokan, joka on toteutettava ja testattava itse.
  • ActionInterface-rajapinta perillisineen kuvaa yksittäisiä pelaajan siirtoja vaikutuksineen. Agentin vetäytymisen kuvaava WithdrawAction toteutetaan valmiiksi malliksi, joskaan sitä ei ole pakko käyttää jos vetäytymisen haluaa toimivan toisin.
  • ControlInterface tarjoaa rajapinnan pelaajien siirtojen kysymiselle. Sen aliluokat joko palauttavat seuraavan siirron (tekoäly) tai jättävät siirron valinnan muun tahon ratkaistavaksi (käyttöliittymää käyttävä ihminen). Valmiille paikallista ihmispelaajaa edustavalle ManualControl-luokalle voi asettaa ulkoa käsin seuraavan siirron palautettavaksi.
  • Runner kyselee hallintaolioilta siirtoja ja suorittaa niitä, ja palauttaa kontrollin käyttöliittymään jos hallintaolio ei osaa kertoa seuraavaa siirtoa. Tällöin käyttöliittymästä käsin voidaan esim. kertoa ManualControl-oliolle seuraava siirto ja käskeä sitten ``Runner``in yrittää uudestaan.
  • Random on yksinkertainen satunnaislukugeneraattori, jota voi hyödyntää omassa toteutuksessa. Sen käyttö ei ole pakollista.
  • SettingsReader lukee avain-arvopareja asetustiedostosta ja tarjoaa niitä kysyjille. Sitä voi käyttää omien yksinkertaisten asetusten hallintaan, mutta pakollista sen käyttö ei ole.

Siirtojen suorittamisen logiikkaa voi havainnollistaa sekvenssikaavio:

../../_images/runner_sekvenssi.png

Sekvenssikaavio

Kyseisessä kaaviossa suoritetaan ensin tekoälyn siirto ja sitten ihmispelaajan siirto, minkä jälkeen peli päättyy. (Sekvenssikaaviot eivät kuulu kurssin osaamiseen, mutta tuota voi kyllä ymmärtää terveellä järjellä; tarvittaessa apua tulkintaan löytyy mm. täältä ).

Osa valmiista luokista (mm. Game) perii _std::enable_shared_from_this -luokan, jonka avulla ne voivat tuottaa itseensä osoittavan jaetun osoittimen. Tällaisten luokkien instanssit täytyy asettaa jaettujen osoittimien omistukseen välittömästi luomisen jälkeen, luomalla ne make_shared-funktiolla. Erityisesti näistä luokista ei saa luoda olioita pinoon.

Itse ohjelmaan on toteutettava ainakin seuraavat osat:

  • main -pääohjelma, joka avaa pelin ensimmäisen ikkunan ja mahdollisesti alustaa pelin tilan.
  • MainWindow, ohjelman pääikkuna, jonka kautta pelaajat näkevät pelin tilan ja tekevät siirtonsa.
  • Agent, agenttikorttien toteutus AgentInterface -rajapinnan mukaisesti.
  • Useimmat ActionInterface -rajapinnan toteuttavat siirtojen seurauksia kuvaavat luokat.
  • Jokin muu ikkuna tai dialogi, esim. pelin asetusten säätöön, operaatioiden käynnistämiseen tai pelin lopputuloksen esittämiseen.

Todennäköisesti toteutuksen on syytä sisällyttää näiden lisäksi muitakin luokkia.

Vihjeitä

  • Selvittäkää mitä kurssin puolen koodissa tarjotaan valmiina
  • Suunnittelussa:
    • Tehkää oma luokkakaavio, joka selkeyttää rakennetta ja työnjakoa
    • Käykää luokkakaavion ja/tai sekvenssien avulla läpi, miten toiminnot etenevät ohjelmassa
  • Aloittakaa koodaaminen vasta kun suunnitelma on selkeä, jolloin voitte keskittyä koodi-lähtöisten ongelmien ratkontaan.
  • Kaikille peliobjekteille voi lisätä tekstipohjaista metadataa.
  • Kurssikirjastossa löytyvistä bugeista ja laajennuksista voi esittää toivomuksia.
    • HUOM! Lisäykset kirjastoon kannattaa ilmoittaa hyvissä ajoin, jotta niiden toteuttaminen on mahdollista ennen harjoitustyön deadlinea. Tehkää suunnittelu ajoissa ja huolella.
Palautusta lähetetään...