Pommipeli - loppuraportti

Tämä on informaatioverkostojen studio1-kurssia varten tekemäni ohjelmointiprojektin loppuraportti. Ohjelmointiprojektini aiheena oli yksinkertainen räiskintäpeli.

| Pelin kuvaus || Tekninen toteutus || Kokemukset projektista || Yhteenveto |


Pelin kuvaus

Projektityöni vaiheisiin tutustuminen kannattaa aloittaa vilkaisemalla läpi varhainen aihe-ehdotukseni. Sen jälkeen kannattaa tarkistaa projektisuunnitelmastani, mitä lopulta lähdin tekemään. Itse peli poikkeaa jonkin verran molemmista.

Pelin käyttöliittymä on suurelta osin hiirivetoinen. Koko ajan näkyvissä on ikkuna, jossa on yksi valikkorivi ja siinä yksi valikko, Peli. Valikossa on viisi kohtaa. Ensimmäinen on "Aloita peli", jota painettaessa peli alkaa. Pelin ollessa käytössä kyseisen kohdan tekstinä on "Lopeta peli2, ja sitä painettaessa peli päättyy. Toinen valikon kohta on "Keskeytä peli", jota painamalla voi keskeyttää pelin esimerkiksi puhelun tai vessakäynnin ajaksi. Kun peli on keskeytettynä, kyseisen kohdan teksti on "Jatka peliä", ja sen valitsemalla peli jatkuu. Kolmas kohta on "Ohjeet", jota painettaessa tulee näkyviin uusi, peliohjeet näyttävä ikkuna. Neljäs kohta on "Parhaat tulokset", ja sen avulla saa näkyviin kymmenen parasta tulosta. Viides kohta on "Sulje", ja se sulkee peli-ikkunan.

Suurimman osan peli-ikkunan koosta vie varsinainen pelialue, joka koostuu kahdesta osasta: "taivaasta" ja "kaupungista". Aivan taivaan yläreunassa on tekstirivi, joka kertoo pelin tilan: kuluneen ajan, pelaajan pisteet, tuhotut pommit, kaupunkiin osuneet pommit sekä käytetyt ydinlataukset. Kun peli aloitetaan, taivaalta alkaa tippua mustia palloja ("pommeja"), joita pelaaja yrittää tuhota, ennen kuin ne tippuvat kaupunkiin.

Tuhoaminen tapahtuu klikkaamalla hiirellä jotain kohtaa taivaalla kaupungin yläpuolella. Tällöin kaupungin keskellä oleva antenni lähettää kyseiseen kohtaan pulssin, jonka laukeaminen näkyy hetken päästä näytölle piirtyvänä punaisena ympyränä, joka jää näytölle sekunniksi. Jos tänä aikana joku tai jotkin pommeista ovat laukauksen alueella, ne tuhoutuvat. Jotta tuhoaminen ei olisi liian helppoa, voi seuraavan pulssin laukaista vasta, kun edellinen on kadonnut näytöltä. Toinen mahdollisuus pommien tuhoamiseen on ydinlataus, joka laukaistaan painamalla välilyöntiä. Ydinlataus tuhoaa kaikki ruudun pommit, mutta tällöin pelaaja menettää 50 pistettä.

Pommien lähtöpaikka on taivaan yläreuna, välittömästi pelin tilan näyttävän rivin alapuolella. Lähtöpaikan x-koordinaatti määräytyy satunnaisesti, samoin pommin vauhti ja tulokulma. Jos pommin suunta on sellainen, että se on menossa ohi kaupungista, "kimpoaa" se törmätessään ruudun reunaan. Pommien"syntymisen" välissä kuluva aika määräytyy osittain satunnaisesti, mutta kuitenkin siten, että pommeja tulee koko ajan nopeammassa tahdissa, kunnes pelaaja ei ehdi enää tuhoamaan niitä kaikkia. Peliä ei siis voi "päästä läpi", vaan se päättyy, kun viisi pommia on osunut kaupunkiin.

Pelin mielekkyys tulee siitä, että pelaajan suoritus pisteytetään. Jokaisesta tuhotusta pommista saa kymmenen pistettä. Lisäksi jos samalla laukauksella tuhoaa useita pommeja, saa jokaisesta näistä pommista pisteet kerrottuna pommien määrällä. Ohilaukauksesta sakotetaan viisi ja ydinlatauksen käytöstä jo mainitut 50 pistettä. Pelin loputtua peli kysyy pelaajan nimeä ja tallentaa sitten tämän nimen, peliajankohdan, peliin kuluneen ajan, tuhottujen pommien määrän sekä pisteet tiedostoon "Tulokset.txt", jos pelaajan tulos mahtuu kymmenen parhaan tuloksen joukkoon. Sen jälkeen näytetään kymmenen parhaan tuloksen taulukko. Jos pelaaja on päässyt siihen, näkyy hänen rivinsä punaisella tekstillä.

