Moi. Kyhäilin PDCursesilla tämmösen alkeellisen roguelike (ehkä ei tota termiä vielä voi käyttää) pelin. Monia aloittelevia koodaajia tuntuu kiinnostavan tämmöiset, joihin itsekkin lukeudun.
Pelissä on jopa hyvin alkeellinen luolastogeneraattori, collision detection ja victory condition (en keksinyt suomenkielisiä nimiä) kaikki yhdessä main funktiossa. Osa koodista apinoitu internetin syövereistä löytämästäni pienestä esimerkistä, ja muokattu/laajennettu/parenneltu/selkeytetty omiin tarpeisiin sopivaksi.
Tämä ei periaatteessa ole opas (vaatis ainakin lisää kommentointia), mulla ei ehkä riitä rahkeet sellaisen kirjoittamiseen, mutta voi tästä mallia ottaa. Lähinnä postaan tämän tänne koska mulla on hieman kysymyksiä. Saa kommentoida muutenkin, koodissa ei välttämättä ole paljon järkeä, mutta se on C++ peruskurssin + kahden päivän curses opintojen tulos =)
Kääntyy ainakin VC++ 2008:lla, ja todennäköisesti toimii linuxillakin kunhan vaihtaa ton yhden includen, kaikki curses funktiot on suoraa putkan ncurses oppaasta.
Ohje PDCursesin käyttöönottoon Visual C++:lla: http://www.cplusplus.com/forum/windows/7819/
#include <cstdlib> #include <curses.h> // Linuxilla <ncurses.h> #include <ctime> const char MAALI = '8'; const char SEINA = '#'; const char POLKU = '.'; const char HAHMO = '@'; const int LuolastonVari = 8; // vaaleanharmaa const int UkkelinVari = COLOR_RED; const int VAIKEUSASTE = 3; // 1 - X, Mitä suurempi luku, sitä vähemmän esteitä (ei saa olla 0) int main() { // Luolaston piirtäminen char map[50][93]; // Kartan koko // ks. putkan NCurses opas, siellä selitetty nämä // https://www.ohjelmointiputka.net/oppaat/opas.php?tunnus=ncurses keypad(initscr(),1); resize_term(56,93); // Terminaalin ikkunan koko curs_set(0); raw(); noecho(); start_color(); init_pair(LuolastonVari,LuolastonVari,COLOR_BLACK); init_pair(UkkelinVari,UkkelinVari,COLOR_BLACK); uusiluola: // goto-kikkailua attron(A_BOLD); // Ääkköset ei toiminut mulla jollei käyttänyt näitä merkintöjä :S // eli \x84 = 'ä' ja \x94 = 'ö' mvprintw(51,5,"L\x94yd\x84 tiesi maaliin joka sijaitsee oikeassa laidassa."); mvprintw(52,5,"Voit k\x84ytt\x84\x84 joko nuolin\x84pp\x84imi\x84 tai numpadia."); mvprintw(53,5,"Numpadin k\x84ytt\x94 suositeltavaa jotta voit liikkua diagonaalisesti"); mvprintw(54,5,"Jos luolasto on mahdoton, paina r saadaksesi uuden. Lopeta painamalla q"); attroff(A_BOLD); attron(COLOR_PAIR(LuolastonVari)); int y=1; // Aloituskohdan koordinaatit int x=1; int c; // Muuttuja näppäinpainalluksia varten srand(time(NULL)); // Siemennetään satunnaislukugeneraattori for(int yy=0;yy<50;yy++) for(int xx=0;xx<93;xx++) map[yy][xx]=((yy!=0)&&(yy!=49)&&(xx!=0)&&(xx!=92) &&(rand()%VAIKEUSASTE))?POLKU:SEINA; map[rand()%8+1][92] = MAALI; // Määritetään missä kohdassa maali on kartalla // Liikkuminen ja "collision detection" // KEY_A1 - KEY_C3 tarkoittaa numpadin numeroita while((x!=92) && ('q'!=(c=getch()))) // x!=92 on "victory condition", // eli kun hahmo pääsee x-koordinaattiin 92 (maali), peli päättyy { for(int yy=0;yy<50;yy++) for(int xx=0;xx<93;xx++) mvaddch(yy,xx,map[yy][xx]); // Ylös, alas ja sivuille liikkuminen joko nuolinäppäimillä // tai numpadilla. Liikkuminen haluttuun suuntaan onnistuu siis vain, jos kyseisessä suunnassa on merkki POLKU (tai MAALI) // eikä SEINA tai mikään muukaan. Eli siinä alkeellinen "collision detection" :D if((KEY_UP==c || c==KEY_A2) && (POLKU==map[y-1][x] || MAALI==map[y-1][x])) y--; if((KEY_DOWN==c || c==KEY_C2) && (POLKU==map[y+1][x] || MAALI==map[y+1][x])) y++; if((KEY_LEFT==c || c==KEY_B1) && (POLKU==map[y][x-1] || MAALI==map[y][x-1])) x--; if((KEY_RIGHT==c || c==KEY_B3) && (POLKU==map[y][x+1] || MAALI==map[y][x+1])) x++; // Viistoittain liikkuminen numpadilla if(KEY_A1==c && (POLKU==map[y-1][x-1] || MAALI==map[y-1][x-1])) { x--; y--; } if(KEY_A3==c && (POLKU==map[y-1][x+1] || MAALI==map[y-1][x+1])) { x++; y--; } if(KEY_C1==c && (POLKU==map[y+1][x-1] || MAALI==map[y+1][x-1])) { x--; y++; } if(KEY_C3==c && (POLKU==map[y+1][x+1] || MAALI==map[y+1][x+1])) { x++; y++; } if(c=='r') goto uusiluola; mvaddch(y,x,HAHMO|COLOR_PAIR(UkkelinVari)|A_BOLD); } attroff(COLOR_PAIR(LuolastonVari)); clear(); mvprintw(4,15,"Selviydyit loppuun!"); mvprintw(5,15,"Paina nappia jatkaaksesi"); getch(); endwin(); }
Oli paljonkin kysymyksiä vielä tässä aikaisemmin mielessä, mutta nyt ei keksi. Kyselen sitä tahtia kun tulee mieleen.
1. Miten ton "MAALI":n värin saa muutettua? Oon yrittänyt ton map[rand()%8+1][92] = MAALI; kohdan ympärille laittaa attron(COLOR_PAIR()) juttuja mutta ei vaan toimi.
2. Jos haluan jotain muitakin merkkejä randomisti kartalle, niin miten se toteutetaan? Tai tietääkö joku jotain järkevämpää ja hienompaa luolaston generointitapaa? =)
3. Sitten tämä kohta
map[yy][xx]=((yy!=0)&&(yy!=49)&&(xx!=0)&&(xx!=92) &&(rand()%VAIKEUSASTE))?POLKU:SEINA;
Tän sain eräältä roguelike foorumilta, enkä täysin tajua sitä. Tajuan, että loppujen lopuksi se täyttää kartan merkeillä POLKU ja SEINA, mutta muuten en tajua mitä tossa tapahtuu. Jotain ehtolauseita sijoittamisessa? Ja mikä toi '?' merkki on? Joku operaattori josta en ole kuullut?
collision detection = törmäystarkistus
victory condition = voittoehto
1. Värin muuttaminen pitäisi tehdä siinä vaiheessa, kun merkki tulostetaan, eli mvaddch-kutsun ympärillä joudut tarkistamaan, tulostetaanko MAALI eli pitääkö vaihtaa väriä.
2. Niitäkin voi laittaa samalla tavalla kuin tuon maalin. Käyttämällä for-silmukkaa voi helposti määrätä luotavan määrän.
3. Rivin voisi kirjoittaa myös näin:
// laitetaan kaikkiin ruutuihin ensin seinä map[yy][xx] = SEINA; // jos ruutu ei ole reunalla if ((yy != 0) && (yy != 49) && (xx != 0) && (xx != 92)) { // jos satunnaisluku ei ole jaollinen VAIKEUSASTE-vakiolla if (rand() % VAIKEUSASTE != 0) { // laitetaan polku map[yy][xx] = POLKU; } } // a = (x ? y : z); // if (x) a = y; else a = z; // operaattori toimii muissakin yhteyksissä kuin sijoituksen kanssa.
Tässä siis arvotaan, tuleeko ruutuun seinä vain polku. Seinän todennäköisyys on 1/VAIKEUSASTE. Mitä suurempi vaikeusaste on, sitä helpompia tasoista itse asiassa tulee.
Menetelmä ei ole kovin hyvä, koska näin saattaa tulla joskus mahdottomia ja usein myös aivan liian helppoja tasoja. Sopiva algoritmi riippuu siitä, millaisia luolastoja tarkalleen kaipaat.
Kiitos, toi selvensi paljon. Paitsi ykköskohtaa en hiffannut täysin vieläkään. Yritin tehdä erinäköisiä if-lauseita mvaddch komentojen ympärille mutta ei siitä mitään tullut, saman värisenä pysyi tai sitten jotain muuta epämääräistä syntyi...Minkäläinen ton tarkastuksen tarkalleen ottaen pitäisi olla?
Joo ja toi luolaston generointi ei todellakaan ole mikään järkevä =) Se on joko liian helppo aina tai sitten mahdoton. Jos pistää "vaikeusasteeksi" 2, se on jo monesti mahdoton. Sitä isompi luku kuitenkin on jo ihan triviaali. Mutta se on hyvin yksinkertainen tapa luoda satunnaisia kenttiä. Tietenkin joku hienompi tapa olisi kiva kuulla, jos joku on vaikka valmiin funktion joskus keksinyt tai jotain. (Itseasiassa onkin, mutta en osaa käyttää niitä :|)
lainaus:
Sopiva algoritmi riippuu siitä, millaisia luolastoja tarkalleen kaipaat.
No miten sen sanoisi, sellaisia jotka näyttää luolastolta eikä räjähtäneeltä kaivokselta niinkun nyt. Kuten ADOMissa, sellaisia selkeälinjaisia. Ehkä jotain "puskia" ja "lammikoita" siellä täällä. Varmaan vähän liikaa pyydetty jo :D
Olisin lisännyt muutaman selventävän kohdan koodiin mutta en pysty enää muokkaamaan :/
Selkeän luolaston generointi menee suunnilleen näin:
1) Luo n määrä n kokoisia huoneita
2) Määritä kullekkin huoneelle 1..* ovea
3) Luo tunnelit ovien välille
jokupoika kirjoitti:
Monia aloittelevia koodaajia tuntuu kiinnostavan tämmöiset, joihin itsekkin lukeudun.
jokupoika kirjoitti:
... Tietenkin joku hienompi tapa olisi kiva kuulla, jos joku on vaikka valmiin funktion joskus keksinyt tai jotain. (Itseasiassa onkin, mutta en osaa käyttää niitä :|)
Jep, eli karu totuus simppelin ASCII-grafiikan alla alkaa valjeta: toimiva roguelike on todellisuudessa aika monimutkainen väkkyrä, eikä varsinaisesti sovellu aloittelevan koodaajan projektiksi. :(
jokupoika kirjoitti:
Kuten ADOMissa, sellaisia selkeälinjaisia. Ehkä jotain "puskia" ja "lammikoita" siellä täällä. Varmaan vähän liikaa pyydetty jo :D
Esimerkiksi tällaisesta ajatuksesta voisi lähteä liikkeelle: ripottele kartalle erillisiä (suorakaiteen muotoisia) huoneita ja yhdistä ne sopivalla tavalla toisiinsa käytävillä. Tämän voi jakaa useiksi erillisiksi ongelmiksi:
1. Kuinka sijoittaa huone (minkä muotoinen?) siten, ettei se mene vanhojen huoneiden päälle? Onko tämä mahdollista, vai onko kenttä täynnä?
2. Kuinka yhdistän tietyt huoneet (ja mistä kohdista) käytävällä toisiinsa siten, että käytävä ei leikkaa muita huoneita tai käytäviä (väärällä tavalla)? Onko tämä mahdollista?
3. Mitkä huoneet kannattaa yhdistää toisiinsa, jotta kenttä on yhtenäinen, muttei liian helppo?
(EDIT: Zeeli ehtikin jo esittää samanlaisen idean)
Osaan kysymyksistä kelpaa hyvin mielivaltainen vastaus ja osa niistä on eritasoisia algoritmisia ongelmia. Jos pystyt vastaamaan näihin riittävän täsmällisesti, on luolastogeneraattorialgoritmi valmis, ja voit ruveta koodaamaan.
EDIT: Esimerkiksi pelkän luolastogeneraattorin koodaaminen on varmasti hyvää harjoitusta aloittelevalle ja siinä ilmeneviin spesifisempiin ongelmiin voi myös tulla kysymään apua esimerkiksi täältä.
lainaus:
Jep, eli karu totuus simppelin ASCII-grafiikan alla alkaa valjeta: toimiva roguelike on todellisuudessa aika monimutkainen väkkyrä, eikä varsinaisesti sovellu ollenkaan aloittelevan koodaajan projektiksi. :(
Joo ei olis pitäny käyttää tota roguelike sanaa ollenkaan, ei tästä tarkotus sellainen ole tulla ;) Ymmärrän kyllä kuinka laajoja oikeat rogueliket on, on tullut tahkottua monet läpi itse roguesta zangbandiin asti. (ADOM ehdoton suosikki, näin sivumainintana)
Eli ihan vaan tämmönen sokkelon selvitys hässäkkä. Kunhan sais kunnon sokkeloita aikaiseksi.
Sit joku "line of sight" tapainen juttu olis kiva kanssa, että ei valmiiksi näkisi maaliin asti, ehkä joskus saan sellaisenkin aikaiseksi.
Sitten vähitellen lisäilee mitä päähän pälkähtää niin lopputulos voi olla vaikka mitä muutaman vuoden päästä...;)
Kunhan sais ton maalin värjättyä ensin, se hukkuu tonne sekaan...:P
jokupoika kirjoitti:
lainaus:
Jep, eli karu totuus simppelin ASCII-grafiikan alla alkaa valjeta: toimiva roguelike on todellisuudessa aika monimutkainen väkkyrä, eikä varsinaisesti sovellu ollenkaan aloittelevan koodaajan projektiksi. :(
Joo ei olis pitäny käyttää tota roguelike sanaa ollenkaan, ei tästä tarkotus sellainen ole tulla ;) Ymmärrän kyllä kuinka laajoja oikeat rogueliket on, on tullut tahkottua monet läpi itse roguesta zangbandiin asti. (ADOM ehdoton suosikki, näin sivumainintana)
Eli ihan vaan tämmönen sokkelon selvitys hässäkkä. Kunhan sais kunnon sokkeloita aikaiseksi.
Sit joku "line of sight" tapainen juttu olis kiva kanssa, että ei valmiiksi näkisi maaliin asti, ehkä joskus saan sellaisenkin aikaiseksi.
Minä tein bresenham algoritmin tyyppisen ratkaisun digitaalisten viivojen piirtoon ja sitten loopissa peli piirtää noita viivoja pelaajasta jokaiseen ruutuun jota kentässä on, ja valaisee sen mukaan. Jos törmätään seinään, se valaistaan, mutta sitten viivan piirto katkaistaan. Aika toimiva systeemi.
Ja minustakin ADOM on paras kaikista. Vaikken kovin paljoa sitä ole pelaillutkaan.
Yksi melko yksinkertainen luolastonkehitysmenetelmä on lähteä liikkeelle jostain kohti ja liikkua satunnaisesti tiettyjen ehtojen mukaan — "kaivaa" käytäviä. Kirjoitin tätä menetelmää käyttävän ohjelman, ja se loi mm. tällaisen tason (ja monia muitakin, kun käytti erilaisia todennäköisyyksiä kääntymisille ja haarautumisille ja eri satunnaisarvoja):
################################### #.....##########.##.###..########## ###...####..........###..######.### ###....####..#.............#.#...## ####...######..#...##.............# ###....#####...##......#.#.###....# ###...#####.....#..##..#..#.##..#.# ##....#####..#..#.................# ##.....####..#..#........#...#....# ###...#####..#...#.....#.##.......# #####.#.....####..........#.....#.# #####...#...######.####...........# ##########..######..###..##......## ##########...#####..########..#.### ##########...##################..## ############..#################..## ############....###############...# ##############.################..## ###############################...# ###################################
Tämän menetelmän etu on siinä, että luolastosta tulee yhtenäinen ilman erillisiä tarkistuksia. Myös huonemallisia kellareita voi generoida vastaavalla idealla.
Edit. On jäänyt tuo yksi kysymyksesi huomaamatta...
jokupoika kirjoitti:
Yritin tehdä erinäköisiä if-lauseita mvaddch komentojen ympärille mutta ei siitä mitään tullut, saman värisenä pysyi tai sitten jotain muuta epämääräistä syntyi... Minkäläinen ton tarkastuksen tarkalleen ottaen pitäisi olla?
En erityisemmin osaa Cursesia ulkoa, mutta käsittääkseni tällainen:
if (map[yy][xx] == MAALI) { mvaddch(yy, xx, MAALI | COLOR_PAIR(maalin_vari)); } else { mvaddch(yy, xx, map[yy][xx]); }
Plääh noinhan se väriongelma tosiaan ratkesi, olin ajatellut tota iffin ehtoa jotenkin ihan väärin. Kiitos =)
Toi "kaivamis" menetelmä tosiaan kuulostaa hyvältä, kunhan taas ehdin väkertämään niin kokeilen mitä saan sillä aikaan.
lainaus:
Minä tein bresenham algoritmin tyyppisen ratkaisun digitaalisten viivojen piirtoon ja sitten loopissa peli piirtää noita viivoja pelaajasta jokaiseen ruutuun jota kentässä on, ja valaisee sen mukaan. Jos törmätään seinään, se valaistaan, mutta sitten viivan piirto katkaistaan. Aika toimiva systeemi.
Pitää tutkiskella asiaa, kiitos vinkistä ;)
Mites muuten saan tulostettua kokonaisluvun? Tein vuorolaskurin tohon, mutta en saa sitä tulostettua, kun (tietääkseni) kaikki cursesin tulostusfunktiot ottaa vain char tai string tyyppisen parametrin? Mitenhän saisi muutettua kokonaisluvun stringiksi? Vai char-taulukoksiko sitä pitäisi kutsua...
itoa() atoi()
EDIT:
http://notfaq.wordpress.com/2006/08/30/c-convert-int-to-string/
http://www.cplusplus.com/reference/clibrary/
http://www.cplusplus.com/reference/clibrary/
kiitos, nopea vastaus ja vieläpä toimi (tuolla itoa():lla) ;)
tuli varoituksia joista pääsi eroon käyttämällä _itoa_s()
Ei kannata käyttää noita, kun on parempaakin tarjolla. Cursesin vastine printf-funktiolle on mvprintw.
mvprintw(x, y, "%d. kierros", kierros);
No aina vain paranee, kieltämättä paljon kätevämpi vaihtoehto :)
En tuollaista ominaisuutta mvprintw:ssä huomannutkaan (C kieli ei ole tuttu ja toi taitaa olla jokin C kielen kikka?)
C-kielen tulostus- ja lukufunktioihin (printf ja scanf ja näiden sukulaiset) kannattaa ehdottomasti tutustua vähintäänkin yleissivistyksen vuoksi. Minusta ne ovat huomattavasti ilmaisukykyisempiä kuin C++:n virrat. Esimerkiksi taulukkomuotoisen datan tulostus tai tiedostossa olevan tekstin tulkinta käyvät niillä paljon kätevämmin (saa toki olla toistakin mieltä).
os kirjoitti:
Jep, eli karu totuus simppelin ASCII-grafiikan alla alkaa valjeta: toimiva roguelike on todellisuudessa aika monimutkainen väkkyrä, eikä varsinaisesti sovellu aloittelevan koodaajan projektiksi. :(
Minusta roguelike on juurikin täydellinen projekti aloittelevalle koodarille. Roguelikellähän ei nyt tarkoiteta ADOMin tai nethackin kokoista projektia, vaan toimivaa moottoria jota voi sitten hiljalleen laajentaa. Idea on se, että tällainen tämän topikin alussa oleva moottorikin on jo suht helppo väsätä. Sitten kun on se siinä pohjalla, voi alkaa lisäillä ominaisuuksia kuten vaikka näkökenttä, kenttien generointi, mobit ja tekoäly, itemit, useamman kentän hallinta, tallentaminen, yms. ja parannella niitä. Voi myöskin huomata että ominaisuuksien lisäily on kohtuuttoman vaikeaa tuollaiseen koodiin, jolloin pitää kirjoittaa koko juttu uudestaan niin, että ottaa huomioon laajentamisen kannalta olennaisia asioita. Näin tulee opittua myös sopivan hitaasti mutta varmasti projektinhallintaan liittyviä juttuja. Kun tätä tekee pitkään, on helppo saavuttaa ihan hauskakin toimiva pieni peli.
Itse olen nyt jo muutaman kuukautta puuhaillut välillä aktiivisesti ja välillä vähemmän oman roguelikeni parissa. Kun aloitin sen, en osannut C++:sta edes luokkien käyttöä, muistaakseni tiedostoon tallentaminen ja lukeminen olivat myös pimennossa. Koko koodi oli yhdessä tiedostossa, en käyttänyt minkäänlaisia vakioita ja bugikorjaukset ja ominaisuuksien lisäämiset tuli tehtyä aivan purkkamenetelmillä. Hiljalleen kun vaan tekee asioita ja opettelee sitä mukaa kun vaikeuksia tulee vastaan, niin pärjää.
Metabolix kirjoitti:
C-kielen tulostus- ja lukufunktioihin (printf ja scanf ja näiden sukulaiset) kannattaa ehdottomasti tutustua vähintäänkin yleissivistyksen vuoksi. Minusta ne ovat huomattavasti ilmaisukykyisempiä kuin C++:n virrat. Esimerkiksi taulukkomuotoisen datan tulostus tai tiedostossa olevan tekstin tulkinta käyvät niillä paljon kätevämmin (saa toki olla toistakin mieltä).
Hyvä, olen siis luvalla eri mieltä. C:n syöttö- ja tulostusfunktiot ovat kyllä tiiviimpiä, mutteivät ilmaisuvoimaisempia. Kryptisempiä ja virhealttiimpia kyllä.
Overloadaus ylipäätään on tervettä ilmaisuvoimaa. C++:n virroille voi määritellä operaattorit vaikka ihan ikiomille tyypeille. Ainakin minulle vielä merkittävämpää on, että erilaisia syöttö- ja tulostusmanipulaattoreita voi tehdä itse lisää tarpeiden mukaan. C++:n virrat toimivat yhtenäisemmällä tavalla eri lähteiden ja kohteiden kanssa, vaikken ole loputtoman ihastunut toteutuksen luokkahierarkiaan tai <<- ja >>-operaattoreiden käyttöön.
En ihan näe, miten tekstimuotoisen datan tulkinta on C-funktioilla helpompaa tai mikä on niiden etu taulukkomuotoista tietoa tulostettaessa. Sitä vastoin esimerkiksi tämä on yllättävän hankalaa toteuttaa oikein C-funktiolla:
size_t n; cin >> n;
koo kirjoitti:
En ihan näe, miten tekstimuotoisen datan tulkinta on C-funktioilla helpompaa tai mikä on niiden etu taulukkomuotoista tietoa tulostettaessa.
Minusta virroilla on hyvin vaivalloista toteuttaa datan luku esimerkiksi silloin, kun välissä on ylimääräisiä merkkejä ja vapaamuotoista whitespacea:
v[0]=(1.3,2.5,3.6); v [ 1 ] = ( 1.3 , 2.5 , 3.6 );
C:llä tästä datasta saa indeksin ja kolme liukulukua näinkin helposti:
int i; double x, y, z; scanf(" v [ %d ] = ( %lf , %lf , %lf ) ;", &i, &x, &y, &z)
Saman homman hoitava manipulaattori on toki helppo toteuttaa myös C++:n puolelle, mutta siinä on silti sen toteutuksen verran enemmän työtä — puhumattakaan niistä >>-operaattoreista. (Kuulen kyllä mielelläni, jos olen jäänyt paitsi jostain C++:n standardikirjaston ominaisuudesta.)
Vastaavasti tulostuksen yhteydessä C++-koodista voi tulla niin pitkää ja raskaslukuista, että on siinä ja siinä, luenko mieluummin suurennuslasin kanssa läpi yhden "%#04x %-6d %+5.3o"-formaatin vai kaikki setw-setprecision-setfill-setbase-rimpsut.
En nyt mitenkään väitä, että C++:n tapa olisi huono. Tietyissä tilanteissa vain minusta C:n tavalla lopputuloksesta tulee luettavampi. Tietenkin C++:n ominaisuuksissa on joitakin suuria etuja, kukapa sitä olisi C:n pohjalta huonompaa kieltä lähtenyt kehittämään. (Turha tulla kertomaan, että on olemassa C--. ;)
koo kirjoitti:
Overloadaus ylipäätään on tervettä ilmaisuvoimaa. C++:n virroille voi määritellä operaattorit vaikka ihan ikiomille tyypeille.
Tämä on kieltämättä yksi olennainen etu C:hen nähden.
koo kirjoitti:
Sitä vastoin esimerkiksi tämä on yllättävän hankalaa toteuttaa oikein C-funktiolla:
size_t n; cin >> n;
Onko?
size_t n; scanf("%zd", &n);
lainaus:
Minusta roguelike on juurikin täydellinen projekti aloittelevalle koodarille. Roguelikellähän ei nyt tarkoiteta ADOMin tai nethackin kokoista projektia, vaan toimivaa moottoria jota voi sitten hiljalleen laajentaa. Idea on se, että tällainen tämän topikin alussa oleva moottorikin on jo suht helppo väsätä. Sitten kun on se siinä pohjalla, voi alkaa lisäillä ominaisuuksia kuten vaikka näkökenttä, kenttien generointi, mobit ja tekoäly, itemit, useamman kentän hallinta, tallentaminen, yms. ja parannella niitä. Voi myöskin huomata että ominaisuuksien lisäily on kohtuuttoman vaikeaa tuollaiseen koodiin, jolloin pitää kirjoittaa koko juttu uudestaan niin, että ottaa huomioon laajentamisen kannalta olennaisia asioita. Näin tulee opittua myös sopivan hitaasti mutta varmasti projektinhallintaan liittyviä juttuja. Kun tätä tekee pitkään, on helppo saavuttaa ihan hauskakin toimiva pieni peli.
Itse olen nyt jo muutaman kuukautta puuhaillut välillä aktiivisesti ja välillä vähemmän oman roguelikeni parissa. Kun aloitin sen, en osannut C++:sta edes luokkien käyttöä, muistaakseni tiedostoon tallentaminen ja lukeminen olivat myös pimennossa. Koko koodi oli yhdessä tiedostossa, en käyttänyt minkäänlaisia vakioita ja bugikorjaukset ja ominaisuuksien lisäämiset tuli tehtyä aivan purkkamenetelmillä. Hiljalleen kun vaan tekee asioita ja opettelee sitä mukaa kun vaikeuksia tulee vastaan, niin pärjää.
Oon kyllä samaa mieltä tästä, mutta "os" oli tietenkin siinä mielessä oikeassa että monet alottelijat luulevat että joku nethackin koodaaminen on lastenleikkiä sen takia että siinä käytetään vain ascii merkkejä, ja sit tulee kysymyksiä tänne että "miten koodaan tämmösen". Toivottavasti mun viestistä ei nyt sellaista vaikutelmaa saanut, ei ainakaan ollut tarkoitus.
Tätä on ollut todella hauska väkertää. Pohjanahan mulla oli vaan parinkymmenen rivin pätkä epämääriäistä koodia joka ei edes heti kääntynyt, ja jolloin pelissä pystyi liikkumaan neljään suuntaan yhdessä valmiiksi piirretyssä 10*15 kokoisessa luolassa. Sittemmin olen lisännyt värit, luolastogeneraattorin (joka ei kyllä kokonaan omaa koodia ollutkaan) joka tekee halutun kokoisia luolia ja lisäilee myös muita juttuja kuten puita ja puskia, numpad mahdollisuuden, "askelmittarin", hi-score listan :D, mahdollisuuden valita vaikeusaste itse pelistä eikä vain lähdekoodista, ja itse koodiin lisäillyt noita const-vakioita jotta asetusten muuttaminen on helppoa. Olisin lisännyt ne tohon ekan viestin koodiinkin mutta ei voi enää muokata.
Ja hauskaa on ollut, vähän liiankin, sillä yks C++ harkkatyö on vielä roikkumassa mut aina alkaa mielummin koodaan tätä ;)
Metabolix kirjoitti:
Minusta virroilla on hyvin vaivalloista toteuttaa datan luku esimerkiksi silloin, kun välissä on ylimääräisiä merkkejä ja vapaamuotoista whitespacea:
v[0]=(1.3,2.5,3.6); v [ 1 ] = ( 1.3 , 2.5 , 3.6 );
Nyt ymmärrän. Yksi ensimmäisistä tempuistani on ollut tehdä perusmanipulaattoreita mm. näitä juttuja varten. Sellaisia olisi kieltämättä saanut olla jo ihan standardikirjastossakin. Aika vaivatonta tuo on ollut, ei niitä ole moneen kertaan tarvinnut tehdä. Siispä:
int i; double x, y, z; // scanf(" v [ %d ] = ( %lf , %lf , %lf ) ;", &i, &x, &y, &z) cin >> skip(" v [ ") >> i >> skip(" ] = ( ") >> x >> skip(" , ") >> y >> skip(" , ") >> z >> skip(" ) ;");
Metabolix kirjoitti:
Vastaavasti tulostuksen yhteydessä C++-koodista voi tulla niin pitkää ja raskaslukuista, että on siinä ja siinä, luenko mieluummin suurennuslasin kanssa läpi yhden "%#04x %-6d %+5.3o"-formaatin vai kaikki setw-setprecision-setfill-setbase-rimpsut.
Minä taas pidän siitä, että C++:n virtojen yhteydessä nuo jutut voi kertoa niinkin selväkielisesti, ettei sitä suurennuslasia tarvitse.
Metabolix kirjoitti:
koo kirjoitti:
Sitä vastoin esimerkiksi tämä on yllättävän hankalaa toteuttaa oikein C-funktiolla:
size_t n; cin >> n;Onko?
size_t n; scanf("%zd", &n);
[/lainaus]
On. Tuota pituusmäärettä moni koodari ei vaikuta tuntevan, eivätkä kaikki nykykirjastot (esim. MSVC2008) vieläkään toteuta sitä. Mutta varmaan se tästä ajan kanssa paranee.
Mitenköhän sais sekuntikellon aikaiseksi? Sellainen joka lähtisi nollasta liikkeelle ja kasvaisi...
Ja mitenköhän tohon vois toteuttaa sellaisen, että jos seuraava "esine" onkin joku muu kuin SEINA (jolloin ei pääse siihen suuntaan) tai POLKU (jolloin pääsee). Esim OMENA jonka päälle menemällä saa pisteitä (aika laimee mut noin esimerkkinä)? Menee vähän säätämiseks jos tohon samaan if-rakenne rykelmään lisäilee noita
Aikaleiman sekunteina saa time-funktiolla: time(NULL)
. Ota aloitusaika talteen ja tulosta aina time(NULL) - aloitus
.
Liikkuminen kannattaa toteuttaa niin, että lasket ensin näppäinten perusteella uusiin muuttujiin (x2, y2), minne seuraava askel veisi. Sitten tarkistat, mitä kyseisessä ruudussa on. Jos on seinä, ei jatketa, muuten siirrytään; jos on omena, lisätään vielä pisteitä ja korvataan omena vaikka lattialla jne.
lainaus:
Aikaleiman sekunteina saa time-funktiolla: time(NULL). Ota aloitusaika talteen ja tulosta aina time(NULL) - aloitus.
Kiitti. Onko tuota mahdollista saada päivittymään "reaaliajassa" mitenkään? Yritin laittaa sitä johonkin silmukkaan mut se jäi vaan jumittaan =)
lainaus:
Liikkuminen kannattaa toteuttaa niin, että lasket ensin näppäinten perusteella uusiin muuttujiin (x2, y2), minne seuraava askel veisi. Sitten tarkistat, mitä kyseisessä ruudussa on. Jos on seinä, ei jatketa, muuten siirrytään; jos on omena, lisätään vielä pisteitä ja korvataan omena vaikka lattialla jne.
Jotenkin tällein nyt yritin, ei kuitenkaan toimi. (Tässä vähän yksinkertaistettuna tuota alkuperäisen koodin liikkumista)
if( c==KEY_UP ) { y2 = y--; } else if( c==KEY_DOWN ) { y2 = y++; } else if( c==KEY_LEFT ) { x2 = x--; } else if( c==KEY_RIGHT ) { x2 = x++; ++turns; } if(map[y2][x2] != SEINA) { mvaddch(y2,x2,HAHMO|COLOR_PAIR(UkkelinVari)|A_BOLD); }
Ukkeli menee ikään kuin seinän "alle" eli häviää mutta kuitenkin liikkuu eteenpäin eli seuraavalla painalluksella se taas ilmestyy sieltä seinän alta. Periaatteessa luulen ymmärtäväni mistä ongelma johtuu mutta en keksi silti mitään ratkasua siihen :l eli luultavasti ymmärrän väärin
Myös sellainen ongelma että kun päättää vaihtaa suuntaa, niin ukkeli menee vielä yhden askeleen siihen edelliseen suuntaan ja vasta sitten jatkaa oikeaan suuntaan =)
if (c == KEY_UP) { y2 = y - 1; } // jne. if (map[y2][x2] == SEINA) { continue; // ei liikuta, seuraava kierros } // liikutaan y = y2; x = x2; // tutkitaan paikka if (map[y][x] == OMENA) { map[y][x] = LATTIA; pisteet += 10; }
Kannattaa opetella käyttämään myös funktioita. Tuossakin voisit laittaa luolan generoinnin yhteen funktioon, pelisilmukan toiseen (jolloin et tarvitsisi goto-lausetta) ja vielä pelisilmukan sisällä tuon liikkeen käsittelyn eli uuden paikan tarkastuksen kolmanteen.
Joo jotain vastaavaa toteutusta kokeilinkin, mut siinä vähän samaa vikaa, eli ukkeli jää seinän alle piiloon. Kokeilin nyt suoraa tuota ja siinä sama vika
edit: Tai itseasiassa ei jää seinän alle, vaan seinän eteen, mutta ukkeli häviää näkyvistä.
edit2: Itseasiassa ei niinkään...vaikee kyllä selittää miten se käyttäytyy :S
omenoiden kanssa toimii täydellisesti, kiitos =)
ja tota funktiojakoa oon kyllä jo parantanu tuosta alkuperäisestä (jossa sitä ei ole ollenkaan) jonkin verran :)
Mitä jos muuttaisit koko silmukan tällaiseen muotoon (pseudokoodina):
while !voitto piirrä taso ja ukko lue liikesuunta jos edessä on seinä seuraava kierros jos edessä on omena lisää pisteitä muuta omena lattiaksi liiku
Näin saisit tason ja ukon aina piirrettyä oikein, kun kaikki piirtäminen tapahtuu samassa kohdassa ohjelmaa. (Itse asiassa oletin äsken, että koodi olisi järjestetty näin, mutta ei se tainnut ollakaan?)
Toihan piirtää uuden tason sitten joka askeleella, vai tajusinko väärin
Niin tekee. Niinhän tuo aiempikin koodisi tekee:
jokupoika kirjoitti:
while (...) { for(int yy=0;yy<50;yy++) for(int xx=0;xx<93;xx++) mvaddch(yy,xx,map[yy][xx]);
Voit kyllä ihan samalla periaatteella muuttaa vain sen yhdenkin merkin, jos on tarvis. Tällöin piirrät tason ennen silmukkaa, ja liikkumisen yhteydessä pyyhit ukon edellisestä kohdasta ja piirrät uuteen kohtaan. Tämänkin takia on kätevä pitää vanhat ja tulevat koordinaatit muistissa erikseen. Kannattaa lukea QB-merkkigrafiikkapeliopasta; ymmärrät varmasti koodia, ja idea on joka tapauksessa sama.
Yksi tapa olisi tämä:
piirrä taso while !voitto piirrä ukko lue siirto kumita ukko (piirrä tasoa sen päälle) jatka kuten äskeisessä esimerkissä
ahh joo tajusin väärin. Joo eli se piirtää joka askeleella sen saman tason.
Mutta eikös tuo koodi ole jo tuon sinun pseudokoodin muodossa?
edit: siis ton ekan
Tällähetkellä näyttää siis tältä tuo kriittinen osuus:
while((x!=xKOKO-1) && ('q'!=(c=getch()))) // VOITTOEHTO { // KENTÄN PIIRTO for(int yy=0;yy<yKOKO;yy++) { for(int xx=0;xx<xKOKO;xx++) { if (map[yy][xx] == MAALI) { mvaddch(yy, xx, MAALI|COLOR_PAIR(MaalinVari)|A_BOLD); } else if (map[yy][xx] == POLKU) { mvaddch(yy, xx, POLKU|COLOR_PAIR(LattianVari)); } else if (map[yy][xx] == SEINA) { mvaddch(yy, xx, SEINA|COLOR_PAIR(SeinanVari)); } else { mvaddch(yy, xx, map[yy][xx]); } } } // UKKELIN PIIRTO mvaddch(y,x,HAHMO|COLOR_PAIR(UkkelinVari)|A_BOLD); // LIIKKEEN LUKEMINEN if(KEY_UP==c) { y2 = y-1; } if(KEY_DOWN==c) { y2 = y+1; } if(KEY_LEFT==c) { x2 = x-1; } if(KEY_RIGHT==c) { x2 = x+1; } if (map[y2][x2] == SEINA) { continue; } y = y2; x = x2; if(map[y][x] == OMENA) { map[y][x] = POLKU; } }
Mielestäni se on juuri tuon Metabolixin ensimmäisen pseudokoodin mukaisessa järjestyksessä. (Kokeilin tuota toistakin että kenttä piirretään vain kerran ja pyyhitään ukkeli aina erikseen, mutta en onnistunut =)
Ongelma on se, että liikkeen suuntaa vaihdettaessa ukkeli liikkuu vielä yhden askeleen siihen edelliseen suuntaan ennen kuin kääntyy. Ei vaan näköjään riitä mun aivot ratkasemaan ongelmaa itse vaikka kuinka olen yrittänyt. Ei kuitenkaan kuulosta kovin vaikealta ongelmalta :|
Omenat kyllä keräillään nätisti ja seinäänkin pysähtyy =)
Luet näppäimen ennen tilanteen tulostusta, joten ruudulla on aina yhtä aiempi tilanne kuin muistissa. Siirrä getch vasta piirtokoodin jälkeen. (Silmukasta pääsee pois break-lauseella, eli q-tarkistus on silti mahdollinen.)
Kun sijoitat muuttujaan x2 arvon x+1, sinun pitää samalla sijoittaa y2:een y. Muutenhan siinä voi edellisen törmäyksen jälkeen olla yhä y+1, ja ukko liikkuukin seuraavaksi kulmittain. Sama pätee tietysti muihinkin kohtiin.
Muuten koodi näyttää minusta aivan oikealta.
Kiitos paljon, nyt toimii loistavasti! =)
Aihe on jo aika vanha, joten et voi enää vastata siihen.