Tehtävä 5 - Sokoban
Ruotsinkielinen tehtävänanto saatavilla osoitteessa: http://www.niksula.cs.hut.fi/~alipiain/tehtavat/kierros3/tehtava3sve.html
Tehtävänantoon tulleet muutokset, 12.11. klo 11
- Osatehtävässä 1 muutettu top-metodin kuvauksessa toisen ja kolmannen kohdan järjestystä keskenään.
- Osatehtävässä 2 korjattu switch match:ksi.
- Taustaa
- Tehtävänanto
- Osatehtävä - Kehys Sokoban
- Osatehtävä - Enumeraatio GridType
- Osatehtävä - Pelilogiikka
- Osatehtävä - Luokka GamePanel
- Osatehtävä - Pelin status
- Osatehtävä - Valikkojen kuuntelu
- Osatehtävä - Bonustehtäväehdotuksia
- Osatehtävä - Lopuksi
- Palautus
- Arvostelu
Taustaa
Tässä tehtävässä toteutamme Scalan Swing-luokkakirjaston avulla klassisen pelin nimeltä Sokoban (tunnettu myös nimellä Boxxle). Siinä pelaaja liikkuu ylhäältä kuvatussa suorakulmaisessa sokkelossa ja työntää siellä olevia laatikoita, tavoitteena joko saada kaikki laatikot siirrettyä tiettyyn paikkaan tai (kuten meidän pelissämme on) yksinkertaisesti selviytyä itse maaliin.
Osatehtäviä on melko paljon, mutta kolmatta lukuun ottamatta ne ovat melko pieniä. Kannattaa lukea jokainen osatehtävä kokonaan ennen kuin aloittaa sen toteuttamisen. Muista myös kääntää ja testata tehtäväsi uusien ominaisuuksien lisäämisen jälkeen.
Tehtävänanto
Tämän tehtävänannon suorittamisen jälkeen osaat:
- luoda yksinkertaisen ikkunallisen ohjelman käyttäen Scalan Swing-luokkakirjastoa
- käsitellä erilaisia Swing-komponentteja
- luoda enumeraatio-luokan (Enumeration) ja käsitellä sen arvoja
- lukea tekstitiedostosta tietoja ja käsitellä niitä
- aiheuttaa ja käsitellä poikkeuksia (Exception)
- luoda ikkunalliseen ohjelmaasi valikkorivin ja asettaa sen valikoille toiminnot
Tehtävässä tarvittavat luokat voit ladata kerralla zip-pakettina tästä: luokat.zip.
1. Osatehtävä - Kehys Sokoban
Aloitetaan laatimalla objekti Sokoban, joka perii SimpleSwingApplication-luokan. SimpleSwingApplication-luokan periminen on helpoin tapa toteuttaa yksinkertainan graafisella käyttöliittymällä varustettu ohjelma. Toteuttamalla luokan top-metodin pystytään luoda ohjelmallemme pääikkuna. top-metodin toteutuksen tulee siis olla sellainen, että metodi palauttaa MainFrame-olion. Voit katsoa apua Kierroksella 3 käyttämästäsi View.scala-luokasta.
Seuraavaksi top-metodissa:
- määrää ikkunallesi nimi (title)
- määrää ikkunasi suosituskooksi (300, 375) pikseliä
- määrää ikkunasi avautumaan keskelle ruutua
- luo BorderPanel-olio, joka tulee toimimaan ikkunan sisällön hallitsijana (kannattaa tallentaa muuttujaan, jotta olioon voi viitata myöhemmin)
- tässä kohtaan tähän paneeliin ei lisätä vielä sisältöä vaan ainoastaan asetetaan sen suosituskooksi (300, 300) pikseliä
- lisää äsken luomasi BorderPanel-oliosi ikkunaasi
Viimeiseksi luodaan vielä ikkunaasi valikkorivi. Swingillä rakennetut valikkorivit koostuvat kolmentyyppisistä olioista. Aluksi on kaiken kokoava valikkoriviluokka MenuBar, joka sisältää yhden tai useamman valikon eli Menu-olion. Näihin Menu-oliohin voidaan lisätä taas yksi tai useampi valikkonimike MenuItem. Luo siis ikkunallesi valikkorivi, johon asennat valikon nimeltä "Peli" ja tähän valikkoon nimikkeet "Aloita alusta" ja "Lopeta". Myöhemmissä vaiheissa valikkoihin lisätään myös toiminnot.
Tässä vaiheessa olet jo:
- Alustanut luokan Sokoban, joka perii luokan SimpleSwingApplication.
- Olet luonut luokkaan Sokoban metodin top, joka palauttaa MainFrame-olion.
- Asettanut pääikkunallesi nimen, ikkunan aukeamaan näytön keskelle, ikkunalle suosituskoon ja lisäksi se sisältää vielä tyhjän BorderPanel-olion, jolla silläkin on asetettuna suosituskoko.
- Luonut valikkorivin, jossa on yksi valikko, joka sisältää kaksi nimikettä.
Muista kokeilla luokkasi ajamista!
2. Osatehtävä - Enumeraatio GridType
Tässä osatehtävässä tutustumme erikoislaatuiseen luokkaan — enumeraatioon. Enumeraatio on lueteltu tietotyyppi, joka sisältää jonkin määrättävän joukon kaikki alkiot. Esimerkiksi voitaisiin luoda viikonpäiviä kuvastava enumeraatio, joka sisältää kaikki viikonpäivät (maanantai, tiistai, keskiviikko, torstai, perjantai, lauantai, sunnuntai) ja mahdollisesti niiden operoimiseen tarvittavia metodeja. Enumeraatiossa siis määritellään valmiiksi kaikki mahdolliset luokan ilmentymän arvot. Tätä voi siis käyttää silloin, kun mahdollisia vaihtoehtoja on rajallinen määrä ja vaihtoehdot tunnetaan ennalta.
Toteuta pelikentän ruutujen kuvaamiseen tarkoitettu luokka GridType periyttämällä se Enumeration-luokasta. Kirjoita enumeraatiosi omaan tiedostoon. Katso apua tyyppien määrittämiseen luokan Enumeration dokumentaatiosta. Tässä tehtävässä tarvittavat tyypit ovat:
- PLAYER: kuvastaa ruutua, jossa pelaaja on.
- WALL: kuvastaa seinää. Ruutu, josta pelaaja ei pääse kulkemaan ja jota ei voi siirtää.
- OBSTACLE: kuvastaa liikuteltavaa estettä. Este liikkuu pelaajan tieltä tämän astuessa kyseiseen ruutuun.
- GOAL: kuvastaa maalia, johon pelaajan on tarkoitus päästä voittaakseen pelin.
- EMPTY: tyhjä ruutu, johon pelaaja voi vapaasti liikkua.
Tässä vaiheessa luomme enumeraatiollemme myös apumetodin, jonka avulla kullekin enumeraation arvolle saadaan vastaava väri peliruudukkoon. Kirjoita siis metodi getColor, joka ottaa parametrikseen GridType enumeraation arvon ja palauttaa Color-olion. Helpoin ja paras tapa toteuttaa tämä toiminnallisuus on käyttämällä match-rakennetta. Sen, millä väreillä kukin ruutu esitetään on täysin sinun päätettävissäsi.
Tässä vaiheessa olet edellisten lisäksi:
- Luonut luokan GridType, joka perii luokan Enumeration
- Määritellyt viisi eri enumeraation arvoa tähän luokkaan
- Luonut apumetodin getColor, joka palauttaa kullekin enumeraation arvolle eri arvon
3. Osatehtävä - Pelilogiikka
Seuraavaksi rakennamme pelimme pohjaksi sen varsinaisen logiikkakoneiston, joka pitää yllä tilannekuvaa pelikentästä: seinien ja liikuteltavien esteiden paikoista sekä pelaajan ja maalin sijainneista. Laadimme pelilogiikan omaan luokkaansa, joka ei ole riippuvainen mistään tietystä käyttöliittymätoteutuksesta, vaan sitä voisi sopivan julkisen rajapinnan (eli sopivien metodien) kautta käyttää monella erilaisella graafisella ja ei-graafisella käyttöliittymällä. (Esimerkiksi periaatteessa voisit ottaa käyttöösi kaverisi luoman logiikkaluokan ja tämä toimisi sinun graafisen toteutuksesi kanssa.) Yleensä, ellei kyse ole hyvin yksinkertaisesta ohjelmasta, on hyvä ajatus erottaa sovelluslogiikka ja käyttöliittymä toisistaan — tällöin toiseen on helppo tehdä muutoksia ilman, että toista joudutaan merkittävästi sopeuttamaan muutoksiin.
Pelimme looginen ydin on luokka nimeltä Game. Se periytyy ainoastaan luokasta Object (eli se ei ole minkään Swing-luokan aliluokka). Luokka esittää pelikentän kaksiulotteisena taulukkona, jonka alkiot ovat juuri laaditun enumeraation GridType ilmentymiä.
Luokan Game konstruktori ottaa parametrinaan tiedoston (java.io.File), jonka pohjalta luodaan pelikenttä. Tiedoston tulee sisältää määrätyn muotoisia tekstirivejä, jotka luetaan java.util.Scanner-luokan avulla ja tulkitaan siten peliruudukon rakenteeksi. Esimerkki yksinkertaisesta pelikentästä on tiedostossa kentta1.txt
- Tiedoston ensimmäisellä rivillä on positiivinen kokonaisluku, joka ilmaisee kentän leveyden
- Tiedoston toisella rivillä on positiivinen kokonaisluku, joka ilmaisee kentän korkeuden
- Loput rivit määrittävät kentän rakenteen seuraavasti:
- "@" - pelaajan aloitussijainti
- "#" - liikkumaton seinä
- "$" - liikuteltava este
- "G" - maali
- " " - tyhjä ruutu (välilyönti)
- Huomaa, että pelikentän ulkoseiniä ei välttämättä tarvitse merkitä tiedostoon, koska liikkumatila rajoittuu joka tapauksessa kentän reunoihin. Jos tiedostossa on tunnistamattomia merkkejä, ne tulkitaan seiniksi.
Lataa luokkasi pohjaksi seuraava tiedosto: Game.scala.