Seuraavat kuvat selventävät, mistä on kysymys. Ensimmäisessä kuvassa peli ei ole vielä alkanut, toisessa on jo täysi tohina päällä ja kolmannessa näkyy tulostaulukko.

Peli ei ole vielä alkanut. Peli käynnissä. Tulostaulukko.


Tekninen toteutus

Jo aloittaessani työskentelyn tiesin, etten tulisi tekemään sovelmaa, kuten vielä projektisuunnitelmassa kaavailin. Esteeksi muodostui, että sovelmat eivät saa lukea tiedostosta eivätkä kirjoittaa sinne, joten high score -taulukkoa ei voinut toteuttaa. Päädyin siis tekemään sovelluksen. Ratkaisusta oli muutakin hyötyä: minun ei tarvinnut perehtyä tarkemmin sovelmien tekoon, ja saatoin käyttää swing-komponentteja, jotka olisivat olleet sovelmalle liian raskaita.

Luokkia päädyin lopulta tekemään kaikkiaan yhdeksän (sekä neljä sisäluokkaa). Koodia niissä on yhteensä yli 2500 riviä. Luokkien rakenteeseen voi tutustua projektini Javadoc-sivuilla. Projektin luokkakaavio näyttää tältä:

Luokkakaavio

Seuraavassa on kuvattu lyhyesti kunkin luokan merkitys. Kunkin luokan nimeä klikkaamalla saa näkyville kyseisen luokan lähdekoodin. Pelin kehysluokka on Peli, joka perii luokan JFrame. Peli-ikkunaan ladotaan muut komponentit, joita ovat valikkorivi sekä kaksi JPanel-luokan alaluokkaa, Pommipaneeli ja Kaupunki. Näppäimistön painallusten kuuntelija sijaitsee myös Peli-luokassa.

Taivasta vastaava Pommipaneeli -luokka on pelin varsinainen sydän. Se toteuttaa liittymän Runnable, eli siinä on määritelty run-metodin toteutus sekä säie, joka suorittaa kyseistä metodia niin kauan, kuin sovelluksen suoritus jatkuu. Pommipaneelissa ovat kaikki tärkeimmät metodi, jotka määrittelevät pelin kulun. Siinä tapahtuvat kaikki animaatiot, ja myös tulosten tallentaminen ja näyttäminen tapahtuvat tässä luokassa. Pituutta luokan koodille kertyi kommentteineen lopulta lähemmäs tuhat riviä.

Pommi -luokka kuvaa pommia, eli käytännössä mustaa ympyrää. Luokassa on muutama tärkeä algoritmi, joiden avulla pommin piirtäminen ja liikuttaminen tapahtuu.

Laukaus -luokka kuvaa ruudulle piirtyvää punaista, laukausta tarkoittavaa ympyrää. Luokka jäi lopulta melko pieneksi, sillä suurin osa laukauksen metodeista oli loogisempaa sijoittaa Pommipaneeliin. Itse asiassa arvelisin, että luokkaa ei olisi tarvinnut toteuttaa ollenkaan, vaan kaiken minkä se tekee olisi voinut tehdä Pommipaneelissa. Toisaalta jälki on kauniimpaa, kun luokkia on enemmän, ja Pommipaneeli on jo muutenkin hulppean kokoinen.

Ohjekehys -luokka periytyy luokasta JDialog. Sen olio on oma ikkunansa, jossa näytetään peliohjeet. Ohjeet on muotoiltu html:llä käyttäen hyväksi swingin JEditorPane-luokkaa.

ParhaatTulokset -luokka periytyy luokasta JPanel. Koska sillekin tehdään Pommipaneelissa oma ikkuna, olisi luokan voinut varmasti periyttää yhtä hyvin JDialog:sta. Luokkaan ladotaan parhaat tulokset taulukoksi käyttäen JLabeleita ja GridBagConstraints-asettelijaa. Itse asiassa käytin ensin JTablea, mutta hylkäsin sen, kun en löytänyt apista, kirjasta tai Java-tutoriaalista mitään keinoa saada värjättyä taulukon soluja. Olettaisin, että tällainen mahdollisuus täytyy olla, mutta jostain syystä en sitä onnistunut löytämään. Nykyinen ratkaisu ei ehkä ole yhtä kaunis, mutta ainakin värjäys toimii.

Kaupunki on pieni JPanel-luokasta periytyvä luokka, jonka ainoa tehtävä on piirtää ikkunan alareunaan kaupunki.

