Ehhez
a szakdolgozathoz program is készült – J. R. R. Tolkien A
babó című regénye alapján, azonos címmel. Még annak idején,
C64-en írtam egy – hivatalosan is forgalmazásra került – kalandjátékot
(A gálya volt a címe), mely egy, a lehetőségekhez képest
eléggé intelligens szövegértelmezőt tartalmazott (az ismertetett
szóragozó algoritmusok alkalmazásával), benne volt a távoli célpont
megközelítése, a rekurzív tárgy-keresés, többszáz helyiséggel
és tárggyal és hosszú és szövevényes cselekménnyel volt ellátva,
és jelentős újdonsága pedig az volt, hogy egyszerre (pontosabban
csak felváltva) két főhőst lehetett irányítani benne egymástól
függetlenül: ennek a programnak az elkészítése majdnem kereken
két évig tartott. (Igaz, hogy grafika is volt benne, amelyet szintén
én magam rajzoltam meg hozzá, azonkívül két nyelven írtam egyszerre,
magyarul és angolul…) Nyilvánvalóan egy diplomamunka elkészítéséhez
rendelkezésre álló pár hónap alatt nem lehet hasonló színvonalú
végeredményt elvárni, ezért A babó c. játék ennél valamivel
egyszerűbb. Illetőleg maga az alapvető irányítási rendszer jóval
fejlettebb és bonyolultabb, de a játék tartalma töredéke sincs
a másiknak.
A
szöveg – írásos – ábrázolásának alapegysége a betű, s nyilván
én is innen indultam el. A hagyományos ábécé és az erre alapuló
ASCII-kódrendszer eléggé megnehezíti a szavak kezelését bizonyos
körülmények között: már eleve is vegyesen vannak benne a magánhangzók
és a mássalhangzók, s miután az ASCII-rendszer alapvetően az angol
ábécére épül, a PC-s karakterkészletből a magyar ékezetes betűk
egy része hiányzik is, a többi pedig, ami megvan, teljesen összevissza,
elszórtan helyezkedik el. A fontosabb írásjelek is teljesen ésszerűtlenül
lézengenek erre-arra… Így egy karakterről eldönteni, hogy pl.
az ábécébe tartozik-e egyáltalán, eléggé macerás dolog, olyan
finomságokról, mint a szótár szavainak ábécé-sorrendbe való rendezése,
nem is beszélve. Ezért én egy teljesen saját kódrendszert alkalmaztam
helyette: ez úgy néz ki, hogy az elején vannak a magánhangzók,
azok is szétválasztva (előbb az alacsony, majd a magas hangrendűek),
ill. a nullás karakter a szóköz, s mögöttük jönnek a mássalhangzók,
aztán egy ugyanilyen blokkban a nagybetűk, a számok, végül pedig
az írásjelek zárják a sort – mindösszesen éppen 128 karakter.
Annak eldöntése, hogy egy karakter betű-e, szám vagy írásjel,
vagy hogy egy betű magánhangzó vagy mássalhangzó-e, egyetlen összehasonlítással
történik mindenféle táblázatokban való keresgélések helyett.
A
szótár szavai eszerint az új ábécé szerint blokkokba és szegmensekbe
csoportosítva helyezkednek el. Egy blokkba tartozik az összes
olyan szó, aminek az első két betűje egyezik (kis- és nagybetűk
közt nem tesz különbséget), egy szegmensbe pedig az összes olyan
blokk, aminek a legelső betűje egyezik. Van még egy speciális,
ún. vegyesblokk is, amibe az esetleg nem betűvel (hanem mondjuk
számmal stb.) kezdődő, valamint az egybetűs szavak kerülnek. Ezzel
a módszerrel bármilyen hatalmas szótár is villámgyorsan kezelhetővé
válik: ha egy ismeretlen szót szeretnénk azonosítani, az első
két betűje máris kijelöli a blokkot, amiben keresni kell – azonos
blokkba pedig még egy sokezer szavas szótár esetén is max. néhány
tucatnyi kerülhet. Ezt a pár darab szótárbéli szót az értelmező
program minden lehetséges módon végigragozza, s az alakokat sorban
egymás után összehasonlítja a beadott szóval. (Erre azért van
szükség, mert a szavaknak néha eléggé sokféle ragozott alakja
lehet.) Az ellenkező irányból ugyanez az út nem volna járható,
vagyis egy ismeretlen szó végéről nem lehetséges lehasítani semmiféle
ragot, miután képtelenek volnánk megállapítani, hol végződik a
szótő és kezdődik a rag. (Pl. a KÖVET szó egyaránt lehet a KŐ
tárgyesete vagy a KÖVETNI ige alakja – de ha ezt általánosítani
próbálnánk, akkor a program esetleg a LÖVET igéből is levezetne
egy nemlétező LŐ főnevet stb.) Minden egyes szót egy-egy sorszám
azonosít, ezt kapjuk vissza végeredményül, valamint a ragozásnak
a típusát. A szavak mellett szerepel a szótárban mindegyiknek
a sorszáma, továbbá még egyéb információk is: a szófaja (ige,
főnév, melléknév…), hogy milyen típusú ragozást igényelnek és
hasonlók. A különböző szófajok a szótárban abszolút vegyesen helyezkednek
el, a kezdőbetűk törvényének engedve, de a sorszámozás alapján
már különválnak egymástól: az elején a főnevek (azon belül is
az élőlények, tárgyak stb. – ld. a korábbi fejezeteket), majd
a melléknevek, az igék, az igekötők, s végül a speciális, ragozatlan
szavak jönnek (névelők, kötőszók stb.). A parancsok igéből, tárgyból,
helyhatározóból és eszközhatározóból állnak, és lekezeli a program
a „melléknév + főnév” típusú jelzős szerkezeteket a gyűjtőnevek
pontosítására (pl. szerepel benne tizennégy különböző színű csuklya:
zöld, sárga… stb.). A legutóbbi főnév helyettesítésére névmások
is alkalmazhatók (külön van az élők és élettelenek számára is
egy-egy: ŐT és AZT), és egy mondaton belül több parancs is állhat
(ezeket lépésenként hajtja végre). A hiányos mondatokat is lekezeli
a program: ha egy fontos főnév vagy ige hiányzik, akkor kiegészítendő
jellegű kérdéseket tesz föl arra vonatkozólag. A játék teljes
szótára kb. 1000 szót tartalmaz – jóllehet ezek többsége nem igazán
van kihasználva, a jelenlévő főnevek és tárgyak túlnyomó része
inkább csak leíró, díszlet jellegű.
Az
egyes szófajok kezelése és megkülönböztetése úgy történik, hogyha
kiértelmeztük a soron következő szót, akkor megvizsgáljuk, hogy
az ige-e vagy igekötő – s ha igen, akkor még mielőtt elraknánk
a megfelelő változó rekeszébe, ott helyben megpróbáljuk az „ige
+ igekötő” szókapcsolatot egy összetett igévé alakítani. Ha melléknevet
találtunk, úgy azt azonnal eltesszük a melléknév változójába.
Innen csak akkor vesszük elő legközelebb, ha már egy főnév is
érkezett, s akkor – az igekötőkhöz hasonlóan – megpróbáljuk ezt
a jelzős szókapcsolatot is átalakítani. Ezek a lehetséges szópárok
valamennyien egy-egy táblázatban fordulnak elő, ahol egyrészt
el vannak tárolva az igék és igekötők (ill. melléknevek és főnevek)
összerendelt párjai, másrészt az ezekhez tartozó új, kicserélendő
igék (ill. főnevek). A táblázat első részével sorra összehasonlítjuk
a keresett szavak sorszámait, s ha valahol egyezést találunk,
akkor az ahhoz tartozó új szóra cseréljük ki a régit. Hogy a beazonosított
főnév tárgy, helyhatározó vagy eszközhatározó lesz-e, azt annak
ragozása dönti el, s ennek megfelelően a szükséges változóba kerül.
A névmások behelyettesítésekor egy kisebbfajta trükköt kellett
alkalmazni, mert ha csak úgy egyszerűen kicserélnénk azt a legutóbbi
főnévre, akkor például az ADD ODA A KULCSOT NEKI utasítás esetén
(ahol a NEKI a legutóbbi élőlény neve helyett állhat) az elsőként
megtalált KULCS főnév tárolódna el legutóbbiként (felülírva a
korábbiakat), majd az is helyettesítődnék be, s így helytelenül
az ADD KULCSOT KULCSNAK parancsot kapnánk belőle végeredményül…
Ennek elkerülése érdekében a névmásokat két példányban kell tárolni:
külön van a régi, ahonnan a behelyettesítéskor mindig kiolvasunk,
valamint az új, ahová pedig mindig beírjuk az éppen megtalált
főnév sorszámát; a parancs végére érve azután az újból áttöltjük
az ott tárolt értéket a régibe, s így a következő parancs már
azt veszi majd át. Ezenkívül ráadásul kétféle különböző névmást
vezettem be az élő (Ő, ŐT, NEKI, ŐNEKI, ŐVELE… stb.) és az általános
(nemcsak élettelen, hanem mindent, így az élőket is magába foglaló)
objektumok számára (AZT, ANNAK, VELE, AZZAL… stb. alakok). Sőt,
még egy harmadik típusú személyes névmás is létezik, ez pedig
magát a játékost, tehát az általa irányított jelenlegi szereplőt
helyettesíti (ÉN, ENGEM, TE, TÉGED, MAGAD, MAGAM… stb. alakok).
Az eddigiekben elmondottak lehetővé teszik számunkra, hogy a megfogalmazott
utasításaink teljesen szabad szórendűek legyenek – elvégre is
a végrehajtás nem kezdődik meg mindaddig, míg a teljes parancsot
be nem olvasta a program, és valamennyi szófaj és mondatelem egymástól
független módon kezelődik. (Leszámítva, hogy a melléknévnek a
főnévnél előbb, az igekötőnek pedig az igénél hátrébb muszáj állnia.)
Ez
a program tipikusan egy többszereplős kalandjáték: akár tizennégy
főhőst is alakíthatunk benne, amennyiben kedvünk tartja (az eredeti
történet főszereplőit), s mindig követhetjük a többiek szemével
is azt, hogy mit csinál az egyik. Ezt ráadásul úgy oldottam meg,
hogy egy, kettő vagy négy, egymástól teljesen független ablakra
oszthatjuk a képernyőt, s valamennyi ilyen ablakban egy-egy külön
szereplőt irányíthatunk: ezeknek az ablakoknak a kezelése teljesen
párhuzamosan zajlik – akár mind a négyben egyidejűleg (!!!) is
történhet különböző szövegeknek a folyamatos kiíratása. Mindeközben
szakadatlanul szerkeszthetjük a képernyő legalján fönntartott
beviteli mezőben a következő mondatunkat, s azt bármelyik ablak
számára el lehet küldeni. Mindebből következik, hogy a program
természetesen valósidejű időzítéseket alkalmaz. A másodpercenként
kb. 18.2-szer végrehajtódó IRQ megszakításra rászinkronizálva
egy megszakítás-alprogram kb. 1/3 másodpercenként generál egy
órajelet az összes szereplő számára, s valamennyi szereplőhöz
tartozik egy számláló, ami azt tartja nyilván, hány db. ilyen
órajel múlva következik lépésre. Az egyes szereplők számlálói
2-5 másodperces időközökre vannak beállítva, személyenként különbözőképpen
(pl. a testes Bombur mozgása lassúbb, mint a fürge Bilbóé). Minden
szereplő számára fönntart a program egy-egy saját puffert, ahová
a végrehajtandó utasításai kerülnek (ilyenkor még szövegesen,
kiértelmezetlenül), s innen veszi elő egyesével őket. Ha az aktuális
szereplőnknek egy friss mondatot begépelünk, miközben az előzőt
még nem hajtotta végre teljesen, akkor a végrehajtásra váró lépései
elvesznek, felülírja őket az új mondat. Mind a négy ablakban lévő
szereplőnknek külön utasításokat adhatunk, s azután hátradőlve
leshetjük, ahogyan egymással versengve végrehajtják azokat…
Az
ablakokba történő kiíratás ugyancsak – többszörösen – pufferelten
történik, részben ez teszi lehetővé, hogy egymással párhuzamosan
használhassuk őket. Az ablakok itt valóban ablakot jelentenek,
amelyeket a hozzájuk tartozó – jóval nagyobb méretű – szövegpufferok
területén fölfelé-lefelé egyaránt mozgatni lehet (így pl. visszanézhetjük
a korábban kiírt szövegeinket is). Négy ablak létezik és négy
puffer, ezek közül bármelyik puffer bármelyik ablakban megjelenhet
(egyszerre többen is akár). A szövegpufferok tartalmát különféle
mutatók tartják karban: létezik egy-egy a szöveg kezdetének és
végének meghatározására, arra, hogy hol helyezkedik el éppen az
ablak a szövegen, és hogy a szöveg mely pontjától kezdődik annak
még érintetlen, „szűz” része, ami még sohasem volt a képernyőn,
s ezért azt külön felszólítás nélkül is ki kell íratni. Egy-egy
másik változó őrzi a pufferban lévő foglalt és szabad terület
méretét. Egy üzenet kiírása a pufferba úgy történik, hogy először
is fölszabadítjuk számára a szükséges helyet, majd onnantól bemásoljuk
a karaktereket, s ez hozzáépül az alsó „szűz” részhez (hozzáigazítjuk
a megfelelő mutatókat). Ezután kezd el ez soronként innen kiaraszolni
a képernyőre – ha több volna, mint amennyi egyszerre kifér, akkor
a jobb alsó sarokban egy kis szamárfül jelenik meg, és ENTER-re
folytatódik a kiírás (de bizonyos idő elteltével magától is továbblépteti
a szöveget). Az ablakok megjelenítésére a program grafikus képernyőt
használ, a ma már alapnak számító 640*480*256 színű SVGA üzemmódban
(ill. ha ez nem elérhető, akkor a 320*200*256 színű módban). Az
ablakokban levő szöveg görgetése nem karakterenként, hanem képpontokként
történik! (Állíthatóan 1,2,4… stb. pixelenként egy lépésben.)
A program karakterkészlete 8*16 pontos karakterekből áll. Hogy
mind a négy ablakban párhuzamosan történhet a kiírás, az annak
köszönhető, hogy egyszerre mindig csak egy pixelsornyit mozdítunk
rajta, majd vesszük a következő ablakot és azon is, és így tovább.
Ehhez még hozzájön a beviteli sornak a szerkesztése is, valamint
a szereplők beidőzített lépéseinek a végrehajtása – pontosan a
4.1.10. fejezetben elmondottaknak megfelelően. Hogy ne döcögjön
és normális tempóval haladjon mind a négy szöveg, ehhez igen nagy
sebességre van szükség, ami ASSEMBLY nyelvű programozást és a
386-os kódú 32-bites regiszterek használatát vonja maga után.
386-osnál ócskább gépen (vagy ha nincsen hozzá VGA) emiatt nem
grafikus, hanem csak sima szöveges karakteres képernyőn jeleníti
meg a program. (Tehát elméletileg még egy Hercules monitoros XT-n
is működnie kell – bár kipróbálni sajnos nem volt lehetőségem.)
Egyébként is, az egész program színtiszta ASSEMBLY-ben készült…
A
helyiségek és a térkép tárolása pontosan a 4.1.5. fejezetben ismertetett
bejárási táblázat formátumának megfelelően történik. A játékban
mintegy 120 helyiség található. A tárgyak térképen való elhelyezése
és nyilvántartása szintén a 4.1.7. és 4.1.8. fejezetekben leírt
statikus sorszám- és rekurzív táblázatkezelés elve alapján zajlik.
Ha egy helyiségben másodszor járunk, utána már a teljes leírás
helyett csupán pár szavas nevét írja ki nekünk a program. A tárgyak
befogadóképessége is súly szerint korlátozva van.
Az
egyes igékhez különböző belső eljárásai vannak hozzárendelve a
programnak, ezeknek kezdőcímei egy táblázatba foglalva várják,
hogy az ige sorszámával kiindexeljük és végrehajtsuk őket. Még
az ige végrehajtása előtt ellenőrzi azonban, hogy a megadott főnevek
helyesek-e és elérhetőek, s ha minden stimmel, akkor ugrik tovább
a megfelelő eljárásra. Itt még további ellenőrzéseket végez, s
ha sikerült végrehajtania, akkor a tájékoztató jellegű üzeneteket
egy speciális kiíró eljáráson keresztül vezeti át az ablakok puffereibe
– ez a szubrutin mindig végigteszteli mind a négy ablakot és azok
„tulajdonosait”, s amelyik ezek közül ugyanazon a helyszínen tartózkodik,
annak számára írja ki a megfelelően átalakított üzeneteket. (Pl.
„Felveszed a lámpát.” helyett „Bilbó felveszi a lámpát.” stb.)
Ha olyan főnév szerepel benne, amelyik több helyszínen is jelen
van egyszerre (pl. egy ajtó két szobát köt össze), akkor a vele
történt eseményről valamennyi helyen tudósít – de úgy, hogy a
mondatban szereplő további főnevek közül, ha valamelyik nem elérhető,
akkor helyette a „valami” ill. „valaki” szót használja a program
(attól függően, hogy élőlényről van-e szó). Pl. ha kopogtatunk
egy ajtón, akkor annak túloldalán a „Valaki kopog az ajtón.” üzenetet
kapjuk. Akkor is ezek a határozatlan névmások kerülnek elő, ha
a kiírás egy sötét helyen történik; és minden ilyen esetben a
főnévvel együtt az ige ragozása is módosul („felveszed” helyett
„felveszel” – tárgyas helyett alanyi ragozást kap – stb.). Ha
a hivatkozás az ablakban irányított szereplőre vonatkozik, az
ő neve helyett a „te” névmás megfelelően ragozott alakjai („téged”,
„neked”… stb.) jelennek meg; és hasonlóképpen bánik a „maga” (ha
a cselekvő egyezik a tárggyal) ill. a „magad” (ha mindhárom főnév
– tehát a cselekvő, a szemlélő és a tárgy – is ugyanaz) személyes
névmásokkal is. Külön érdekessége a játéknak, hogy az eredeti
regényben szereplő varázslatos Gyűrű az őt viselőnek láthatatlanságot
ad – ez a szereplő tehát egyszerűen bármit megtehet a játékban
anélkül, hogy a többieket erről értesítenie kéne. (Következésképpen
bizonyos akadályokon is átmehet, pl. ha valahol egy őrszem senkit
nem enged át, ott őneki akkor is szabad bejárása kell legyen,
mert nem láthatják, amikor áthalad; vagy elveheti bárkitől a nála
levő tárgyakat stb.)
A
végrehajtást követően a vezérlő főprogram minduntalan visszatér
a beviteli-megjelenítési végtelen főciklusba, ahonnan csak az
egyes szereplőknek adott következő órajelek mozdítják majd ki
a programot. (Kicsit emlékeztet ez a működés a Windows eseménykezelőire:
mindig a kívánt esemény bekövetkezte indítja el a hozzá tartozó
lekezelő eljárást.)