Kergetős játék C-ben

Az előző bejegyzés kapcsán már említettem, hogy pascalban alkottam, egy kis „kergető játékot”. Nem kell nagy dologra gondolni. Még csak normálisan ki sincs dolgozva. A játék lényege, hogy mi vagyunk egy ponttal (jelen esetben 3×3-mas ponttal), és az ellenség is. Ezt a pontot a nyilak segítségével tudjuk a négy irányba mozgatni. Minden periódusba a gép lép felénk egy lépést, így akar elkapni. A játék akkor ér véget, ha a gép elkap minket, azaz a koordinátái megegyeznek a miénkkel (vagy jelen esetben ha bármely koordinátája megegyezik a mi bármely koordinátánkkal). Persze mint mondtam, alapból a C nem rendelkezik a megfelelő eszközökkel. A Unix rendszerek viszont egy NCurses kiegészítés által több mint elég eszközrendszert biztosít számunkra.

Na nézzünk egy részletes leírást a hup wikijéből az NCurses függvényeiről:

NCurses-programozás magyar nyelvű vázlat

A Kezdetek

A teletype -ok időszakában a soros porthoz kapcsolódtak a terminálok, és escape-szekvenciákkal. A termcap (késöbb a terminfo) tartalmazott egy adatbázist a vezérlőkarakterekről az elérhető termináloknál.
A NCurses egy könnyen kezelhető programozói felületet ad terminálok kezeléséhez.
Lehetőség van a segítségével a kurzormozgatásra, ablakok kezelésére, szinek kezelésére, egérkezelésre. A segítségével könnyen lehet konzol módban grafikus felhasználói felületet készíteni menükkel, panelokkal, formokkal.

Egy „Hello Vilag” program

Ha használni szeretnénk az NCurses -t, hivatkoznunk kell rá a forrásban az

#include <ncurses.h> 

sorral, illetve a linkernek meg kell mondani, hogy szerkessze be a megfelelő függvénykönyvtárakat:
gcc <forras_file> -lncurses -o <leforditott_program_neve>

#include <ncurses.h>

int main () {	
  initscr ();			/* a curses mod inditasa */
  printw ("Hello Vilag !!!");	/* kiirjuk a megfelelo szoveget */
  refresh ();			/* megjelenitjuk a szoveget a "valodi" kepernyon */
  endwin ();			/* Kilepes a curses modbol*/

  return 0;
}

Az initscr () inicializálja a terminál curses módját. Ez néhány megvalósításban törli a képernyőt, és fekete képernyőt ad. Ezt kell először meghívni, ha a curses módot szeretnénk használni. Alapállapotba hozza a curses módot, memóriát foglal a rendszer-ablakok számára, melyet az stdscr használ, illetve pár szükséges adat-struktúrát.
A printw () hasonló a normál printf utasításhoz, kiír egy adatot az stdscr által használt ablak aktuális (x;y) pozíciójába. Az első kordináta a (0;0), mely a képernyő bal sarkában helyezkedik el. A kiírt adatok nem jelennek meg rögtön a képernyőn, csupán az stdscr által reprezentált adatstrukturába kerülnek, de ez még nem frissíti a látható képernyőt, erre a refresh () függvény szolgál. A printw () frissít pár jelzőt, és adatstruktúrát a megfelelő stdscr bufferben. A filozófia e mögött az, hogy több dolgot is frissíthetünk a képernyőt szimbolizáló bufferben, majd egyetlen refresh () meghívásával megjeleníthetjük a képernyőn a tartalmát.
Végül pedig az endwin () segítségével elhagyhatjuk a curses módot, erre szükség van a programból történő kilépés előtt, mivel pár terminál nem viselkedhet megfelelően, ha ezt elfelejtjük. Felszabadítja a curses számára lefoglalt adatterületeket, struktúrákat, a curses alrendszert, és a terminált visszahelyezi normál módba.

Az inicializálás

Ne felejtsd el, hogy használat előtt a curses rendszert inicializálni kell az initscr () meghívásával. Külön is lehet inicializálni az egérkezelő-alrendszert, a szinkezelő-alrendszert, de ezek mind meghívásra kerülnek az initscr () -ben.