Tulosrivin ja Tulostaulukon koodeissa käytin pohjana neljännessä ohjelmointiharjoituksessa tehtyjä ratkaisujani, ja tein vain niihin tarpeelliset muutokset. Tulosrivi on Tulostaulukon yksi rivi, Tulostaulukossa taas tapahtuvat kaikki tiedostosta lukemiset ja tiedostoon kirjoittamiset.

Kuuntelija, Nappainkuuntelija, Valikkokuuntelija ja Lopeta ovat kuuntelijoiden toteutuksessa käytettäviä sisäluokkia. Ensin mainittu sijaitsee Pommipaneelissa, loput Pelissä. Kuuntelija kuuntelee laukauksia, Nappainkuuntelija ydinlatauksia(välilyönnin painalluksia), Valikkokuuntelija valikkotapahtumia ja Lopeta ikkunan sulkemista. Tärkeimpiä pelin algoritmeista ovat pommien liikkuminen, matkalla olevan laukauksen (pienen punaisen pisteen) suunnan määrittely ja liikkuminen, laukauksen matka-ajan määrittely, pommien lisäys sekä sen testaaminen, osuuko laukaus pommiin.

Pommien liikkuminen oli vaivatonta toteuttaa, sillä löysin Kalakirjasta esimerkin, jossa näytettiin, miten ympäri näyttöä liikkuvan pallon animointi toteutetaan. Laukauksen toteuttamiseenkin keksin nopeasti näppärän tavan, ja kuusi tuntia aloittamisestani näin jo näytöllä putoilevia pommeja ja pystyin tuhoamaan niitä klikkaamalla niitä hiirellä. Matkalla olevan laukauksen toteutuksen lisäsin vasta loppuvaiheessa, mutta samalla periaatteella. Laukauksen matka-ajan ja matkalla olevan laukauksen suunnan määrittelyssä oli eniten matematiikan makua. Math-luokasta löytyi mukavia apuvälineitä, joiden avulla laskin matkan pituuden käyttäen vanhaa kunnon Pythagoraan lausetta.

Pommien lisäysalgoritmia viilasin vähän väliä, enkä vieläkään ole siihen täysin tyytyväinen. Algoritmissa on ehkä liikaa satunnaisuutta, minkä vuoksi toisessa pelissä saa helposti 1000 pistettä, kun toisessa 500 tuntuu jo mahdottomalta saavutukselta. Toisaalta jokainen peli on erilainen, ja kahta samaa pistemäärää tulee harvoin. Peli vaikeutuu myös juuri sopivan nopeasti, joten missään vaiheessa ei ehdi kyllästyä. Lisäksi satunnaisuuden ongelmia poistaa osin ydinlatausten käyttö taktisesti oikeissa paikoissa.

Koodasin pelini alusta loppuun kotioloissa, työvälineenä XEmacs. Apuna käytin Kalakirjaa (Vesterholm - Kyppö: Java-ohjelmointi, 3.painos), josta oli paljon iloa varsinkin säikeiden ja animaation kanssa sekä Java-apia, joka oli lähes korvaamaton. Satunnaisia tietoja hain myös muilta web-sivuilta, joista hyödyllisimpiä olivat Sunin Java-tutoriaali sekä Javaworld. Pelin grafiikat piirsin Paint Shop Pro -kuvankäsittelyohjelmalla. Äänitiedostot hain osoitteesta http://www.freeaudioclips.com.

Javan valmiista kirjastoista käytössä oli mm. paljon tavaraa java.awt ja javax.swing -pakkauksista käyttöliittymää varten. Kuuntelijat tulivat pakkauksesta java.awt.event ja tiedostoon kirjoittamiseen ja siitä lukemiseen liittyvät toiminnot pakkauksesta java.io. Lisäksi käytin muutamia työkaluja pakkauksesta java.util (Random, Set, HashSet, Vector, Date) ja ääniefektien takia tarvitsin myös pakkauksia java.applet sekä java.net. Esimerkiksi luokassa Pommipaneeli on yhteensä 18 import-sanalla alkavaa riviä.


Kokemukset projektista

En ollut suunnitellut työskentelyäni etukäteen kovin tarkasti projektini ohjelmointia aloittaessani. Minulla ei ollut kovin kummoista käsitystä siitä, mitä luokkia tulisin tarvitsemaan, koska en ollut vielä täysin perehtynyt kaikkiin säikeiden ominaisuuksiin. Työskentely eteni silti koko ajan jouhevasti, mutta paremmalla suunnittelulla olisi saattanut kenties säästää jonkin verran aikaa ja koodista olisi saattanut tulla selkeämpää. En ole varma, onko luokkarakenteeni kaikilta osin optimaalinen. Esimerkiksi ratkaisu, jossa Laukaus on oma luokkansa, mutta matkalla oleva laukaus toteutetaan kokonaan pommipaneelissa, tuntuu jotenkin keinotekoiselta. Lisäksi loin varmasti monta turhaa apumuuttujaa vain, koska en jaksanut miettiä kauniimpaa tapaa toteuttaa jokin asia. Pääpiirteissään olen kuitenkin melko tyytyväinen koodiini ja luokkarakenteeseeni.

