Programozástechnika: Hogyan írjunk játékot ZX Spectrumra...

5 csillagos Cikk értékelése: 5,00 (14 szavazatból)

A Sinclair.hu egy igen fontos küldetésének tesz eleget azzal, hogy útjára indítja az alábbi cikksorozat fordítását. Magyarul mindenképp hiánypótló írás mindamellett reméljük, hogy talán többen kapnak kedvet egy kicsit kódolni z80 assembly-ben, netalántán Spectrum programok írására adják a fejüket. De még ha nem is lepik el Spectrum játékprogramozók az utcákat, akik elolvassák majd e sorokat, betekintést nyerhetnek a Spectrum játékprogramozás alapjaiba annó és most.

Természetesen mindezt Jonathan Cauldwell engedélyével tesszük.
"Hi, Yes, feel free to translate and publish a Hungarian version. The more people who read it, the better. :) Jonathan."


Tartalom
1. fejezet - Egyszerű szöveg és grafika

Bevezetés
Szóval végigolvastad a Z80-as dokumentációját, tudod, miként befolyásolják az utasítások a regisztereket, és most szeretnéd végre felhasználni ezt a felbecsülhetetlen értékű tudást? Abból ítélve, hány e-mailt kapok olyan témákban, mint például: hogyan kell a billentyűzetről beolvasni, miként lehet a képernyőcímeket kiszámolni, vagy kiszűrni a statikus zajt a pittyegőből, világossá vált, hogy egyáltalán nincs bőségesen eleresztve felhasználható források tekintetében az újdonsült Spectrum programozó. Azt remélem, hogy ez a dokumentum képes lesz betölteni ezt az űrt a gyarapodása során. Jelen állapotában még évek vannak hátra, amíg teljes egészében elkészül, de a már eddig megírt néhány fontos fejezet publikálása remélhetőleg jó szolgálatot tesz más programozóknak.

A ZX Spectrum 1982 áprilisában indult útjára, bár mai mércével mérve egy primitív kis masinának tűnik. Az Egyesült Királyságban és néhány más országban a 80-as évek legnépszerűbb játékgépe volt, és az emulálás lehetőségének köszönhetően sokaknak lehet részük egy örömteli és nosztalgikus utazásban fiatalkoruk játékainak köszönhetően. Mások még csak most kezdték meg a gép felfedezését, és néhányan belevágnak az embert próbáló feladatba, hogy játékszoftvert írjanak erre az egyszerű kis számítógépre. Mindemellett, ha képes vagy elkészíteni egy becsületes gépi kódú játékprogramot egy 1980-as évekbeli számítógépre, akkor valószínűleg kevés feladat van, amivel ne lennél képes megbirkózni.
A maximalisták valószínűleg nem fogják kedvelni ezt a cikksorozatot, ugyanis szerintem ha az ember játékprogramot ír, akkor nem az a célkitűzése, hogy tökéletes Z80 kódot állítson elő - mintha egyáltalán létezne ilyen. Egy Spectrum játék megírása jelentős vállalkozás, és soha nem fogsz egyről a kettőre jutni, ha túlságosan megszállott vagy, és minden áron a legjobb pontozó mechanizmust vagy billentyűzet olvasó algoritmust akarod elkészíteni. Ha elkészült egy rutin, ami kiszolgálja a feladatát és nem okoz problémát máshol, haladj tovább a következőre! Nem számít, ha kissé kacifántos, vagy nem annyira hatékony, mert a lényeges dolog az, hogy a játékmenet jól működjön. Épeszű ember nem fogja visszafejteni a kódodat és hibákat keresni benne!

A cikkek ebben a sorozatban oly módon kerültek elrendezésre, hogy mihamarabb belevághasson érdemben az olvasó egy egyszerűbb játék írásába. Semmi sem múlja felül azt az izgalmat, amikor az ember az első saját, teljesen gépi kódú játékán munkálkodhat. Ezért úgy készítettem el ezt a leírást, hogy az első néhány fejezet lefedje az ehhez minimálisan szükséges tudásanyagot. Ezután továbbhaladunk az összetettebb technikák felé, hogy javítsunk az általunk írandó játékok minőségén.

A cikksorozat megírása során számos feltételezéssel fogok élni. Kezdetnek feltételezem, hogy az olvasó ismeri a Z80-as műveleti-kódok zömét, és tisztában van a működésükkel. Amennyiben ez nem így lenne, számos leírás fellelhető, amelyek sokkal jobban és részletesebben foglalkoznak a témával, mint ami itt helyet kaphatna. A gépi kódú utasítások elsajátítása egyáltalán nem nehéz feladat, ám értelmes módon történő összefűzésük igényel némi leleményességet. A load (ld) - betöltés, compare (cp) - összehasonlítás, és a conditional jump (jp z / jp c / jp nc) - feltételes ugró utasítások megismerése kíváló kezdet. A többi majd magától a helyére kerül, ha ezekkel tisztában van az ember.

Eszközök
Napjainkban már sokkal kifinomultabb hardverrel rendelkezünk, és nem vagyunk arra kényszerülve, hogy ugyanazon a gépen fejlesszük a szoftvert, mint amelyiken majd futni fog. Számos remek cross-assembler program létezik, amelyek lehetővé teszik, hogy PC-n fejlesszük a Spectrumra szánt programot, majd az előállított futtatható fájlokat importáljuk egy emulátorba – erre alkalmas a SPIN nevű népszerű emulátor, amely többek között ilyen funkcióval is rendelkezik.

A grafikát illetően én egy SevenUp nevű eszközt használok, és nyugodt szívvel ajánlok mindenkinek. Bittérképek konvertálását teszi lehetővé Spectrum képformátumra, valamint lehetőséget ad a programozónak a sprite-ok vagy más grafikai elemek sorrendjének meghatározására. A kimenet lehet bináris kép, vagy forráskód is. Egy másik hasonlóan népszerű program a TommyGun.

A zenét illetően a SoundTracker segédprogramot ajánlanám, amely letölthető a World of Spectrum archívumából. Továbbá szükség lesz egy különálló fordító programra is. Ne feledkezzünk meg róla, hogy ezek Spectrum programok, nem pedig PC-s eszközök, amelyeket emulátorban kell futtatni! Szerkesztők és cross-compilerek tekintetében sajnos nem tudok felvilágosítást adni a legújabb kellékeket illetően, ugyanis én 1985-ben írt archaikus Z80 Macro cross-assemblert és szerkesztőt használok, amelyek még DOS-os ablakokban futnak. Egyiket sem javasolnám. Amennyiben tanácsra van szükséged, hogy mely eszközök felelnének meg a legjobban az elvárásaidnak, javaslom a World of Spectrum development forum böngészését. Ez a kedves közösség igen nagy tapasztalattal rendelkezik, és roppant segítőkész.

