View
3
Download
0
Category
Preview:
Citation preview
SVEUČILIŠTE U ZAGREBU
FAKULTET ORGANIZACIJE I INFORMATIKE
V A R A Ţ D I N
Bernard Brček Bedeković
STRUKTURE PODATAKA U RAZVOJU RAČUNALNIH IGARA
ZAVRŠNI RAD
Varaţdin, 2010.
SVEUČILIŠTE U ZAGREBU
FAKULTET ORGANIZACIJE I INFORMATIKE
V A R A Ţ D I N
Bernard Brček Bedeković
Redoviti student
Broj indeksa: 35616/06-R.
Smjer: Informacijski sustavi
Preddiplomski studij
STRUKTURE PODATAKA U RAZVOJU RAČUNALNIH IGARA
ZAVRŠNI RAD
Mentor:
Tihomir Orehovački, dipl. inf.
Varaţdin, rujan 2010.
I
Sadrţaj
1. UVOD ....................................................................................................................................................................... 1
2. POLJA ..................................................................................................................................................................... 3
2.1. JEDNODIMENZIONALNA POLJA ........................................................................................................................... 3 2.1.1. Statično polje ............................................................................................................................................. 3 2.1.2. Dinamično polje ....................................................................................................................................... 4
2.2. VIŠEDIMENZIONALNA POLJA .............................................................................................................................. 6 2.2.1. Statično polje ............................................................................................................................................. 6 2.2.2. Dinamično polje ....................................................................................................................................... 7
2.3. PRIMJENA POLJA U IGRAMA ................................................................................................................................ 8
3. BITVECTOR ........................................................................................................................................................... 9
3.1. KREIRANJE BITVECTORA .................................................................................................................................... 9 3.2. POSTAVLJANJE VRIJEDNOSTI BITOVA U BITVECTORU ......................................................................................... 9 3.3. ČITANJE VRIJEDNOSTI BITA U BITVECTORU ...................................................................................................... 10 3.4. PRIMJENA BITVECTORA U IGRAMA ................................................................................................................... 10
4. BITFIELD ............................................................................................................................................................. 12
4.1. PRIMJENA BITFIELDA U IGRAMA ....................................................................................................................... 12
5. VEZANE LISTE ................................................................................................................................................... 13
5.1. JEDNOSTRUKO VEZANA LISTA .......................................................................................................................... 13 5.2. DVOSTRUKO VEZANA LISTA ............................................................................................................................. 14 5.3. PRIMJENA VEZANIH LISTI U IGRAMA ................................................................................................................. 14
6. STOG ..................................................................................................................................................................... 15
6.1. PRIMJENA STOGA U IGRAMA ............................................................................................................................. 16
7. RED ........................................................................................................................................................................ 18
7.1. PRIMJENA REDA U IGRAMA ............................................................................................................................... 19
8. HASH TABLICE .................................................................................................................................................. 21
8.1. HASH FUNKCIJA................................................................................................................................................ 22 8.2. HASH VRIJEDNOST ............................................................................................................................................ 23 8.3. PRIMJENA HASH TABLICA ................................................................................................................................. 24
8.3.1. Primjena hash tablica u igrama .............................................................................................................. 24
9. REKURZIJA ......................................................................................................................................................... 26
9.1. PRIMJENA REKURZIJE U IGRAMA ...................................................................................................................... 26
10. STABLA ............................................................................................................................................................... 28
10.1. DIJELOVI STABLA ........................................................................................................................................... 28 10.2. VRSTE STABALA ............................................................................................................................................. 30 10.3. BINARNO STABLO ........................................................................................................................................... 30 10.4. BINARNO STABLO PRETRAŢIVANJA ................................................................................................................ 32 10.5. PRIMJENA STABALA U IGRAMA ....................................................................................................................... 33
11. HRPA ................................................................................................................................................................... 36
12. GRAFOVI ............................................................................................................................................................ 39
12.1. VRSTE GRAFOVA............................................................................................................................................ 39 12.1.1. Dvosmjerni graf .................................................................................................................................... 39 12.1.2. Jednosmjerni graf .................................................................................................................................. 40 12.1.3. Težinski graf .......................................................................................................................................... 40
12.2. IMPLEMENTACIJA GRAFA ................................................................................................................................ 41 12.3. PRETRAŢIVANJE GRAFA .................................................................................................................................. 43
12.3.1. Prvo-u-dubinu ........................................................................................................................................ 44 12.3.2. Prvo-u-širinu ......................................................................................................................................... 44
12.4. PRIMJENA GRAFOVA U IGRAMA ...................................................................................................................... 45
II
13. ZAKLJUČAK...................................................................................................................................................... 46
14. LITERATURA .................................................................................................................................................... 47
1
1. Uvod
Svaki računalni program se na svojoj najniţoj razini sastoji od podataka i algoritama koji se na
tim podacima izvršavaju. Spomenuti podaci su obično strukturirani na odreĎeni način da bi bili
pogodni izvršavanje pojedinih algoritama nad njima. Upravo ti načini na koji podaci mogu biti
strukturirani će biti navedeni i opisani u ovom završnom radu s naglaskom na upotrebu
spomenutih struktura u jednoj posebnoj vrsti aplikacija – računalnim igrama. TakoĎer će uz ovaj
rad biti napravljena i jedna igra koja će posluţiti kao demonstracija primjene struktura podataka
koje će biti navedene u ovom radu. Naravno nisu sve strukture podataka pogodne za sve igre pa
tako ni prilikom izrade te igre neće biti korištene baš sve u ovom radu navedene strukture. Za
svaku strukturu će biti navedene neke od mogućih primjena, a kod onih struktura koje će se
koristiti u popratnoj igri to će biti posebno istaknuto. Kod nekih struktura osim opisa biti će
navedena i vremenska sloţenost algoritama koji se nad njima izvršavaju kako vi se istaknula
brzina odreĎenog algoritma. Vremenska sloţenost se označava sa velikim slovom O i koristi se
za opisivanje teoretskih performansi algoritama pri čemu se u obzir uzima vremensko trajanje ili
količina memorije koji su algoritmu potrebni prilikom izvoĎenja [Sherrod, 2007, str. 15].
Najmanja vremenska sloţenost iznosi O(1) te se algoritmi koji imaju takvu sloţenost smatraju
vrlo brzima. Pošto svaka struktura podataka moţe biti implementirana na različite načine
vremenska sloţenost se moţe koristiti za usporedbu pojedinih implementacija.
Programski jezik koji je odabran za izradu popratne igre je C++ jer je on jako popularan kad je
riječ o razvoju računalnih igara [Sherrod, 2007, str. 12]. Igra koja dolazi uz ovaj rad je
jednostavan primjer strategije u realnom vremenu. U spomenutoj, igri igraču se daje na
raspolaganje nekoliko vrsta jedinica (tenk, sjedište baze, spremište tenkova) kojima on moţe
upravljati na način da im dijeli naredbe. Pošto se radi o jednostavnom primjeru, igrač moţe
zadati dvije vrste naredbi – idi na lokaciju, koju igrač moţe zadati samo mobilnim jedinicama te
napadni, koju igrač moţe zadati svim jedinicama. Zadate naredbe dodaju se na listu naredbi
svake jedinice koja je implementirana pomoću reda. Igraču i njegovim jedinicama suprotstavit će
se protivničke jedinice kojima upravlja računalo. Protivničke jedinice su po svojim svojstvima i
izgledu jednake igračevima te ih ima u jednakom broju. Pošto je programiranje igre poprilično
zahtjevan posao većina programera za potrebe igre napiše poseban set klasa, funkcija i
algoritama posebno pripremljenih za odreĎeni tip igre. Takav set odnosno biblioteka naziva se
game engine. Za potrebe ovog rada nije izraĎen posebni game engine nego je korišten postojeći.
Zove se DarkGDK. Radi se o besplatnom engine-u otvorenog koda čiji je autor firma The Game
2
Creators iz Velike Britanije, a za renderiranje virtualnih svjetova koristi DirectX1 9. Cilj izrade
ovog rada i popratne igre je upoznavanje najpoznatijih struktura podataka i načina njihova
korištenja u izradi računalnih igara.
1 DirectX je kolekcija aplikacijskih programskih sučelja (API-ja) za izvršavanje zadaća povezanih s multimedijom,
posebno s programiranjem igara, na operacijskom sustavu Windows. Sastoji se od sljedećih sučelja: Direct3D,
DirectDraw, DirectMusic, DirectPlay, DirectSound itd. Prva verzija, s oznakom 1, izdana je 30. Rujna 1995. godine
a posljednja i aktualna u vrijeme pisanja ovog rada 27. Listopada 2009 i nosi oznaku 11.
3
2. Polja
Polja su najjednostavniji i najkorišteniji oblik struktura podataka. Polje se moţe zamisliti kao
jedna velika jednodimenzionalna struktura koja sadrţi brojne ćelije a svakoj ćeliji pristupamo
pomoću indeksa [Penton, 2003, str. 40]. Uobičajeno je da prva ćelija u polju ima indeks 0, a
posljednja indeks koji je za jedan manji od veličine polja (npr. kod polja sa 9 elemenata
posljednja ćelija imala bi indeks 8). Polja su linearne strukture podataka jer su ćelije u memoriji
računala smještene jedna za drugom [Kutti i Padhye, 2004, str. 64]. Vrijeme pristupa pojedinoj
ćeliji u polju je vremenske sloţenosti O(1). Bez obzira koliko elemenata ili ćelija polje sadrţi,
vrijeme pristupa će uvijek biti isto. Pošto se svakom elementu polja moţe pristupiti instantno,
polja se još nazivaju i strukture podataka sa nasumičnim pristupom (eng. random-access
structures) [Penton, 2003, str. 41]. Postoje dvije vrste polja: statično i dinamično [Kutti i Padhye,
2004, str. 75].
2.1. Jednodimenzionalna polja
2.1.1. Statično polje
Statičko polje je polje koje se alocira jednom i njegova veličina se ne moţe naknadno promijeniti
[Penton, 2003, str. 43].
Primjer 1. Alokacija statičnog polja
int polje[10];
Da bi kreirali polje od deset elemenata tipa integer moramo uz ime polja staviti uglatu zagradu u
kojoj moramo specificirati broj elemenata koje će to polje sadrţavati. U primjeru 1 polje ima
deset elemenata čiji su valjani indeksi u rasponu od 0 do 9. Devetom elementu polja bi postavili
vrijednost na pet na način koji je prikazan u primjeru 2.
Primjer 2. Postavljanje vrijednosti elemenata u polju
polje[8]=5;
Vaţno je napomenuti da je nakon kreiranja polja elemente polja potrebno postaviti na odreĎenu
vrijednost jer C++ to neće sam napraviti. Naime, ako probamo pristupiti nekom elementu polja
23 13 0 9 10 66 19
Slika 2.1. Grafički prikaz polja sa 7 elemenata
4
prije nego to napravimo naići ćemo na čudne vrijednosti koje nemaju smisla i bilo kakav rad s
tim vrijednostima moţe prouzročiti rušenje programa. Prethodno kreirano polje moţemo
popuniti vrijednostima na dva načina koji su prikazani u primjeru 3.
Primjer 3. Popunjavanje polja vrijednostima
int polje[10]={0,0,0,0,0,0,0,0,0,0};
ili
for(int a=0; a<10; a++) {polje[a]=0;}
Nakon što nam alocirano polje više ne treba, potrebno ga je obrisati korištenjem ključne riječi
delete kao što je prikazano u primjeru 4.
Primjer 4. Brisanje polja
delete[] polje;
Uglate zagrade do ključne riječi delete znači da ţelimo obrisati polje. Bez uglatih zagrada
obrisali bi samo prvi element polja. Ukoliko polje kreiramo unutar funkcije nije ga nuţno
obrisati jer će to program napraviti za nas automatski kad se funkcija izvrši do kraja.
2.1.2. Dinamično polje
Dinamično polje je polje čija se veličina tijekom izvoĎenja programa moţe mijenjati po potrebi.
Takvo polje se mora kreirati upotrebom pokazivača a moţe se kreirati na tri načina [Penton,
2003, str. 50]:
Primjer 5. Alokacija dinamičnog polja pomoću funkcije malloc
polje=(int*)malloc(sizeof(int)*10);
Pomoću funkcije malloc alociramo memoriju dovoljno veliku da u nju stane polje ţeljene
veličine. Pošto funkcija malloc vraća pokazivač na alociranu memoriju tipa void, taj pokazivač je
potrebno konvertirati u tip podataka koji će biti spremljeni u polju, u ovoj slučaju je to integer
[Ibid, str. 50]. Ako prilikom alociranja memorije funkcija malloc vrati vrijednost 0 znači da ne
postoji dovoljno slobodne memoriju u računalu za polje te veličine. Prilikom ovakvog načina
alociranja memorije potrebno je navesti jedan parametar koji označava količinu memorije u
bajtovima koju ţelimo alocirati.
Primjer 6. Alokacija dinamičnog polja pomoću funkcije calloc
polje=(int*)calloc(10,sizeof(int));
5
Funkcija calloc alocira memoriju na isti način kao i funkcija malloc s jednom bitnom razlikom:
nakon alociranja svi elementi polja se postave na vrijednost nula. Prilikom ovakvog načina
alociranja memorije potrebno je navesti dva parametra: prvi označava broj elemenata u polju a
drugi veličinu jednog elementa u bajtovima [Ibid, str. 51].
Primjer 7. Alokacija dinamičnog polja pomoću funkcije new
polje=new int[10];
Funkcija new će alocirati polje zadane veličine te će za svaki element novo alociranog polja
pozvati njegovu konstruktor metodu ako ona postoji [Ibid, str. 52]. Zbog toga je ovakav način
alociranja memorije sporiji od malloc-a i calloc-a. Jedini parametar koji treba navesti u ovom
slučaju označava broj elemenata koje će polje sadrţavati a pokazivač na tu memoriju koji
funkcija new vraća je uvijek istog tipa kao i elementi polja pa ga nije potrebno konvertirati.
Za razliku od statičnih polja koja, ako su alocirana unutar funkcije, program sam obriše iz
memorije nakon što se funkcija izvrši do kraja, dinamička polja moramo obrisati sami. Ukoliko
to ne učinimo prouzrokovati ćemo problem koji se zove memory leak - memorija koju smo
jednom alocirali a više nam ne treba i dalje zauzima mjesto u memoriji jer program ne zna da
nam više ne treba [Ibid, str. 53]. Ako nastavimo alocirati novu memorijsku lokaciju bez da smo
obrisali već alociranu koja nam više ne treba, memorija računala će biti sve punija i punija, a u
jednom trenutku bi moglo i ponestati mjesta u memoriji. Prilikom brisanja dinamički alociranih
polja način brisanja ovisi o načinu na koji smo ih alocirali. Ako je polje alocirano pomoću
funkcije malloc ili calloc treba ga obrisati pomoću funkcije free.
Primjer 8. Brisanje dinamičnog polja pomoću funkcije free
free(polje);
Ako je alocirano pomoću funkcije new, onda ga brišemo isto kao i statično polje, ključnom
riječju delete. Za razliku od funkcije free, delete prije brisanja polja poziva destruktor metodu
svakog elementa polja ukoliko potonji element ima destruktor metodu. Nadalje, ako ţelimo
promijeniti veličinu dinamičkog polja to moţemo napraviti na dva načina koji su prikazani u
primjerima 9 i 10 [Ibid, str. 54].
Primjer 9. Promjena veličine dinamičkog polja pomoću funkcije realloc
polje=(int*)realloc(polje,sizeof(int)*20);
6
Ako je polje alocirano pomoću funkcija malloc ili calloc veličinu mijenjamo funkcijom realloc.
Funkcija realloc traţi dva parametra: prvi je pokazivač na polje čiju veličinu ţelimo promijeniti
a drugi označava novu veličinu polja. U slučaju da je moguće alocirati novo polje na istom
mjestu u memoriji funkcija realloc će to učiniti te se u tom slučaju pokazivač na polje neće
promijeniti. MeĎutim, ukoliko to nije moguće polje će biti alocirano na drugom mjestu u
memoriji. Konačno, ukoliko u memoriji nema dovoljno mjesta za novo polje realloc će vratiti
nula [Ibid, str. 55].
Ako je polje alocirano pomoću funkcije new, nije mu moguće promijeniti broj elemenata na
jednostavan način. Najprije je potrebno alocirati novo polje sa ţeljenim brojem elemenata, zatim
kopirati podatke iz starog polja u novo te na kraju obrisati staro polje [Ibid, str. 57].
Primjer 10. Kopiranje dinamičnog polja na drugu memorijsku lokaciju
int *polje=new int[10];
int *novo_polje=new int[20];
memcpy( novo_polje, polje, sizeof(int)*10);
delete[] polje;
2.2. Višedimenzionalna polja
Za razliku od jednodimenzionalnih polja koja ubrajamo u linearne strukture podataka,
višedimenzionalna polja pripadaju kompleksnijim strukturama podataka. Kod
jednodimenzionalnih polja, podaci su u memoriji smješteni jedan iza drugog. Razlog tome je
jednostavnost ali i brzina pristupa podacima jer se svakom elementu moţe brţe pristupiti ako mu
se zna adresa u memoriji koju je lako izračunati. MeĎutim kod višedimenzionalnih polja stvar je
malo drugačija. Podaci nisu u memoriji smješteni jedan iza drugoga nego se za smještaj
pojedinog elementa polja računa adresa po odreĎenoj formuli. Ta formula nije kod svakog
programskog jezika ista jer svaki programer koji razvija pojedini programski jezik pokušava naći
jednostavniji i brţi način za izračun adrese elementa polja dok istovremeno treba i pohraniti
podatke u memoriju na način da im se moţe što brţe pristupiti.
Isto kao i u slučaju jednodimenzionalnih polja, C++ nam daje mogućnost da višedimenzionalno
polje deklariramo statički ili dinamički.
2.2.1. Statično polje
Statično polje se deklarira slično kao i jednodimenzionalno polje – veličinu svake pojedine
dimenzije navodimo u uglatim zagradama nakon imena polja [Penton, 2003, str. 112].
7
Primjer 11. Alokacija dvodimenzionalnog statičnog polja
int polje[2][3];
Primjer 12. Alokacija trodimenzionalnog statičnog polja
int polje[2][3][4];
Isto kao i jednodimenzionalna polja i višedimenzionalna trebaju nakon kreiranja biti postavljena
na početne vrijednosti [Ibid, str. 113].
Primjer 13. Postavljanje višedimenzionalnog statičnog polja na početne vrijednosti
int polje[3][3]={ {1,2,3},
{4,5,6},
{7,8,9} };
Pošto se dvodimenzionalna polja mogu zamisliti kao polja čiji su elementi opet polja svaki red u
polju je definiran kao normalno polje [Ibid, str. 113]. U primjeru 13, kreirano polje ima dvije
dimenzije, a svaka dimenzija po 3 elementa. Broj u prvoj uglatoj zagradi odreĎuje broj
elemenata polja, a broj u drugoj uglatoj zagradi broj elemenata od kojih se sastoji svaki element
polja. Budući da je svaki element polja sastavljen od više drugih elemenata on se moţe
promatrati kao manje polje unutar većeg [Ibid, str. 113].
Ovakvi tipovi polja su najčešći u razvoju igara jer jako rijetko postoji potreba za naknadnim
povećavanjem ili smanjivanjem broja elemenata [Ibid, str. 113].
2.2.2. Dinamično polje
Ponekad ne znamo koliko nam elemenata bude potrebno u nekom trenutku pa je polja potrebno
deklarirati kao dinamička (npr. prilikom učitavanja slika kojima ne znamo unaprijed rezoluciju).
Takvo polje se deklarira, isto kao i dinamičko jednodimenzionalno, pomoću pokazivača. Nakon
što polje jednom deklariramo veličinu mu moţemo odrediti naknadno kada ćemo znati koliko
elemenata nam treba. Prilikom toga mora se uzeti u obzir jedno ograničenje koje govori da, osim
prve, sve dimenzije moraju biti poznate [Lipljin, 2004, str. 170].
Primjer 14. Deklariranje višedimenzionalnog dinamičnog polja
int (*polje)[5];
Primjer 15. Alociranje višedimenzionalnog dinamičnog polja
int (*polje)[5]=new int[2][5];
8
2.3. Primjena polja u igrama
Kad je riječ o računalnim igrama, memorija treba sadrţavati puno podataka, a još je vaţnije da se
tim podacima moţe brzo pristupiti i da ih se moţe brzo promijeniti. Upravo su zbog toga polja
jako korisna te se jako često koriste u razvoju igara. Najčešće se koriste za spremanje podataka
kojima je potrebno brzo i često pristupanje čak i do nekoliko desetaka puta u sekundi [Penton,
2003, str. 71]. Najčešće su takvi podaci u pitanju kada u igri postoje razni likovi, bilo prijateljski
ili neprijateljski, s kojima je igrač stalno u doticaju.
Primjer 16. Primjer polja
class cEnemy{
public:
int m_iObj;
int m_iHealth;
int m_iAmmo;
int m_iStatus : 3;
int m_iType : 3;
bool m_iStatic;
int m_iTarget;
}
class cResurs{
public:
int m_iObj;
double m_fTime;
float m_fX,m_fY,m_fZ;
int m_iAmount;
int m_iType;
bool m_bCollected;
};
U igri koja dolazi uz ovaj rad polja se koriste na sljedeće načine:
1) Sve neprijateljske jedinice će biti smještene u polju tipa cEnemy. Svi podaci o
neprijateljskim jedinicama biti će unaprijed odreĎeni i spremljeni u datoteku
„enemys.dat“. Kada igrač odabere New Game iz glavnog izbornika podaci će iz datoteke
„enemys.dat“ biti učitani i tada će igrač započeti s igrom
2) Na nivou će takoĎer postojati nekoliko točaka na kojima će se pojavljivati odreĎeni
resursi koje će igrač tijekom igre moći pokupiti. One će biti smještene u polju tipa
cResurs, a njihove lokacije će biti nasumično odreĎene prije početka igre. Broj točaka
ovisi o teţini igre koju igrač odabere u glavnom izborniku.
Osim za likove, polja mogu posluţiti za spremanje podataka o nivou koji iz nekog razloga treba
biti interaktivan pa je potrebno pristupati podacima što brţe.
9
3. Bitvector
Bitvector je posebna vrsta polja koja sluţi za drţanje podataka veličine jednog bita na način da ti
podaci budu što saţetiji i time da zauzimaju što je moguće manje memorije [Penton, 2003, str.
84]. Upotreba bitvectora posebno dolazi do izraţaja kada je potrebno slati podatke preko mreţe
ili prilikom spremanja masivnih virtualnih svjetova na hard disk [Ibid, str. 105]. Najefikasniji
način za kreiranje bitvectora je kreiranje bitvectora one veličine kojom se šalju podaci po
sabirnici računala. Na većini 32 bitnih računala se podaci po sabirnici šalju u djelićima od 32 bita
što odgovara veličini podatka long integer [Ibid, str. 84].
0 0 0 0 1 1 0 1 0 1 1 1 0 0 1 0 0 0 1 1 0 1 1 1 1 0 1 0 0 1 1 0
Slika 3.1 Bitvector sa 32 indeksa, svaki veličine jednog bita
3.1. Kreiranje bitvectora
Pošto je bitvector zapravo posebna vrsta polja kreiramo ga isto kao i dinamičko polje. MeĎutim,
svi elementi moraju biti tipa long integer ako ţelimo da jedan element polja bude velik 32 bita. S
obzirom da bit moţe poprimiti vrijednost 0 ili 1, potrebni su nam samo pozitivni brojevi pa
koristimo tip podatka unsigned long integer. Ukoliko ne ţelimo da elementi polja budu veliki 32
bita moţemo koristiti i druge tipove podataka.
Primjer 17 predstavlja ilustraciju kreiranja bitvector polja veličine jednog elementa odnosno 32
bita.
Primjer 17. Kreiranje bitvectora
unsigned long int* polje=new unsigned long int[1];
Pošto je svaki element velik 32 bita, bitvector polje iz primjera 17 moţe sadrţavati samo broj
bitova koji su višekratnici broja 32 (32, 64, 96 ...). Broj u uglatoj zagradi predstavlja koliko puta
po 32 bita će bitvector polje sadrţavati.
3.2. Postavljanje vrijednosti bitova u bitvectoru
Da bi postavili vrijednost pojedinog bita, prvo moramo znati u kojem elementu polja se taj bit
nalazi te redni broj tog bita u tom elementu. Da bi saznali u kojem elementu se bit nalazi
moramo redni broj bita podijeliti sa veličinom jednog elementa polja u bitovima – u ovom
10
slučaju to je 32. Da bi saznali koji bit u prethodno odreĎenom elementu odgovara onom
traţenom, koristimo operator % (modulo). Na kraju, kad znamo redni broj bita, njegovu
vrijednost postavljamo na jedan pomoću operatora AND, odnosno na nulu korištenjem OR
operatora [Ibid, str. 91].
Primjerice, ţelimo postaviti vrijednost bita s indeksom 36 na vrijednost 1:
Primjer 18. Postavljanje vrijednosti bita sa indeksom 36 na vrijednost 1
int element=36/32;
int redni_broj=36 % 32;
polje[element]=(polje[element] | (1<<redni_broj));
Primjer 19. Postavljanje vrijednosti bita sa indeksom 36 na vrijednost 0
polje[element]=(polje[element] & (~(1<<redni_broj)));
3.3. Čitanje vrijednosti bita u bitvectoru
Čitanje vrijednosti bita iz primjera 18 je prikazano u primjeru 20.
Primjer 20. Čitanje vrijednosti bita iz bitvectora
bool vrijednost=(polje[element]&(1<<redni_broj)) >> redni_broj;
Slučajevi čitanja i postavljanja vrijednosti u bitvectoru se zasnivaju na binarnim matematičkim
pravilima koja glase [Ibid, str. 91]:
1. X and 1 = X
2. X and 0 = 0
3. X or 1 = 1
4. X or 0 = X
3.4. Primjena bitvectora u igrama
Većina današnjih igara ima quick-save mogućnost odnosno mogućnost da igra sama snimi stanje
u kojem se virtualni svijet nalazi u nekom trenutku tako da igrač ima mogućnost da nakon
prekida igranja ponovo nastavi gdje je stao [Ibid, str. 96]. Da igrač ne bi morao voditi računa o
tome igra to radi za njega. Budući da su virtualni svjetovi ogromni i zauzimaju stotine megabajta
kod simuliranja više stvari odjednom, spremanje potpunog stanja igre u nekom trenutku moglo
bi prouzrokovati duţu stanku u igranju jer je brzina hard diska u usporedbi s RAM memorijom
računala jako mala. Tu na scenu nastupa bitvector koji moţe posluţiti za voĎenje evidencije o
tome koji dijelovi igre su se promijenili od zadnjeg snimanja stanja. U tom slučaju svaka stavka
11
igre koja se mijenja kroz vrijeme moţe biti predstavljena jednim bitom u bitvectoru. Na taj način
spremanje potpunog stanja igre potrebno je napraviti samo jednom te se kod svakog naknadnog
snimanja spremaju samo promijenjeni podaci.
Primjer 21. Funkcija za pomicanje neprijatelja u igri
Void Enemy_SetPosition(cEnemy *enemy, float x, float y, float
z){
enemy.m_fX=x;
enemy.m_fY=y;
enemy.m_fZ=z;
Bitvector_Set(enemys_bv, enemy.index, 1);
}
Funkcija Enemy_SetPosition iz primjera 21 prilikom svakog pozivanja pomiče neprijatelja na
zadanu poziciju te postavlja odreĎeni bit u bit vektoru na 1. Indeks bita u bitvectoru za svakog
neprijatelja je odreĎen unaprijed i spremljen u indeks varijablu unutar klase cEnemy; Bitvector
enemys_bv kreiran je unaprijed i sadrţi onoliko bitova koliko je neprijatelja u igri.
Svaki put kada bi se pozvala funkcija za quick save za svakog neprijatelja u igri bi se provjerila
vrijednost bita u bitvectoru. Ukoliko bi ta vrijednost bila 1, znači da se od posljednjeg pozivanja
quick save funkcije stanje tog neprijatelja promijenilo te bi novo stanje trebalo ponovo spremiti u
odreĎenu datoteku. Nakon toga vrijednost bita bi se ponovo postavila na 0. Ako bi vrijednost bita
bila nula, stanje tog neprijatelja ne bi bilo potrebno ponovo spremati pošto se nije promijenilo od
zadnjeg pozivanja quick save funkcije.
12
4. Bitfield
Bitfield je struktura podataka slična bitvectoru kod koje svaki indeks moţe poprimiti više
vrijednosti [Penton, 2003, str. 102]. Kao i bitvector, bitfield je integer varijabla čiju veličinu u
bitovima moţemo sami odrediti i moţe biti deklarirana samo unutar klase ili strukture [Ibid, str.
102].
Primjer 22. Kreiranje bitfielda
class cPlayer{
public:
unsigned m_state : 2; Ovo je bitfiled varijabla veličine 2 bita – moţe poprimiti 4
različite vrijednosti.
};
4.1. Primjena bitfielda u igrama
U slučaju kada pojedine varijable u igri u svakom trenutku mogu zauzimati vrijednosti iz malog
intervala te kada takvih varijabli, postoji mogućnost da programer sam odabere koliko bitova će
C++ koristiti za pojedinu varijablu. Ako za više varijabli programer odabere manji broj bitova
moţe uštedjeti memorijski prostor. Pošto današnja računala dolaze opremljena sa velikim
količinama memorije takva ušteda neće bitno utjecati na performanse računala ili neće utjecati
uopće. No u slučaju da se razvija igra kod koje je potrebno slati velike količine podataka o stanju
virtualnog svijeta i igrača, ušteda koju omogućuje bitfield bi mogla biti od velike vaţnosti. Kod
takvih igara slanje prevelike količine podataka preko interneta bi mogla jako utjecati na
performanse same igre. Primjer takve igre je MMORPG2 (Massive Multiplayer Online Role-
Playing Game).
2 MMORPG je vrsta računalne igre u kojoj je veliki broj igrača u meĎusobnoj interakciji unutar virtualnog svijeta.
Igrač preuzima ulogu virtualnog lika i preuzima kontrolu na njegovim potezima. Glavna razlika izmeĎu ove vrste
igara i ostalih je u velikom brojku igrača koji sudjeluju u MMORPG-u te trajnosti virtualnog svijeta kojim se kreću.
Čak i dok igrač ne igra svijet se razvija i dalje jer je igra stalno pokrenuta na udaljenom serveru koji je obično u
vlasništvu izdavača igre.
13
5. Vezane liste
Vezana lista je struktura podataka slična polju koja rješava dva najveća problema polja –
elementi se u polje ne mogu ubaciti ili izvaditi iz polja na jednostavan način i njihova veličina je
fiksna. Vezana lista se sastoji od ćelija kao i polje ali se te ćelije nazivaju čvorovi i za razliku od
polja kod kojeg se ćelije nalaze jedna do druge u memoriji i ne sadrţe ništa osim podataka u
polju, čvorovi su razbacani po memoriji i svaki čvor osim podataka sadrţi i adresu odnosno
pokazivač na sljedeći čvor u listi [Penton, 2003, str. 148]. Zbog načina na koji su vezane liste
strukturirane umetanje i brisanje pojedinih čvorova u listi je vrlo jednostavno. Postoje razne
varijacije vezanih listi a u ovom radu će biti spomenute dvije – jednostruko i dvostruko vezane
liste. Liste mogu biti implementirane na dva načina – pomoću polja i pomoću pokazivača, što će
biti prikazano u nastavku ovoga rada.
5.1. Jednostruko vezana lista
Jednostruko vezana lista je najjednostavniji oblik vezane liste kod koje svaki čvor sadrţi samo
podatke i adresu sljedećeg čvora u listi. Slika 5.1. prikazuje grafički prikaz vezane liste sa 3
čvora.
Slika 5.1. Grafički prikaz jednostruko vezane liste sa 3 čvora
Jednostruko vezana lista ima nekoliko prednosti nad poljem [Sherrod, 2007, str. 152]:
moguće je umetati čvorove u listu i brisati čvorove iz bilo kojeg dijela liste vrlo
jednostavno,
moguće je dodavati nove čvorove u listu u nedogled pod uvjetom da ima dovoljno
slobodne memorije u računalu.
Glavni nedostaci su nemogućnost pristupanja pojedinom čvoru preko indeksa i činjenica da je
jedini mogući obilazak čvorova od početka prema kraju.
Podaci Pokazivač na
sljedeći element
14
5.2. Dvostruko vezana lista
Za razliku od jednostruko vezane liste, kod koje svaki čvor pokazuje na sljedeći čvor u listi,
čvorovi kod dvostruko vezane liste pokazuju na prethodni i na sljedeći čvor u listi [Penton, 2003,
str. 169]. Slika 5.2 prikazuje grafički prikaz dvostruko vezane liste sa 3 čvora.
Slika 5.2 Grafički prikaz dvostruko vezane liste sa 3 čvora
U usporedbi s poljem dvostruko vezana lista ima iste prednosti i nedostatke kao i jednostruko
vezana lista ali i omogućuje obilazak liste od kraja prema početku [Sherrod, 2007, str. 152].
5.3. Primjena vezanih listi u igrama
Glavna primjena vezanih listi u igrama je kod onih stvari koje ne ţelimo imati u ograničenom
broju. Primjerice, u RPG ţanru igre, likovi imaju mogućnost skupljanja raznih oruţja, napitaka,
opreme itd.. koji se dodaju u inventar onim redoslijedom kojim ih likovi skupljaju [Penton, 2003,
str. 177]. Primjer 23 prikazuje odsječak programskog koda koji demonstrira dodavanje novog
predmeta u inventar.
Primjer 23. Dodavanje novog predmeta u inventar
Class cItem{
int m_iType;
LPSTR m_szName;
};
class cPlayer{
list<cItem*> m_inventar;
};
cPlayer g_player;
void AddItem(cItem *item){
g_player.m_inventar.push(item);
}
Podaci Pokazivač na
sljedeći element
Pokazivač na
prethodni element
15
6. Stog
Stog je apstraktni tip podatka kod kojeg se elementi dodaju i brišu samo s jednog kraja – s vrha.
Zbog toga se stog naziva i LIFO ( eng. Last In First Out) strukturom. Stog se moţe nazvati i
linearnom kolekcijom podatkovnih elemenata u kojoj su elementi poredani po vremenu koje su
proveli u stogu – najnoviji element se uvijek nalazi na vrhu, a najstariji na dnu stoga [Dale, 2003,
str. 196].
Sljedeće su operacije moguće na stogu [S. Childs, 2008, str.175]:
1) Push – dodaje novi element na vrh stoga
2) Pop – vraća element na vrhu stoga i briše ga sa stoga
3) Top - vraća element na vrhu stoga
4) MakeEmpty – kreira prazan stog
5) IsEmpty – vraća 1 ako je stog prazan inače vraća 0
6) IsFull - vraća 1 ako je stog pun tj. ako nije moguće dodati novi element na kraj stoga
(neke implementacije stoga (npr. pomoću polja) imaju unaprijed odreĎen maksimalni
broj elemenata)
Stog se moţe implementirati na više načina: pomoću polja (slika 6.2.), vezane liste (slika 6.1.)
itd.. Kod implementacije pomoću polja moguća je implementacija pomoću statičnog i
dinamičnog polja. Stog implementiran statičnim poljem ima unaprijed definiranu veličinu te
postoji mogućnost da stog bude napunjen do kraja. Nasuprot tome, implementacija pomoću
dinamičkog polja omogućuje naknadno povećanje stoga i u slučaju stog nije moguće napuniti do
kraja [S. Childs, 2008, str.176].
Slika 6.1. Grafički prikaz stoga implementiranog pomoću vezane liste
Vrh stoga Dno stoga
16
.
6.1. Primjena stoga u igrama
U igrama stog se koristi u izradi izbornika. Svaka igra ima glavni izbornik koji se uglavnom
sastoji od sljedećih opcija: New Game, Load Game, Save Game, Options, Exit. Korisnik prilikom
odabira bilo koje opcije u izborniku otvara novi izbornik tzv. podizbornik koji sadrţi nove
opcije. Npr. odabirom opcije Options obično se otvara podizbornik koji sadrţi sljedeće: Sound,
Video, Controls. Pritiskom na tipku Escape (Esc) korisnik se vraća na prethodni izbornik.
Pomoću stoga ovaj mehanizam navigacije kroz izbornike u igri se implementira na sljedeći
način: svaki put kada korisnik odabere podizbornik iz trenutnog izbornika taj podizbornik se
kreira i stavlja na vrh stoga. Svaki put kada korisnik pritisne tipku Escape, trenutni izbornik se
briše sa vrha stoga te izbornik koji se nakon toga nalazi na vrhu stoga postaje novi trenutni
izbornik.
Primjer 24 prikazuje programski kod, korišten u igri koja dolazi uz rad, za kretanje korisnika
kroz glavni izbornik igre.
Primjer 24. Mehanizam kretanja kroz izbornik implementiran pomoću stoga
struct sMenuItem{
LPSTR m_szText;
DWORD m_dColor;
cMenu *m_pMenu;
sMenuItem(){
m_szText=NULL;
m_pMenu=NULL;
}
};
class cMenu{
public:
int m_iCount;
int m_iLeft;
int m_iTop;
sMenuItem *m_pItems;
cMenu(){}
cMenu(int count){
m_iCount=count;
m_pItems=new sMenuItem[count];
}
14 22 7 39
Dno stoga Vrh stoga
Slika 6.2. Grafički prikaz stoga implementiranog pomoću polja
17
};
stack<cMenu*> menus1;
stack<cMenu*> menus2;
stack<cMenu*> *menus;
void ProcessMenu(){
if(dbEscapeKey()){
if(menus->size()>1) menus->pop();
}
cMenu *menu=menus->top();
LPSTR text="";
if(dbMouseClick()==0) g_iM=0;
for(int i=0; i<menu->m_iCount; i++){
dbInk(dbRGB(0,255,0),dbRGB(255,255,255));
int x=dbMouseX(), y=dbMouseY();
int width=dbTextWidth(menu->m_pItems[i].m_szText);
int height=dbTextHeight(menu->m_pItems[i].m_szText);
if(x>menu->m_iLeft && x<menu->m_iLeft+width){
if(y>menu->m_iTop+(20*i) && y<menu->m_iTop+(20*i)+height){
dbInk(dbRGB(255,0,0),dbRGB(255,255,255));
if(dbMouseClick()==1 && g_iM==0) {
g_iM=1;
text=menu->m_pItems[i].m_szText;
if(menu->m_pItems[i].m_pMenu){
menus->push(menu->m_pItems[i].m_pMenu);
}
}
}
}
dbText(menu->m_iLeft,menu->m_iTop+(20*i),menu-
>m_pItems[i].m_szText);
}
if(strcmp(text,"Easy")==0){
g_iGO=1; menus->pop(); g_iResourceCount=15;
InitLevel();
}
if(strcmp(text,"Medium")==0){
g_iGO=1; menus->pop(); g_iResourceCount=5;
InitLevel();
}
if(strcmp(text,"Hard")==0){
g_iGO=1; menus->pop(); g_iResourceCount=0;
InitLevel();
}
if(strcmp(text,"Exit")==0) exit(0);
if(strcmp(text,"Resume")==0) g_iGO=1;
if(strcmp(text,"Exit to main menu")==0){g_iGO=0;
menus=&menus1;}
}
18
7. Red
Red je homogena grupa elemenata na koju se elementi dodaju na jedan kraj koji se naziva
začelje (eng. End ili Rear), a uzimaju na drugom kraju pod nazivom čelo (eng. Front) [N.
Kasiviswanath, 2007, str. 79]. Zbog toga se red takoĎer naziva i FIFO (eng. First in First Out)
strukturom.
Sljedeće operacije su moguće na redu [Dale, 2003, str. 198]:
1) Enqueue – dodavanje novog elementa na kraj reda
2) Dequeue – brisanje elementa s početka reda
3) Front – vraća element na početku reda
4) IsEmpty – vraća 1 ako je red prazan inače vraća 0
5) IsFull – vraća 1 ako je red pun tj. ako nije moguće dodati novi element na kraj reda (neke
implementacije reda (npr. pomoću polja) imaju unaprijed odreĎen maksimalni broj
elemenata)
6) MakeEmpty – kreira prazan red
Red je, kao i stog, moguće implementirati na više različitih načina, primjerice pomoću polja
(slika 7.1.), vezane liste (slika 7.2.) itd.
Slika 7.1. Grafički prikaz reda sa 5 elemenata implementiranog pomoću polja
Čelo Začelje
0 1 2 3 4
Čelo Začelje
7.2. Grafički prikaz reda sa 3 elementa implementiranog pomoću vezane liste
19
Postoje 4 vrste reda [Sherrod, 2007, str. 173-182]:
1) Obični red kod kojeg se elementi dodaju na jedan kraj a brišu sa drugog (slika 7.1)
2) Dvostruki red kod kojeg se elementi mogu dodati i brisati sa oba kraja
3) Kruţni red kod kojeg se elementi, u slučaju da je red već pun, dodaju na popunjena
mjesta u redu pri čemu se elementi koji su u redu prebrišu s novo dodanim elementima
(slika 7.3)
4) Prioritetni red kod kojeg su elementi poredani po prioritetu
Slika 7.3. Grafički prikaz kruţnog reda [prilagoĎeno prema Penton, 2003, str. 209]
U primjeru igre koja dolazi uz ovaj rad korišten je STL3 red koji je implementiran pomoću reda s
dvostrukim krajem (eng. double-ended queue ili deque).
7.1. Primjena reda u igrama
Redovi imaju najčešću primjenu u RTS (Real time Strategy) igrama odnosno strategijama u
realnom vremenu u kojima igrač raspolaţe raznim borbenim i ne borbenim jedinicama (npr.
tenkovi, vojnici, tvornice) te ima mogućnost nareĎivanja pojedinim jedinicama da nešto rade
3 Programski jezik C++ ima standardni set predloţaka klasa koji su poznati pod nazivom Standard Template Library
(STL). Ta biblioteka klasa se koristi za odrţavanje i ponovno korištenje koda i sastoji se od učinkovito dizajniranih
komponenti i algoritama koji se često mogu vidjeti u kompjuterskom programiranju. Jedan od glavih ciljeva STL-a
je abstrakcija i učinkovitost. To omogućava korištenje STL-a u velikom broju aplikacija. [Allen Sherrod, 2007, str.
11]
1
2
3
4
6
7
8
5
Čelo
Začelje
Stvarna granica
polja
20
(npr. odu s jednog mjesta na drugo, napadnu neprijateljske jedinice). Te naredbe se skupljaju u
red i onim redom kojim ih je igrač zadao se izvršavaju. Neke od poznatijih igara koje spadaju u
tu kategoriju su: Warcraft, Starcraft i Command & Conquer. U primjeru 25 je prikazan odsječak
programskog koda iz igre koja dolazi uz ovaj rad a sluţi dodavanje naredbi u red koje igrač
izdaje svojim jedinicama.
Primjer 25. Dodavanje naredbi na kraj reda
class cCommand{
public:
int m_iType;
int m_iTarget;
float m_fX, m_fY, m_fZ;
cCommand(){}
~cCommand(){}
};
class cUnit{
public:
int m_iObj;
int m_iObj2;
int m_iHealth;
int m_iAmmo;
int m_iStatus;
int m_iStatic;
int m_iType;
int m_iTemp;
queue<cCommand*> commands;
cUnit(){
m_iTemp=0;
}
~cUnit(){}
};
cCommand *naredba=new cCommand;
naredba->m_iType=0;
naredba->m_fX=dbObjectPositionX(g_iMarker);
naredba->m_fY=dbObjectPositionY(g_iMarker);
naredba->m_fZ=dbObjectPositionZ(g_iMarker);
unit->commands.push(naredba);
21
8. Hash tablice
Hash tablica je struktura podataka koja omogućuje vrlo brze operacije umetanja elemenata i
traţenja elemenata koji se već nalaze u tablici [Sherrod, 2007, str. 210]. Većina operacija nad
hash tablicom ima sloţenost od O(1) [Ibid, str. 210]. Grafički prikaz hash tablice prikazan je na
slici 8.1.
Slika 8.1. Hash tablica [prilagoĎeno prema Sherrod, 2007, str. 210]
Hash tablice su brze strukture podataka. Čak i kod velikog broja elemenata, potraga za nekim
elementom u tablici moţe biti instantna. Uzrok te brzine leţi u činjenici da su hash tablice
bazirane na strukturi polja. Kada je indeks u polju nekog elementa poznat, traţenje tog elementa
u hash tablici nije ništa drugo nego čitanje elementa iz polja. Obzirom da se hash tablice baziraju
na polju, one takoĎer nasljeĎuju sve prednosti i mane koje polje ima.
Sljedeće činjenice vrijede za hash tablice [Sherrod, 2007, str. 211]:
1) Promjena veličine hash tablice je komplicirana i vremenski zahtjevna
2) Elementi u tablici se ne mogu jednostavno poredati
3) Traţenje elemenata u tablici je vrlo brzo
4) Kopiranje hash tablica je sporo i nuţno je da tablica koja se kopira i tablica u koju se
kopira budu iste veličine
5) Dodavanje novih elemenata u hash tablicu je brzo
Objekt
Objekt
Objekt poslan u hash funkciju
HASH TABLICA
Objekt smješten u
hash tsblicu na
indeks generiran u
hash funkciji
22
Princip rada hash tablice je sljedeći [Ibid, str. 211]:
svaki objekt koji se ţeli dodati u hash tablicu ima vlastiti ključ,
ključ je oznaka tog objekta koja je pretvorena u indeks u polju hash tablice,
za ključ nekog objekta moţe se upotrijebiti bilo što, pa čak i sam objekt.
8.1. Hash funkcija
Kod koji se koristi za pretvaranje ključa u indeks zove se hash funkcija (slika 8.2.).
Slika 8.2. Grafički prikaz pretvaranja ključa u indeks [prilagoĎeno prema Sherrod, 2007, str. 211]
Učinkovitost hash funkcije je vrlo vaţna. Korištenje loše hash funkcije moţe dovesti do kolizije
u hash tablici. Kolizija se javlja kad hash funkcija pretvori više od jednog ključa u isti indeks
[Sherrod, 2007, str. 215]. Kada doĎe do kolizije programer moţe učiniti jedno od sljedećeg [Ibid,
str. 215]:
1) Ne dodati objekt u hash tablicu
2) Zamijeniti objekt u tablici s onim koji u tablicu treba biti dodan
3) Pronaći novi indeks za objekt koristeći tehniku otvorenog adresiranja (eng. Open
addressing)
4) Napraviti hash tablicu tako da omogućuje smještaj više od jednog objekta na pojedinom
indeksu u tablici
Kolizije koje se dogaĎaju u tablici mogu imati poguban učinak na performanse tablice te stoga
hash funkcija mora biti učinkovita. Nijedna hash funkcija ne moţe garantirati da do kolizija neće
doći, ali kada do njih doĎe, potrebno ih je riješiti jednom od dvije glavne metode [Ibid, str. 215-
217]:
1) Open addressing – ukoliko se dogodi kolizija tijekom umetanja novog objekta u hash
tablicu traţi se nova pozicija za objekt. To se radi sekvencijalnim pretraţivanjem hash
tablice dok se ne pronaĎe prva slobodna pozicija u tablici.
OBJEKT Hash
funkcija
OBJEKT
INDEKS
U POLJU
23
2) Separate chaining – umjesto traţenja praznog elementa u tablici kod ove metode svaka
ćelija u hash tablici je zapravo vezana lista. Ukoliko i doĎe do kolizije, objekt se
jednostavno doda na kraj vezan liste.
8.2. Hash vrijednost
Hash funkcija je algoritam koji neku vrijednost pretvori u brojčanu i to u onaj broj koji je veći od
0 i manji od zadnjeg indeksa u hash tablici [Smith, 2004, str. 178]. Ta vrijednost se naziva ključ
a on moţe biti string, broj ili nešto treće. Hash vrijednost pojedinog ključa ovisi o veličini hash
tablice. U slučaju da povećamo hash tablicu sve hash vrijednosti koje se nalaze u tablici postat će
nevaljane te će se ponovo morati pretvarati u nove.
Kod pretvaranja brojčane vrijednosti u hash vrijednost, moţe se koristiti operator modulo u
kombinaciji sa veličinom hash tablice kako bi hash vrijednost bila u onom rasponu indeksa koji
postoje u hash tablici.
Primjer 26. Pretvaranje brojčane vrijednosti u hash vrijednost
int hash=key % m_size;
Što je veća hash tablica, to su manje šanse da doĎe do kolizija. Grafički prikaz izračuna hash
funkcije prikazan je na slici 8.3.
Slika 8.3. Primjer hash funkcije na hash tablici sa 23 elementa [prilagoĎeno prema Allen Sherrod, 2007, str.
214]
Osim brojeva, još jedan tip podatka se često sluţi za pretvaranje u hash vrijednosti – riječ je o
stringu. Primjer 27 prikazuje hash funkciju za pretvaranje stringova u hash vrijednosti kod kojeg
dolazi do malo kolizija:
Primjer 27. Pretvaranje stringa u hash vrijednost
unsigned long int StringHash( const char* p_string ){
unsigned long int hash = 0;
112 Hash
funkcija 20
112 % 23
24
int i;
int length = strlen( p_string );
for( i = 0; i < length; i++ ){
hash += ( (i + 1) * p_string[i] );
}
return hash;
}
Ova metoda se bazira na činjenici da je svaki znak u stringu zapravo cijeli broj izmeĎu 0 i 255
(slika 8.4.).
Slika 8.4. String predstavljen pomoću brojeva
8.3. Primjena hash tablica
Hash tablice imaju mnoge primjene u generalnom računalnom programiranju i u razvoju igara.
Neke od primjena hash tablica su sljedeće [Sherrod, 2007, str 212]:
1) Programi za provjeru pravopisa
2) Sustavi baza podataka
3) Rječnici
4) Telefonski imenici
5) Implementacija tablice simbola u skriptnom jeziku za igre ili u generalnom programiranju
6) Implementacija transpozicijske tablice u igri šaha
7) Lista igrača u MMORPG (Massively Multiplayer Online Role-Playing Game) igri
8) Čuvanje podataka o resursima koje igra koristi
8.3.1. Primjena hash tablica u igrama
Postaje sve uobičajenije da uz igre dolaze i alati koji omogućuju korisnicima da dizajniraju nove
mape ili likove za igru. U tim alatima korisnici imaju mogućnost da resurse (zvukovi, teksture)
koje će igra koristiti navedu po imenu. Kada se hash tablica ne bi koristila, svaki put kada bi
korisnik naveo ime resursa, igra bi morala pretraţiti sve učitane resurse da vidi da li je pojedini
resurs već učitan [Penton, 2003, str. 235]. Pošto većina igara sadrţi velike količine resursa, takvo
pretraţivanje bilo bi jako sporo, što bi loše djelovalo na njihove performanse. Uz upotrebu hash
tablica takva pretraţivanja nisu potrebna jer naziv pojedinog resursa moţe posluţiti kao ključ
[Ibid, str. 235]. U tom slučaju svaki element hash tablice bi sadrţavao dva podatka – ime resursa
P O Z D R A V
80 79 90 68 82 65 86
25
i pokazivač na adresu u memoriji gdje je resurs pohranjen. Svaki puta kada bi trebalo učitati novi
resurs njegov naziv bi pomoću hash funkcije bio pretvoren u hash vrijednost. Ako bi element u
hash tablici, koji odgovara toj hash vrijednosti bio prazan značilo bi da taj resurs još nije učitan
te bi ga bilo potrebno učitati. Nasuprot tome, ako bi element bio popunjen, ne bi bilo potrebno
ponovo učitavati resurs već bi se adresa već prije učitanog resursa pročitala iz hash tablice.
Primjer 28 prikazuje programski kod potreban za učitavanje resursa i njegovo spremanje u
odgovarajući indeks u hash tablici.
Primjer 28. Programski kod za učitavanje resursa
struct cTableEntry{
LPSTR m_szName;
int m_iIndex;
};
list<cTableEntry*> hash_tablica[32];
unsigned long int StringHash( const char* p_string ){
unsigned long int hash = 0;
int i;
int length = strlen( p_string );
for( i = 0; i < length; i++ ){
hash += ( (i + 1) * p_string[i] );
}
return hash;
}
void Object_SetTexture(int obj, LPSTR tex){
int found=0;
int hash_value=StringHash(tex) % 32;
if(hash_tablica[hash_value].empty()==false){
list<cTableEntry*>::iterator i=hash_tablica[hash_value].begin();
while(i!=hash_tablica[hash_value].end()){
cTableEntry *entry=*i;
if(strcmp(entry->m_szName,tex)==0){
found=entry->m_iIndex;
}
i++;
}
}
if(found==0){
found=free_image();
dbLoadImage(tex,found);
cTableEntry *entry=new cTableEntry();
entry->m_iIndex=found;
entry->m_szName=tex;
hash_tablica[hash_value].push_back(entry);
}
dbTextureObject(obj,found);
}
26
9. Rekurzija
U računalnom programiranju postoji mogućnost da jedna funkcija pozove sama sebe. Kada bi
programer u svojem programu napisao takvu funkciju bez mogućnosti da se takvo pozivanje u
odreĎenom trenutku prekine u programu mogla bi nastati beskonačna petlja koja bi se izvršavala
sve dok se nasilno ne prekine. Kada se u takvu beskonačnu petlju doda mogućnost nenasilnog
prekidanja dobije se rekurzija [Sherrod, 2007, str. 74]. Rekurzija ima mnoge korisne primjene u
programiranju i moţe biti ključna u dizajniranju programskog koda. Svrha rekurzivne funkcije je
pronalazak rješenja malog djelića velikog problema i svaki put kada se funkcija rekurzivno
pozove novi dio problema biva riješen [Ibid, str. 74].
Postoje dvije vrste rekurzije [Dale, 2003, str. 400]:
1) Direktna – kada funkcija zove sama sebe
2) Indirektna – kada jedna ili više funkcija poziva funkciju unutar koje su one pozvane
Rekurzija je moćna programerska tehnika, ali programer mora biti oprezan kada je koristi jer
rješenja pomoću rekurzije mogu biti sporija od rješenja pomoću iteracije [Ibid, str. 401].
Primjer 29 prikazuje rekurzivnu funkciju koja sluţi za izračunavanje y potencije broja x.
Primjer 29. Funkcija za izračunavanje y potencije broja x
int power(int x, int y)
{
if(y==0) return 1;
else{
return x*power(x,y-1);
}
}
9.1. Primjena rekurzije u igrama
Ponekad da bi se postigao odreĎeni efekt u igrama je objekte koji sačinjavaju virtualni svijet
potrebo renderirati odreĎenim redoslijedom. Jedan od takvih efekata je transparentnost kod kojeg
je potrebno objekte renderirati od onog najudaljenijeg od igrača do onog najbliţeg. Jedini način
da se odredi takav redoslijed je sortiranje niza objekata po njihovoj udaljenosti od igrača. Budući
da igrač u igri neprestano mijenja svoju poziciju takvo sortiranje je potrebo izvršavati više
desetaka puta u sekundi. Zbog toga je potrebno da algoritam sortiranja bude vrlo brz. Upravo
takav je quick sort algoritam koji se temelji na rekurziji i njegov kod je prikazan u primjeru 30.
27
Primjer 30. Quick sort algoritam
void q_sort(int array[], int left, int right){
int pivot, l_hold, r_hold;
l_hold = left;
r_hold = right;
pivot = array[left];
while (left < right){
while ((array[right] >= pivot) && (left < right))
right--;
if(left != right){
array[left] = array[right];
left++;
}
while ((array[left] <= pivot) && (left < right))
left++;
if(left != right){
array[right] = array[left];
right--;
}
}
array[left] = pivot;
pivot = left;
left = l_hold;
right = r_hold;
if (left < pivot)
q_sort(array, left, pivot-1);
if (right > pivot) q_sort(array, pivot+1, right);
}
void quickSort(int array[], int array_size){
q_sort(array, 0, array_size - 1);
}
28
10. Stabla
U računalnoj znanosti, stablo je struktura podataka koja je formirana u neku smislenu formaciju
[Sherrod, 2007, str. 286]. Početni dio stabla naziva se korijen. Iz korijena izlaze čvorovi koji su
posloţeni u hijerarhiju, a meĎusobno su povezani rubovima [Penton, 2003, str. 331]. Rubovi su u
C++-u predstavljeni pokazivačima (ako se radi o implementaciji stabla pomoću pokazivača) koji
pokazuju na druge čvorove u hijerarhiji, slično pokazivačima kod vezanih listi. Rubovi koji
izlaze iz korijena još se nazivaju i grane. Osim pokazivačima, rubovi mogu biti predstavljeni i
indeksima ukoliko se radi o implementaciji stabla pomoću polja. Grafički prikaz strukture
podataka stabla moguće je vidjeti na slici 10.1.
Slika 10.1. Grafički prikaz stabla
10.1. Dijelovi stabla
Vrstu stabla odreĎuju njegovi dijelovi [Sherrod, 2007, str. 287]. Općenito stablo se sastoji od
čvorova i rubova. Rubovi mogu biti bilo koji način povezivanja čvorova – pokazivačima ili
indeksima. Svaki čvor je podstablo sa svojim podacima i rubovima. Veze izmeĎu čvorova su tipa
roditelj-dijete. U općenitom stablo svaki čvor moţe sadrţavati proizvoljan broj čvorova djece.
Dijelovi stabla mogu se vidjeti na slici 10.2.
Korijen
Dijete 1 Dijete 2 Dijete 3
29
Slika 10.2. Dijelovi stabla [Penton, 2003, str. 331]
U tabeli 10.1. navedeni su termini koji se koriste za opis stabala.
Tabela 10.1. Termini za opis stabala [Ron Penton, 2003, str. 331 i Allen Sherrod, 2007, str. 288]
Termin Opis Primjer (slika 10.2.)
Korijen Najgornji čvor u stablu Čvor A je korijen stabla
Dijete Čvor ispod drugog čvora na istoj grani stabla B je dijete od A
Roditelj Čvor iznad drugog čvora na istoj grani B je roditelj od C
Brat Čvor na istoj razini sa drugim čvorom C je brat od D
List Čvor bez djece C i D su listovi
Razina Opisuje visinu čvora A je na nultoj razini, B na
prvoj, C i D na drugoj
Podstablo Stablo sadrţano unutar drugog stabla B je korijen podstabla stabla A
Ključevi Ključ se koristi kako bi se odredilo na koji
način će se dodati novi čvor u stablo. Ne koriste
sva stabla ključeve, samo neka (npr. binarno
stablo)
Obilazak Obilazak stabla je proces pomicanja kroz
čvorove stabla s ciljem provoĎenja nekog
algoritma na stablu
Stabla se smatraju rekurzivnim strukturama podataka jer je svako dijete u stablu stablo za sebe
[Penton, 2003, str. 332] (slika 10.3.).
a
b
c d
30
Slika 10.3. Stablo koje demonstrira rekurzivnu prirodu stabala, čvorovi b i c su stabla za sebe [Penton, 2003,
str. 331]
10.2. Vrste stabala
Postoje razne vrste stabala od kojih svaka ima svoju primjenu.
Neka od poznatijih vrsta su [Sherrod, 2007, str. 289]:
Općenito hijerarhijsko stablo
Binarno stablo
Kd - stablo
Crveno - crno stablo
Hrpa
Graf scene
BSP (Binary Space Partitioning) stablo
Quad-stablo (quad-tree)
Oc-stablo (octree)
10.3. Binarno stablo
Binarno stablo je stablo kod kojeg svaki čvor moţe imati najviše 2 čvora kao svoju djecu
[Penton, 2007, str. 360]. Ti čvorovi se obično nazivaju lijevo i desno dijete. Slika 10.4. prikazuje
čvor u binarnom stablu.
a
b c
31
Slika 10.4. Čvor u binarnom stablu
Sljedeća svojstva su karakteristična za binarna stabla [Penton, 2003, str. 361-362]:
1. Punoća – binarno stablo moţe biti puno. Zbog činjenice da svaki čvor moţe imati najviše
dvoje djece, moguće je napuniti stablo tako da se više ne mogu dodavati novi čvorovi bez
da se poveća visina stabla (slika 10.5.)
2. Gustoća – binarno stablo moţe biti i gusto. Gustoća se postiţe dodavanjem novih čvorova
na najdoljnjoj razini stabla prvo na lijevu stranu odnosno kao lijevo dijete
3. Balansiranost – balansirano binarno stablo je stablo kod kojeg svaki čvor ima otprilike
isti broj čvorova na lijevoj i na desnoj strani
Slika 10.5. Puno binarno stablo
Lijevo dijete Desno dijete
Roditelj
32
Slika 10.6. Gusto binarno stablo
10.4. Binarno stablo pretraživanja
Binarno stablo pretraţivanja je posebna vrsta binarnog stabla kod kojeg se čvorovi dodaju po
posebnom algoritmu tako da je kasnije pretraţivanje tog stabla vrlo jednostavno i brzo.
Smještaj novog čvora prilikom operacije dodavanja ovisi o ključu. Ukoliko je vrijednost ključa
čvora kojeg ţelimo dodati manja od vrijednosti ključa čvora kojem dodajemo dijete, taj dodani
čvor će postati lijevo dijete, a ako je vrijednost ključa veća onda će postati desno dijete [Kutti i
Padhye, 2004, str. 165]. Slika 10.7. prikazuje dodavanje novog čvora u binarno stablo
pretraţivanja s ključem čija je vrijednost 23.
Slika 10.7. Dodavanje novog čvora u binarno stablo pretraţivanja [Allen Sherrod, 2007, str. 300]
Iz slike se takoĎer vidi da nije potrebno puno usporedbi da bi se našlo mjesto na koje treba
dodati novi čvor. Što je stablo veće, to će biti efikasnije prilikom izvoĎenja raznih algoritama.
40
30 46
23
33
Primjerice, kod stabla sa milijun čvorova prilikom traţenja mjesta za novi čvor trebalo bi
napraviti najviše 20 usporedbi. Broj usporedbi takoĎer ovisi i o tome da li je stablo balansirano
ili nije. Najgori mogući scenarij kod nebalansiranog stabla je kada su vrijednosti koje se dodaju
već sortirane zbog čega bi svi dodani čvorovi bili smješteni sa samo jedne strane stabla. Kod
takvog stabla pretraţivanje bi bilo isto kao i kod vezane liste – sekvencijalno.
Slika 10.8. Najgori slučaj nebalansiranog stabla
10.5. Primjena stabala u igrama
U razvoju računalnih igara, stabla imaju razne primjene. Sluţe za ubrzavanje renderiranja scene,
ubrzavanje fizike u stvarnom vremenu, ubrzavanje detekcije kolizija, za ubrzavanje svjetlosnih
efekata, itd..
Graf scene je općenita struktura podataka koja se koristi za opis veza izmeĎu objekata u
virtualnim svjetovima [Sherrod, 2007, str. 449]. Veze izmeĎu objekata formiraju hijerarhiju u
kojoj su čvorovi meĎusobno povezani rubovima i mogu predstavljati materijale, odnose izmeĎu
fizičkih podataka, odnose izmeĎu pozicijskih podataka itd. Slika 10.9. prikazuje primjer grafa
scene.
45
36
20
15
34
Slika 10.9. Graf scene [Sherrod, 2007, str. 449]
Na grafu scene, struktura podataka moţe biti implementirana pomoću grafa ili stabla. Svaki čvor
u stablu moţe imati proizvoljan broj djece, ali samo jednog roditelja. Prema tome, kada je riječ o
implementaciji pomoću stabla, jedina moguća implementacija je pomoću općenitog stabla.
BSP (Binary Space Partitioning) stablo je struktura podataka kod koje se uzima set poligona koji
predstavljaju pojedini nivo igre te se od njih napravi odreĎena hijerarhija kako bi procesiranje
scene tijekom igre bilo brţe [Sherrod, 2007, str. 460]. U BSP stablu čvorovi djeca se obično
nazivaju prednji i straţnji čvor. BSP rekurzivno rascjepljuje virtualnu scenu na dva dijela.
Odabere se neka ravnina te se svaki poligon u nivou testira da se vidi s koje strane ravnine se
nalazi. Ako se poligon nalazi samo sa jedne strane, onda se on stavlja na jednu polovicu scene.
MeĎutim, ako se poligon nalazi s obje strane ravnine, on se prepolovi tako da se jedan njegov dio
nalazi s jedne, a drugi dio s druge strane ravnine [Ibid, str. 461]. Postupak se ponavlja sve dok se
neki odreĎeni uvjet ne ispuni (npr. dok se ne dosegne odreĎeni broj poligona u čvoru). Mnoge
popularne igre koriste BSP stabla za poboljšanje performansi. Neke od njih su Quake 4 i Doom
3.
Quad stablo je struktura podataka kod koje se virtualna scena dijeli na četiri jednaka dijela te se
zatim rekurzivno svaki taj dio dijeli na nova četiri jednaka dijela [Ibid, str. 466]. Taj postupak se
nastavlja sve dok se ne zadovolji neki uvjet. Svaki taj dio predstavlja jedan čvor u stablu te se
prilikom svakog dijeljenja odreĎuje koji se sve poligoni nalaze u kojem dijelu.
Root
World
pos World
pos
Texture
1 Texture
2
Object
1 Object
2 Object
3 Object
4
35
Slika 10.10. Quad stablo [Sherrod, 2007, str. 467]
Quad stabla se takoĎer mogu koristiti za kreiranje terena kod kojeg čvorovi sadrţe nekoliko
verzija virtualnog svijeta. [Ibid, str. 470]. Verzije se meĎusobno razlikuju po količini detalja
odnosno broju poligona. Koja verzija virtualnog svijeta u pojedinom čvoru će se renderirati ovisi
o udaljenosti čvora od igrača. Što je čvor udaljeniji od igrača to su i detalji u njemu manje
vidljivi igraču pa nije potrebno renderirati najdetaljniju verziju. Na taj način se smanjuje broj
poligona koji se trebaju renderirati te time performanse igre postaju bolje.
36
11. Hrpa
Hrpa je posebna vrsta binarnog stabla kod kojeg je vrijednost u svakom čvoru veća ili manja od
vrijednosti u čvorovima svoje djece [Penton, 2003, str. 410]. U slučaju da je veći radi se o max
hrpi, a u slučaju da je manju radi se o min hrpi. Primjer max hrpe nalazi se na slici 11.1.
Slika 11.1. Max hrpa
Postoje tri glavne karakteristike hrpe:
1) Hrpa je binarno stablo,
2) Hrpa je kompletna struktura podataka (čvorovi su potpuno napunjeni od lijeve strane
prema desnoj),
3) Vrijednost u svakom čvoru hrpe je veća ili jednaka od vrijednosti u čvorovima njegove
djece ako se radi o max hrpi, odnosno manja ili jednaka ukoliko se radi o min hrpi.
Prethodno nabrojane karakteristike ili pravila mora zadovoljavati svako stablo da bi se moglo
nazvati hrpom.
Hrpa je slabo sortirano binarno stablo jer iako su vrijednosti čvorova djeca svakog čvora manja
ili veća od vrijednosti svojeg roditelja, ta djeca nisu posloţena po nekom odreĎenom redoslijedu
[Sherrod, 2007, str. 329]. Usprkos tome, svrha strukture podataka hrpe je omogućavanje brzog
dodavanja novih čvorova. Prilikom dodavanja novog čvora, taj čvor se smješta na dno hrpe
(slika 11.2.). Nakon toga čvor se miče gore kroz hrpu dok se ne naĎe odgovarajuće mjesto tako
da sva pravila hrpe budu zadovoljena.
90
79 82
55 50 69 75
37
Slika 11.2. Dodavanje novog čvora u hrpu [Allen Sherrod, 2007, str. 330]
U slučaju brisanja čvorova uvijek se briše onaj čvor koji je na vrhu hrpe odnosno u korijenu.
Prilikom brisanja najgornjeg čvora u hrpi ostaje rupa koja se popunjava najdoljnjim čvorom u
hrpi koji se zatim miče dolje kroz hrpu dok mu se ne naĎe odgovarajuće mjesto u hrpi kako bi se
zadovoljila sva pravila hrpe [Sherrod, 2007, str. 330] (slika 11.3.).
Slika 11.3. Brisanje čvora sa hrpe [prilagoĎeno prema Sherrod, 2007, str. 330]
100
50 40
15 35 27 13
Korijen Prije brisanja
čvora sa
vrijednošću 100
50
35 40
15 13 27
Korijen Nakon brisanja
čvora sa
vrijednošću 100
100
50 40
15 35 27 13
Korijen
Dodani
čvor
38
Zbog toga što se najveća vrijednost uvijek nalazi u korijenu hrpa se moţe koristiti i kao
prioritetni red. Dokazano je da je hrpa najbrţa implementacija prioritetnog reda [Penton, 2003,
str. 416]. Tabela 11.1. prikazuje broj usporedbi potreban za ubacivanje i brisanje čvora iz
prioritetnog reda za dvije različite implementacije.
Tabela 11.1. Broj usporedbi prilikom brisanja i dodavanja čvorova u prioritetni red implementiran pomoću
vezane liste i hrpe
Broj čvorova Ubacivanje Brisanje Zbroj
lista hrpa lista hrpa lista hrpa
7 7 3 0 6 7 9
15 15 4 0 8 15 12
31 31 5 0 10 31 15
63 63 6 0 12 63 18
127 127 7 0 14 127 21
Hrpe nemaju nikakvu direktnu primjenu u igrama, ali zato implementacija prioritetnog reda
pomoću hrpe ima. Naime, u igrama je najvaţnije da se pojedini algoritam izvrši što brţe a hrpa je
najbrţa implementacija prioritetnog reda. U RTS (Real Time Strategy) igrama poput Warcrafta
ili Starcrafta, igrač ima mogućnost gradnje tvornica kojima zatim ima mogućnost zadavanja
gradnje nekih jedinica [Penton, 2003, str. 429]. Pošto su neke jedinice igraču vaţnije od drugih,
svakoj se jedinici moţe dodijeliti prioritet te će tako, ukoliko igrač tvornici zada gradnju više
različitih jedinica, tvornica uvijek prvo graditi onu jedinicu koja je najvaţnija odnosno onu koja
se nalazi na početku prioritetnog reda.
39
12. Grafovi
Ako uzmemo stablo, kod kojeg svaki čvor pokazuje samo na svoju djecu, i dodamo mogućnost
da svaki čvor pokazuje na bilo koji drugi čvor, dobijemo graf [Penton, 2003, str. 482]. Dakle graf
je struktura podataka bazirana na čvorovima kod koje svaki čvor moţe pokazivati na bilo koji
drugi čvor. Slika 12.1. prikazuje primjer grafa.
Slika 12.1. Graf
Stablo i graf su slične strukture podataka [Ibid, str. 482]:
1) Imaju čvorove u kojima su smješteni objekti,
2) Imaju rubove, ali se oni nazivaju lukovi,
3) Imaju čvorove koji su meĎusobno povezani te
4) Omogućuju obilazak čvorova koji se koristi kod raznih algoritama
Glavna razlika izmeĎu stabla i grafa je u tome što čvorovi u stablu mogu imati samo jednog
roditelja, dok čvorovi grafa mogu imati proizvoljan broj roditelja.
12.1. Vrste Grafova
Postoji puno različitih vrsta grafova. U ovom radu će biti opisani tri najpoznatije vrste:
dvosmjerni, jednosmjerni, te teţinski graf [Ibid, str. 483].
12.1.1. Dvosmjerni graf
Ovo je najjednostavniji oblik grafa kod kojeg svaki luk pokazuje na dva čvora [Ibid, str. 483].
Slika 12.2. prikazuje primjer dvosmjernog grafa.
1
2
3
4
5
40
Slika 12.2. Dvosmjerni graf
12.1.2. Jednosmjerni graf
Kod ove vrste grafa, svaki luk pokazuje na samo jedan čvor [Ibid, str. 483]. Slika 12.3. prikazuje
primjer jednosmjernog grafa.
Slika 12.3. Jednosmjerni graf
12.1.3. Težinski graf
Na teţinskom grafu svaki luk ima dodijeljenu teţinu [Ibid, str. 484]. Primjer teţinskog grafa
nalazi se na slici 12.4.
1
2
3
4
1
2
3
4
41
Slika 12.4. Teţinski graf [Ron Penton, 2003, str. 484]
Slika prikazuje kartu SAD-a na kojoj je 6 gradova prikazano pomoću čvorova a lukovi izmeĎu
njih imaju dodijeljene teţine gdje svaka teţina predstavlja udaljenost izmeĎu 2 čvora koja
pojedini luk povezuje.
12.2. Implementacija grafa
Graf je moguće implementirati na nekoliko različitih načina [Penton, str. 486-491]:
1) Adjacency tablice – kod ove metode koriste se dvodimenzionalna polja. Slika 12.5.
prikazuje adjacency tablicu za sliku 12.4. Da bi preko tablice saznali teţinu izmeĎu dva
različita grada treba naći stupac na x osi gdje se nalazi prvi grad i red na y osi u kojem se
nalazi drugi grad – ćelija u tablici gdje se stupac i redak sijeku predstavlja teţinu izmeĎu
ta dva grada.
New
York
Atl
anta
Den
ver
Sea
tlle
Dal
las
Lo
s
Ang
eles
New York 850 1800
Atlanta 850 800
Denver 1800 1350 1050
Seatlle 1350
Dallas 800
Los Angeles 1050
Slika 12.5. Adjacency tablica za primjer na slici 12.4. [Ron Penton, 2003, str. 487]
42
2) Smjerne tablice – kod ove metode se takoĎer koriste dvodimenzionalna polja, a
pretpostavka je da postoji odreĎeni broj lukova koji mogu izlaziti iz svakog čvora te da ni
jedan čvor nema više lukova od tog broja. Slika 12.6. prikazuje primjer grafa i
pripadajuće smjerne tablice. Način na koji radi ova tablica je sljedeći:
a. na x osi su navedeni svi mogući smjerovi koje pojedini luk iz svakog čvore moţe
imati,
b. na y osi su navedeni čvorovi,
c. u svakom stupcu za svaki čvor je navedena teţina pojedinog luka,
d. ukoliko teţina nije navedena znači da taj luk u dotičnom čvoru ne postoji.
Slika 12.6. Graf i njegova smjerna tablica [Ron Penton, 2003, str. 488]
3) Vezani grafovi opće namjene:
a. Dvosmjerni graf - kod ove metode struktura grafa se sastoji od dva dijela – prvi
dio predstavlja čvorove u grafu koji se mogu drţati u polju ili vezanoj listi, a
drugi dio predstavlja lukove koji isto tako mogu biti u polju ili u vezanoj listi.
Čvorovi grafa sluţe za drţanje podataka o pojedinom čvoru, a lukovi sadrţavaju
dva pokazivača na čvorove koje povezuju.
43
Slika 12.7. Dvosmjerni vezani graf
b. Jednosmjerni graf – Ovo je najčešći i najbrţi oblik vezanog grafa. Struktura grafa
se sastoji od čvorova koji mogu biti u polju ili vezanoj listi. Svaki čvor u grafu
osim podataka o samom čvoru sadrţi i vezanu listu lukova koji izlaze iz njega.
Svaki luk pokazuje na samo jedan čvor.
Slika 12.8. Jednosmjerni vezani graf
12.3. Pretraživanje grafa
Pretraţivanje grafa se provodi kako bi se saznalo do kojih čvorova moţemo doći iz početnog
čvora slijedeći lukove [Sherrod, 2007, str. 357].
Postoje dva načina pretraţivanja grafa [Ibid, str. 358]: prvo-u-dubinu i prvo-u-širinu.
C
1
2
3
A
B
1
2
3
A
B
C
ČVOROVI
A
C
1
2
3
B
1
2
3
ČVOROVI
A
B
C
LUKOVI
44
12.3.1. Prvo-u-dubinu
Pretraţivanje prvo-u-dubinu je algoritam koji koristi stog kako bi se kretao od početnog čvora do
završnog ukoliko je to moguće, ili dok se ne obiĎu svi čvorovi, a završni čvor nije pronaĎen
[Sherrod, 2007, str. 359].
Algoritam se sastoji od sljedećih koraka [Ibid, str. 358]:
1) Izabrati početni čvor i označiti ga kao trenutni
2) Staviti trenutni čvor na stog i označiti ga da je provjeren
3) Ako je trenutni čvor ujedno i završni onda stati inače nastaviti
4) Posjetiti prvi čvor koji je povezan s trenutnim čvorom a da još nije provjeren i označiti ga
kao trenutni čvor
5) Ponavljati korake od 2. do 4. sve dok algoritam ne provjeri sve čvorove na jednom putu
6) Ako završni čvor nije naĎen, maknuti trenutni čvor sa stoga i posjetiti sljedeći
neprovjereni čvor s kojim je trenutni čvor povezan
7) Ponavljati korake od 2. do 6. sve dok se svi čvorovi ne provjere, što znači da završni čvor
nije pronaĎen, ili dok se ne pronaĎe završni čvor
Ukoliko se pronaĎe završni čvor, čvorovi koji se nalaze na stogu nakon završetka algoritma
predstavljaju čvorove koji tvore put od početnog čvora do završnog.
12.3.2. Prvo-u-širinu
Pretraţivanje prvo-u-širinu je algoritam koji koristi red kako bi se kretao od početnog čvora do
završnog ukoliko je to moguće [Sherrod, 2007, str. 367].
Algoritam ima sljedeće korake [Ibid, str. 367]:
1) Staviti početni čvor na kraj reda i onda započeti petlju koja će se ponavljati sve dok je
red pun
2) Unutar petlje izvaditi čvor s početka reda i označiti ga kao trenutni čvor
3) Staviti sve neprovjerene čvorove koji su povezani sa trenutnim čvorom na kraj reda i
označiti ih kao provjerene
4) Ako više nema neprovjerenih čvorova koji su povezani s trenutnim provjeriti da li je
trenutni čvor završni
5) Ako je završni čvor pronaĎen, algoritam se završava
6) Ako završni čvor nije pronaĎen, ponoviti korake od 2. do 5.
Pretraţivanje prvo-u-širinu je korisno ako traţimo najkraći mogući put izmeĎu dva čvora na
grafu.
45
12.4. Primjena grafova u igrama
Jedna od najvaţnijih stvari u igrama je umjetna inteligencija. Bez obzira da li se radi od
dvodimenzionalnoj ili trodimenzionalnoj igri umjetna inteligencija je u igrama prisutna od
njezinih početaka. Jedna od najvaţnijih stvari što se tiče umjetne inteligencije je pronaći put kroz
virtualni svijet od točke A do točke B. Jedan od jako popularnih algoritama koji sluţe upravo
tomu je A* (A-star) algoritam [Sherrod, 2007, str. 391].
A* algoritam se izvodi na teţinskom grafu [Ibid, str. 391]. U virtualnom svijetu područja u koja
mogu zalaziti virtualni likovi su predstavljena čvorovima. Ti čvorovi su meĎusobno povezani
lukovima koji tvore putove koji govore igri kako iz jednog područja virtualnog svijeta doći u
drugo. Slika 13.9. prikazuje pogled odozgora na virtualni svijet u kojem čvorovi odreĎuju kako
iz jednog područja doći u drugo.
Slika 12.9. Pogled na virtualni svijet u kojem čvorovi definiraju putove iz jednog područja u drugo
Teţine na pojedinom čvoru mogu označavati razne stvari koje su drugačije od igre do igre.
Primjerice, mogu označavati „trošak“ puta do odreĎenog čvora [Ibid, str. 392]. Primjer toga bila
bi igra u kojem kompjuterski likovi imaju mogućnost voţnje kroz virtualni svijet. U tom slučaju,
teţine pojedinih čvorova mogle bi se odrediti prema tipu terena na kojem se pojedini čvor nalazi.
Čvorovi koji se nalaze na cesti imali bi najmanji trošak, dok bi oni koji se nalaze na travi ili blatu
imali veći trošak [Ibid, str. 392].
46
13. Zaključak
U računalnom programiranju i razvoju igara postoji mnogo različitih struktura podataka. Temelj
svakog programiranja leţi u učinkovitom korištenju spomenutih struktura i algoritama koji se
izvode nad njima. U ovom radu su navedene i opisane najpoznatije strukture podataka i načini na
koji se mogu iskoristiti prilikom razvoja igara. Pošto svaka igra započinje glavnim izbornikom i
igra koja dolazi uz ovaj rad ima svoj izbornik. On je baziran na stogu koji je opisan u sedmom
poglavlju. Budući da se radi o RTS ţanru igre u kojem je jedna od osnovnih stvari zadavanje
naredbi jedinicama, tako i igra uz ovaj rad dolazi s tom mogućnošću koja je implementirana
pomoću strukture podataka red, a opisana u osmom poglavlju ovog rada. Osim zadavanja
naredbi potrebno je i pristupati raznim podacima koji postoje o svakoj jedinice ali i ostalim
dijelovima igre. Da se performanse igre ne bi pogoršale takvim podacima je potrebno pristupati
brzo. Upravo tomu sluţe polja čija je najbolja namjena čuvanje podataka kojima se treba brzo i
često pristupati. Polja su opisana u drugom poglavlju. U četvrtom poglavlju je opisana bitvector
struktura uz pomoć koje je izvedena quick save funkcija, a čija je namjena da igraču omogući
spremanje stanja igre odnosno virtualnog svijeta u nekom odreĎenom trenutku, prekine s igrom
te nastavi kasnije. Jedan od vaţnijih aspekata svake igre svakako je i manipulacija resursima
kojima igra raspolaţe. Zbog ograničene veličine računalne memorije vaţno je da se pojedini
resurs ne učita u memoriju više od jedanput. Pošto današnje igre obiluju resursima, sekvencijalno
pretraţivanje već učitanih resursa sa ciljem provjeravanja li odreĎeni resurs već postoji, bilo bi
presporo. U takvim situacijama potrebno je to pretraţivanje ubrzati. Prateća igra ovom radu
koristi hash tablicu kao metodu ubrzavanja pretraţivanja već učitanih resursa što je opisano u
devetom poglavlju. Osim struktura podataka koje su korištene u popratnoj igri u samom radu sa
navedene još neke koje imaju primjene u računalnim igrama. To su stabla te grafovi koji se često
koriste u umjetnoj inteligenciji. Naravno nijedna od tih struktura ne mora se koristiti upravo
tako. Neke strukture su pogodnije za jedan tip igre a druge za drugi. Na programeru je da odluči
koja struktura podataka će za njegovu igru biti najkorisnija. U igrama je najbitnija brzina
izvoĎenja pojedinih algoritama nad podacima pa programer prilikom odabira pojedinih struktura
mora obratiti pozornost i na različite implementacije pojedinih struktura i način na koji će ih
koristiti. Ponekad je sporija varijanta neke strukture korisnija ako se algoritmi nad njome rijetko
izvršavaju te ako je jednostavna za implementirati. U tom slučaju programer moţe ţrtvovati
brzinu zbog jednostavnosti.
47
14. Literatura
1) Ammeraal, L. (1996). Algorithms and Data Structures in C++, Wiley
2) Brownsey, K. (2000). The Essence of Data Structures Using C++ (1. izdanje), Pearson
Education Limited
3) Conger, D., Little R. (2006). Creating Games in C++: a step-by-step guide, New Riders
4) Dale, N. (2003). C++ Data Structures (3. izdanje), Jones and Barttlett Publishers
5) Kasiviswanath, N. (2007). Data Structures Using C++, Laxmi Publications LTD
6) Keogh J. E., Davidson K. (2004). Dana Structures Demystified, McGraw-Hill
7) Kutti N. S., Padhye P. Y. (2004). Data Structures in C++, Prentice-Hall
8) Lipljin, N. (2004). Programiranje/1, TIVA Varaţdin
9) Llopis, N. (2003). C++ for Game Programmers, Cengage Learning
10) Penton, R. (2003). Data Structures for Game Programmers, Premier Press
11) S. Childs, J. (2008). C++ Classes and Data Structures, Prentice Hall
12) Sahni, S. (2004). Data Structures, Algorithms, And Applications In C++.
Silicon Press
13) Sedgewick, R. (2002). Algorithms in C++, Parts 1-5: Fundamentals, Data
Structures, Sorting, Searching, and Graph Algorithms. Third Edition,
Addison Wesley Professional
14) Sherrod, A. (2007). Data Structures and Algorithms for Game Programmers, Charles
River Media
15) Smith, P. (2004). Applied Dana Structures with C++, Jones and Bartlett Publishers, Inc.
16) Weiss, M. A. (2006). Data Structures and Algorithm Analysis in C++. Third
Edition, Addison Wesley
Recommended