Heti alkuvaiheessa tein kuitenkin aivan katastrofaalisen virheen, jonka ymmärsin vasta useita päiviä myöhemmin. Jostain käsittämättömästä syystä sain päähäni, että koska yksikin animoitava kuva tarvitsee säikeen, on jokaiselle pommille oltava oma säie. Tämän ratkaisun toteuttamiseen taas en keksinyt muuta tapaa kuin tehdä Pommista luokan JPanel aliluokka, jolloin jokainen pommi piirsi ruudulle oman paneelinsa. Samoin teki jokainen laukaus. Ratkaisu toimi, mutta se söi käsittämättömästi muistia, mikä näkyi käytännössä pelin mielekkään pelaamisen estävänä ruudunpäivityksen tökkimisenä. Ymmärrettyäni, että yksi säie riittää minulta kesti noin päivä tehdä tarvittavat muutokset. Luokka- rakenteeni koki tuolloin melkoisen myllerryksen: loin uuden Pommipaneeli-nimisen luokan, josta tuli pelin ydin. Kyseiseen luokkaan siirrettiin suurin osa Pommin, Laukauksen ja Pelin metodeista. Tyydytys oli suuri, kun ratkaisu lopulta toimi.

Edellä kuvatun lisäksi suurempia ongelmia ei ollut. Pienemmistä mainittakoon vaikeus käydä läpi näytöllä olevat pommit, joka ratkesi, kun keksin käyttää säieturvallista Vector-tietorakennetta. Myös kaava, jolla pommi saadaan tuhoutumaan jos ja vain jos se osuu laukaukseen aiheutti päänvaivaa, vaikka olikin lopulta melko yksinkertainen. Pikkubugeja ja virheilmoituksia oli useita, mutta usein syynä oli kirjoitusvirhe tai ikävä tapani koodata "tajunnanvirtana" sen kummemmin miettimättä, kääntää ja pohtia vasta virheilmoitusten saamisen jälkeen, mitä oikein olen tehnyt.

Käytin projektini tekoon 11 päivän aikana noin 100 (90-110) tuntia, mikä oli myös alkuperäinen ajankäyttöarvioni. Siinä mielessä voin olla etenemiseeni varsin tyytyväinen. Toisaalta etenin alussa niin vauhdikkaasti, että ehdin jo arvioida selviäväni noin 70 tunnilla, mikä olisikin ollut realistista ilman mainitsemaani suunnitteluvirhettä. Silti voin olla ajankäyttööni tyytyväinen, koska sain pelini valmiiksi sen verran hyvissä ajoin, että saatoin pitää kahden ja puolen viikon joululoman.

Yksityiskohtainen kuvaus työskentelyni etenemisestä on luettavissa projektipäiväkirjastani, jota pidin säännöllisesti jokaisena työskentelypäivänä.


Yhteenveto

Olen tyytyväinen aikaansaannokseeni. Sain mahdutettua mukaan lähes kaikki haluamani ominaisuudet. Vain hiiren liikkeiden mukaan kääntyvä tykinputki jäi tekemättä, ja vain koska katsoin apia puusilmällä enkä löytänyt sopivaa metodia. Vasta palautettuani koodin tajusin, että kyseinen ominaisuus olisi ollut varsin helppo toteuttaa. Mutta kaikkea ei voi saada. Joka tapauksessa pelini on toimiva, omien havaintojeni perusteella bugiton, tyylikkään näköinen ja kaiken huipuksi vieläpä varsin hauska ja haastava pelattava.

Uskon oppineeni projektin teon yhteydessä ainakin jonkin verran projektityön suunnittelusta. Javasta opin paljon varsinkin säikeiden käytön osalta, ja myös swing ja awt ovat nyt tuttua kauraa. Lisäksi kokemus vahvisti entisestään sitä jossain viitosharjoitusta tehdessä syntynyttä tunnetta siitä, että todellakin ymmärrän jotain Javasta ja osaan ohjelmoida ja ratkaista ongelmia itsenäisesti. Tämän jälkeen olisi huomattavasti helpompaa lähteä toteuttamaan mitä tahansa Java-projektia.

| Sivun alkuun || Portfolioni pääsivulle |


Copyright © Juho Makkonen
jomakko2 (@) cc.hut.fi
http://www.hut.fi/~jomakko2/