Személyes megjegyzések
Az elmúlt jó néhány év alatt amióta Spectrum programokat írok, számos, elsőre különös szokást vettem fel. Például az általam használt koordináta rendszer sem igazodik a matematikai konvenciókhoz. Ehelyett, gépi kódú programjaimban inkább a Sinclair BASIC PRINT AT x,y utasítás szabványát követem, ahol x a karakterek vagy pixelek számát jelöli a képernyő tetejétől számolva, y pedig a karakterek/pixelek száma a kép bal szélétől. Ha ez elsőre zavarónak tűnik, előre is elnézést kérek érte. Valahogy mindig is logikusabbnak tűnt ez a fajta viszonyítási mód. Néhány általam használt megoldás szokatlannak tűnhet. Amennyiben bárhol jobb ötleted van, nyugodtan használd a saját változatodat!
Még egy dolog: megjegyzéseket írni a kódhoz menet közben igen fontos, ha nem elengedhetetlen! Egy annotálatlan rutinban pokolian nehéz megtalálni a hibát, még akkor is, ha az csupán néhány héttel ezelőtt íródott. Unalmasnak tűnhet minden egyes megírt szubrutin dokumentálása, ám a munka egészét tekintve mégis lecsökkenti a fejlesztési időt. Amennyiben a jövőben szeretnéd újra felhasználni az eljárást egy másik játékban, a megfelelően elhelyezett kommentek nagyban megkönnyítik a rutin kiemelését és a következő projektben történő felhasználását, átalakítását.
Mindezek mellett jó szórakozást kívánok! Amennyiben észrevételed van, vagy esetleg hibát véltél felfedezni, lépj velem kapcsolatba!

Jonathan Cauldwell, 2007 januárja.

Hello World
Az első BASIC program, amit a kezdő programozó megír, általában az alábbi két sorból áll:

10 PRINT "Hello World"
20 GOTO 10