Huomaa, että luokan toteutuksen alussa on kummallinen try-catch -temppu. try-catch-finally on Scalassa käytetty rakenne, jonka avulla voidaan käsitellä poikkeuksia (eli luokan Exception ilmentymiä, joita pian sinäkin pääset käyttämään). try-lohko sisältää koodin, joka saattaa aiheuttaa poikkeuksen nämä mahdolliset poikkeukset sitten käsitellään catch-lohkossa.
catch-lohkossa voi määritellä yhden tai useammantyyppisen poikkeuksen käsittelyn. Huomaa kuitenkin, että jos ensimmäisenä ja/tai ainoana poikkeustyyppinä on Exception, kaikki poikkeukset jäävät tähän haaviin. catch-lohkon sisäinen rakenne perustuu case:jen määräämiin tapauksiin (samalla tavalla kuin match-rakenteessa). Edellisten lisäksi voidaan jatkona käyttää vielä finally-lohkoa, joka toteutetaan joka tapauksessa huolimatta siitä kuinka try-lohkon suoritus päättyy. Voit lukea tarkemman, esimerkein varustetun kuvauksen seuraavasta lähteestä: http://www.tutorialspoint.com/scala/scala_exception_handling.htm.
Kun toteutat omia try-catch-lohkoja, muista sijoittaa kaikki mahdollisesti virheen aiheuttavan käskyn lopputulosta tarvitsevasta koodista kokonaisuudessaan try-lohkon sisään, ettei käy niin kun hauvalle tuossa yllä. Tietenkään aina ei ole tarkoituksenmukaista olla koodimaailman Ash Ketchum ja napata kaikkia poikkeuksia. On tapauksia, jolloin ohjelman suorituksen tulisi loppua virheeseen.
Välikevennyksen voimin sitten varsinaiseen osatehtävän pihviin.
Toteuta ainakin seuraavat metodit luokkaan Game:
- private def initGame(f:File) : Unit
- Tässä metodissa käytämme (toisin kuin kierroksen 3 View-luokan toteutuksessa) tiedoston lukemiseen Java-kirjaston tarjoamaa Scanner-luokkaa. Luo siis aluksi yksi luokan instanssi, jota käytät tiedostosta tarvittavien tietojen lukemiseen. Anna Scanner-luokan konstruktorille parametrina metodille välitettävä tiedosto. Huomaa, että Scanner-olion luonti saattaa aiheuttaa FileNotFoundException:in. Käsittele poikkeus tulostamalla virhevirtaan (System.err) viesti tiedoston lukemisen epäonnistumisesta.
- Seuraavaksi käsitellään kentän koko. Lue siis seuraavilta riveiltä tiedostosta pelikentän leveys ja korkeus ja tulkitse nämä kokonaisluvuiksi. Mikäli jompikumpi numeroista (tai seuraavista riveistä) ei ole kokonaisluku aiheutuu NumberFormatException. Käsittele poikkeus heittämällä vastaavan tyyppinen poikkeus eteenpäin avainsanan throw avulla, käytä luokan parametrillista konstruktoria ja määritä poikkeukselle sopiva viesti. Toinen mahdollinen poikkeus tässä kohdassa on NoSuchElementExeption, joka aiheutuu jos toinen pelikentän koon arvoista puuttuu. Käsittele tämä poikkeus vastaavalla tavalla kuin edellinen.
- Kun pelikentän koon lukeminen on onnistunut, voit luoda kaksiulotteisen taulukon säiliöksi kentän asetelmasta. Kaksiulotteisen taulukkosi tulisi sisältää GridType-olioita ja olla tiedostossa määritettyjen mittojen mukainen.
- Lopuksi vielä täytämme taulukon käyttäen apuna alla esiteltyä translateToGridType-metodia. Lue tiedostosta korkeintaan määritettyjen leveyden ja korkeuden verran merkkejä. Tallenna kukin merkki tulkittuna GridType-olioksi oikeaan sijaintiin luomaasi taulukkoon.
- Tarkista vielä lopuksi onko kentässäsi täsmälleen yksi pelaaja ja ainakin yksi maali.
- Tämän metodin toteutuksen yhteydessä kannattaa miettiä, mitkä käytettävistä arvoista kannattaisi laittaa luokan kentiksi. Ja mitkä näistä saattaisivat tarvita julkisia gettereitä.
- private def translateToGridType(s:String, x:Int, y:Int) : GridType.Value
- Metodi palauttaa GridType-tyyppisiä olioita sille annetun merkkijonon s perusteella.
- Parametreina otetaan myös merkin sijainti, jotta voitaisiin tallentaa pelaajan ja maalin sijainti sekä laskea näiden määrä.
- def getGridType(x:Int, y:Int) : GridType.Value
- Metodi palauttaa pyydetystä kentän kohdasta GridType-tyypin olion.
- def hasGameFinished() : Boolean
- Helppo apumetodi, joka palauttaa totuusarvon riippuen siitä onko peli päättynyt, eli onko pelaaja saavuttanut maalin, vai ei.
- private def canMoveTo(x:Int, y:Int) : Boolean
- Metodi tarkistaa voidaanko pelaajaa siirtää pyydettyyn sijaintiin.
- Pelaajan siirtäminen onnistuu jos:
- Pyydetty ruutu on kentän sisäpuolella
- Pyydetty ruutu on joko pysty- tai vaakasuunnassa yhden ruudun päässä pelaajan sijainnista. Diagonaalisti ei käy!
- Pyydetty ruutu on tyhjä..
- ..tai jos siinä on este, jonka takana on tyhjää tilaa, johon este voidaan työntää.
- Pyydetty ruutu on maali.
- Pelaajan siirtäminen ei siis onnistu:
- Kentän ulkopuolelle.
- Pidemmälle kuin yhden ruudun päähän tai vinoittain viereisiin ruutuihin.
- Seinään tai esteen päälle.
- def move(x:Int, y:Int) : Boolean
- Metodi tarkastaa onnistuuko pelaajan siirtäminen pyydettyihin koordinaatteihin ja huolehtii siirrosta. Metodissa siis päivitetään pelikenttää, pelaajan sijaintia ja mahdollisesti siirretään esteitä.
- def startOver() : Unit
- Aloittaa pelin alusta alkuperäisellä kentällä.
- def startOver(f:File) : Unit
- Aloittaa pelin alusta uudella kentällä, joka määritellään parametrina annetussa tiedostossa.
- Tätä metodia ei tässä vaiheessa tarvitse toteuttaa, ja sitä vaaditaan ainoastaan bonustehtävässä.
Kun Game-luokkasi on valmis, voit testata sen toiminnan seuraavan luokan avulla: SikobanTest.scala. Se käyttää ruudukkoa apuna yksinkertaisessa merkkipohjaisessa Sokoban-pelissä, jota voi pelata syöttämällä aina sen ruudun koordinaatit, johon pelaajan halutaan seuraavaksi siirtyvän.
Luomasi pelilogiikka on aika kytkeä osaksi edellä aloitettuun graafiseen käyttöliittymään.
Tässä vaiheessa olet edellisten lisäksi:
- Luonut Game-luokan, joka toimii logiikkaluokkana pelissä.
- Toteuttanut tiedostojen lukemiseen perustuneen rakenteen, jonka avulla voidaan luoda Sokoban pelin kenttiä.
- Toteuttanut käsittelyn tarvittaville poikkeuksille, jotka voivat ilmetä kentän luonnin yhteydessä.
- Toteuttanut joukon apumetodeja joiden avulla voidaan mm. varmistua pelin päättymisestä ja aloittaa peli alusta.
- Olet testannut ja varmistanut luomasi pelilogiikan oikeanlaisen toiminnan.
4. Osatehtävä - Luokka GamePanel
Seuraavaksi luodaan paneeli, joka kuvastaa ruudukoitua pelikenttää. GamePanel perii luokan GridPanel. Koska GridPanel-luokan konstruktori vaatii rivien ja sarakkeiden lukumäärät, täytyy vastaavanlainen konstruktori määrittää myös GamePanel-luokallemme. Edellisten parametrien lisäksi luokkamme konstruktori ottaa vielä äsken luomamme Game-luokan instanssin.
Tässä vaiheessa voit hetkeksi palata Sokoban luokan pariin ja liittää GamePanel:in osaksi käyttöliittymää. Sijoita tämä GamePanel aikaisemmin luomaasi BorderPanel-tyyppiseen säiliöön. Lisää myös Sokoban-luokkaasii vastaavalla tavalla kuin Game-luokassa try-catch-lohko, joka tässä kohti yrittää aloittaa uutta peliä annetulla kentta1.txt-tiedostolla. Tässä poikkeuksiin voidaan reagoida kaikkiin samalla tavalla, ainoastaan tulostamalla virheen viesti.
Käytämme GamePanel-luokassa ruudukon piirtämiseen OLO-tapauksessa esiteltyä Graphics2D-oliota. Ylikirjoitamme suojatun (protected, ei löydy Scala API:sta näkyvyytensä takia) metodin paintComponent, joka ottaa parametrinaan Graphics2D-olion. Metodin ei tule palauttaa mitään. paintComponent-metodia kutsutaan automaattisesti aina kun metodin edellisen kutsun suoritus on saatu päätökseen, mistä huolehtii Scalan Swing.
Meidän toteutuksessamme paintComponent-metodin toiminta etenee niin, että jokainen Game-luokan instanssin määräämä ruutu piirretään aikaisemmin luodun enumeraation GridType määrämällä värillä. Määritä piirtämiesi ruutujen kooksi (30, 30) pikseliä ja käytä Graphics2D-oliota vastaavasti kuin OLO-tapauksessa.
Seuraavaksi toteutamme pelikentän reaktiot käyttäjän toimintaan. Voit valita omien mieltymystesi mukaan haluatko pelisi toimivan seuraamalla käyttäjän hiiren liikkeitä vai klikkauksia (näppäimistön kuuntelu jätetään bonustehtäväksi). Lisää siis valitsemasi hiiren toiminnot luokan GamePanel kuunneltavaksi käyttämällä listenTo-metodia.
Määrittele sitten rektiot näihin klikkauksiin, muokkaamalla GamePanel:in reactions-kenttää. Saat selville hiiren aiheuttamasta tapahtumasta (joko MouseMoved tai MouseClicked) hiiren sen hetkisen kursorin sijainnen, minkä perusteella voit pyytää Game-luokan instanssilta siirtymistä siihen pelikentän ruutuun. Toteuta hiiren kuuntelu niin, että kun peli on saatu päätökseen pelaajan liikuttaminen ei enää onnistu.
Kokeile nyt myös graafisesti liikkuuko pelaaja oikein.
Tässä vaiheessa olet edellisten lisäksi:
- Luonut luokan GamePanel, joka perii luokan GridPanel.
- Lisännyt tämän GamePanel- ja Game-luokat osaksi pelisi toteutusta.
- Ylikirjoittanut paintComponent-metodin, joka ottaa parametrikseen Graphics2D-olion ja piirtää sen avulla pelikenttää kuvastavan ruudukon.
- Toteuttanut käyttäjän hiiren kuuntelun, jonka perusteella pelaajaa voidaan liikuttaa peliruudukossa.
- Testannut ensimmäisen toimivan Sokoban-pelisi myös graafisesti.
5. Osatehtävä - Pelin status
Luodaan seuraavaksi mekanismi, joka antaa lisätietoa pelin tilanteesta. Luo Label-olio, johon pelin alussa asetetaan teksti, joka kuvastaa sitä, että peli on käynnissä. Muista liittää tämä osaksi Sokoban-pelisi käyttöliittymään peliruudukon alle.
Luo lisäksi julkinen metodi, joka ottaa parametrikseen merkkijonon ja asentaa sen Label:isi tekstiksi.
Nyt voit muokata aikaisemmin määrittelemäsi GamePanel-luokan reaktioihin tekstin muuttumisen pelin päätyttyä.
Tässä vaiheessa olet edellisten lisäksi:
- Luonut mekanismin, jolla pelaaja voidaan pitää ajan tasalla pelin tilanteesta.
- Laatinut tekstin päivittymään pelin päätyttyä.
6. Osatehtävä - Valikkojen kuuntelu
Nyt lisäämme ensimmäisessä osatehtävässäsi luomiin valikkoihin kuuntelijat.
Lopeta -valikko
Muokkaa valikkonimikkeen luontia niin, että se ottaa parametrinaan uuden Action-olion. Action-olio ottaa parametrinaan valikon nimen. Rakenna tälle instanssille luokkalohko, jossa toteutat apply-metodin ja asetat valikolle pikanäppäimen. Tässä pieni apu, josta voit lähteä liikkeelle:
contents += new MenuItem(new Action("valikon nimi") { def apply() { /* apply metodin sisältö */ } accelerator = Some( /* näppäinkomento, johon valikko reagoi */ ) })Määrittelemme tässä siis uuden nimettömän luokan, jota käytämme vain tässä. Muokkaa nyt apply-metodisi lopettamaan ohjelmasi suorituksen. Pikanäppäimen määrittämiseen voit käyttää omaa harkintaasi, tyypillinen vaihtoehto olisi esimerkiksi alt+F4. Tarvitset tämän toteuttamiseen Java Swing-luokkakirjaston KeyStroke-luokkaa. Helpoin tapa on käyttää merkkijonon perametrikseen ottavaa luokan konstruktoria.
Aloita alusta -valikko
Pelin alusta aloittaminen toteutetaan samanlaisella rakenteella kuin edellä, mutta luonnollisesti erilaisella apply-metodin toiminnalla ja pikanäppäimellä. apply-metodisi tulisi päivittää ikkunasi alaosassa näkyvä teksti kuvastamaan sitä, että peli on aloitettu alusta ja huolehtia pelikentän saattamisesta alkuasetelmaan. Käytä tässä jälkimmäisessä tehtävässä pelilogiikka luokkaan Game luomaasi startOver-metodia.
Pikanäppäimen valinnassa voit käyttää jälleen omaa harkintakykyäsi, mutta tyypillinen vaihtoehto olisi F2-näppäin.
Tässä vaiheessa olet edellisten lisäksi:
- Luonut pelin lopettamiseen toimivan valikon ja lisännyt siihen myös pikanäppäinkomennon.
- Luonut pelin aloittamiseen toimivan valikon ja lisännyt siihen myös pikanäppäinkomennon.
7. Osatehtävä - Bonustehtäväehdotuksia
Tässä vaiheessa sinulla pitäisi olla toimiva Sokoban peli, joka toimii sellaisenaan.. mutta ei ehkä ole kovin monipuolinen eikä välttämättä näytä hyvältä. Voit kehittää peliäsi eteenpäin vielä muutamalla bonus-ominaisuudella (ei vaadita täysiin pisteisiin), joista muutamia esitellään seuraavassa osatehtävässä. Voit aina kehittää myös omia ominaisuuksiasi.
Päheämmät grafiikat
Lataamassasi luokat.zip-paketissa oli mukana myös kaksi kuvaa. Nämä kuvat on laitettu mukaan silmälläpitäen pelimme ulkoasua. Voit käyttää näitä kuvia tai luoda omasi saadaksesi peliin eloa. Voit käyttää kuvia esittämään pelaajaa (sika) ja estettä (betoniporsas)