Alapállapotban a terminál-kezelő buffereli a leütött karaktereket az első sorvége vagy kocsivissza karakterig. De a programoknak szükségük lehet arra, hogy a felhasználó által leütött karaktert rögtön megkapják. A raw () és a cbreak () letiltja a soronkénti bufferelést. A különbség a két megoldás között a vezérlőkarakterek (felfüggesztés <CTRL-Z>, megszakítás, és a kilépés <CTRL-C>) feldolgozásában rejlik. A raw () módban az alkalmazás közvetlenül elkapja ezeket a karaktereket, és nem generál szignált. Ellenben cbreak () módban a vezérlőkaraktereket értelmezi a terminál-kezelő, de a szerkesztőkaraktereket (pl. Backspace) már az alkalmazásnak kell feldolgoznia. Véleményem szerint érdemes a raw () módot előnyben részesíteni, mert így nagyobb ellenörzést lehet biztosítani a vezérlés felett. A raw () módot hívják egyes leírásokban nyers módnak is.

Az echo () mód használatával a leütött karakterek visszhangoznak a terminálon, a noecho () pedig kikapcsolja ezt. Erre azért van szükség, mert felesleges visszhangot kérni, ha az alkalmazás közvetlenül kér be adatokat a getch () használatával. A legtöbb interaktív program meghívja a noecho () függvényt, majd visszhangozza a megfelelő adatokat ellenőrzött módon. Ez megadja a rugalmasságot a programozónak bármelyik helyen anélkül, hogy frissítenie kellene az aktuális (x;y) kordinátát.

A keypad () lehetővé teszi, hogy beolvassuk a funkcióbillentyűket (F1, F2, stb), a kurzormozgató gombokat, és egyéb speciális karaktereket. Használd a keypad(stdscr, TRUE) -t a bekapcsoláshoz a szabályos ablakra (stdscr).

A halfdelay () -t nem használják túl gyakran, de hasznos pár esetben. Ez engedélyezi a half-delay módot, ami hasonló a cbreak () módhoz, de jól használható, ha egy adott ideig vársz egy karakterre, és ha az nem érkezik meg, akkor folytatódik a program futása. Pl. ilyen helyzet a jelszó-bekérés.

Itt egy példa-forrás a kezelésre:

#include <ncurses.h>

int main () {  
  int ch;

  initscr ();             /* Belepes curses modba  */
  raw ();                 /* A soronkenti buffereles letiltasa */
  keypad (stdscr, TRUE);  /* Engedelyezzuk a funkciobillentyuk figyeleset */
  noecho ();              /* ne legyen visszhangja a getch () -nak */
  ch = getch ();          /* ha a raw () -ot nem hivod meg, akkor elobb enter-t */
                         /* kell utni a programban valo tovabblepeshez */
  if (ch == KEY_F(1))     /* Ha az F1 lett leutve, keypad () nelkül ez nem elerheto */
     printw ("F1 lett lenyomva");    /*  Kiirjuk a szoveget, nem figyelunk több karaktert */
                         /* noecho () nelkul nehany csunya escape-karakter lesz megjelenitve */

  else {   
    printw ("A leutott karakter: ");
    attron (A_BOLD);
    printw ("%c", ch);
    attroff (A_BOLD);
  }
  refresh ();           /* Megjelenitjuk a valodi kepernyon */
  endwin ();            /* Kilepunk a curses módból */
  return 0;
}

Bár a program magyarázza önmagát, de pár funkciója még nem lett teljesen elmagyarázva, ilyen például az attron () és az attroff () működése.

Egy kis ablakkezelés

A NCurses-ben lehetőségünk nyílik arra, hogy az adatokat különféle ablakokban jelenítsük meg, de ez nem teljes analógiája. Amikor belépsz curses módba, létrejön az alapértelmezett ablak, melynek a neve stdscr lesz (mérete pedig 80×25, vagy annak a képernyőnek a mérete, melyben fut az alkalmazásod).

Például ha lefut ez a két sor:

printw ("Itt vagyok !");
refresh ();

akkor ez a sztring kiírodik az stdscr -re az aktuális kurzorpozícióba. Hasonlóan, mint mikor meghívod a refresh () -t, ez is csak az stdscr -re dolgozik.
Ha te készítesz egy ablakot, akkor meghivhatod ezeket a szokásos funkciókat, csak egy „w” betűt írj a nevük elé:

wprintw (win, "Itt vagyok !");
wrefresh (win);