Rendben, esetleg a szöveg más lehetett. Talán az első próbálkozásod a „Gyurika okos”, vagy a „Bali itt járt” volt, de nézzünk szembe a tényekkel: szöveg és grafika megjelenítése a képernyőn talán a legfontosabb aspektusa bármely számítógépes játék megírásának, valamint – a flipper és félkarú rabló játékoktól eltekintve – ezek nélkül egy valamire való játék elkészítése elképzelhetetlen. Ennek megfelelően, talán kezdjük ezt az ismertetőt néhány fontos, a Spectrum ROMban lévő megjelenítő rutinnal.
Hogyan is végezhetjük el a fenti program gépi kódra történő átírását? Nos, például PRINT-elhetünk az RST 16 utasítással – tulajdonképpen ez a PRINT CHR$ A megfelelője -, azzal a különbséggel, hogy az akkumulátorban lévő karaktert írja ki az aktuális csatornára. Ahhoz, hogy egy karakterláncot írjunk ki a képernyőre, két rutin meghívása szükséges – egy a felső képernyőrész írásra történő megnyitásához (channel 2), utána pedig a második a karakterlánc képernyőre viteléhez. Az 5633-as ROM-címen lévő rutin megnyitja az akkumulátorban meghatározott számú csatornát, a 8252-es pedig kiírja a DE címen kezdődő BC hosszúságú szöveget az imént megnyitott csatornára. Miután a 2-es csatornát megnyitottuk, minden kiírás a felső képernyőrészen kerül kijelzésre egészen addig, amíg újra meghívjuk az 5633-as rutint egy másik csatornaszámmal, oda irányítva a további kijelzést. További fontos csatornaszámok az 1-es csatorna, amely a képernyő alsó részét reprezentálja (hasonlóan a PRINT #1 BASIC utasításhoz, illetve ennek segítségével írhatunk az alsó két sorba is), valamint a 3-as, a ZX Printer számára.

 
; Példa 1.1

	ld	a, 2		; felső képernyő
	call	5633		; csatorna nyitás
loop	ld	de, string	; szöveg kezdete
	ld	bc, eostr-string; kiírandó szöveg hossza
	call	8252		; szöveg kiírása
	jp	loop		; ismétlés, amíg megtelik a képernyő

string	defb '(A neved) okos'
eostr	equ $

Lefuttatva a fenti programot, addig írja a megadott szöveget a képernyőre, amíg a scroll? meg nem jelenik alul. Hamar észre fogod venni, hogy a BASIC változattól eltérően, ahol minden mondat külön sorban jelenik meg, a következő karakterlánc közvetlenül az előtte lévő szöveg utolsó karaktere után kezdődik, ami némiképp alul marad elvárásainkhoz képest. Hogy valóra váltsuk elképzelésünket, magunknak kell új sort kezdenünk egy ASCII vezérlő kód segítségével. Az egyik változat, hogy betöltjük az akkumulátorba a soremelés kódját (13), és az RST 16–ot használva kiírjuk ezt az utasítást. Egy másik hatékonyabb megoldás, ha hozzácsapjuk ezt az ASCII kódot a karakterláncunk végéhez, ekképpen:

 
; Példa 1.2

string	defb '(A neved) okos'
	defb 13
eostr	equ $

Számos ASCII vezérlő utasítás van, amelyek befolyásolják a kiírás helyét, színét, stb… Érdemes mindenkinek magának kikísérleteznie, melyeket tartja a leghasznosabbaknak. Én személy szerint az alábbiakat használom a leggyakrabban:
13 NEWLINE: a következő sor elejére állítja a következő kiírás helyét.
16,c INK: a tinta színét c értékére állítja.
17,c PAPER a papír színét c-re állítja.
22,x,y AT: a következő kijelzés helyét az x,y koordinátára állítja.
A 22-es kód rendkívül hasznos utasítás. Segítésével beállíthatjuk a soron következő karakter vagy grafikai elem kijelzésének pozícióját. A következő példa egy felkiáltójelet jelenít meg a képernyő jobb alsó szegletében:

 
; Példa 1.3

	ld	a, 2		; felső képernyő
	call	5633		; csatorna nyitása
	ld	de, string	; szöveg címe
	ld	bc, eostr-string; szöveg hossza
	call	8252		; szöveg megjelenítése
	ret
	
string	defb 22,21,31,'!'
eostr 	equ $

A következő program egy lépéssel tovább lép: egy csillagocskát mozgat a képernyő aljától a tetejéig:

 
; Példa 1.4

	ld	a, 2		; 2 = felső képernyő
	call	5633		; csatorna nyitás
	ld	a, 21		; 21. sor = a képernyő alja
	ld	(xcoord), a	; kezdeti x koordináta
loop	call	setxy		; x/y koordináta beállítása
	ld	a, '*'		; ide egy csillag
	rst	16		; megjelenítés
	call	delay		; várakozunk
	call	setxy		; x/y koordináta beállítása
	ld	a, 32		; szóköz ASCII kódja
	rst	16		; régi csillag törlése
	call	setxy		; x/y koordináta beállítása
	ld	hl, xcoord	; függőleges pozíció
	dec	(hl)		; egy sorral feljebb
	ld	a, (xcoord)	; hol is van?
	cp	255		; elhagytuk már a képernyő tetejét?
	jr	nz, loop	; nem, folytassuk
	ret
delay 	ld	b, 10		; várakozás hossza
delay0	halt			; megszakításra várakozás
	djnz	delay0		; ciklus
	ret			; visszatérés
setxy 	ld	a, 22		; AT ASCII kódja
	rst	16		; kiküldés
	ld	a, (xcoord)	; függőleges pozíció
	rst	16		; kiírás
	ld	a, (ycoord)	; y koordináta
	rst	16		; kiírás
	ret

xcoord	defb 0
ycoord	defb 15

Egyszerű grafika megjelenítése
Csillagokat mozgatni a képernyőn keresztbe-hosszába nagyon szuper dolog, de még a legegyszerűbb játéknak is tartalmaznia kell grafikai elemeket. A fejlettebb grafikáról a későbbi fejezetekben értekezünk, most csupán egyszerű Space Invader jellegű grafikát fogunk használni. Bármely BASIC programozó lehet a megmondhatója, a Spectrum egy igen egyszerű mechanizmussal rendelkezik ennek megvalósításához – ez a User Defined Graphic (Felhasználói Grafika), röviden UDG.
A Spectrum ASCII kódtáblája tartalmaz 21 (128K-os üzemmódban 19) felhasználó által definiálható grafikus karaktert, a 144-es kódtól kezdődően a 164-es (128K esetén 162-es) kódig. BASIC-ben az UDG úgy adható meg, ha "belepoke-oljuk" az adatot a RAM tetején található UDG területre, ám gépi kód esetén sokkal elegánsabb, ha megváltoztatjuk a rendszerváltozó értékét, amelyik az UDG-t tartalmazó memória területre mutat. Ezt úgy tehetjük meg, ha átírjuk a 23675-ös címen található kétbájtos értéket.
Itt az ideje, hogy módosítsuk előző programunkat, hogy egy mozgó csillag helyett valamilyen grafikát jelenítsen meg. Ezt az alább aláhúzott néhány változtatással tehetjük meg:

 
; Példa 1.5

	ld	hl, udgs	; UDG-k
	ld	(23675), hl	; UDG rendszerváltozó beállítása
	ld	a, 2		; 2 = felső képernyő
	call	5633		; csatorna nyitás
	ld	a, 21		; 21. sor = a képernyő alja
	ld	(xcoord), a	; kezdeti x koordináta
loop	call	setxy		; x/y koordináta beállítása
	ld	a, 144		; UDG megjelenítése csillag helyett
	rst	16		; megjelenítés
	call	delay		; várakozunk
	call	setxy		; x/y koordináta beállítása
	ld	a, 32		; szóköz ASCII kódja
	rst	16		; régi karakter törlése
	call	setxy		; x/y koordináta beállítása
	ld	hl, xcoord	; függőleges pozíció
	dec	(hl)		; egy sorral feljebb
	ld	a, (xcoord)	; hol is van?
	cp	255		; elhagytuk már a képernyő tetejét?
	jr	nz, loop	; nem, folytassuk
	ret
delay	ld	b, 10		; várakozás hossza
delay0	halt			; megszakításra várakozás
	djnz	delay0 		; ciklus
	ret			; visszatérés
setxy 	ld	a, 22		; AT ASCII kódja
	rst	16		; kiküldés
	ld	a, (xcoord)	; függőleges pozíció
	rst	16 		; kiírás
	ld	a, (ycoord)	; y koordináta
	rst	16		; kiírás
	ret
	
xcoord	defb	0
ycoord	defb	15
udgs	defb	60,126,219,153
	defb	255,255,219,219

Ahogyan Rolf Harris mondta: "Ráismersz már, mi ez?"

Természetesen semmi sem gátol meg bennünket abban, hogy 21 saját grafikai elemnél többet használjunk, ha úgy tetszik. Egyszerűen létre kell hoznunk a memóriában UDG-k 21-es csoportjait, és a rendszerváltozóval arra a csoportra mutatni, amelyikre éppen szükségünk van.
Egy szóba jövő másik módszer a karakter készlet átdefiniálása. Ez az ASCII karakterek nagy skáláját adja 32-től (SZÓKÖZ) 127-ig (© szimbólum). Ha úgy tartja kedved, keverheted is a szöveget a grafikával, átalakítva a betűkészlet betűit és számait tetszés szerint, majd UFOkat, zombikat, vagy ezekhez hasonló alakzatokat alakíthatsz ki a kisbetűkből és szimbólumokból, bármit ami a játékhoz szükséges. Ahhoz, hogy egy másik fontkészletet válasszunk ki, kivonunk 256-ot a font kezdőcíméből, és ezt a számot helyezzük el a 23606-os kétbájtos memóriacímen. Az alapértelmezett Sinclair font például a 15616-os ROM címen található, ennek megfelelően a gép bekapcsolásakor a 23606-os címről a 15360-as érték olvasható ki.
Az alábbi kód átmásolja a Sinclair ROM betűkészletet a RAM-ba, közben félkövérré alakítva azt, majd beállítja a rendszerváltozót, hogy a memóriában lévő betűkészletre mutasson:

 
; Példa 1.6

	ld	hl, 15616	; ROM font
	ld	de, 60000	; a saját fontunk címe
	ld	bc, 768		; megváltoztatandó 8 sor * 96 karakter
font1	ld	a, (hl)		; bitkép A-ba
	rlca			; balra forgatás
	or	(hl)		; a két kép kombinációja
	ld	(de), a		; beírás az új fontba
	inc	hl		; következő bájt a régiből
	inc	de		; következő bájt az újból
	dec	bc		; számláló csökkentése
	ld	a, b		; magas bájt
	or	c		; kombinációja az alacsony bájttal
	jr	nz, font1	; ismétlés amíg bc=zero lesz
	ld	hl, 60000-256	; font mínusz 32*8
	ld	(23606), hl	; új font cím beírása
	ret

Számok kiírása
A legtöbb játékban érdemes a játékos elért pontját ASCII számkódok láncaként definiálni, annak ellenére, hogy ez többletmunkát jelent a pontozórutin számára, valamint nagyobb pontozótábla kezelése igen nagy fejfájást jelenthet a tapasztalatlan assembly programozónak. Ezt a technikát egy későbbi fejezetben mutatjuk be, most egyelőre praktikus ROM rutinokat fogunk felhasználni a számok kiírásához.
Két módszert használhatunk a számok képernyőre viteléhez: az elsőben segítségül hívjuk ugyanazt az eljárást, amelyet a ROM használ a Sinclair BASIC sorszámok megjelenítéséhez. Ehhez egyszerűen betöltjük a megjeleníteni kívánt számot a BC regiszterpárba, majd meghívjuk a 6683-as címen található rutint:

 
	ld	bc, (pont)
	call	6683

Mivel a BASIC sorok csupán 9999-ig számozódhatnak, ennek a módszernek megvan az a hátránya, hogy csak maximum négyjegyű számot lehet vele megjeleníttetni. Amint a játékos pontszáma eléri a 10000-et, más ASCII karakterek kerülnek kijelzésre számok helyett. Szerencsére létezik egy másik metódus, amely sokkal nagyobb szabadságot ad. Ahelyett, hogy a sorszámot megjelenítő rutint hívnánk, rakassuk a BC regiszter tartalmát a kalkulátor verembe, majd futtassuk azt a kódot, amelyik az ennek a veremnek a tetején lévő szám kijelzéséért felelős. Ne fájjon most a fejünk amiatt, mi is lehet az a kalkulátor verem, vagy mire szolgál, ugyanis vajmi kevés haszna van egy árkád-játék programozó számára, ellenben ahol csak hasznát vehetjük, felhasználjuk. Elég azt megjegyeznünk, hogy az alábbi sorok egy számot jelenítenek meg a 0 és 65535 zárt intervallumból:

 
	ld	bc, (pont)
	call	11563		; BC tartalma a verembe
	call	11747		; kalk. verem tetejének kijelzése

Színek Megváltoztatása
Ahhoz, hogy megváltoztassuk a mindenkori tinta, papír, világosság és villogás értékeket, közvetlenül módosíthatjuk a 23693-as címen található rendszerváltozót, majd törölhetjük a képernyőt egy ROM hívással:

 
; Citromságra képernyőt szeretnénk.
	ld	a, 49		; kék tinta (1) sárga papíron (6*8)
	ld	(23693), a	; színek beállítása
	call	3503		; képernyő törlése

A keretszín megváltoztatásának legegyszerűbb és leggyorsabb módja, ha a 254-es portra írunk. A kiküldött bájt 3 legalacsonyabb bitje határozza meg a színt. A keret pirosra színezése a következőképpen valósítható meg:

 
	ld	a, 2		; 2 a piros szín kódja
	out	(254), a	; kiküldése a 254-es portra

Szintén a 254-es port vezérli a hangszóró- és mikrofonaljzatot a 3-as és 4-es biten. Sajnos a keret csak addig őrzi meg az általunk beállított színt, amíg nem intézünk hívást a ROM-ban lévő csipogó-rutinhoz (erről bővebben később), így egy maradandóbb megoldás után kell néznünk. Nem kell mást tennünk, mint betölteni az akkumulátorba a kívánt szín kódját, majd meghívni a 8859-es ROM műveletet. Ez megváltoztatja a keret színét és megfelelően beállítja a BORDCR változót a 23624-es címen. Állítsunk be maradandó piros keretszínt:

 
	ld	a, 2		; 2 a piros szín kódja
	call	8859		; keretszín beállítása

2. Fejezet - Billentyűzetes és botkormány irányítás
Egyszerre csak egy gombot!
Amennyiben nem kapcsoltad ki vagy babráltál más módon a Spectrum alapértelmezett megszakítás-módjával, a ROM másodpercenként ötvenszer automatikusan olvassa a billentyűzetet és frissít több rendszerváltozót a 23552-es memóriacímen. A legegyszerűbb módszer egy billentyű megnyomásának beolvasására, ha először a 23560-as címre nullát töltünk, majd folyamatosan figyeljük az itt található értéket, amíg nullától különböző értéket nem találunk ott. Az ekkor olvasott szám a leütött billentyű ASCII kódja. Ezt a megoldást leginkább a "Nyomj meg egy gombot a folytatáshoz" szituációkban tudjuk felhasználni, menüpontok kiválasztásánál, vagy a játékos nevének bekéréséhez az elért pontszám rögzítésekor. Egy ilyen rutin a következőképpen nézne ki:

; Példa 2.1

	ld	hl, 23560	; LAST K rendszerváltozó címe
	ld	(hl), 0		; nullázzuk
loop	ld	a, (hl)		; LAST K új értéke
	cp	0		; továbbra is 0?
	jr	z, loop		; ha igen, nem történt bill. leütés

	ret			; volt gombnyomás

Billentyűkombinációk
Egyszerre csupán egyetlen billentyű leütése igen ritka, főleg gyors árkád játékok esetében, így igen hasznos volna, ha képesek lennénk egyszerre több billentyű lenyomását is észlelni. Ezen a ponton válik a dolog egy kicsit trükkösebbé. Memóriacímek olvasása helyett nyolc portról kérhetünk be adatot, amelyek mindegyikéhez egy 5 billentyűből álló sor tartozik. Természetesen, a legtöbb Spectrum modell ennél sokkal több billentyűvel rendelkezik, szóval joggal tehetjük fel a kérdést: hova tűnt a többi gomb? Nos, tulajdonképpen nem tűntek el, mert igazából ott sem voltak. Az eredeti Spectrum billentyűzet kiosztás csupán 40 billentyűből állt, ötös csoportokba szervezve – nyolc csoportot alkotva így. Bizonyos funkciók eléréséhez a gombok egy meghatározott kombinációját kellett megnyomni – például a törléshez a CAPS SHIFT és 0 billentyűket. Sinclair 1985-ben a Spectrum Plus színrelépésekor adta hozzá a billentyűzethez az új billentyűket. Működésük abban áll, hogy az általuk nyújtott funkciók eléréséhez az eredeti gumi-billentyűs modell billentyűkombinációinak leütését szimulálják.
Az eredeti billentyűzetkiosztás az alábbi csoportokból állt:

PortGombok
32766B, N, M, Symbol Shift, Space
49150H, J, K, L, Enter
57342Y, U, I, O, P
614386, 7, 8, 9, 0
634865, 4, 3, 2, 1
64510T, R, E, W, Q
65022G, F, D, S, A
65278V, C, X, Z, Caps Shift

Hogy kiderítsük, melyik billentyűk vannak éppen lenyomva, beolvassuk az adatot a megfelelő portról, amelyben a sorban mind az öt billentyűhöz egy bit van megfeleltetve a d0-d4 alsó bitek közül (értékük 1,2,4,8 és 16). A d0-ás bit felel meg a külső billentyűnek, a d4-es a legbelsőnek. Furcsamódon, a bitek magas értékűek, ha a hozzájuk tartozó gomb nincsen lenyomott állapotban, és alacsonyak ha a gombot nyomva tartjuk éppen – pontosan fordított működést produkálva, mint amire számítanánk.

Hogy ellenőrizzük valamelyik sorban a billentyűk állapotát, egyszerűen betöltjük a sorhoz tartozó port számát a BC regiszterpárba, majd végrehajtjuk az IN A,(C) utasítást. Mivelhogy csupán az alsó bitek értékére vagyunk kíváncsiak, figyelmen kívül hagyhatjuk a többit, a számunkra érdektelen biteket kinullázva egy AND 31 utasítással, vagy a szignifikánsakat kiforgatva az akkumulátorból az átviteli regiszterbe a vizsgálathoz, öt egymás után kiadott RRA:CALL C,(cím) utasítással.
Amennyiben az imént ismertetett módszer nehezen érthető, tekintsük a következő példát:

; Példa 2.2

	ld	bc, 63486	; 1-5 billentyűk/joystick port 2
	in	a, (c)		; lássuk a lenyomott billentyűket
	rra			; legkülső bit = 1-es gomb
	push	af		; megjegyezzük az értéket
	call	nc, mpl		; ha megnyomva, mozgatás balra
	pop	af		; akkumulátor visszaállítása
	rra			; következő bit (2-es érték) = 2-es gomb
	push	af		; megjegyezzük az értéket
	call	nc, mpr		; ha megnyomva, mozgatás jobbra
	pop	af		; akkumulátor visszaállítása
	rra			; következő bit (4-es érték) = 3-as gomb
	push	af		; megjegyezzük az értéket
	call	nc, mpd		; ha megnyomva, mozgatás lefelé
	pop	af		; akkumulátor visszaállítása
	rra			; következő bit (8-es érték) = 4-es gomb
	call	nc, mpu		; ha megnyomva, mozgatás felfelé

Botkormányok
A Sinclair borkormányok 1-es és 2-es portja egyszerűen a billentyűzet két számcsoportjának lett megfeleltetve. Erről könnyedén megbizonyosodhatunk: a BASIC szerkesztőbe belépve a joystick-okat mozgatva számok fognak megjelenni. A Port 1 (Interface 2) hozzárendelései a 6,7,8,9 és 0 gombok, a Port 2-é (Interface 1) pedig az 1,2,3,4 és 5. A botkormány mozgatásának észleléséhez ugyanúgy olvassuk a megfelelő portot, ahogyan azt a billentyűzet esetében tettük. A Sinclair botkormányok a 63486-os (Interface 1/port 2), és a 61438-as (Interface 2/port 1) portokat használják, ahol a d0-d4 bitek értéke 0 megnyomott állapotba, 1-es nyugalmi helyzetben.

A népszerű Kempston joystick formátum nincsen társítva a billentyűzethez, hanem a 31-es port figyelésével lehet kiolvasni az éppen aktuális állapotot. Ez azt jelenti, hogy mindösszesen egy egyszerű IN A, (31) utasítást kell használnunk. Ismételten, a d0-d4-es bitek jelölik az állapotot, ám ezúttal a bitek jelentése aszerint alakul, ahogyan azt eredetileg vártuk: a magas bit érték jelöli a botkormány kimozdítását valamelyik irányba.

 
; Példa 2.3
; Példa botkormány vezérlő rutin

joycon	ld	bc, 31		; Kempston joystick port
	in	a, (c)		; bemenet olvasása
	and	2		; "balra" bit ellenőrzése
	call	nz, joyl	; mozgás balra
	in	a, (c)		; bemenet olvasása
	and	1		; "jobbra" bit teszt
	call	nz, joyr	; mozgás jobbra
	in	a, (c)		; bemenet olvasása
	and	8		; "felfelé" bit
	call	nz, joyu	; mozgás felfelé
	in	a, (c)		; bemenet olvasása
	and	4		; "lefele" irány
	call	nz, joyd	; mozgás lefelé
	in	a, (c)		; bemenet olvasása
	and	16 		; tűzgomb ellenőrzése
	call	nz, fire	; tüzelés

Egy egyszerű játék
Tegyük is meg a következő lépést, és az eddig tanultakat átültetve a gyakorlatba, írjuk meg egy egyszerű játék fő irányító részét! Ez lesz az alapja egy Százlábú játéknak, amit az elkövetkezendő néhány fejezet alatt fogunk fejleszteni. Még nincs birtokunkban minden szükséges tudás egy ilyen játékprogram elkészítéséhez, de azért el tudjuk kezdeni egy kis irányítási hurok megírásával, amely lehetővé teszi a játékos számára egy kis törzs irányítását a képernyőn. Vigyázat! A program nem tartalmaz kilépési lehetőséget a BASIC-be, ezért mindenképpen mentsük el a forrásszöveget futtatás előtt!

; Példa 2.4

; Fekete képernyőt szeretnénk

	ld	a, 71		; fehér tinta (7) fekete papíron (0),
				; világosan (64)
	ld	(23693), a	; képernyő színek beállítása
	xor	a		; akkumulátor nullázásának gyors módja
	call	8859		; maradandó keretszín

; Grafika beállítása

	ld	hl, blocks	; felhasználói grafikus elemek címe
	ld	(23675), hl	; az UDG ide mutasson

; Rendben, kezdődjön a játék!

	call	3503		; ROM rutin – képernyő törlése, 
				;2-es csat. nyitása

; Koordináták inicializálása

	ld	hl, 21+15*256	; kezdeti koordináták a HL-be
	ld	(plx), hl	; játékos koordinátái

	call	basexy		; x és y pozíciójának beállítása
	call	splayr		; játékos törzsének megjelenítése

; Ez a főhurok

mloop	equ $

; Játékos törlése

	call	basexy		; x és y pozíciójának beállítása
	call	wspace		; üres hely a játékos pozíciójába

; Törölve van a játékos, átmozgathatjuk az új pozícióba mielőtt újra
; megjelenítjük

	ld	bc, 63486	; billentyűk 1-5/joystick port 2
	in	a, (c)		; kiolvassuk a megnyomott gombokat
	rra			; legkülső bit = 1-es gomb
	push	af		; megjegyezzük
	call	nc, mpl		; ha megnyomva, mozgás balra
	pop	af		; akku helyreállítása
	rra			; következő bit (2-es helyiérték)
				; = 2-es gomb
	push	af		; megjegyezzük
	call	nc, mpr		; ha megnyomva, mozgás jobbra
	pop	af		; akku helyreállítása
	rra 			; következő bit (4-es helyiérték)
				; = 3-es gomb
	push	af		; megjegyezzük
	call	nc, mpd		; ha megnyomva, mozgás lefelé
	pop	af		; akku helyreállítása
	rra 			; következő bit (8-es helyiérték)
				; = 4-es gomb
	call	nc, mpu		; ha megnyomva, mozgás felfelé

; Az átmozgatás megtörtént, újra megjeleníthetjük a játékost

	call	basexy		; x és y koordináta beállítása
	call	splayr		; játékos megjelenítése

	halt			; várakozás

; Visszaugrás a főhurok elejére

	jp	mloop

; Játékos balra mozgatása

mpl	ld	hl, ply		; Emlékezzünk, 
				; y a vízszintes koordináta!
	ld	a, (hl		; Mi a mostani érték?
	and	a		; Nulla?
	ret	z		; Ha igen, 
				; nem tudunk tovább balra menni!
	dec	(hl)		; különben y = y-1
	ret

; Játékos jobbra mozgatása.

mpr	ld	hl, ply		; Emlékezzünk, 
				; y a vízszintes koordináta!
	ld	a, (hl)		; Mi a mostani érték?
	cp	31		; A jobb szélén vagyunk (31)?
	ret	z		; Ha igen, 
				; nem tudunk tovább jobbra menni!
	inc	(hl)		; különben y = y+1
	ret

; Játékos felfelé mozgatása.

mpu	ld	hl, plx		; Emlékezzünk, 
				; x a függőleges koordináta!
	ld	a, (hl)		; Mi a mostani érték?
	cp	4		; a pálya tetején vagyunk (4)?
	ret	z		; Ha igen, 
				;nem tudunk tovább felfele menni!
	dec 	(hl)		; különben x = x-1
	ret

; Játékos lefelé mozgatása.

mpd	ld	hl, plx		; Emlékezzünk, 
				; x a függőleges koordináta!
	ld	a, (hl)		; Mi a mostani érték?
	cp	21		; A képernyő alján vagyunk (21)?
	ret	z		; Ha igen, 
				; nem tudunk tovább lefele menni!
	inc	(hl)		; különben x = x+1
	ret

; A játékos törzsének, x és y koordináta értékének beállítása,
; ez a rutin kerül meghívásra a törzs törlése és megjelenítése előtt

basexy	ld	a, 22		; AT pozícionáló kód.
	rst	16
	ld	a, (plx)	; játékos függőleges koord.
	rst	16		; beállítjuk
	ld	a, (ply)	; játékos vízszintes koord.
	rst	16		; ezt is beállítjuk
	ret

; Megjelenítjük a játékost a jelenlegi PRINT pozícióban

splayr	ld	a, 69		; cián tinta (5) fekete papíron (0),
				; világosan (64)
	ld	(23695), a	; beállítjuk az ideiglenes színeket
	ld	a, 144		; 'A' UDG ASCII kódja
	rst	16		; játékos kirajzolása
	ret

wspace	ld	a, 71		; fehér tinta (7) fekete papíron (0),
				; világos (64)
	ld	(23695), a	; beállítjuk az ideiglenes színeket
	ld	a, 32		; SZÓKÖZ karakter
	rst	16		; üres hely megjelenítése
	ret

plx	defb	0		; játékos x koordinátája
ply	defb	0		; játékos y koordinátája

; UDG grafika

blocks	defb	16,16,56,56,124,124,254,254	; játékos törzse

Gyors, ugye? Igazából még le is lassítottuk a ciklust egy várakozó utasítással, de még így is 50 kép/másodperces sebességgel száguldunk, ami még mindig egy kicsit gyorsnak bizonyul. Ne aggódjunk, ahogy további funkciókat adunk a kódhoz, lelassítja majd a játékot. Ha érzel magadban elég önbizalmat, megpróbálhatod átalakítani a fenti programot, hogy Kempston botkormánnyal lehessen irányítani! A kivitelezés igazán nem bonyolult, pusztán ki kell cserélni a 63486-os port számot a 31-esre, valamint helyettesíteni a négy call nc,(address) utasítást ezzel: call c,(address). (A bitek jelentése fel van cserélve, emlékszel?)

Az újradefiniálható irányítás kicsit furmányosabb. Ahogyan már említettem, az eredeti Spectrum billentyűzet 8 sorra volt felosztva, mindegyikben 5 billentyűvel. Ahhoz, hogy meghatározzuk, pontosan melyik billentyű is van lenyomva, először beolvassuk a biteket a megfelelő sorhoz tartozó portról, majd a d0-d4-es bitekből meghatározzuk a lenyomott gombot. Amennyiben lecserélnénk a kódban az ld bc,31 utasítást az ld bc,49150 kódrészre, figyelhetnénk a billentyűk lenyomását H-tól az Enterig – bár ez nem nyújtana túl kényelmes újradefiniálási lehetőséget. Szerencsére van más lehetőség is a megvalósításra.

Meghatározhatjuk a billentyűsorok figyeléséhez szükséges portot a Spectrum leírásában szereplő formula segítségével is. A port címét az alábbi képlettel tudjuk meghatározni: 254+256*(255-2^n), ahol n a port száma a 0-7 zárt intervallumból. A 654-es címen található egy ROM-rutin, amely igen sok fáradságos munkát spórolhat nekünk: az E regiszterben visszaadja a lenyomott billentyű sorszámát a 0-39 tartományban. A 0-7 értékek a sorok legbelső gombjainak felelnek meg, (ezek rendre B, H, Y, 6, 5, T, G és V), 8-15 jelöli a következő oszlopnyi billentyűt, és így tovább egészen a legkülső sor 39-es billentyűjéig, ami a CAPS SHIFT. A teljesség kedvéért, a SHIFT billentyű státuszát a D regiszter tartalmazza. Amennyiben egyik gomb sincsen lenyomva, E értéke 255.

Az említett ROM-rutin csupán egyetlen billentyű lenyomását adja vissza, így sajnos nem alkalmas billentyű kombinációk figyelésére. Ahhoz, hogy több lenyomott gomb esetén is megmondhassuk, mely gombok vannak nyomva tartva, magunknak kell meghatároznunk a tesztelni kívánt billentyű portját, kiolvasni az értéket, majd kiértékelni az eredményt a megfelelő bit állapota alapján. Én egy igen hasznos rutint használok a feladat elvégzéséhez, amely az egyetlen eljárás játékaimban, amit nem magam írtam. Az érdem Stephen Jones-é, aki számos kiváló cikket írt a Spectrum Discovery Club számára sok évvel ezelőtt. A kód használatához be kell tölteni az ellenőrizni kívánt billentyű számát az akkumulátorba, meghívni a ktest címet, majd ellenőrizni az átviteli jelzőt (carry flag). Ha történt átvitel, a billentyű nem volt lenyomva. Amennyiben ez a fordított logika zavaró, helyezz el egy CCF utasítást a RET elé!

; Példa 2.5

; Mr. Jones billentyű tesztelő rutinja

ktest	ld	c, a		; billentyű szám A-ba
	and	7		; d0-d2 bitek maszkolása
	inc	a		; 1-8-as tartományba növelés
	ld	b, a		; B-be
	srl	c		; c-t elosztjuk 8-cal,
	srl	c		; hogy megtaláljuk a sor számát
	srl	c
	ld	a, 5		; soronként 5 gomb
	sub	c		; kivonjuk a pozíciót
	ld	c, a		; C-be áttöltjük
	ld	a, 254		; az olvasandó port magas bájtja
ktest0	rrca			; pozícióba forgatás
	djnz	ktest0		; ismétlés amíg releváns sort találunk
	in	a, (254)	; port olvasása 
				; (a=magas bájt, 254=alacsony)
ktest1	rra			; bit kiforgatása az eredményből
	dec	c		; ciklus számláló
	jp	nz, ktest1	; ismételjük amíg a carry-ben lévő 
				; bithez érünk
	ret

3. fejezet - Hanghatások
A hangszóró
Kétféleképpen csalhatunk elő hangokat és zenét a ZX Spectrumból. Ezek közül a tetszetősebb és bonyolultabb út az AY38912 hangchipen keresztül vezet a 128K-s modellekben. Ezt a módszert egy későbbi fejezetben ismertetem részletesen, most a 48K-s gép képességeivel fogunk foglalkozni. Habár egyszerűnek tűnik, mégis rendkívül alkalmas rövid, éles hangeffektek előállítására a játékok színesítésénél.

Beep
Először is azt kell tudnunk, miként tudunk megadott hangmagasságú és hosszúságú hangot generálni. A Sinclair ROM-ban található egy könnyedén használható rutin a 949-es címen, amelyik pont megfelel a célnak. Csupán annyit kell tennünk, hogy betöltjük a hangmagasságnak megfelelő paraméter értéket a HL regiszterpárba, a hosszét a DE-be, majd meghívjuk a 949-et, és már van is egy megfelelő "beep" hangunk!

Sajnos a paraméterek előállításának módszere egy kicsit trükkösebb, ugyanis igényel némi számítgatást. Ismernünk kell a kibocsátandó hang magasságának frekvencia értékét Herz-ben megadva - lényegében ez a szám megfelel annak az értéknek, ahányszor a hangszórónak hangot kell kiadnia egy másodperc alatt, hogy a megfelelő hangmagasságot állítsa elő. Az egyvonalas C hang oktávjának hangjait foglalja össze az alábbi táblázat (# a zenei keresztet jelenti):

Egyvonalas C 261.63
C# 277.18
D 293.66
D# 311.13
E 329.63
F 349.23
F# 369.99
G 392.00
G# 415.30
A 440.00
A# 466.16
B 493.88

(A programba történő könnyebb beilleszthetőség érdekében meghagytam a tizedespontokat. A magyar helyesírásnak megfelelően tizedes vesszőknek kellene szerepelnie a táblázatban – a fordító.)

Hogy egy oktávval magasabban lévő hangot kapjunk, egyszerűen duplázzuk meg a frekvenciát, egy oktávval alacsonyabbhoz felezzük! Például ha kétvonalas C hangot szeretnénk, vegyük az egyvonalas C hanghoz tartozó frekvenciát – 261.63 –, majd szorozzuk meg kettővel: 523.26.

Miután a frekvenciát meghatároztuk, megszorozzuk a kívánt hanghossznak megfelelő értékkel másodpercben kifejezve, és ezt adjuk át a ROM rutinnak a DE regiszterekben. Ennek megfelelően, ha egy egyvonalas C hangot szeretnénk megszólaltatni egy tized másodpercen keresztül, az ehhez szükséges érték 261,63*0,1 = 26. A hangmagasság a következőképpen számítandó ki: 437500/(a frekvencia), majd ebből vonjunk ki 30,125-öt! Ezt az értéket kell átadnunk HL-ben. Egyvonalas C esetében ez a következő: (437500/261,63)-30,125 = 1642. Összefoglalva:

DE = Hossz = Frekvencia * Másodpercek száma
HL = Magasság = (437500 / Frekvencia) - 30.125

(Habár az osztás művelet precedenciája magasabb a kivonásénál, az egyértelműség kedvéért kitettem az implicit zárójelet – a fordító.)

A fentebb leírtaknak megfelelően, az alábbiak szerint tudunk megszólaltatni egy kétvonalas Gisz hangot (G#) 1/4 másodpercig:

; Példa 3.1

; Egyvonalas Gisz frekvenciája = 415.30
; Kétvonalas Gisz frekvenciája = 830.60
; Hossz = 830.6 / 4 = 207.65
; Magasság = 437500 / 830.6 - 30.125 = 496.6

	ld	hl, 497		; magasság
	ld	de, 208		; hossz
	call	949		; ROM beep rutin
	
	ret

; Példa 3.2
	
; Természetesen ez a rutin nem csak zenei hangok 
; megszólaltatására alkalmas - számos hanghatás 
; előidézhető vele! Az egyik kedvencem, 
; egy egyszerű hang-hajlító eljárás:

	ld	hl, 500		; Kezdő hangmagasság
	ld	b, 250		; effekt hossza
loop	push	bc
	push	hl		; magasság tárolása
	ld	de, 1		; nagyon rövid hossz
	call	949		; ROM beep rutin hívása
	pop	hl		; hossz visszaállítása
	inc	hl		; magasság növelése
	pop	bc
	djnz	loop		; ismétlés
	
	ret
Ha időnk engedi, játszadozzunk a fenti rutinnal – elég könnyű a hang magasságát feljebb vagy lejjebb állítani, valamint a kezdő frekvencia, a hossz és a hang hajlításának megváltoztatásával számos érdekes hatást idézhetünk elő. Egy dologra érdemes odafigyelni: ne adjunk meg nagyon ésszerűtlen hangmagasság és hosszúság értékeket, mert a beeper rutin beragadhat, és csak abban az esetben nyerhetjük vissza az irányítást gépünk fölött, ha újraindítjuk!

Fehér Zaj
A hangszóró használata esetén nem szükséges ragaszkodnunk a ROM-ban lévő rutinok használatához. Könnyen elkészíthetjük a saját hanggeneráló algoritmusainkat is, főleg ha csupán statikus zörejt szeretnénk előállítani csattanásokhoz és robbanásokhoz. A fehér zaj előállítása általában izgalmasabb az eddig látottaknál.

Ilyen zörej megszólaltatásához pusztán egy gyors és egyszerű véletlen-szám generátorra van szükségünk. (Egy Fibonacci sorozat éppen kapóra jön, azt javaslom, léptessünk egy mutatót az első 8K-s ROM részen, és olvassuk be időről-időre az aktuális helyen lévő bájtot, hogy megfelelően véletlen 8 bites számot kapjunk.) Ezután írjuk ki ezt az értéket a 254-es portra! Emlékezzünk, hogy ugyanezen a porton keresztül vezérelhetjük a keret színét is, így amennyiben ha el akarjuk kerülni a több színnel csíkozott keret effektust, le kell maszkolnunk a keret-biteket egy AND 248 utasítással, és hozzáadni a megjeleníteni kívánt keret színének a számát az értékhez – 1 a kék, 2 a piros, stb – mielőtt kiadjuk az OUT (254) utasítást. Miután ez megvolt, egy rövid várakozási ciklust kell beiktatnunk – magas hanghoz rövidebbet, mélyebb hanghoz hosszabbat –, és megismételni a műveletet néhány százszor. Így egy kiváló, ütközésre emlékeztető hanghatást kapunk.

Az alábbi rutin egy hangeffekten alapul az Egghead 3-ból:

; Példa 3.3

noise	ld	e, 250		; ismétlés 250-szer
	ld	hl, 0		; kezdő mutató a ROM-ban
noise2	push	de
	ld	b, 32		; lépés mértéke
noise0	push	bc
	ld	a, (hl)		; következő "véletlen" szám
	inc	hl		; mutató
	and	248		; fekete keretet szeretnénk
	out	(254), a	; hangszóróra ki
	ld	a, e		; ahogy e értéke csökken...
	cpl			; ...növeljük a várakozást
noise1	dec	a		; csökkentjük a ciklus számlálót
	jr	nz, noise1	; várakozó ciklus
	pop	bc
	djnz	noise0		; következő lépés
	pop	de
	ld	a, e
	sub	24		; lépés mérete
	cp	30		; tartomány vége
	ret	z
	ret	c
	ld	e, a
	cpl
noise3	ld	b, 40		; csend
noise4	djnz	noise4
	dec	a
	jr	nz, noise3
	jr	noise2
4. fejezet - Véletlen számok

Véletlen számok generálása gépi kódból trükkösebb feladatnak bizonyulhat a gyakorlatlan programozó számára, mint amilyennek elsőre gondolnánk. Először is, tisztázzunk egy fontos tényt: teljesen véletlen számgenerátor, mint olyan, nem létezik. A processzor pusztán utasításokat hajt végre és nem bír önálló akarattal, aminek segítségével véletlen számokkal tudna előállni hasraütés-szerűen. Ennek híján egy előre meghatározott formula segítségével tudja számok előre meg nem jósolható sorozatát készíteni, amelyek látszólag semmilyen mintát nem követnek, így a véletlenszerűség látszatát keltik. Ennek fényében megállapíthatjuk, hogy csupán csak hamis-, vagyis pszeudo-véletlen számokkal kell beérnünk.

Egy elegáns és kézenfekvő módja a pszeudo-véletlen szám generálásának a Fibonacci számsor felhasználása. Mielőtt megrémülnénk, rendelkezésünkre áll egy könnyebb és gyorsabb módszer is 8 bites véletlen számok előállítására Spectrumunkon: léptessünk egy mutatót a ROM címeken, és az éppen aktuális címen tárolt bájtot olvassuk ki. Ennek a megközelítésnek azért van egy hátulütője is: a Sinclair ROM tartalmaz egy meglehetősen egységes és egyáltalán nem véletlenszerű területet a vége felé, amelyet érdemes elkerülnünk. Még abban az esetben is, ha a mutató határait az első 8 KB-nyi ROM-ra korlátozzuk, 8192 „véletlen” számhoz jutunk, amely jóval több, mint amire egy átlagos játékhoz szükségünk lehet. Minden játékom, amelyik véletlen számokkal dolgozik az alábbi, vagy ahhoz nagyon hasonló metódust használ a véletlenszerűség eléréséhez:

; Példa 4.1

; Egyszerű pszeudo-véletlen szám generátor.
; Egy mutatót léptet a ROM területen (a seed-ben tárolva), visszaadva
; a megcímzett bájt tartalmát.

random	ld	hl, (seed)	; Mutató
	ld	a, h
	and	31		; Az első 8 KB-on belül tartjuk
	ld	h, a
	ld	a, (hl)		; "Véletlen" szám a mutatott helyről
	inc	hl		; Mutató léptetése
	ld	(seed), hl
	ret

seed	defw	0

Állítsuk is munkába új véletlen szám generátorunkat a Százlábú játékban! Minden Százlábú játéknak szüksége van gombákra – mégpedig meglehetősen sokra –, szétszórva a játéktérben. Jó szolgálatot fog tenni a fenti rutin a gombák koordinátáinak véletlenszerű meghatározásához. Az aláhúzott részekkel egészítsük ki a programot:

; Példa 4.2

; Fekete képernyőt szeretnénk

	ld	a, 71		; fehér tinta (7) fekete papíron (0),
				; világosan (64)
	ld	(23693), a	; képernyő színek beállítása
	xor	a		; akkumulátor nullázásának gyors módja
	call	8859		; maradandó keretszín

; Grafika beállítása

	ld	hl, blocks	; felhasználói grafikus elemek címe
	ld	(23675), hl	; az UDG ide mutasson

; Rendben, kezdődjön a játék!

	call	3503		; ROM rutin – képernyő törlése, 
				; 2-es csat. nyitása

; Koordináták inicializálása

	ld	hl, 21+15*256	; kezdeti koordináták a HL-be
	ld	(plx), hl	; játékos koordinátái

	call	basexy		; x és y pozíciójának beállítása
	call	splayr		; játékos törzsének megjelenítése

; Feltöltjük a játékteret gombákkal

	ld	a, 68		; zöld tinta (4) fekete papíron (0),
				; világos színnel (64)
	ld	(23695), a	; ideiglenes szín beállítása
	ld	b, 50		; kezdetnek csak pár gomba
mushlp	ld	a, 22		; AT karakter vezérlőkódja
       rst	16
       call	random		; "véletlen" szám generálása
       and	15		; a [0..15] függőleges tartományban
       rst	16
       call	random		; újabb véletlen szám
       and	31		; a [0..31] vízszintes tartományban
       rst	16
       ld	a, 145		; UDG 'B' a gomba grafikája
       rst	16		; kihelyezzük a képernyőre
       djnz	mushlp		; ciklus amíg nem végeztünk a gombákkal

; Ez a főhurok

mloop 	equ $

; Játékos törlése

	call	basexy		; x és y pozíciójának beállítása
	call	wspace		; üres hely a játékos pozíciójába

; Törölve van a játékos, átmozgathatjuk az új pozícióba mielőtt újra
; megjelenítjük

	ld	bc, 63486	; billentyűk 1-5/joystick port 2
	in	a, (c)		; kiolvassuk a megnyomott gombokat
	rra			; legkülső bit = 1-es gomb
	push	af		; megjegyezzük
	call	nc, mpl		; ha megnyomva, mozgás balra
	pop	af		; akku helyreállítása
	rra			; következő bit (2-es helyiérték) 
				; = 2-es gomb
	push	af		; megjegyezzük
	call	nc, mpr		; ha megnyomva, mozgás jobbra
	pop	af		; akku helyreállítása
	rra			; következő bit (4-es helyiérték) 
				; = 3-es gomb
	push	af		; megjegyezzük
	call	nc, mpd		; ha megnyomva, mozgás lefelé
	pop	af		; akku helyreállítása
	rra			; következő bit (8-es helyiérték) 
				; = 4-es gomb
	call	nc, mpu		; ha megnyomva, mozgás felfelé

; Az átmozgatás megtörtént, újra megjeleníthetjük a játékost

	call	basexy		; x és y koordináta beállítása
	call	splayr		; játékos megjelenítése

	halt			; várakozás

; Visszaugrás a főhurok elejére

	jp	mloop

; Játékos balra mozgatása

mpl	ld	hl, ply		; Emlékezzünk, y a vízszintes 
				; koordináta!
	ld	a, (hl)		; Mi a mostani érték?
	and	a		; Nulla?
	ret	z		; Ha igen, nem tudunk tovább 
				; balra menni!
	dec	(hl)		; különben y = y-1
	ret

; Játékos jobbra mozgatása

mpr	ld	hl, ply		; Emlékezzünk, y a vízszintes 
				; koordináta!
	ld	a, (hl)		; Mi a mostani érték?
	cp	31		; A jobb szélén vagyunk (31)?
	ret	z		; Ha igen, nem tudunk tovább 
				; jobbra menni!
	inc	(hl)		; különben y = y+1
	ret

; Játékos felfelé mozgatása

mpu	ld	hl, plx		; Emlékezzünk, x a függőleges 
				; koordináta!
	ld	a, (hl)		; Mi a mostani érték?
	cp	4		; a pálya tetején vagyunk (4)?
	ret	z		; Ha igen, nem tudunk tovább 
				; felfelé menni!
	dec	(hl)		; különben x = x-1
	ret

; Játékos lefelé mozgatása

mpd	ld	hl,plx		; Emlékezzünk, x a függőleges 
				; koordináta!
	ld	a,(hl)		; Mi a mostani érték?
	cp	21		; A képernyő alján vagyunk (21)?
	ret	z		; Ha igen, nem tudunk tovább 
				; lefelé menni!
	inc	(hl)		; különben x = x+1
	ret

; A játékos törzsének, x és y koordináta értékének beállítása,
; ez a rutin kerül meghívásra a törzs törlése és megjelenítése előtt

basexy	ld	a, 22		; AT pozícionáló kód
	rst	16
	ld	a, (plx)	; játékos függőleges koord
	rst	16		; beállítjuk
	ld	a, (ply)	; játékos vízszintes koord
	rst	16		; ezt is beállítjuk
	ret

; Megjelenítjük a játékost a jelenlegi PRINT pozícióban

splayr	ld	a, 69		; cián tinta (5) fekete papíron (0),
				; világosan (64)
	ld	(23695), a	; beállítjuk az ideiglenes színeket
	ld	a, 144		; 'A' UDG ASCII kódja
	rst	16		; játékos kirajzolása
	ret

wspace	ld	a, 71		; fehér tinta (7) fekete papíron (0),
				; világos (64)
	ld	(23695), a	; beállítjuk az ideiglenes színeket
	ld	a, 32		; SZÓKÖZ karakter
	rst	16		; üres hely megjelenítése
	ret

; Egyszerű pszeudo-véletlen szám generátor
; Egy mutatót léptet a ROM területen (a seed-ben tárolva), visszaadva
; a megcímzett bájt tartalmát

random	ld	hl, (seed)	; Mutató
	ld	a, h
	and	31		; Az első 8 KB-on belül tartjuk
	ld	h, a
	ld	a, (hl)		; „Véletlen” szám a mutatott helyről
	inc	hl		; Mutató léptetése
	ld	(seed), hl
	ret
seed	defw	0

plx	defb	0		; játékos x koordinátája
ply	defb	0		; játékos y koordinátája

; UDG grafika

blocks	defb 16,16,56,56,124,124,254,254	; játékos törzse
	defb 24,126,255,255,60,60,60,60		; mushroom
Ha lefuttatjuk a fent listázott programot láthatjuk, hogy már inkább hasonlít egy Százlábú játékra, mint előtte, ellenben van egy apró probléma: ugyan a gombák véletlenszerűen szét vannak szórva a képernyőn, a játékos akadálytalanul képes áthaladni rajtuk. Valamiféle ütközés észlelésre lenne szükség ennek a megakadályozásához. Ezzel fogunk foglalkozni a következő fejezetben.

(Folytatjuk ...)