Matopeliä olen viime aikoina tehnyt ja alkaa olla valmis, mutta pitkään olen yrittänyt saada madon hännän seuraamaan itse matoa. Ajatuksena on, että madon x ja y arvo talletetaan taulukkoon ja seuraavassa kertauksessa piirretään häntäpala entiseen madon kohdalle. Ennen mitään liikkumisia piirretään mato+kolme häntäpalaa vierekkäin (10 pikselin välein)ruudulle. Sitten jokaisen palan x ja y arvot talletetaan taulukkoon kohtiin p_valiax[pala] ja p_valiay[pala]. Sitten joka liikkumiskerralla otetaan aina tämänhetkinen madon x ja y arvo ja sijoitetaan taulukon kohtiin 0. Seuraavalla kerralla [0]:n arvo sijoitetaan kohtaan [1] ja piirretään se, mutta näin ei toimi. Alussa näyttöön tulee vain yksi madon pala, omenan syömisen jälkeen omenan paikalle ilmestyy häntäpala (joka ei liiku) ja seuraavan omenan syömisen jälkeen matoa seuraa välkkyvä pala melkein madon päällä. Seuraavan omenan syömisen jälkeen uusi pala ilmestyy ylänurkkaan. Mikähän vikana ja miten itse toteuttaisitte tämän.
Koodi:
int valitaulu[1][800]={0}; if (p_omenalkm==10) { int mihinx=mx; p_valiax[0]=mx; p_valiay[0]=my; for (int i=0; i<p_mlkm;) { draw (mato, screen, p_valiax[i], p_valiay[i]); mihinx+=10; p_valiax[i]=mihinx; p_valiay[i]=my; i++; } } while (!kertaus) //Toistetaan jos kertaus ei ole tosi. { draw (ltausta, screen, 0, 0); p_valiax[0]=mx; p_valiay[0]=my; for (int i=0; i<p_mlkm;) { valitaulu[0][i]=p_valiax[i]; valitaulu[1][i]=p_valiay[i]; i++; } for (int i=0; i<p_mlkm;) { draw (mato, screen, p_valiax[i], p_valiay[i]); i++; } for (int a=1; a<p_mlkm;) { p_valiax[a]=valitaulu[0][osa]; p_valiay[a]=valitaulu[1][osa]; a++; osa=a; osa--; } SDL_Flip (screen);
No ainakin koodista päätellen pitäisi olla valitaulu[2][800].
Ensinnäkin, on hirvittävän rumaa pitää yhtä taulua x-koordinaateille ja toista y-koordinaateille. Tee struct, joka sisältää x:n ja y:n ja käytä sellaisia sijaintien ilmoittamiseen.
Ja kun kerran C++:aa käytät, niin ota siitä ilo irti: käytä jonoa (STL + queue; Google tietää). Toinen äkkiseltään mieleen tuleva vaihtoehto on, että käytät tasotaulua, jossa siis pidät kaikkea tietoa tasosta, ja varaat madolle vaikka kaikki negatiiviset numerot. Joka framella kierrät taulun läpi, pienennät kaikkia negatiivisia yhdellä ja poistat, jos menee pienemmäksi kuin madon pituus (negatiivisena).
Tuosta queuesta. Kun kääntää ohjelmaa niin tulee linkkeri virhe. Eli pitääkö sinne projectin optionsiin laittaa taas jotain tekstiä? (SDL_image tapauksessa se -ISDLmain vaimikäolikaan)
Ei pitäisi tarvita. Kai koodisi kääntyy g++:lla eikä gcc:llä? -lstdc++ voi kuitenkin auttaa.
hmm... g++, gcc, minulle tuntemattomia käsitteitä. Dev-cpp:tä käytän ja mureakuhan komentojono esimerkkikoodia yritän kääntää.
No mikähän linkkerivirhe siitä sitten mahtaa tulla?
[Linker error] undefined reference to 'CommandQueue::InsertCommand(void(*)())' [Linker error] undefined reference to 'CommandQueue::InsertCommand(void(*)())' Id returned 1 exit status
Ja jos tuohon esimerkkikoodiin nyt laittaisi sitten niitä omia komentoja nuitten tulostuskomentojen tilalle niin toimisiko se sillä?
Oletko aivan varmasti kopioinut koko tuon esimerkkikoodin? Kyllä se nimittäin toimii aivan hyvin. Aivan kuin olisit jättänyt tuon yhden funktion kopioimatta.
*hävettää* (pahoittelut vaivaamisesta)
Koko koodi oli kopioitu, mutta kun dev-cpp ei osaa itse nuita kopioituja rivittää niin olipahan kommenttien sekaan jäänyt yksi tärkeä sinnekuulumaton lause.
Lisää:
Mites tuossa queue jutussa, kun siinähän laitetaan arvoja peräkkäin, niin sehän toimisi pelkällä x arvolla. Pitääkö tehdä kaksi queueta, että voi kaksi jonoa tehdä?
Kuten aiemminkin jo vinkattiin: tee yks jono structeista.
Ei se queue välttämättä olekaan tähän aivan paras, kun sitä ei voi käydä läpi. Elikkä vector ehkä ennemmin.
Joo, vectorilla se lienee helpoin tehdä. Oikeastaanhan tuohon saattaisi sopia parhaiten list, mutta se taas vaatisi iteraattoreden käyttöä.
Itse käytän usein perus-containerina std::deque
ta, ellei nimenomaan ole tarvis olla yhteensopiva C-tyylin taulukon kanssa, mihin taas vector
on suunniteltu.
Tähän hommaan sopivin voisi tosiaan olla list
. Iteraattorit voivat kuulostaa vaikealta, mutta ei niiden käyttö sitten niin hankalaa ole, nehän ovat melkein kuin osoittimia. Kannattaa perehtyä joka tapauksessa.
Ei tässä mitään STL:lää tarvita...
Harjoituksen vuoksi voi koodata madolle vaikka ihan oman linkitetyn hännän:
class Nikama { public: int x, y; Nikama *seuraava; Nikama(int x0, int y0, Nikama *seur) : x(x0), y(y0), seuraava(seur) {} Nikama *PoistaViimeinen() { if(seuraava==NULL) { delete this; return NULL; } seuraava = seuraava->PoistaViimeinen(); return this; } }; Nikama *mato = new Nikama(x,y,NULL);
joka framella:
mato = new Nikama(mato->x+xnop,mato->y+ynop, mato); if(mato ei ole syönyt) mato->PoistaViimeinen();
Ööh. Mitenhän tuohon saisi sitten vielä sen piirtovaiheen. Joka framella kohdan jälkeen, lisäämällä draw(matoi, screen, x, y); sain edelleen sen yhden madon palan tulemaan ruudulle. Pitääkö se piirtokohta laittaa eri tavalla?
No tietenkin ne pitää kaikki piirtää. (Kerro ensin, mitä tapaa käytät, niin on helpompi kertoa, miten ne kaikki käydään läpi.)
No ennen tuon esimerkkikoodin copy-pasteamista käytin tapaa:
for (int i=0; i<p_mlkm;) //p_mlkm on funktiolle parametrina välitettävä madon "osien" lukumäärä. { draw (matoi, screen, mx, my); //matoi on madon kuva ennen esimerkkikoodin lisäystä nimenä oli pelkkä mato. i++; } //mx ja my on vain muodon vuoksi, kun en tiedä mitä nyt pitäisi laittaa. Ennen esimerkkikoodia se oli p_valiax[i] ja p_valiay[i]
Älä ikinä, siis ikinä vain kopioi esimerkkikoodia, vaan aina yritä ajatuksella ymmärtää sen idea, ja tuollaisessa lyhyessä tapauksessa kirjoita oma versiosi! Tarkoituksenasi on kuitenkin toivottavasti oppia tekemään matopeli eikä vain tehdä sitä muiden antamista pätkistä ymmärtämättä, miksi se toimii.
Linkitetyn listan voit käydä läpi niin, että teet uuden osoittimen ja osoitat sen ensin listan ensimmäiseen kohtaan. Eli osoitin = mato. Siirryt aina seuraavaan kohtaan laittamalla osoittimen arvoksi osoitin->seuraava. Tätä jatkat, kunnes osoitin == NULL, koska listan viimeisen kohdan "seuraava" on NULL eli nolla eli tyhjä osoitin.
Sanallinen selitys siksi, että joudut itse ajattelemaan etkä voi vain kopioida. Kyllä siinä sanotaan kaikki tarpeellinen, ja jos et heti ymmärrä, lue uudestaan kaikessa rauhassa ja kokeile muutaman kerran.
Kuitenkin piirretään nuilla x ja y arvoilla, vai mitä tekemistä xnop ja ynopilla on tuossa.
Tietenkin piirretään x- ja y-arvoilla, mistä muualta sen sijainnin saisi? Mutta xnop ja ynop ovat ilmeisesti nopeutta kuvaavia suureita.
Eiku minun omassa versiossani piirto tapahtui taulukosta: draw (matoi, screen, p_valiax[mikäkohta], p_valiay[mikäkohta]);
Mutta kun et enää ilmeisesti käytä taulukoita, niin et voi ottaa sieltä koordinaattiarvojakaan. Sen sijaan jos nyt käytät linkitettyä listaa, voit äsken selostamallani tavalla käydä kaikki nikamat läpi ja jokaisen kohdalla piirtää käyttäen sen x- ja y-arvoja.
Tein sen näin:
int* osoittelija=0; osoittelija=&mato; for (; osoitin!=NULL;) { osoitin->seuraava; draw (matoi, screen, x, y); }
Mutta tulee virhe, mistäköhän johtuu?
Korjattu koodi.
Nikama * osoitin = mato; while (osoitin != NULL) { draw(matoi, screen, osoitin->x, osoitin->y); osoitin = osoitin->seuraava; }
Helpoitenhan kaikista matoon liittyvistä toimista selviää rekursiolla. Esimerkkikoodiini kannatta lisätä ainakin seuraavat jäsenfunktiot:
void Nikama::Piirra( /*parametreja (matoi, screen)*/ ) { draw(matoi, screen, x, y); if(seuraava!=NULL) seuraava->Piirra(matoi, screen); } Nikama::~Nikama() { if(seuraava!=NULL) delete seuraava; }
Ja käyttö:
mato->Piirra(matoi, screen);
lopuksi mato täytyy tietysti tuhota (muistin vapautus)
delete mato;
Asiaan liittyen. Java WTK:n mukana tuli J2ME esimerkki aplikaatioita, jossa oli minun mielestäni esimerkillisen hyvin tehty matopeli olioita hyväksi käyttäen.
Täältä voi katsoa miten se on tehty jos ei halua koko WTK:ta imuroida:
http://machine.homeunix.net/~petriai/code/
Aihe on jo aika vanha, joten et voi enää vastata siihen.