Mint látható, szinte minden „w”-nélküli eljárásnak megvan a „w’-vel kezdődő párja, mely egy adott ablakra dolgozik, és első paraméterként várja az adott ablak nevét (vagy az stdscr -t).

printw (string);           /* Kiirja a szoveget az aktualis kurzorpozicion */
mvprintw (y, x, string);   /* az (x;y) kordinatakra mozog a kurzorral, es kiirja a szoveget     */
wprintw (win, string);     /* Kiirja a szoveget a "win" ablakba az aktualis pozícion */
mvwprintw (win, y, x, string);   /* az (x;y) relativ kordinatara mozog a "win" ablakban, es 
                                 kiírja a szoveget */

Formázott kiiratás a printw (), és társaival

Mivel úgy érzem, hogy már nem tudsz tovább várni, hogy valami nagyobb megvalósítást is láss, hát jöjjön:

– addch () csoport: Egy karaktert lehet vele kiiratni, attributumokkal
– printw () csoport: Formázott kimenet, hasonlóan a printf () -hez

– addstr () csoport: Sztring kinyomtatása

Az addch () csoport eljárásaival tudsz egy karaktert az aktuális kordinátákra (vagy egy adott másik helyre) kiiratni, persze ha gondolod, megadhatsz attributumokat is.
Ha a karakterhez hozzárendelsz attributumokat (pl. félkövér, fordított kiírás), amikor a curses megjeleníti a karaktert, akkor az attributum is megjelenik.
Ha szeretnéd kombinálni a karaktert több attributummal, akkor erre kétféle lehetőséged nyílik:

– Ha a „VAGY” műveletet használod a szükséges attributumok makróit megtalálod az „ncurses.h” fejléc-állományban. Például, ha te ki szeretnéd írni a „ch” karaktert (aminek a típusa természetesen char) félkövéren és aláhúzottan, hívd meg így az addch () függvényt:

       addch (ch | A_BOLD | A_UNDERLINE);

– Használhatod az attrset (), attron (), attroff () függvényeket, ezek majd még ki lesznek fejtve részletesebben az „Attributumok” részben. Röviden, ezzel manipulálni tudod az aktuális attributumokat a használt ablakban. Egyszer beállítod, és a karakterek így fognak megjelenni a kért ablakban, amíg le nem kapcsolod ezt az attroff () funkcióval.

Ráadásul a curses tartalmaz néhány különleges karaktert a karakter-központú grafika használatához. Ezek segítségével tudsz rajzolni tábklázatokat, kereteket, vízszintes és függőleges vonalakat, stb. Megtalálsz minden elérhető karaktert az „ncurses.h” fejlécállományban. Érdemes megnézned az ACS_ kezdetű makrókat is ebben az állományban.

Az mvaddch () a kurzort egy adott helyre mozgatja, és ott megjelenít egy adott karaktert. Tehát ezt a két utasítást:

move (row,col);    /* a kurzort a (row;col) kordinatara mozgatja */
addch (ch);

Nyugodtan lecserélheted erre:

mvaddch (row,col,ch);

A waddch () hasonló az addch () -hoz, csak a karaktert egy adott ablakban jeleníti meg. (Megjegyzés: Persze, az addch () is egy ablakban dolgozik, de ez az ablak ott mindenképpen csak az stdscr lehet.)
És még ide tartozik az mvwaddch() is, amely a változatosség kedvéért egy adott karaktert egy adott relatív pozícióban jelentet meg, persze egy adott ablakban.
De ha belegondolsz, nagyon bosszantó lenne egy sztringet karakterenként megjeleníteni csak azért, hogy formázhassuk. Szerencsére az ncurses tartalmaz a printf () -hez, és a puts () -hoz hasonló megoldásokat.

Ha szeretnél élni a printf () adta lehetőségekkel, akkor több függvény áll rendelkezésedre. Nos, ilyenkor jön jól a printw () csoport
Az mvprintw () képes arra, hogy mozgassa a kurzort egy adott helyre a képernyőn, és ott megjelenítsen egy adott szöveget. Ha te szeretnéd elöbb mozgatni a kurzort, majd kiírni az adott dolgot a printw () használatával, akkor hívd meg a move () függvényt, majd utána használd a printw () -t, ugyan én még nem láttam olyan esetet, mikor nem lett volna jó az mvprintw (), de lehet, hogy te rugalmasabban akarsz hatni a dolgokra.
A wprintw () és az mvwprintw () hasonlóan használható, mint a printw() és az mvprintw (), csak itt egy adott ablakban lehet dolgozni az alapértelmezett stdscr helyett. A vwprintw () pedig alteregrója a vprintf () -nek, akkor érdemes használnod, mikor változó hosszuságú argumentumlistát szeretnél kiiratni.