Pystyt käyttämään kuvien piirtämiseen paintComponent-metodissa käyttämääsi samaista Graphics2D-oliota.
Kentän vaihtaminen
Lisää valikkoosi uusi nimike, joka on vastuussa pelikenttätiedoston vaihtamisesta. Toteuta valikkonimikkeelle kuuntelija vastaavasti kuin kuudennessa osatehtävässä. Sinun täytyy luoda tiedostonvalitsija ( FileChooser, joka hyväksyy tekstitiedostoja. Pystyt rajaamaan vain tiettyihin tiedostoihin FileFilter-luokan avulla.
Luo Game-luokkaasi uusi metodi, joka ylikirjoittaa startOver-metodin ottamalla parametrinaan File tyyppisen olion, jonka perusteella uusi peli aloitetaan.
Kenttien generointi (vaikea: 6p arvoinen)
Luo peliisi mekanismi, jolla generoidaan jokaisella pelikentällä (voitettavissa oleva) uusi sokkelo.
8. Osatehtävä - Lopuksi
Testaa vielä lopuksi kaikkien tekemiesi luokkien toiminta ja varmista, että kaikki toimii toivomallasi tavalla. Viimeistään tässä vaiheessa kommentoi koodisi ne pätkät, jotka saattavat olla vaikeaselkoisia. Pakkaa koko ratkaisusi niin, että tarkistavan assistentin ei tarvitse ladata mitään muuta lisäksi ohjelman toimimiseksi. Laita siis palautettavaan pakettiin ainakin seuraavat tiedostot: Game.scala, GamePanel.scala, GridType.scala, SikobanTest.scala, Sokoban.scala, kentta1.txt sekä mahdolliset kuvatiedostosi, jos olet luonut peliisi hienommat grafiikat. Tämän lisäksi lisää vielä pakettiisi readme, jonka pohjan voit kopioida alta.
# ME-C2120 Syksy 2013 # # Kierros 5: Sokoban Opiskelijanumero: Vaadittuihin osiin käytetty aika tunteina (arvio): Bonustehtäviin käytetty aika tunteina (arvio): # Mitkä kierroksen osiot toteutit merkitse ne sanalla 'tehty'. # Voit merkitä myös tekemäsi, mutta toteuttamatta jääneet kohdat sanalla 'yritetty'. # Voit mainita, miksi näiden tehtävien toteuttaminen ei onnistunut. O1 Kehys Sokoban: ei ole tehty Pelin ikkuna tehdään ja se näkyy oikeankokoisena: ei ole tehty Pelin ikkuna avautuu keskelle näyttöä: ei ole tehty Pelin ikkunalla on valikkorivi: ei ole tehty O2 Enumeraatio GridType: ei ole tehty Enumeraatio sisältää vaaditut tyypit: ei ole tehty Enumeraatiolla on vaadittu apumetodi: ei ole tehty O3 Pelilogiikka: ei ole tehty Pelikenttä tulkitaan oikein: ei ole tehty Pelaaja liikkuu sääntöjen mukaan pelissä: ei ole tehty Peli voi päätyä: ei ole tehty O4 Luokka GamePanel: ei ole tehty Pelissä tulkitaan kenttätiedosto graafisesti oikein: ei ole tehty Pelisi reagoi käyttäjän komentoihin: ei ole tehty O5 Pelin status: ei ole tehty Pelin status päivittyy tarvittaessa: ei ole tehty O6 Valikkojen kuuntelu: ei ole tehty Pelin lopettava valikko toimii: ei ole tehty Pelin uudestaan aloittava valikko toimii: ei ole tehty # Teitkö annettuja bonustehtäviä tai jotain muita lisäominaisuuksia? (Kerro mitä teit ja lisää mahdolliset käyttöohjeet, mikäli toiminnallisuus poikkeaa suuresti vaaditusta kokoelmasta.) # Jäikö ohjelmaasi virheitä tai kohtia, jotka eivät toimi oikein? (Listaa kaikki tuntemasi ongelmasi. Mainitse, jos mahdollista, omat arviosi näiden syyksi ja kuinka olet yrittänyt korjata ne.) # Vapaat tehtävään liittyvät kommentit? (Voit vapaasti kommentoida tehtävää, mutta muista ennen kaikkea jättää varsinainen palaute palautelomakkeen kautta.)Täytä readme.txt ja liitä sekin osaksi palautustasi. Nimeä palautettava pakettisi muotoon opnro_kierros5.zip (ei vitsiniekkoja tällä kertaa, vaan opnro:n tilalle oma opiskelijanumero :).
Palautus
Tehtävän palautus tehdään aikarajaan lauantaihin 23.11. klo 18.00 mennessä Rubyric-järjestelmään. Myöhästyneestä palautuksesta seuraa 2 pisteen vähennys alkavaa vuorokautta kohden – eli esimerkiksi sunnuntaina klo 18.01 palautetun työn pisteistä vähennetään 4 pistettä. Väärästä pakkausmuodosta (sallittuja .jar ja .zip) seuraa yhden pisteen sakko. .scala-tiedoston puuttumisesta seuraa yhden pisteen sakko. readme.txt-tiedoston puuttumisesta seuraa yhden pisteen sakko.
Palautuksen jälkeen käy vielä vastaamassa palautteeseen osoitteessa: http://www.cs.hut.fi/cgi-bin/teekysely.pl?action=showform&id=studio1-scala5-2013&lang=FIN
Arvostelu
Ohjelmointitehtävät arvostellaan erillisten kriteerien mukaisesti. Tehtävän maksimipistemäärä on 60 pistettä ja hyväksytty suoritus on vähintään 30 pistettä.
Jos tehtäväpalautus on selvästi hyvin keskeneräinen ja se saa alle vaaditun hyväksytyn pistemäärän, se voidaan palauttaa tekijälle korjattavaksi (ns. bumerangi). Tehtävä on hyväksytty vasta, kun se on palautettu riittävän laajuisena ja syvällisenä. Bumerangina palautetusta tehtävästä voidaan antaa korjattunakin enintään minimipistemäärä 30.
Kannattaa kiinnittää huomiota koodin ja sisennyksen selkeyteen; jos assistentti näkee koodista selkeästi ajatuksenkulkusi, helpottaa se koodin toimivuuden ja vikatilanteiden vakavuuden arviointia. Kannattaa myös varmistaa, että palautettava koodi kääntyy ja että kyseessä on viimeisin versio harjoituksestasi.
- Tehtävänannon täyttyminen ja toimivuus - 40 pistettä
- Jos tehtävänannossa kuvattua toiminnallisuutta puuttuu, ei suorituksesta voi antaa täysiä pisteitä. Karkeasti voisi arvioida, että puoleen osion pisteistä vaaditaan vähintään 50-prosenttisesti toteutettu tehtävänanto, ja 90-prosenttisesti oikein tehty tehtävä on suunnilleen 36 pisteen arvoinen.
- Tehtävänantoa ei kuitenkaan ole pakko noudattaa orjallisesti – ohjeista poikkeaminen on sallittua, kunhan sen tekee hyvin perusteluin.
- Selkeyden vuoksi tällä kierroksella toimivuus on liitetty samaan osioon. Pelkästään osatehtävien toteutus ei riitä vaan niiden on myös toimittava oikein.
- Järkevyys ja selkeys - 10 pistettä
- Usein, saman asian voi toteuttaa monella tapaa, eikä yksi tapa välttämättä ole toisia parempi. Käyttäkää vapaasti luovuuttanne, mutta koettakaa pyrkiä myös eleganssiin – tehkää loppuun asti mietittyjä, helposti ymmärrettäviä ratkaisuja, välttäkää turhia koodirivejä elleivät ne selkiytä koodia.
- Tyyli - 5 pistettä
- Tähän kuuluvat muun muassa systemaattinen, oikeaoppinen sisennys, omien muuttujien ja metodien tarkoituksenmukainen nimeäminen sekä kommentointi siellä, missä se parantaa koodin ymmärrettävyyttä.
- Yleisvaikutelma - 5 pistettä
- Edellisten kohtien lisäksi työstä voi saada korkeintaan viisi lisäpistettä. Täydet pisteet saa moitteettomasta tai lähes moitteettomasta suorituksesta, neljästä kolmeen pistettä saa mikäli työssä on joitakin puutteita ("ihan ok"), 2-1 pisteen saaminen tarkoittaa jo merkittäviä erehdyksiä ja nolla pistettä tarkoittaa jo hyvin suuria puutteita työn sisällössä tai muodossa.
Lisäksi ohjelmointitehtävissä on mahdollista saada lisäpisteitä tehtävänannon ylittävästä suorituksesta