4.5. Tehtävä: Reading a human-writeable file
Pistearvo: 200p
Teema
Tämän ja edellisen luvun tehtävissä luetaan eri formaatein tallennettua dataa. Molemmissa luettava data kuvaa shakkipelin tilanteita. Shakkipeliä kuvaavat luokat ja tiedoston tulkintaa varten olevan luokan pohja annetaan valmiina.
Oppimis- ja kertaustavoitteet
- Harjoitellaan lukemista tietovirrasta
- Tutustutaan mahdolliseen tapaan säilyttää tietoa tiedostossa.
- Kirjoitetaan isomman ohjelman keskeinen osa. Lähes jokaisessa projektiaiheessa ohjelman tulee pystyä lukemaan ja tallentamaan tiedostoja. Monessa työssä formaatti voi olla yksinkertaisempi.
- Tutustutaan merkkijonotyökaluihin. Tehtävässä merkkijonoista täytyy etsiä alkioita, merkkijonoja täytyy pilkkoa osiin, ylimääräistä whitespacea (tyhjää tilaa: välilyönnit, tabulaattorit, rivinvaihdot jne.) täytyy pystyä poistamaan, jne
Intro : Tiedostoformaateista
Yksi keskeinen ominaisuus lähes joka ohjelmassa on mahdollisuus tallentaa ohjelman toimintaan liittyvää tietoa tiedostoihin sekä ladata ohjelman käyttöön. Tallennettavalla tiedolla on lähes aina jokin selkeä rakenne. Tiedon esitystapaa tiedostoissa kutsutaan tiedostoformaatiksi.
Formaattien suunnitteluun tai olemassa olevan formaatin valintaan johonkin tehtävään vaikuttaa joukko erilaisia tavoitteita kuten
Luettavuus
- Editoitavuus (käsin tai apuohjelmilla)
- Kirjoittaminen alusta myös käsin
- Mahdollisuus lisätä väliin
- Virheherkkyys
- Yhtälailla formaatin kuin parserin (tiedoston tulkitsijan) tehtävä
Tilankäyttö
Formaatin laajennettavuus
Nopeus
Tunnetut formaatit (esim CSV) joita käyttämällä ohjelman tallentama data on muilla ohjelmilla käsiteltävissä.
Käsin kirjoitettava formaatti
Tässä tehtävässä kirjoitetaan ohjelma, joka lukee käsin editoitavaa tiedostoformaattia. Mm. monet asetustiedostot ovat tällaisia. Niitä on helppo lukea ja kirjoittaa käsin. Nämäkin formaatit ovat helposti laajennettavissa: Niihin voidaan lisätä tietoa, jota ohjelman ei välttämättä tarvitse ymmärtää (esim. ohjelman plugineille tarkoitettuja asetustietoja), mutta tällaisen tiedon tulee olla ohitettavissa.
Käsin kirjoitettava formaatti ei voi olla yhtä sitova kuin konelukemiseen tarkoitettu, ja sitä lukevan koodin tulisi sietää erityisesti ylimääräistä whitespacea, vaihtelua rivien järjestyksessä jne. Mikäli käyttäjä rikkoo formaatin sääntöjä, pitäisi tälle tulostaa mahdollisimman selkeä kuvaus ongelmasta, jotta hän pystyy korjaamaan virheen. (Tässä tehtävässä riittää heittää poikkeuksia, joskin ne voisivat kantaa tarvittavaa informaatiota)
Usein tällaiseen tiedostoon pitää voida kirjoittaa kommentteja (kuten vaikkapa Python-koodiin), jotka tiedostoa lukeva koodi ohittaa. Tässä tehtävässä kommentteja ei käytetä.
Esimerkkejä muista formaateista
Edellisessä luvussa käsitellään konelukuun tarkoitettua formaattia.
Tehtävä : käytettävä formaatti
Tehtävän formaatti on kehitetty shakkipelin tilanteen tallennukseen. Tässä on pelkkä lopputilanne, ei siirtoja. Alla on esitetty esimerkkitallennus, ja sen jälkeen seuraa formaalimpi esitys siitä, millainen tehtävän käyttämä rakenne on.
Esitetyn formaatin suunnitteluperiaatteina on sen luettavuus helposti ihmisen toimesta, mahdollisuus lisätä uusia lohkoja säilyttäen silti mahdollisuus lukea tietoa vanhoilla ohjelmilla sekä vikasietoisuus (vain yksi asia per rivi). Tiedostossa on myös versionumero ja tunniste, joiden avulla voidaan estää tilanteet, joissa yritetään lukea väärän tyyppistä dataa.
Esimerkki
Tiedosto on tarkoitettu ihmisen luettavaksi ja kirjoitettavaksi. Tutki itse alla olevaa esimerkkiä.
SHAKKI 1.2 Tallennustiedosto #Pelin tiedot Tallennettu : 5.7.2001 Musta : Marko Valkoinen : Lauri #Kommentit Laurin revanssipeli, joskin huonosti on menossa... #Musta Kuningas : a4 Torni : a6 Sotilas : b3 Kuningatar : c8 #Valkoinen Kuningas : d3 Ratsu : f1
Vähemmän siisti (mutta ehjä) esimerkki
Ihmisen kirjoittamassa tiedostossa täytyy mielellään sallia hieman vaihtelua whitespacen käytön suhteen. Alla olevassa esimerkissä kyseessä on uudempi versio tiedostoformaatista, joka on kuitenkin ladattavissa vanhalla ohjelmamme versiolla. Ladattaessa menetämme tiedon siirroista ja turnauksesta, mutta kaikki version 1.2 ominaisuudet ovat ladattavissa normaalisti. Esimerkissä whitespace on värjätty keltaiseksi. Huomaa vaihteleva tyhjien rivien käyttö, välilyönnit rivien päissä ja keskellä.
SHAKKI 1.3 Tallennustiedosto
#pelin TIedot
Tallennettu :5.7.2001
Musta : Marko
Valkoinen:Lauri
#Kommentit
Laurin revanssipeli, joskin huonosti on menossa...
#Siirrot
Kb4
b3
Ka4
#Turnaus
Ei turnausta
#musta
Kuningas : a4
Torni : a6
Sotilas :b3
Kuningatar: c8
#Valkoinen
Kuningas : d3
Ratsu : f1
Tiedoston rakenne
Tiedoston rakenne on seuraava:
Header-rivi sisältää yksittäisillä välilyönneillä erotettuina seuraavat osat: teksti "SHAKKI" heti rivin alussa, versionumeron muotoa 1.2 (numero.numero, Voit ohittaa numeron tarkistuksen tässä tehtävässä), sekä tekstin "Tallennustiedosto" Tätä seuraa (1 - N) lohkoa (jotka alkavat merkillä # ja tämän perään kirjoitetulla lohkon tunnisteella) Jokainen lohko päättyy joko tiedoston päättymiseen tai seuraavan lohkon tunnisteeseen. Merkin # täytyy olla rivin ensimmäinen merkki, jotta rivi tulkitaan lohkon tunnisteeksi. Ainakin lohkojen "pelin tiedot", "musta" ja "valkoinen" täytyy löytyä jotta peli voidaan ladata. "Pelin tiedot"-lohko tulee aina ennen "musta"- ja "valkoinen"-lohkoja.
Lohkot
Lohkon rakenne
Tunniste = merkki '#' ja jokin merkkijono Mahdollinen whitespace tunnisteen lopussa ei kuulu tunnisteeseen. Tunnisteen tekstissä isoilla ja pienillä kirjaimilla ei ole väliä. Lohko jatkuu seuraavaan '#'-merkkiin tai tiedoston/tietovirran loppuun asti. Tyhjät tai pelkkää whitespacea sisältävät rivit ohitetaan, jollei lohkon kuvauksessa muuten kerrota, muut rivit sisältävät lohkon dataa. Näillä riveillä voi rivin päissä tai erotinmerkin ':' ympärillä olla vaihteleva määrä whitespacea. VINKKI: str-luokan metodin strip() käyttö sopivassa vaiheessa voi olla kätevää.
Lohkojen kuvaukset
Kommenttilohko (tunniste #Kommentit)
Ohitetaan tässä tehtävässä
Kommenttilohko. Koko lohkon sisältö on merkkijono, joka kuvaa tallennettua tilannetta. Teksin alussa ja lopussa olevat tyhjät rivit eivät kuulu kommenttiin, mutta tekstin sisällä olevat kyllä.
Pelin tiedot (tunniste #Pelin tiedot)
Alla on kuvattu tämän osion mahdollisesti sisältämät rivit. "Tallennettu" ":" DATA DATA on pelin tallennuspäivämäärä muodossa Päivä.Kuukausi. Vuosi Ohitetaan tässä tehtävässä "Musta" ":" DATA DATA on mustan pelaajan nimi "Valkoinen" ":" DATA DATA on valkoisen pelaajan nimi Yllämainitut rivit voivat olla missä tahansa järjestyksessä. Pelaajan nimessä saa olla välilyöntejä. esim. "Teemu Teekkari" (nimen päissä oleva whitespace ei kuitenkaan kuulu nimeen)
Nappuloiden sijainnit (tunnisteet #Musta ja #Valkoinen)
Tunnisteella #Musta alkava lohko kertoo mustien nappuloiden sijainteja. #Valkoinen vastaavasti valkoisten sijainteja.
"Kuningas"/"Kuningatar"/"Torni"/"Lahetti"/"Ratsu"/"Sotilas" ":" DATA Rivin alku määrittää shakkinappulan tyypin. DATA on nappulan sijainti laudalla, esim. "a4" Huomaa että lähetti on tiedostoformaatissa Lahetti. (ei ä-kirjainta)
Shakkinotaatio:
Shakkilaudan koko on 8x8. Laudalla on 8 riviä jotka on numeroitu 1-8, sekä 8 saraketta, jotka on merkitty pienin kirjaimin a-h.
Sijainti laudalla merkitään liittämällä peräkkäin sarakkeen kirjain ja rivin numero. Esim "h8" on yksi laudan nurkista.
Muut lohkot #???
Tuntemattomat lohkot ovat todennäköisesti formaattiin uudemmissa versioissa lisättyjä ominaisuuksia. Nämä lohkot tulee tehtävässä ohittaa. Ne eivät ole virhetilanteita.
Virhetilanteet
Virheelliset nappuloiden nimet, puuttuvat pelaajat, puuttuvat (toisen tai molempien) pelaajan nappuloiden paikat jne. ovat kaikki virheitä, joiden tulee johtaa poikkeuksen CorruptedChessFileException
heittämiseen.
Tehtävänanto
Toteuta ja testaa luokkaan HumanWritableIO
pyydetty metodi load_game
def load_game(self, input)
Luo uuden peliolion (Game), jonka se metodin päätteeksi palauttaa. Metodi lukee pelin pelaajat ja nappuloiden sijainnin käyttäen apuna muita luokan metodeja. Tiedoston päättyessä palauttaa peliolion, jossa on tiedostossa annetut pelaajat ja lauta asetettuna. (Mikäli olennaisia tietoja puuttuu heitetään poikkeus) Kommenttilohko ja mahdolliset tuntemattomat lohkot metodin tulee ohittaa. Luo ja sulje tietovirta testiluokassasi, ei tässä metodissa.
Jos luettavassa tiedostossa on jotain pielessä, metodi heittää poikkeuksen
CorruptedChessFileException
. Annettu tehtäväpohja heittää jo tällaisen poikkeuksen tilanteessa, jossa tietovirran luvussa on jokin ongelma. Sinun pitäisi heittää sellainen, jos luetun tiedon rakenteessa on jokin ongelma.
Valmiiksi annettu koodi
board.py. (Ei palauteta) Hyvin yksinkertainen luokka joka kuvaa pelilautaa. Mahdollistaa nappuloiden sijoittamisen laudalle ja laudan ruutujen tarkastelun. Sisältää apumetodin sarakekirjainten muuntamiseen ruudukon indekseiksi.
player.py. (Ei palauteta) Hyvin yksinkertainen luokka joka kuvaa pelaajaa. Mahdollistaa nimen ja pelaajan värin asettamisen ja näiden attribuuttien lukemisen.
game.py. (Ei palauteta) Hyvin yksinkertainen luokka joka koostaa pelissä tarvitut luokat yhteen. Mahdollistaa pelaajien ja pelilaudan asettamisen ja hakemisen.
piece.py. (Ei palauteta) Hyvin yksinkertainen luokka joka kuva shakkinappulaa. Shakkinappulalla on omistaja (pelaaja) sekä tyyppi (Kuningas, Ratsu, jne...)
corrupted_chess_file_error.py. (Ei palauteta) Poikkeusluokka tiedostonluvussa esiintyvien ongelmien esittämiseen.
broken_reader.py. (Ei palauteta) Apuluokka virhetilanteiden testaamiseen.
human_writeable_IO.py. Palautetaan
Apuluokka, jonka avulla voidaan ladata ja tallentaa (tässä harjoituksessa tallennusta ei toteuteta) pelitilanne tiedostosta.
Toteuta luokkaan vaadittujen metodien lisäksi halutessasi myös apumetodeja. Esimerkiksi eri tyyppisten lohkojen lukemiseen voi olla kätevää tehdä omat metodit.
test.py. Palautetaan Testitiedoston pohja. Testiluokan nimen tulee olla Test jotta A+ tunnistaa sen. Eli Test tiedostossa test.py.