Kártyalapok az univerzumban
A programozásról szóló sorozatomban az elejétől fogva azt a szemléletet választottam, hogy programozáskor egy képzeletbeli univerzumot építünk és működtetünk. Kezdjünk bele egy ilyen univerzum megteremtésébe. Persze nagyon egyszerű, már-már bugyuta példával érdemes kezdeni. Ha már egyszer a fejembe vettem, hogy megtanítom a teljesen tájékozatlan olvasót, akkor neki kis segítséggel szinte magának kell mindent kitalálnia. Aki tudja ezeket, az nem tartozik a célközönséghez, aki meg nem, annak jobb teljesen elemi dolgokkal kezdenie.
Szóval megint a sima 52 lapos francia kártya lesz a példa. Ebben az a jó, hogy mindegyik lap önálló lény a virtuális világunkban, amit a színével (treff, káró, kőr vagy pikk) és az értékével (2–10, bubi, dáma, király és ász) lehet jellemezni, és mindegyik lapból csak egy van (például nincs két kőr 9-es). Ugyanakkor önálló lénynek tekinthető a teljes pakli is, sőt ha például négy egyenlő részre osztjuk a paklit (ahogy például a bridzsben kell), akkor a négy ún. kéz is egy-egy 13 lapból álló lénynek tekinthető, amit valahogy ábrázolnunk kell a számítógép memóriájában.
Tárgyak és tulajdonságok
De maradjunk egyelőre az egyes lapoknál. Talán ott kéne kezdeni, hogy nemcsak ezek a valóságos tárgyak tekinthetők lényeknek a programozáskor, hanem az adott szempontból fontos tulajdonságaik is. Például majdnem mindig fontos a kártyalapok színe és értéke, ezért a színeknek megfelelő 4 tulajdonságot és az értékeknek megfelelő 13 tulajdonságot is ábrázolnunk kell valahogy, még akkor is, ha nem foglalnak külön helyet a memóriában. Minden egyes lapnak szüksége van egy kis helyre, egy bit-sorozatra, amiből leolvasható a színe és az értéke, ilyen értelemben kell a színek és az értékek ábrázolását is kitalálnunk.
Bár a memóriában minden adat egy-egy bit-sorozatnak felel meg, ha nem muszáj, akkor nem bajlódunk bitekkel, hanem a bit-sorozatokat szokásosabb adattípusoknak fogjuk fel, amiket könnyebb leírni, megjegyezni stb. Vagyis a virtuális létezőinket magukat is virtuális létezőkkel ábrázoljuk. Például nagyon gyakran ábrázolunk dolgokat számokkal, mert egyszerű bánni velük, van természetes sorrendjük, és még ezer más okból. (Egy egész szám a memóriában általában 32 bit helyet foglal el, de a számokkal olyan egyszerű bánni, hogy még akkor is szoktunk számokat használni, amikor az ábrázolt dolgok megkülönböztetéséhez messze nincs szükség ennyi bitre.) Hasonlóan jól kezelhetők a karakterek (itt a hagyományos 256 ASCII karakterre kell gondolni, ezek 8 bit helyet foglalnak el), és gyakran használjuk az „igaz” és „hamis” értelmezésű két igazságértéket (más néven Boole-féle értéket), ennek ábrázolásához elvileg csak 1 bit memóriára lenne szükség.
Számokkal nagyon egyszerűen ábrázolhatjuk a színeket, a lapértékeket, a lapokat és a paklikat (kezeket), sőt, többféle megoldás közül választhatunk. Például mondhatjuk, hogy az 52 lapot összesen 52 számmal, mondjuk a 0 és 51 közötti számokkal ábrázoljuk (szokjuk meg, hogy 0-tól kezdjük a számolást, sok szempontból ez a praktikus, ezért ez a szokás), az egész paklit pedig az 52 lapnak megfelelő 52 számból álló tömbbel. Akkor a lapok sorszámából egy egyszerű eljárással kiszámolhatjuk a színüket és az értéküket: megnézzük, hogy az adott lap sorszámában hányszor van meg a 13, és mennyi a maradék. Az első szám 0 és 3 között lesz, vagyis négyféle lehet, ezek felelhetnek meg a négy színnek; a mardék 13-féle lehet, ezek felelhetnek meg a lapértéknek. (Fordítva is csinálhatjuk, 4-gyel osztva.)
Ha nem akarunk állandóan maradékos osztást számolni, akkor megtehetjük azt is, hogy minden lapot két számmal ábrázolunk (egy két számból álló tömbbel), az egyik a színét, a másik az értékét jelöli. Ekkor az egész pakli tárolásához nem 52, hanem 104 számra van szükségünk. Például ezt így jelenthetjük ki valahol a programunk elején:
int pakli[104];
Ezt a sort úgy kell kiolvasni, hogy a pakli szimbólum ezek után egy 104 egész számból (angolul: integer, ezért int) álló tömbre utal, ennyi helyet lefoglalunk a memóriában erre a célra. (Persze másképpen is csinálhattuk volna, például úgy, hogy két 52 hosszúságú tömbnek foglalunk helyet, úgy, hogy az egyikben az egyes lapok színét, a másikban a lapértékét tároljuk.)
Legutóbb beszéltem arról, hogy milyen programozási nyelvvel érdemes kezdeni a tanulást. Erre most nagyon furcsa választ fogok adni: szerintem egyszerre több nyelvvel érdemes kezdeni, majdnem párhuzamosan. De ebből a több nyelvből csak egészen alapvető dolgokat érdemes megtanulni, és aztán később választani olyat, amiben bonyolultabb feladatokat akarunk megoldani. Így például a múltkor a függvényekkel és az eljárásokkal kapcsolatban a Pascal nyelvből hoztam példát, mert az szépen megkülönbözteti a kettőt. Ezentúl is így járok majd el, mindig olyan nyelvből hozok példát, amiben a legjobban látszik, amit magyarázok. Például a fenti sort olyan programozási nyelveken lehet leírni (közelebbről a C vagy a C++ nyelven), ahol a programokban általában külön be kell jelenteni, hogy mennyi memóriát szeretnénk lefoglalni, és hogy melyik szimbólummal melyik memóriabeli címre fogunk hivatkozni.
Attól, hogy a fenti deklarációval bevezettük a pakli szimbólumot, és némi helyet foglaltunk a paklinak a memóriában, még nem lesz semmi értelmes adat azon a helyen. Ha úgy tetszik, létrehoztuk vele a paklit mint virtuális létezőt, de anélkül, hogy az egyes lapokat létrehoztuk volna. Ahhoz még ki kéne tölteni a pakli-nak nevezett tömböt, mind a 104 elemét kitöltve, laponkét két-két számmal, úgy, hogy 52 különböző lapot kapjunk. Ha tudjuk, hogy hogyan kell egy tömbnek egy elemét kitölteni, akkor ezt 104 egymás utáni utasítással meg tudjuk tenni. Például:
pakli[34] = 2;
pakli[35] = 8;
Vagyis a tömb valahanyadik elemének úgy tudjuk megváltoztatni az értékét, hogy a neve után szögletes zárójelben megadjuk az elem sorszámát, és utána egy egyenlőségjel után az új értéket. Itt feltételeztem, hogy a pakliban a lapok szépen sorrendben vannak (a színek sorrendje: treff – káró – kőr – pikk, tehát a kőrnek a 2 szám felel meg, a lapértékek pedig 0-tól 12-ig terjedő számok). A tömb 34-edik és 35-ödik rekesze a pakli 18-adik lapjának felel meg, ez a kőr 10-es, ezért választottam színnek a 2, lapértéknek pedig a 8 értékeket (a 10-esnek azért a 8 felel meg, mert úgy döntöttem, hogy a 2-es a legalacsonyabb lap, az ász a legmagasabb).
Mielőtt a következő részben egészen más irányba haladnánk tovább, még gyorsan elmondom, hogyan lehet megspórolni, hogy 104 ilyen utasítást kelljen egymás után leírnunk. Megint a C vagy a C++ nyelvet veszem példának, ezekben a nyelvekben (és még néhány más nyelvben is) a következőképpen tehetjük meg ezt egész tömören:
int i;
for ( i = 0; i < 52; i = i + 1 )
{
pakli[i * 2] = i / 13;
pakli[i * 2 + 1] = i % 13;
}
Elmondom, mit jelent ez. Először is deklarálunk egy i nevű egész számot, ez lesz a pakliban annak a lapnak a sorszáma, ahol éppen tartunk. (Ez a szám nem lesz valami nevezetes létező az univerzumunkban, csak egy statiszta, akinek ebben az egy jelenetben van szerepe, amíg feltöltjük a paklinkat lapokkal.) Ezután jön egy ún. ciklus, vagyis egy olyan programrész, aminek a végrehajtását sokszor megismételjük. Hogy mit ismétlünk meg, az a „{” és a „}” jelek között szerepel. A for-ral kezdődő sor pedig három dolgot mond meg (zárójelek között, „;”-vel elválasztva egymástól). Az első az, hogy mit teszünk az ismételgetés előtt: az i-nek a 0 értéket adjuk, mert ott akarjuk kezdeni, a tömb első eleménél. A második az, hogy meddig folytatjuk az ismételgetést: addig, amíg az i értéke kisebb, mint 52, mert az utolsó lap sorszáma 51, hiszen 0-nál kezdtük. A harmadik pedig az, hogy mit teszünk két ismétlés között: 1-gyel növeljük az i értékét. Amit pedig ismételgetünk: az i-edik laphoz két elem tartozik a tömbünkben, az elsőnek pont az i kétszerese a sorszáma (0, 2, 4 stb.), ez jelöli majd a lap színét, a másodiknak pedig a következő, eggyel nagyobb sorszámú elem (1, 3, 5 stb.), ez jelöli majd a lapértéket. Mivel sorban haladunk a lapokkal, a színt úgy kapjuk meg, hogy hányszor van meg a lap sorszámában a 13 (ezt fejezi ki a „/” jel), a lapértéket pedig úgy, hogy megnézzük, mennyi a maradék ennél az osztásnál (ezt fejezi ki a „%” jel).
Na, és merre fogunk innen továbbhaladni? Elég sok problémánk van még. Az egyik az, hogy a kártyánk lapjaihoz még más információkat is szeretnénk társítani, például azt, hogy hogyan lehet megjeleníteni (például a képernyőre kiírni) egy-egy lapot (a színét és az értékét). Hogy tudjuk majd azt megtenni? A másik problémánk az, hogy a leendő programunkat, ahogy kezdettől fogva mondtam, modulárissá kell tennünk, és ez azzal jár, hogy ha meggondolnánk magunkat, és mégis másképpen ábrázolnánk a paklinkat (vagy a lapokat, vagy a színeket, vagy az értékeket), akkor semmi mást ne kelljen a programunkban megváltoztatni. Mindezekhez újabb boszorkányságokra lesz majd szükségünk.