Jöjjön egy egyszerű példa a print -ek használatára:

#include <ncurses.h>           /* ncurses.h hozza az stdio.h -t is */  
#include <string.h> 

int main () {
  char mesg []="Ez egy szoveg"; /* az uzenet, amit meg szeretnenk jeleniteni */
  int row,col;                 /* eltaroljuk a sorok es oszlopok szamat a kepernyon */

  initscr ();                   /* go to heaven... indulunk a curses modba */
  getmaxyx (stdscr,row,col);    /* lekerdezi egy adott ablak sorainak es oszlopainak szamat */
  mvprintw (row/2,(col-strlen(mesg))/2,"%s",mesg);
                               /* megjeleniti a szoveget a kepernyo kozepen */
  mvprintw (row-2,0,"A kepernyo %d sort es %d oszlopot tartalmaz\n",row,col);
  printw ("Meretezd at az ablakot (ha tudod), es futtasd ujra a programot !");
  refresh ();
  getch ();
  endwin ();

  return 0;
}           

Ez a program jól mutatja, milyen könnyű használni a printw () -t. A programban lett használva pár új dolog, egyik ilyen a getmaxyx (), mely egy makróként van definiálva az „ncurses.h” -ban. Ez megadja a sorok és oszlopok számát egy ablakban, de azt nem szabad elfelejteni, hogy ez egy makró, tehát integer típusú változókat vár, és nem mutatókat, mint a legtöbb függvény.

Végül, nem utolsó sorban következzék harmadikként az addstr () csoport. Az addstr () segítségével tudsz kitenni egy sztringet a kért ablakba, ez a funkció hasonló, ha minden karakterre egyszer meghívnád az addch () alprogramot. Vannak még ehhez hasonló függvények: mvaddstr (), mvwaddstr () és a waddstr (), de az elnevezésből ki lehet találni, hogy pontosan mit csinálnak (pl. mvaddstr () hasonló, mintha meghívnád a move () -t, majd az addstr () -t). Egy másik alprogram a csoportból a addnstr (), ami vár még egy integer típusú paramétert is. Ez megjelenít n darab adott karaktert a képernyőn, ha n negatív, akkor az egész sztring hozzáadódik.
Amire a kezdőknek figyelni érdemes, hogy ne cseréljék fel a paraméterek sorrendjét,pl. a kordinátáknál, tehát (y;x) és nem fordítva. Ha bonyolult dolgokat szetnél megvalósítani, érdemes felosztani a képernyőt több ablakra, és külön kezelni őket.

Adatok bevitele: a scanw () és társai

A getch () beolvas egy karaktert a terminálról. De ez néhány megvalósításban problémát okozhat.

Forrás: Hup Wiki

Amennyiben már tisztában vagyunk az alap függvények alkalmazásával, már neki is kezdhetünk alkalmazásához, és egy program megalkotásához. Vegyük alapul az én kis kergetős játékomat. Első sorban ki kell rajzoltatnunk a játékteret. Ezek után a minket szimbolizáló figurát, majd az ellenfelet/ellenfeleket. Amennyiben ezek megvannak, meg kell határoznunk, hogyan tudunk játszani, tehát az irányítást szolgáló billentyűket, és azok hatásait kell definiálnunk. Figyelnünk kell itt nagyon arra, hogy az irányításnál figyelembe kell vennünk azt is, hogy a pályát megjelenítő határoló vonalakra ne mehessünk rá, így minden lépésnél ellenőriznünk kell ezt is.

Ez a kis kezdetleges program, nagyon sok mindent lehetne benne még csinálni, azért rakom fel, hogy egy valamilyen szinten működő implementált programban lehessen tanulmányozni az egyes függvények működését használatát.

Szerintem még igen is van, lenne igény a jól működő és ízléses szöveges terminálos megjelenítésre. Remélem akit érdekel annak adtam egy kezdő löketet. A játék C forrás kódja elérhető itt.

Vélemény, hozzászólás?

Az e-mail címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük